1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package android.support.design.widget; 17 18import android.content.res.Resources; 19import android.graphics.drawable.GradientDrawable; 20import android.support.annotation.ColorInt; 21import android.support.annotation.IdRes; 22import android.support.design.test.R; 23import android.support.v4.content.res.ResourcesCompat; 24import android.support.v4.view.GravityCompat; 25import android.support.v4.widget.DrawerLayout; 26import android.support.v7.widget.RecyclerView; 27import android.support.v7.widget.SwitchCompat; 28import android.test.suitebuilder.annotation.SmallTest; 29import android.view.LayoutInflater; 30import android.view.Menu; 31import android.view.MenuItem; 32import android.view.View; 33import org.hamcrest.Matcher; 34import org.junit.Before; 35import org.junit.Test; 36 37import java.util.HashMap; 38import java.util.Map; 39 40import static android.support.design.testutils.DrawerLayoutActions.closeDrawer; 41import static android.support.design.testutils.DrawerLayoutActions.openDrawer; 42import static android.support.design.testutils.NavigationViewActions.*; 43import static android.support.design.testutils.TestUtilsMatchers.*; 44import static android.support.test.espresso.Espresso.onView; 45import static android.support.test.espresso.action.ViewActions.click; 46import static android.support.test.espresso.assertion.ViewAssertions.matches; 47import static android.support.test.espresso.matcher.ViewMatchers.*; 48import static org.hamcrest.core.AllOf.allOf; 49import static org.junit.Assert.*; 50import static org.mockito.Mockito.*; 51 52public class NavigationViewTest 53 extends BaseInstrumentationTestCase<NavigationViewActivity> { 54 private static final int[] MENU_CONTENT_ITEM_IDS = { R.id.destination_home, 55 R.id.destination_profile, R.id.destination_people, R.id.destination_settings }; 56 private Map<Integer, String> mMenuStringContent; 57 58 private DrawerLayout mDrawerLayout; 59 60 private NavigationView mNavigationView; 61 62 public NavigationViewTest() { 63 super(NavigationViewActivity.class); 64 } 65 66 @Before 67 public void setUp() throws Exception { 68 final NavigationViewActivity activity = mActivityTestRule.getActivity(); 69 mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout); 70 mNavigationView = (NavigationView) mDrawerLayout.findViewById(R.id.start_drawer); 71 72 // Close the drawer to reset the state for the next test 73 onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START)); 74 75 final Resources res = activity.getResources(); 76 mMenuStringContent = new HashMap<>(MENU_CONTENT_ITEM_IDS.length); 77 mMenuStringContent.put(R.id.destination_home, res.getString(R.string.navigate_home)); 78 mMenuStringContent.put(R.id.destination_profile, res.getString(R.string.navigate_profile)); 79 mMenuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people)); 80 mMenuStringContent.put(R.id.destination_settings, 81 res.getString(R.string.navigate_settings)); 82 } 83 84 @Test 85 @SmallTest 86 public void testBasics() { 87 // Open our drawer 88 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 89 90 // Check the contents of the Menu object 91 final Menu menu = mNavigationView.getMenu(); 92 assertNotNull("Menu should not be null", menu); 93 assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length, 94 menu.size()); 95 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 96 final MenuItem currItem = menu.getItem(i); 97 assertEquals("ID for Item #" + i, MENU_CONTENT_ITEM_IDS[i], currItem.getItemId()); 98 } 99 100 // Check that we have the expected menu items in our NavigationView 101 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 102 onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), 103 isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed())); 104 } 105 } 106 107 @Test 108 @SmallTest 109 public void testTextAppearance() { 110 // Open our drawer 111 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 112 113 final Resources res = mActivityTestRule.getActivity().getResources(); 114 final int defaultTextSize = res.getDimensionPixelSize(R.dimen.text_medium_size); 115 116 // Check the default style of the menu items in our NavigationView 117 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 118 onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), 119 isDescendantOfA(withId(R.id.start_drawer)))).check( 120 matches(withTextSize(defaultTextSize))); 121 } 122 123 // Set a new text appearance on our NavigationView 124 onView(withId(R.id.start_drawer)).perform(setItemTextAppearance(R.style.TextSmallStyle)); 125 126 // And check that all the menu items have the new style 127 final int newTextSize = res.getDimensionPixelSize(R.dimen.text_small_size); 128 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 129 onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), 130 isDescendantOfA(withId(R.id.start_drawer)))).check( 131 matches(withTextSize(newTextSize))); 132 } 133 } 134 135 @Test 136 @SmallTest 137 public void testTextColor() { 138 // Open our drawer 139 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 140 141 final Resources res = mActivityTestRule.getActivity().getResources(); 142 final @ColorInt int defaultTextColor = ResourcesCompat.getColor(res, 143 R.color.emerald_text, null); 144 145 // Check the default text color of the menu items in our NavigationView 146 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 147 onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), 148 isDescendantOfA(withId(R.id.start_drawer)))).check( 149 matches(withTextColor(defaultTextColor))); 150 } 151 152 // Set a new text color on our NavigationView 153 onView(withId(R.id.start_drawer)).perform(setItemTextColor( 154 ResourcesCompat.getColorStateList(res, R.color.color_state_list_lilac, null))); 155 156 // And check that all the menu items have the new color 157 final @ColorInt int newTextColor = ResourcesCompat.getColor(res, 158 R.color.lilac_default, null); 159 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 160 onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), 161 isDescendantOfA(withId(R.id.start_drawer)))).check( 162 matches(withTextColor(newTextColor))); 163 } 164 } 165 166 @Test 167 @SmallTest 168 public void testBackground() { 169 // Open our drawer 170 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 171 172 final Resources res = mActivityTestRule.getActivity().getResources(); 173 final @ColorInt int defaultFillColor = ResourcesCompat.getColor(res, 174 R.color.sand_default, null); 175 176 // Check the default fill color of the menu items in our NavigationView 177 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 178 // Note that here we're tying ourselves to the implementation details of the 179 // internal structure of the NavigationView. Specifically, we're looking at the 180 // direct child of RecyclerView which is expected to have the background set 181 // on it. If the internal implementation of NavigationView changes, the second 182 // Matcher below will need to be tweaked. 183 Matcher menuItemMatcher = allOf( 184 hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), 185 isChildOfA(isAssignableFrom(RecyclerView.class)), 186 isDescendantOfA(withId(R.id.start_drawer))); 187 188 onView(menuItemMatcher).check(matches(withBackgroundFill(defaultFillColor))); 189 } 190 191 // Set a new background (flat fill color) on our NavigationView 192 onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource( 193 R.drawable.test_background_blue)); 194 195 // And check that all the menu items have the new fill 196 final @ColorInt int newFillColorBlue = ResourcesCompat.getColor(res, 197 R.color.test_blue, null); 198 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 199 Matcher menuItemMatcher = allOf( 200 hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), 201 isChildOfA(isAssignableFrom(RecyclerView.class)), 202 isDescendantOfA(withId(R.id.start_drawer))); 203 204 onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorBlue))); 205 } 206 207 // Set another new background on our NavigationView 208 onView(withId(R.id.start_drawer)).perform(setItemBackground( 209 ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null))); 210 211 // And check that all the menu items have the new fill 212 final @ColorInt int newFillColorGreen = ResourcesCompat.getColor(res, 213 R.color.test_green, null); 214 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 215 Matcher menuItemMatcher = allOf( 216 hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), 217 isChildOfA(isAssignableFrom(RecyclerView.class)), 218 isDescendantOfA(withId(R.id.start_drawer))); 219 220 onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorGreen))); 221 } 222 } 223 224 /** 225 * Custom drawable class that provides a reliable way for testing various tinting scenarios 226 * across a range of platform versions. ColorDrawable doesn't support tinting on Kitkat and 227 * below, and BitmapDrawable (PNG sources) appears to slightly alter green and blue channels 228 * by a few units on some of the older platform versions (Gingerbread). Using GradientDrawable 229 * allows doing reliable tests at the level of individual channels (alpha / red / green / blue) 230 * for tinted and untinted icons in the testIconTinting method. 231 */ 232 private class TestDrawable extends GradientDrawable { 233 private int mWidth; 234 private int mHeight; 235 236 public TestDrawable(@ColorInt int color, int width, int height) { 237 super(Orientation.TOP_BOTTOM, new int[] { color, color }); 238 mWidth = width; 239 mHeight = height; 240 } 241 242 @Override 243 public int getIntrinsicWidth() { 244 return mWidth; 245 } 246 247 @Override 248 public int getIntrinsicHeight() { 249 return mHeight; 250 } 251 } 252 253 @Test 254 @SmallTest 255 public void testIconTinting() { 256 // Open our drawer 257 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 258 259 final Resources res = mActivityTestRule.getActivity().getResources(); 260 final @ColorInt int redFill = ResourcesCompat.getColor(res, R.color.test_red, null); 261 final @ColorInt int greenFill = ResourcesCompat.getColor(res, R.color.test_green, null); 262 final @ColorInt int blueFill = ResourcesCompat.getColor(res, R.color.test_blue, null); 263 final int iconSize = res.getDimensionPixelSize(R.dimen.drawable_small_size); 264 onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_home, 265 new TestDrawable(redFill, iconSize, iconSize))); 266 onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_profile, 267 new TestDrawable(greenFill, iconSize, iconSize))); 268 onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_people, 269 new TestDrawable(blueFill, iconSize, iconSize))); 270 271 final @ColorInt int defaultTintColor = ResourcesCompat.getColor(res, 272 R.color.emerald_translucent, null); 273 274 // We're allowing a margin of error in checking the color of the items' icons. 275 // This is due to the translucent color being used in the icon tinting 276 // and off-by-one discrepancies of SRC_IN when it's compositing 277 // translucent color. Note that all the checks below are written for the current 278 // logic on NavigationView that uses the default SRC_IN tint mode - effectively 279 // replacing all non-transparent pixels in the destination (original icon) with 280 // our translucent tint color. 281 final int allowedComponentVariance = 1; 282 283 // Note that here we're tying ourselves to the implementation details of the 284 // internal structure of the NavigationView. Specifically, we're checking the 285 // start drawable of the text view with the specific text. If the internal 286 // implementation of NavigationView changes, the second Matcher in the lookups 287 // below will need to be tweaked. 288 onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)), 289 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 290 withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance))); 291 onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), 292 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 293 withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance))); 294 onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), 295 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 296 withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance))); 297 298 final @ColorInt int newTintColor = ResourcesCompat.getColor(res, 299 R.color.red_translucent, null); 300 301 onView(withId(R.id.start_drawer)).perform(setItemIconTintList( 302 ResourcesCompat.getColorStateList(res, R.color.color_state_list_red_translucent, 303 null))); 304 // Check that all menu items with icons now have icons tinted with the newly set color 305 onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)), 306 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 307 withStartDrawableFilledWith(newTintColor, allowedComponentVariance))); 308 onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), 309 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 310 withStartDrawableFilledWith(newTintColor, allowedComponentVariance))); 311 onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), 312 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 313 withStartDrawableFilledWith(newTintColor, allowedComponentVariance))); 314 315 // And now remove all icon tinting 316 onView(withId(R.id.start_drawer)).perform(setItemIconTintList(null)); 317 // And verify that all menu items with icons now have the original colors for their icons. 318 // Note that since there is no tinting at this point, we don't allow any color variance 319 // in these checks. 320 onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)), 321 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 322 withStartDrawableFilledWith(redFill, 0))); 323 onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), 324 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 325 withStartDrawableFilledWith(greenFill, 0))); 326 onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), 327 isDescendantOfA(withId(R.id.start_drawer)))).check(matches( 328 withStartDrawableFilledWith(blueFill, 0))); 329 } 330 331 /** 332 * Gets the list of header IDs (which can be empty) and verifies that the actual header content 333 * of our navigation view matches the expected header content. 334 */ 335 private void verifyHeaders(@IdRes int ... expectedHeaderIds) { 336 final int expectedHeaderCount = (expectedHeaderIds != null) ? expectedHeaderIds.length : 0; 337 final int actualHeaderCount = mNavigationView.getHeaderCount(); 338 assertEquals("Header count", expectedHeaderCount, actualHeaderCount); 339 340 if (expectedHeaderCount > 0) { 341 for (int i = 0; i < expectedHeaderCount; i++) { 342 final View currentHeader = mNavigationView.getHeaderView(i); 343 assertEquals("Header at #" + i, expectedHeaderIds[i], currentHeader.getId()); 344 } 345 } 346 } 347 348 @Test 349 @SmallTest 350 public void testHeaders() { 351 // Open our drawer 352 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 353 354 // We should have no headers at the start 355 verifyHeaders(); 356 357 // Inflate one header and check that it's there in the navigation view 358 onView(withId(R.id.start_drawer)).perform( 359 inflateHeaderView(R.layout.design_navigation_view_header1)); 360 verifyHeaders(R.id.header1); 361 362 final LayoutInflater inflater = LayoutInflater.from(mActivityTestRule.getActivity()); 363 364 // Add one more header and check that it's there in the navigation view 365 onView(withId(R.id.start_drawer)).perform( 366 addHeaderView(inflater, R.layout.design_navigation_view_header2)); 367 verifyHeaders(R.id.header1, R.id.header2); 368 369 final View header1 = mNavigationView.findViewById(R.id.header1); 370 // Remove the first header and check that we still have the second header 371 onView(withId(R.id.start_drawer)).perform(removeHeaderView(header1)); 372 verifyHeaders(R.id.header2); 373 374 // Add one more header and check that we now have two headers 375 onView(withId(R.id.start_drawer)).perform( 376 inflateHeaderView(R.layout.design_navigation_view_header3)); 377 verifyHeaders(R.id.header2, R.id.header3); 378 379 // Add another "copy" of the header from the just-added layout and check that we now 380 // have three headers 381 onView(withId(R.id.start_drawer)).perform( 382 addHeaderView(inflater, R.layout.design_navigation_view_header3)); 383 verifyHeaders(R.id.header2, R.id.header3, R.id.header3); 384 } 385 386 @Test 387 @SmallTest 388 public void testNavigationSelectionListener() { 389 // Open our drawer 390 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 391 392 // Click one of our items 393 onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), 394 isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); 395 // Check that the drawer is still open 396 assertTrue("Drawer is still open after click", 397 mDrawerLayout.isDrawerOpen(GravityCompat.START)); 398 399 // Register a listener 400 NavigationView.OnNavigationItemSelectedListener mockedListener = 401 mock(NavigationView.OnNavigationItemSelectedListener.class); 402 mNavigationView.setNavigationItemSelectedListener(mockedListener); 403 404 // Click one of our items 405 onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), 406 isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); 407 // Check that the drawer is still open 408 assertTrue("Drawer is still open after click", 409 mDrawerLayout.isDrawerOpen(GravityCompat.START)); 410 // And that our listener has been notified of the click 411 verify(mockedListener, times(1)).onNavigationItemSelected( 412 mNavigationView.getMenu().findItem(R.id.destination_profile)); 413 414 // Set null listener to test that the next click is not going to notify the 415 // previously set listener 416 mNavigationView.setNavigationItemSelectedListener(null); 417 418 // Click one of our items 419 onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)), 420 isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); 421 // Check that the drawer is still open 422 assertTrue("Drawer is still open after click", 423 mDrawerLayout.isDrawerOpen(GravityCompat.START)); 424 // And that our previous listener has not been notified of the click 425 verifyNoMoreInteractions(mockedListener); 426 } 427 428 private void verifyCheckedAppearance(@IdRes int checkedItemId, 429 @ColorInt int uncheckedItemForeground, @ColorInt int checkedItemForeground, 430 @ColorInt int uncheckedItemBackground, @ColorInt int checkedItemBackground) { 431 for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { 432 final boolean expectedToBeChecked = (MENU_CONTENT_ITEM_IDS[i] == checkedItemId); 433 final @ColorInt int expectedItemForeground = 434 expectedToBeChecked ? checkedItemForeground : uncheckedItemForeground; 435 final @ColorInt int expectedItemBackground = 436 expectedToBeChecked ? checkedItemBackground : uncheckedItemBackground; 437 438 // For the background fill check we need to select a view that has its background 439 // set by the current implementation (see disclaimer in testBackground) 440 Matcher menuItemMatcher = allOf( 441 hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), 442 isChildOfA(isAssignableFrom(RecyclerView.class)), 443 isDescendantOfA(withId(R.id.start_drawer))); 444 onView(menuItemMatcher).check(matches(withBackgroundFill(expectedItemBackground))); 445 446 // And for the foreground color check we need to select a view with the text content 447 Matcher menuItemTextMatcher = allOf( 448 withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), 449 isDescendantOfA(withId(R.id.start_drawer))); 450 onView(menuItemTextMatcher).check(matches(withTextColor(expectedItemForeground))); 451 } 452 } 453 454 @Test 455 @SmallTest 456 public void testCheckedAppearance() { 457 // Open our drawer 458 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 459 460 // Reconfigure our navigation view to use foreground (text) and background visuals 461 // with explicitly different colors for the checked state 462 final Resources res = mActivityTestRule.getActivity().getResources(); 463 onView(withId(R.id.start_drawer)).perform(setItemTextColor( 464 ResourcesCompat.getColorStateList(res, R.color.color_state_list_sand, null))); 465 onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource( 466 R.drawable.test_drawable_state_list)); 467 468 final @ColorInt int uncheckedItemForeground = ResourcesCompat.getColor(res, 469 R.color.sand_default, null); 470 final @ColorInt int checkedItemForeground = ResourcesCompat.getColor(res, 471 R.color.sand_checked, null); 472 final @ColorInt int uncheckedItemBackground = ResourcesCompat.getColor(res, 473 R.color.test_green, null); 474 final @ColorInt int checkedItemBackground = ResourcesCompat.getColor(res, 475 R.color.test_blue, null); 476 477 // Verify that all items are rendered with unchecked visuals 478 verifyCheckedAppearance(0, uncheckedItemForeground, checkedItemForeground, 479 uncheckedItemBackground, checkedItemBackground); 480 481 // Mark one of the items as checked 482 onView(withId(R.id.start_drawer)).perform(setCheckedItem(R.id.destination_profile)); 483 // And verify that it's now rendered with checked visuals 484 verifyCheckedAppearance(R.id.destination_profile, 485 uncheckedItemForeground, checkedItemForeground, 486 uncheckedItemBackground, checkedItemBackground); 487 488 // Register a navigation listener that "marks" the selected item 489 mNavigationView.setNavigationItemSelectedListener( 490 new NavigationView.OnNavigationItemSelectedListener() { 491 @Override 492 public boolean onNavigationItemSelected(MenuItem item) { 493 return true; 494 } 495 }); 496 497 // Click one of our items 498 onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), 499 isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); 500 // and verify that it's now checked 501 verifyCheckedAppearance(R.id.destination_people, 502 uncheckedItemForeground, checkedItemForeground, 503 uncheckedItemBackground, checkedItemBackground); 504 505 // Register a navigation listener that doesn't "mark" the selected item 506 mNavigationView.setNavigationItemSelectedListener( 507 new NavigationView.OnNavigationItemSelectedListener() { 508 @Override 509 public boolean onNavigationItemSelected(MenuItem item) { 510 return false; 511 } 512 }); 513 514 // Click another items 515 onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)), 516 isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); 517 // and verify that the checked state remains on the previously clicked item 518 // since the current navigation listener returns false from its callback 519 // implementation 520 verifyCheckedAppearance(R.id.destination_people, 521 uncheckedItemForeground, checkedItemForeground, 522 uncheckedItemBackground, checkedItemBackground); 523 } 524 525 @Test 526 @SmallTest 527 public void testActionLayout() { 528 // Open our drawer 529 onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); 530 531 // There are four conditions to "find" the menu item with action layout (switch): 532 // 1. Is in the NavigationView 533 // 2. Is direct child of a class that extends RecyclerView 534 // 3. Has a child with "people" text 535 // 4. Has fully displayed child that extends SwitchCompat 536 // Note that condition 2 makes a certain assumption about the internal implementation 537 // details of the NavigationMenu, while conditions 3 and 4 aim to be as generic as 538 // possible and to not rely on the internal details of the current layout implementation 539 // of an individual menu item in NavigationMenu. 540 Matcher menuItemMatcher = allOf( 541 isDescendantOfA(withId(R.id.start_drawer)), 542 isChildOfA(isAssignableFrom(RecyclerView.class)), 543 hasDescendant(withText(mMenuStringContent.get(R.id.destination_people))), 544 hasDescendant(allOf( 545 isAssignableFrom(SwitchCompat.class), 546 isCompletelyDisplayed()))); 547 548 // While we don't need to perform any action on our row, the invocation of perform() 549 // makes our matcher actually run. If for some reason NavigationView fails to inflate and 550 // display our SwitchCompat action layout, the next line will fail in the matcher pass. 551 onView(menuItemMatcher).perform(click()); 552 } 553} 554