chromium/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/hub/TabSwitcherStation.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.test.transit.hub;

import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withParent;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertTrue;

import static org.chromium.base.test.transit.ViewSpec.viewSpec;

import android.view.View;

import org.hamcrest.Matcher;

import org.chromium.base.test.transit.Condition;
import org.chromium.base.test.transit.Elements;
import org.chromium.base.test.transit.Transition;
import org.chromium.base.test.transit.ViewSpec;
import org.chromium.base.test.util.ViewActionOnDescendant;
import org.chromium.chrome.browser.hub.HubFieldTrial;
import org.chromium.chrome.browser.hub.HubToolbarView;
import org.chromium.chrome.browser.hub.PaneId;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.tab_management.TabGridView;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.transit.page.PageStation;
import org.chromium.chrome.test.transit.tabmodel.TabCountChangedCondition;

/** The base station for Hub tab switcher stations. */
public abstract class TabSwitcherStation extends HubBaseStation {
    public static final ViewSpec TAB_LIST_RECYCLER_VIEW =
            viewSpec(
                    allOf(
                            isDescendantOfA(HubBaseStation.HUB_PANE_HOST.getViewMatcher()),
                            withId(R.id.tab_list_recycler_view)));

    public static final ViewSpec TOOLBAR_NEW_TAB_BUTTON =
            viewSpec(
                    allOf(
                            withId(R.id.toolbar_action_button),
                            isDescendantOfA(instanceOf(HubToolbarView.class))));

    public static final ViewSpec FLOATING_NEW_TAB_BUTTON =
            viewSpec(
                    allOf(
                            withId(R.id.host_action_button),
                            isDescendantOfA(HubBaseStation.HUB_PANE_HOST.getViewMatcher())));

    public static final Matcher<View> TAB_CLOSE_BUTTON =
            allOf(
                    withId(R.id.action_button),
                    isDescendantOfA(
                            allOf(
                                    withId(R.id.content_view),
                                    withParent(instanceOf(TabGridView.class)))),
                    isDisplayed());
    public static final Matcher<View> TAB_THUMBNAIL =
            allOf(
                    withId(R.id.tab_thumbnail),
                    isDescendantOfA(
                            allOf(
                                    withId(R.id.content_view),
                                    withParent(instanceOf(TabGridView.class)))),
                    isDisplayed());

    private final boolean mIsIncognito;

    public TabSwitcherStation(
            boolean isIncognito, boolean regularTabsExist, boolean incognitoTabsExist) {
        super(regularTabsExist, incognitoTabsExist);
        mIsIncognito = isIncognito;
    }

    @Override
    public void declareElements(Elements.Builder elements) {
        super.declareElements(elements);

        elements.declareView(getNewTabButtonViewSpec());
        elements.declareView(TAB_LIST_RECYCLER_VIEW);
    }

    public boolean isIncognito() {
        return mIsIncognito;
    }

    /**
     * Opens the app menu.
     *
     * @return the {@link TabSwitcherAppMenuFacility} for the Hub.
     */
    public TabSwitcherAppMenuFacility openAppMenu() {
        recheckActiveConditions();

        return enterFacilitySync(
                new TabSwitcherAppMenuFacility(mIsIncognito), HUB_MENU_BUTTON::click);
    }

    /**
     * @param index The tab index to select.
     * @param destinationBuilder Builder for the specific type of PageStation expected to appear.
     * @return Builder of the {@link PageStation} for the tab that was selected.
     */
    public <T extends PageStation> T selectTabAtIndex(
            int index, PageStation.Builder<T> destinationBuilder) {
        recheckActiveConditions();

        T destination =
                destinationBuilder
                        .withIncognito(mIsIncognito)
                        .withIsOpeningTabs(0)
                        .withIsSelectingTabs(1)
                        .build();

        return travelToSync(
                destination,
                () -> {
                    ViewActionOnDescendant.performOnRecyclerViewNthItemDescendant(
                            TAB_LIST_RECYCLER_VIEW.getViewMatcher(), index, TAB_THUMBNAIL, click());
                });
    }

    /**
     * Close a tab and end in a destination.
     *
     * @param index The index of the tab to close.
     */
    public <T extends TabSwitcherStation> T closeTabAtIndex(
            int index, Class<T> expectedDestination) {
        TabModelSelector tabModelSelector = getActivity().getTabModelSelector();
        boolean incognitoModelSelected = tabModelSelector.isOffTheRecordModelSelected();
        int expectedIncognitoTabs = tabModelSelector.getModel(/* incognito= */ true).getCount();
        int expectedRegularTabs = tabModelSelector.getModel(/* incognito= */ false).getCount();

        // By default stay in the same tab switcher state, unless closing the last incognito tab.
        boolean landInIncognitoSwitcher = false;
        if (getPaneId() == PaneId.INCOGNITO_TAB_SWITCHER) {
            assertTrue(incognitoModelSelected);
            expectedIncognitoTabs--;
            if (tabModelSelector.getCurrentModel().getCount() <= 1) {
                landInIncognitoSwitcher = false;
            } else {
                landInIncognitoSwitcher = true;
            }
        } else {
            expectedRegularTabs--;
        }

        T tabSwitcher =
                expectedDestination.cast(
                        HubStationUtils.createHubStation(
                                landInIncognitoSwitcher
                                        ? PaneId.INCOGNITO_TAB_SWITCHER
                                        : PaneId.TAB_SWITCHER,
                                expectedRegularTabs > 0,
                                expectedIncognitoTabs > 0));
        Condition tabCountDecremented =
                new TabCountChangedCondition(
                        tabModelSelector.getModel(incognitoModelSelected),
                        /* expectedChange= */ -1);
        return travelToSync(
                tabSwitcher,
                Transition.conditionOption(tabCountDecremented),
                () -> {
                    ViewActionOnDescendant.performOnRecyclerViewNthItemDescendant(
                            TAB_LIST_RECYCLER_VIEW.getViewMatcher(),
                            index,
                            TAB_CLOSE_BUTTON,
                            click());
                });
    }

    protected ViewSpec getNewTabButtonViewSpec() {
        if (HubFieldTrial.usesFloatActionButton()) {
            return FLOATING_NEW_TAB_BUTTON;
        } else {
            return TOOLBAR_NEW_TAB_BUTTON;
        }
    }

    /**
     * Returns to the previous tab via the back button.
     *
     * @param destinationBuilder Builder for the specific type of PageStation expected to appear.
     * @return the {@link PageStation} that Hub returned to.
     */
    public <T extends PageStation> T leaveHubToPreviousTabViaBack(
            PageStation.Builder<T> destinationBuilder) {
        T destination =
                destinationBuilder
                        .withIsOpeningTabs(0)
                        .withIsSelectingTabs(1)
                        .withIncognito(mIsIncognito)
                        .build();
        return leaveHubToPreviousTabViaBack(destination);
    }
}