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