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