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

// Copyright 2019 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.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

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

import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.createTabs;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.enterTabSwitcher;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.leaveTabSwitcher;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabSwitcherCardCount;

import android.content.Context;
import android.content.res.Configuration;
import android.util.Pair;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;

import androidx.annotation.IntDef;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.MediumTest;

import org.junit.After;
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.test.util.Batch;
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.DisableIf;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.layouts.LayoutType;
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.ActivityTestUtils;
import org.chromium.ui.test.util.UiRestriction;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** Tests for reordering tabs in grid tab switcher in accessibility mode. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@Batch(Batch.PER_CLASS)
public class TabGridAccessibilityHelperTest {
    @IntDef({
        TabMovementDirection.LEFT,
        TabMovementDirection.RIGHT,
        TabMovementDirection.UP,
        TabMovementDirection.DOWN
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface TabMovementDirection {
        int LEFT = 0;
        int RIGHT = 1;
        int UP = 2;
        int DOWN = 3;
        int NUM_ENTRIES = 4;
    }

    @ClassRule
    public static ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

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

    @Before
    public void setUp() {
        CriteriaHelper.pollUiThread(
                sActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);

        TabUiTestHelper.getTabSwitcherLayoutAndVerify(sActivityTestRule.getActivity());
    }

    @After
    public void tearDown() {
        ActivityTestUtils.clearActivityOrientation(sActivityTestRule.getActivity());
        final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
        if (cta != null && cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER)) {
            leaveTabSwitcher(cta);
        }
    }

    @Test
    @MediumTest
    // Low-end uses list mode.
    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
    // Fails to rotate on some ARM devices.
    // TODO(crbug.com/40917078): fix and re-enable on ARM devices.
    @DisableIf.Build(supported_abis_includes = "armeabi-v7a")
    @DisableIf.Build(supported_abis_includes = "arm64-v8a")
    public void testGetPotentialActionsForView() throws Exception {
        final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
        final AccessibilityActionChecker checker = new AccessibilityActionChecker(cta);
        createTabs(cta, false, 5);
        enterTabSwitcher(cta);
        verifyTabSwitcherCardCount(cta, 5);

        ViewGroup outerView =
                (ViewGroup) cta.findViewById(TabUiTestHelper.getTabSwitcherAncestorId(cta));
        View view = outerView.findViewById(R.id.tab_list_recycler_view);
        assertTrue(view instanceof TabListMediator.TabGridAccessibilityHelper);
        TabListMediator.TabGridAccessibilityHelper helper =
                (TabListMediator.TabGridAccessibilityHelper) view;

        // Verify action list in portrait mode with span count = 2.
        onView(
                        allOf(
                                isDescendantOfA(
                                        withId(TabUiTestHelper.getTabSwitcherAncestorId(cta))),
                                withId(R.id.tab_list_recycler_view)))
                .check(
                        (v, noMatchingViewException) -> {
                            if (noMatchingViewException != null) {
                                throw noMatchingViewException;
                            }
                            assertTrue(v instanceof RecyclerView);
                            RecyclerView recyclerView = (RecyclerView) v;
                            assertEquals(
                                    2,
                                    ((GridLayoutManager) recyclerView.getLayoutManager())
                                            .getSpanCount());

                            View item1 = getItemViewForPosition(recyclerView, 0);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item1),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.RIGHT,
                                                    TabMovementDirection.DOWN)));

                            View item2 = getItemViewForPosition(recyclerView, 1);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item2),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.LEFT,
                                                    TabMovementDirection.DOWN)));

                            View item3 = getItemViewForPosition(recyclerView, 2);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item3),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.RIGHT,
                                                    TabMovementDirection.UP,
                                                    TabMovementDirection.DOWN)));

                            View item4 = getItemViewForPosition(recyclerView, 3);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item4),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.LEFT,
                                                    TabMovementDirection.UP)));

                            View item5 = getItemViewForPosition(recyclerView, 4);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item5),
                                    new ArrayList<>(Arrays.asList(TabMovementDirection.UP)));
                        });

        assertTrue(view instanceof TabListRecyclerView);
        TabListRecyclerView tabListRecyclerView = (TabListRecyclerView) view;
        CallbackHelper callbackHelper = new CallbackHelper();
        OnLayoutChangeListener listener =
                (rv, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                    callbackHelper.notifyCalled();
                };
        tabListRecyclerView.addOnLayoutChangeListener(listener);
        final int callCount = callbackHelper.getCallCount();
        ActivityTestUtils.rotateActivityToOrientation(cta, Configuration.ORIENTATION_LANDSCAPE);
        callbackHelper.waitForCallback(callCount);

        // Verify action list in landscape mode with span count = 3.
        onView(
                        allOf(
                                isDescendantOfA(
                                        withId(TabUiTestHelper.getTabSwitcherAncestorId(cta))),
                                withId(R.id.tab_list_recycler_view)))
                .check(
                        (v, noMatchingViewException) -> {
                            if (noMatchingViewException != null) {
                                throw noMatchingViewException;
                            }
                            assertTrue(v instanceof RecyclerView);
                            RecyclerView recyclerView = (RecyclerView) v;
                            // This case only applies for a span of 3.
                            if (((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount()
                                    != 3) {
                                return;
                            }

                            View item1 = getItemViewForPosition(recyclerView, 0);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item1),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.RIGHT,
                                                    TabMovementDirection.DOWN)));

                            View item2 = getItemViewForPosition(recyclerView, 1);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item2),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.LEFT,
                                                    TabMovementDirection.RIGHT,
                                                    TabMovementDirection.DOWN)));

                            View item3 = getItemViewForPosition(recyclerView, 2);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item3),
                                    new ArrayList<>(Arrays.asList(TabMovementDirection.LEFT)));

                            View item4 = getItemViewForPosition(recyclerView, 3);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item4),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.RIGHT,
                                                    TabMovementDirection.UP)));

                            View item5 = getItemViewForPosition(recyclerView, 4);
                            checker.verifyListOfAccessibilityAction(
                                    helper.getPotentialActionsForView(item5),
                                    new ArrayList<>(
                                            Arrays.asList(
                                                    TabMovementDirection.LEFT,
                                                    TabMovementDirection.UP)));
                        });
    }

    @Test
    @MediumTest
    // Low-end uses list mode.
    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
    // Fails to rotate on some ARM devices.
    // TODO(crbug.com/40917078): fix and re-enable on ARM devices.
    @DisableIf.Build(supported_abis_includes = "armeabi-v7a")
    @DisableIf.Build(supported_abis_includes = "arm64-v8a")
    public void testGetPositionsOfReorderAction() throws Exception {
        final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
        int leftActionId = R.id.move_tab_left;
        int rightActionId = R.id.move_tab_right;
        int upActionId = R.id.move_tab_up;
        int downActionId = R.id.move_tab_down;
        createTabs(cta, false, 5);
        enterTabSwitcher(cta);
        verifyTabSwitcherCardCount(cta, 5);

        ViewGroup outerView =
                (ViewGroup) cta.findViewById(TabUiTestHelper.getTabSwitcherAncestorId(cta));
        View view = outerView.findViewById(R.id.tab_list_recycler_view);
        assertTrue(view instanceof TabListMediator.TabGridAccessibilityHelper);
        TabListMediator.TabGridAccessibilityHelper helper =
                (TabListMediator.TabGridAccessibilityHelper) view;

        // Span count 2.
        onView(
                        allOf(
                                isDescendantOfA(
                                        withId(TabUiTestHelper.getTabSwitcherAncestorId(cta))),
                                withId(R.id.tab_list_recycler_view)))
                .check(
                        (v, noMatchingViewException) -> {
                            if (noMatchingViewException != null) {
                                throw noMatchingViewException;
                            }
                            assertTrue(v instanceof RecyclerView);
                            RecyclerView recyclerView = (RecyclerView) v;
                            assertEquals(
                                    2,
                                    ((GridLayoutManager) recyclerView.getLayoutManager())
                                            .getSpanCount());

                            Pair<Integer, Integer> positions;

                            View item1 = getItemViewForPosition(recyclerView, 0);
                            positions = helper.getPositionsOfReorderAction(item1, rightActionId);
                            assertEquals(0, (int) positions.first);
                            assertEquals(1, (int) positions.second);

                            positions = helper.getPositionsOfReorderAction(item1, downActionId);
                            assertEquals(0, (int) positions.first);
                            assertEquals(2, (int) positions.second);

                            View item4 = getItemViewForPosition(recyclerView, 3);
                            positions = helper.getPositionsOfReorderAction(item4, leftActionId);
                            assertEquals(3, (int) positions.first);
                            assertEquals(2, (int) positions.second);

                            positions = helper.getPositionsOfReorderAction(item4, upActionId);
                            assertEquals(3, (int) positions.first);
                            assertEquals(1, (int) positions.second);
                        });

        assertTrue(view instanceof TabListRecyclerView);
        TabListRecyclerView tabListRecyclerView = (TabListRecyclerView) view;
        CallbackHelper callbackHelper = new CallbackHelper();
        OnLayoutChangeListener listener =
                (rv, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                    callbackHelper.notifyCalled();
                };
        tabListRecyclerView.addOnLayoutChangeListener(listener);
        final int callCount = callbackHelper.getCallCount();
        ActivityTestUtils.rotateActivityToOrientation(cta, Configuration.ORIENTATION_LANDSCAPE);
        callbackHelper.waitForCallback(callCount);

        // Span count 3.
        onView(
                        allOf(
                                isDescendantOfA(
                                        withId(TabUiTestHelper.getTabSwitcherAncestorId(cta))),
                                withId(R.id.tab_list_recycler_view)))
                .check(
                        (v, noMatchingViewException) -> {
                            if (noMatchingViewException != null) {
                                throw noMatchingViewException;
                            }
                            assertTrue(v instanceof RecyclerView);
                            RecyclerView recyclerView = (RecyclerView) v;
                            // This case only applies for a span of 3.
                            if (((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount()
                                    != 3) {
                                return;
                            }

                            Pair<Integer, Integer> positions;

                            View item2 = getItemViewForPosition(recyclerView, 1);
                            positions = helper.getPositionsOfReorderAction(item2, leftActionId);
                            assertEquals(1, (int) positions.first);
                            assertEquals(0, (int) positions.second);

                            positions = helper.getPositionsOfReorderAction(item2, rightActionId);
                            assertEquals(1, (int) positions.first);
                            assertEquals(2, (int) positions.second);

                            positions = helper.getPositionsOfReorderAction(item2, downActionId);
                            assertEquals(1, (int) positions.first);
                            assertEquals(4, (int) positions.second);

                            View item5 = getItemViewForPosition(recyclerView, 4);
                            positions = helper.getPositionsOfReorderAction(item5, leftActionId);
                            assertEquals(4, (int) positions.first);
                            assertEquals(3, (int) positions.second);

                            positions = helper.getPositionsOfReorderAction(item5, upActionId);
                            assertEquals(4, (int) positions.first);
                            assertEquals(1, (int) positions.second);
                        });
    }

    private View getItemViewForPosition(RecyclerView recyclerView, int position) {
        // Scroll to position to ensure the ViewHolder is not recycled.
        ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPosition(position);
        RecyclerView.ViewHolder viewHolder =
                recyclerView.findViewHolderForAdapterPosition(position);
        assertNotNull(viewHolder);
        return viewHolder.itemView;
    }

    private static class AccessibilityActionChecker {
        private final Context mContext;

        AccessibilityActionChecker(ChromeTabbedActivity cta) {
            mContext = cta;
        }

        void verifyListOfAccessibilityAction(
                List<AccessibilityAction> actions, List<Integer> directions) {
            assertEquals(directions.size(), actions.size());
            for (int i = 0; i < actions.size(); i++) {
                verifyAccessibilityAction(actions.get(i), directions.get(i));
            }
        }

        void verifyAccessibilityAction(
                AccessibilityAction action, @TabMovementDirection int direction) {
            switch (direction) {
                case TabMovementDirection.LEFT:
                    assertEquals(R.id.move_tab_left, action.getId());
                    assertEquals(
                            mContext.getString(R.string.accessibility_tab_movement_left),
                            action.getLabel());
                    break;
                case TabMovementDirection.RIGHT:
                    assertEquals(R.id.move_tab_right, action.getId());
                    assertEquals(
                            mContext.getString(R.string.accessibility_tab_movement_right),
                            action.getLabel());
                    break;
                case TabMovementDirection.UP:
                    assertEquals(R.id.move_tab_up, action.getId());
                    assertEquals(
                            mContext.getString(R.string.accessibility_tab_movement_up),
                            action.getLabel());
                    break;
                case TabMovementDirection.DOWN:
                    assertEquals(R.id.move_tab_down, action.getId());
                    assertEquals(
                            mContext.getString(R.string.accessibility_tab_movement_down),
                            action.getLabel());
                    break;
                default:
                    assert false;
            }
        }
    }
}