EmojiCompatTest.java revision 0d1e48d934880b40237ce980d154c3f3ff1c32f0
1/*
2 * Copyright (C) 2017 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 */
16package android.support.text.emoji;
17
18import static android.support.text.emoji.TestConfigBuilder.TestConfig;
19import static android.support.text.emoji.TestConfigBuilder.WaitingDataLoader;
20import static android.support.text.emoji.TestConfigBuilder.config;
21import static android.support.text.emoji.util.Emoji.CHAR_DEFAULT_EMOJI_STYLE;
22import static android.support.text.emoji.util.Emoji.CHAR_DEFAULT_TEXT_STYLE;
23import static android.support.text.emoji.util.Emoji.CHAR_DIGIT;
24import static android.support.text.emoji.util.Emoji.CHAR_FITZPATRICK;
25import static android.support.text.emoji.util.Emoji.CHAR_VS_EMOJI;
26import static android.support.text.emoji.util.Emoji.CHAR_VS_TEXT;
27import static android.support.text.emoji.util.Emoji.DEFAULT_TEXT_STYLE;
28import static android.support.text.emoji.util.Emoji.EMOJI_ASTERISK_KEYCAP;
29import static android.support.text.emoji.util.Emoji.EMOJI_DIGIT_ES;
30import static android.support.text.emoji.util.Emoji.EMOJI_DIGIT_ES_KEYCAP;
31import static android.support.text.emoji.util.Emoji.EMOJI_DIGIT_KEYCAP;
32import static android.support.text.emoji.util.Emoji.EMOJI_FLAG;
33import static android.support.text.emoji.util.Emoji.EMOJI_GENDER;
34import static android.support.text.emoji.util.Emoji.EMOJI_GENDER_WITHOUT_VS;
35import static android.support.text.emoji.util.Emoji.EMOJI_REGIONAL_SYMBOL;
36import static android.support.text.emoji.util.Emoji.EMOJI_SINGLE_CODEPOINT;
37import static android.support.text.emoji.util.Emoji.EMOJI_SKIN_MODIFIER;
38import static android.support.text.emoji.util.Emoji.EMOJI_SKIN_MODIFIER_TYPE_ONE;
39import static android.support.text.emoji.util.Emoji.EMOJI_SKIN_MODIFIER_WITH_VS;
40import static android.support.text.emoji.util.Emoji.EMOJI_UNKNOWN_FLAG;
41import static android.support.text.emoji.util.Emoji.EMOJI_WITH_ZWJ;
42import static android.support.text.emoji.util.EmojiMatcher.hasEmoji;
43import static android.support.text.emoji.util.EmojiMatcher.hasEmojiAt;
44import static android.support.text.emoji.util.EmojiMatcher.hasEmojiCount;
45
46import static junit.framework.TestCase.assertFalse;
47
48import static org.hamcrest.Matchers.instanceOf;
49import static org.hamcrest.Matchers.not;
50import static org.junit.Assert.assertEquals;
51import static org.junit.Assert.assertNotNull;
52import static org.junit.Assert.assertNull;
53import static org.junit.Assert.assertSame;
54import static org.junit.Assert.assertThat;
55import static org.junit.Assert.assertTrue;
56
57import android.os.Bundle;
58import android.support.test.InstrumentationRegistry;
59import android.support.test.filters.SmallTest;
60import android.support.test.runner.AndroidJUnit4;
61import android.support.text.emoji.EmojiCompat.Config;
62import android.support.text.emoji.util.Emoji.EmojiMapping;
63import android.support.text.emoji.util.TestString;
64import android.text.Editable;
65import android.text.Spannable;
66import android.text.SpannableString;
67import android.text.SpannableStringBuilder;
68import android.text.SpannedString;
69import android.view.inputmethod.EditorInfo;
70
71import org.junit.Before;
72import org.junit.Test;
73import org.junit.runner.RunWith;
74
75import java.util.HashSet;
76import java.util.Set;
77
78@SmallTest
79@RunWith(AndroidJUnit4.class)
80public class EmojiCompatTest {
81
82    @Before
83    public void setup() {
84        EmojiCompat.reset(config());
85    }
86
87    @Test(expected = IllegalStateException.class)
88    public void testGet_throwsException() throws Exception {
89        EmojiCompat.reset((EmojiCompat) null);
90        EmojiCompat.get();
91    }
92
93    @Test
94    public void testProcess_doesNothing_withNullCharSequence() throws Exception {
95        assertNull(EmojiCompat.get().process(null));
96    }
97
98    @Test
99    public void testProcess_returnsEmptySpanned_withEmptyString() throws Exception {
100        final CharSequence charSequence = EmojiCompat.get().process("");
101        assertNotNull(charSequence);
102        assertEquals(0, charSequence.length());
103        assertThat(charSequence, not(hasEmoji()));
104    }
105
106    @Test(expected = IllegalArgumentException.class)
107    public void testProcess_withNegativeStartValue() throws Exception {
108        EmojiCompat.get().process("a", -1, 1);
109    }
110
111    @Test(expected = IllegalArgumentException.class)
112    public void testProcess_withNegativeEndValue() throws Exception {
113        EmojiCompat.get().process("a", 1, -1);
114    }
115
116    @Test(expected = IllegalArgumentException.class)
117    public void testProcess_withStartSmallerThanEndValue() throws Exception {
118        EmojiCompat.get().process("aa", 1, 0);
119    }
120
121    @Test(expected = IllegalArgumentException.class)
122    public void testProcess_withStartGreaterThanLength() throws Exception {
123        EmojiCompat.get().process("a", 2, 2);
124    }
125
126    @Test(expected = IllegalArgumentException.class)
127    public void testProcess_withEndGreaterThanLength() throws Exception {
128        EmojiCompat.get().process("a", 0, 2);
129    }
130
131    @Test
132    public void testProcessWithStartEnd_withNoOpValues() throws Exception {
133        final Spannable spannable = new SpannableString(new TestString('a')
134                .withPrefix().withSuffix().toString());
135        // early return check
136        assertSame(spannable, EmojiCompat.get().process(spannable, 0, 0));
137        assertSame(spannable, EmojiCompat.get().process(spannable, 1, 1));
138        assertSame(spannable, EmojiCompat.get().process(spannable, spannable.length(),
139                spannable.length()));
140    }
141
142
143    @Test
144    public void testProcess_doesNotAddEmojiSpan() throws Exception {
145        final String string = "abc";
146        final CharSequence charSequence = EmojiCompat.get().process(string);
147        assertNotNull(charSequence);
148        assertEquals(string, charSequence.toString());
149        assertThat(charSequence, not(hasEmoji()));
150    }
151
152    @Test
153    public void testProcess_addsSingleCodePointEmoji() throws Exception {
154        assertCodePointMatch(EMOJI_SINGLE_CODEPOINT);
155    }
156
157    @Test
158    public void testProcess_addsFlagEmoji() throws Exception {
159        assertCodePointMatch(EMOJI_FLAG);
160    }
161
162    @Test
163    public void testProcess_addsUnknownFlagEmoji() throws Exception {
164        assertCodePointMatch(EMOJI_UNKNOWN_FLAG);
165    }
166
167    @Test
168    public void testProcess_addsRegionalIndicatorSymbol() throws Exception {
169        assertCodePointMatch(EMOJI_REGIONAL_SYMBOL);
170    }
171
172    @Test
173    public void testProcess_addsKeyCapEmoji() throws Exception {
174        assertCodePointMatch(EMOJI_DIGIT_KEYCAP);
175    }
176
177    @Test
178    public void testProcess_doesNotAddEmojiForNumbers() throws Exception {
179        assertCodePointDoesNotMatch(new int[] {CHAR_DIGIT});
180    }
181
182    @Test
183    public void testProcess_doesNotAddEmojiForNumbers_1() throws Exception {
184        final TestString string = new TestString(EMOJI_SINGLE_CODEPOINT).append('1', 'f');
185        CharSequence charSequence = EmojiCompat.get().process(string.toString());
186        assertThat(charSequence, hasEmojiCount(1));
187    }
188
189    @Test
190    public void testProcess_addsVariantSelectorEmoji() throws Exception {
191        assertCodePointMatch(EMOJI_DIGIT_ES);
192    }
193
194    @Test
195    public void testProcess_doesNotAddVariantSelectorTextStyle() throws Exception {
196        assertCodePointDoesNotMatch(new int[]{CHAR_DIGIT, CHAR_VS_TEXT});
197    }
198
199    @Test
200    public void testProcess_addsVariantSelectorAndKeyCapEmoji() throws Exception {
201        assertCodePointMatch(EMOJI_DIGIT_ES_KEYCAP);
202    }
203
204    @Test
205    public void testProcess_doesNotAddEmoji_forVariantBaseWithoutSelector() throws Exception {
206        assertCodePointDoesNotMatch(new int[]{CHAR_DIGIT});
207    }
208
209    @Test
210    public void testProcess_addsAsteriskKeyCapEmoji() throws Exception {
211        assertCodePointMatch(EMOJI_ASTERISK_KEYCAP);
212    }
213
214    @Test
215    public void testProcess_addsSkinModifierEmoji() throws Exception {
216        assertCodePointMatch(EMOJI_SKIN_MODIFIER);
217        assertCodePointMatch(EMOJI_SKIN_MODIFIER_TYPE_ONE);
218    }
219
220    @Test
221    public void testProcess_addsSkinModifierEmoji_withVariantSelector() throws Exception {
222        assertCodePointMatch(EMOJI_SKIN_MODIFIER_WITH_VS);
223    }
224
225    @Test
226    public void testProcess_addsSkinModifierEmoji_270c_withVariantSelector() throws Exception {
227        // 0x270c is a Standardized Variant Base, Emoji Modifier Base and also Emoji
228        // therefore it is different than i.e. 0x1f3c3. The code actually failed for this test
229        // at first.
230        assertCodePointMatch(0xF0734, new int[]{0x270C, CHAR_VS_EMOJI, CHAR_FITZPATRICK});
231    }
232
233    @Test
234    public void testProcess_defaultStyleDoesNotAddSpan() throws Exception {
235        assertCodePointDoesNotMatch(new int[]{CHAR_DEFAULT_TEXT_STYLE});
236        assertCodePointMatch(DEFAULT_TEXT_STYLE);
237    }
238
239    @Test
240    public void testProcess_defaultEmojiStyle_withTextStyleVs() throws Exception {
241        assertCodePointMatch(EMOJI_SINGLE_CODEPOINT.id(),
242                new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_EMOJI});
243        assertCodePointDoesNotMatch(new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_TEXT});
244    }
245
246    @Test
247    public void testProcess_genderEmoji() throws Exception {
248        assertCodePointMatch(EMOJI_GENDER);
249        assertCodePointMatch(EMOJI_GENDER_WITHOUT_VS);
250    }
251
252    @Test
253    public void testProcess_standardizedVariantEmojiExceptions() throws Exception {
254        final int[][] exceptions = new int[][]{
255                {0x2600, 0xF034D},
256                {0x2601, 0xF0167},
257                {0x260E, 0xF034E},
258                {0x261D, 0xF0227},
259                {0x263A, 0xF02A6},
260                {0x2660, 0xF0350},
261                {0x2663, 0xF033F},
262                {0x2665, 0xF033B},
263                {0x2666, 0xF033E},
264                {0x270C, 0xF0079},
265                {0x2744, 0xF0342},
266                {0x2764, 0xF0362}
267        };
268
269        for (int i = 0; i < exceptions.length; i++) {
270            final int[] codepoints = new int[]{exceptions[i][0]};
271            assertCodePointMatch(exceptions[i][1], codepoints);
272        }
273    }
274
275    @Test
276    public void testProcess_addsZwjEmoji() throws Exception {
277        assertCodePointMatch(EMOJI_WITH_ZWJ);
278    }
279
280    @Test
281    public void testProcess_doesNotAddEmojiForNumbersAfterZwjEmo() throws Exception {
282        TestString string = new TestString(EMOJI_WITH_ZWJ).append(0x20, 0x2B, 0x31)
283                .withSuffix().withPrefix();
284        CharSequence charSequence = EmojiCompat.get().process(string.toString());
285        assertThat(charSequence, hasEmojiAt(EMOJI_WITH_ZWJ, string.emojiStartIndex(),
286                string.emojiEndIndex() - 3));
287        assertThat(charSequence, hasEmojiCount(1));
288
289        string = new TestString(EMOJI_WITH_ZWJ).withSuffix().withPrefix();
290        charSequence = EmojiCompat.get().process(string.toString());
291        assertThat(charSequence, hasEmojiCount(1));
292    }
293
294    @Test
295    public void testProcess_withAppend() throws Exception {
296        final Editable editable = new SpannableStringBuilder(new TestString('a').withPrefix()
297                .withSuffix().toString());
298        final int start = 1;
299        final int end = start + EMOJI_SINGLE_CODEPOINT.charCount();
300        editable.insert(start, new TestString(EMOJI_SINGLE_CODEPOINT).toString());
301        EmojiCompat.get().process(editable, start, end);
302        assertThat(editable, hasEmojiCount(1));
303        assertThat(editable, hasEmojiAt(EMOJI_SINGLE_CODEPOINT, start, end));
304    }
305
306    @Test
307    public void testProcess_doesNotCreateSpannable_ifNoEmoji() throws Exception {
308        CharSequence processed = EmojiCompat.get().process("abc");
309        assertNotNull(processed);
310        assertThat(processed, instanceOf(String.class));
311
312        processed = EmojiCompat.get().process(new SpannedString("abc"));
313        assertNotNull(processed);
314        assertThat(processed, instanceOf(SpannedString.class));
315    }
316
317    @Test
318    public void testProcess_reprocess() throws Exception {
319        final String string = new TestString(EMOJI_SINGLE_CODEPOINT)
320                .append(EMOJI_SINGLE_CODEPOINT)
321                .append(EMOJI_SINGLE_CODEPOINT)
322                .withPrefix().withSuffix().toString();
323
324        Spannable processed = (Spannable) EmojiCompat.get().process(string);
325        assertThat(processed, hasEmojiCount(3));
326
327        final EmojiSpan[] spans = processed.getSpans(0, processed.length(), EmojiSpan.class);
328        final Set<EmojiSpan> spanSet = new HashSet<>();
329        for (int i = 0; i < spans.length; i++) {
330            spanSet.add(spans[i]);
331        }
332
333        processed = (Spannable) EmojiCompat.get().process(processed);
334        assertThat(processed, hasEmojiCount(3));
335        // new spans should be new instances
336        final EmojiSpan[] newSpans = processed.getSpans(0, processed.length(), EmojiSpan.class);
337        for (int i = 0; i < newSpans.length; i++) {
338            assertFalse(spanSet.contains(newSpans[i]));
339        }
340    }
341
342    @Test
343    public void testHasGlyph_returnsMetadata() throws Exception {
344        final String sequence = new TestString(EMOJI_FLAG).toString();
345        assertNotNull(EmojiCompat.get().hasEmojiGlyph(sequence));
346    }
347
348    @Test
349    public void testHasGlyph_returnsNullForNonExistentEmoji() throws Exception {
350        final String sequence = new TestString(EMOJI_FLAG).append(0x1111).toString();
351        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence));
352    }
353
354    @Test
355    public void testHashGlyph_withDefaultEmojiStyles() throws Exception {
356        String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
357        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
358
359        sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_EMOJI}).toString();
360        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
361
362        sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_TEXT}).toString();
363        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence));
364    }
365
366    @Test
367    public void testHashGlyph_withMetadataVersion() throws Exception {
368        final String sequence = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
369        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence, 0));
370        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence, Integer.MAX_VALUE));
371    }
372
373    @Test
374    public void testGetLoadState_returnsSuccessIfLoadSuccess() throws InterruptedException {
375        final WaitingDataLoader metadataLoader = new WaitingDataLoader(true /*success*/);
376        final Config config = new TestConfig(metadataLoader);
377        EmojiCompat.reset(config);
378
379        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_LOADING);
380
381        metadataLoader.getLoaderLatch().countDown();
382        metadataLoader.getTestLatch().await();
383        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
384
385        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_SUCCESS);
386    }
387
388    @Test
389    public void testGetLoadState_returnsFailIfLoadFail() throws InterruptedException {
390        final WaitingDataLoader metadataLoader = new WaitingDataLoader(false/*fail*/);
391        final Config config = new TestConfig(metadataLoader);
392        EmojiCompat.reset(config);
393
394        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_LOADING);
395
396        metadataLoader.getLoaderLatch().countDown();
397        metadataLoader.getTestLatch().await();
398        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
399
400        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_FAILURE);
401    }
402
403    @Test
404    public void testUpdateEditorInfoAttrs_doesNotSetKeyIfNotInitialized() {
405        final EditorInfo editorInfo = new EditorInfo();
406        editorInfo.extras = new Bundle();
407
408        final WaitingDataLoader metadataLoader = new WaitingDataLoader();
409        final Config config = new TestConfig(metadataLoader);
410        EmojiCompat.reset(config);
411
412        EmojiCompat.get().updateEditorInfoAttrs(editorInfo);
413
414        final Bundle extras = editorInfo.extras;
415        assertFalse(extras.containsKey(EmojiCompat.EDITOR_INFO_METAVERSION_KEY));
416        assertFalse(extras.containsKey(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
417
418        metadataLoader.getLoaderLatch().countDown();
419    }
420
421    @Test
422    public void testUpdateEditorInfoAttrs_setsKeysIfInitialized() {
423        final EditorInfo editorInfo = new EditorInfo();
424        editorInfo.extras = new Bundle();
425        Config config = new TestConfig().setReplaceAll(false);
426        EmojiCompat.reset(config);
427        EmojiCompat.get().updateEditorInfoAttrs(editorInfo);
428
429        final Bundle extras = editorInfo.extras;
430        assertTrue(extras.containsKey(EmojiCompat.EDITOR_INFO_METAVERSION_KEY));
431        assertTrue(extras.getInt(EmojiCompat.EDITOR_INFO_METAVERSION_KEY) > 0);
432        assertTrue(extras.containsKey(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
433        assertFalse(extras.getBoolean(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
434
435        config = new TestConfig().setReplaceAll(true);
436        EmojiCompat.reset(config);
437        EmojiCompat.get().updateEditorInfoAttrs(editorInfo);
438
439        assertTrue(extras.containsKey(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
440        assertTrue(extras.getBoolean(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
441    }
442
443    private void assertCodePointMatch(EmojiMapping emoji) {
444        assertCodePointMatch(emoji.id(), emoji.codepoints());
445    }
446
447    private void assertCodePointMatch(int id, int[] codepoints) {
448        TestString string = new TestString(codepoints);
449        CharSequence charSequence = EmojiCompat.get().process(string.toString());
450        assertThat(charSequence, hasEmojiAt(id, string.emojiStartIndex(), string.emojiEndIndex()));
451
452        // case where Emoji is in the middle of string
453        string = new TestString(codepoints).withPrefix().withSuffix();
454        charSequence = EmojiCompat.get().process(string.toString());
455        assertThat(charSequence, hasEmojiAt(id, string.emojiStartIndex(), string.emojiEndIndex()));
456
457        // case where Emoji is at the end of string
458        string = new TestString(codepoints).withSuffix();
459        charSequence = EmojiCompat.get().process(string.toString());
460        assertThat(charSequence, hasEmojiAt(id, string.emojiStartIndex(), string.emojiEndIndex()));
461    }
462
463    private void assertCodePointDoesNotMatch(int[] codepoints) {
464        TestString string = new TestString(codepoints);
465        CharSequence charSequence = EmojiCompat.get().process(string.toString());
466        assertThat(charSequence, not(hasEmoji()));
467
468        string = new TestString(codepoints).withSuffix().withPrefix();
469        charSequence = EmojiCompat.get().process(string.toString());
470        assertThat(charSequence, not(hasEmoji()));
471
472        string = new TestString(codepoints).withPrefix();
473        charSequence = EmojiCompat.get().process(string.toString());
474        assertThat(charSequence, not(hasEmoji()));
475    }
476
477    //FAILS: CHAR_DIGIT, CHAR_VS_EMOJI, CHAR_VS_TEXT
478}
479