1package com.xtremelabs.robolectric.shadows;
2
3import android.app.Activity;
4import android.content.Context;
5import android.graphics.Point;
6import android.graphics.Rect;
7import android.graphics.drawable.ColorDrawable;
8import android.view.*;
9import android.view.View.MeasureSpec;
10import android.view.animation.Animation;
11import android.widget.LinearLayout;
12import com.xtremelabs.robolectric.R;
13import com.xtremelabs.robolectric.Robolectric;
14import com.xtremelabs.robolectric.WithTestDefaultsRunner;
15import com.xtremelabs.robolectric.util.*;
16import org.junit.Before;
17import org.junit.Test;
18import org.junit.runner.RunWith;
19
20import static com.xtremelabs.robolectric.Robolectric.shadowOf;
21import static junit.framework.Assert.assertEquals;
22import static org.hamcrest.CoreMatchers.*;
23import static org.hamcrest.MatcherAssert.assertThat;
24import static org.junit.Assert.*;
25
26@RunWith(WithTestDefaultsRunner.class)
27public class ViewTest {
28    private View view;
29
30    @Before
31    public void setUp() throws Exception {
32        view = new View(new Activity());
33    }
34
35    @Test
36    public void testHasEmptyLayoutParams() throws Exception {
37        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
38        assertThat(layoutParams, notNullValue());
39    }
40
41    @Test
42    public void layout_shouldAffectWidthAndHeight() throws Exception {
43        assertThat(view.getWidth(), equalTo(0));
44        assertThat(view.getHeight(), equalTo(0));
45
46        view.layout(100, 200, 303, 404);
47        assertThat(view.getWidth(), equalTo(303 - 100));
48        assertThat(view.getHeight(), equalTo(404 - 200));
49    }
50
51    @Test
52    public void shouldFocus() throws Exception {
53        final Transcript transcript = new Transcript();
54
55        view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
56            @Override
57            public void onFocusChange(View v, boolean hasFocus) {
58                transcript.add(hasFocus ? "Gained focus" : "Lost focus");
59            }
60        });
61
62        assertFalse(view.isFocused());
63        assertFalse(view.hasFocus());
64        transcript.assertNoEventsSoFar();
65
66        view.requestFocus();
67        assertTrue(view.isFocused());
68        assertTrue(view.hasFocus());
69        transcript.assertEventsSoFar("Gained focus");
70
71        view.clearFocus();
72        assertFalse(view.isFocused());
73        assertFalse(view.hasFocus());
74        transcript.assertEventsSoFar("Lost focus");
75    }
76
77    @Test
78    public void shouldNotBeFocusableByDefault() throws Exception {
79        assertFalse(view.isFocusable());
80
81        view.setFocusable(true);
82        assertTrue(view.isFocusable());
83    }
84
85    @Test
86    public void shouldKnowIfThisOrAncestorsAreVisible() throws Exception {
87        assertTrue(shadowOf(view).derivedIsVisible());
88
89        ViewGroup grandParent = new LinearLayout(null);
90        ViewGroup parent = new LinearLayout(null);
91        grandParent.addView(parent);
92        parent.addView(view);
93
94        grandParent.setVisibility(View.GONE);
95
96        assertFalse(shadowOf(view).derivedIsVisible());
97    }
98
99    @Test
100    public void shouldInflateMergeRootedLayoutAndNotCreateReferentialLoops() throws Exception {
101        LinearLayout root = new LinearLayout(null);
102        root.inflate(new Activity(), R.layout.inner_merge, root);
103        for (int i = 0; i < root.getChildCount(); i++) {
104            View child = root.getChildAt(i);
105            assertNotSame(root, child);
106        }
107    }
108
109    @Test
110    public void performLongClick_shouldClickOnView() throws Exception {
111        TestOnLongClickListener clickListener = new TestOnLongClickListener();
112        view.setOnLongClickListener(clickListener);
113        shadowOf(view).performLongClick();
114
115        assertTrue(clickListener.clicked);
116    }
117
118    @Test
119    public void checkedClick_shouldClickOnView() throws Exception {
120        TestOnClickListener clickListener = new TestOnClickListener();
121        view.setOnClickListener(clickListener);
122        shadowOf(view).checkedPerformClick();
123
124        assertTrue(clickListener.clicked);
125    }
126
127    @Test(expected = RuntimeException.class)
128    public void checkedClick_shouldThrowIfViewIsNotVisible() throws Exception {
129        ViewGroup grandParent = new LinearLayout(null);
130        ViewGroup parent = new LinearLayout(null);
131        grandParent.addView(parent);
132        parent.addView(view);
133        grandParent.setVisibility(View.GONE);
134
135        shadowOf(view).checkedPerformClick();
136    }
137
138    @Test(expected = RuntimeException.class)
139    public void checkedClick_shouldThrowIfViewIsDisabled() throws Exception {
140        view.setEnabled(false);
141        shadowOf(view).checkedPerformClick();
142    }
143
144    @Test
145    public void getBackground_shouldReturnNullIfNoBackgroundHasBeenSet() throws Exception {
146        assertThat(view.getBackground(), nullValue());
147    }
148
149    @Test
150    public void shouldSetBackgroundColor() {
151        view.setBackgroundColor(R.color.android_red);
152        int intColor = view.getResources().getColor(R.color.android_red);
153
154        assertThat((ColorDrawable) view.getBackground(), equalTo(new ColorDrawable(intColor)));
155    }
156
157    @Test
158    public void shouldSetBackgroundResource() throws Exception {
159        view.setBackgroundResource(R.drawable.an_image);
160        assertThat(view.getBackground(), equalTo(view.getResources().getDrawable(R.drawable.an_image)));
161    }
162
163    @Test
164    public void shouldRecordBackgroundColor() {
165        int[] colors = {0, 1, 727};
166
167        for (int color : colors) {
168            view.setBackgroundColor(color);
169            assertThat(shadowOf(view).getBackgroundColor(), equalTo(color));
170        }
171    }
172
173    @Test
174    public void shouldPostActionsToTheMessageQueue() throws Exception {
175        Robolectric.pauseMainLooper();
176
177        TestRunnable runnable = new TestRunnable();
178        view.post(runnable);
179        assertFalse(runnable.wasRun);
180
181        Robolectric.unPauseMainLooper();
182        assertTrue(runnable.wasRun);
183    }
184
185    @Test
186    public void shouldPostInvalidateDelayed() throws Exception {
187        Robolectric.pauseMainLooper();
188
189        view.postInvalidateDelayed(100);
190        ShadowView shadowView = shadowOf(view);
191        assertFalse(shadowView.wasInvalidated());
192
193        Robolectric.unPauseMainLooper();
194        assertTrue(shadowView.wasInvalidated());
195    }
196
197    @Test
198    public void shouldPostActionsToTheMessageQueueWithDelay() throws Exception {
199        Robolectric.pauseMainLooper();
200
201        TestRunnable runnable = new TestRunnable();
202        view.postDelayed(runnable, 1);
203        assertFalse(runnable.wasRun);
204
205        Robolectric.getUiThreadScheduler().advanceBy(1);
206        assertTrue(runnable.wasRun);
207    }
208
209    @Test
210    public void shouldSupportAllConstructors() throws Exception {
211        new View(null);
212        new View(null, null);
213        new View(null, null, 0);
214    }
215
216    @Test
217    public void shouldSetAnimation() throws Exception {
218        Animation anim = new TestAnimation();
219        view.setAnimation(anim);
220        assertThat(view.getAnimation(), sameInstance(anim));
221    }
222
223    @Test
224    public void shouldStartAndClearAnimation() throws Exception {
225        Animation anim = new TestAnimation();
226        TestAnimationListener listener = new TestAnimationListener();
227        anim.setAnimationListener(listener);
228        assertThat(listener.wasStartCalled, equalTo(false));
229        assertThat(listener.wasRepeatCalled, equalTo(false));
230        assertThat(listener.wasEndCalled, equalTo(false));
231        view.startAnimation(anim);
232        assertThat(listener.wasStartCalled, equalTo(true));
233        assertThat(listener.wasRepeatCalled, equalTo(false));
234        assertThat(listener.wasEndCalled, equalTo(false));
235        view.clearAnimation();
236        assertThat(listener.wasStartCalled, equalTo(true));
237        assertThat(listener.wasRepeatCalled, equalTo(false));
238        assertThat(listener.wasEndCalled, equalTo(true));
239    }
240
241    @Test
242    public void shouldFindViewWithTag() {
243        view.setTag("tagged");
244        assertThat(view.findViewWithTag("tagged"), sameInstance(view));
245    }
246
247    @Test
248    public void shouldFindViewWithTag_whenViewOverridesGetTag() throws Exception {
249        View view = new View(Robolectric.application) {
250            @Override
251            public Object getTag() {
252                return "blarg";
253            }
254        };
255        assertThat(view.findViewWithTag("blarg"), sameInstance(view));
256    }
257
258    @Test
259    public void scrollTo_shouldStoreTheScrolledCoordinates() throws Exception {
260        view.scrollTo(1, 2);
261        assertThat(shadowOf(view).scrollToCoordinates, equalTo(new Point(1, 2)));
262    }
263
264    @Test
265    public void shouldScrollTo() throws Exception {
266        view.scrollTo(7, 6);
267
268        assertEquals(7, view.getScrollX());
269        assertEquals(6, view.getScrollY());
270    }
271
272    @Test
273    public void shouldGetScrollXAndY() {
274        assertEquals(0, view.getScrollX());
275        assertEquals(0, view.getScrollY());
276    }
277
278    @Test
279    public void getViewTreeObserver_shouldReturnTheSameObserverFromMultipleCalls() throws Exception {
280        ViewTreeObserver observer = view.getViewTreeObserver();
281        assertThat(observer, instanceOf(ViewTreeObserver.class));
282        assertThat(view.getViewTreeObserver(), sameInstance(observer));
283    }
284
285    @Test
286    public void dispatchTouchEvent_sendsMotionEventToOnTouchEvent() throws Exception {
287        TouchableView touchableView = new TouchableView(null);
288        MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 12f, 34f, 0);
289        touchableView.dispatchTouchEvent(event);
290        assertThat(touchableView.event, sameInstance(event));
291        view.dispatchTouchEvent(event);
292        assertThat(shadowOf(view).getLastTouchEvent(), sameInstance(event));
293    }
294
295    @Test
296    public void dispatchTouchEvent_listensToTrueFromListener() throws Exception {
297        view.setOnTouchListener(new View.OnTouchListener() {
298            @Override
299            public boolean onTouch(View view, MotionEvent motionEvent) {
300                return true;
301            }
302        });
303        MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 12f, 34f, 0);
304        view.dispatchTouchEvent(event);
305        assertThat(shadowOf(view).getLastTouchEvent(), nullValue());
306    }
307
308    @Test
309    public void dispatchTouchEvent_listensToFalseFromListener() throws Exception {
310        view.setOnTouchListener(new View.OnTouchListener() {
311            @Override
312            public boolean onTouch(View view, MotionEvent motionEvent) {
313                return false;
314            }
315        });
316        MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 12f, 34f, 0);
317        view.dispatchTouchEvent(event);
318        assertThat(shadowOf(view).getLastTouchEvent(), sameInstance(event));
319    }
320
321    @Test
322    public void dispatchFocusEvent_sendsFocusToOnFocusChanged() throws Exception {
323        FocusableView focusableView = new FocusableView(null);
324        focusableView.requestFocus();
325        assertThat(focusableView.onFocusChangedWasCalled, equalTo(true));
326    }
327
328    @Test
329    public void test_nextFocusDownId() throws Exception {
330        assertEquals(View.NO_ID, view.getNextFocusDownId());
331
332        view.setNextFocusDownId(R.id.icon);
333        assertEquals(R.id.icon, view.getNextFocusDownId());
334    }
335
336    @Test
337    public void dispatchOnAnimationEnd() throws Exception {
338        TestView view1 = new TestView(new Activity());
339        assertFalse(view1.onAnimationEndWasCalled);
340        shadowOf(view1).finishedAnimation();
341        assertTrue(view1.onAnimationEndWasCalled);
342    }
343
344    @Test
345    public void test_measuredDimension() {
346    	// View does not provide its own onMeasure implementation
347    	TestView view1 = new TestView(new Activity());
348
349    	assertThat(view1.getHeight(), equalTo(0));
350    	assertThat(view1.getWidth(), equalTo(0));
351    	assertThat(view1.getMeasuredHeight(), equalTo(0));
352    	assertThat(view1.getMeasuredWidth(), equalTo(0));
353
354    	view1.measure( MeasureSpec.makeMeasureSpec(150, MeasureSpec.AT_MOST),
355    				   MeasureSpec.makeMeasureSpec(300, MeasureSpec.AT_MOST) );
356
357    	assertThat(view1.getHeight(), equalTo(0));
358    	assertThat(view1.getWidth(), equalTo(0));
359    	assertThat(view1.getMeasuredHeight(), equalTo(300));
360    	assertThat(view1.getMeasuredWidth(), equalTo(150));
361    }
362
363    @Test
364    public void test_measuredDimensionCustomView() {
365       	// View provides its own onMeasure implementation
366    	TestView2 view2 = new TestView2(new Activity());
367
368    	assertThat(view2.getHeight(), equalTo(0));
369    	assertThat(view2.getWidth(), equalTo(0));
370    	assertThat(view2.getMeasuredHeight(), equalTo(0));
371    	assertThat(view2.getMeasuredWidth(), equalTo(0));
372
373    	view2.measure( MeasureSpec.makeMeasureSpec(1000, MeasureSpec.AT_MOST),
374    				   MeasureSpec.makeMeasureSpec(600, MeasureSpec.AT_MOST) );
375
376    	assertThat(view2.getHeight(), equalTo(0));
377    	assertThat(view2.getWidth(), equalTo(0));
378    	assertThat(view2.getMeasuredHeight(), equalTo(400));
379    	assertThat(view2.getMeasuredWidth(), equalTo(800));
380    }
381
382    @Test
383    public void testFilterTouchesWhenObscured() {
384        assertFalse(view.getFilterTouchesWhenObscured());
385
386        view.setFilterTouchesWhenObscured(true);
387        assertTrue(view.getFilterTouchesWhenObscured());
388
389        view.setFilterTouchesWhenObscured(false);
390        assertFalse(view.getFilterTouchesWhenObscured());
391    }
392
393    @Test
394    public void testFilterTouchesWhenObscuredWhenLoadedFromXml() {
395        LinearLayout root = new LinearLayout(null);
396        ShadowView.inflate(new Activity(), R.layout.views, root);
397
398        View defaultView = root.findViewById(R.id.default_view);
399        assertFalse(defaultView.getFilterTouchesWhenObscured());
400
401        View filterFalseView = root.findViewById(R.id.filter_touches_false_view);
402        assertFalse(filterFalseView.getFilterTouchesWhenObscured());
403
404        View filterTrueView = root.findViewById(R.id.filter_touches_true_view);
405        assertTrue(filterTrueView.getFilterTouchesWhenObscured());
406    }
407
408    @Test
409    public void testClickable() {
410        assertFalse(view.isClickable());
411
412        view.setClickable(true);
413        assertTrue(view.isClickable());
414
415        view.setClickable(false);
416        assertFalse(view.isClickable());
417    }
418
419    @Test
420    public void testClickableWhenLoadedFromXml() {
421        LinearLayout root = new LinearLayout(null);
422        ShadowView.inflate(new Activity(), R.layout.views, root);
423
424        View defaultView = root.findViewById(R.id.default_view);
425        assertFalse(defaultView.isClickable());
426
427        View clickableFalseView = root.findViewById(R.id.clickable_false_view);
428        assertFalse(clickableFalseView.isClickable());
429
430        View clickableTrueView = root.findViewById(R.id.clickable_true_view);
431        assertTrue(clickableTrueView.isClickable());
432    }
433
434    @Test
435    public void testFocusable() {
436        assertFalse(view.isFocusable());
437
438        view.setFocusable(true);
439        assertTrue(view.isFocusable());
440
441        view.setFocusable(false);
442        assertFalse(view.isFocusable());
443    }
444
445    @Test
446    public void testFocusableWhenLoadedFromXml() {
447        LinearLayout root = new LinearLayout(null);
448        ShadowView.inflate(new Activity(), R.layout.views, root);
449
450        View defaultView = root.findViewById(R.id.default_view);
451        assertFalse(defaultView.isFocusable());
452
453        View focusableFalseView = root.findViewById(R.id.focusable_false_view);
454        assertFalse(focusableFalseView.isFocusable());
455
456        View focusableTrueView = root.findViewById(R.id.focusable_true_view);
457        assertTrue(focusableTrueView.isFocusable());
458    }
459
460    private static class TestAnimation extends Animation {
461    }
462
463    private static class TouchableView extends View {
464        MotionEvent event;
465
466        public TouchableView(Context context) {
467            super(context);
468        }
469
470        @Override
471        public boolean onTouchEvent(MotionEvent event) {
472            this.event = event;
473            return false;
474        }
475    }
476
477    private static class TestView extends View {
478        boolean onAnimationEndWasCalled;
479
480        public TestView(Context context) {
481            super(context);
482        }
483
484        @Override
485        protected void onAnimationEnd() {
486            super.onAnimationEnd();
487            onAnimationEndWasCalled = true;
488        }
489    }
490
491    private static class FocusableView extends View {
492      boolean onFocusChangedWasCalled;
493
494      public FocusableView(Context context) {
495        super(context);
496      }
497
498      @Override
499      protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
500        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
501
502        onFocusChangedWasCalled = true;
503      }
504    }
505
506    private static class TestView2 extends View {
507    	public TestView2(Context context) {
508            super(context);
509        }
510
511		@Override
512		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
513			super.onMeasure(800, 400);
514		}
515    }
516}
517