chromium/chrome/android/javatests/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarControllerTest.java

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.undo_tab_close_snackbar;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import android.widget.TextView;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.ui.test.util.UiRestriction;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

/** Tests for the UndoBarController. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class UndoBarControllerTest {
    @ClassRule
    public static ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
            new BlankCTATabInitialStateRule(sActivityTestRule, true);

    private SnackbarManager mSnackbarManager;
    private TabModel mTabModel;
    private TabGroupModelFilter mTabGroupModelFilter;

    @Before
    public void setUp() throws Exception {
        mSnackbarManager = sActivityTestRule.getActivity().getSnackbarManager();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mSnackbarManager.dismissAllSnackbars();
                });

        mTabGroupModelFilter =
                (TabGroupModelFilter)
                        sActivityTestRule
                                .getActivity()
                                .getTabModelSelector()
                                .getTabModelFilterProvider()
                                .getTabModelFilter(false);
        mTabModel = mTabGroupModelFilter.getTabModel();
    }

    @Test
    @SmallTest
    public void testCloseAll_SingleTab_Undo() throws Exception {
        assertNull(getCurrentSnackbar());
        assertEquals(1, mTabModel.getCount());

        ChromeTabUtils.closeAllTabs(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("Closed about:blank", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(1, mTabModel.getCount());
    }

    @Test
    @SmallTest
    public void testCloseAll_SingleTab_Dismiss() throws Exception {
        assertNull(getCurrentSnackbar());
        assertEquals(1, mTabModel.getCount());

        ChromeTabUtils.closeAllTabs(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("Closed about:blank", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        dismissSnackbars();

        assertNull(getCurrentSnackbar());
        assertEquals(0, mTabModel.getCount());
    }

    @Test
    @SmallTest
    public void testCloseAll_MultipleTabs_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());

        ChromeTabUtils.closeAllTabs(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tabs closed", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
    }

    @Test
    @SmallTest
    public void testCloseAll_MultipleTabs_Dismiss() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());

        ChromeTabUtils.closeAllTabs(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tabs closed", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        dismissSnackbars();

        assertNull(getCurrentSnackbar());
        assertEquals(0, mTabModel.getCount());
    }

    @Test
    @SmallTest
    @DisableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testCloseTabGroup_Undo_SyncDisabled() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.mergeListOfTabsToGroup(
                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)),
                            mTabModel.getTabAt(0),
                            /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)))
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tabs tab group closed", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testCloseTabGroup_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.mergeListOfTabsToGroup(
                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)),
                            mTabModel.getTabAt(0),
                            /* notify= */ false);
                    mTabGroupModelFilter.setTabGroupTitle(
                            mTabModel.getTabAt(0).getRootId(), "My group");
                });

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)))
                                    .hideTabGroups(true)
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("My group tab group closed and saved", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.setTabGroupTitle(mTabModel.getTabAt(0).getRootId(), null);
                });
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testDeleteTabGroup_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.mergeListOfTabsToGroup(
                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)),
                            mTabModel.getTabAt(0),
                            /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)))
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tabs tab group deleted", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testDeleteTabGroup_WithOtherTab_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(0), /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(3, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(
                                                    mTabModel.getTabAt(0),
                                                    mTabModel.getTabAt(1),
                                                    mTabModel.getTabAt(2)))
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("1 tab group, 2 tabs deleted", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(3, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testCloseTabGroup_WithOtherTabs_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(0), /* notify= */ false);
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(1), /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(3, mTabModel.getCount());
        assertEquals(2, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(
                                                    mTabModel.getTabAt(0),
                                                    mTabModel.getTabAt(1),
                                                    mTabModel.getTabAt(2)))
                                    .hideTabGroups(true)
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tab groups, 1 tab closed and saved", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(3, mTabModel.getCount());
        assertEquals(2, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testPartialDeleteTabGroup_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.mergeListOfTabsToGroup(
                            List.of(
                                    mTabModel.getTabAt(0),
                                    mTabModel.getTabAt(1),
                                    mTabModel.getTabAt(2)),
                            mTabModel.getTabAt(0),
                            /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(3, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)))
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tabs closed", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(1, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(3, mTabModel.getCount());
        assertEquals(1, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testDeleteTabGroups_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(0), /* notify= */ false);
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(1), /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(2, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)))
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tab groups deleted", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(2, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID})
    public void testCloseTabGroups_Undo() throws Exception {
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(0), /* notify= */ false);
                    mTabGroupModelFilter.createSingleTabGroup(
                            mTabModel.getTabAt(1), /* notify= */ false);
                });

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(2, mTabGroupModelFilter.getTabGroupCount());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabGroupModelFilter.closeTabs(
                            TabClosureParams.closeTabs(
                                            List.of(mTabModel.getTabAt(0), mTabModel.getTabAt(1)))
                                    .hideTabGroups(true)
                                    .build());
                });

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("2 tab groups closed and saved", getSnackbarText());
        assertTrue(currentSnackbar.getController() instanceof UndoBarController);
        assertEquals(0, mTabModel.getCount());

        clickSnackbar();

        assertNull(getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());
        assertEquals(2, mTabGroupModelFilter.getTabGroupCount());
    }

    @Test
    @SmallTest
    public void testUndoSnackbarDisabled_AccessibilityEnabled() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(true));
        ChromeTabUtils.newTabFromMenu(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        assertNull("Snack bar should be null initially", getCurrentSnackbar());
        assertEquals(2, mTabModel.getCount());

        ChromeTabUtils.closeCurrentTab(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        assertNull(
                "Undo snack bar should not be showing in accessibility mode", getCurrentSnackbar());
    }

    @Test
    @SmallTest
    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
    public void testUndoSnackbarEnabled_AccessibilityEnabled() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> ChromeAccessibilityUtil.get().setAccessibilityEnabledForTesting(true));

        assertNull("Snack bar should be null initially", getCurrentSnackbar());
        assertEquals("Tab Model should contain 1 tab", 1, mTabModel.getCount());

        ChromeTabUtils.closeAllTabs(
                InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());

        Snackbar currentSnackbar = getCurrentSnackbar();
        assertEquals("Incorrect snackbar text", "Closed about:blank", getSnackbarText());
        assertTrue(
                "Incorrect SnackbarController type",
                currentSnackbar.getController() instanceof UndoBarController);
        assertEquals("Tab Model should contain 0 tab after tab closed", 0, mTabModel.getCount());
    }

    private void clickSnackbar() {
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        mSnackbarManager.onClick(
                                sActivityTestRule
                                        .getActivity()
                                        .findViewById(R.id.snackbar_button)));
    }

    private void dismissSnackbars() {
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        mSnackbarManager.dismissSnackbars(
                                mSnackbarManager.getCurrentSnackbarForTesting().getController()));
    }

    private String getSnackbarText() {
        return ((TextView) sActivityTestRule.getActivity().findViewById(R.id.snackbar_message))
                .getText()
                .toString();
    }

    private Snackbar getCurrentSnackbar() throws ExecutionException {
        return ThreadUtils.runOnUiThreadBlocking(
                new Callable<Snackbar>() {
                    @Override
                    public Snackbar call() {
                        return mSnackbarManager.getCurrentSnackbarForTesting();
                    }
                });
    }
}