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.widget;
18
19import static android.support.test.espresso.Espresso.onView;
20import static android.support.test.espresso.Espresso.pressBack;
21import static android.support.test.espresso.action.ViewActions.clearText;
22import static android.support.test.espresso.action.ViewActions.click;
23import static android.support.test.espresso.action.ViewActions.replaceText;
24import static android.support.test.espresso.assertion.ViewAssertions.matches;
25import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
26import static android.support.test.espresso.matcher.ViewMatchers.withId;
27import static android.support.test.espresso.matcher.ViewMatchers.withText;
28import static android.widget.espresso.DragHandleUtils.onHandleView;
29import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
30import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
31import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
32import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupContainsItem;
33import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsDisplayed;
34import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsNotDisplayed;
35import static android.widget.espresso.SuggestionsPopupwindowUtils.clickSuggestionsPopupItem;
36import static android.widget.espresso.SuggestionsPopupwindowUtils.onSuggestionsPopup;
37import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
38import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
39import static org.hamcrest.Matchers.is;
40import android.content.res.TypedArray;
41import android.support.test.espresso.NoMatchingViewException;
42import android.support.test.espresso.ViewAssertion;
43import android.test.ActivityInstrumentationTestCase2;
44import android.test.suitebuilder.annotation.SmallTest;
45import android.test.suitebuilder.annotation.Suppress;
46import android.text.Selection;
47import android.text.Spannable;
48import android.text.Spanned;
49import android.text.TextPaint;
50import android.text.style.SuggestionSpan;
51import android.text.style.TextAppearanceSpan;
52import android.view.View;
53
54import com.android.frameworks.coretests.R;
55
56/**
57 * SuggestionsPopupWindowTest tests.
58 *
59 * TODO: Add tests for when there are no suggestions
60 */
61public class SuggestionsPopupWindowTest extends ActivityInstrumentationTestCase2<TextViewActivity> {
62
63    public SuggestionsPopupWindowTest() {
64        super(TextViewActivity.class);
65    }
66
67    @Override
68    protected void setUp() throws Exception {
69        super.setUp();
70        getActivity();
71    }
72
73    private void setSuggestionSpan(SuggestionSpan span, int start, int end) {
74        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
75        textView.post(
76                () -> {
77                    final Spannable text = (Spannable) textView.getText();
78                    text.setSpan(span, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
79                    Selection.setSelection(text, (start + end) / 2);
80                });
81        getInstrumentation().waitForIdleSync();
82    }
83
84    @SmallTest
85    public void testOnTextContextMenuItem() {
86        final String text = "abc def ghi";
87
88        onView(withId(R.id.textview)).perform(click());
89        onView(withId(R.id.textview)).perform(replaceText(text));
90
91        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
92                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
93        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
94
95        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
96        textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE));
97        getInstrumentation().waitForIdleSync();
98
99        assertSuggestionsPopupIsDisplayed();
100    }
101
102    @SmallTest
103    public void testSelectionActionMode() {
104        final String text = "abc def ghi";
105
106        onView(withId(R.id.textview)).perform(click());
107        onView(withId(R.id.textview)).perform(replaceText(text));
108
109        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
110                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
111        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
112
113        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
114        sleepForFloatingToolbarPopup();
115        assertFloatingToolbarContainsItem(
116                getActivity().getString(com.android.internal.R.string.replace));
117        sleepForFloatingToolbarPopup();
118        clickFloatingToolbarItem(
119                getActivity().getString(com.android.internal.R.string.replace));
120
121        assertSuggestionsPopupIsDisplayed();
122    }
123
124    @SmallTest
125    public void testInsertionActionMode() {
126        final String text = "abc def ghi";
127
128        onView(withId(R.id.textview)).perform(click());
129        onView(withId(R.id.textview)).perform(replaceText(text));
130
131        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
132                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
133        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
134
135        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
136        onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
137        sleepForFloatingToolbarPopup();
138        assertFloatingToolbarContainsItem(
139                getActivity().getString(com.android.internal.R.string.replace));
140        clickFloatingToolbarItem(
141                getActivity().getString(com.android.internal.R.string.replace));
142
143        assertSuggestionsPopupIsDisplayed();
144    }
145
146    private void showSuggestionsPopup() {
147        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
148        textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE));
149        getInstrumentation().waitForIdleSync();
150        assertSuggestionsPopupIsDisplayed();
151    }
152
153    @SmallTest
154    public void testSuggestionItems() {
155        final String text = "abc def ghi";
156
157        onView(withId(R.id.textview)).perform(click());
158        onView(withId(R.id.textview)).perform(replaceText(text));
159
160        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
161                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
162        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
163
164        showSuggestionsPopup();
165
166        assertSuggestionsPopupIsDisplayed();
167        assertSuggestionsPopupContainsItem("DEF");
168        assertSuggestionsPopupContainsItem("Def");
169        assertSuggestionsPopupContainsItem(
170                getActivity().getString(com.android.internal.R.string.delete));
171
172        // Select an item.
173        clickSuggestionsPopupItem("DEF");
174        assertSuggestionsPopupIsNotDisplayed();
175        onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi")));
176
177        showSuggestionsPopup();
178        assertSuggestionsPopupIsDisplayed();
179        assertSuggestionsPopupContainsItem("def");
180        assertSuggestionsPopupContainsItem("Def");
181        assertSuggestionsPopupContainsItem(
182                getActivity().getString(com.android.internal.R.string.delete));
183
184        // Delete
185        clickSuggestionsPopupItem(
186                getActivity().getString(com.android.internal.R.string.delete));
187        assertSuggestionsPopupIsNotDisplayed();
188        onView(withId(R.id.textview)).check(matches(withText("abc ghi")));
189    }
190
191    @SmallTest
192    public void testMisspelled() {
193        final String text = "abc def ghi";
194
195        onView(withId(R.id.textview)).perform(click());
196        onView(withId(R.id.textview)).perform(replaceText(text));
197
198        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
199                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_MISSPELLED);
200        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
201
202        showSuggestionsPopup();
203
204        assertSuggestionsPopupIsDisplayed();
205        assertSuggestionsPopupContainsItem("DEF");
206        assertSuggestionsPopupContainsItem("Def");
207        assertSuggestionsPopupContainsItem(
208                getActivity().getString(com.android.internal.R.string.addToDictionary));
209        assertSuggestionsPopupContainsItem(
210                getActivity().getString(com.android.internal.R.string.delete));
211
212        // Click "Add to dictionary".
213        clickSuggestionsPopupItem(
214                getActivity().getString(com.android.internal.R.string.addToDictionary));
215        // TODO: Check if add to dictionary dialog is displayed.
216    }
217
218    @SmallTest
219    public void testEasyCorrect() {
220        final String text = "abc def ghi";
221
222        onView(withId(R.id.textview)).perform(click());
223        onView(withId(R.id.textview)).perform(replaceText(text));
224
225        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
226                new String[]{"DEF", "Def"},
227                SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
228        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
229
230        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
231
232        assertSuggestionsPopupIsDisplayed();
233        assertSuggestionsPopupContainsItem("DEF");
234        assertSuggestionsPopupContainsItem("Def");
235        assertSuggestionsPopupContainsItem(
236                getActivity().getString(com.android.internal.R.string.delete));
237
238        // Select an item.
239        clickSuggestionsPopupItem("DEF");
240        assertSuggestionsPopupIsNotDisplayed();
241        onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi")));
242
243        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
244        assertSuggestionsPopupIsNotDisplayed();
245
246        showSuggestionsPopup();
247        assertSuggestionsPopupIsDisplayed();
248        assertSuggestionsPopupContainsItem("def");
249        assertSuggestionsPopupContainsItem("Def");
250        assertSuggestionsPopupContainsItem(
251                getActivity().getString(com.android.internal.R.string.delete));
252    }
253
254    @SmallTest
255    public void testTextAppearanceInSuggestionsPopup() {
256        final String text = "abc def ghi";
257
258        final String[] singleWordCandidates = {"DEF", "Def"};
259        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
260                singleWordCandidates, SuggestionSpan.FLAG_MISSPELLED);
261        final String[] multiWordCandidates = {"ABC DEF GHI", "Abc Def Ghi"};
262        final SuggestionSpan multiWordSuggestionSpan = new SuggestionSpan(getActivity(),
263                multiWordCandidates, SuggestionSpan.FLAG_MISSPELLED);
264
265        final TypedArray array =
266                getActivity().obtainStyledAttributes(com.android.internal.R.styleable.Theme);
267        final int id = array.getResourceId(
268                com.android.internal.R.styleable.Theme_textEditSuggestionHighlightStyle, 0);
269        array.recycle();
270        final TextAppearanceSpan expectedSpan = new TextAppearanceSpan(getActivity(), id);
271        final TextPaint tmpTp = new TextPaint();
272        expectedSpan.updateDrawState(tmpTp);
273        final int expectedHighlightTextColor = tmpTp.getColor();
274        final float expectedHighlightTextSize = tmpTp.getTextSize();
275        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
276
277        // In this test, the SuggestionsPopupWindow looks like
278        //   abc def ghi
279        // -----------------
280        // | abc *DEF* ghi |
281        // | abc *Def* ghi |
282        // | *ABC DEF GHI* |
283        // | *Abc Def Ghi* |
284        // -----------------
285        // | DELETE        |
286        // -----------------
287        // *XX* means that XX is highlighted.
288        for (int i = 0; i < 2; i++) {
289            onView(withId(R.id.textview)).perform(click());
290            onView(withId(R.id.textview)).perform(replaceText(text));
291            setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
292            setSuggestionSpan(multiWordSuggestionSpan, 0, text.length());
293
294            showSuggestionsPopup();
295            assertSuggestionsPopupIsDisplayed();
296            assertSuggestionsPopupContainsItem("abc DEF ghi");
297            assertSuggestionsPopupContainsItem("abc Def ghi");
298            assertSuggestionsPopupContainsItem("ABC DEF GHI");
299            assertSuggestionsPopupContainsItem("Abc Def Ghi");
300            assertSuggestionsPopupContainsItem(
301                    getActivity().getString(com.android.internal.R.string.delete));
302
303            onSuggestionsPopup().check(new ViewAssertion() {
304                @Override
305                public void check(View view, NoMatchingViewException e) {
306                    final ListView listView = (ListView) view.findViewById(
307                            com.android.internal.R.id.suggestionContainer);
308                    assertNotNull(listView);
309                    final int childNum = listView.getChildCount();
310                    assertEquals(singleWordCandidates.length + multiWordCandidates.length,
311                            childNum);
312
313                    for (int j = 0; j < childNum; j++) {
314                        final TextView suggestion = (TextView) listView.getChildAt(j);
315                        assertNotNull(suggestion);
316                        final Spanned spanned = (Spanned) suggestion.getText();
317                        assertNotNull(spanned);
318
319                        // Check that the suggestion item order is kept.
320                        final String expectedText;
321                        if (j < singleWordCandidates.length) {
322                            expectedText = "abc " + singleWordCandidates[j] + " ghi";
323                        } else {
324                            expectedText = multiWordCandidates[j - singleWordCandidates.length];
325                        }
326                        assertEquals(expectedText, spanned.toString());
327
328                        // Check that the text is highlighted with correct color and text size.
329                        final TextAppearanceSpan[] taSpan = spanned.getSpans(
330                                text.indexOf('d'), text.indexOf('f') + 1, TextAppearanceSpan.class);
331                        assertEquals(1, taSpan.length);
332                        TextPaint tp = new TextPaint();
333                        taSpan[0].updateDrawState(tp);
334                        assertEquals(expectedHighlightTextColor, tp.getColor());
335                        assertEquals(expectedHighlightTextSize, tp.getTextSize());
336
337                        // Check the correct part of the text is highlighted.
338                        final int expectedStart;
339                        final int expectedEnd;
340                        if (j < singleWordCandidates.length) {
341                            expectedStart = text.indexOf('d');
342                            expectedEnd = text.indexOf('f') + 1;
343                        } else {
344                            expectedStart = 0;
345                            expectedEnd = text.length();
346                        }
347                        assertEquals(expectedStart, spanned.getSpanStart(taSpan[0]));
348                        assertEquals(expectedEnd, spanned.getSpanEnd(taSpan[0]));
349                    }
350                }
351            });
352            pressBack();
353            onView(withId(R.id.textview))
354                    .inRoot(withDecorView(is(getActivity().getWindow().getDecorView())))
355                    .perform(clearText());
356        }
357    }
358}
359