1package com.xtremelabs.robolectric.shadows;
2
3import android.app.Activity;
4import android.graphics.Typeface;
5import android.text.*;
6import android.text.method.ArrowKeyMovementMethod;
7import android.text.method.MovementMethod;
8import android.text.style.URLSpan;
9import android.view.Gravity;
10import android.view.KeyEvent;
11import android.view.MotionEvent;
12import android.view.inputmethod.EditorInfo;
13import android.widget.TextView;
14import com.xtremelabs.robolectric.R;
15import com.xtremelabs.robolectric.Robolectric;
16import com.xtremelabs.robolectric.WithTestDefaultsRunner;
17import junit.framework.Assert;
18import org.hamcrest.CoreMatchers;
19import org.junit.Before;
20import org.junit.Test;
21import org.junit.runner.RunWith;
22
23import java.util.ArrayList;
24import java.util.List;
25import java.util.Random;
26
27import static com.xtremelabs.robolectric.Robolectric.shadowOf;
28import static java.util.Arrays.asList;
29import static junit.framework.Assert.assertFalse;
30import static org.hamcrest.CoreMatchers.*;
31import static org.junit.Assert.*;
32import static org.mockito.Mockito.mock;
33import static org.mockito.Mockito.verify;
34
35@RunWith(WithTestDefaultsRunner.class)
36public 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