1/* 2 * Copyright (C) 2016 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 17package com.android.setupwizardlib.view; 18 19import static com.google.common.truth.Truth.assertThat; 20 21import static org.junit.Assert.assertEquals; 22import static org.junit.Assert.assertFalse; 23import static org.junit.Assert.assertSame; 24import static org.junit.Assert.assertTrue; 25import static org.mockito.Matchers.eq; 26import static org.mockito.Mockito.doReturn; 27import static org.mockito.Mockito.mock; 28import static org.mockito.Mockito.spy; 29import static org.mockito.Mockito.verify; 30import static org.robolectric.RuntimeEnvironment.application; 31 32import android.annotation.SuppressLint; 33import android.content.Context; 34import android.content.ContextWrapper; 35import android.os.Build.VERSION; 36import android.os.Build.VERSION_CODES; 37import android.text.Annotation; 38import android.text.SpannableStringBuilder; 39import android.text.Spanned; 40import android.text.style.TextAppearanceSpan; 41import android.view.MotionEvent; 42 43import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner; 44import com.android.setupwizardlib.span.LinkSpan; 45import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; 46import com.android.setupwizardlib.view.TouchableMovementMethod.TouchableLinkMovementMethod; 47 48import org.junit.Test; 49import org.junit.runner.RunWith; 50import org.robolectric.annotation.Config; 51 52import java.util.Arrays; 53 54@RunWith(SuwLibRobolectricTestRunner.class) 55@Config(sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK }) 56public class RichTextViewTest { 57 58 @Test 59 public void testLinkAnnotation() { 60 Annotation link = new Annotation("link", "foobar"); 61 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 62 ssb.setSpan(link, 1, 2, 0 /* flags */); 63 64 RichTextView textView = new RichTextView(application); 65 textView.setText(ssb); 66 67 final CharSequence text = textView.getText(); 68 assertTrue("Text should be spanned", text instanceof Spanned); 69 70 assertThat(textView.getMovementMethod()).isInstanceOf(TouchableLinkMovementMethod.class); 71 72 Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); 73 assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); 74 75 spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); 76 assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); 77 assertTrue("The span should be a LinkSpan", spans[0] instanceof LinkSpan); 78 assertEquals("The LinkSpan should have id \"foobar\"", 79 "foobar", ((LinkSpan) spans[0]).getId()); 80 } 81 82 @Test 83 public void testOnLinkClickListener() { 84 Annotation link = new Annotation("link", "foobar"); 85 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 86 ssb.setSpan(link, 1, 2, 0 /* flags */); 87 88 RichTextView textView = new RichTextView(application); 89 textView.setText(ssb); 90 91 OnLinkClickListener listener = mock(OnLinkClickListener.class); 92 textView.setOnLinkClickListener(listener); 93 94 assertSame(listener, textView.getOnLinkClickListener()); 95 96 CharSequence text = textView.getText(); 97 LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); 98 spans[0].onClick(textView); 99 100 verify(listener).onLinkClick(eq(spans[0])); 101 } 102 103 @Test 104 public void testLegacyContextOnClickListener() { 105 // Click listener implemented by context should still be invoked for compatibility. 106 Annotation link = new Annotation("link", "foobar"); 107 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 108 ssb.setSpan(link, 1, 2, 0 /* flags */); 109 110 TestContext context = spy(new TestContext(application)); 111 RichTextView textView = new RichTextView(context); 112 textView.setText(ssb); 113 114 CharSequence text = textView.getText(); 115 LinkSpan[] spans = ((Spanned) text).getSpans(0, text.length(), LinkSpan.class); 116 spans[0].onClick(textView); 117 118 verify(context).onClick(eq(spans[0])); 119 } 120 121 @Test 122 public void onTouchEvent_clickOnLinks_shouldReturnTrue() { 123 Annotation link = new Annotation("link", "foobar"); 124 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 125 ssb.setSpan(link, 0, 2, 0 /* flags */); 126 127 RichTextView textView = new RichTextView(application); 128 textView.setText(ssb); 129 130 TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); 131 textView.setMovementMethod(mockMovementMethod); 132 133 MotionEvent motionEvent = 134 MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); 135 doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); 136 doReturn(true).when(mockMovementMethod).isLastTouchEventHandled(); 137 assertThat(textView.onTouchEvent(motionEvent)).isTrue(); 138 } 139 140 @Test 141 public void onTouchEvent_clickOutsideLinks_shouldReturnFalse() { 142 Annotation link = new Annotation("link", "foobar"); 143 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 144 ssb.setSpan(link, 0, 2, 0 /* flags */); 145 146 RichTextView textView = new RichTextView(application); 147 textView.setText(ssb); 148 149 TouchableLinkMovementMethod mockMovementMethod = mock(TouchableLinkMovementMethod.class); 150 textView.setMovementMethod(mockMovementMethod); 151 152 MotionEvent motionEvent = 153 MotionEvent.obtain(123, 22, MotionEvent.ACTION_DOWN, 0, 0, 0); 154 doReturn(motionEvent).when(mockMovementMethod).getLastTouchEvent(); 155 doReturn(false).when(mockMovementMethod).isLastTouchEventHandled(); 156 assertThat(textView.onTouchEvent(motionEvent)).isFalse(); 157 } 158 159 @Test 160 public void testTextStyle() { 161 Annotation link = new Annotation("textAppearance", "foobar"); 162 SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world"); 163 ssb.setSpan(link, 1, 2, 0 /* flags */); 164 165 RichTextView textView = new RichTextView(application); 166 textView.setText(ssb); 167 168 final CharSequence text = textView.getText(); 169 assertTrue("Text should be spanned", text instanceof Spanned); 170 171 Object[] spans = ((Spanned) text).getSpans(0, text.length(), Annotation.class); 172 assertEquals("Annotation should be removed " + Arrays.toString(spans), 0, spans.length); 173 174 spans = ((Spanned) text).getSpans(0, text.length(), TextAppearanceSpan.class); 175 assertEquals("There should be one span " + Arrays.toString(spans), 1, spans.length); 176 assertTrue("The span should be a TextAppearanceSpan", 177 spans[0] instanceof TextAppearanceSpan); 178 } 179 180 @Test 181 public void testTextContainingLinksAreFocusable() { 182 Annotation testLink = new Annotation("link", "value"); 183 SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Linked"); 184 spannableStringBuilder.setSpan(testLink, 0, 3, 0); 185 186 RichTextView view = new RichTextView(application); 187 view.setText(spannableStringBuilder); 188 189 assertTrue("TextView should be focusable since it contains spans", view.isFocusable()); 190 } 191 192 193 @SuppressLint("SetTextI18n") // It's OK. This is just a test. 194 @Test 195 public void testTextContainingNoLinksAreNotFocusable() { 196 RichTextView textView = new RichTextView(application); 197 textView.setText("Thou shall not be focusable!"); 198 199 assertFalse("TextView should not be focusable since it does not contain any span", 200 textView.isFocusable()); 201 } 202 203 204 // Based on the text contents of the text view, the "focusable" property of the element 205 // should also be automatically changed. 206 @SuppressLint("SetTextI18n") // It's OK. This is just a test. 207 @Test 208 public void testRichTextViewFocusChangesWithTextChange() { 209 RichTextView textView = new RichTextView(application); 210 textView.setText("Thou shall not be focusable!"); 211 212 assertFalse(textView.isFocusable()); 213 assertFalse(textView.isFocusableInTouchMode()); 214 215 SpannableStringBuilder spannableStringBuilder = 216 new SpannableStringBuilder("I am focusable"); 217 spannableStringBuilder.setSpan(new Annotation("link", "focus:on_me"), 0, 1, 0); 218 textView.setText(spannableStringBuilder); 219 assertTrue(textView.isFocusable()); 220 if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) { 221 assertTrue(textView.isFocusableInTouchMode()); 222 assertFalse(textView.getRevealOnFocusHint()); 223 } else { 224 assertFalse(textView.isFocusableInTouchMode()); 225 } 226 } 227 228 public static class TestContext extends ContextWrapper implements LinkSpan.OnClickListener { 229 230 public TestContext(Context base) { 231 super(base); 232 } 233 234 @Override 235 public void onClick(LinkSpan span) { 236 // Ignore. Can be verified using Mockito 237 } 238 } 239} 240