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