12da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov/*
22da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Copyright (C) 2016 The Android Open Source Project
32da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *
42da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Licensed under the Apache License, Version 2.0 (the "License");
52da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * you may not use this file except in compliance with the License.
62da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * You may obtain a copy of the License at
72da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *
82da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *      http://www.apache.org/licenses/LICENSE-2.0
92da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov *
102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Unless required by applicable law or agreed to in writing, software
112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * distributed under the License is distributed on an "AS IS" BASIS,
122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * See the License for the specific language governing permissions and
142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * limitations under the License.
152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */
162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovpackage android.support.v7.widget;
172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.Espresso.onData;
1970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.Espresso.onView;
2070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.action.ViewActions.click;
2170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
2270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.assertion.ViewAssertions.matches;
2370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
2470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.RootMatchers.withDecorView;
2570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
2670058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.withClassName;
2770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.withId;
2870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static android.support.test.espresso.matcher.ViewMatchers.withText;
2970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas
3070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.Matchers.allOf;
3170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.Matchers.anything;
3270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.core.Is.is;
3370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.hamcrest.core.IsNot.not;
3470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.junit.Assert.assertEquals;
3570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.junit.Assert.assertNotNull;
3670058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.any;
3770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.mock;
3870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.never;
3970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.times;
4070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport static org.mockito.Mockito.verify;
410f3f71c172e87b05b0a6b766cf129f4be56179d5Chris Banes
422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.app.Instrumentation;
432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.content.res.Resources;
442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.graphics.Rect;
452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.graphics.drawable.Drawable;
462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.os.SystemClock;
4770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport android.support.test.InstrumentationRegistry;
482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.test.espresso.Root;
492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.test.espresso.UiController;
502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.test.espresso.ViewAction;
5178f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikasimport android.support.test.filters.LargeTest;
524bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikasimport android.support.test.filters.MediumTest;
538d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikasimport android.support.test.rule.ActivityTestRule;
548d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikasimport android.support.test.runner.AndroidJUnit4;
552748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikasimport android.support.testutils.PollingCheck;
5644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheevimport android.support.v4.view.MenuItemCompat;
572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.v7.appcompat.test.R;
5844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheevimport android.text.TextUtils;
592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MenuInflater;
602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MenuItem;
612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MotionEvent;
622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.View;
632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.ViewParent;
642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.widget.Button;
652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.widget.FrameLayout;
662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Description;
6870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Matcher;
6970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Matchers;
7070058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.TypeSafeMatcher;
71c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikasimport org.junit.After;
7270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.junit.Before;
738d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikasimport org.junit.Rule;
7470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.junit.Test;
758d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikasimport org.junit.runner.RunWith;
768d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas
778d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas@RunWith(AndroidJUnit4.class)
788d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikaspublic class PopupMenuTest {
798d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas    @Rule
808d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas    public final ActivityTestRule<PopupTestActivity> mActivityTestRule =
818d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas            new ActivityTestRule<>(PopupTestActivity.class);
822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // Since PopupMenu doesn't expose any access to the underlying
842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // implementation (like ListPopupWindow.getListView), we're relying on the
8594940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov    // class name of the list view from MenuPopupWindow that is being used
862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // in PopupMenu. This is not the cleanest, but it's not making any assumptions
872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    // on the platform-specific details of the popup windows.
882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private static final String DROP_DOWN_CLASS_NAME =
8994940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov            "android.support.v7.widget.MenuPopupWindow$MenuDropDownListView";
902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private FrameLayout mContainer;
912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private Button mButton;
932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private PopupMenu mPopupMenu;
952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
96eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov    private Resources mResources;
97eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov
98eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov    private View mMainDecorView;
99eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov
1000f3f71c172e87b05b0a6b766cf129f4be56179d5Chris Banes    @Before
1012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void setUp() throws Exception {
102eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        final PopupTestActivity activity = mActivityTestRule.getActivity();
1038d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas        mContainer = activity.findViewById(R.id.container);
1048d3b808ab4720ef4e9d58f2bba4e31f741d3898cAurimas Liutikas        mButton = mContainer.findViewById(R.id.test_button);
105eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        mResources = mActivityTestRule.getActivity().getResources();
106eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        mMainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
1072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
1082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
109c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas    @After
110c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas    public void tearDown() {
111c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas        if (mPopupMenu != null) {
112c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
113c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                @Override
114c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                public void run() {
115c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                    mPopupMenu.dismiss();
116c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas                }
117c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas            });
118c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas        }
119c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas    }
120c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas
1212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
1224bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
12344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev    public void testBasicContent() throws Throwable {
1242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Builder menuBuilder = new Builder();
1252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
1262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
1282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        assertNotNull("Popup menu created", mPopupMenu);
12944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
13044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        final MenuItem hightlightItem = mPopupMenu.getMenu().findItem(R.id.action_highlight);
13144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertEquals(mResources.getString(R.string.popup_menu_highlight_description),
13244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.getContentDescription(hightlightItem));
13344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertEquals(mResources.getString(R.string.popup_menu_highlight_tooltip),
13444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.getTooltipText(hightlightItem));
13544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
13644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        final MenuItem editItem = mPopupMenu.getMenu().findItem(R.id.action_edit);
13744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertNotNull(MenuItemCompat.getContentDescription(hightlightItem));
13844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        assertNotNull(MenuItemCompat.getTooltipText(hightlightItem));
13944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        mActivityTestRule.runOnUiThread(new Runnable() {
14044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            @Override
14144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            public void run() {
14244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.setContentDescription(editItem,
14344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_edit_description));
14444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                MenuItemCompat.setTooltipText(editItem,
14544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_edit_tooltip));
14644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
14744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        });
14844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
1492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
1502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
1512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
1522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).check(matches(isDisplayed()));
1532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Note that MenuItem.isVisible() refers to the current "data" visibility state
1552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // and not the "on screen" visibility state. This is why we're testing the display
1562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // visibility of our main and sub menu items.
15770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        // onData(anything()).atPosition(x) operates on the content of the popup. It is used to
15870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        // scroll the popup if necessary to make sure the menu item views are visible.
15970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(0).check(matches(isDisplayed()));
160eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_highlight)))
161eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
16244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(isDisplayed()))
16344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(selfOrParentWithContentDescription(
16444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_highlight_description))));
16570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(1).check(matches(isDisplayed()));
166eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_edit)))
167eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
16844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(isDisplayed()))
16944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                .check(matches(selfOrParentWithContentDescription(
17044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        mResources.getString(R.string.popup_menu_edit_description))));
17170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(2).check(matches(isDisplayed()));
172eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_delete)))
173eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
17570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(3).check(matches(isDisplayed()));
176eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_ignore)))
177eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
17970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(4).check(matches(isDisplayed()));
180eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share)))
181eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
18370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas        onData(anything()).atPosition(5).check(matches(isDisplayed()));
184eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_print)))
185eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
1872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Share submenu items should not be visible
189eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
190eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(doesNotExist());
192eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
193eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
1942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(doesNotExist());
1952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
1962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
1972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
1982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns the location of our popup menu in its window.
1992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private int[] getPopupLocationInWindow() {
2012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] location = new int[2];
2022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
2032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).perform(new ViewAction() {
2042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public Matcher<View> getConstraints() {
2062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return isDisplayed();
2072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public String getDescription() {
2112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return "Popup matcher";
2122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void perform(UiController uiController, View view) {
2162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                view.getLocationInWindow(location);
2172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
2192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return location;
2202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
2212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
2232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns the location of our popup menu on the screen.
2242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private int[] getPopupLocationOnScreen() {
2262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] location = new int[2];
2272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
2282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).perform(new ViewAction() {
2292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public Matcher<View> getConstraints() {
2312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return isDisplayed();
2322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public String getDescription() {
2362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return "Popup matcher";
2372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void perform(UiController uiController, View view) {
2412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                view.getLocationOnScreen(location);
2422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
2442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return location;
2452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
2462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
2482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns the combined padding around the content of our popup menu.
2492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private Rect getPopupPadding() {
2512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Rect result = new Rect();
2522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
2532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .inRoot(isPlatformPopup()).perform(new ViewAction() {
2542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public Matcher<View> getConstraints() {
2562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return isDisplayed();
2572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public String getDescription() {
2612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return "Popup matcher";
2622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void perform(UiController uiController, View view) {
2662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                // Traverse the parent hierarchy and combine all their paddings
2672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                result.setEmpty();
2682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                final Rect current = new Rect();
2692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                while (true) {
2702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    ViewParent parent = view.getParent();
2712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    if (parent == null || !(parent instanceof View)) {
2722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        return;
2732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    }
2742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    view = (View) parent;
2762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    Drawable currentBackground = view.getBackground();
2772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    if (currentBackground != null) {
2782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        currentBackground.getPadding(current);
2792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.left += current.left;
2802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.right += current.right;
2812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.top += current.top;
2822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        result.bottom += current.bottom;
2832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    }
2842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                }
2852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
2872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return result;
2882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
2892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
2902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
2912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Returns a root matcher that matches roots that have window focus on their decor view.
2922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
2932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    private static Matcher<Root> hasWindowFocus() {
2942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        return new TypeSafeMatcher<Root>() {
2952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
2962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void describeTo(Description description) {
2972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                description.appendText("has window focus");
2982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
2992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
3012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public boolean matchesSafely(Root root) {
3022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                View rootView = root.getDecorView();
3032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                return rootView.hasWindowFocus();
3042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
3052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        };
3062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
3072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
30978f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikas    @LargeTest
3102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testAnchoring() {
3112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder();
3122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
3132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
3152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] anchorOnScreenXY = new int[2];
3172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] popupOnScreenXY = getPopupLocationOnScreen();
3182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] popupInWindowXY = getPopupLocationInWindow();
3192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Rect popupPadding = getPopupPadding();
3202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        mButton.getLocationOnScreen(anchorOnScreenXY);
3222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Allow for off-by-one mismatch in anchoring
3242748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas
3252748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas        // See if Anchoring Y gets animated to the expected value.
3262748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas        PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
3272748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas            @Override
3282748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas            public boolean canProceed() {
3292748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas                final int y = anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight()
3302748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas                        - popupPadding.top;
3312748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas                return Math.abs(y - popupOnScreenXY[1]) <= 1;
3322748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas            }
3332748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas        });
3342748072a718079fc19991ccfe66149e784a4ddf1Aurimas Liutikas
3352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0],
3362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                popupOnScreenXY[0], 1);
337d7d01383deb3aac00ea54aab340cb43ff1ba3984Kirill Grouchnikov        assertEquals("Anchoring Y",
338d7d01383deb3aac00ea54aab340cb43ff1ba3984Kirill Grouchnikov                anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight() - popupPadding.top,
33994940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov                popupOnScreenXY[1], 1);
3402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
3412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
3434bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
34442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testDismissalViaAPI() throws Throwable {
3452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withDismissListener();
3462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
3472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
3492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Since PopupMenu is not a View, we can't use Espresso's view actions to invoke
3512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // the dismiss() API
35242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
3532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
3542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
3552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.dismiss();
3562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
3572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
3582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
35931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
36031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
3612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
3622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
3632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
3642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
3652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
3674bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
3682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testDismissalViaTouch() throws Throwable {
3692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withDismissListener();
3702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
3712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
3732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Determine the location of the popup on the screen so that we can emulate
3752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // a tap outside of its bounds to dismiss it
3762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final int[] popupOnScreenXY = getPopupLocationOnScreen();
3772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        final Rect popupPadding = getPopupPadding();
3782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        int emulatedTapX = popupOnScreenXY[0] - popupPadding.left - 20;
3812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        int emulatedTapY = popupOnScreenXY[1] + popupPadding.top + 20;
3822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // The logic below uses Instrumentation to emulate a tap outside the bounds of the
3842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // displayed popup menu. This tap is then treated by the framework to be "split" as
3852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying
3862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // view root if the popup is not modal.
3872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // It is not correct to emulate these two sequences separately in the test, as it
3882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // wouldn't emulate the user-facing interaction for this test. Note that usage
3892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // of Instrumentation is necessary here since Espresso's actions operate at the level
3902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as
3912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // that would require emulation of two separate sequences as well.
3922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
393eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
3942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
3952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Inject DOWN event
3962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        long downTime = SystemClock.uptimeMillis();
3972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        MotionEvent eventDown = MotionEvent.obtain(
3982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                downTime, downTime, MotionEvent.ACTION_DOWN, emulatedTapX, emulatedTapY, 1);
3992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.sendPointerSync(eventDown);
4002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Inject MOVE event
4022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        long moveTime = SystemClock.uptimeMillis();
4032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        MotionEvent eventMove = MotionEvent.obtain(
4042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedTapX, emulatedTapY, 1);
4052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.sendPointerSync(eventMove);
4062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Inject UP event
4082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        long upTime = SystemClock.uptimeMillis();
4092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        MotionEvent eventUp = MotionEvent.obtain(
4102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                upTime, upTime, MotionEvent.ACTION_UP, emulatedTapX, emulatedTapY, 1);
4112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.sendPointerSync(eventUp);
4122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Wait for the system to process all events in the queue
4142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        instrumentation.waitForIdleSync();
4152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // At this point our popup should not be showing and should have notified its
4172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // dismiss listener
41831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
4192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
4202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
4212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
4234bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
4242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testSimpleMenuItemClickViaEvent() {
4252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
4262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
4272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
4292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
43031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
43131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
4322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
433eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_delete)))
434eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
4352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .perform(click());
43631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
43731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
43831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
43931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_delete));
4402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item
4422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
4432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
4442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
4464bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
44742e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testSimpleMenuItemClickViaAPI() throws Throwable {
4482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
4492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
4502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
4522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
45331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
45431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
45531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
45642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
4572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
4582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
4592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.getMenu().performIdentifierAction(R.id.action_highlight, 0);
4602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
4612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
4622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
46331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
46431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
46531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_highlight));
4662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item
4682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
4692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
4702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
4724bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
4732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testSubMenuClicksViaEvent() throws Throwable {
4742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
4752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
4762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
4782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
47931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
48031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
4812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
482eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share)))
483eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(withDecorView(not(is(mMainDecorView))))
4842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .perform(click());
48531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
48631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
48731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
48831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share));
4892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Sleep for a bit to allow the menu -> submenu transition to complete
4912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Thread.sleep(1000);
4922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
4932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // At this point we should now have our sub-menu displayed. At this point on newer
4942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // platform versions (L+) we have two view roots on the screen - one for the main popup
4952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // menu and one for the submenu that has just been activated. If we only use the
4962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // logic based on decor view, Espresso will time out on waiting for the first root
4972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // to acquire window focus. This is why from this point on in this test we are using
4982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // two root conditions to detect the submenu - one with decor view not being the same
4992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // as the decor view of our main activity window, and the other that checks for window
5002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // focus.
5012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
5032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
5042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
505eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Note that MenuItem.isVisible() refers to the current "data" visibility state
5092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // and not the "on screen" visibility state. This is why we're testing the display
5102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // visibility of our main and sub menu items.
5112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Share submenu items should now be visible
513eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
514eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
516eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
517eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Now click an item in the sub-menu
521eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
522eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .perform(click());
52431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
52531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
52631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
52731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share_circles));
5282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item in the submenu
5302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
5312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
5322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    @Test
5344bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas    @MediumTest
5352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    public void testSubMenuClicksViaAPI() throws Throwable {
5362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Builder menuBuilder = new Builder().withMenuItemClickListener();
5372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        menuBuilder.wireToActionButton();
5382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withId(R.id.test_button)).perform(click());
5402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
54131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that our menu item click listener hasn't been called yet
54231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
54331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
54442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
5452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
5462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
5472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.getMenu().performIdentifierAction(R.id.action_share, 0);
5482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
5492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
5502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
55131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
55231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
55331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share));
5542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Sleep for a bit to allow the menu -> submenu transition to complete
5562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        Thread.sleep(1000);
5572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // At this point we should now have our sub-menu displayed. At this point on newer
5592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // platform versions (L+) we have two view roots on the screen - one for the main popup
5602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // menu and one for the submenu that has just been activated. If we only use the
5612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // logic based on decor view, Espresso will time out on waiting for the first root
5622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // to acquire window focus. This is why from this point on in this test we are using
5632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // two root conditions to detect the submenu - one with decor view not being the same
5642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // as the decor view of our main activity window, and the other that checks for window
5652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // focus.
5662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
5682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Use a custom matcher to check the visibility of the drop down list view instead.
5692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
570eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Note that MenuItem.isVisible() refers to the current "data" visibility state
5742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // and not the "on screen" visibility state. This is why we're testing the display
5752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // visibility of our main and sub menu items.
5762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Share submenu items should now be visible
578eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_email)))
579eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
581eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov        onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
582eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov                .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
5832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                .check(matches(isDisplayed()));
5842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Now ask the share submenu to perform an action on its specific menu item
58642e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
5872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            @Override
5882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            public void run() {
5892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share).getSubMenu().
5902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                        performIdentifierAction(R.id.action_share_email, 0);
5912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
5922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        });
59331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov
59431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        // Verify that out menu item click listener has been called with the expected menu item
59531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
59631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.getMenu().findItem(R.id.action_share_email));
5972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
5982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        // Popup menu should be automatically dismissed on selecting an item in the submenu
5992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
6002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
6012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    /**
6032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * Inner helper class to configure an instance of <code>PopupMenu</code> for the
6042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * specific test. The main reason for its existence is that once a popup menu is shown
6052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * with the show() method, most of its configuration APIs are no-ops. This means that
6062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * we can't add logic that is specific to a certain test once it's shown and we have a
6072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     * reference to a displayed PopupMenu.
6082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov     */
60931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov    public class Builder {
6102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        private boolean mHasDismissListener;
6112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        private boolean mHasMenuItemClickListener;
6122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
61331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener;
61431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov        private PopupMenu.OnDismissListener mOnDismissListener;
6152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        public Builder withMenuItemClickListener() {
6172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mHasMenuItemClickListener = true;
6182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            return this;
6192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        public Builder withDismissListener() {
6222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mHasDismissListener = true;
6232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            return this;
6242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        private void show() {
6272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mPopupMenu = new PopupMenu(mContainer.getContext(), mButton);
6282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            final MenuInflater menuInflater = mPopupMenu.getMenuInflater();
6292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu());
6302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            if (mHasMenuItemClickListener) {
63231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                // Register a mock listener to be notified when a menu item in our popup menu has
6332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                // been clicked.
63431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mOnMenuItemClickListener = mock(PopupMenu.OnMenuItemClickListener.class);
63531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener);
6362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
6372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            if (mHasDismissListener) {
63931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                // Register a mock listener to be notified when our popup menu is dismissed.
64031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mOnDismissListener = mock(PopupMenu.OnDismissListener.class);
64131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov                mPopupMenu.setOnDismissListener(mOnDismissListener);
6422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            }
6432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            // Show the popup menu
6452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mPopupMenu.show();
6462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov
6482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        public void wireToActionButton() {
6492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            mButton.setOnClickListener(new View.OnClickListener() {
6502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                @Override
6512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                public void onClick(View v) {
6522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                    show();
6532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov                }
6542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov            });
6552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov        }
6562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov    }
65744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
65844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev    private static Matcher<View> selfOrParentWithContentDescription(final CharSequence expected) {
65944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        return new TypeSafeMatcher<View>() {
66044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            @Override
66144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            public void describeTo(Description description) {
66244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                description.appendText("self of parent has content description: " + expected);
66344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
66444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
66544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            @Override
66644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            public boolean matchesSafely(View view) {
66744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                return TextUtils.equals(expected, getSelfOrParentContentDescription(view));
66844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
66944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev
67044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            private CharSequence getSelfOrParentContentDescription(View view) {
67144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                while (view != null) {
67244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    final CharSequence contentDescription = view.getContentDescription();
67344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    if (contentDescription != null) {
67444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        return contentDescription;
67544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    }
67644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    final ViewParent parent = view.getParent();
67744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    if (!(parent instanceof View)) {
67844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                        break;
67944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    }
68044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                    view = (View) parent;
68144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                }
68244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev                return null;
68344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev            }
68444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev        };
68544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev    }
6862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov}
687