chromium/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/ArchivedTabsDialogCoordinatorTest.java

// Copyright 2024 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.tasks.tab_management;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;

import android.view.View;
import android.view.ViewGroup;

import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UserActionTester;
import org.chromium.chrome.browser.app.tabmodel.ArchivedTabModelOrchestrator;
import org.chromium.chrome.browser.app.tabmodel.ArchivedTabModelOrchestrator.Observer;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.layouts.LayoutTestUtils;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabArchiveSettings;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.tabmodel.TabCreator;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.util.ActivityTestUtils;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.test.util.UiRestriction;
import org.chromium.url.GURL;

/** End-to-end test for ArchivedTabsDialogCoordinator. */
@RunWith(ChromeJUnit4ClassRunner.class)
@DoNotBatch(reason = "TODO(crbug.com/348068134): Batch this test suite.")
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures({ChromeFeatureList.ANDROID_TAB_DECLUTTER})
public class ArchivedTabsDialogCoordinatorTest {
    @Rule
    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();

    private final TabListEditorTestingRobot mRobot = new TabListEditorTestingRobot();

    private Profile mProfile;
    private ArchivedTabModelOrchestrator mArchivedTabModelOrchestrator;
    private TabModel mArchivedTabModel;
    private ViewGroup mParentView;
    private TabCreator mRegularTabCreator;
    private TabModel mRegularTabModel;
    private UserActionTester mUserActionTester;
    private TabArchiveSettings mTabArchiveSettings;
    private int mTimesShown;

    @Before
    public void setUp() throws Exception {
        mActivityTestRule.startMainActivityOnBlankPage();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mProfile =
                            mActivityTestRule
                                    .getActivity()
                                    .getProfileProviderSupplier()
                                    .get()
                                    .getOriginalProfile();
                    mRegularTabCreator = mActivityTestRule.getActivity().getTabCreator(false);
                    mRegularTabModel =
                            mActivityTestRule
                                    .getActivity()
                                    .getTabModelSelectorSupplier()
                                    .get()
                                    .getModel(false);
                });

        mArchivedTabModelOrchestrator = ArchivedTabModelOrchestrator.getForProfile(mProfile);
        mArchivedTabModel = mArchivedTabModelOrchestrator.getTabModelSelector().getModel(false);
        mUserActionTester = new UserActionTester();
        mTabArchiveSettings = mArchivedTabModelOrchestrator.getTabArchiveSettings();
        mTabArchiveSettings.setShouldShowDialogIphForTesting(false);
        waitForArchivedTabModelsToLoad(mArchivedTabModelOrchestrator);
    }

    @After
    public void tearDown() {
        mUserActionTester.tearDown();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mArchivedTabModel.closeTabs(TabClosureParams.closeAllTabs().build());
                    mTabArchiveSettings.resetSettingsForTesting();
                });
    }

    @Test
    @MediumTest
    public void testOneInactiveTab() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(1);
        onView(withText("1 inactive tab")).check(matches(isDisplayed()));
        mRobot.resultRobot.verifyTabListEditorIsVisible().verifyAdapterHasItemCount(1);

        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Restore all", true)
                .verifyToolbarMenuItemState("Select tabs", true)
                .verifyToolbarMenuItemState("Settings", true);
    }

    @Test
    @MediumTest
    public void testTwoInactiveTabs() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));
        // The dialog isn't scrollable, so the shadow should be hidden.
        onView(withId(R.id.close_all_tabs_button_container_shadow))
                .check(matches(not(isDisplayed())));
    }

    @Test
    @MediumTest
    public void testDialogIPH() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");

        mTabArchiveSettings.setShouldShowDialogIphForTesting(true);
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));
        mRobot.resultRobot.verifyAdapterHasItemCount(3);
        assertEquals(1, mUserActionTester.getActionCount("Tabs.ArchivedTabsDialogIphShown"));
    }

    @Test
    @MediumTest
    public void testDialogIPH_Clicked() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");

        mTabArchiveSettings.setShouldShowDialogIphForTesting(true);
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));

        SettingsActivity activity =
                ActivityTestUtils.waitForActivity(
                        InstrumentationRegistry.getInstrumentation(),
                        SettingsActivity.class,
                        new Runnable() {
                            @Override
                            public void run() {
                                mRobot.actionRobot.clickItemAtAdapterPosition(0);
                            }
                        });
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        ActivityTestUtils.waitForFragmentToAttach(activity, TabArchiveSettingsFragment.class);
        assertEquals(1, mUserActionTester.getActionCount("Tabs.ArchivedTabsDialogIphClicked"));
    }

    @Test
    @MediumTest
    public void testDialogIPH_CloseDialog() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");

        mTabArchiveSettings.setShouldShowDialogIphForTesting(true);

        enterTabSwitcherAndShowDialog(2);
        dismissIphMessage(/* numOfArchivedTabs= */ 2);
        assertTrue(mTabArchiveSettings.shouldShowDialogIph());
        assertEquals(1, mUserActionTester.getActionCount("Tabs.ArchivedTabsDialogIphDismissed"));
        mRobot.actionRobot.clickToolbarNavigationButton(
                R.string.accessibility_archived_tabs_dialog_back_button);
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        showDialog(2);
        dismissIphMessage(/* numOfArchivedTabs= */ 2);
        assertTrue(mTabArchiveSettings.shouldShowDialogIph());
        assertEquals(2, mUserActionTester.getActionCount("Tabs.ArchivedTabsDialogIphDismissed"));
        mRobot.actionRobot.clickToolbarNavigationButton(
                R.string.accessibility_archived_tabs_dialog_back_button);
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        showDialog(2);
        dismissIphMessage(/* numOfArchivedTabs= */ 2);
        assertFalse(mTabArchiveSettings.shouldShowDialogIph());
        assertEquals(3, mUserActionTester.getActionCount("Tabs.ArchivedTabsDialogIphDismissed"));
        mRobot.actionRobot.clickToolbarNavigationButton(
                R.string.accessibility_archived_tabs_dialog_back_button);
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        // After 3 dismisses, the iph message won't show again.
        showDialog(2);
        mRobot.resultRobot.verifyAdapterHasItemCount(2);
    }

    @Test
    @MediumTest
    public void testRestoreAllInactiveTabs() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));

        HistogramWatcher histogramExpectation =
                HistogramWatcher.newSingleRecordWatcher(
                        "Tabs.RestoreAllArchivedTabsMenuItem.TabCount", 2);
        assertEquals(1, mRegularTabModel.getCount());
        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Restore all");
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        assertEquals(3, mRegularTabModel.getCount());
        assertEquals(0, mArchivedTabModel.getCount());
        histogramExpectation.assertExpected();
        assertEquals(1, mUserActionTester.getActionCount("Tabs.RestoreAllArchivedTabsMenuItem"));
    }

    @Test
    @MediumTest
    public void testRestoreArchivedTabsAndOpenLast() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Restore all");
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        // Back to the regular tab switcher -- verify that the undo button is showing.
        int index = 2;
        onView(withId(R.id.tab_list_recycler_view))
                .perform(
                        new ViewAction() {
                            @Override
                            public Matcher<View> getConstraints() {
                                return isDisplayed();
                            }

                            @Override
                            public String getDescription() {
                                return "click on end button of item with index "
                                        + String.valueOf(index);
                            }

                            @Override
                            public void perform(UiController uiController, View view) {
                                RecyclerView recyclerView = (RecyclerView) view;
                                RecyclerView.ViewHolder viewHolder =
                                        recyclerView.findViewHolderForAdapterPosition(index);
                                if (viewHolder.itemView == null) return;
                                viewHolder.itemView.performClick();
                            }
                        });
        LayoutTestUtils.waitForLayout(
                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING);
        Tab activityTab = mActivityTestRule.getActivity().getActivityTabProvider().get();
        assertEquals(mRegularTabModel.getTabAt(2), activityTab);
    }

    @Test
    @MediumTest
    public void testSettings() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);

        SettingsActivity activity =
                ActivityTestUtils.waitForActivity(
                        InstrumentationRegistry.getInstrumentation(),
                        SettingsActivity.class,
                        new Runnable() {
                            @Override
                            public void run() {
                                mRobot.actionRobot
                                        .clickToolbarMenuButton()
                                        .clickToolbarMenuItem("Settings");
                            }
                        });
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        ActivityTestUtils.waitForFragmentToAttach(activity, TabArchiveSettingsFragment.class);
        assertEquals(1, mUserActionTester.getActionCount("Tabs.OpenArchivedTabsSettingsMenuItem"));
    }

    @Test
    @MediumTest
    public void testTurnOffArchiveThroughSettings() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);

        SettingsActivity activity =
                ActivityTestUtils.waitForActivity(
                        InstrumentationRegistry.getInstrumentation(),
                        SettingsActivity.class,
                        new Runnable() {
                            @Override
                            public void run() {
                                mRobot.actionRobot
                                        .clickToolbarMenuButton()
                                        .clickToolbarMenuItem("Settings");
                            }
                        });
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        ActivityTestUtils.waitForFragmentToAttach(activity, TabArchiveSettingsFragment.class);
        assertEquals(1, mUserActionTester.getActionCount("Tabs.OpenArchivedTabsSettingsMenuItem"));

        onView(withText("Never")).perform(click());
        CriteriaHelper.pollUiThread(() -> mRegularTabModel.getCount() == 3);
        assertEquals(0, mArchivedTabModel.getCount());
    }

    @Test
    @MediumTest
    public void testCloseAllArchivedTabs() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);

        HistogramWatcher histogramExpectation =
                HistogramWatcher.newSingleRecordWatcher("Tabs.CloseAllArchivedTabs.TabCount", 2);

        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));
        onView(withText("Close all inactive tabs")).perform(click());
        onView(withText("Close all")).perform(click());

        mRobot.resultRobot.verifyTabListEditorIsHidden();
        assertEquals(0, mArchivedTabModel.getCount());
        histogramExpectation.assertExpected();
        assertEquals(1, mUserActionTester.getActionCount("Tabs.CloseAllArchivedTabsMenuItem"));
    }

    @Test
    @MediumTest
    public void testCloseAllArchivedTabs_Cancel() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);

        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));
        onView(withText("Close all inactive tabs")).perform(click());
        onView(withText("Cancel")).perform(click());

        assertEquals(2, mArchivedTabModel.getCount());
        assertEquals(0, mUserActionTester.getActionCount("Tabs.CloseAllArchivedTabsMenuItem"));
    }

    @Test
    @MediumTest
    public void testSelectTabs() {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Select tabs");
        assertEquals(1, mUserActionTester.getActionCount("Tabs.SelectArchivedTabsMenuItem"));

        mRobot.resultRobot
                .verifyAdapterHasItemCount(2)
                .verifyItemNotSelectedAtAdapterPosition(0)
                .verifyItemNotSelectedAtAdapterPosition(1)
                .verifyToolbarSelectionText("2 inactive tabs");

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.resultRobot.verifyToolbarSelectionText("2 tabs");

        mRobot.actionRobot.clickToolbarNavigationButton(
                R.string.accessibility_archived_tabs_dialog_back_button);
        mRobot.resultRobot
                .verifyTabListEditorIsVisible()
                .verifyAdapterHasItemCount(2)
                .verifyToolbarSelectionText("2 inactive tabs");
    }

    @Test
    @MediumTest
    public void testSelectionModeMenuItems() {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Select tabs");
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Close tabs", false)
                .verifyToolbarMenuItemState("Restore tabs", false);
        Espresso.pressBack();

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot.verifyToolbarSelectionText("1 tab");

        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Close tab", true)
                .verifyToolbarMenuItemState("Restore tab", true);
        Espresso.pressBack();

        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.resultRobot.verifyToolbarSelectionText("2 tabs");

        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Close tabs", true)
                .verifyToolbarMenuItemState("Restore tabs", true);
    }

    @Test
    @MediumTest
    public void testSelectionModeMenuItem_CloseTabs() {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        addArchivedTab(new GURL("https://google.com"), "test 3");
        enterTabSwitcherAndShowDialog(3);
        assertEquals(1, mRegularTabModel.getCount());
        assertEquals(3, mArchivedTabModel.getCount());

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Select tabs");

        HistogramWatcher histogramExpectation =
                HistogramWatcher.newSingleRecordWatcher(
                        "Tabs.CloseArchivedTabsMenuItem.TabCount", 2);
        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.resultRobot.verifyToolbarSelectionText("2 tabs");
        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Close tabs");
        mRobot.resultRobot
                .verifyAdapterHasItemCount(1)
                .verifyUndoSnackbarWithTextIsShown("2 tabs closed");
        assertEquals(1, mRegularTabModel.getCount());
        assertEquals(1, mArchivedTabModel.getCount());
        histogramExpectation.assertExpected();
        assertEquals(1, mUserActionTester.getActionCount("Tabs.CloseArchivedTabsMenuItem"));
    }

    @Test
    @MediumTest
    public void testSelectionModeMenuItem_CloseTabs_SelectAll() {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        addArchivedTab(new GURL("https://google.com"), "test 3");
        enterTabSwitcherAndShowDialog(3);
        assertEquals(1, mRegularTabModel.getCount());
        assertEquals(3, mArchivedTabModel.getCount());

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Select tabs");

        HistogramWatcher histogramExpectation =
                HistogramWatcher.newSingleRecordWatcher(
                        "Tabs.CloseArchivedTabsMenuItem.TabCount", 3);
        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.actionRobot.clickItemAtAdapterPosition(2);
        mRobot.resultRobot.verifyToolbarSelectionText("3 tabs");
        // Closing all the tabs through selection mode will display a confirmation dialog. This is
        // done because the opteration cannot be undone.
        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Close tabs");
        mRobot.resultRobot.verifyUndoSnackbarWithTextIsShown("3 tabs closed");

        assertEquals(1, mRegularTabModel.getCount());
        assertEquals(0, mArchivedTabModel.getCount());
        histogramExpectation.assertExpected();
        assertEquals(1, mUserActionTester.getActionCount("Tabs.CloseArchivedTabsMenuItem"));
    }

    @Test
    @MediumTest
    public void testSelectionModeMenuItem_RestoreTabs() {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        addArchivedTab(new GURL("https://google.com"), "test 3");
        enterTabSwitcherAndShowDialog(3);
        assertEquals(1, mRegularTabModel.getCount());
        assertEquals(3, mArchivedTabModel.getCount());

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Select tabs");

        HistogramWatcher histogramExpectation =
                HistogramWatcher.newSingleRecordWatcher(
                        "Tabs.RestoreArchivedTabsMenuItem.TabCount", 2);
        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.resultRobot.verifyToolbarSelectionText("2 tabs");
        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Restore tabs");
        mRobot.resultRobot.verifyAdapterHasItemCount(1);
        assertEquals(3, mRegularTabModel.getCount());
        assertEquals(1, mArchivedTabModel.getCount());
        histogramExpectation.assertExpected();
        assertEquals(1, mUserActionTester.getActionCount("Tabs.RestoreArchivedTabsMenuItem"));

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Select tabs");

        histogramExpectation =
                HistogramWatcher.newSingleRecordWatcher(
                        "Tabs.RestoreArchivedTabsMenuItem.TabCount", 1);
        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Restore tab");
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        assertEquals(4, mRegularTabModel.getCount());
        assertEquals(0, mArchivedTabModel.getCount());
        histogramExpectation.assertExpected();
        assertEquals(2, mUserActionTester.getActionCount("Tabs.RestoreArchivedTabsMenuItem"));
    }

    @Test
    @MediumTest
    public void testCloseDialogWithBackButton() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(1);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mActivityTestRule.getActivity().getOnBackPressedDispatcher().onBackPressed();
                });
        mRobot.resultRobot.verifyTabListEditorIsHidden();
    }

    @Test
    @MediumTest
    public void testRestoreAndOpenSingleTab() throws Exception {
        Tab tab = addArchivedTab(new GURL("https://www.google.com"), "test 1");
        addArchivedTab(new GURL("https://test.com"), "test 2");
        assertEquals(1, mRegularTabModel.getCount());
        assertEquals(2, mArchivedTabModel.getCount());
        enterTabSwitcherAndShowDialog(2);

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        assertEquals(2, mRegularTabModel.getCount());
        assertEquals(1, mArchivedTabModel.getCount());

        LayoutTestUtils.waitForLayout(
                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING);
        Tab activityTab = mActivityTestRule.getActivity().getActivityTabProvider().get();
        assertEquals(tab, activityTab);
        assertEquals(1, mUserActionTester.getActionCount("Tabs.RestoreSingleTab"));
    }

    @Test
    @MediumTest
    public void testCloseArchivedTab() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));

        mRobot.actionRobot.clickViewIdAtAdapterPosition(1, R.id.action_button);
        mRobot.resultRobot
                .verifyAdapterHasItemCount(1)
                .verifyUndoSnackbarWithTextIsShown("Closed google");
    }

    @Test
    @MediumTest
    public void testCloseArchivedTab_SnackbarResetForTabSwitcher() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        enterTabSwitcherAndShowDialog(2);
        onView(withText("2 inactive tabs")).check(matches(isDisplayed()));

        mRobot.actionRobot.clickViewIdAtAdapterPosition(1, R.id.action_button);
        mRobot.resultRobot
                .verifyAdapterHasItemCount(1)
                .verifyUndoSnackbarWithTextIsShown("Closed google");
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mActivityTestRule.getActivity().getOnBackPressedDispatcher().onBackPressed();
                });
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        // Back to the regular tab switcher -- verify that the undo button is showing.
        int index = 1;
        onView(withId(R.id.tab_list_recycler_view))
                .perform(
                        new ViewAction() {
                            @Override
                            public Matcher<View> getConstraints() {
                                return isDisplayed();
                            }

                            @Override
                            public String getDescription() {
                                return "click on end button of item with index "
                                        + String.valueOf(index);
                            }

                            @Override
                            public void perform(UiController uiController, View view) {
                                RecyclerView recyclerView = (RecyclerView) view;
                                RecyclerView.ViewHolder viewHolder =
                                        recyclerView.findViewHolderForAdapterPosition(index);
                                if (viewHolder.itemView == null) return;
                                viewHolder.itemView.findViewById(R.id.action_button).performClick();
                            }
                        });
        mRobot.resultRobot.verifyUndoSnackbarWithTextIsShown("Closed about:blank");
    }

    @Test
    @MediumTest
    public void testContentDescription() {
        onView(withContentDescription(R.string.accessibility_tab_selection_editor_back_button));
        onView(withContentDescription(R.string.accessibility_tab_selection_editor));
    }

    @Test
    @MediumTest
    public void testBottomShadowView() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        addArchivedTab(new GURL("https://google.com"), "test 3");
        addArchivedTab(new GURL("https://google.com"), "test 4");
        addArchivedTab(new GURL("https://google.com"), "test 5");
        addArchivedTab(new GURL("https://google.com"), "test 6");
        addArchivedTab(new GURL("https://google.com"), "test 7");
        addArchivedTab(new GURL("https://google.com"), "test 8");
        addArchivedTab(new GURL("https://google.com"), "test 9");
        addArchivedTab(new GURL("https://google.com"), "test 10");
        addArchivedTab(new GURL("https://google.com"), "test 11");
        enterTabSwitcherAndShowDialog(11);
        onView(withText("11 inactive tabs")).check(matches(isDisplayed()));
        mRobot.resultRobot.verifyTabListEditorIsVisible().verifyAdapterHasItemCount(11);

        // When there is more than a page of tabs, then the bottom container should have a shadow.
        onView(withId(R.id.close_all_tabs_button_container_shadow)).check(matches(isDisplayed()));
    }

    @Test
    @MediumTest
    // Scrolling all the way down on tablets is flaky.
    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
    public void testBottomShadowView_DisappersWhenFullyScrolled() throws Exception {
        addArchivedTab(new GURL("https://google.com"), "test 1");
        addArchivedTab(new GURL("https://google.com"), "test 2");
        addArchivedTab(new GURL("https://google.com"), "test 3");
        addArchivedTab(new GURL("https://google.com"), "test 4");
        addArchivedTab(new GURL("https://google.com"), "test 5");
        addArchivedTab(new GURL("https://google.com"), "test 6");
        addArchivedTab(new GURL("https://google.com"), "test 7");
        addArchivedTab(new GURL("https://google.com"), "test 8");
        addArchivedTab(new GURL("https://google.com"), "test 9");
        addArchivedTab(new GURL("https://google.com"), "test 10");
        addArchivedTab(new GURL("https://google.com"), "test 11");
        enterTabSwitcherAndShowDialog(11);
        onView(withText("11 inactive tabs")).check(matches(isDisplayed()));
        mRobot.resultRobot.verifyTabListEditorIsVisible().verifyAdapterHasItemCount(11);

        // When there is more than a page of tabs, then the bottom container should have a shadow.
        onView(withId(R.id.close_all_tabs_button_container_shadow)).check(matches(isDisplayed()));

        // When the recycler view is scrolled all the way down, the shadow should be hidden.
        onView(
                        allOf(
                                isDescendantOfA(withId(R.id.tab_list_editor_container)),
                                withId(R.id.tab_list_recycler_view)))
                .perform(scrollToPosition(10));
        onView(withId(R.id.close_all_tabs_button_container_shadow))
                .check(matches(not(isDisplayed())));
    }

    private void enterTabSwitcherAndShowDialog(int numOfArchivedTabs) {
        // Enter the tab switcher and click the message.
        TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity());
        showDialog(numOfArchivedTabs);
    }

    private void showDialog(int numOfArchivedTabs) {
        onViewWaiting(
                        withText(
                                mActivityTestRule
                                        .getActivity()
                                        .getResources()
                                        .getQuantityString(
                                                R.plurals.archived_tab_card_title,
                                                numOfArchivedTabs,
                                                numOfArchivedTabs)))
                .perform(click());
        mRobot.resultRobot.verifyTabListEditorIsVisible();
        mTimesShown++;
        assertEquals(mTimesShown, mUserActionTester.getActionCount("Tabs.ArchivedTabsDialogShown"));
    }

    private @TabListCoordinator.TabListMode int getMode() {
        return SysUtils.isLowEndDevice()
                ? TabListCoordinator.TabListMode.LIST
                : TabListCoordinator.TabListMode.GRID;
    }

    private Tab addArchivedTab(GURL url, String title) {
        return ThreadUtils.runOnUiThreadBlocking(
                () ->
                        mArchivedTabModelOrchestrator
                                .getArchivedTabCreatorForTesting()
                                .createNewTab(
                                        new LoadUrlParams(new GURL("https://google.com")),
                                        "google",
                                        TabLaunchType.FROM_RESTORE,
                                        null,
                                        mArchivedTabModel.getCount()));
    }

    private void removeArchivedTab(Tab tab) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mArchivedTabModel.removeTab(tab);
                    return null;
                });
    }

    private void waitForArchivedTabModelsToLoad(
            ArchivedTabModelOrchestrator archivedTabModelOrchestrator) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    CallbackHelper callbackHelper = new CallbackHelper();
                    if (archivedTabModelOrchestrator.isTabModelInitialized()) {
                        callbackHelper.notifyCalled();
                    } else {
                        archivedTabModelOrchestrator.addObserver(
                                new Observer() {
                                    @Override
                                    public void onTabModelCreated(TabModel archivedTabModel) {
                                        archivedTabModelOrchestrator.removeObserver(this);
                                        callbackHelper.notifyCalled();
                                    }
                                });
                    }

                    return null;
                });
    }

    private void dismissIphMessage(int numOfArchivedTabs) {
        mRobot.resultRobot.verifyAdapterHasItemCount(3);
        mRobot.actionRobot.clickViewIdAtAdapterPosition(0, R.id.close_button);
        mRobot.resultRobot.verifyAdapterHasItemCount(2);
    }
}