1 package com.xtremelabs.robolectric.shadows; 2 3 import android.app.Activity; 4 import android.graphics.Typeface; 5 import android.text.*; 6 import android.text.method.ArrowKeyMovementMethod; 7 import android.text.method.MovementMethod; 8 import android.text.style.URLSpan; 9 import android.view.Gravity; 10 import android.view.KeyEvent; 11 import android.view.MotionEvent; 12 import android.view.inputmethod.EditorInfo; 13 import android.widget.TextView; 14 import com.xtremelabs.robolectric.R; 15 import com.xtremelabs.robolectric.Robolectric; 16 import com.xtremelabs.robolectric.WithTestDefaultsRunner; 17 import junit.framework.Assert; 18 import org.hamcrest.CoreMatchers; 19 import org.junit.Before; 20 import org.junit.Test; 21 import org.junit.runner.RunWith; 22 23 import java.util.ArrayList; 24 import java.util.List; 25 import java.util.Random; 26 27 import static com.xtremelabs.robolectric.Robolectric.shadowOf; 28 import static java.util.Arrays.asList; 29 import static junit.framework.Assert.assertFalse; 30 import static org.hamcrest.CoreMatchers.*; 31 import static org.junit.Assert.*; 32 import static org.mockito.Mockito.mock; 33 import static org.mockito.Mockito.verify; 34 35 @RunWith(WithTestDefaultsRunner.class) 36 public class TextViewTest { 37 38 private static final String INITIAL_TEXT = "initial text"; 39 private static final String NEW_TEXT = "new text"; 40 private TextView textView; 41 42 @Before 43 public void setUp() throws Exception { 44 textView = new TextView(new Activity()); 45 } 46 47 @Test 48 public void shouldTriggerTheImeListener() { 49 TextView textView = new TextView(null); 50 TestOnEditorActionListener actionListener = new TestOnEditorActionListener(); 51 textView.setOnEditorActionListener(actionListener); 52 53 shadowOf(textView).triggerEditorAction(EditorInfo.IME_ACTION_GO); 54 55 assertThat(actionListener.textView, is(textView)); 56 assertThat(actionListener.sentImeId, equalTo(EditorInfo.IME_ACTION_GO)); 57 } 58 59 @Test 60 public void testGetUrls() throws Exception { 61 textView.setText("here's some text http://google.com/\nblah\thttp://another.com/123?456 blah"); 62 63 assertThat(urlStringsFrom(textView.getUrls()), equalTo(asList( 64 "http://google.com/", 65 "http://another.com/123?456" 66 ))); 67 } 68 69 @Test 70 public void testGetGravity() throws Exception { 71 assertThat(textView.getGravity(), not(equalTo(Gravity.CENTER))); 72 textView.setGravity(Gravity.CENTER); 73 assertThat(textView.getGravity(), equalTo(Gravity.CENTER)); 74 } 75 76 @Test 77 public void testMovementMethod() { 78 MovementMethod movement = new ArrowKeyMovementMethod(); 79 80 assertNull(textView.getMovementMethod()); 81 textView.setMovementMethod(movement); 82 assertThat(textView.getMovementMethod(), sameInstance(movement)); 83 } 84 85 @Test 86 public void testLinksClickable() { 87 assertThat(textView.getLinksClickable(), equalTo(false)); 88 89 textView.setLinksClickable(true); 90 assertThat(textView.getLinksClickable(), equalTo(true)); 91 92 textView.setLinksClickable(false); 93 assertThat(textView.getLinksClickable(), equalTo(false)); 94 } 95 96 @Test 97 public void testGetTextAppearanceId() throws Exception { 98 TextView textView = new TextView(null); 99 textView.setTextAppearance(null, 5); 100 101 assertThat(shadowOf(textView).getTextAppearanceId(), equalTo(5)); 102 } 103 104 @Test 105 public void shouldSetTextAndTextColorWhileInflatingXmlLayout() throws Exception { 106 Activity activity = new Activity(); 107 activity.setContentView(R.layout.text_views); 108 109 TextView black = (TextView) activity.findViewById(R.id.black_text_view); 110 assertThat(black.getText().toString(), equalTo("Black Text")); 111 assertThat(shadowOf(black).getTextColorHexValue(), equalTo(0)); 112 113 TextView white = (TextView) activity.findViewById(R.id.white_text_view); 114 assertThat(white.getText().toString(), equalTo("White Text")); 115 assertThat(shadowOf(white).getTextColorHexValue(), equalTo(activity.getResources().getColor(android.R.color.white))); 116 117 TextView grey = (TextView) activity.findViewById(R.id.grey_text_view); 118 assertThat(grey.getText().toString(), equalTo("Grey Text")); 119 assertThat(shadowOf(grey).getTextColorHexValue(), equalTo(activity.getResources().getColor(R.color.grey42))); 120 } 121 122 @Test 123 public void shouldSetHintAndHintColorWhileInflatingXmlLayout() throws Exception { 124 Activity activity = new Activity(); 125 activity.setContentView(R.layout.text_views_hints); 126 127 TextView black = (TextView) activity.findViewById(R.id.black_text_view_hint); 128 assertThat(black.getHint().toString(), equalTo("Black Hint")); 129 assertThat(shadowOf(black).getHintColorHexValue(), equalTo(0)); 130 131 TextView white = (TextView) activity.findViewById(R.id.white_text_view_hint); 132 assertThat(white.getHint().toString(), equalTo("White Hint")); 133 assertThat(shadowOf(white).getHintColorHexValue(), equalTo(activity.getResources().getColor(android.R.color.white))); 134 135 TextView grey = (TextView) activity.findViewById(R.id.grey_text_view_hint); 136 assertThat(grey.getHint().toString(), equalTo("Grey Hint")); 137 assertThat(shadowOf(grey).getHintColorHexValue(), equalTo(activity.getResources().getColor(R.color.grey42))); 138 } 139 140 @Test 141 public void shouldNotHaveTransformationMethodByDefault() { 142 ShadowTextView view = new ShadowTextView(); 143 assertThat(view.getTransformationMethod(), is(CoreMatchers.<Object>nullValue())); 144 } 145 146 @Test 147 public void shouldAllowSettingATransformationMethod() { 148 ShadowTextView view = new ShadowTextView(); 149 view.setTransformationMethod(new ShadowPasswordTransformationMethod()); 150 assertEquals(view.getTransformationMethod().getClass(), ShadowPasswordTransformationMethod.class); 151 } 152 153 @Test 154 public void testGetInputType() throws Exception { 155 assertThat(textView.getInputType(), not(equalTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD))); 156 textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 157 assertThat(textView.getInputType(), equalTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)); 158 } 159 160 @Test 161 public void givenATextViewWithATextWatcherAdded_WhenSettingTextWithTextResourceId_ShouldNotifyTextWatcher() { 162 MockTextWatcher mockTextWatcher = new MockTextWatcher(); 163 textView.addTextChangedListener(mockTextWatcher); 164 165 textView.setText(R.string.hello); 166 167 assertEachTextWatcherEventWasInvoked(mockTextWatcher); 168 } 169 170 @Test 171 public void givenATextViewWithATextWatcherAdded_WhenSettingTextWithCharSequence_ShouldNotifyTextWatcher() { 172 MockTextWatcher mockTextWatcher = new MockTextWatcher(); 173 textView.addTextChangedListener(mockTextWatcher); 174 175 textView.setText("text"); 176 177 assertEachTextWatcherEventWasInvoked(mockTextWatcher); 178 } 179 180 @Test 181 public void givenATextViewWithATextWatcherAdded_WhenSettingNullText_ShouldNotifyTextWatcher() { 182 MockTextWatcher mockTextWatcher = new MockTextWatcher(); 183 textView.addTextChangedListener(mockTextWatcher); 184 185 textView.setText(null); 186 187 assertEachTextWatcherEventWasInvoked(mockTextWatcher); 188 } 189 190 @Test 191 public void givenATextViewWithMultipleTextWatchersAdded_WhenSettingText_ShouldNotifyEachTextWatcher() { 192 List<MockTextWatcher> mockTextWatchers = anyNumberOfTextWatchers(); 193 for (MockTextWatcher textWatcher : mockTextWatchers) { 194 textView.addTextChangedListener(textWatcher); 195 } 196 197 textView.setText("text"); 198 199 for (MockTextWatcher textWatcher : mockTextWatchers) { 200 assertEachTextWatcherEventWasInvoked(textWatcher); 201 } 202 } 203 204 @Test 205 public void whenSettingText_ShouldFireBeforeTextChangedWithCorrectArguments() { 206 textView.setText(INITIAL_TEXT); 207 TextWatcher mockTextWatcher = mock(TextWatcher.class); 208 textView.addTextChangedListener(mockTextWatcher); 209 210 textView.setText(NEW_TEXT); 211 212 verify(mockTextWatcher).beforeTextChanged(INITIAL_TEXT, 0, INITIAL_TEXT.length(), NEW_TEXT.length()); 213 } 214 215 @Test 216 public void whenSettingText_ShouldFireOnTextChangedWithCorrectArguments() { 217 textView.setText(INITIAL_TEXT); 218 TextWatcher mockTextWatcher = mock(TextWatcher.class); 219 textView.addTextChangedListener(mockTextWatcher); 220 221 textView.setText(NEW_TEXT); 222 223 verify(mockTextWatcher).onTextChanged(NEW_TEXT, 0, INITIAL_TEXT.length(), NEW_TEXT.length()); 224 } 225 226 @Test 227 public void whenSettingText_ShouldFireAfterTextChangedWithCorrectArgument() { 228 MockTextWatcher mockTextWatcher = new MockTextWatcher(); 229 textView.addTextChangedListener(mockTextWatcher); 230 231 textView.setText(NEW_TEXT); 232 233 assertThat(mockTextWatcher.afterTextChangeArgument.toString(), equalTo(NEW_TEXT)); 234 } 235 236 @Test 237 public void whenAppendingText_ShouldAppendNewTextAfterOldOne() { 238 textView.setText(INITIAL_TEXT); 239 textView.append(NEW_TEXT); 240 241 assertEquals(INITIAL_TEXT + NEW_TEXT, textView.getText()); 242 } 243 244 @Test 245 public void whenAppendingText_ShouldFireBeforeTextChangedWithCorrectArguments() { 246 textView.setText(INITIAL_TEXT); 247 TextWatcher mockTextWatcher = mock(TextWatcher.class); 248 textView.addTextChangedListener(mockTextWatcher); 249 250 textView.append(NEW_TEXT); 251 252 verify(mockTextWatcher).beforeTextChanged(INITIAL_TEXT, 0, INITIAL_TEXT.length(), INITIAL_TEXT.length() + NEW_TEXT.length()); 253 } 254 255 @Test 256 public void whenAppendingText_ShouldFireOnTextChangedWithCorrectArguments() { 257 textView.setText(INITIAL_TEXT); 258 TextWatcher mockTextWatcher = mock(TextWatcher.class); 259 textView.addTextChangedListener(mockTextWatcher); 260 261 textView.append(NEW_TEXT); 262 263 verify(mockTextWatcher).onTextChanged(INITIAL_TEXT + NEW_TEXT, 0, INITIAL_TEXT.length(), INITIAL_TEXT.length() + NEW_TEXT.length()); 264 } 265 266 @Test 267 public void whenAppendingText_ShouldFireAfterTextChangedWithCorrectArgument() { 268 textView.setText(INITIAL_TEXT); 269 MockTextWatcher mockTextWatcher = new MockTextWatcher(); 270 textView.addTextChangedListener(mockTextWatcher); 271 272 textView.append(NEW_TEXT); 273 274 assertThat(mockTextWatcher.afterTextChangeArgument.toString(), equalTo(INITIAL_TEXT + NEW_TEXT)); 275 } 276 277 @Test 278 public void removeTextChangedListener_shouldRemoveTheListener() throws Exception { 279 MockTextWatcher watcher = new MockTextWatcher(); 280 textView.addTextChangedListener(watcher); 281 assertTrue(shadowOf(textView).getWatchers().contains(watcher)); 282 283 textView.removeTextChangedListener(watcher); 284 assertFalse(shadowOf(textView).getWatchers().contains(watcher)); 285 } 286 287 @Test 288 public void getPaint_returnsMeasureTextEnabledObject() throws Exception { 289 assertThat(textView.getPaint().measureText("12345"), equalTo(5f)); 290 } 291 292 @Test 293 public void append_whenSelectionIsAtTheEnd_shouldKeepSelectionAtTheEnd() throws Exception { 294 textView.setText("1"); 295 shadowOf(textView).setSelection(0, 0); 296 textView.append("2"); 297 assertEquals(0, textView.getSelectionEnd()); 298 assertEquals(0, textView.getSelectionStart()); 299 300 shadowOf(textView).setSelection(2, 2); 301 textView.append("3"); 302 assertEquals(3, textView.getSelectionEnd()); 303 assertEquals(3, textView.getSelectionStart()); 304 } 305 306 @Test 307 public void append_whenSelectionReachesToEnd_shouldExtendSelectionToTheEnd() throws Exception { 308 textView.setText("12"); 309 shadowOf(textView).setSelection(0, 2); 310 textView.append("3"); 311 assertEquals(3, textView.getSelectionEnd()); 312 assertEquals(0, textView.getSelectionStart()); 313 } 314 315 @Test 316 public void testSetCompountDrawablesWithIntrinsicBounds_int_shouldCreateDrawablesWithResourceIds() throws Exception { 317 textView.setCompoundDrawablesWithIntrinsicBounds(6, 7, 8, 9); 318 319 Assert.assertEquals(6, shadowOf(textView.getCompoundDrawables()[0]).getLoadedFromResourceId()); 320 Assert.assertEquals(7, shadowOf(textView.getCompoundDrawables()[1]).getLoadedFromResourceId()); 321 Assert.assertEquals(8, shadowOf(textView.getCompoundDrawables()[2]).getLoadedFromResourceId()); 322 Assert.assertEquals(9, shadowOf(textView.getCompoundDrawables()[3]).getLoadedFromResourceId()); 323 } 324 325 @Test 326 public void testSetCompountDrawablesWithIntrinsicBounds_int_shouldNotCreateDrawablesForZero() throws Exception { 327 textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 328 329 Assert.assertNull(textView.getCompoundDrawables()[0]); 330 Assert.assertNull(textView.getCompoundDrawables()[1]); 331 Assert.assertNull(textView.getCompoundDrawables()[2]); 332 Assert.assertNull(textView.getCompoundDrawables()[3]); 333 } 334 335 @Test 336 public void canSetAndGetTypeface() throws Exception { 337 Typeface typeface = Robolectric.newInstanceOf(Typeface.class); 338 textView.setTypeface(typeface); 339 Assert.assertEquals(typeface, textView.getTypeface()); 340 } 341 342 @Test 343 public void onTouchEvent_shouldCallMovementMethodOnTouchEventWithSetMotionEvent() throws Exception { 344 TestMovementMethod testMovementMethod = new TestMovementMethod(); 345 346 textView.setMovementMethod(testMovementMethod); 347 MotionEvent event = MotionEvent.obtain(0, 0, 0, 0, 0, 0); 348 textView.dispatchTouchEvent(event); 349 350 assertEquals(testMovementMethod.event, event); 351 } 352 353 @Test 354 public void canSetAndGetLayout() throws Exception { 355 StaticLayout layout = new StaticLayout(null, null, 0, null, 0, 0, true); 356 shadowOf(textView).setLayout(layout); 357 assertEquals(textView.getLayout(), layout); 358 } 359 360 @Test 361 public void testGetError() { 362 assertNull(textView.getError()); 363 CharSequence error = "myError"; 364 textView.setError(error); 365 assertEquals(error, textView.getError()); 366 } 367 368 private List<MockTextWatcher> anyNumberOfTextWatchers() { 369 List<MockTextWatcher> mockTextWatchers = new ArrayList<MockTextWatcher>(); 370 int numberBetweenOneAndTen = new Random().nextInt(10) + 1; 371 for (int i = 0; i < numberBetweenOneAndTen; i++) { 372 mockTextWatchers.add(new MockTextWatcher()); 373 } 374 return mockTextWatchers; 375 } 376 377 private void assertEachTextWatcherEventWasInvoked(MockTextWatcher mockTextWatcher) { 378 assertTrue("Expected each TextWatcher event to have been invoked once", mockTextWatcher.methodsCalled.size() == 3); 379 380 assertThat(mockTextWatcher.methodsCalled.get(0), equalTo("beforeTextChanged")); 381 assertThat(mockTextWatcher.methodsCalled.get(1), equalTo("onTextChanged")); 382 assertThat(mockTextWatcher.methodsCalled.get(2), equalTo("afterTextChanged")); 383 } 384 385 private List<String> urlStringsFrom(URLSpan[] urlSpans) { 386 List<String> urls = new ArrayList<String>(); 387 for (URLSpan urlSpan : urlSpans) { 388 urls.add(urlSpan.getURL()); 389 } 390 return urls; 391 } 392 393 private static class TestOnEditorActionListener implements TextView.OnEditorActionListener { 394 private TextView textView; 395 private int sentImeId; 396 397 @Override 398 public boolean onEditorAction(TextView textView, int sentImeId, KeyEvent keyEvent) { 399 this.textView = textView; 400 this.sentImeId = sentImeId; 401 return false; 402 } 403 } 404 405 private static class MockTextWatcher implements TextWatcher { 406 407 List<String> methodsCalled = new ArrayList<String>(); 408 Editable afterTextChangeArgument; 409 410 @Override 411 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 412 methodsCalled.add("beforeTextChanged"); 413 } 414 415 @Override 416 public void onTextChanged(CharSequence s, int start, int before, int count) { 417 methodsCalled.add("onTextChanged"); 418 } 419 420 @Override 421 public void afterTextChanged(Editable s) { 422 methodsCalled.add("afterTextChanged"); 423 afterTextChangeArgument = s; 424 } 425 426 } 427 428 private static class TestMovementMethod implements MovementMethod { 429 public MotionEvent event; 430 public boolean touchEventWasCalled; 431 432 @Override 433 public void initialize(TextView widget, Spannable text) { 434 } 435 436 @Override 437 public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) { 438 return false; 439 } 440 441 @Override 442 public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) { 443 return false; 444 } 445 446 @Override 447 public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { 448 return false; 449 } 450 451 @Override 452 public void onTakeFocus(TextView widget, Spannable text, int direction) { 453 } 454 455 @Override 456 public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { 457 return false; 458 } 459 460 @Override 461 public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) { 462 this.event = event; 463 touchEventWasCalled = true; 464 return false; 465 } 466 467 @Override 468 public boolean canSelectArbitrarily() { 469 return false; 470 } 471 472 @Override 473 public boolean onGenericMotionEvent(TextView widget, Spannable text, 474 MotionEvent event) { 475 return false; 476 } 477 } 478 } 479