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.FlakyTest; 5278f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikasimport android.support.test.filters.LargeTest; 534bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikasimport android.support.test.filters.MediumTest; 5444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheevimport android.support.v4.view.MenuItemCompat; 552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.v7.app.BaseInstrumentationTestCase; 562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.support.v7.appcompat.test.R; 5744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheevimport android.text.TextUtils; 582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MenuInflater; 592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MenuItem; 602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.MotionEvent; 612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.View; 622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.view.ViewParent; 632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.widget.Button; 642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovimport android.widget.FrameLayout; 652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6670058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Description; 6770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Matcher; 6870058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.Matchers; 6970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.hamcrest.TypeSafeMatcher; 70c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikasimport org.junit.After; 7170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.junit.Before; 7270058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikasimport org.junit.Test; 732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikovpublic class PopupMenuTest extends BaseInstrumentationTestCase<PopupTestActivity> { 752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Since PopupMenu doesn't expose any access to the underlying 762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // implementation (like ListPopupWindow.getListView), we're relying on the 7794940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov // class name of the list view from MenuPopupWindow that is being used 782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // in PopupMenu. This is not the cleanest, but it's not making any assumptions 792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // on the platform-specific details of the popup windows. 802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private static final String DROP_DOWN_CLASS_NAME = 8194940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov "android.support.v7.widget.MenuPopupWindow$MenuDropDownListView"; 822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private FrameLayout mContainer; 832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private Button mButton; 852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private PopupMenu mPopupMenu; 872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 88eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov private Resources mResources; 89eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov 90eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov private View mMainDecorView; 91eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov 922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public PopupMenuTest() { 932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov super(PopupTestActivity.class); 942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 960f3f71c172e87b05b0a6b766cf129f4be56179d5Chris Banes @Before 972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void setUp() throws Exception { 98eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov final PopupTestActivity activity = mActivityTestRule.getActivity(); 992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mContainer = (FrameLayout) activity.findViewById(R.id.container); 1002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mButton = (Button) mContainer.findViewById(R.id.test_button); 101eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov mResources = mActivityTestRule.getActivity().getResources(); 102eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov mMainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView(); 1032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 1042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 105c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas @After 106c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas public void tearDown() { 107c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas if (mPopupMenu != null) { 108c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 109c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas @Override 110c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas public void run() { 111c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas mPopupMenu.dismiss(); 112c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas } 113c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas }); 114c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas } 115c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas } 116c60b5de9dc03985a6aa9028dbeb983e1013402a0Aurimas Liutikas 1172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 1184bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 11944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev public void testBasicContent() throws Throwable { 1202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final Builder menuBuilder = new Builder(); 1212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 1222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 1232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 1242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov assertNotNull("Popup menu created", mPopupMenu); 12544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev 12644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev final MenuItem hightlightItem = mPopupMenu.getMenu().findItem(R.id.action_highlight); 12744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev assertEquals(mResources.getString(R.string.popup_menu_highlight_description), 12844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev MenuItemCompat.getContentDescription(hightlightItem)); 12944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev assertEquals(mResources.getString(R.string.popup_menu_highlight_tooltip), 13044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev MenuItemCompat.getTooltipText(hightlightItem)); 13144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev 13244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev final MenuItem editItem = mPopupMenu.getMenu().findItem(R.id.action_edit); 13344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev assertNotNull(MenuItemCompat.getContentDescription(hightlightItem)); 13444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev assertNotNull(MenuItemCompat.getTooltipText(hightlightItem)); 13544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev mActivityTestRule.runOnUiThread(new Runnable() { 13644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev @Override 13744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev public void run() { 13844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev MenuItemCompat.setContentDescription(editItem, 13944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev mResources.getString(R.string.popup_menu_edit_description)); 14044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev MenuItemCompat.setTooltipText(editItem, 14144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev mResources.getString(R.string.popup_menu_edit_tooltip)); 14244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 14344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev }); 14444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev 1452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing. 1462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Use a custom matcher to check the visibility of the drop down list view instead. 1472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))) 1482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .inRoot(isPlatformPopup()).check(matches(isDisplayed())); 1492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 1502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Note that MenuItem.isVisible() refers to the current "data" visibility state 1512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // and not the "on screen" visibility state. This is why we're testing the display 1522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // visibility of our main and sub menu items. 15370058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas // onData(anything()).atPosition(x) operates on the content of the popup. It is used to 15470058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas // scroll the popup if necessary to make sure the menu item views are visible. 15570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas onData(anything()).atPosition(0).check(matches(isDisplayed())); 156eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_highlight))) 157eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 15844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev .check(matches(isDisplayed())) 15944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev .check(matches(selfOrParentWithContentDescription( 16044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev mResources.getString(R.string.popup_menu_highlight_description)))); 16170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas onData(anything()).atPosition(1).check(matches(isDisplayed())); 162eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_edit))) 163eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 16444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev .check(matches(isDisplayed())) 16544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev .check(matches(selfOrParentWithContentDescription( 16644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev mResources.getString(R.string.popup_menu_edit_description)))); 16770058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas onData(anything()).atPosition(2).check(matches(isDisplayed())); 168eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_delete))) 169eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 1702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 17170058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas onData(anything()).atPosition(3).check(matches(isDisplayed())); 172eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_ignore))) 173eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 1742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 17570058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas onData(anything()).atPosition(4).check(matches(isDisplayed())); 176eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share))) 177eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 1782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 17970058e4bc399c316f1c7197b694ee54d0001b985Aurimas Liutikas onData(anything()).atPosition(5).check(matches(isDisplayed())); 180eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_print))) 181eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 1822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 1832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 1842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Share submenu items should not be visible 185eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_email))) 186eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 1872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(doesNotExist()); 188eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_circles))) 189eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 1902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(doesNotExist()); 1912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 1922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 1932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov /** 1942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Returns the location of our popup menu in its window. 1952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */ 1962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private int[] getPopupLocationInWindow() { 1972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final int[] location = new int[2]; 1982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))) 1992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .inRoot(isPlatformPopup()).perform(new ViewAction() { 2002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public Matcher<View> getConstraints() { 2022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return isDisplayed(); 2032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public String getDescription() { 2072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return "Popup matcher"; 2082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void perform(UiController uiController, View view) { 2122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov view.getLocationInWindow(location); 2132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 2152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return location; 2162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov /** 2192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Returns the location of our popup menu on the screen. 2202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */ 2212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private int[] getPopupLocationOnScreen() { 2222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final int[] location = new int[2]; 2232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))) 2242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .inRoot(isPlatformPopup()).perform(new ViewAction() { 2252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public Matcher<View> getConstraints() { 2272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return isDisplayed(); 2282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public String getDescription() { 2322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return "Popup matcher"; 2332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void perform(UiController uiController, View view) { 2372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov view.getLocationOnScreen(location); 2382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 2402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return location; 2412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov /** 2442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Returns the combined padding around the content of our popup menu. 2452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */ 2462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private Rect getPopupPadding() { 2472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final Rect result = new Rect(); 2482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))) 2492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .inRoot(isPlatformPopup()).perform(new ViewAction() { 2502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public Matcher<View> getConstraints() { 2522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return isDisplayed(); 2532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public String getDescription() { 2572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return "Popup matcher"; 2582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void perform(UiController uiController, View view) { 2622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Traverse the parent hierarchy and combine all their paddings 2632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov result.setEmpty(); 2642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final Rect current = new Rect(); 2652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov while (true) { 2662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov ViewParent parent = view.getParent(); 2672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov if (parent == null || !(parent instanceof View)) { 2682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return; 2692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov view = (View) parent; 2722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Drawable currentBackground = view.getBackground(); 2732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov if (currentBackground != null) { 2742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov currentBackground.getPadding(current); 2752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov result.left += current.left; 2762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov result.right += current.right; 2772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov result.top += current.top; 2782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov result.bottom += current.bottom; 2792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 2832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return result; 2842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov /** 2872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Returns a root matcher that matches roots that have window focus on their decor view. 2882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */ 2892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private static Matcher<Root> hasWindowFocus() { 2902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return new TypeSafeMatcher<Root>() { 2912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void describeTo(Description description) { 2932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov description.appendText("has window focus"); 2942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 2952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 2962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 2972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public boolean matchesSafely(Root root) { 2982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov View rootView = root.getDecorView(); 2992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return rootView.hasWindowFocus(); 3002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 3012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }; 3022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 3032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 30478f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikas @FlakyTest(bugId = 33669575) 3052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 30678f46d60d55f30fb384aa9da9eddb588f368e092Aurimas Liutikas @LargeTest 3072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void testAnchoring() { 3082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder(); 3092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 3102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 3122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final int[] anchorOnScreenXY = new int[2]; 3142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final int[] popupOnScreenXY = getPopupLocationOnScreen(); 3152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final int[] popupInWindowXY = getPopupLocationInWindow(); 3162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final Rect popupPadding = getPopupPadding(); 3172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mButton.getLocationOnScreen(anchorOnScreenXY); 3192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3202da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Allow for off-by-one mismatch in anchoring 3212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0], 3222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov popupOnScreenXY[0], 1); 323d7d01383deb3aac00ea54aab340cb43ff1ba3984Kirill Grouchnikov assertEquals("Anchoring Y", 324d7d01383deb3aac00ea54aab340cb43ff1ba3984Kirill Grouchnikov anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight() - popupPadding.top, 32594940fe6cff4af4f084b8cb5692e0501e3fe7912Kirill Grouchnikov popupOnScreenXY[1], 1); 3262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 3272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 3294bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 33042e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas public void testDismissalViaAPI() throws Throwable { 3312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder().withDismissListener(); 3322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 3332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 3352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Since PopupMenu is not a View, we can't use Espresso's view actions to invoke 3372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // the dismiss() API 33842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityTestRule.runOnUiThread(new Runnable() { 3392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 3402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void run() { 3412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mPopupMenu.dismiss(); 3422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 3432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 3442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 34531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu); 34631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 3472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing. 3482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Use a custom matcher to check the visibility of the drop down list view instead. 3492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist()); 3502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 3512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 3534bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 3542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void testDismissalViaTouch() throws Throwable { 3552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder().withDismissListener(); 3562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 3572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 3592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Determine the location of the popup on the screen so that we can emulate 3612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // a tap outside of its bounds to dismiss it 3622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final int[] popupOnScreenXY = getPopupLocationOnScreen(); 3632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final Rect popupPadding = getPopupPadding(); 3642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3652da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov int emulatedTapX = popupOnScreenXY[0] - popupPadding.left - 20; 3672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov int emulatedTapY = popupOnScreenXY[1] + popupPadding.top + 20; 3682da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // The logic below uses Instrumentation to emulate a tap outside the bounds of the 3702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // displayed popup menu. This tap is then treated by the framework to be "split" as 3712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying 3722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // view root if the popup is not modal. 3732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // It is not correct to emulate these two sequences separately in the test, as it 3742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // wouldn't emulate the user-facing interaction for this test. Note that usage 3752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // of Instrumentation is necessary here since Espresso's actions operate at the level 3762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as 3772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // that would require emulation of two separate sequences as well. 3782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 379eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 3802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Inject DOWN event 3822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov long downTime = SystemClock.uptimeMillis(); 3832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov MotionEvent eventDown = MotionEvent.obtain( 3842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov downTime, downTime, MotionEvent.ACTION_DOWN, emulatedTapX, emulatedTapY, 1); 3852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov instrumentation.sendPointerSync(eventDown); 3862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Inject MOVE event 3882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov long moveTime = SystemClock.uptimeMillis(); 3892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov MotionEvent eventMove = MotionEvent.obtain( 3902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedTapX, emulatedTapY, 1); 3912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov instrumentation.sendPointerSync(eventMove); 3922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Inject UP event 3942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov long upTime = SystemClock.uptimeMillis(); 3952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov MotionEvent eventUp = MotionEvent.obtain( 3962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov upTime, upTime, MotionEvent.ACTION_UP, emulatedTapX, emulatedTapY, 1); 3972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov instrumentation.sendPointerSync(eventUp); 3982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 3992da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Wait for the system to process all events in the queue 4002da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov instrumentation.waitForIdleSync(); 4012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // At this point our popup should not be showing and should have notified its 4032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // dismiss listener 40431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu); 4052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist()); 4062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 4072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 4094bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 4102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void testSimpleMenuItemClickViaEvent() { 4112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder().withMenuItemClickListener(); 4122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 4132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 4152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 41631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that our menu item click listener hasn't been called yet 41731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class)); 4182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 419eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_delete))) 420eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 4212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .perform(click()); 42231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 42331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that out menu item click listener has been called with the expected menu item 42431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick( 42531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_delete)); 4262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4272da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Popup menu should be automatically dismissed on selecting an item 4282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist()); 4292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 4302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 4324bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 43342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas public void testSimpleMenuItemClickViaAPI() throws Throwable { 4342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder().withMenuItemClickListener(); 4352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 4362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 4382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 43931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that our menu item click listener hasn't been called yet 44031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class)); 44131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 44242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityTestRule.runOnUiThread(new Runnable() { 4432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 4442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void run() { 4452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mPopupMenu.getMenu().performIdentifierAction(R.id.action_highlight, 0); 4462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 4472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 4482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 44931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that out menu item click listener has been called with the expected menu item 45031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick( 45131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_highlight)); 4522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Popup menu should be automatically dismissed on selecting an item 4542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist()); 4552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 4562da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 4584bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 4592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void testSubMenuClicksViaEvent() throws Throwable { 4602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder().withMenuItemClickListener(); 4612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 4622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 4642da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 46531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that our menu item click listener hasn't been called yet 46631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class)); 4672da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 468eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share))) 469eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(withDecorView(not(is(mMainDecorView)))) 4702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .perform(click()); 47131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 47231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that out menu item click listener has been called with the expected menu item 47331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick( 47431c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_share)); 4752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Sleep for a bit to allow the menu -> submenu transition to complete 4772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Thread.sleep(1000); 4782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4792da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // At this point we should now have our sub-menu displayed. At this point on newer 4802da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // platform versions (L+) we have two view roots on the screen - one for the main popup 4812da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // menu and one for the submenu that has just been activated. If we only use the 4822da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // logic based on decor view, Espresso will time out on waiting for the first root 4832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // to acquire window focus. This is why from this point on in this test we are using 4842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // two root conditions to detect the submenu - one with decor view not being the same 4852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // as the decor view of our main activity window, and the other that checks for window 4862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // focus. 4872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing. 4892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Use a custom matcher to check the visibility of the drop down list view instead. 4902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))) 491eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 4922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 4932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Note that MenuItem.isVisible() refers to the current "data" visibility state 4952da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // and not the "on screen" visibility state. This is why we're testing the display 4962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // visibility of our main and sub menu items. 4972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 4982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Share submenu items should now be visible 499eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_email))) 500eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 5012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 502eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_circles))) 503eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 5042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 5052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Now click an item in the sub-menu 507eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_circles))) 508eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 5092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .perform(click()); 51031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 51131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that out menu item click listener has been called with the expected menu item 51231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick( 51331c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_share_circles)); 5142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Popup menu should be automatically dismissed on selecting an item in the submenu 5162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist()); 5172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 5182da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Test 5204bb57b2c7c5e8fa607119bd5089535d5862cb979Aurimas Liutikas @MediumTest 5212da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void testSubMenuClicksViaAPI() throws Throwable { 5222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Builder menuBuilder = new Builder().withMenuItemClickListener(); 5232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuBuilder.wireToActionButton(); 5242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5252da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withId(R.id.test_button)).perform(click()); 5262da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 52731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that our menu item click listener hasn't been called yet 52831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class)); 52931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 53042e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityTestRule.runOnUiThread(new Runnable() { 5312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 5322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void run() { 5332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mPopupMenu.getMenu().performIdentifierAction(R.id.action_share, 0); 5342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 5352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 5362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 53731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that out menu item click listener has been called with the expected menu item 53831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick( 53931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_share)); 5402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Sleep for a bit to allow the menu -> submenu transition to complete 5422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov Thread.sleep(1000); 5432da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5442da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // At this point we should now have our sub-menu displayed. At this point on newer 5452da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // platform versions (L+) we have two view roots on the screen - one for the main popup 5462da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // menu and one for the submenu that has just been activated. If we only use the 5472da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // logic based on decor view, Espresso will time out on waiting for the first root 5482da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // to acquire window focus. This is why from this point on in this test we are using 5492da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // two root conditions to detect the submenu - one with decor view not being the same 5502da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // as the decor view of our main activity window, and the other that checks for window 5512da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // focus. 5522da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5532da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing. 5542da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Use a custom matcher to check the visibility of the drop down list view instead. 5552da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))) 556eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 5572da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 5582da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5592da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Note that MenuItem.isVisible() refers to the current "data" visibility state 5602da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // and not the "on screen" visibility state. This is why we're testing the display 5612da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // visibility of our main and sub menu items. 5622da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5632da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Share submenu items should now be visible 564eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_email))) 565eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 5662da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 567eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov onView(withText(mResources.getString(R.string.popup_menu_share_circles))) 568eb673c3e06976775075d489cdd53f65208e5b551Kirill Grouchnikov .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus())) 5692da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov .check(matches(isDisplayed())); 5702da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5712da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Now ask the share submenu to perform an action on its specific menu item 57242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas mActivityTestRule.runOnUiThread(new Runnable() { 5732da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 5742da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void run() { 5752da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_share).getSubMenu(). 5762da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov performIdentifierAction(R.id.action_share_email, 0); 5772da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 5782da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 57931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov 58031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Verify that out menu item click listener has been called with the expected menu item 58131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick( 58231c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.getMenu().findItem(R.id.action_share_email)); 5832da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5842da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Popup menu should be automatically dismissed on selecting an item in the submenu 5852da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist()); 5862da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 5872da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 5882da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov /** 5892da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * Inner helper class to configure an instance of <code>PopupMenu</code> for the 5902da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * specific test. The main reason for its existence is that once a popup menu is shown 5912da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * with the show() method, most of its configuration APIs are no-ops. This means that 5922da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * we can't add logic that is specific to a certain test once it's shown and we have a 5932da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov * reference to a displayed PopupMenu. 5942da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov */ 59531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov public class Builder { 5962da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private boolean mHasDismissListener; 5972da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private boolean mHasMenuItemClickListener; 5982da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 59931c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener; 60031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov private PopupMenu.OnDismissListener mOnDismissListener; 6012da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6022da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public Builder withMenuItemClickListener() { 6032da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mHasMenuItemClickListener = true; 6042da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return this; 6052da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6062da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6072da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public Builder withDismissListener() { 6082da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mHasDismissListener = true; 6092da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov return this; 6102da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6112da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6122da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov private void show() { 6132da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mPopupMenu = new PopupMenu(mContainer.getContext(), mButton); 6142da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov final MenuInflater menuInflater = mPopupMenu.getMenuInflater(); 6152da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu()); 6162da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6172da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov if (mHasMenuItemClickListener) { 61831c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Register a mock listener to be notified when a menu item in our popup menu has 6192da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // been clicked. 62031c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mOnMenuItemClickListener = mock(PopupMenu.OnMenuItemClickListener.class); 62131c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener); 6222da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6232da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6242da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov if (mHasDismissListener) { 62531c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov // Register a mock listener to be notified when our popup menu is dismissed. 62631c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mOnDismissListener = mock(PopupMenu.OnDismissListener.class); 62731c1690d6ed7ebec75bd9f6c0c74ac29528bd2f8Kirill Grouchnikov mPopupMenu.setOnDismissListener(mOnDismissListener); 6282da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6292da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6302da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov // Show the popup menu 6312da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mPopupMenu.show(); 6322da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6332da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov 6342da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void wireToActionButton() { 6352da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov mButton.setOnClickListener(new View.OnClickListener() { 6362da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov @Override 6372da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov public void onClick(View v) { 6382da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov show(); 6392da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6402da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov }); 6412da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 6422da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov } 64344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev 64444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev private static Matcher<View> selfOrParentWithContentDescription(final CharSequence expected) { 64544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev return new TypeSafeMatcher<View>() { 64644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev @Override 64744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev public void describeTo(Description description) { 64844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev description.appendText("self of parent has content description: " + expected); 64944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 65044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev 65144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev @Override 65244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev public boolean matchesSafely(View view) { 65344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev return TextUtils.equals(expected, getSelfOrParentContentDescription(view)); 65444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 65544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev 65644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev private CharSequence getSelfOrParentContentDescription(View view) { 65744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev while (view != null) { 65844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev final CharSequence contentDescription = view.getContentDescription(); 65944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev if (contentDescription != null) { 66044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev return contentDescription; 66144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 66244ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev final ViewParent parent = view.getParent(); 66344ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev if (!(parent instanceof View)) { 66444ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev break; 66544ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 66644ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev view = (View) parent; 66744ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 66844ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev return null; 66944ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 67044ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev }; 67144ee68ea8e537af604d731f47cb7f0ac0ffb4903Vladislav Kaznacheev } 6722da48305c96b4cd11dda7f480003b1559c774ed7Kirill Grouchnikov} 673