root/chrome/android/javatests/src/org/chromium/chrome/browser/appmenu/AppMenuTest.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. onOptionsItemSelected
  2. setUp
  3. Feature
  4. testKeyboardMenuBoundaries
  5. Feature
  6. testKeyboardMenuEnterOnOpen
  7. Feature
  8. testKeyboardEnterAfterMovePastTopItem
  9. Feature
  10. testKeyboardEnterAfterMovePastBottomItem
  11. Feature
  12. testKeyboardMenuEnterOnTopItemLandscape
  13. Feature
  14. testKeyboardMenuEnterOnTopItemPortrait
  15. hitEnterAndAssertAppMenuDismissed
  16. moveToBoundary
  17. pressKey
  18. getCurrentFocusedRow
  19. getCount

// Copyright 2014 The Chromium Authors. All rights reserved.
// 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.appmenu;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListPopupWindow;
import android.widget.ListView;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.shell.ChromeShellActivity;
import org.chromium.chrome.shell.ChromeShellActivity.AppMenuHandlerFactory;
import org.chromium.chrome.shell.ChromeShellTestBase;
import org.chromium.chrome.shell.R;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;

/**
 * Tests AppMenu popup
 */
public class AppMenuTest extends ChromeShellTestBase {
    private AppMenu mAppMenu;
    private AppMenuHandlerForTest mAppMenuHandler;

    /**
     * AppMenuHandler that will be used to intercept item selections for testing.
     */
    public static class AppMenuHandlerForTest extends AppMenuHandler {
        int mLastSelectedItemId = -1;

        /**
         * AppMenuHandler for intercepting options item selections.
         */
        public AppMenuHandlerForTest(Activity activity, AppMenuPropertiesDelegate delegate,
                int menuResourceId) {
            super(activity, delegate, menuResourceId);
        }

        @Override
        void onOptionsItemSelected(MenuItem item) {
            mLastSelectedItemId = item.getItemId();
        }

    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ChromeShellActivity.setAppMenuHandlerFactory(new AppMenuHandlerFactory() {
            @Override
            public AppMenuHandler getAppMenuHandler(Activity activity,
                    AppMenuPropertiesDelegate delegate, int menuResourceId) {
                mAppMenuHandler = new AppMenuHandlerForTest(activity, delegate, menuResourceId);
                return mAppMenuHandler;
            }
        });
        launchChromeShellWithBlankPage();
        assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());

        final View menuButton = getActivity().findViewById(R.id.menu_button);
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                menuButton.performClick();
            }
        });
        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
            @Override
            public boolean isSatisfied() {
                return getActivity().getAppMenuHandler().isAppMenuShowing();
            }
        }));
        mAppMenu = getActivity().getAppMenuHandler().getAppMenu();
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mAppMenu.getPopup().getListView().setSelection(0);
            }
        });
        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
            @Override
            public boolean isSatisfied() {
                return getCurrentFocusedRow() == 0;
            }
        }));
        getInstrumentation().waitForIdleSync();
    }

    /**
     * Test bounds when accessing the menu through the keyboard.
     * Make sure that the menu stays open when trying to move past the first and last items.
     */
    @SmallTest
    @Feature({"Browser", "Main"})
    public void testKeyboardMenuBoundaries() throws InterruptedException {
        moveToBoundary(false, true);
        assertEquals(getCount() - 1, getCurrentFocusedRow());
        moveToBoundary(true, true);
        assertEquals(0, getCurrentFocusedRow());
        moveToBoundary(false, true);
        assertEquals(getCount() - 1, getCurrentFocusedRow());
    }

    /**
     * Test that typing ENTER immediately opening the menu works.
     */
    @SmallTest
    @Feature({"Browser", "Main"})
    public void testKeyboardMenuEnterOnOpen() throws InterruptedException {
        hitEnterAndAssertAppMenuDismissed();
    }

    /**
     * Test that hitting ENTER past the top item doesn't crash Chrome.
     */
    @SmallTest
    @Feature({"Browser", "Main"})
    public void testKeyboardEnterAfterMovePastTopItem() throws InterruptedException {
        moveToBoundary(true, true);
        assertEquals(0, getCurrentFocusedRow());
        hitEnterAndAssertAppMenuDismissed();
    }

    /**
     * Test that hitting ENTER past the bottom item doesn't crash Chrome.
     * Catches regressions for http://crbug.com/181067
     */
    @SmallTest
    @Feature({"Browser", "Main"})
    public void testKeyboardEnterAfterMovePastBottomItem() throws InterruptedException {
        moveToBoundary(false, true);
        assertEquals(getCount() - 1, getCurrentFocusedRow());
        hitEnterAndAssertAppMenuDismissed();
    }

    /**
     * Test that hitting ENTER on the top item actually triggers the top item.
     * Catches regressions for https://crbug.com/191239 for shrunken menus.
     */
    @SmallTest
    @Feature({"Browser", "Main"})
    public void testKeyboardMenuEnterOnTopItemLandscape() throws InterruptedException {
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        moveToBoundary(true, false);
        assertEquals(0, getCurrentFocusedRow());
        hitEnterAndAssertAppMenuDismissed();
    }

    /**
     * Test that hitting ENTER on the top item doesn't crash Chrome.
     */
    @SmallTest
    @Feature({"Browser", "Main"})
    public void testKeyboardMenuEnterOnTopItemPortrait() throws InterruptedException {
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        moveToBoundary(true, false);
        assertEquals(0, getCurrentFocusedRow());
        hitEnterAndAssertAppMenuDismissed();
    }

    private void hitEnterAndAssertAppMenuDismissed() throws InterruptedException {
        getInstrumentation().waitForIdleSync();
        pressKey(KeyEvent.KEYCODE_ENTER);
        assertTrue("AppMenu did not dismiss",
                CriteriaHelper.pollForCriteria(new Criteria() {
                    @Override
                    public boolean isSatisfied() {
                        return !mAppMenuHandler.isAppMenuShowing();
                    }
                }));
    }

    private void moveToBoundary(boolean towardsTop, boolean movePast) throws InterruptedException {
        // Move to the boundary.
        final int end = towardsTop ? 0 : getCount() - 1;
        int increment = towardsTop ? -1 : 1;
        for (int index = getCurrentFocusedRow(); index != end; index += increment) {
            pressKey(towardsTop ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN);
            final int expectedPosition = index + increment;
            assertTrue("Focus did not move to the next menu item",
                    CriteriaHelper.pollForCriteria(new Criteria() {
                        @Override
                        public boolean isSatisfied() {
                            return getCurrentFocusedRow() == expectedPosition;
                        }
                    }));
        }

        // Try moving past it by one.
        if (movePast) {
            pressKey(towardsTop ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN);
            assertTrue("Focus moved past the edge menu item",
                    CriteriaHelper.pollForCriteria(new Criteria() {
                        @Override
                        public boolean isSatisfied() {
                            return getCurrentFocusedRow() == end;
                        }
                    }));
        }

        // The menu should stay open.
        assertTrue(mAppMenu.isShowing());
    }

    private void pressKey(final int keycode) {
        final View view = mAppMenu.getPopup().getListView();
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keycode));
                view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keycode));
            }
        });
        getInstrumentation().waitForIdleSync();
    }

    private int getCurrentFocusedRow() {
        ListPopupWindow popup = mAppMenu.getPopup();
        if (popup == null || popup.getListView() == null) return ListView.INVALID_POSITION;
        ListView listView = popup.getListView();
        return listView.getSelectedItemPosition();
    }

    private int getCount() {
        ListPopupWindow popup = mAppMenu.getPopup();
        if (popup == null || popup.getListView() == null) return 0;
        return popup.getListView().getCount();
    }
}

/* [<][>][^][v][top][bottom][index][help] */