1 /*
2  * Copyright (C) 2016 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 com.android.documentsui.bots;
18 
19 import static androidx.test.espresso.Espresso.onView;
20 import static androidx.test.espresso.action.ViewActions.click;
21 import static androidx.test.espresso.assertion.ViewAssertions.matches;
22 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
23 import static androidx.test.espresso.matcher.ViewMatchers.withId;
24 import static androidx.test.espresso.matcher.ViewMatchers.withText;
25 
26 import static org.hamcrest.CoreMatchers.allOf;
27 import static org.hamcrest.CoreMatchers.anyOf;
28 import static org.hamcrest.CoreMatchers.is;
29 
30 import android.content.Context;
31 import android.support.test.uiautomator.UiDevice;
32 import android.support.test.uiautomator.UiObjectNotFoundException;
33 import android.view.View;
34 
35 import androidx.test.espresso.ViewInteraction;
36 import androidx.test.espresso.matcher.BoundedMatcher;
37 
38 import com.android.documentsui.DragOverTextView;
39 import com.android.documentsui.DropdownBreadcrumb;
40 import com.android.documentsui.R;
41 import com.android.documentsui.base.DocumentInfo;
42 
43 import junit.framework.Assert;
44 
45 import org.hamcrest.Description;
46 import org.hamcrest.Matcher;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51 import java.util.function.Predicate;
52 
53 /**
54  * A test helper class that provides support for controlling the UI Breadcrumb
55  * programmatically, and making assertions against the state of the UI.
56  * <p>
57  * Support for working directly with Roots and Directory view can be found in the respective bots.
58  */
59 public class BreadBot extends Bots.BaseBot {
60 
61     private static final Matcher<View> DROPDOWN_BREADCRUMB = withId(
62             R.id.dropdown_breadcrumb);
63 
64     private static final Matcher<View> HORIZONTAL_BREADCRUMB = withId(
65             R.id.horizontal_breadcrumb);
66 
67     // When any 'ol breadcrumb will do. Could be dropdown or horizontal.
68     @SuppressWarnings("unchecked")
69     private static final Matcher<View> BREADCRUMB = anyOf(
70             DROPDOWN_BREADCRUMB, HORIZONTAL_BREADCRUMB);
71 
72     private UiBot mMain;
73 
BreadBot( UiDevice device, Context context, int timeout, UiBot main)74     public BreadBot(
75             UiDevice device, Context context, int timeout, UiBot main) {
76         super(device, context, timeout);
77         mMain = main;
78     }
79 
assertTitle(String expected)80     public void assertTitle(String expected) {
81         // There is no discrete title part on the horizontal breadcrumb...
82         // so we only test on dropdown.
83         if (mMain.inDrawerLayout()) {
84             Matcher<Object> titleMatcher = dropdownTitleMatcher(expected);
85             onView(BREADCRUMB)
86                     .check(matches(titleMatcher));
87         }
88     }
89 
90     /**
91      * Reveals the bread crumb if it was hidden. This will likely be the case
92      * when the app is in drawer mode.
93      */
revealAsNeeded()94     public void revealAsNeeded() throws Exception {
95         if (mMain.inDrawerLayout()) {
96             onView(DROPDOWN_BREADCRUMB).perform(click());
97         }
98     }
99 
clickItem(String label)100     public void clickItem(String label) throws UiObjectNotFoundException {
101         if (mMain.inFixedLayout()) {
102             findHorizontalEntry(label).perform(click());
103         } else {
104             mMain.findMenuWithName(label).click();
105         }
106     }
107 
assertItemsPresent(String... items)108     public void assertItemsPresent(String... items) {
109         Predicate<String> checker = mMain.inFixedLayout()
110                     ? this::hasHorizontalEntry
111                     : mMain::hasMenuWithName;
112 
113         assertItemsPresent(items, checker);
114     }
115 
assertItemsPresent(String[] items, Predicate<String> predicate)116     public void assertItemsPresent(String[] items, Predicate<String> predicate) {
117         List<String> absent = new ArrayList<>();
118         for (String item : items) {
119             if (!predicate.test(item)) {
120                 absent.add(item);
121             }
122         }
123         if (!absent.isEmpty()) {
124             Assert.fail("Expected iteams " + Arrays.asList(items)
125                     + ", but missing " + absent);
126         }
127     }
128 
hasHorizontalEntry(String label)129     public boolean hasHorizontalEntry(String label) {
130         return Matchers.present(findHorizontalEntry(label), withText(label));
131     }
132 
133     @SuppressWarnings("unchecked")
findHorizontalEntry(String label)134     public ViewInteraction findHorizontalEntry(String label) {
135         return onView(allOf(isAssignableFrom(DragOverTextView.class), withText(label)));
136     }
137 
dropdownTitleMatcher(String expected)138     private static Matcher<Object> dropdownTitleMatcher(String expected) {
139         final Matcher<String> textMatcher = is(expected);
140         return new BoundedMatcher<Object, DropdownBreadcrumb>(DropdownBreadcrumb.class) {
141             @Override
142             public boolean matchesSafely(DropdownBreadcrumb breadcrumb) {
143                 DocumentInfo selectedDoc = (DocumentInfo) breadcrumb.getSelectedItem();
144                 return textMatcher.matches(selectedDoc.displayName);
145             }
146 
147             @Override
148             public void describeTo(Description description) {
149                 description.appendText("with breadcrumb title: ");
150                 textMatcher.describeTo(description);
151             }
152         };
153     }
154 }
155