Home | History | Annotate | Download | only in widget
      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  */
     16 package android.support.design.widget;
     17 
     18 import android.content.res.Resources;
     19 import android.graphics.drawable.GradientDrawable;
     20 import android.support.annotation.ColorInt;
     21 import android.support.annotation.IdRes;
     22 import android.support.design.test.R;
     23 import android.support.v4.content.res.ResourcesCompat;
     24 import android.support.v4.view.GravityCompat;
     25 import android.support.v4.widget.DrawerLayout;
     26 import android.support.v7.widget.RecyclerView;
     27 import android.support.v7.widget.SwitchCompat;
     28 import android.test.suitebuilder.annotation.SmallTest;
     29 import android.view.LayoutInflater;
     30 import android.view.Menu;
     31 import android.view.MenuItem;
     32 import android.view.View;
     33 import org.hamcrest.Matcher;
     34 import org.junit.Before;
     35 import org.junit.Test;
     36 
     37 import java.util.HashMap;
     38 import java.util.Map;
     39 
     40 import static android.support.design.testutils.DrawerLayoutActions.closeDrawer;
     41 import static android.support.design.testutils.DrawerLayoutActions.openDrawer;
     42 import static android.support.design.testutils.NavigationViewActions.*;
     43 import static android.support.design.testutils.TestUtilsMatchers.*;
     44 import static android.support.test.espresso.Espresso.onView;
     45 import static android.support.test.espresso.action.ViewActions.click;
     46 import static android.support.test.espresso.assertion.ViewAssertions.matches;
     47 import static android.support.test.espresso.matcher.ViewMatchers.*;
     48 import static org.hamcrest.core.AllOf.allOf;
     49 import static org.junit.Assert.*;
     50 import static org.mockito.Mockito.*;
     51 
     52 public 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