1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.car.widget; 18 19 import static android.support.test.espresso.Espresso.onView; 20 import static android.support.test.espresso.action.ViewActions.click; 21 import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; 22 import static android.support.test.espresso.matcher.ViewMatchers.withId; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertNull; 27 28 import android.content.Context; 29 import android.support.test.filters.MediumTest; 30 import android.support.test.rule.ActivityTestRule; 31 import android.support.test.runner.AndroidJUnit4; 32 import androidx.core.util.Preconditions; 33 import android.view.View; 34 import android.widget.ImageButton; 35 import android.widget.LinearLayout; 36 import android.widget.Space; 37 import android.widget.ToggleButton; 38 39 import org.junit.Before; 40 import org.junit.Rule; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import androidx.car.R; 45 46 /** Unit tests for {@link ActionBar}. */ 47 @RunWith(AndroidJUnit4.class) 48 @MediumTest 49 public class ActionBarTest { 50 @Rule 51 public ActivityTestRule<ActionBarTestActivity> mActivityRule = 52 new ActivityTestRule<>(ActionBarTestActivity.class); 53 private ActionBarTestActivity mActivity; 54 private ActionBar mActionBar; 55 private LinearLayout mRowsContainer; 56 private View[] mItems; 57 58 private static final int TOP_ROW_IDX = 0; 59 private static final int BOTTOM_ROW_IDX = 1; 60 private static final int NUM_COLS = 5; 61 62 @Before 63 public void setUp() { 64 mActivity = mActivityRule.getActivity(); 65 mActionBar = mActivity.findViewById(androidx.car.test.R.id.action_bar); 66 mRowsContainer = mActionBar.findViewById(R.id.rows_container); 67 } 68 69 private void setUpActionBarItems(int numItems) { 70 mItems = new View[numItems]; 71 for (int i = 0; i < numItems; i++) { 72 mItems[i] = createButton(mActivity); 73 } 74 mActivity.runOnUiThread(() -> mActionBar.setViews(mItems)); 75 } 76 77 private ImageButton createButton(Context context) { 78 ImageButton button = new ImageButton(context, null, R.style.Widget_Car_Button_ActionBar); 79 button.setImageDrawable(context.getDrawable(androidx.car.test.R.drawable.ic_overflow)); 80 return button; 81 } 82 83 /** 84 * Asserts that only the first 'numItems' slots are used. 85 */ 86 private void assertLeftItemsNotEmpty(int rowIdx, int numItems) { 87 for (int colIdx = 0; colIdx < NUM_COLS - 1; colIdx++) { 88 if (colIdx < numItems) { 89 assertNotNull(String.format("Slot (%d, %d) should be taken", rowIdx, colIdx), 90 mActionBar.getViewAt(rowIdx, colIdx)); 91 } else { 92 assertNull(String.format("Slot (%d, %d) should be empty", rowIdx, colIdx), 93 mActionBar.getViewAt(rowIdx, colIdx)); 94 } 95 } 96 } 97 98 /** 99 * Tests that the bar with no children views is displayed correctly 100 */ 101 @Test 102 public void testEmptyState() { 103 setUpActionBarItems(0); 104 onView(withId(androidx.car.test.R.id.action_bar)).check((view, noViewFoundException) -> { 105 Preconditions.checkNotNull(view); 106 // All slots should be empty. 107 assertLeftItemsNotEmpty(TOP_ROW_IDX, 0); 108 assertLeftItemsNotEmpty(BOTTOM_ROW_IDX, 0); 109 }); 110 } 111 112 /** 113 * Tests that slots are used from left to right and from bottom to top 114 */ 115 @Test 116 public void testNormalSlotUsage() { 117 for (int items = 1; items < NUM_COLS - 1; items++) { 118 setUpActionBarItems(items); 119 final int numItems = items; 120 onView(withId(androidx.car.test.R.id.action_bar)) 121 .check((view, noViewFoundException) -> { 122 Preconditions.checkNotNull(view); 123 // Top row should be empty 124 assertLeftItemsNotEmpty(TOP_ROW_IDX, 0); 125 // Expand/collapse slot should be empty 126 assertNull("Expand/collapse should be empty" , 127 mActionBar.getViewAt(BOTTOM_ROW_IDX, NUM_COLS - 1)); 128 // Slots on the bottom left should be taken while the rest should be empty. 129 assertLeftItemsNotEmpty(BOTTOM_ROW_IDX, numItems); 130 }); 131 } 132 } 133 134 private void assertRowVisibility(int rowIdx, int visibility) { 135 assertEquals(visibility, mRowsContainer.getChildAt(rowIdx).getVisibility()); 136 } 137 138 /** 139 * Tests that the expand/collapse button is added if enough views are set 140 */ 141 @Test 142 public void testExpandCollapseEnabled() { 143 setUpActionBarItems(NUM_COLS + 1); 144 145 // Top row should have 2 slot taken (as expand/collapse takes one slot on the bottom row) 146 onView(withContentDescription(R.string.action_bar_expand_collapse_button)) 147 .check((view, noViewFoundException) -> { 148 Preconditions.checkNotNull(view); 149 assertLeftItemsNotEmpty(TOP_ROW_IDX, 2); 150 assertLeftItemsNotEmpty(BOTTOM_ROW_IDX, NUM_COLS); 151 assertRowVisibility(TOP_ROW_IDX, View.GONE); 152 }) 153 // Check that expand/collapse works 154 .perform(click()) 155 .check((view, noViewFoundException) -> { 156 assertRowVisibility(TOP_ROW_IDX, View.VISIBLE); 157 }) 158 .perform(click()) 159 .check((view, noViewFoundException) -> { 160 assertRowVisibility(TOP_ROW_IDX, View.GONE); 161 }); 162 } 163 164 private void setViewInPosition(View view, @ActionBar.SlotPosition int position) { 165 mActivity.runOnUiThread(() -> { 166 mActionBar.setView(view, position); 167 }); 168 } 169 170 /** 171 * Tests that reserved slots are not used by normal views. 172 */ 173 @Test 174 public void testReservingNamedSlots() { 175 View mainView = createButton(mActivity); 176 setViewInPosition(mainView, ActionBar.SLOT_MAIN); 177 View leftView = new Space(mActivity); 178 setViewInPosition(leftView, ActionBar.SLOT_LEFT); 179 setUpActionBarItems(NUM_COLS + 1); 180 181 // Expand/collapse plus two other slots should be taken in the bottom row. 182 onView(withContentDescription(R.string.action_bar_expand_collapse_button)) 183 .check((view, noViewFoundException) -> { 184 // Only 2 items fit in the bottom row. The remaining 4 should be on the top 185 Preconditions.checkNotNull(view); 186 assertLeftItemsNotEmpty(TOP_ROW_IDX, 4); 187 assertLeftItemsNotEmpty(BOTTOM_ROW_IDX, NUM_COLS); 188 assertRowVisibility(TOP_ROW_IDX, View.GONE); 189 assertEquals(mainView, mActionBar.getViewAt(BOTTOM_ROW_IDX, 2)); 190 assertEquals(leftView, mActionBar.getViewAt(BOTTOM_ROW_IDX, 1)); 191 }) 192 .perform(click()) 193 .check((view, noViewFoundException) -> { 194 assertRowVisibility(TOP_ROW_IDX, View.VISIBLE); 195 }); 196 } 197 198 private void setExpandCollapseCustomView(View view) { 199 mActivity.runOnUiThread(() -> { 200 mActionBar.setExpandCollapseView(view); 201 }); 202 } 203 204 /** 205 * Tests setting custom expand/collapse views. 206 */ 207 @Test 208 public void testCustomExpandCollapseView() { 209 View customExpandCollapse = new ToggleButton(mActivity); 210 customExpandCollapse.setContentDescription(mActivity.getString( 211 R.string.action_bar_expand_collapse_button)); 212 setExpandCollapseCustomView(customExpandCollapse); 213 setUpActionBarItems(NUM_COLS + 1); 214 215 onView(withContentDescription(R.string.action_bar_expand_collapse_button)) 216 .check((view, noViewFoundException) -> { 217 Preconditions.checkNotNull(view); 218 assertEquals(customExpandCollapse, mActionBar.getViewAt(BOTTOM_ROW_IDX, 219 NUM_COLS - 1)); 220 }); 221 } 222 } 223