/* * 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 com.google.android.material.bottomnavigation; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.google.android.material.testutils.BottomNavigationViewActions.addMenuItem; import static com.google.android.material.testutils.BottomNavigationViewActions.setIconForMenuItem; import static com.google.android.material.testutils.BottomNavigationViewActions.setIconSize; import static com.google.android.material.testutils.BottomNavigationViewActions.setItemIconTintList; import static com.google.android.material.testutils.BottomNavigationViewActions.setLabelVisibilityMode; import static com.google.android.material.testutils.BottomNavigationViewActions.showBadgeNumberForMenuItem; import static org.hamcrest.CoreMatchers.not; 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.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.annotation.TargetApi; import android.app.Activity; import android.content.res.Resources; import android.os.Build; import android.os.Parcelable; import android.util.SparseArray; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import androidx.annotation.ColorInt; import androidx.core.content.res.ResourcesCompat; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.LargeTest; import androidx.test.filters.SdkSuppress; import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.google.android.material.navigation.NavigationBarItemView; import com.google.android.material.navigation.NavigationBarMenuView; import com.google.android.material.navigation.NavigationBarView; import com.google.android.material.navigation.NavigationBarView.OnItemReselectedListener; import com.google.android.material.navigation.NavigationBarView.OnItemSelectedListener; import com.google.android.material.testapp.BottomNavigationViewActivity; import com.google.android.material.testapp.R; import com.google.android.material.testutils.AccessibilityUtils; import com.google.android.material.testutils.TestDrawable; import com.google.android.material.testutils.TestUtilsMatchers; import java.util.HashMap; import java.util.Map; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class BottomNavigationViewTest { @Rule public final ActivityTestRule activityTestRule = new ActivityTestRule<>(BottomNavigationViewActivity.class); private static final int[] MENU_CONTENT_ITEM_IDS = { R.id.destination_home, R.id.destination_profile, R.id.destination_people }; private Map menuStringContent; private BottomNavigationView bottomNavigation; @Before public void setUp() throws Exception { final BottomNavigationViewActivity activity = activityTestRule.getActivity(); activity.setTheme(R.style.Theme_MaterialComponents_Light); bottomNavigation = activity.findViewById(R.id.bottom_navigation); final Resources res = activity.getResources(); menuStringContent = new HashMap<>(MENU_CONTENT_ITEM_IDS.length); menuStringContent.put(R.id.destination_home, res.getString(R.string.navigate_home)); menuStringContent.put(R.id.destination_profile, res.getString(R.string.navigate_profile)); menuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people)); } @UiThreadTest @Test @SmallTest public void testAddItemsWithoutMenuInflation() { BottomNavigationView navigation = new BottomNavigationView(activityTestRule.getActivity()); activityTestRule.getActivity().setContentView(navigation); navigation.getMenu().add("Item1"); navigation.getMenu().add("Item2"); assertEquals(2, navigation.getMenu().size()); navigation.getMenu().removeItem(0); navigation.getMenu().removeItem(0); assertEquals(0, navigation.getMenu().size()); } @Test @SmallTest public void testBasics() { // Check the contents of the Menu object final Menu menu = bottomNavigation.getMenu(); assertNotNull("Menu should not be null", menu); assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length, 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()); } } @Test @LargeTest public void testNavigationSelectionListener() { OnItemSelectedListener mockedListener = mock(OnItemSelectedListener.class); bottomNavigation.setOnItemSelectedListener(mockedListener); // Make the listener return true to allow selecting the item. when(mockedListener.onNavigationItemSelected(any(MenuItem.class))).thenReturn(true); onView( allOf( withText(menuStringContent.get(R.id.destination_profile)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify our listener has been notified of the click verify(mockedListener, times(1)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_profile)); // Verify the item is now selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Select the same item again onView( allOf( withText(menuStringContent.get(R.id.destination_profile)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify our listener has been notified of the click verify(mockedListener, times(2)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_profile)); // Verify the item is still selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Make the listener return false to disallow selecting the item. when(mockedListener.onNavigationItemSelected(any(MenuItem.class))).thenReturn(false); onView( allOf( withText(menuStringContent.get(R.id.destination_people)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify our listener has been notified of the click verify(mockedListener, times(1)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_people)); // Verify the previous item is still selected assertFalse(bottomNavigation.getMenu().findItem(R.id.destination_people).isChecked()); assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Set null listener to test that the next click is not going to notify the // previously set listener and will allow selecting items. bottomNavigation.setOnItemSelectedListener(null); // Click one of our items onView( allOf( withText(menuStringContent.get(R.id.destination_home)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify that our previous listener has not been notified of the click verifyNoMoreInteractions(mockedListener); // Verify the correct item is now selected. assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_home).isChecked()); } @UiThreadTest @Test @SmallTest public void testSetSelectedItemId() { OnItemSelectedListener mockedListener = mock(OnItemSelectedListener.class); bottomNavigation.setOnItemSelectedListener(mockedListener); // Make the listener return true to allow selecting the item. when(mockedListener.onNavigationItemSelected(any(MenuItem.class))).thenReturn(true); // Programmatically select an item bottomNavigation.setSelectedItemId(R.id.destination_profile); // Verify our listener has been notified of the click verify(mockedListener, times(1)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_profile)); // Verify the item is now selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Select the same item bottomNavigation.setSelectedItemId(R.id.destination_profile); // Verify our listener has been notified of the click verify(mockedListener, times(2)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_profile)); // Verify the item is still selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Make the listener return false to disallow selecting the item. when(mockedListener.onNavigationItemSelected(any(MenuItem.class))).thenReturn(false); // Programmatically select an item bottomNavigation.setSelectedItemId(R.id.destination_people); // Verify our listener has been notified of the click verify(mockedListener, times(1)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_people)); // Verify the previous item is still selected assertFalse(bottomNavigation.getMenu().findItem(R.id.destination_people).isChecked()); assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Set null listener to test that the next click is not going to notify the // previously set listener and will allow selecting items. bottomNavigation.setOnItemSelectedListener(null); // Select one of our items bottomNavigation.setSelectedItemId(R.id.destination_home); // Verify that our previous listener has not been notified of the click verifyNoMoreInteractions(mockedListener); // Verify the correct item is now selected. assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_home).isChecked()); } @Test @SmallTest public void testNavigationReselectionListener() { // Add an OnItemReselectedListener OnItemReselectedListener reselectedListener = mock(NavigationBarView.OnItemReselectedListener.class); bottomNavigation.setOnItemReselectedListener(reselectedListener); // Select an item onView( allOf( withText(menuStringContent.get(R.id.destination_profile)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify the item is now selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Verify the listener was not called verify(reselectedListener, never()).onNavigationItemReselected(any(MenuItem.class)); // Select the same item again onView( allOf( withText(menuStringContent.get(R.id.destination_profile)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify the item is still selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Verify the listener was called verify(reselectedListener, times(1)) .onNavigationItemReselected(bottomNavigation.getMenu().findItem(R.id.destination_profile)); // Add an OnItemSelectedListener OnItemSelectedListener selectedListener = mock(OnItemSelectedListener.class); bottomNavigation.setOnItemSelectedListener(selectedListener); // Make the listener return true to allow selecting the item. when(selectedListener.onNavigationItemSelected(any(MenuItem.class))).thenReturn(true); // Select another item onView( allOf( withText(menuStringContent.get(R.id.destination_people)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify the item is now selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_people).isChecked()); // Verify the correct listeners were called verify(selectedListener, times(1)) .onNavigationItemSelected(bottomNavigation.getMenu().findItem(R.id.destination_people)); verify(reselectedListener, never()) .onNavigationItemReselected(bottomNavigation.getMenu().findItem(R.id.destination_people)); // Select the same item again onView( allOf( withText(menuStringContent.get(R.id.destination_people)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify the item is still selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_people).isChecked()); // Verify the correct listeners were called verifyNoMoreInteractions(selectedListener); verify(reselectedListener, times(1)) .onNavigationItemReselected(bottomNavigation.getMenu().findItem(R.id.destination_people)); // Remove the OnItemReselectedListener bottomNavigation.setOnItemReselectedListener(null); // Select the same item again onView( allOf( withText(menuStringContent.get(R.id.destination_people)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Verify the item is still selected assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_people).isChecked()); // Verify the reselectedListener was not called verifyNoMoreInteractions(reselectedListener); } @UiThreadTest @Test @SmallTest public void testSelectedItemIdWithEmptyMenu() { // First item initially selected assertEquals(R.id.destination_home, bottomNavigation.getSelectedItemId()); // Remove all the items for (int id : menuStringContent.keySet()) { bottomNavigation.getMenu().removeItem(id); } // Verify selected ID is zero assertEquals(0, bottomNavigation.getSelectedItemId()); // Add an item bottomNavigation.getMenu().add(0, R.id.destination_home, 0, R.string.navigate_home); // Verify item is selected assertEquals(R.id.destination_home, bottomNavigation.getSelectedItemId()); // Try selecting an invalid ID bottomNavigation.setSelectedItemId(R.id.destination_people); // Verify the view has not changed assertEquals(R.id.destination_home, bottomNavigation.getSelectedItemId()); } @Test @SmallTest public void testIconTinting() { final Resources res = activityTestRule.getActivity().getResources(); @ColorInt final int redFill = ResourcesCompat.getColor(res, R.color.test_red, null); @ColorInt final int greenFill = ResourcesCompat.getColor(res, R.color.test_green, null); @ColorInt final int blueFill = ResourcesCompat.getColor(res, R.color.test_blue, null); final int iconSize = res.getDimensionPixelSize(R.dimen.drawable_small_size); onView(withId(R.id.bottom_navigation)) .perform( setIconForMenuItem( R.id.destination_home, new TestDrawable(redFill, iconSize, iconSize))); onView(withId(R.id.bottom_navigation)) .perform( setIconForMenuItem( R.id.destination_profile, new TestDrawable(greenFill, iconSize, iconSize))); onView(withId(R.id.bottom_navigation)) .perform( setIconForMenuItem( R.id.destination_people, new TestDrawable(blueFill, iconSize, iconSize))); @ColorInt final 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 BottomNavigationView 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 BottomNavigationView. Specifically, we're checking the drawable the // ImageView with id R.id.navigation_bar_item_icon_view. If the internal implementation of // BottomNavigationView // changes, the second Matcher in the lookups below will need to be tweaked. onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_home)))) .check(matches(TestUtilsMatchers.drawable(defaultTintColor, allowedComponentVariance))); onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_profile)))) .check(matches(TestUtilsMatchers.drawable(defaultTintColor, allowedComponentVariance))); onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_people)))) .check(matches(TestUtilsMatchers.drawable(defaultTintColor, allowedComponentVariance))); @ColorInt final int newTintColor = ResourcesCompat.getColor(res, R.color.red_translucent, null); onView(withId(R.id.bottom_navigation)) .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( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_home)))) .check(matches(TestUtilsMatchers.drawable(newTintColor, allowedComponentVariance))); onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_profile)))) .check(matches(TestUtilsMatchers.drawable(newTintColor, allowedComponentVariance))); onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_people)))) .check(matches(TestUtilsMatchers.drawable(newTintColor, allowedComponentVariance))); // And now remove all icon tinting onView(withId(R.id.bottom_navigation)).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( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_home)))) .check(matches(TestUtilsMatchers.drawable(redFill, allowedComponentVariance))); onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_profile)))) .check(matches(TestUtilsMatchers.drawable(greenFill, allowedComponentVariance))); onView( allOf( withId(R.id.navigation_bar_item_icon_view), isDescendantOfA(withId(R.id.destination_people)))) .check(matches(TestUtilsMatchers.drawable(blueFill, allowedComponentVariance))); } @UiThreadTest @Test @SmallTest public void testItemChecking() throws Throwable { final Menu menu = bottomNavigation.getMenu(); assertTrue(menu.getItem(0).isChecked()); checkAndVerifyExclusiveItem(menu, R.id.destination_home); checkAndVerifyExclusiveItem(menu, R.id.destination_profile); checkAndVerifyExclusiveItem(menu, R.id.destination_people); } @UiThreadTest @Test @SmallTest public void testAutoLabelVisibilityItemChecking() throws Throwable { bottomNavigation.getMenu().clear(); bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_AUTO); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_shifting_content); final Menu menu = bottomNavigation.getMenu(); assertTrue(menu.getItem(0).isChecked()); checkAndVerifyExclusiveItem(menu, R.id.destination_home); checkAndVerifyExclusiveItem(menu, R.id.destination_profile); checkAndVerifyExclusiveItem(menu, R.id.destination_people); } @UiThreadTest @Test @SmallTest public void testClearingMenu() throws Throwable { bottomNavigation.getMenu().clear(); assertEquals(0, bottomNavigation.getMenu().size()); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_content); assertEquals(3, bottomNavigation.getMenu().size()); } @UiThreadTest @Test @SmallTest public void testClearingAutoMenu() throws Throwable { bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_AUTO); bottomNavigation.getMenu().clear(); assertEquals(0, bottomNavigation.getMenu().size()); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_shifting_content); assertEquals(4, bottomNavigation.getMenu().size()); } @UiThreadTest @Test @SmallTest public void testClearingSelectedMenu() throws Throwable { bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_SELECTED); bottomNavigation.getMenu().clear(); assertEquals(0, bottomNavigation.getMenu().size()); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_shifting_content); assertEquals(4, bottomNavigation.getMenu().size()); } @UiThreadTest @Test @SmallTest public void testClearingLabeledMenu() throws Throwable { bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED); bottomNavigation.getMenu().clear(); assertEquals(0, bottomNavigation.getMenu().size()); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_content); assertEquals(3, bottomNavigation.getMenu().size()); } @UiThreadTest @Test @SmallTest public void testClearingUnlabeledMenu() throws Throwable { bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_UNLABELED); bottomNavigation.getMenu().clear(); assertEquals(0, bottomNavigation.getMenu().size()); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_shifting_content); assertEquals(4, bottomNavigation.getMenu().size()); } @UiThreadTest @Test @SmallTest public void testSettingMenuItemVisibility() throws Throwable { final MenuItem homeMenuItem = bottomNavigation.getMenu().findItem(R.id.destination_home); assertTrue(homeMenuItem.isVisible()); homeMenuItem.setVisible(false); assertFalse(homeMenuItem.isVisible()); bottomNavigation.getMenu().clear(); bottomNavigation.inflateMenu(R.menu.bottom_navigation_view_with_invisible_button_content); assertEquals(3, bottomNavigation.getMenu().size()); final MenuItem destinationMenuItem = bottomNavigation.getMenu().findItem(R.id.destination_profile); assertFalse(destinationMenuItem.isVisible()); destinationMenuItem.setVisible(true); assertTrue(destinationMenuItem.isVisible()); } @UiThreadTest @Test @SmallTest public void testSettingLabeledMenuItemVisibility() throws Throwable { bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED); final MenuItem homeMenuItem = bottomNavigation.getMenu().findItem(R.id.destination_home); assertTrue(homeMenuItem.isVisible()); homeMenuItem.setVisible(false); assertFalse(homeMenuItem.isVisible()); } @UiThreadTest @Test @SmallTest public void testSettingAutoItemVisibility() throws Throwable { bottomNavigation.getMenu().clear(); bottomNavigation.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_AUTO); bottomNavigation.inflateMenu( R.menu.bottom_navigation_view_shifting_with_invisible_button_content); assertEquals(4, bottomNavigation.getMenu().size()); final MenuItem destinationMenuItem = bottomNavigation.getMenu().findItem(R.id.destination_profile); assertFalse(destinationMenuItem.isVisible()); destinationMenuItem.setVisible(true); assertTrue(destinationMenuItem.isVisible()); } @Test @SmallTest public void testLabelVisibilityDefaultShouldNotShift() { // Check that there are 3 menu items, so that shifting mode should not be enabled. assertEquals(3, bottomNavigation.getMenu().size()); // Ensure that the items do not shift when there are 3 or less items. onView(withId(R.id.destination_profile)).perform(click()); checkLabelVisibilityIsLabeled(); onView(withId(R.id.destination_people)).perform(click()); checkLabelVisibilityIsLabeled(); } @Test @SmallTest public void testLabelVisibilityDefaultShouldShift() { // Add a navigation item to trigger the default shifting behavior when there are more than 3 // navigation items. onView(withId(R.id.bottom_navigation)).perform(addMenuItem("Settings")); assertEquals(4, bottomNavigation.getMenu().size()); // Ensure that the items shift when there are more than 3 items. onView(withId(R.id.destination_profile)).perform(click()); checkLabelVisibilityIsSelected(); onView(withId(R.id.destination_people)).perform(click()); checkLabelVisibilityIsSelected(); } @Test @SmallTest public void testLabelVisibilityModeUnlabeledLabelsAreNotDisplayed() { onView(withId(R.id.bottom_navigation)) .perform(setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_UNLABELED)); onView(withId(R.id.destination_profile)).perform(click()); checkLabelVisibilityIsUnlabeled(); } @Test @SmallTest public void testLabelVisibilityModeLabeledLabelsAreDisplayed() { onView(withId(R.id.bottom_navigation)) .perform(setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED)); onView(withId(R.id.destination_profile)).perform(click()); checkLabelVisibilityIsLabeled(); } @Test @SmallTest public void testLabelVisibilityModeSelectedLabelsAreDisplayedWhenSelected() { onView(withId(R.id.bottom_navigation)) .perform(setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_SELECTED)); onView(withId(R.id.destination_profile)).perform(click()); checkLabelVisibilityIsSelected(); } @Test @SmallTest public void testIconSizeChanges() { int size = 32; onView(withId(R.id.bottom_navigation)).perform(setIconSize(size)); assertEquals(size, bottomNavigation.getItemIconSize()); // Change the size again to ensure that the size actually changes. size = 18; onView(withId(R.id.bottom_navigation)).perform(setIconSize(size)); assertEquals(size, bottomNavigation.getItemIconSize()); } @Test @SmallTest public void testSavedState() throws Throwable { // Select an item other than the first onView( allOf( withText(menuStringContent.get(R.id.destination_profile)), isDescendantOfA(withId(R.id.bottom_navigation)), isDisplayed())) .perform(click()); // Show badge number on the first bottom navigation item view. onView(withId(R.id.bottom_navigation)) .perform(showBadgeNumberForMenuItem(R.id.destination_home, 75)); assertTrue(bottomNavigation.getMenu().findItem(R.id.destination_profile).isChecked()); // Save the current state SparseArray container = new SparseArray<>(); bottomNavigation.saveHierarchyState(container); // Restore the state into a fresh BottomNavigationView activityTestRule.runOnUiThread( () -> { BottomNavigationView testView = new BottomNavigationView(activityTestRule.getActivity()); testView.setId(R.id.bottom_navigation); testView.inflateMenu(R.menu.bottom_navigation_view_content); testView.restoreHierarchyState(container); assertTrue(testView.getMenu().findItem(R.id.destination_profile).isChecked()); assertTrue( ((NavigationBarMenuView) testView.getMenuView()) .findItemView(R.id.destination_home) .getBadge() .isVisible()); assertTrue(testView.getBadge(R.id.destination_home).isVisible()); assertEquals(75, testView.getBadge(R.id.destination_home).getNumber()); assertEquals(999, testView.getBadge(R.id.destination_home).getMaxNumber()); }); } @UiThreadTest @Test @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N) public void testPointerIcon() throws Throwable { final Activity activity = activityTestRule.getActivity(); final MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); final Menu menu = bottomNavigation.getMenu(); for (int i = 0; i < menu.size(); i++) { final MenuItem item = menu.getItem(i); assertTrue(item.isEnabled()); final View itemView = activity.findViewById(item.getItemId()); assertNull(itemView.onResolvePointerIcon(event, 0)); item.setEnabled(false); assertNull(itemView.onResolvePointerIcon(event, 0)); item.setEnabled(true); assertNull(itemView.onResolvePointerIcon(event, 0)); } } @UiThreadTest @Test @SmallTest public void testContentDescription() { ViewGroup menuView = (ViewGroup) bottomNavigation.getChildAt(0); final int count = menuView.getChildCount(); for (int i = 0; i < count; i++) { View child = menuView.getChildAt(i); // We're using the same strings for content description assertEquals(menuStringContent.get(child.getId()), child.getContentDescription().toString()); } menuView.getChildAt(0).getContentDescription(); } @UiThreadTest @Test @SmallTest @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M) public void testOnInitializeAccessibilityNodeInfo() { NavigationBarMenuView menuView = (NavigationBarMenuView) bottomNavigation.getMenuView(); AccessibilityNodeInfoCompat groupInfoCompat = AccessibilityNodeInfoCompat.obtain(); ViewCompat.onInitializeAccessibilityNodeInfo(menuView, groupInfoCompat); CollectionInfoCompat collectionInfo = groupInfoCompat.getCollectionInfo(); Assert.assertEquals(3, collectionInfo.getColumnCount()); Assert.assertEquals(1, collectionInfo.getRowCount()); NavigationBarItemView secondChild = (NavigationBarItemView) menuView.getChildAt(1); secondChild.setSelected(true); AccessibilityNodeInfoCompat buttonInfoCompat = AccessibilityNodeInfoCompat.obtain(); ViewCompat.onInitializeAccessibilityNodeInfo(secondChild, buttonInfoCompat); // A tab that is currently selected won't be clickable by a11y assertFalse(buttonInfoCompat.isClickable()); assertFalse( AccessibilityUtils.hasAction(buttonInfoCompat, AccessibilityActionCompat.ACTION_CLICK)); CollectionItemInfoCompat itemInfo = buttonInfoCompat.getCollectionItemInfo(); Assert.assertEquals(1, itemInfo.getColumnIndex()); Assert.assertEquals(0, itemInfo.getRowIndex()); assertTrue(itemInfo.isSelected()); } private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable { menu.findItem(id).setChecked(true); for (int i = 0; i < menu.size(); i++) { final MenuItem item = menu.getItem(i); if (item.getItemId() == id) { assertTrue(item.isChecked()); } else { assertFalse(item.isChecked()); } } } private void checkLabelVisibilityIsLabeled() { for (int id : MENU_CONTENT_ITEM_IDS) { MenuItem item = bottomNavigation.getMenu().findItem(id); if (item.isChecked()) { checkLargeLabelIsShown(String.valueOf(item.getTitle())); } else { checkSmallLabelIsShown(String.valueOf(item.getTitle())); } } } private void checkLabelVisibilityIsUnlabeled() { for (int id : MENU_CONTENT_ITEM_IDS) { checkNoLabelIsShown(String.valueOf(bottomNavigation.getMenu().findItem(id).getTitle())); } } private void checkLabelVisibilityIsSelected() { for (int id : MENU_CONTENT_ITEM_IDS) { MenuItem item = bottomNavigation.getMenu().findItem(id); if (item.isChecked()) { checkLargeLabelIsShown(String.valueOf(item.getTitle())); } else { checkNoLabelIsShown(String.valueOf(item.getTitle())); } } } private void checkSmallLabelIsShown(String label) { onView(allOf(withId(R.id.navigation_bar_item_large_label_view), withText(label))) .check(matches(not(isDisplayed()))); onView(allOf(withId(R.id.navigation_bar_item_small_label_view), withText(label))) .check(matches(isDisplayed())); } private void checkLargeLabelIsShown(String label) { onView(allOf(withId(R.id.navigation_bar_item_large_label_view), withText(label))) .check(matches(isDisplayed())); onView(allOf(withId(R.id.navigation_bar_item_small_label_view), withText(label))) .check(matches(not(isDisplayed()))); } private void checkNoLabelIsShown(String label) { onView(allOf(withId(R.id.navigation_bar_item_large_label_view), withText(label))) .check(matches(not(isDisplayed()))); onView(allOf(withId(R.id.navigation_bar_item_small_label_view), withText(label))) .check(matches(not(isDisplayed()))); } }