1 /* 2 * Copyright 2018 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 package androidx.emoji.text; 17 18 import static androidx.emoji.util.Emoji.EMOJI_FLAG; 19 import static androidx.emoji.util.Emoji.EMOJI_WITH_ZWJ; 20 import static androidx.emoji.util.EmojiMatcher.hasEmoji; 21 import static androidx.emoji.util.EmojiMatcher.hasEmojiCount; 22 23 import static org.hamcrest.MatcherAssert.assertThat; 24 import static org.hamcrest.core.IsNot.not; 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertTrue; 28 import static org.mockito.Mockito.mock; 29 30 import android.annotation.SuppressLint; 31 import android.support.test.filters.SdkSuppress; 32 import android.support.test.filters.SmallTest; 33 import android.support.test.runner.AndroidJUnit4; 34 import android.text.Editable; 35 import android.text.Selection; 36 import android.text.SpannableStringBuilder; 37 import android.view.inputmethod.InputConnection; 38 39 import androidx.emoji.util.Emoji; 40 import androidx.emoji.util.TestString; 41 42 import org.junit.Before; 43 import org.junit.BeforeClass; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 47 @SmallTest 48 @RunWith(AndroidJUnit4.class) 49 @SdkSuppress(minSdkVersion = 19) 50 public class SoftDeleteTest { 51 private InputConnection mInputConnection; 52 private TestString mTestString; 53 private Editable mEditable; 54 55 @BeforeClass 56 public static void setupEmojiCompat() { 57 EmojiCompat.reset(TestConfigBuilder.config()); 58 } 59 60 @Before 61 public void setup() { 62 mInputConnection = mock(InputConnection.class); 63 mTestString = new TestString(Emoji.EMOJI_WITH_ZWJ).withPrefix().withSuffix(); 64 mEditable = new SpannableStringBuilder(mTestString.toString()); 65 EmojiCompat.get().process(mEditable); 66 assertThat(mEditable, hasEmojiCount(1)); 67 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 68 } 69 70 @Test 71 public void testDelete_doesNotDelete_whenSelectionIsUndefined() { 72 // no selection is set on editable 73 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 74 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 75 76 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 77 assertEquals(mTestString.toString(), mEditable.toString()); 78 } 79 80 @Test 81 public void testDelete_doesNotDelete_whenThereIsSelectionLongerThanZero() { 82 Selection.setSelection(mEditable, mTestString.emojiStartIndex(), 83 mTestString.emojiEndIndex() + 1); 84 85 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 86 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 87 88 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 89 assertEquals(mTestString.toString(), mEditable.toString()); 90 } 91 92 @Test 93 public void testDelete_withNullEditable() { 94 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 95 96 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, null, 97 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 98 99 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 100 assertEquals(mTestString.toString(), mEditable.toString()); 101 } 102 103 @Test 104 public void testDelete_withNullInputConnection() { 105 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 106 107 assertFalse(EmojiCompat.handleDeleteSurroundingText(null, mEditable, 108 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 109 110 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 111 assertEquals(mTestString.toString(), mEditable.toString()); 112 } 113 114 @SuppressLint("Range") 115 @Test 116 public void testDelete_withInvalidLength() { 117 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 118 119 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 120 -1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 121 122 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 123 assertEquals(mTestString.toString(), mEditable.toString()); 124 } 125 126 @SuppressLint("Range") 127 @Test 128 public void testDelete_withInvalidAfterLength() { 129 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 130 131 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 132 0 /*beforeLength*/, -1 /*afterLength*/, false /*inCodePoints*/)); 133 134 assertThat(mEditable, hasEmoji(EMOJI_WITH_ZWJ)); 135 assertEquals(mTestString.toString(), mEditable.toString()); 136 } 137 138 @Test 139 public void testDelete_backward() { 140 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 141 142 // backwards delete 1 character, it will delete the emoji 143 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 144 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 145 146 assertThat(mEditable, not(hasEmoji())); 147 assertEquals(new TestString().withPrefix().withSuffix().toString(), mEditable.toString()); 148 } 149 150 @Test 151 public void testDelete_backward_inCodepoints() { 152 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 153 154 // backwards delete 1 character, it will delete the emoji 155 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 156 1 /*beforeLength*/, 0 /*afterLength*/, true /*inCodePoints*/)); 157 158 assertThat(mEditable, not(hasEmoji())); 159 assertEquals(new TestString().withPrefix().withSuffix().toString(), mEditable.toString()); 160 } 161 162 @Test 163 public void testDelete_forward() { 164 Selection.setSelection(mEditable, mTestString.emojiStartIndex()); 165 166 // forward delete 1 character, it will dele the emoji. 167 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 168 0 /*beforeLength*/, 1 /*afterLength*/, false /*inCodePoints*/)); 169 170 assertThat(mEditable, not(hasEmoji())); 171 assertEquals(new TestString().withPrefix().withSuffix().toString(), mEditable.toString()); 172 } 173 174 @Test 175 public void testDelete_forward_inCodepoints() { 176 Selection.setSelection(mEditable, mTestString.emojiStartIndex()); 177 178 // forward delete 1 codepoint, it will delete the emoji. 179 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 180 0 /*beforeLength*/, 1 /*afterLength*/, false /*inCodePoints*/)); 181 182 assertThat(mEditable, not(hasEmoji())); 183 assertEquals(new TestString().withPrefix().withSuffix().toString(), mEditable.toString()); 184 } 185 186 @Test 187 public void testDelete_backward_doesNotDeleteWhenSelectionAtCharSequenceStart() { 188 // make sure selection at 0 does not do something weird for backward delete 189 Selection.setSelection(mEditable, 0); 190 191 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 192 1 /*beforeLength*/, 0 /*afterLength*/, false /*inCodePoints*/)); 193 194 assertThat(mEditable, hasEmoji()); 195 assertEquals(mTestString.toString(), mEditable.toString()); 196 } 197 198 @Test 199 public void testDelete_forward_doesNotDeleteWhenSelectionAtCharSequenceEnd() { 200 // make sure selection at end does not do something weird for forward delete 201 Selection.setSelection(mEditable, mTestString.emojiEndIndex()); 202 203 assertFalse(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 204 0 /*beforeLength*/, 1 /*afterLength*/, false /*inCodePoints*/)); 205 206 assertThat(mEditable, hasEmoji()); 207 assertEquals(mTestString.toString(), mEditable.toString()); 208 } 209 210 @Test 211 public void testDelete_withMultipleCharacters() { 212 // prepare string as abc[emoji]def 213 mTestString = new TestString(EMOJI_FLAG); 214 mEditable = new SpannableStringBuilder("abc" + mTestString.toString() + "def"); 215 EmojiCompat.get().process(mEditable); 216 217 // set the selection in the middle of emoji 218 Selection.setSelection(mEditable, "abc".length() + EMOJI_FLAG.charCount() / 2); 219 220 // delete 4 characters forward, 4 character backwards 221 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 222 4 /*beforeLength*/, 4 /*afterLength*/, false /*inCodePoints*/)); 223 224 assertThat(mEditable, not(hasEmoji())); 225 assertEquals("af", mEditable.toString()); 226 } 227 228 @Test 229 public void testDelete_withMultipleCodepoints() { 230 // prepare string as abc[emoji]def 231 mTestString = new TestString(EMOJI_FLAG); 232 mEditable = new SpannableStringBuilder("abc" + mTestString.toString() + "def"); 233 EmojiCompat.get().process(mEditable); 234 235 // set the selection in the middle of emoji 236 Selection.setSelection(mEditable, "abc".length() + EMOJI_FLAG.charCount() / 2); 237 238 // delete 3 codepoints forward, 3 codepoints backwards 239 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 240 3 /*beforeLength*/, 3 /*afterLength*/, true /*inCodePoints*/)); 241 242 assertThat(mEditable, not(hasEmoji())); 243 assertEquals("af", mEditable.toString()); 244 } 245 246 @Test 247 public void testDelete_withMultipleCharacters_withDeleteLengthLongerThanString() { 248 // prepare string as abc[emoji]def 249 mTestString = new TestString(EMOJI_FLAG); 250 mEditable = new SpannableStringBuilder("abc" + mTestString.toString() + "def"); 251 EmojiCompat.get().process(mEditable); 252 253 // set the selection in the middle of emoji 254 Selection.setSelection(mEditable, "abc".length() + EMOJI_FLAG.charCount() / 2); 255 256 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 257 100 /*beforeLength*/, 100 /*afterLength*/, false /*inCodePoints*/)); 258 259 assertThat(mEditable, not(hasEmoji())); 260 assertEquals("", mEditable.toString()); 261 } 262 263 @Test 264 public void testDelete_withMultipleCodepoints_withDeleteLengthLongerThanString() { 265 // prepare string as abc[emoji]def 266 mTestString = new TestString(EMOJI_FLAG); 267 mEditable = new SpannableStringBuilder("abc" + mTestString.toString() + "def"); 268 EmojiCompat.get().process(mEditable); 269 270 // set the selection in the middle of emoji 271 Selection.setSelection(mEditable, "abc".length() + EMOJI_FLAG.charCount() / 2); 272 273 assertTrue(EmojiCompat.handleDeleteSurroundingText(mInputConnection, mEditable, 274 100 /*beforeLength*/, 100 /*afterLength*/, true /*inCodePoints*/)); 275 276 assertThat(mEditable, not(hasEmoji())); 277 assertEquals("", mEditable.toString()); 278 } 279 } 280