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 */ 16 package androidx.emoji.widget; 17 18 import static org.hamcrest.Matchers.arrayWithSize; 19 import static org.hamcrest.Matchers.instanceOf; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertSame; 23 import static org.junit.Assert.assertThat; 24 import static org.mockito.Matchers.any; 25 import static org.mockito.Matchers.anyInt; 26 import static org.mockito.Matchers.anyObject; 27 import static org.mockito.Matchers.same; 28 import static org.mockito.Mockito.mock; 29 import static org.mockito.Mockito.never; 30 import static org.mockito.Mockito.reset; 31 import static org.mockito.Mockito.times; 32 import static org.mockito.Mockito.verify; 33 import static org.mockito.Mockito.withSettings; 34 35 import android.support.test.filters.SmallTest; 36 import android.support.test.runner.AndroidJUnit4; 37 import android.text.Editable; 38 import android.text.SpanWatcher; 39 import android.text.Spannable; 40 import android.text.Spanned; 41 import android.text.TextWatcher; 42 import android.text.style.QuoteSpan; 43 44 import androidx.emoji.text.EmojiSpan; 45 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 @SmallTest 51 @RunWith(AndroidJUnit4.class) 52 public class SpannableBuilderTest { 53 54 private TextWatcher mWatcher; 55 private Class mClass; 56 57 @Before 58 public void setup() { 59 mWatcher = mock(TextWatcher.class, withSettings().extraInterfaces(SpanWatcher.class)); 60 mClass = mWatcher.getClass(); 61 } 62 63 @Test 64 public void testConstructor() { 65 new SpannableBuilder(mClass); 66 67 new SpannableBuilder(mClass, "abc"); 68 69 new SpannableBuilder(mClass, "abc", 0, 3); 70 71 // test spannable copying? do I need it? 72 } 73 74 @Test 75 public void testSubSequence() { 76 final SpannableBuilder spannable = new SpannableBuilder(mClass, "abc"); 77 final QuoteSpan span1 = mock(QuoteSpan.class); 78 final QuoteSpan span2 = mock(QuoteSpan.class); 79 spannable.setSpan(span1, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 80 spannable.setSpan(span2, 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 81 82 final CharSequence subsequence = spannable.subSequence(0, 1); 83 assertNotNull(subsequence); 84 assertThat(subsequence, instanceOf(SpannableBuilder.class)); 85 86 final QuoteSpan[] spans = spannable.getSpans(0, 1, QuoteSpan.class); 87 assertThat(spans, arrayWithSize(1)); 88 assertSame(spans[0], span1); 89 } 90 91 @Test 92 public void testSetAndGetSpan() { 93 final SpannableBuilder spannable = new SpannableBuilder(mClass, "abcde"); 94 spannable.setSpan(mWatcher, 1, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 95 96 // getSpans should return the span 97 Object[] spans = spannable.getSpans(0, spannable.length(), mClass); 98 assertNotNull(spans); 99 assertThat(spans, arrayWithSize(1)); 100 assertSame(mWatcher, spans[0]); 101 102 // span attributes should be correct 103 assertEquals(1, spannable.getSpanStart(mWatcher)); 104 assertEquals(2, spannable.getSpanEnd(mWatcher)); 105 assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, spannable.getSpanFlags(mWatcher)); 106 107 // should remove the span 108 spannable.removeSpan(mWatcher); 109 spans = spannable.getSpans(0, spannable.length(), QuoteSpan.class); 110 assertNotNull(spans); 111 assertThat(spans, arrayWithSize(0)); 112 } 113 114 @Test 115 public void testNextSpanTransition() { 116 final SpannableBuilder spannable = new SpannableBuilder(mClass, "abcde"); 117 spannable.setSpan(mWatcher, 1, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 118 final int start = spannable.nextSpanTransition(0, spannable.length(), mClass); 119 assertEquals(1, start); 120 } 121 122 @Test 123 public void testBlocksSpanCallbacks_forEmojiSpans() { 124 final EmojiSpan span = mock(EmojiSpan.class); 125 final SpannableBuilder spannable = new SpannableBuilder(mClass, "123456"); 126 spannable.setSpan(mWatcher, 0, spannable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 127 spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 128 reset(mWatcher); 129 130 spannable.delete(0, 3); 131 132 // verify that characters are deleted 133 assertEquals("456", spannable.toString()); 134 // verify EmojiSpan is deleted 135 EmojiSpan[] spans = spannable.getSpans(0, spannable.length(), EmojiSpan.class); 136 assertThat(spans, arrayWithSize(0)); 137 138 // verify the call to span callbacks are blocked 139 verify((SpanWatcher) mWatcher, never()).onSpanRemoved(any(Spannable.class), 140 same(span), anyInt(), anyInt()); 141 verify((SpanWatcher) mWatcher, never()).onSpanAdded(any(Spannable.class), 142 same(span), anyInt(), anyInt()); 143 verify((SpanWatcher) mWatcher, never()).onSpanChanged(any(Spannable.class), 144 same(span), anyInt(), anyInt(), anyInt(), anyInt()); 145 146 // verify the call to TextWatcher callbacks are called 147 verify(mWatcher, times(1)).beforeTextChanged(any(CharSequence.class), anyInt(), 148 anyInt(), anyInt()); 149 verify(mWatcher, times(1)).onTextChanged(any(CharSequence.class), anyInt(), anyInt(), 150 anyInt()); 151 verify(mWatcher, times(1)).afterTextChanged(any(Editable.class)); 152 } 153 154 @Test 155 public void testDoesNotBlockSpanCallbacks_forNonEmojiSpans() { 156 final QuoteSpan span = mock(QuoteSpan.class); 157 final SpannableBuilder spannable = new SpannableBuilder(mClass, "123456"); 158 spannable.setSpan(mWatcher, 0, spannable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 159 spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 160 reset(mWatcher); 161 162 spannable.delete(0, 3); 163 164 // verify that characters are deleted 165 assertEquals("456", spannable.toString()); 166 // verify QuoteSpan is deleted 167 QuoteSpan[] spans = spannable.getSpans(0, spannable.length(), QuoteSpan.class); 168 assertThat(spans, arrayWithSize(0)); 169 170 // verify the call to span callbacks are not blocked 171 verify((SpanWatcher) mWatcher, times(1)).onSpanRemoved(any(Spannable.class), 172 anyObject(), anyInt(), anyInt()); 173 174 // verify the call to TextWatcher callbacks are called 175 verify(mWatcher, times(1)).beforeTextChanged(any(CharSequence.class), anyInt(), anyInt(), 176 anyInt()); 177 verify(mWatcher, times(1)).onTextChanged(any(CharSequence.class), anyInt(), anyInt(), 178 anyInt()); 179 verify(mWatcher, times(1)).afterTextChanged(any(Editable.class)); 180 } 181 182 @Test 183 public void testDoesNotBlockSpanCallbacksForOtherWatchers() { 184 final TextWatcher textWatcher = mock(TextWatcher.class); 185 final SpanWatcher spanWatcher = mock(SpanWatcher.class); 186 187 final EmojiSpan span = mock(EmojiSpan.class); 188 final SpannableBuilder spannable = new SpannableBuilder(mClass, "123456"); 189 spannable.setSpan(textWatcher, 0, spannable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 190 spannable.setSpan(spanWatcher, 0, spannable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 191 spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 192 reset(textWatcher); 193 194 spannable.delete(0, 3); 195 196 // verify that characters are deleted 197 assertEquals("456", spannable.toString()); 198 // verify EmojiSpan is deleted 199 EmojiSpan[] spans = spannable.getSpans(0, spannable.length(), EmojiSpan.class); 200 assertThat(spans, arrayWithSize(0)); 201 202 // verify the call to span callbacks are blocked 203 verify(spanWatcher, times(1)).onSpanRemoved(any(Spannable.class), same(span), 204 anyInt(), anyInt()); 205 206 // verify the call to TextWatcher callbacks are called 207 verify(textWatcher, times(1)).beforeTextChanged(any(CharSequence.class), anyInt(), 208 anyInt(), anyInt()); 209 verify(textWatcher, times(1)).onTextChanged(any(CharSequence.class), anyInt(), anyInt(), 210 anyInt()); 211 verify(textWatcher, times(1)).afterTextChanged(any(Editable.class)); 212 } 213 } 214