EmojiCompatTest.java revision 34f638e630f75357a5f706f387ee9099c97af26b
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;
45import static android.support.text.emoji.util.KeyboardUtil.del;
46
47import static junit.framework.TestCase.assertFalse;
48
49import static org.hamcrest.Matchers.instanceOf;
50import static org.hamcrest.Matchers.not;
51import static org.junit.Assert.assertEquals;
52import static org.junit.Assert.assertNotNull;
53import static org.junit.Assert.assertNull;
54import static org.junit.Assert.assertSame;
55import static org.junit.Assert.assertThat;
56import static org.junit.Assert.assertTrue;
57import static org.mockito.Matchers.any;
58import static org.mockito.Matchers.anyInt;
59import static org.mockito.Mockito.mock;
60import static org.mockito.Mockito.reset;
61import static org.mockito.Mockito.spy;
62import static org.mockito.Mockito.times;
63import static org.mockito.Mockito.verify;
64import static org.mockito.Mockito.verifyNoMoreInteractions;
65import static org.mockito.Mockito.when;
66
67import android.annotation.SuppressLint;
68import android.os.Bundle;
69import android.support.test.InstrumentationRegistry;
70import android.support.test.filters.SdkSuppress;
71import android.support.test.filters.SmallTest;
72import android.support.test.runner.AndroidJUnit4;
73import android.support.text.emoji.EmojiCompat.Config;
74import android.support.text.emoji.util.Emoji.EmojiMapping;
75import android.support.text.emoji.util.TestString;
76import android.text.Editable;
77import android.text.Selection;
78import android.text.Spannable;
79import android.text.SpannableString;
80import android.text.SpannableStringBuilder;
81import android.text.SpannedString;
82import android.view.KeyEvent;
83import android.view.inputmethod.EditorInfo;
84import android.view.inputmethod.InputConnection;
85
86import org.junit.Before;
87import org.junit.Test;
88import org.junit.runner.RunWith;
89
90import java.util.Collections;
91import java.util.HashSet;
92import java.util.Set;
93
94@SmallTest
95@RunWith(AndroidJUnit4.class)
96public class EmojiCompatTest {
97
98    @Before
99    public void setup() {
100        EmojiCompat.reset(config());
101    }
102
103    @Test(expected = IllegalStateException.class)
104    public void testGet_throwsException() {
105        EmojiCompat.reset((EmojiCompat) null);
106        EmojiCompat.get();
107    }
108
109    @Test
110    public void testProcess_doesNothing_withNullCharSequence() {
111        assertNull(EmojiCompat.get().process(null));
112    }
113
114    @Test
115    public void testProcess_returnsEmptySpanned_withEmptyString() {
116        final CharSequence charSequence = EmojiCompat.get().process("");
117        assertNotNull(charSequence);
118        assertEquals(0, charSequence.length());
119        assertThat(charSequence, not(hasEmoji()));
120    }
121
122    @SuppressLint("Range")
123    @Test(expected = IllegalArgumentException.class)
124    public void testProcess_withNegativeStartValue() {
125        EmojiCompat.get().process("a", -1, 1);
126    }
127
128    @SuppressLint("Range")
129    @Test(expected = IllegalArgumentException.class)
130    public void testProcess_withNegativeEndValue() {
131        EmojiCompat.get().process("a", 1, -1);
132    }
133
134    @Test(expected = IllegalArgumentException.class)
135    public void testProcess_withStartSmallerThanEndValue() {
136        EmojiCompat.get().process("aa", 1, 0);
137    }
138
139    @Test(expected = IllegalArgumentException.class)
140    public void testProcess_withStartGreaterThanLength() {
141        EmojiCompat.get().process("a", 2, 2);
142    }
143
144    @Test(expected = IllegalArgumentException.class)
145    public void testProcess_withEndGreaterThanLength() {
146        EmojiCompat.get().process("a", 0, 2);
147    }
148
149    @Test
150    public void testProcessWithStartEnd_withNoOpValues() {
151        final Spannable spannable = new SpannableString(new TestString('a')
152                .withPrefix().withSuffix().toString());
153        // early return check
154        assertSame(spannable, EmojiCompat.get().process(spannable, 0, 0));
155        assertSame(spannable, EmojiCompat.get().process(spannable, 1, 1));
156        assertSame(spannable, EmojiCompat.get().process(spannable, spannable.length(),
157                spannable.length()));
158    }
159
160    @Test
161    public void testProcess_doesNotAddEmojiSpan() {
162        final String string = "abc";
163        final CharSequence charSequence = EmojiCompat.get().process(string);
164        assertNotNull(charSequence);
165        assertEquals(string, charSequence.toString());
166        assertThat(charSequence, not(hasEmoji()));
167    }
168
169    @Test
170    @SdkSuppress(maxSdkVersion = 18)
171    public void testProcess_returnsSameCharSequence_pre19() {
172        assertNull(EmojiCompat.get().process(null));
173
174        CharSequence testString = "abc";
175        assertSame(testString, EmojiCompat.get().process(testString));
176
177        testString = new SpannableString("abc");
178        assertSame(testString, EmojiCompat.get().process(testString));
179
180        testString = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
181        assertSame(testString, EmojiCompat.get().process(testString));
182    }
183
184    @Test
185    @SdkSuppress(minSdkVersion = 19)
186    public void testProcess_addsSingleCodePointEmoji() {
187        assertCodePointMatch(EMOJI_SINGLE_CODEPOINT);
188    }
189
190    @Test
191    @SdkSuppress(minSdkVersion = 19)
192    public void testProcess_addsFlagEmoji() {
193        assertCodePointMatch(EMOJI_FLAG);
194    }
195
196    @Test
197    @SdkSuppress(minSdkVersion = 19)
198    public void testProcess_addsUnknownFlagEmoji() {
199        assertCodePointMatch(EMOJI_UNKNOWN_FLAG);
200    }
201
202    @Test
203    @SdkSuppress(minSdkVersion = 19)
204    public void testProcess_addsRegionalIndicatorSymbol() {
205        assertCodePointMatch(EMOJI_REGIONAL_SYMBOL);
206    }
207
208    @Test
209    @SdkSuppress(minSdkVersion = 19)
210    public void testProcess_addsKeyCapEmoji() {
211        assertCodePointMatch(EMOJI_DIGIT_KEYCAP);
212    }
213
214    @Test
215    @SdkSuppress(minSdkVersion = 19)
216    public void testProcess_doesNotAddEmojiForNumbers() {
217        assertCodePointDoesNotMatch(new int[] {CHAR_DIGIT});
218    }
219
220    @Test
221    @SdkSuppress(minSdkVersion = 19)
222    public void testProcess_doesNotAddEmojiForNumbers_1() {
223        final TestString string = new TestString(EMOJI_SINGLE_CODEPOINT).append('1', 'f');
224        CharSequence charSequence = EmojiCompat.get().process(string.toString());
225        assertThat(charSequence, hasEmojiCount(1));
226    }
227
228    @Test
229    @SdkSuppress(minSdkVersion = 19)
230    public void testProcess_addsVariantSelectorEmoji() {
231        assertCodePointMatch(EMOJI_DIGIT_ES);
232    }
233
234    @Test
235    @SdkSuppress(minSdkVersion = 19)
236    public void testProcess_doesNotAddVariantSelectorTextStyle() {
237        assertCodePointDoesNotMatch(new int[]{CHAR_DIGIT, CHAR_VS_TEXT});
238    }
239
240    @Test
241    @SdkSuppress(minSdkVersion = 19)
242    public void testProcess_addsVariantSelectorAndKeyCapEmoji() {
243        assertCodePointMatch(EMOJI_DIGIT_ES_KEYCAP);
244    }
245
246    @Test
247    public void testProcess_doesNotAddEmoji_forVariantBaseWithoutSelector() {
248        assertCodePointDoesNotMatch(new int[]{CHAR_DIGIT});
249    }
250
251    @Test
252    @SdkSuppress(minSdkVersion = 19)
253    public void testProcess_addsAsteriskKeyCapEmoji() {
254        assertCodePointMatch(EMOJI_ASTERISK_KEYCAP);
255    }
256
257    @Test
258    @SdkSuppress(minSdkVersion = 19)
259    public void testProcess_addsSkinModifierEmoji() {
260        assertCodePointMatch(EMOJI_SKIN_MODIFIER);
261        assertCodePointMatch(EMOJI_SKIN_MODIFIER_TYPE_ONE);
262    }
263
264    @Test
265    @SdkSuppress(minSdkVersion = 19)
266    public void testProcess_addsSkinModifierEmoji_withVariantSelector() {
267        assertCodePointMatch(EMOJI_SKIN_MODIFIER_WITH_VS);
268    }
269
270    @Test
271    @SdkSuppress(minSdkVersion = 19)
272    public void testProcess_addsSkinModifierEmoji_270c_withVariantSelector() {
273        // 0x270c is a Standardized Variant Base, Emoji Modifier Base and also Emoji
274        // therefore it is different than i.e. 0x1f3c3. The code actually failed for this test
275        // at first.
276        assertCodePointMatch(0xF0734, new int[]{0x270C, CHAR_VS_EMOJI, CHAR_FITZPATRICK});
277    }
278
279    @Test
280    @SdkSuppress(minSdkVersion = 19)
281    public void testProcess_defaultStyleDoesNotAddSpan() {
282        assertCodePointDoesNotMatch(new int[]{CHAR_DEFAULT_TEXT_STYLE});
283        assertCodePointMatch(DEFAULT_TEXT_STYLE);
284    }
285
286    @Test
287    @SdkSuppress(minSdkVersion = 19)
288    public void testProcess_defaultEmojiStyle_withTextStyleVs() {
289        assertCodePointMatch(EMOJI_SINGLE_CODEPOINT.id(),
290                new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_EMOJI});
291        assertCodePointDoesNotMatch(new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_TEXT});
292    }
293
294    @Test
295    @SdkSuppress(minSdkVersion = 19)
296    public void testProcess_genderEmoji() {
297        assertCodePointMatch(EMOJI_GENDER);
298        assertCodePointMatch(EMOJI_GENDER_WITHOUT_VS);
299    }
300
301    @Test
302    @SdkSuppress(minSdkVersion = 19)
303    public void testProcess_standardizedVariantEmojiExceptions() {
304        final int[][] exceptions = new int[][]{
305                {0x2600, 0xF034D},
306                {0x2601, 0xF0167},
307                {0x260E, 0xF034E},
308                {0x261D, 0xF0227},
309                {0x263A, 0xF02A6},
310                {0x2660, 0xF0350},
311                {0x2663, 0xF033F},
312                {0x2665, 0xF033B},
313                {0x2666, 0xF033E},
314                {0x270C, 0xF0079},
315                {0x2744, 0xF0342},
316                {0x2764, 0xF0362}
317        };
318
319        for (int i = 0; i < exceptions.length; i++) {
320            final int[] codepoints = new int[]{exceptions[i][0]};
321            assertCodePointMatch(exceptions[i][1], codepoints);
322        }
323    }
324
325    @Test
326    @SdkSuppress(minSdkVersion = 19)
327    public void testProcess_addsZwjEmoji() {
328        assertCodePointMatch(EMOJI_WITH_ZWJ);
329    }
330
331    @Test
332    @SdkSuppress(minSdkVersion = 19)
333    public void testProcess_doesNotAddEmojiForNumbersAfterZwjEmo() {
334        TestString string = new TestString(EMOJI_WITH_ZWJ).append(0x20, 0x2B, 0x31)
335                .withSuffix().withPrefix();
336        CharSequence charSequence = EmojiCompat.get().process(string.toString());
337        assertThat(charSequence, hasEmojiAt(EMOJI_WITH_ZWJ, string.emojiStartIndex(),
338                string.emojiEndIndex() - 3));
339        assertThat(charSequence, hasEmojiCount(1));
340
341        string = new TestString(EMOJI_WITH_ZWJ).withSuffix().withPrefix();
342        charSequence = EmojiCompat.get().process(string.toString());
343        assertThat(charSequence, hasEmojiCount(1));
344    }
345
346    @Test
347    @SdkSuppress(minSdkVersion = 19)
348    public void testProcess_withAppend() {
349        final Editable editable = new SpannableStringBuilder(new TestString('a').withPrefix()
350                .withSuffix().toString());
351        final int start = 1;
352        final int end = start + EMOJI_SINGLE_CODEPOINT.charCount();
353        editable.insert(start, new TestString(EMOJI_SINGLE_CODEPOINT).toString());
354        EmojiCompat.get().process(editable, start, end);
355        assertThat(editable, hasEmojiCount(1));
356        assertThat(editable, hasEmojiAt(EMOJI_SINGLE_CODEPOINT, start, end));
357    }
358
359    @Test
360    public void testProcess_doesNotCreateSpannable_ifNoEmoji() {
361        CharSequence processed = EmojiCompat.get().process("abc");
362        assertNotNull(processed);
363        assertThat(processed, instanceOf(String.class));
364
365        processed = EmojiCompat.get().process(new SpannedString("abc"));
366        assertNotNull(processed);
367        assertThat(processed, instanceOf(SpannedString.class));
368    }
369
370    @Test
371    @SdkSuppress(minSdkVersion = 19)
372    public void testProcess_reprocess() {
373        final String string = new TestString(EMOJI_SINGLE_CODEPOINT)
374                .append(EMOJI_SINGLE_CODEPOINT)
375                .append(EMOJI_SINGLE_CODEPOINT)
376                .withPrefix().withSuffix().toString();
377
378        Spannable processed = (Spannable) EmojiCompat.get().process(string);
379        assertThat(processed, hasEmojiCount(3));
380
381        final EmojiSpan[] spans = processed.getSpans(0, processed.length(), EmojiSpan.class);
382        final Set<EmojiSpan> spanSet = new HashSet<>();
383        Collections.addAll(spanSet, spans);
384
385        processed = (Spannable) EmojiCompat.get().process(processed);
386        assertThat(processed, hasEmojiCount(3));
387        // new spans should be new instances
388        final EmojiSpan[] newSpans = processed.getSpans(0, processed.length(), EmojiSpan.class);
389        for (int i = 0; i < newSpans.length; i++) {
390            assertFalse(spanSet.contains(newSpans[i]));
391        }
392    }
393
394    @SuppressLint("Range")
395    @Test(expected = IllegalArgumentException.class)
396    public void testProcess_throwsException_withMaxEmojiSetToNegative() {
397        final String original = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
398
399        final CharSequence processed = EmojiCompat.get().process(original, 0, original.length(),
400                -1 /*maxEmojiCount*/);
401
402        assertThat(processed, not(hasEmoji()));
403    }
404
405    @Test
406    public void testProcess_withMaxEmojiSetToZero() {
407        final String original = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
408
409        final CharSequence processed = EmojiCompat.get().process(original, 0, original.length(),
410                0 /*maxEmojiCount*/);
411
412        assertThat(processed, not(hasEmoji()));
413    }
414
415    @Test
416    @SdkSuppress(minSdkVersion = 19)
417    public void testProcess_withMaxEmojiSetToOne() {
418        final String original = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
419
420        final CharSequence processed = EmojiCompat.get().process(original, 0, original.length(),
421                1 /*maxEmojiCount*/);
422
423        assertThat(processed, hasEmojiCount(1));
424        assertThat(processed, hasEmoji(EMOJI_SINGLE_CODEPOINT));
425    }
426
427    @Test
428    @SdkSuppress(minSdkVersion = 19)
429    public void testProcess_withMaxEmojiSetToLessThenExistingSpanCount() {
430        final String original = new TestString(EMOJI_SINGLE_CODEPOINT)
431                .append(EMOJI_SINGLE_CODEPOINT)
432                .append(EMOJI_SINGLE_CODEPOINT)
433                .toString();
434
435        // add 2 spans
436        final CharSequence processed = EmojiCompat.get().process(original, 0, original.length(), 2);
437
438        assertThat(processed, hasEmojiCount(2));
439
440        // use the Spannable with 2 spans, but use maxEmojiCount=1, start from the beginning of
441        // last (3rd) emoji
442        EmojiCompat.get().process(processed, original.length() - EMOJI_SINGLE_CODEPOINT.charCount(),
443                original.length(), 1 /*maxEmojiCount*/);
444
445        // expectation: there are still 2 emojis
446        assertThat(processed, hasEmojiCount(2));
447    }
448
449    @Test
450    @SdkSuppress(minSdkVersion = 19)
451    public void testProcess_withMaxEmojiSet_withExistingEmojis() {
452        // test string with two emoji characters
453        final String original = new TestString(EMOJI_SINGLE_CODEPOINT)
454                .append(EMOJI_FLAG).toString();
455
456        // process and add 1 EmojiSpan, maxEmojiCount=1
457        CharSequence processed = EmojiCompat.get().process(original, 0, original.length(),
458                1 /*maxEmojiCount*/);
459
460        // assert that there is a single emoji
461        assertThat(processed, hasEmojiCount(1));
462        assertThat(processed,
463                hasEmojiAt(EMOJI_SINGLE_CODEPOINT, 0, EMOJI_SINGLE_CODEPOINT.charCount()));
464
465        // call process again with the charSequence that already has 1 span
466        processed = EmojiCompat.get().process(processed, EMOJI_SINGLE_CODEPOINT.charCount(),
467                processed.length(), 1 /*maxEmojiCount*/);
468
469        // assert that there is still a single emoji
470        assertThat(processed, hasEmojiCount(1));
471        assertThat(processed,
472                hasEmojiAt(EMOJI_SINGLE_CODEPOINT, 0, EMOJI_SINGLE_CODEPOINT.charCount()));
473
474        // make the same call, this time with maxEmojiCount=2
475        processed = EmojiCompat.get().process(processed, EMOJI_SINGLE_CODEPOINT.charCount(),
476                processed.length(), 2 /*maxEmojiCount*/);
477
478        // assert that it contains 2 emojis
479        assertThat(processed, hasEmojiCount(2));
480        assertThat(processed,
481                hasEmojiAt(EMOJI_SINGLE_CODEPOINT, 0, EMOJI_SINGLE_CODEPOINT.charCount()));
482        assertThat(processed,
483                hasEmojiAt(EMOJI_FLAG, EMOJI_SINGLE_CODEPOINT.charCount(),
484                        original.length()));
485    }
486
487    @Test
488    @SdkSuppress(minSdkVersion = 19)
489    public void testProcess_withReplaceNonExistent_callsGlyphChecker() {
490        final Config config = TestConfigBuilder.config().setReplaceAll(true);
491        EmojiCompat.reset(config);
492
493        final EmojiProcessor.GlyphChecker glyphChecker = mock(EmojiProcessor.GlyphChecker.class);
494        when(glyphChecker.hasGlyph(any(CharSequence.class), anyInt(), anyInt())).thenReturn(true);
495        EmojiCompat.get().setGlyphChecker(glyphChecker);
496
497        final String original = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
498
499        CharSequence processed = EmojiCompat.get().process(original, 0, original.length(),
500                Integer.MAX_VALUE /*maxEmojiCount*/, EmojiCompat.REPLACE_STRATEGY_NON_EXISTENT);
501
502        // when function overrides config level replaceAll, a call to GlyphChecker is expected.
503        verify(glyphChecker, times(1)).hasGlyph(any(CharSequence.class), anyInt(), anyInt());
504
505        // since replaceAll is false, there should be no EmojiSpans
506        assertThat(processed, not(hasEmoji()));
507    }
508
509    @Test
510    @SdkSuppress(minSdkVersion = 19)
511    public void testProcess_withReplaceDefault_doesNotCallGlyphChecker() {
512        final Config config = TestConfigBuilder.config().setReplaceAll(true);
513        EmojiCompat.reset(config);
514
515        final EmojiProcessor.GlyphChecker glyphChecker = mock(EmojiProcessor.GlyphChecker.class);
516        when(glyphChecker.hasGlyph(any(CharSequence.class), anyInt(), anyInt())).thenReturn(true);
517        EmojiCompat.get().setGlyphChecker(glyphChecker);
518
519        final String original = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
520        // call without replaceAll, config value (true) should be used
521        final CharSequence processed = EmojiCompat.get().process(original, 0, original.length(),
522                Integer.MAX_VALUE /*maxEmojiCount*/, EmojiCompat.REPLACE_STRATEGY_DEFAULT);
523
524        // replaceAll=true should not call hasGlyph
525        verify(glyphChecker, times(0)).hasGlyph(any(CharSequence.class), anyInt(), anyInt());
526
527        assertThat(processed, hasEmojiCount(1));
528        assertThat(processed, hasEmoji(EMOJI_SINGLE_CODEPOINT));
529    }
530
531    @Test(expected = NullPointerException.class)
532    public void testHasEmojiGlyph_withNullCharSequence() {
533        EmojiCompat.get().hasEmojiGlyph(null);
534    }
535
536    @Test(expected = NullPointerException.class)
537    public void testHasEmojiGlyph_withMetadataVersion_withNullCharSequence() {
538        EmojiCompat.get().hasEmojiGlyph(null, Integer.MAX_VALUE);
539    }
540
541    @Test
542    @SdkSuppress(maxSdkVersion = 18)
543    public void testHasEmojiGlyph_pre19() {
544        String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
545        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence));
546    }
547
548    @Test
549    @SdkSuppress(maxSdkVersion = 18)
550    public void testHasEmojiGlyph_withMetaVersion_pre19() {
551        String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
552        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence, Integer.MAX_VALUE));
553    }
554
555    @Test
556    @SdkSuppress(minSdkVersion = 19)
557    public void testHasEmojiGlyph_returnsTrueForExistingEmoji() {
558        final String sequence = new TestString(EMOJI_FLAG).toString();
559        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
560    }
561
562    @Test
563    public void testHasGlyph_returnsFalseForNonExistentEmoji() {
564        final String sequence = new TestString(EMOJI_FLAG).append(0x1111).toString();
565        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence));
566    }
567
568    @Test
569    @SdkSuppress(minSdkVersion = 19)
570    public void testHashEmojiGlyph_withDefaultEmojiStyles() {
571        String sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE}).toString();
572        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
573
574        sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_EMOJI}).toString();
575        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence));
576
577        sequence = new TestString(new int[]{CHAR_DEFAULT_EMOJI_STYLE, CHAR_VS_TEXT}).toString();
578        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence));
579    }
580
581    @Test
582    @SdkSuppress(minSdkVersion = 19)
583    public void testHashEmojiGlyph_withMetadataVersion() {
584        final String sequence = new TestString(EMOJI_SINGLE_CODEPOINT).toString();
585        assertFalse(EmojiCompat.get().hasEmojiGlyph(sequence, 0));
586        assertTrue(EmojiCompat.get().hasEmojiGlyph(sequence, Integer.MAX_VALUE));
587    }
588
589    @Test
590    @SdkSuppress(maxSdkVersion = 18)
591    public void testGetLoadState_returnsSuccess_pre19() {
592        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_SUCCEEDED);
593    }
594
595    @Test
596    @SdkSuppress(minSdkVersion = 19)
597    public void testGetLoadState_returnsSuccessIfLoadSuccess() throws InterruptedException {
598        final WaitingDataLoader metadataLoader = new WaitingDataLoader(true /*success*/);
599        final Config config = new TestConfig(metadataLoader);
600        EmojiCompat.reset(config);
601
602        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_LOADING);
603
604        metadataLoader.getLoaderLatch().countDown();
605        metadataLoader.getTestLatch().await();
606        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
607
608        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_SUCCEEDED);
609    }
610
611    @Test
612    @SdkSuppress(minSdkVersion = 19)
613    public void testGetLoadState_returnsFailIfLoadFail() throws InterruptedException {
614        final WaitingDataLoader metadataLoader = new WaitingDataLoader(false/*fail*/);
615        final Config config = new TestConfig(metadataLoader);
616        EmojiCompat.reset(config);
617
618        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_LOADING);
619
620        metadataLoader.getLoaderLatch().countDown();
621        metadataLoader.getTestLatch().await();
622        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
623
624        assertEquals(EmojiCompat.get().getLoadState(), EmojiCompat.LOAD_STATE_FAILED);
625    }
626
627    @Test
628    public void testUpdateEditorInfoAttrs_doesNotSetKeyIfNotInitialized() {
629        final EditorInfo editorInfo = new EditorInfo();
630        editorInfo.extras = new Bundle();
631
632        final WaitingDataLoader metadataLoader = new WaitingDataLoader();
633        final Config config = new TestConfig(metadataLoader);
634        EmojiCompat.reset(config);
635
636        EmojiCompat.get().updateEditorInfoAttrs(editorInfo);
637
638        final Bundle extras = editorInfo.extras;
639        assertFalse(extras.containsKey(EmojiCompat.EDITOR_INFO_METAVERSION_KEY));
640        assertFalse(extras.containsKey(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
641
642        metadataLoader.getLoaderLatch().countDown();
643    }
644
645    @Test
646    @SdkSuppress(minSdkVersion = 19)
647    public void testUpdateEditorInfoAttrs_setsKeysIfInitialized() {
648        final EditorInfo editorInfo = new EditorInfo();
649        editorInfo.extras = new Bundle();
650        Config config = new TestConfig().setReplaceAll(false);
651        EmojiCompat.reset(config);
652        EmojiCompat.get().updateEditorInfoAttrs(editorInfo);
653
654        final Bundle extras = editorInfo.extras;
655        assertTrue(extras.containsKey(EmojiCompat.EDITOR_INFO_METAVERSION_KEY));
656        assertTrue(extras.getInt(EmojiCompat.EDITOR_INFO_METAVERSION_KEY) > 0);
657        assertTrue(extras.containsKey(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
658        assertFalse(extras.getBoolean(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
659
660        config = new TestConfig().setReplaceAll(true);
661        EmojiCompat.reset(config);
662        EmojiCompat.get().updateEditorInfoAttrs(editorInfo);
663
664        assertTrue(extras.containsKey(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
665        assertTrue(extras.getBoolean(EmojiCompat.EDITOR_INFO_REPLACE_ALL_KEY));
666    }
667
668    @Test
669    @SdkSuppress(maxSdkVersion = 18)
670    public void testHandleDeleteSurroundingText_pre19() {
671        final TestString testString = new TestString(EMOJI_SINGLE_CODEPOINT);
672        final InputConnection inputConnection = mock(InputConnection.class);
673        final Editable editable = spy(new SpannableStringBuilder(testString.toString()));
674
675        Selection.setSelection(editable, testString.emojiEndIndex());
676
677        reset(editable);
678        reset(inputConnection);
679        verifyNoMoreInteractions(editable);
680        verifyNoMoreInteractions(inputConnection);
681
682        // try backwards delete 1 character
683        assertFalse(
684                EmojiCompat.handleDeleteSurroundingText(inputConnection, editable, 1, 0, false));
685    }
686
687    @Test
688    @SdkSuppress(maxSdkVersion = 18)
689    public void testOnKeyDown_pre19() {
690        final TestString testString = new TestString(EMOJI_SINGLE_CODEPOINT);
691        final Editable editable = spy(new SpannableStringBuilder(testString.toString()));
692        Selection.setSelection(editable, testString.emojiEndIndex());
693        final KeyEvent event = del();
694
695        reset(editable);
696        verifyNoMoreInteractions(editable);
697
698        assertFalse(EmojiCompat.handleOnKeyDown(editable, event.getKeyCode(), event));
699    }
700
701    private void assertCodePointMatch(EmojiMapping emoji) {
702        assertCodePointMatch(emoji.id(), emoji.codepoints());
703    }
704
705    private void assertCodePointMatch(int id, int[] codepoints) {
706        TestString string = new TestString(codepoints);
707        CharSequence charSequence = EmojiCompat.get().process(string.toString());
708        assertThat(charSequence, hasEmojiAt(id, string.emojiStartIndex(), string.emojiEndIndex()));
709
710        // case where Emoji is in the middle of string
711        string = new TestString(codepoints).withPrefix().withSuffix();
712        charSequence = EmojiCompat.get().process(string.toString());
713        assertThat(charSequence, hasEmojiAt(id, string.emojiStartIndex(), string.emojiEndIndex()));
714
715        // case where Emoji is at the end of string
716        string = new TestString(codepoints).withSuffix();
717        charSequence = EmojiCompat.get().process(string.toString());
718        assertThat(charSequence, hasEmojiAt(id, string.emojiStartIndex(), string.emojiEndIndex()));
719    }
720
721    private void assertCodePointDoesNotMatch(int[] codepoints) {
722        TestString string = new TestString(codepoints);
723        CharSequence charSequence = EmojiCompat.get().process(string.toString());
724        assertThat(charSequence, not(hasEmoji()));
725
726        string = new TestString(codepoints).withSuffix().withPrefix();
727        charSequence = EmojiCompat.get().process(string.toString());
728        assertThat(charSequence, not(hasEmoji()));
729
730        string = new TestString(codepoints).withPrefix();
731        charSequence = EmojiCompat.get().process(string.toString());
732        assertThat(charSequence, not(hasEmoji()));
733    }
734}
735