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