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;
27
28import org.junit.Before;
29import org.junit.Test;
30import org.junit.runner.RunWith;
31
32/**
33 * Test forward delete key handling of  {@link android.text.method.BaseKeyListener}.
34 *
35 * Only contains edge cases. For normal cases, see {@see android.text.method.cts.ForwardDeleteTest}.
36 * TODO: introduce test cases for surrogate pairs and replacement span.
37 */
38@SmallTest
39@RunWith(AndroidJUnit4.class)
40public class ForwardDeleteTest {
41    private EditText mTextView;
42
43    private static final BaseKeyListener mKeyListener = new BaseKeyListener() {
44        public int getInputType() {
45            return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
46        }
47    };
48
49    @Before
50    public void setup() {
51        mTextView = new EditText(InstrumentationRegistry.getInstrumentation().getContext());
52    }
53
54    // Sync the state to the TextView and call onKeyDown with KEYCODE_FORWARD_DEL key event.
55    // Then update the state to the result of TextView.
56    private void forwardDelete(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_FORWARD_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        // multiple COMBINING ENCLOSING KEYCAP
75        state.setByString("| '1' U+20E3 U+20E3");
76        forwardDelete(state, 0);
77        state.assertEquals("|");
78
79        // Isolated COMBINING ENCLOSING KEYCAP
80        state.setByString("| U+20E3");
81        forwardDelete(state, 0);
82        state.assertEquals("|");
83
84        // Isolated multiple COMBINING ENCLOSING KEYCAP
85        state.setByString("| U+20E3 U+20E3");
86        forwardDelete(state, 0);
87        state.assertEquals("|");
88    }
89
90    @Test
91    public void testVariationSelector() {
92        EditorState state = new EditorState();
93
94        // Isolated variation selectors
95        state.setByString("| U+FE0F");
96        forwardDelete(state, 0);
97        state.assertEquals("|");
98
99        state.setByString("| U+E0100");
100        forwardDelete(state, 0);
101        state.assertEquals("|");
102
103        // Isolated multiple variation selectors
104        state.setByString("| U+FE0F U+FE0F");
105        forwardDelete(state, 0);
106        state.assertEquals("|");
107
108        state.setByString("| U+FE0F U+E0100");
109        forwardDelete(state, 0);
110        state.assertEquals("|");
111
112        state.setByString("| U+E0100 U+FE0F");
113        forwardDelete(state, 0);
114        state.assertEquals("|");
115
116        state.setByString("| U+E0100 U+E0100");
117        forwardDelete(state, 0);
118        state.assertEquals("|");
119
120        // Multiple variation selectors
121        state.setByString("| '#' U+FE0F U+FE0F");
122        forwardDelete(state, 0);
123        state.assertEquals("|");
124
125        state.setByString("| '#' U+FE0F U+E0100");
126        forwardDelete(state, 0);
127        state.assertEquals("|");
128
129        state.setByString("| U+845B U+E0100 U+FE0F");
130        forwardDelete(state, 0);
131        state.assertEquals("|");
132
133        state.setByString("| U+845B U+E0100 U+E0100");
134        forwardDelete(state, 0);
135        state.assertEquals("|");
136    }
137
138    @Test
139    public void testEmojiZeroWidthJoinerSequence() {
140        EditorState state = new EditorState();
141
142        // U+200D is ZERO WIDTH JOINER.
143        state.setByString("| U+1F441 U+200D U+1F5E8");
144        forwardDelete(state, 0);
145        state.assertEquals("|");
146
147        state.setByString("| U+1F468 U+200D U+2764 U+FE0F U+200D U+1F48B U+200D U+1F468");
148        forwardDelete(state, 0);
149        state.assertEquals("|");
150
151        // End with ZERO WIDTH JOINER
152        state.setByString("| U+1F441 U+200D");
153        forwardDelete(state, 0);
154        state.assertEquals("|");
155
156        // Start with ZERO WIDTH JOINER
157        state.setByString("| U+200D U+1F5E8");
158        forwardDelete(state, 0);
159        state.assertEquals("| U+1F5E8");
160        forwardDelete(state, 0);
161        state.assertEquals("|");
162
163        // Multiple ZERO WIDTH JOINER
164        state.setByString("| U+1F441 U+200D U+200D U+1F5E8");
165        forwardDelete(state, 0);
166        state.assertEquals("| U+1F5E8");
167        forwardDelete(state, 0);
168        state.assertEquals("|");
169
170        // Isolated ZERO WIDTH JOINER
171        state.setByString("| U+200D");
172        forwardDelete(state, 0);
173        state.assertEquals("|");
174
175        // Isolated multiple ZERO WIDTH JOINER
176        state.setByString("| U+200D U+200D");
177        forwardDelete(state, 0);
178        state.assertEquals("|");
179    }
180
181    @Test
182    public void testFlags() {
183        EditorState state = new EditorState();
184
185        // Isolated regional indicator symbol
186        state.setByString("| U+1F1FA");
187        forwardDelete(state, 0);
188        state.assertEquals("|");
189
190        // Odd numbered regional indicator symbols
191        state.setByString("| U+1F1FA U+1F1F8 U+1F1FA");
192        forwardDelete(state, 0);
193        state.assertEquals("| U+1F1FA");
194        forwardDelete(state, 0);
195        state.assertEquals("|");
196
197        // Incomplete sequence. (no tag_term:U+E007E)
198        state.setByString("| 'a' U+1F3F4 U+E0067 'b'");
199        forwardDelete(state, 0);
200        state.assertEquals("| U+1F3F4 U+E0067 'b'");
201        forwardDelete(state, 0);
202        state.assertEquals("| 'b'");
203
204        // No tag_base
205        state.setByString("| 'a' U+E0067 U+E007F 'b'");
206        forwardDelete(state, 0);
207        state.assertEquals("| 'b'");
208
209        // Isolated tag chars
210        state.setByString("| 'a' U+E0067 U+E0067 'b'");
211        forwardDelete(state, 0);
212        state.assertEquals("| 'b'");
213
214        // Isolated tag base.
215        state.setByString("| 'a' U+1F3F4 U+1F3F4 'b'");
216        forwardDelete(state, 0);
217        state.assertEquals("| U+1F3F4 U+1F3F4 'b'");
218        forwardDelete(state, 0);
219        state.assertEquals("| U+1F3F4 'b'");
220        forwardDelete(state, 0);
221        state.assertEquals("| 'b'");
222
223        // Isolated tab term.
224        state.setByString("| 'a' U+E007F U+E007F 'b'");
225        forwardDelete(state, 0);
226        state.assertEquals("| 'b'");
227
228        // Immediate tag_term after tag_base
229        state.setByString("| 'a' U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'");
230        forwardDelete(state, 0);
231        state.assertEquals("| U+1F3F4 U+E007F U+1F3F4 U+E007F 'b'");
232        forwardDelete(state, 0);
233        state.assertEquals("| U+1F3F4 U+E007F 'b'");
234        forwardDelete(state, 0);
235        state.assertEquals("| 'b'");
236    }
237
238    @Test
239    public void testEmojiModifier() {
240        EditorState state = new EditorState();
241
242        // U+1F3FB is EMOJI MODIFIER FITZPATRICK TYPE-1-2.
243        state.setByString("| U+1F466 U+1F3FB");
244        forwardDelete(state, 0);
245        state.assertEquals("|");
246
247        // Isolated emoji modifier
248        state.setByString("| U+1F3FB");
249        forwardDelete(state, 0);
250        state.assertEquals("|");
251
252        // Isolated multiple emoji modifier
253        state.setByString("| U+1F3FB U+1F3FB");
254        forwardDelete(state, 0);
255        state.assertEquals("| U+1F3FB");
256        forwardDelete(state, 0);
257        state.assertEquals("|");
258
259        // Multiple emoji modifiers
260        state.setByString("| U+1F466 U+1F3FB U+1F3FB");
261        forwardDelete(state, 0);
262        state.assertEquals("| U+1F3FB");
263        forwardDelete(state, 0);
264        state.assertEquals("|");
265    }
266
267    @Test
268    public void testMixedEdgeCases() {
269        EditorState state = new EditorState();
270
271        // COMBINING ENCLOSING KEYCAP + variation selector
272        state.setByString("| '1' U+20E3 U+FE0F");
273        forwardDelete(state, 0);
274        state.assertEquals("|");
275
276        // Variation selector + COMBINING ENCLOSING KEYCAP
277        state.setByString("| U+2665 U+FE0F U+20E3");
278        forwardDelete(state, 0);
279        state.assertEquals("|");
280
281        // COMBINING ENCLOSING KEYCAP + ending with ZERO WIDTH JOINER
282        state.setByString("| '1' U+20E3 U+200D");
283        forwardDelete(state, 0);
284        state.assertEquals("|");
285
286        // COMBINING ENCLOSING KEYCAP + ZERO WIDTH JOINER
287        state.setByString("| '1' U+20E3 U+200D U+1F5E8");
288        forwardDelete(state, 0);
289        state.assertEquals("| U+1F5E8 ");
290
291        // Start with ZERO WIDTH JOINER + COMBINING ENCLOSING KEYCAP
292        state.setByString("| U+200D U+20E3");
293        forwardDelete(state, 0);
294        state.assertEquals("|");
295
296        // ZERO WIDTH JOINER + COMBINING ENCLOSING KEYCAP
297        state.setByString("| U+1F441 U+200D U+20E3");
298        forwardDelete(state, 0);
299        state.assertEquals("|");
300
301        // COMBINING ENCLOSING KEYCAP + regional indicator symbol
302        state.setByString("| '1' U+20E3 U+1F1FA");
303        forwardDelete(state, 0);
304        state.assertEquals("| U+1F1FA");
305        forwardDelete(state, 0);
306        state.assertEquals("|");
307
308        // Regional indicator symbol + COMBINING ENCLOSING KEYCAP
309        state.setByString("| U+1F1FA U+20E3");
310        forwardDelete(state, 0);
311        state.assertEquals("|");
312
313        // COMBINING ENCLOSING KEYCAP + emoji modifier
314        state.setByString("| '1' U+20E3 U+1F3FB");
315        forwardDelete(state, 0);
316        state.assertEquals("| U+1F3FB");
317
318        // Emoji modifier + COMBINING ENCLOSING KEYCAP
319        state.setByString("| U+1F466 U+1F3FB U+20E3");
320        forwardDelete(state, 0);
321        state.assertEquals("|");
322
323        // Variation selector + end with ZERO WIDTH JOINER
324        state.setByString("| U+2665 U+FE0F U+200D");
325        forwardDelete(state, 0);
326        state.assertEquals("|");
327
328        // Variation selector + ZERO WIDTH JOINER
329        state.setByString("| U+1F469 U+200D U+2764 U+FE0F U+200D U+1F469");
330        forwardDelete(state, 0);
331        state.assertEquals("|");
332
333        // Start with ZERO WIDTH JOINER + variation selector
334        state.setByString("| U+200D U+FE0F");
335        forwardDelete(state, 0);
336        state.assertEquals("|");
337
338        // ZERO WIDTH JOINER + variation selector
339        state.setByString("| U+1F469 U+200D U+FE0F");
340        forwardDelete(state, 0);
341        state.assertEquals("|");
342
343        // Variation selector + regional indicator symbol
344        state.setByString("| U+2665 U+FE0F U+1F1FA");
345        forwardDelete(state, 0);
346        state.assertEquals("| U+1F1FA");
347        forwardDelete(state, 0);
348        state.assertEquals("|");
349
350        // Regional indicator symbol + variation selector
351        state.setByString("| U+1F1FA U+FE0F");
352        forwardDelete(state, 0);
353        state.assertEquals("|");
354
355        // Variation selector + emoji modifier
356        state.setByString("| U+2665 U+FE0F U+1F3FB");
357        forwardDelete(state, 0);
358        state.assertEquals("| U+1F3FB");
359
360        // Emoji modifier + variation selector
361        state.setByString("| U+1F466 U+1F3FB U+FE0F");
362        forwardDelete(state, 0);
363        state.assertEquals("|");
364
365        // Start with ZERO WIDTH JOINER + regional indicator symbol
366        state.setByString("| U+200D U+1F1FA");
367        forwardDelete(state, 0);
368        state.assertEquals("| U+1F1FA");
369        forwardDelete(state, 0);
370        state.assertEquals("|");
371
372        // ZERO WIDTH JOINER + regional indicator symbol
373        state.setByString("| U+1F469 U+200D U+1F1FA");
374        forwardDelete(state, 0);
375        state.assertEquals("| U+1F1FA");
376        forwardDelete(state, 0);
377        state.assertEquals("|");
378
379        // Regional indicator symbol + end with ZERO WIDTH JOINER
380        state.setByString("| U+1F1FA U+200D");
381        forwardDelete(state, 0);
382        state.assertEquals("|");
383
384        // Regional indicator symbol + ZERO WIDTH JOINER
385        state.setByString("| U+1F1FA U+200D U+1F469");
386        forwardDelete(state, 0);
387        state.assertEquals("| U+1F469");
388        forwardDelete(state, 0);
389        state.assertEquals("|");
390
391        // Start with ZERO WIDTH JOINER + emoji modifier
392        state.setByString("| U+200D U+1F3FB");
393        forwardDelete(state, 0);
394        state.assertEquals("| U+1F3FB");
395
396        // ZERO WIDTH JOINER + emoji modifier
397        state.setByString("| U+1F469 U+200D U+1F3FB");
398        forwardDelete(state, 0);
399        state.assertEquals("|");
400
401        // Emoji modifier + end with ZERO WIDTH JOINER
402        state.setByString("| U+1F466 U+1F3FB U+200D");
403        forwardDelete(state, 0);
404        state.assertEquals("|");
405
406        // Emoji modifier + ZERO WIDTH JOINER
407        state.setByString("| U+1F466 U+1F3FB U+200D U+1F469");
408        forwardDelete(state, 0);
409        state.assertEquals("| U+1F469");
410        forwardDelete(state, 0);
411        state.assertEquals("|");
412
413        // Regional indicator symbol + emoji modifier
414        state.setByString("| U+1F1FA U+1F3FB");
415        forwardDelete(state, 0);
416        state.assertEquals("| U+1F3FB");
417        forwardDelete(state, 0);
418        state.assertEquals("|");
419
420        // Emoji modifier + regional indicator symbol
421        state.setByString("| U+1F466 U+1F3FB U+1F1FA");
422        forwardDelete(state, 0);
423        state.assertEquals("| U+1F1FA");
424        forwardDelete(state, 0);
425        state.assertEquals("|");
426    }
427}
428