1/* 2 * Copyright (C) 2015 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 17package android.support.design.testutils; 18 19import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 20import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 21import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast; 22import static android.support.test.espresso.matcher.ViewMatchers.isRoot; 23 24import android.graphics.drawable.Drawable; 25import android.os.Parcelable; 26import android.support.annotation.LayoutRes; 27import android.support.annotation.MenuRes; 28import android.support.annotation.Nullable; 29import android.support.design.widget.CollapsingToolbarLayout; 30import android.support.design.widget.NavigationView; 31import android.support.design.widget.TabLayout; 32import android.support.test.espresso.UiController; 33import android.support.test.espresso.ViewAction; 34import android.support.v4.view.ViewCompat; 35import android.support.v4.widget.TextViewCompat; 36import android.util.SparseArray; 37import android.view.LayoutInflater; 38import android.view.View; 39import android.view.ViewGroup; 40import android.widget.TextView; 41 42import org.hamcrest.Matcher; 43 44public class TestUtilsActions { 45 /** 46 * Replaces an existing {@link TabLayout} with a new one inflated from the specified 47 * layout resource. 48 */ 49 public static ViewAction replaceTabLayout(final @LayoutRes int tabLayoutResId) { 50 return new ViewAction() { 51 @Override 52 public Matcher<View> getConstraints() { 53 return isDisplayingAtLeast(90); 54 } 55 56 @Override 57 public String getDescription() { 58 return "Replace TabLayout"; 59 } 60 61 @Override 62 public void perform(UiController uiController, View view) { 63 uiController.loopMainThreadUntilIdle(); 64 65 final ViewGroup viewGroup = (ViewGroup) view; 66 final int childCount = viewGroup.getChildCount(); 67 // Iterate over children and find TabLayout 68 for (int i = 0; i < childCount; i++) { 69 View child = viewGroup.getChildAt(i); 70 if (child instanceof TabLayout) { 71 // Remove the existing TabLayout 72 viewGroup.removeView(child); 73 // Create a new one 74 final LayoutInflater layoutInflater = 75 LayoutInflater.from(view.getContext()); 76 final TabLayout newTabLayout = (TabLayout) layoutInflater.inflate( 77 tabLayoutResId, viewGroup, false); 78 // Make sure we're adding the new TabLayout at the same index 79 viewGroup.addView(newTabLayout, i); 80 break; 81 } 82 } 83 84 uiController.loopMainThreadUntilIdle(); 85 } 86 }; 87 } 88 89 /** 90 * Sets layout direction on the view. 91 */ 92 public static ViewAction setLayoutDirection(final int layoutDirection) { 93 return new ViewAction() { 94 @Override 95 public Matcher<View> getConstraints() { 96 return isDisplayed(); 97 } 98 99 @Override 100 public String getDescription() { 101 return "set layout direction"; 102 } 103 104 @Override 105 public void perform(UiController uiController, View view) { 106 uiController.loopMainThreadUntilIdle(); 107 108 ViewCompat.setLayoutDirection(view, layoutDirection); 109 110 uiController.loopMainThreadUntilIdle(); 111 } 112 }; 113 } 114 115 /** 116 * Sets title on the {@link CollapsingToolbarLayout}. 117 */ 118 public static ViewAction setTitle(final CharSequence title) { 119 return new ViewAction() { 120 @Override 121 public Matcher<View> getConstraints() { 122 return isAssignableFrom(CollapsingToolbarLayout.class); 123 } 124 125 @Override 126 public String getDescription() { 127 return "set toolbar title"; 128 } 129 130 @Override 131 public void perform(UiController uiController, View view) { 132 uiController.loopMainThreadUntilIdle(); 133 134 CollapsingToolbarLayout collapsingToolbarLayout = 135 (CollapsingToolbarLayout) view; 136 collapsingToolbarLayout.setTitle(title); 137 138 uiController.loopMainThreadUntilIdle(); 139 } 140 }; 141 } 142 143 /** 144 * Sets text content on {@link TextView} 145 */ 146 public static ViewAction setText(final @Nullable CharSequence text) { 147 return new ViewAction() { 148 @Override 149 public Matcher<View> getConstraints() { 150 return isAssignableFrom(TextView.class); 151 } 152 153 @Override 154 public String getDescription() { 155 return "TextView set text"; 156 } 157 158 @Override 159 public void perform(UiController uiController, View view) { 160 uiController.loopMainThreadUntilIdle(); 161 162 TextView textView = (TextView) view; 163 textView.setText(text); 164 165 uiController.loopMainThreadUntilIdle(); 166 } 167 }; 168 } 169 170 /** 171 * Adds tabs to {@link TabLayout} 172 */ 173 public static ViewAction addTabs(final String... tabs) { 174 return new ViewAction() { 175 @Override 176 public Matcher<View> getConstraints() { 177 return isAssignableFrom(TabLayout.class); 178 } 179 180 @Override 181 public String getDescription() { 182 return "TabLayout add tabs"; 183 } 184 185 @Override 186 public void perform(UiController uiController, View view) { 187 uiController.loopMainThreadUntilIdle(); 188 189 TabLayout tabLayout = (TabLayout) view; 190 for (int i = 0; i < tabs.length; i++) { 191 tabLayout.addTab(tabLayout.newTab().setText(tabs[i])); 192 } 193 194 uiController.loopMainThreadUntilIdle(); 195 } 196 }; 197 } 198 199 /** 200 * Dummy Espresso action that waits until the UI thread is idle. This action can be performed 201 * on the root view to wait for an ongoing animation to be completed. 202 */ 203 public static ViewAction waitUntilIdle() { 204 return new ViewAction() { 205 @Override 206 public Matcher<View> getConstraints() { 207 return isRoot(); 208 } 209 210 @Override 211 public String getDescription() { 212 return "wait for idle"; 213 } 214 215 @Override 216 public void perform(UiController uiController, View view) { 217 uiController.loopMainThreadUntilIdle(); 218 } 219 }; 220 } 221 222 public static ViewAction setEnabled(final boolean enabled) { 223 return new ViewAction() { 224 @Override 225 public Matcher<View> getConstraints() { 226 return isDisplayed(); 227 } 228 229 @Override 230 public String getDescription() { 231 return "set enabled"; 232 } 233 234 @Override 235 public void perform(UiController uiController, View view) { 236 uiController.loopMainThreadUntilIdle(); 237 238 view.setEnabled(enabled); 239 240 uiController.loopMainThreadUntilIdle(); 241 } 242 }; 243 } 244 245 public static ViewAction setClickable(final boolean clickable) { 246 return new ViewAction() { 247 @Override 248 public Matcher<View> getConstraints() { 249 return isDisplayed(); 250 } 251 252 @Override 253 public String getDescription() { 254 return "set clickable"; 255 } 256 257 @Override 258 public void perform(UiController uiController, View view) { 259 uiController.loopMainThreadUntilIdle(); 260 261 view.setClickable(clickable); 262 263 uiController.loopMainThreadUntilIdle(); 264 } 265 }; 266 } 267 268 public static ViewAction setSelected(final boolean selected) { 269 return new ViewAction() { 270 @Override 271 public Matcher<View> getConstraints() { 272 return isDisplayed(); 273 } 274 275 @Override 276 public String getDescription() { 277 return "set selected"; 278 } 279 280 @Override 281 public void perform(UiController uiController, View view) { 282 uiController.loopMainThreadUntilIdle(); 283 view.setSelected(selected); 284 uiController.loopMainThreadUntilIdle(); 285 } 286 }; 287 } 288 289 /** 290 * Sets compound drawables on {@link TextView} 291 */ 292 public static ViewAction setCompoundDrawablesRelative(final @Nullable Drawable start, 293 final @Nullable Drawable top, final @Nullable Drawable end, 294 final @Nullable Drawable bottom) { 295 return new ViewAction() { 296 @Override 297 public Matcher<View> getConstraints() { 298 return isAssignableFrom(TextView.class); 299 } 300 301 @Override 302 public String getDescription() { 303 return "TextView set compound drawables relative"; 304 } 305 306 @Override 307 public void perform(UiController uiController, View view) { 308 uiController.loopMainThreadUntilIdle(); 309 310 TextView textView = (TextView) view; 311 TextViewCompat.setCompoundDrawablesRelative(textView, start, top, end, bottom); 312 313 uiController.loopMainThreadUntilIdle(); 314 } 315 }; 316 } 317 318 /** 319 * Restores the saved hierarchy state. 320 * 321 * @param container The saved hierarchy state. 322 */ 323 public static ViewAction restoreHierarchyState(final SparseArray<Parcelable> container) { 324 return new ViewAction() { 325 @Override 326 public Matcher<View> getConstraints() { 327 return isAssignableFrom(View.class); 328 } 329 330 @Override 331 public String getDescription() { 332 return "restore the saved state"; 333 } 334 335 @Override 336 public void perform(UiController uiController, View view) { 337 uiController.loopMainThreadUntilIdle(); 338 view.restoreHierarchyState(container); 339 uiController.loopMainThreadUntilIdle(); 340 } 341 }; 342 } 343 344 /** 345 * Clears and inflates the menu. 346 * 347 * @param menuResId The menu resource XML to be used. 348 */ 349 public static ViewAction reinflateMenu(final @MenuRes int menuResId) { 350 return new ViewAction() { 351 @Override 352 public Matcher<View> getConstraints() { 353 return isAssignableFrom(NavigationView.class); 354 } 355 356 @Override 357 public String getDescription() { 358 return "clear and inflate menu " + menuResId; 359 } 360 361 @Override 362 public void perform(UiController uiController, View view) { 363 uiController.loopMainThreadUntilIdle(); 364 final NavigationView nv = (NavigationView) view; 365 nv.getMenu().clear(); 366 nv.inflateMenu(menuResId); 367 uiController.loopMainThreadUntilIdle(); 368 } 369 }; 370 } 371 372} 373