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
17package android.text.method;
18
19import android.support.test.InstrumentationRegistry;
20import android.support.test.filters.SmallTest;
21import android.support.test.runner.AndroidJUnit4;
22import android.text.InputType;
23import android.util.KeyUtils;
24import android.view.KeyEvent;
25import android.widget.EditText;
26import android.widget.TextView.BufferType;
27import org.junit.Before;
28import org.junit.Test;
29import org.junit.runner.RunWith;
30
31/**
32 * Test backspace key handling of {@link android.text.method.BaseKeyListener}.
33 *
34 * Only contains edge cases. For normal cases, see {@see android.text.method.cts.BackspaceTest}.
35 * TODO: introduce test cases for surrogate pairs and replacement span.
36 */
37@SmallTest
38@RunWith(AndroidJUnit4.class)
39public class BackspaceTest {
40    private EditText mTextView;
41
42    private static final BaseKeyListener mKeyListener = new BaseKeyListener() {
43        public int getInputType() {
44            return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
45        }
46    };
47
48    @Before
49    public void setup() {
50        mTextView = new EditText(InstrumentationRegistry.getInstrumentation().getContext());
51    }
52
53
54    // Sync the state to the TextView and call onKeyDown with KEYCODE_DEL key event.
55    // Then update the state to the result of TextView.
56    private void backspace(final EditorState state, int modifiers) {
57        mTextView.setText(state.mText, BufferType.EDITABLE);
58        mTextView.setKeyListener(mKeyListener);
59        mTextView.setSelection(state.mSelectionStart, state.mSelectionEnd);
60
61        final KeyEvent keyEvent = KeyUtils.generateKeyEvent(
62            KeyEvent.KEYCODE_DEL, KeyEvent.ACTION_DOWN, modifiers);
63        mTextView.onKeyDown(keyEvent.getKeyCode(), keyEvent);
64
65        state.mText = mTextView.getText();
66        state.mSelectionStart = mTextView.getSelectionStart();
67        state.mSelectionEnd = mTextView.getSelectionEnd();
68    }
69
70    @Test
71    public void testCombiningEnclosingKeycaps() {
72        EditorState state = new EditorState();
73
74        state.setByString("'1' U+E0101 U+20E3 |");
75        backspace(state, 0);
76        state.assertEquals("|");
77
78        // multiple COMBINING ENCLOSING KEYCAP
79        state.setByString("'1' U+20E3 U+20E3 |");
80        backspace(state, 0);
81        state.assertEquals("'1' U+20E3 |");
82        backspace(state, 0);
83        state.assertEquals("|");
84
85        // Isolated COMBINING ENCLOSING KEYCAP
86        state.setByString("U+20E3 |");
87        backspace(state, 0);
88        state.assertEquals("|");
89
90        // Isolated multiple COMBINING ENCLOSING KEYCAP
91        state.setByString("U+20E3 U+20E3 |");
92        backspace(state, 0);
93        state.assertEquals("U+20E3 |");
94        backspace(state, 0);
95        state.assertEquals("|");
96    }
97
98    @Test
99    public void testVariationSelector() {
100        EditorState state = new EditorState();
101
102        // Isolated variation selector
103        state.setByString("U+FE0F |");
104        backspace(state, 0);
105        state.assertEquals("|");
106
107        state.setByString("U+E0100 |");
108        backspace(state, 0);
109        state.assertEquals("|");
110
111        // Isolated multiple variation selectors
112        state.setByString("U+FE0F U+FE0F |");
113        backspace(state, 0);
114        state.assertEquals("U+FE0F |");
115        backspace(state, 0);
116        state.assertEquals("|");
117
118        state.setByString("U+FE0F U+E0100 |");
119        backspace(state, 0);
120        state.assertEquals("U+FE0F |");
121        backspace(state, 0);
122        state.assertEquals("|");
123
124        state.setByString("U+E0100 U+FE0F |");
125        backspace(state, 0);
126        state.assertEquals("U+E0100 |");
127        backspace(state, 0);
128        state.assertEquals("|");
129
130        state.setByString("U+E0100 U+E0100 |");
131        backspace(state, 0);
132        state.assertEquals("U+E0100 |");
133        backspace(state, 0);
134        state.assertEquals("|");
135
136        // Multiple variation selectors
137        state.setByString("'#' U+FE0F U+FE0F |");
138        backspace(state, 0);
139        state.assertEquals("'#' U+FE0F |");
140        backspace(state, 0);
141        state.assertEquals("|");
142
143        state.setByString("'#' U+FE0F U+E0100 |");
144        backspace(state, 0);
145        state.assertEquals("'#' U+FE0F |");
146        backspace(state, 0);
147        state.assertEquals("|");
148
149        state.setByString("U+845B U+E0100 U+FE0F |");
150        backspace(state, 0);
151        state.assertEquals("U+845B U+E0100 |");
152        backspace(state, 0);
153        state.assertEquals("|");
154
155        state.setByString("U+845B U+E0100 U+E0100 |");
156        backspace(state, 0);
157        state.assertEquals("U+845B U+E0100 |");
158        backspace(state, 0);
159        state.assertEquals("|");
160    }
161
162    @Test
163    public void testEmojiZWJSequence() {
164        EditorState state = new EditorState();
165
166        // U+200D is ZERO WIDTH JOINER.
167        state.setByString("U+1F441 U+200D U+1F5E8 |");
168        backspace(state, 0);
169        state.assertEquals("|");
170
171        state.setByString("U+1F441 U+200D U+1F5E8 U+FE0E |");
172        backspace(state, 0);
173        state.assertEquals("|");
174
175        state.setByString("U+1F469 U+200D U+1F373 |");
176        backspace(state, 0);
177        state.assertEquals("|");
178
179        state.setByString("U+1F487 U+200D U+2640 |");
180        backspace(state, 0);
181        state.assertEquals("|");
182
183        state.setByString("U+1F487 U+200D U+2640 U+FE0F |");
184        backspace(state, 0);
185        state.assertEquals("|");
186
187        state.setByString("U+1F468 U+200D U+2764 U+FE0F U+200D U+1F48B U+200D U+1F468 |");
188        backspace(state, 0);
189        state.assertEquals("|");
190
191        // Emoji modifier can be appended to the first emoji.
192        state.setByString("U+1F469 U+1F3FB U+200D U+1F4BC |");
193        backspace(state, 0);
194        state.assertEquals("|");
195
196        // End with ZERO WIDTH JOINER
197        state.setByString("U+1F441 U+200D |");
198        backspace(state, 0);
199        state.assertEquals("U+1F441 |");
200        backspace(state, 0);
201        state.assertEquals("|");
202
203        // Start with ZERO WIDTH JOINER
204        state.setByString("U+200D U+1F5E8 |");
205        backspace(state, 0);
206        state.assertEquals("U+200D |");
207        backspace(state, 0);
208        state.assertEquals("|");
209
210        state.setByString("U+FE0E U+200D U+1F5E8 |");
211        backspace(state, 0);
212        state.assertEquals("U+FE0E U+200D |");
213        backspace(state, 0);
214        state.assertEquals("U+FE0E |");
215        backspace(state, 0);
216        state.assertEquals("|");
217
218        // Multiple ZERO WIDTH JOINER
219        state.setByString("U+1F441 U+200D U+200D U+1F5E8 |");
220        backspace(state, 0);
221        state.assertEquals("U+1F441 U+200D U+200D |");
222        backspace(state, 0);
223        state.assertEquals("U+1F441 U+200D |");
224        backspace(state, 0);
225        state.assertEquals("U+1F441 |");
226        backspace(state, 0);
227        state.assertEquals("|");
228
229        // Isolated ZERO WIDTH JOINER
230        state.setByString("U+200D |");
231        backspace(state, 0);
232        state.assertEquals("|");
233
234        // Isolated multiple ZERO WIDTH JOINER
235        state.setByString("U+200D U+200D |");
236        backspace(state, 0);
237        state.assertEquals("U+200D |");
238        backspace(state, 0);
239        state.assertEquals("|");
240    }
241
242    @Test
243    public void testFlags() {
244        EditorState state = new EditorState();
245
246        // Isolated regional indicator symbol
247        state.setByString("U+1F1FA |");
248        backspace(state, 0);
249        state.assertEquals("|");
250
251        // Odd numbered regional indicator symbols
252        state.setByString("U+1F1FA U+1F1F8 U+1F1FA |");
253        backspace(state, 0);
254        state.assertEquals("U+1F1FA U+1F1F8 |");
255        backspace(state, 0);
256        state.assertEquals("|");
257
258        // Incomplete sequence. (no tag_term: U+E007E)
259        state.setByString("'a' U+1F3F4 U+E0067 'b' |");
260        backspace(state, 0);
261        state.assertEquals("'a' U+1F3F4 U+E0067 |");
262        backspace(state, 0);
263        state.assertEquals("'a' U+1F3F4 |");
264        backspace(state, 0);
265        state.assertEquals("'a' |");
266
267        // No tag_base
268        state.setByString("'a' U+E0067 U+E007F 'b' |");
269        backspace(state, 0);
270        state.assertEquals("'a' U+E0067 U+E007F |");
271        backspace(state, 0);
272        state.assertEquals("'a' U+E0067 |");
273        backspace(state, 0);
274        state.assertEquals("'a' |");
275
276        // Isolated tag chars
277        state.setByString("'a' U+E0067 U+E0067 'b' |");
278        backspace(state, 0);
279        state.assertEquals("'a' U+E0067 U+E0067 |");
280        backspace(state, 0);
281        state.assertEquals("'a' U+E0067 |");
282        backspace(state, 0);
283        state.assertEquals("'a' |");
284
285        // Isolated tab term.
286        state.setByString("'a' U+E007F U+E007F 'b' |");
287        backspace(state, 0);
288        state.assertEquals("'a' U+E007F U+E007F |");
289        backspace(state, 0);
290        state.assertEquals("'a' U+E007F |");
291        backspace(state, 0);
292        state.assertEquals("'a' |");
293
294        // Immediate tag_term after tag_base
295        state.setByString("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b' |");
296        backspace(state, 0);
297        state.assertEquals("'a' U+1F3F4 U+E007F U+1F3F4 U+E007F |");
298        backspace(state, 0);
299        state.assertEquals("'a' U+1F3F4 U+E007F |");
300        backspace(state, 0);
301        state.assertEquals("'a' |");
302    }
303
304    @Test
305    public void testEmojiModifier() {
306        EditorState state = new EditorState();
307
308        // U+1F3FB is EMOJI MODIFIER FITZPATRICK TYPE-1-2.
309        state.setByString("U+1F466 U+1F3FB |");
310        backspace(state, 0);
311        state.assertEquals("|");
312
313        // Isolated emoji modifier
314        state.setByString("U+1F3FB |");
315        backspace(state, 0);
316        state.assertEquals("|");
317
318        // Isolated multiple emoji modifier
319        state.setByString("U+1F3FB U+1F3FB |");
320        backspace(state, 0);
321        state.assertEquals("U+1F3FB |");
322        backspace(state, 0);
323        state.assertEquals("|");
324
325        // Multiple emoji modifiers
326        state.setByString("U+1F466 U+1F3FB U+1F3FB |");
327        backspace(state, 0);
328        state.assertEquals("U+1F466 U+1F3FB |");
329        backspace(state, 0);
330        state.assertEquals("|");
331    }
332
333    @Test
334    public void testMixedEdgeCases() {
335        EditorState state = new EditorState();
336
337        // COMBINING ENCLOSING KEYCAP + variation selector
338        state.setByString("'1' U+20E3 U+FE0F |");
339        backspace(state, 0);
340        state.assertEquals("'1' |");
341        backspace(state, 0);
342        state.assertEquals("|");
343
344        // Variation selector + COMBINING ENCLOSING KEYCAP
345        state.setByString("U+2665 U+FE0F U+20E3 |");
346        backspace(state, 0);
347        state.assertEquals("U+2665 U+FE0F |");
348        backspace(state, 0);
349        state.assertEquals("|");
350
351        // COMBINING ENCLOSING KEYCAP + ending with ZERO WIDTH JOINER
352        state.setByString("'1' U+20E3 U+200D |");
353        backspace(state, 0);
354        state.assertEquals("'1' U+20E3 |");
355        backspace(state, 0);
356        state.assertEquals("|");
357
358        // COMBINING ENCLOSING KEYCAP + ZERO WIDTH JOINER
359        state.setByString("'1' U+20E3 U+200D U+1F5E8 |");
360        backspace(state, 0);
361        state.assertEquals("'1' U+20E3 U+200D |");
362        backspace(state, 0);
363        state.assertEquals("'1' U+20E3 |");
364        backspace(state, 0);
365        state.assertEquals("|");
366
367        // Start with ZERO WIDTH JOINER + COMBINING ENCLOSING KEYCAP
368        state.setByString("U+200D U+20E3 |");
369        backspace(state, 0);
370        state.assertEquals("U+200D |");
371        backspace(state, 0);
372        state.assertEquals("|");
373
374        // ZERO WIDTH JOINER + COMBINING ENCLOSING KEYCAP
375        state.setByString("U+1F441 U+200D U+20E3 |");
376        backspace(state, 0);
377        state.assertEquals("U+1F441 U+200D |");
378        backspace(state, 0);
379        state.assertEquals("U+1F441 |");
380        backspace(state, 0);
381        state.assertEquals("|");
382
383        // COMBINING ENCLOSING KEYCAP + regional indicator symbol
384        state.setByString("'1' U+20E3 U+1F1FA |");
385        backspace(state, 0);
386        state.assertEquals("'1' U+20E3 |");
387        backspace(state, 0);
388        state.assertEquals("|");
389
390        // Regional indicator symbol + COMBINING ENCLOSING KEYCAP
391        state.setByString("U+1F1FA U+20E3 |");
392        backspace(state, 0);
393        state.assertEquals("U+1F1FA |");
394        backspace(state, 0);
395        state.assertEquals("|");
396
397        // COMBINING ENCLOSING KEYCAP + emoji modifier
398        state.setByString("'1' U+20E3 U+1F3FB |");
399        backspace(state, 0);
400        state.assertEquals("'1' U+20E3 |");
401        backspace(state, 0);
402        state.assertEquals("|");
403
404        // Emoji modifier + COMBINING ENCLOSING KEYCAP
405        state.setByString("U+1F466 U+1F3FB U+20E3 |");
406        backspace(state, 0);
407        state.assertEquals("U+1f466 U+1F3FB |");
408        backspace(state, 0);
409        state.assertEquals("|");
410
411        // Variation selector + end with ZERO WIDTH JOINER
412        state.setByString("U+2665 U+FE0F U+200D |");
413        backspace(state, 0);
414        state.assertEquals("U+2665 U+FE0F |");
415        backspace(state, 0);
416        state.assertEquals("|");
417
418        // Variation selector + ZERO WIDTH JOINER
419        state.setByString("U+1F469 U+200D U+2764 U+FE0F U+200D U+1F469 |");
420        backspace(state, 0);
421        state.assertEquals("|");
422
423        // Start with ZERO WIDTH JOINER + variation selector
424        state.setByString("U+200D U+FE0F |");
425        backspace(state, 0);
426        state.assertEquals("|");
427
428        // ZERO WIDTH JOINER + variation selector
429        state.setByString("U+1F469 U+200D U+FE0F |");
430        backspace(state, 0);
431        state.assertEquals("U+1F469 |");
432        backspace(state, 0);
433        state.assertEquals("|");
434
435        // Variation selector + regional indicator symbol
436        state.setByString("U+2665 U+FE0F U+1F1FA |");
437        backspace(state, 0);
438        state.assertEquals("U+2665 U+FE0F |");
439        backspace(state, 0);
440        state.assertEquals("|");
441
442        // Regional indicator symbol + variation selector
443        state.setByString("U+1F1FA U+FE0F |");
444        backspace(state, 0);
445        state.assertEquals("|");
446
447        // Variation selector + emoji modifier
448        state.setByString("U+2665 U+FE0F U+1F3FB |");
449        backspace(state, 0);
450        state.assertEquals("U+2665 U+FE0F |");
451        backspace(state, 0);
452        state.assertEquals("|");
453
454        // Emoji modifier + variation selector
455        state.setByString("U+1F466 U+1F3FB U+FE0F |");
456        backspace(state, 0);
457        state.assertEquals("U+1F466 |");
458        backspace(state, 0);
459        state.assertEquals("|");
460
461        // Start withj ZERO WIDTH JOINER + regional indicator symbol
462        state.setByString("U+200D U+1F1FA |");
463        backspace(state, 0);
464        state.assertEquals("U+200D |");
465        backspace(state, 0);
466        state.assertEquals("|");
467
468        // ZERO WIDTH JOINER + Regional indicator symbol
469        state.setByString("U+1F469 U+200D U+1F1FA |");
470        backspace(state, 0);
471        state.assertEquals("U+1F469 U+200D |");
472        backspace(state, 0);
473        state.assertEquals("U+1F469 |");
474        backspace(state, 0);
475        state.assertEquals("|");
476
477        // Regional indicator symbol + end with ZERO WIDTH JOINER
478        state.setByString("U+1F1FA U+200D |");
479        backspace(state, 0);
480        state.assertEquals("U+1F1FA |");
481        backspace(state, 0);
482        state.assertEquals("|");
483
484        // Regional indicator symbol + ZERO WIDTH JOINER
485        state.setByString("U+1F1FA U+200D U+1F469 |");
486        backspace(state, 0);
487        state.assertEquals("|");
488
489        // Start with ZERO WIDTH JOINER + emoji modifier
490        state.setByString("U+200D U+1F3FB |");
491        backspace(state, 0);
492        state.assertEquals("U+200D |");
493        backspace(state, 0);
494        state.assertEquals("|");
495
496        // ZERO WIDTH JOINER + emoji modifier
497        state.setByString("U+1F469 U+200D U+1F3FB |");
498        backspace(state, 0);
499        state.assertEquals("U+1F469 U+200D |");
500        backspace(state, 0);
501        state.assertEquals("U+1F469 |");
502        backspace(state, 0);
503        state.assertEquals("|");
504
505        // Emoji modifier + end with ZERO WIDTH JOINER
506        state.setByString("U+1F466 U+1F3FB U+200D |");
507        backspace(state, 0);
508        state.assertEquals("U+1F466 U+1F3FB |");
509        backspace(state, 0);
510        state.assertEquals("|");
511
512        // Regional indicator symbol + Emoji modifier
513        state.setByString("U+1F1FA U+1F3FB |");
514        backspace(state, 0);
515        state.assertEquals("U+1F1FA |");
516        backspace(state, 0);
517        state.assertEquals("|");
518
519        // Emoji modifier + regional indicator symbol
520        state.setByString("U+1F466 U+1F3FB U+1F1FA |");
521        backspace(state, 0);
522        state.assertEquals("U+1F466 U+1F3FB |");
523        backspace(state, 0);
524        state.assertEquals("|");
525
526        // RIS + LF
527        state.setByString("U+1F1E6 U+000A |");
528        backspace(state, 0);
529        state.assertEquals("U+1F1E6 |");
530        backspace(state, 0);
531        state.assertEquals("|");
532    }
533}
534