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