package org.robolectric.shadows; import android.app.Activity; import android.graphics.Typeface; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; import android.text.Selection; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.method.ArrowKeyMovementMethod; import android.text.method.MovementMethod; import android.text.method.PasswordTransformationMethod; import android.text.style.URLSpan; import android.text.util.Linkify; import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.widget.FrameLayout; import android.widget.TextView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.R; import org.robolectric.RuntimeEnvironment; import org.robolectric.TestRunners; import org.robolectric.shadow.api.Shadow; import org.robolectric.android.controller.ActivityController; import java.util.ArrayList; import java.util.List; import java.util.Random; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.robolectric.Robolectric.buildActivity; import static org.robolectric.Shadows.shadowOf; @RunWith(TestRunners.MultiApiSelfTest.class) public class ShadowTextViewTest { private static final String INITIAL_TEXT = "initial text"; private static final String NEW_TEXT = "new text"; private TextView textView; private ActivityController<Activity> activityController; @Before public void setUp() throws Exception { activityController = buildActivity(Activity.class); Activity activity = activityController.create().get(); textView = new TextView(activity); activity.setContentView(textView); activityController.start().resume().visible(); } @Test public void shouldTriggerTheImeListener() { TestOnEditorActionListener actionListener = new TestOnEditorActionListener(); textView.setOnEditorActionListener(actionListener); textView.onEditorAction(EditorInfo.IME_ACTION_GO); assertThat(actionListener.textView).isSameAs(textView); assertThat(actionListener.sentImeId).isEqualTo(EditorInfo.IME_ACTION_GO); } @Test public void shouldCreateGetterForEditorActionListener() { TestOnEditorActionListener actionListener = new TestOnEditorActionListener(); textView.setOnEditorActionListener(actionListener); assertThat(shadowOf(textView).getOnEditorActionListener()).isSameAs(actionListener); } @Test public void testGetUrls() throws Exception { textView.setAutoLinkMask(Linkify.ALL); textView.setText("here's some text http://google.com/\nblah\thttp://another.com/123?456 blah"); assertThat(urlStringsFrom(textView.getUrls())).isEqualTo(asList( "http://google.com", "http://another.com/123?456" )); } @Test public void testGetGravity() throws Exception { assertThat(textView.getGravity()).isNotEqualTo(Gravity.CENTER); textView.setGravity(Gravity.CENTER); assertThat(textView.getGravity()).isEqualTo(Gravity.CENTER); } @Test public void testMovementMethod() { MovementMethod movement = new ArrowKeyMovementMethod(); assertNull(textView.getMovementMethod()); textView.setMovementMethod(movement); assertThat(textView.getMovementMethod()).isSameAs(movement); } @Test public void testLinksClickable() { assertThat(textView.getLinksClickable()).isTrue(); textView.setLinksClickable(false); assertThat(textView.getLinksClickable()).isFalse(); textView.setLinksClickable(true); assertThat(textView.getLinksClickable()).isTrue(); } @Test public void testGetTextAppearanceId() throws Exception { textView.setTextAppearance(RuntimeEnvironment.application, android.R.style.TextAppearance_Small); assertThat(shadowOf(textView).getTextAppearanceId()).isEqualTo(android.R.style.TextAppearance_Small); } @Test public void shouldSetTextAndTextColorWhileInflatingXmlLayout() throws Exception { Activity activity = activityController.get(); activity.setContentView(R.layout.text_views); TextView black = (TextView) activity.findViewById(R.id.black_text_view); assertThat(black.getText().toString()).isEqualTo("Black Text"); assertThat(black.getCurrentTextColor()).isEqualTo(0xff000000); TextView white = (TextView) activity.findViewById(R.id.white_text_view); assertThat(white.getText().toString()).isEqualTo("White Text"); assertThat(white.getCurrentTextColor()).isEqualTo(activity.getResources().getColor(android.R.color.white)); TextView grey = (TextView) activity.findViewById(R.id.grey_text_view); assertThat(grey.getText().toString()).isEqualTo("Grey Text"); assertThat(grey.getCurrentTextColor()).isEqualTo(activity.getResources().getColor(R.color.grey42)); } @Test public void shouldSetHintAndHintColorWhileInflatingXmlLayout() throws Exception { Activity activity = activityController.get(); activity.setContentView(R.layout.text_views_hints); TextView black = (TextView) activity.findViewById(R.id.black_text_view_hint); assertThat(black.getHint().toString()).isEqualTo("Black Hint"); assertThat(black.getCurrentHintTextColor()).isEqualTo(0xff000000); TextView white = (TextView) activity.findViewById(R.id.white_text_view_hint); assertThat(white.getHint().toString()).isEqualTo("White Hint"); assertThat(white.getCurrentHintTextColor()).isEqualTo(activity.getResources().getColor(android.R.color.white)); TextView grey = (TextView) activity.findViewById(R.id.grey_text_view_hint); assertThat(grey.getHint().toString()).isEqualTo("Grey Hint"); assertThat(grey.getCurrentHintTextColor()).isEqualTo(activity.getResources().getColor(R.color.grey42)); } @Test public void shouldNotHaveTransformationMethodByDefault() { assertThat(textView.getTransformationMethod()).isNull(); } @Test public void shouldAllowSettingATransformationMethod() { textView.setTransformationMethod(PasswordTransformationMethod.getInstance()); assertThat(textView.getTransformationMethod()).isInstanceOf(PasswordTransformationMethod.class); } @Test public void testGetInputType() throws Exception { assertThat(textView.getInputType()).isNotEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); assertThat(textView.getInputType()).isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } @Test public void givenATextViewWithATextWatcherAdded_WhenSettingTextWithTextResourceId_ShouldNotifyTextWatcher() { MockTextWatcher mockTextWatcher = new MockTextWatcher(); textView.addTextChangedListener(mockTextWatcher); textView.setText(R.string.hello); assertEachTextWatcherEventWasInvoked(mockTextWatcher); } @Test public void givenATextViewWithATextWatcherAdded_WhenSettingTextWithCharSequence_ShouldNotifyTextWatcher() { MockTextWatcher mockTextWatcher = new MockTextWatcher(); textView.addTextChangedListener(mockTextWatcher); textView.setText("text"); assertEachTextWatcherEventWasInvoked(mockTextWatcher); } @Test public void givenATextViewWithATextWatcherAdded_WhenSettingNullText_ShouldNotifyTextWatcher() { MockTextWatcher mockTextWatcher = new MockTextWatcher(); textView.addTextChangedListener(mockTextWatcher); textView.setText(null); assertEachTextWatcherEventWasInvoked(mockTextWatcher); } @Test public void givenATextViewWithMultipleTextWatchersAdded_WhenSettingText_ShouldNotifyEachTextWatcher() { List<MockTextWatcher> mockTextWatchers = anyNumberOfTextWatchers(); for (MockTextWatcher textWatcher : mockTextWatchers) { textView.addTextChangedListener(textWatcher); } textView.setText("text"); for (MockTextWatcher textWatcher : mockTextWatchers) { assertEachTextWatcherEventWasInvoked(textWatcher); } } @Test public void whenSettingText_ShouldFireBeforeTextChangedWithCorrectArguments() { textView.setText(INITIAL_TEXT); TextWatcher mockTextWatcher = mock(TextWatcher.class); textView.addTextChangedListener(mockTextWatcher); textView.setText(NEW_TEXT); verify(mockTextWatcher).beforeTextChanged(INITIAL_TEXT, 0, INITIAL_TEXT.length(), NEW_TEXT.length()); } @Test public void whenSettingText_ShouldFireOnTextChangedWithCorrectArguments() { textView.setText(INITIAL_TEXT); TextWatcher mockTextWatcher = mock(TextWatcher.class); textView.addTextChangedListener(mockTextWatcher); textView.setText(NEW_TEXT); ArgumentCaptor<SpannableStringBuilder> builderCaptor = ArgumentCaptor.forClass(SpannableStringBuilder.class); verify(mockTextWatcher).onTextChanged(builderCaptor.capture(), eq(0), eq(INITIAL_TEXT.length()), eq(NEW_TEXT.length())); assertThat(builderCaptor.getValue().toString()).isEqualTo(NEW_TEXT); } @Test public void whenSettingText_ShouldFireAfterTextChangedWithCorrectArgument() { MockTextWatcher mockTextWatcher = new MockTextWatcher(); textView.addTextChangedListener(mockTextWatcher); textView.setText(NEW_TEXT); assertThat(mockTextWatcher.afterTextChangeArgument.toString()).isEqualTo(NEW_TEXT); } @Test public void whenAppendingText_ShouldAppendNewTextAfterOldOne() { textView.setText(INITIAL_TEXT); textView.append(NEW_TEXT); assertThat(textView.getText().toString()).isEqualTo(INITIAL_TEXT + NEW_TEXT); } @Test public void whenAppendingText_ShouldFireBeforeTextChangedWithCorrectArguments() { textView.setText(INITIAL_TEXT); TextWatcher mockTextWatcher = mock(TextWatcher.class); textView.addTextChangedListener(mockTextWatcher); textView.append(NEW_TEXT); verify(mockTextWatcher).beforeTextChanged(eq(INITIAL_TEXT), eq(0), eq(INITIAL_TEXT.length()), eq(INITIAL_TEXT.length())); } @Test public void whenAppendingText_ShouldFireOnTextChangedWithCorrectArguments() { textView.setText(INITIAL_TEXT); TextWatcher mockTextWatcher = mock(TextWatcher.class); textView.addTextChangedListener(mockTextWatcher); textView.append(NEW_TEXT); ArgumentCaptor<SpannableStringBuilder> builderCaptor = ArgumentCaptor.forClass(SpannableStringBuilder.class); verify(mockTextWatcher).onTextChanged(builderCaptor.capture(), eq(0), eq(INITIAL_TEXT.length()), eq(INITIAL_TEXT.length())); assertThat(builderCaptor.getValue().toString()).isEqualTo(INITIAL_TEXT + NEW_TEXT); } @Test public void whenAppendingText_ShouldFireAfterTextChangedWithCorrectArgument() { textView.setText(INITIAL_TEXT); MockTextWatcher mockTextWatcher = new MockTextWatcher(); textView.addTextChangedListener(mockTextWatcher); textView.append(NEW_TEXT); assertThat(mockTextWatcher.afterTextChangeArgument.toString()).isEqualTo(INITIAL_TEXT + NEW_TEXT); } @Test public void removeTextChangedListener_shouldRemoveTheListener() throws Exception { MockTextWatcher watcher = new MockTextWatcher(); textView.addTextChangedListener(watcher); assertTrue(shadowOf(textView).getWatchers().contains(watcher)); textView.removeTextChangedListener(watcher); assertFalse(shadowOf(textView).getWatchers().contains(watcher)); } @Test public void getPaint_returnsMeasureTextEnabledObject() throws Exception { assertThat(textView.getPaint().measureText("12345")).isEqualTo(5f); } @Test public void append_whenSelectionIsAtTheEnd_shouldKeepSelectionAtTheEnd() throws Exception { textView.setText("1", TextView.BufferType.EDITABLE); Selection.setSelection(textView.getEditableText(), 0, 0); textView.append("2"); assertEquals(0, textView.getSelectionEnd()); assertEquals(0, textView.getSelectionStart()); Selection.setSelection(textView.getEditableText(), 2, 2); textView.append("3"); assertEquals(3, textView.getSelectionEnd()); assertEquals(3, textView.getSelectionStart()); } @Test public void append_whenSelectionReachesToEnd_shouldExtendSelectionToTheEnd() throws Exception { textView.setText("12", TextView.BufferType.EDITABLE); Selection.setSelection(textView.getEditableText(), 0, 2); textView.append("3"); assertEquals(3, textView.getSelectionEnd()); assertEquals(0, textView.getSelectionStart()); } @Test public void testSetCompountDrawablesWithIntrinsicBounds_int_shouldCreateDrawablesWithResourceIds() throws Exception { textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.an_image, R.drawable.an_other_image, R.drawable.third_image, R.drawable.fourth_image); assertEquals(R.drawable.an_image, shadowOf(textView.getCompoundDrawables()[0]).getCreatedFromResId()); assertEquals(R.drawable.an_other_image, shadowOf(textView.getCompoundDrawables()[1]).getCreatedFromResId()); assertEquals(R.drawable.third_image, shadowOf(textView.getCompoundDrawables()[2]).getCreatedFromResId()); assertEquals(R.drawable.fourth_image, shadowOf(textView.getCompoundDrawables()[3]).getCreatedFromResId()); } @Test public void testSetCompountDrawablesWithIntrinsicBounds_int_shouldNotCreateDrawablesForZero() throws Exception { textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); assertNull(textView.getCompoundDrawables()[0]); assertNull(textView.getCompoundDrawables()[1]); assertNull(textView.getCompoundDrawables()[2]); assertNull(textView.getCompoundDrawables()[3]); } @Test public void canSetAndGetTypeface() throws Exception { Typeface typeface = Shadow.newInstanceOf(Typeface.class); textView.setTypeface(typeface); assertSame(typeface, textView.getTypeface()); } @Test public void onTouchEvent_shouldCallMovementMethodOnTouchEventWithSetMotionEvent() throws Exception { TestMovementMethod testMovementMethod = new TestMovementMethod(); textView.setMovementMethod(testMovementMethod); textView.setLayoutParams(new FrameLayout.LayoutParams(100, 100)); textView.measure(100, 100); MotionEvent event = MotionEvent.obtain(0, 0, 0, 0, 0, 0); textView.dispatchTouchEvent(event); assertEquals(testMovementMethod.event, event); } @Test public void testGetError() { assertNull(textView.getError()); CharSequence error = "myError"; textView.setError(error); assertEquals(error, textView.getError()); } @Test public void canSetAndGetInputFilters() throws Exception { final InputFilter[] expectedFilters = new InputFilter[]{new InputFilter.LengthFilter(1)}; textView.setFilters(expectedFilters); assertThat(textView.getFilters()).isSameAs(expectedFilters); } @Test public void testHasSelectionReturnsTrue() { textView.setText("1", TextView.BufferType.SPANNABLE); textView.onTextContextMenuItem(android.R.id.selectAll); assertTrue(textView.hasSelection()); } @Test public void testHasSelectionReturnsFalse() { textView.setText("1", TextView.BufferType.SPANNABLE); assertFalse(textView.hasSelection()); } @Test public void whenSettingTextToNull_WatchersSeeEmptyString() { TextWatcher mockTextWatcher = mock(TextWatcher.class); textView.addTextChangedListener(mockTextWatcher); textView.setText(null); ArgumentCaptor<SpannableStringBuilder> builderCaptor = ArgumentCaptor.forClass(SpannableStringBuilder.class); verify(mockTextWatcher).onTextChanged(builderCaptor.capture(), eq(0), eq(0), eq(0)); assertThat(builderCaptor.getValue().toString()).isEmpty(); } @Test public void getPaint_returnsNonNull() { assertNotNull(textView.getPaint()); } @Test public void testNoArgAppend() { textView.setText("a"); textView.append("b"); assertThat(textView.getText().toString()).isEqualTo("ab"); } @Test public void setTextSize_shouldHandleDips() throws Exception { shadowOf(RuntimeEnvironment.application.getResources()).setDensity(1.5f); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10); assertThat(textView.getTextSize()).isEqualTo(15f); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); assertThat(textView.getTextSize()).isEqualTo(30f); } @Test public void setTextSize_shouldHandleSp() throws Exception { textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); assertThat(textView.getTextSize()).isEqualTo(10f); shadowOf(RuntimeEnvironment.application.getResources()).setScaledDensity(1.5f); textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); assertThat(textView.getTextSize()).isEqualTo(15f); } @Test public void setTextSize_shouldHandlePixels() throws Exception { shadowOf(RuntimeEnvironment.application.getResources()).setDensity(1.5f); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10); assertThat(textView.getTextSize()).isEqualTo(10f); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 20); assertThat(textView.getTextSize()).isEqualTo(20f); } @Test public void getPaintFlagsAndSetPaintFlags_shouldWork() { assertThat(textView.getPaintFlags()).isEqualTo(0); textView.setPaintFlags(100); assertThat(textView.getPaintFlags()).isEqualTo(100); } @Test public void setCompoundDrawablesWithIntrinsicBounds_setsValues() { textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.l0_red, R.drawable.l1_orange, R.drawable.l2_yellow, R.drawable.l3_green); assertThat(shadowOf(textView).getCompoundDrawablesWithIntrinsicBoundsLeft()).isEqualTo(R.drawable.l0_red); assertThat(shadowOf(textView).getCompoundDrawablesWithIntrinsicBoundsTop()).isEqualTo(R.drawable.l1_orange); assertThat(shadowOf(textView).getCompoundDrawablesWithIntrinsicBoundsRight()).isEqualTo(R.drawable.l2_yellow); assertThat(shadowOf(textView).getCompoundDrawablesWithIntrinsicBoundsBottom()).isEqualTo(R.drawable.l3_green); } private List<MockTextWatcher> anyNumberOfTextWatchers() { List<MockTextWatcher> mockTextWatchers = new ArrayList<>(); int numberBetweenOneAndTen = new Random().nextInt(10) + 1; for (int i = 0; i < numberBetweenOneAndTen; i++) { mockTextWatchers.add(new MockTextWatcher()); } return mockTextWatchers; } private void assertEachTextWatcherEventWasInvoked(MockTextWatcher mockTextWatcher) { assertTrue("Expected each TextWatcher event to have been invoked once", mockTextWatcher.methodsCalled.size() == 3); assertThat(mockTextWatcher.methodsCalled.get(0)).isEqualTo("beforeTextChanged"); assertThat(mockTextWatcher.methodsCalled.get(1)).isEqualTo("onTextChanged"); assertThat(mockTextWatcher.methodsCalled.get(2)).isEqualTo("afterTextChanged"); } private List<String> urlStringsFrom(URLSpan[] urlSpans) { List<String> urls = new ArrayList<>(); for (URLSpan urlSpan : urlSpans) { urls.add(urlSpan.getURL()); } return urls; } private static class TestOnEditorActionListener implements TextView.OnEditorActionListener { private TextView textView; private int sentImeId; @Override public boolean onEditorAction(TextView textView, int sentImeId, KeyEvent keyEvent) { this.textView = textView; this.sentImeId = sentImeId; return false; } } private static class MockTextWatcher implements TextWatcher { List<String> methodsCalled = new ArrayList<>(); Editable afterTextChangeArgument; @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { methodsCalled.add("beforeTextChanged"); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { methodsCalled.add("onTextChanged"); } @Override public void afterTextChanged(Editable s) { methodsCalled.add("afterTextChanged"); afterTextChangeArgument = s; } } private static class TestMovementMethod implements MovementMethod { public MotionEvent event; public boolean touchEventWasCalled; @Override public void initialize(TextView widget, Spannable text) { } @Override public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) { return false; } @Override public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) { return false; } @Override public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { return false; } @Override public void onTakeFocus(TextView widget, Spannable text, int direction) { } @Override public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { return false; } @Override public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) { this.event = event; touchEventWasCalled = true; return false; } @Override public boolean canSelectArbitrarily() { return false; } @Override public boolean onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event) { return false; } } }