| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.support.design.widget; |
| |
| import static android.support.design.testutils.DrawerLayoutActions.closeDrawer; |
| import static android.support.design.testutils.DrawerLayoutActions.openDrawer; |
| import static android.support.design.testutils.NavigationViewActions.addHeaderView; |
| import static android.support.design.testutils.NavigationViewActions.inflateHeaderView; |
| import static android.support.design.testutils.NavigationViewActions.removeHeaderView; |
| import static android.support.design.testutils.NavigationViewActions.removeMenuItem; |
| import static android.support.design.testutils.NavigationViewActions.setCheckedItem; |
| import static android.support.design.testutils.NavigationViewActions.setIconForMenuItem; |
| import static android.support.design.testutils.NavigationViewActions.setItemBackground; |
| import static android.support.design.testutils.NavigationViewActions.setItemBackgroundResource; |
| import static android.support.design.testutils.NavigationViewActions.setItemIconTintList; |
| import static android.support.design.testutils.NavigationViewActions.setItemTextAppearance; |
| import static android.support.design.testutils.NavigationViewActions.setItemTextColor; |
| import static android.support.design.testutils.TestUtilsActions.reinflateMenu; |
| import static android.support.design.testutils.TestUtilsActions.restoreHierarchyState; |
| import static android.support.design.testutils.TestUtilsMatchers.isActionViewOf; |
| import static android.support.design.testutils.TestUtilsMatchers.isChildOfA; |
| import static android.support.design.testutils.TestUtilsMatchers.withBackgroundFill; |
| import static android.support.design.testutils.TestUtilsMatchers.withStartDrawableFilledWith; |
| import static android.support.design.testutils.TestUtilsMatchers.withTextColor; |
| import static android.support.design.testutils.TestUtilsMatchers.withTextSize; |
| import static android.support.test.espresso.Espresso.onView; |
| import static android.support.test.espresso.action.ViewActions.click; |
| import static android.support.test.espresso.assertion.ViewAssertions.matches; |
| import static android.support.test.espresso.matcher.ViewMatchers.Visibility; |
| import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; |
| import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; |
| import static android.support.test.espresso.matcher.ViewMatchers.isChecked; |
| import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; |
| import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; |
| import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; |
| import static android.support.test.espresso.matcher.ViewMatchers.isNotChecked; |
| import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; |
| import static android.support.test.espresso.matcher.ViewMatchers.withId; |
| import static android.support.test.espresso.matcher.ViewMatchers.withText; |
| |
| import static org.hamcrest.core.AllOf.allOf; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| |
| import android.content.res.Resources; |
| import android.os.Build; |
| import android.os.Parcelable; |
| import android.support.annotation.ColorInt; |
| import android.support.annotation.IdRes; |
| import android.support.design.test.R; |
| import android.support.design.testutils.TestDrawable; |
| import android.support.test.filters.MediumTest; |
| import android.support.test.filters.SdkSuppress; |
| import android.support.v4.content.res.ResourcesCompat; |
| import android.support.v4.view.GravityCompat; |
| import android.support.v4.widget.DrawerLayout; |
| import android.support.v7.widget.RecyclerView; |
| import android.support.v7.widget.SwitchCompat; |
| import android.util.SparseArray; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.widget.TextView; |
| |
| import org.hamcrest.Matcher; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| @MediumTest |
| public class NavigationViewTest |
| extends BaseInstrumentationTestCase<NavigationViewActivity> { |
| private static final int[] MENU_CONTENT_ITEM_IDS = { R.id.destination_home, |
| R.id.destination_profile, R.id.destination_people, R.id.destination_settings }; |
| private Map<Integer, String> mMenuStringContent; |
| |
| private DrawerLayout mDrawerLayout; |
| |
| private NavigationTestView mNavigationView; |
| |
| public NavigationViewTest() { |
| super(NavigationViewActivity.class); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| final NavigationViewActivity activity = mActivityTestRule.getActivity(); |
| mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout); |
| mNavigationView = (NavigationTestView) mDrawerLayout.findViewById(R.id.start_drawer); |
| |
| // Close the drawer to reset the state for the next test |
| onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START)); |
| |
| final Resources res = activity.getResources(); |
| mMenuStringContent = new HashMap<>(MENU_CONTENT_ITEM_IDS.length); |
| mMenuStringContent.put(R.id.destination_home, res.getString(R.string.navigate_home)); |
| mMenuStringContent.put(R.id.destination_profile, res.getString(R.string.navigate_profile)); |
| mMenuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people)); |
| mMenuStringContent.put(R.id.destination_settings, |
| res.getString(R.string.navigate_settings)); |
| } |
| |
| @Test |
| public void testBasics() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| // Check the contents of the Menu object |
| final Menu menu = mNavigationView.getMenu(); |
| assertNotNull("Menu should not be null", menu); |
| assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length + 1, |
| menu.size()); |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| final MenuItem currItem = menu.getItem(i); |
| assertEquals("ID for Item #" + i, MENU_CONTENT_ITEM_IDS[i], currItem.getItemId()); |
| } |
| |
| // Check that we have the expected menu items in our NavigationView |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed())); |
| } |
| } |
| |
| @Test |
| public void testWillNotDraw() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| if (Build.VERSION.SDK_INT >= 21) { |
| if (mNavigationView.hasSystemWindowInsets()) { |
| assertFalse(mNavigationView.willNotDraw()); |
| } else { |
| assertTrue(mNavigationView.willNotDraw()); |
| } |
| } else { |
| assertTrue(mNavigationView.willNotDraw()); |
| } |
| } |
| |
| @Test |
| public void testTextAppearance() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| final Resources res = mActivityTestRule.getActivity().getResources(); |
| final int defaultTextSize = res.getDimensionPixelSize(R.dimen.text_medium_size); |
| |
| // Check the default style of the menu items in our NavigationView |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), |
| isDescendantOfA(withId(R.id.start_drawer)))).check( |
| matches(withTextSize(defaultTextSize))); |
| } |
| |
| // Set a new text appearance on our NavigationView |
| onView(withId(R.id.start_drawer)).perform(setItemTextAppearance(R.style.TextSmallStyle)); |
| |
| // And check that all the menu items have the new style |
| final int newTextSize = res.getDimensionPixelSize(R.dimen.text_small_size); |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), |
| isDescendantOfA(withId(R.id.start_drawer)))).check( |
| matches(withTextSize(newTextSize))); |
| } |
| } |
| |
| @Test |
| public void testTextColor() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| final Resources res = mActivityTestRule.getActivity().getResources(); |
| final @ColorInt int defaultTextColor = ResourcesCompat.getColor(res, |
| R.color.emerald_text, null); |
| |
| // Check the default text color of the menu items in our NavigationView |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), |
| isDescendantOfA(withId(R.id.start_drawer)))).check( |
| matches(withTextColor(defaultTextColor))); |
| } |
| |
| // Set a new text color on our NavigationView |
| onView(withId(R.id.start_drawer)).perform(setItemTextColor( |
| ResourcesCompat.getColorStateList(res, R.color.color_state_list_lilac, null))); |
| |
| // And check that all the menu items have the new color |
| final @ColorInt int newTextColor = ResourcesCompat.getColor(res, |
| R.color.lilac_default, null); |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), |
| isDescendantOfA(withId(R.id.start_drawer)))).check( |
| matches(withTextColor(newTextColor))); |
| } |
| } |
| |
| @Test |
| public void testBackground() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| final Resources res = mActivityTestRule.getActivity().getResources(); |
| final @ColorInt int defaultFillColor = ResourcesCompat.getColor(res, |
| R.color.sand_default, null); |
| |
| // Check the default fill color of the menu items in our NavigationView |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| // Note that here we're tying ourselves to the implementation details of the |
| // internal structure of the NavigationView. Specifically, we're looking at the |
| // direct child of RecyclerView which is expected to have the background set |
| // on it. If the internal implementation of NavigationView changes, the second |
| // Matcher below will need to be tweaked. |
| Matcher menuItemMatcher = allOf( |
| hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), |
| isChildOfA(isAssignableFrom(RecyclerView.class)), |
| isDescendantOfA(withId(R.id.start_drawer))); |
| |
| onView(menuItemMatcher).check(matches(withBackgroundFill(defaultFillColor))); |
| } |
| |
| // Set a new background (flat fill color) on our NavigationView |
| onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource( |
| R.drawable.test_background_blue)); |
| |
| // And check that all the menu items have the new fill |
| final @ColorInt int newFillColorBlue = ResourcesCompat.getColor(res, |
| R.color.test_blue, null); |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| Matcher menuItemMatcher = allOf( |
| hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), |
| isChildOfA(isAssignableFrom(RecyclerView.class)), |
| isDescendantOfA(withId(R.id.start_drawer))); |
| |
| onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorBlue))); |
| } |
| |
| // Set another new background on our NavigationView |
| onView(withId(R.id.start_drawer)).perform(setItemBackground( |
| ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null))); |
| |
| // And check that all the menu items have the new fill |
| final @ColorInt int newFillColorGreen = ResourcesCompat.getColor(res, |
| R.color.test_green, null); |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| Matcher menuItemMatcher = allOf( |
| hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), |
| isChildOfA(isAssignableFrom(RecyclerView.class)), |
| isDescendantOfA(withId(R.id.start_drawer))); |
| |
| onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorGreen))); |
| } |
| } |
| |
| @Test |
| public void testIconTinting() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| final Resources res = mActivityTestRule.getActivity().getResources(); |
| final @ColorInt int redFill = ResourcesCompat.getColor(res, R.color.test_red, null); |
| final @ColorInt int greenFill = ResourcesCompat.getColor(res, R.color.test_green, null); |
| final @ColorInt int blueFill = ResourcesCompat.getColor(res, R.color.test_blue, null); |
| final int iconSize = res.getDimensionPixelSize(R.dimen.drawable_small_size); |
| onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_home, |
| new TestDrawable(redFill, iconSize, iconSize))); |
| onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_profile, |
| new TestDrawable(greenFill, iconSize, iconSize))); |
| onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_people, |
| new TestDrawable(blueFill, iconSize, iconSize))); |
| |
| final @ColorInt int defaultTintColor = ResourcesCompat.getColor(res, |
| R.color.emerald_translucent, null); |
| |
| // We're allowing a margin of error in checking the color of the items' icons. |
| // This is due to the translucent color being used in the icon tinting |
| // and off-by-one discrepancies of SRC_IN when it's compositing |
| // translucent color. Note that all the checks below are written for the current |
| // logic on NavigationView that uses the default SRC_IN tint mode - effectively |
| // replacing all non-transparent pixels in the destination (original icon) with |
| // our translucent tint color. |
| final int allowedComponentVariance = 1; |
| |
| // Note that here we're tying ourselves to the implementation details of the |
| // internal structure of the NavigationView. Specifically, we're checking the |
| // start drawable of the text view with the specific text. If the internal |
| // implementation of NavigationView changes, the second Matcher in the lookups |
| // below will need to be tweaked. |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance))); |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance))); |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance))); |
| |
| final @ColorInt int newTintColor = ResourcesCompat.getColor(res, |
| R.color.red_translucent, null); |
| |
| onView(withId(R.id.start_drawer)).perform(setItemIconTintList( |
| ResourcesCompat.getColorStateList(res, R.color.color_state_list_red_translucent, |
| null))); |
| // Check that all menu items with icons now have icons tinted with the newly set color |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(newTintColor, allowedComponentVariance))); |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(newTintColor, allowedComponentVariance))); |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(newTintColor, allowedComponentVariance))); |
| |
| // And now remove all icon tinting |
| onView(withId(R.id.start_drawer)).perform(setItemIconTintList(null)); |
| // And verify that all menu items with icons now have the original colors for their icons. |
| // Note that since there is no tinting at this point, we don't allow any color variance |
| // in these checks. |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(redFill, 0))); |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(greenFill, 0))); |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), |
| isDescendantOfA(withId(R.id.start_drawer)))).check(matches( |
| withStartDrawableFilledWith(blueFill, 0))); |
| } |
| |
| /** |
| * Gets the list of header IDs (which can be empty) and verifies that the actual header content |
| * of our navigation view matches the expected header content. |
| */ |
| private void verifyHeaders(@IdRes int ... expectedHeaderIds) { |
| final int expectedHeaderCount = (expectedHeaderIds != null) ? expectedHeaderIds.length : 0; |
| final int actualHeaderCount = mNavigationView.getHeaderCount(); |
| assertEquals("Header count", expectedHeaderCount, actualHeaderCount); |
| |
| if (expectedHeaderCount > 0) { |
| for (int i = 0; i < expectedHeaderCount; i++) { |
| final View currentHeader = mNavigationView.getHeaderView(i); |
| assertEquals("Header at #" + i, expectedHeaderIds[i], currentHeader.getId()); |
| } |
| } |
| } |
| |
| @Test |
| public void testHeaders() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| // We should have no headers at the start |
| verifyHeaders(); |
| |
| // Inflate one header and check that it's there in the navigation view |
| onView(withId(R.id.start_drawer)).perform( |
| inflateHeaderView(R.layout.design_navigation_view_header1)); |
| verifyHeaders(R.id.header1); |
| |
| final LayoutInflater inflater = LayoutInflater.from(mActivityTestRule.getActivity()); |
| |
| // Add one more header and check that it's there in the navigation view |
| onView(withId(R.id.start_drawer)).perform( |
| addHeaderView(inflater, R.layout.design_navigation_view_header2)); |
| verifyHeaders(R.id.header1, R.id.header2); |
| |
| final View header1 = mNavigationView.findViewById(R.id.header1); |
| // Remove the first header and check that we still have the second header |
| onView(withId(R.id.start_drawer)).perform(removeHeaderView(header1)); |
| verifyHeaders(R.id.header2); |
| |
| // Add one more header and check that we now have two headers |
| onView(withId(R.id.start_drawer)).perform( |
| inflateHeaderView(R.layout.design_navigation_view_header3)); |
| verifyHeaders(R.id.header2, R.id.header3); |
| |
| // Add another "copy" of the header from the just-added layout and check that we now |
| // have three headers |
| onView(withId(R.id.start_drawer)).perform( |
| addHeaderView(inflater, R.layout.design_navigation_view_header3)); |
| verifyHeaders(R.id.header2, R.id.header3, R.id.header3); |
| } |
| |
| @SdkSuppress(minSdkVersion = 11) |
| @Test |
| public void testHeaderState() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| // Inflate a header with a toggle switch and check that it's there in the navigation view |
| onView(withId(R.id.start_drawer)).perform( |
| inflateHeaderView(R.layout.design_navigation_view_header_switch)); |
| verifyHeaders(R.id.header_frame); |
| |
| onView(withId(R.id.header_toggle)) |
| .check(matches(isNotChecked())) |
| .perform(click()) |
| .check(matches(isChecked())); |
| |
| // Save the current state |
| SparseArray<Parcelable> container = new SparseArray<>(); |
| mNavigationView.saveHierarchyState(container); |
| |
| // Remove the header |
| final View header = mNavigationView.findViewById(R.id.header_frame); |
| onView(withId(R.id.start_drawer)).perform(removeHeaderView(header)); |
| verifyHeaders(); |
| |
| // Inflate the header again |
| onView(withId(R.id.start_drawer)).perform( |
| inflateHeaderView(R.layout.design_navigation_view_header_switch)); |
| verifyHeaders(R.id.header_frame); |
| |
| // Restore the saved state |
| onView(withId(R.id.start_drawer)).perform( |
| restoreHierarchyState(container)); |
| |
| // Confirm that the state was restored |
| onView(withId(R.id.header_toggle)) |
| .check(matches(isChecked())); |
| } |
| |
| @SdkSuppress(minSdkVersion = 11) |
| @Test |
| public void testActionViewState() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| final Menu menu = mNavigationView.getMenu(); |
| onView(isActionViewOf(menu, R.id.destination_people)) |
| .check(matches(isNotChecked())) // Not checked by default |
| .perform(click()) // Check it |
| .check(matches(isChecked())); |
| |
| // Remove the other action view to simulate the case where it is not yet inflated |
| onView(isActionViewOf(menu, R.id.destination_custom)) |
| .check(matches(isDisplayed())); |
| onView(withId(R.id.start_drawer)) |
| .perform(removeMenuItem(R.id.destination_custom)); |
| |
| // Save the current state |
| SparseArray<Parcelable> container = new SparseArray<>(); |
| mNavigationView.saveHierarchyState(container); |
| |
| // Restore the saved state |
| onView(withId(R.id.start_drawer)) |
| .perform(reinflateMenu(R.menu.navigation_view_content)) |
| .perform(restoreHierarchyState(container)); |
| |
| // Checked state should be restored |
| onView(isActionViewOf(menu, R.id.destination_people)) |
| .check(matches(isChecked())); |
| } |
| |
| @Test |
| public void testNavigationSelectionListener() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| // Click one of our items |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), |
| isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); |
| // Check that the drawer is still open |
| assertTrue("Drawer is still open after click", |
| mDrawerLayout.isDrawerOpen(GravityCompat.START)); |
| |
| // Register a listener |
| NavigationView.OnNavigationItemSelectedListener mockedListener = |
| mock(NavigationView.OnNavigationItemSelectedListener.class); |
| mNavigationView.setNavigationItemSelectedListener(mockedListener); |
| |
| // Click one of our items |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)), |
| isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); |
| // Check that the drawer is still open |
| assertTrue("Drawer is still open after click", |
| mDrawerLayout.isDrawerOpen(GravityCompat.START)); |
| // And that our listener has been notified of the click |
| verify(mockedListener, times(1)).onNavigationItemSelected( |
| mNavigationView.getMenu().findItem(R.id.destination_profile)); |
| |
| // Set null listener to test that the next click is not going to notify the |
| // previously set listener |
| mNavigationView.setNavigationItemSelectedListener(null); |
| |
| // Click one of our items |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)), |
| isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); |
| // Check that the drawer is still open |
| assertTrue("Drawer is still open after click", |
| mDrawerLayout.isDrawerOpen(GravityCompat.START)); |
| // And that our previous listener has not been notified of the click |
| verifyNoMoreInteractions(mockedListener); |
| } |
| |
| private void verifyCheckedAppearance(@IdRes int checkedItemId, |
| @ColorInt int uncheckedItemForeground, @ColorInt int checkedItemForeground, |
| @ColorInt int uncheckedItemBackground, @ColorInt int checkedItemBackground) { |
| for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) { |
| final boolean expectedToBeChecked = (MENU_CONTENT_ITEM_IDS[i] == checkedItemId); |
| final @ColorInt int expectedItemForeground = |
| expectedToBeChecked ? checkedItemForeground : uncheckedItemForeground; |
| final @ColorInt int expectedItemBackground = |
| expectedToBeChecked ? checkedItemBackground : uncheckedItemBackground; |
| |
| // For the background fill check we need to select a view that has its background |
| // set by the current implementation (see disclaimer in testBackground) |
| Matcher menuItemMatcher = allOf( |
| hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))), |
| isChildOfA(isAssignableFrom(RecyclerView.class)), |
| isDescendantOfA(withId(R.id.start_drawer))); |
| onView(menuItemMatcher).check(matches(withBackgroundFill(expectedItemBackground))); |
| |
| // And for the foreground color check we need to select a view with the text content |
| Matcher menuItemTextMatcher = allOf( |
| withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])), |
| isDescendantOfA(withId(R.id.start_drawer))); |
| onView(menuItemTextMatcher).check(matches(withTextColor(expectedItemForeground))); |
| } |
| } |
| |
| @Test |
| public void testCheckedAppearance() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| // Reconfigure our navigation view to use foreground (text) and background visuals |
| // with explicitly different colors for the checked state |
| final Resources res = mActivityTestRule.getActivity().getResources(); |
| onView(withId(R.id.start_drawer)).perform(setItemTextColor( |
| ResourcesCompat.getColorStateList(res, R.color.color_state_list_sand, null))); |
| onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource( |
| R.drawable.test_drawable_state_list)); |
| |
| final @ColorInt int uncheckedItemForeground = ResourcesCompat.getColor(res, |
| R.color.sand_default, null); |
| final @ColorInt int checkedItemForeground = ResourcesCompat.getColor(res, |
| R.color.sand_checked, null); |
| final @ColorInt int uncheckedItemBackground = ResourcesCompat.getColor(res, |
| R.color.test_green, null); |
| final @ColorInt int checkedItemBackground = ResourcesCompat.getColor(res, |
| R.color.test_blue, null); |
| |
| // Verify that all items are rendered with unchecked visuals |
| verifyCheckedAppearance(0, uncheckedItemForeground, checkedItemForeground, |
| uncheckedItemBackground, checkedItemBackground); |
| |
| // Mark one of the items as checked |
| onView(withId(R.id.start_drawer)).perform(setCheckedItem(R.id.destination_profile)); |
| // And verify that it's now rendered with checked visuals |
| verifyCheckedAppearance(R.id.destination_profile, |
| uncheckedItemForeground, checkedItemForeground, |
| uncheckedItemBackground, checkedItemBackground); |
| |
| // Register a navigation listener that "marks" the selected item |
| mNavigationView.setNavigationItemSelectedListener( |
| new NavigationView.OnNavigationItemSelectedListener() { |
| @Override |
| public boolean onNavigationItemSelected(MenuItem item) { |
| return true; |
| } |
| }); |
| |
| // Click one of our items |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)), |
| isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); |
| // and verify that it's now checked |
| verifyCheckedAppearance(R.id.destination_people, |
| uncheckedItemForeground, checkedItemForeground, |
| uncheckedItemBackground, checkedItemBackground); |
| |
| // Register a navigation listener that doesn't "mark" the selected item |
| mNavigationView.setNavigationItemSelectedListener( |
| new NavigationView.OnNavigationItemSelectedListener() { |
| @Override |
| public boolean onNavigationItemSelected(MenuItem item) { |
| return false; |
| } |
| }); |
| |
| // Click another items |
| onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)), |
| isDescendantOfA(withId(R.id.start_drawer)))).perform(click()); |
| // and verify that the checked state remains on the previously clicked item |
| // since the current navigation listener returns false from its callback |
| // implementation |
| verifyCheckedAppearance(R.id.destination_people, |
| uncheckedItemForeground, checkedItemForeground, |
| uncheckedItemBackground, checkedItemBackground); |
| } |
| |
| @Test |
| public void testActionLayout() { |
| // Open our drawer |
| onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START)); |
| |
| // There are four conditions to "find" the menu item with action layout (switch): |
| // 1. Is in the NavigationView |
| // 2. Is direct child of a class that extends RecyclerView |
| // 3. Has a child with "people" text |
| // 4. Has fully displayed child that extends SwitchCompat |
| // Note that condition 2 makes a certain assumption about the internal implementation |
| // details of the NavigationMenu, while conditions 3 and 4 aim to be as generic as |
| // possible and to not rely on the internal details of the current layout implementation |
| // of an individual menu item in NavigationMenu. |
| Matcher menuItemMatcher = allOf( |
| isDescendantOfA(withId(R.id.start_drawer)), |
| isChildOfA(isAssignableFrom(RecyclerView.class)), |
| hasDescendant(withText(mMenuStringContent.get(R.id.destination_people))), |
| hasDescendant(allOf( |
| isAssignableFrom(SwitchCompat.class), |
| isCompletelyDisplayed()))); |
| |
| // While we don't need to perform any action on our row, the invocation of perform() |
| // makes our matcher actually run. If for some reason NavigationView fails to inflate and |
| // display our SwitchCompat action layout, the next line will fail in the matcher pass. |
| onView(menuItemMatcher).perform(click()); |
| |
| // Check that the full custom view is displayed without title and icon. |
| final Resources res = mActivityTestRule.getActivity().getResources(); |
| Matcher customItemMatcher = allOf( |
| isDescendantOfA(withId(R.id.start_drawer)), |
| isChildOfA(isAssignableFrom(RecyclerView.class)), |
| hasDescendant(withText(res.getString(R.string.navigate_custom))), |
| hasDescendant(allOf( |
| isAssignableFrom(TextView.class), |
| withEffectiveVisibility(Visibility.GONE)))); |
| onView(customItemMatcher).perform(click()); |
| } |
| } |