1package org.robolectric.shadows;
2
3import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
4import static android.os.Build.VERSION_CODES.LOLLIPOP;
5import static org.assertj.core.api.Assertions.assertThat;
6import static org.junit.Assert.assertEquals;
7import static org.junit.Assert.assertFalse;
8import static org.junit.Assert.assertNotNull;
9import static org.junit.Assert.assertNotSame;
10import static org.junit.Assert.assertTrue;
11import static org.mockito.Mockito.mock;
12import static org.mockito.Mockito.verify;
13import static org.mockito.Mockito.verifyZeroInteractions;
14import static org.robolectric.Robolectric.buildActivity;
15import static org.robolectric.Shadows.shadowOf;
16
17import android.app.Activity;
18import android.content.Context;
19import android.graphics.BitmapFactory;
20import android.graphics.Point;
21import android.graphics.Rect;
22import android.graphics.drawable.BitmapDrawable;
23import android.graphics.drawable.ColorDrawable;
24import android.graphics.drawable.Drawable;
25import android.os.Bundle;
26import android.util.AttributeSet;
27import android.view.ContextMenu;
28import android.view.HapticFeedbackConstants;
29import android.view.MotionEvent;
30import android.view.View;
31import android.view.View.MeasureSpec;
32import android.view.View.OnClickListener;
33import android.view.View.OnLongClickListener;
34import android.view.ViewGroup;
35import android.view.ViewParent;
36import android.view.ViewTreeObserver;
37import android.view.WindowId;
38import android.view.WindowManager;
39import android.view.animation.AlphaAnimation;
40import android.view.animation.Animation;
41import android.widget.FrameLayout;
42import android.widget.LinearLayout;
43import java.util.ArrayList;
44import java.util.List;
45import java.util.concurrent.atomic.AtomicBoolean;
46import org.junit.Before;
47import org.junit.Test;
48import org.junit.runner.RunWith;
49import org.robolectric.R;
50import org.robolectric.Robolectric;
51import org.robolectric.RobolectricTestRunner;
52import org.robolectric.RuntimeEnvironment;
53import org.robolectric.android.DeviceConfig;
54import org.robolectric.android.controller.ActivityController;
55import org.robolectric.annotation.AccessibilityChecks;
56import org.robolectric.annotation.Config;
57import org.robolectric.util.ReflectionHelpers;
58import org.robolectric.util.TestRunnable;
59
60@RunWith(RobolectricTestRunner.class)
61public class ShadowViewTest {
62  private View view;
63  private List<String> transcript;
64
65  @Before
66  public void setUp() throws Exception {
67    transcript = new ArrayList<>();
68    view = new View(RuntimeEnvironment.application);
69  }
70
71  @Test
72  public void testHasNullLayoutParamsUntilAddedToParent() throws Exception {
73    assertThat(view.getLayoutParams()).isNull();
74    new LinearLayout(RuntimeEnvironment.application).addView(view);
75    assertThat(view.getLayoutParams()).isNotNull();
76  }
77
78  @Test
79  public void layout_shouldAffectWidthAndHeight() throws Exception {
80    assertThat(view.getWidth()).isEqualTo(0);
81    assertThat(view.getHeight()).isEqualTo(0);
82
83    view.layout(100, 200, 303, 404);
84    assertThat(view.getWidth()).isEqualTo(303 - 100);
85    assertThat(view.getHeight()).isEqualTo(404 - 200);
86  }
87
88  @Test
89  public void measuredDimensions() throws Exception {
90    View view1 = new View(RuntimeEnvironment.application) {
91      {
92        setMeasuredDimension(123, 456);
93      }
94    };
95    assertThat(view1.getMeasuredWidth()).isEqualTo(123);
96    assertThat(view1.getMeasuredHeight()).isEqualTo(456);
97  }
98
99  @Test
100  public void layout_shouldCallOnLayoutOnlyIfChanged() throws Exception {
101    View view1 = new View(RuntimeEnvironment.application) {
102      @Override
103      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
104        transcript.add("onLayout " + changed + " " + left + " " + top + " " + right + " " + bottom);
105      }
106    };
107    view1.layout(0, 0, 0, 0);
108    assertThat(transcript).isEmpty();
109    view1.layout(1, 2, 3, 4);
110    assertThat(transcript).containsExactly("onLayout true 1 2 3 4");
111    transcript.clear();
112    view1.layout(1, 2, 3, 4);
113    assertThat(transcript).isEmpty();
114  }
115
116  @Test
117  public void shouldFocus() throws Exception {
118    final List<String> transcript = new ArrayList<>();
119
120    view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
121      @Override
122      public void onFocusChange(View v, boolean hasFocus) {
123        transcript.add(hasFocus ? "Gained focus" : "Lost focus");
124      }
125    });
126
127    assertFalse(view.isFocused());
128    assertFalse(view.hasFocus());
129    assertThat(transcript).isEmpty();
130
131    view.requestFocus();
132    assertFalse(view.isFocused());
133    assertFalse(view.hasFocus());
134    assertThat(transcript).isEmpty();
135
136    view.setFocusable(true);
137    view.requestFocus();
138    assertTrue(view.isFocused());
139    assertTrue(view.hasFocus());
140    assertThat(transcript).containsExactly("Gained focus");
141    transcript.clear();
142
143    shadowOf(view).setMyParent(new LinearLayout(RuntimeEnvironment.application)); // we can never lose focus unless a parent can take it
144
145    view.clearFocus();
146    assertFalse(view.isFocused());
147    assertFalse(view.hasFocus());
148    assertThat(transcript).containsExactly("Lost focus");
149  }
150
151  @Test
152  public void shouldNotBeFocusableByDefault() throws Exception {
153    assertFalse(view.isFocusable());
154
155    view.setFocusable(true);
156    assertTrue(view.isFocusable());
157  }
158
159  @Test
160  public void shouldKnowIfThisOrAncestorsAreVisible() throws Exception {
161    assertThat(view.isShown()).describedAs("view isn't considered shown unless it has a view root").isFalse();
162    shadowOf(view).setMyParent(ReflectionHelpers.createNullProxy(ViewParent.class));
163    assertThat(view.isShown()).isTrue();
164    shadowOf(view).setMyParent(null);
165
166    ViewGroup parent = new LinearLayout(RuntimeEnvironment.application);
167    parent.addView(view);
168
169    ViewGroup grandParent = new LinearLayout(RuntimeEnvironment.application);
170    grandParent.addView(parent);
171
172    grandParent.setVisibility(View.GONE);
173
174    assertFalse(view.isShown());
175  }
176
177  @Test
178  public void shouldInflateMergeRootedLayoutAndNotCreateReferentialLoops() throws Exception {
179    LinearLayout root = new LinearLayout(RuntimeEnvironment.application);
180    LinearLayout.inflate(RuntimeEnvironment.application, R.layout.inner_merge, root);
181    for (int i = 0; i < root.getChildCount(); i++) {
182      View child = root.getChildAt(i);
183      assertNotSame(root, child);
184    }
185  }
186
187  @Test
188  public void performLongClick_shouldClickOnView() throws Exception {
189    OnLongClickListener clickListener = mock(OnLongClickListener.class);
190    shadowOf(view).setMyParent(ReflectionHelpers.createNullProxy(ViewParent.class));
191    view.setOnLongClickListener(clickListener);
192    view.performLongClick();
193
194    verify(clickListener).onLongClick(view);
195  }
196
197  @Test
198  public void checkedClick_shouldClickOnView() throws Exception {
199    OnClickListener clickListener = mock(OnClickListener.class);
200    shadowOf(view).setMyParent(ReflectionHelpers.createNullProxy(ViewParent.class));
201    view.setOnClickListener(clickListener);
202    shadowOf(view).checkedPerformClick();
203
204    verify(clickListener).onClick(view);
205  }
206
207  @Test(expected = RuntimeException.class)
208  public void checkedClick_shouldThrowIfViewIsNotVisible() throws Exception {
209    ViewGroup grandParent = new LinearLayout(RuntimeEnvironment.application);
210    ViewGroup parent = new LinearLayout(RuntimeEnvironment.application);
211    grandParent.addView(parent);
212    parent.addView(view);
213    grandParent.setVisibility(View.GONE);
214
215    shadowOf(view).checkedPerformClick();
216  }
217
218  @Test(expected = RuntimeException.class)
219  public void checkedClick_shouldThrowIfViewIsDisabled() throws Exception {
220    view.setEnabled(false);
221    shadowOf(view).checkedPerformClick();
222  }
223
224  /*
225   * This test will throw an exception because the accessibility checks depend on the  Android
226   * Support Library. If the support library is included at some point, a single test from
227   * AccessibilityUtilTest could be moved here to make sure the accessibility checking is run.
228   */
229  @Test(expected = RuntimeException.class)
230  @AccessibilityChecks
231  public void checkedClick_withA11yChecksAnnotation_shouldThrow() throws Exception {
232    shadowOf(view).checkedPerformClick();
233  }
234
235  @Test
236  public void getBackground_shouldReturnNullIfNoBackgroundHasBeenSet() throws Exception {
237    assertThat(view.getBackground()).isNull();
238  }
239
240  @Test
241  public void shouldSetBackgroundColor() {
242    int red = 0xffff0000;
243    view.setBackgroundColor(red);
244    assertThat((ColorDrawable) view.getBackground()).isEqualTo(new ColorDrawable(red));
245  }
246
247  @Test
248  public void shouldSetBackgroundResource() throws Exception {
249    view.setBackgroundResource(R.drawable.an_image);
250    assertThat(view.getBackground()).isEqualTo(view.getResources().getDrawable(R.drawable.an_image));
251    assertThat(shadowOf(view).getBackgroundResourceId()).isEqualTo(R.drawable.an_image);
252  }
253
254  @Test
255  public void shouldClearBackgroundResource() throws Exception {
256    view.setBackgroundResource(R.drawable.an_image);
257    view.setBackgroundResource(0);
258    assertThat(view.getBackground()).isEqualTo(null);
259    assertThat(shadowOf(view).getBackgroundResourceId()).isEqualTo(-1);
260  }
261
262  @Test
263  public void shouldRecordBackgroundColor() {
264    int[] colors = {R.color.black, R.color.clear, R.color.white};
265
266    for (int color : colors) {
267      view.setBackgroundColor(color);
268      assertThat(shadowOf(view).getBackgroundColor()).isEqualTo(color);
269    }
270  }
271
272  @Test
273  public void shouldRecordBackgroundDrawable() {
274    Drawable drawable = new BitmapDrawable(BitmapFactory.decodeFile("some/fake/file"));
275    view.setBackgroundDrawable(drawable);
276    assertThat(view.getBackground()).isSameAs(drawable);
277    assertThat(ShadowView.visualize(view)).isEqualTo("background:\nBitmap for file:some/fake/file");
278  }
279
280  @Test
281  public void shouldPostActionsToTheMessageQueue() throws Exception {
282    ShadowLooper.pauseMainLooper();
283
284    TestRunnable runnable = new TestRunnable();
285    view.post(runnable);
286    assertFalse(runnable.wasRun);
287
288    ShadowLooper.unPauseMainLooper();
289    assertTrue(runnable.wasRun);
290  }
291
292  @Test
293  public void shouldPostInvalidateDelayed() throws Exception {
294    ShadowLooper.pauseMainLooper();
295
296    view.postInvalidateDelayed(100);
297    ShadowView shadowView = shadowOf(view);
298    assertFalse(shadowView.wasInvalidated());
299
300    ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
301    assertTrue(shadowView.wasInvalidated());
302  }
303
304  @Test
305  public void shouldPostActionsToTheMessageQueueWithDelay() throws Exception {
306    ShadowLooper.pauseMainLooper();
307
308    TestRunnable runnable = new TestRunnable();
309    view.postDelayed(runnable, 1);
310    assertFalse(runnable.wasRun);
311
312    Robolectric.getForegroundThreadScheduler().advanceBy(1);
313    assertTrue(runnable.wasRun);
314  }
315
316  @Test
317  public void shouldRemovePostedCallbacksFromMessageQueue() throws Exception {
318    TestRunnable runnable = new TestRunnable();
319    view.postDelayed(runnable, 1);
320
321    view.removeCallbacks(runnable);
322
323    Robolectric.getForegroundThreadScheduler().advanceBy(1);
324    assertThat(runnable.wasRun).isFalse();
325  }
326
327  @Test
328  public void shouldSupportAllConstructors() throws Exception {
329    new View(RuntimeEnvironment.application);
330    new View(RuntimeEnvironment.application, null);
331    new View(RuntimeEnvironment.application, null, 0);
332  }
333
334  @Test
335  public void shouldRememberIsPressed() {
336    view.setPressed(true);
337    assertTrue(view.isPressed());
338    view.setPressed(false);
339    assertFalse(view.isPressed());
340  }
341
342  @Test
343  public void shouldAddOnClickListenerFromAttribute() throws Exception {
344    AttributeSet attrs = Robolectric.buildAttributeSet()
345        .addAttribute(android.R.attr.onClick, "clickMe")
346        .build()
347        ;
348
349    view = new View(RuntimeEnvironment.application, attrs);
350    assertNotNull(shadowOf(view).getOnClickListener());
351  }
352
353  @Test
354  public void shouldCallOnClickWithAttribute() throws Exception {
355    MyActivity myActivity = buildActivity(MyActivity.class).create().get();
356
357    AttributeSet attrs = Robolectric.buildAttributeSet()
358        .addAttribute(android.R.attr.onClick, "clickMe")
359        .build();
360
361    view = new View(myActivity, attrs);
362    view.performClick();
363    assertTrue("Should have been called", myActivity.called);
364  }
365
366  @Test(expected = RuntimeException.class)
367  public void shouldThrowExceptionWithBadMethodName() throws Exception {
368    MyActivity myActivity = buildActivity(MyActivity.class).create().get();
369
370    AttributeSet attrs = Robolectric.buildAttributeSet()
371        .addAttribute(android.R.attr.onClick, "clickYou")
372        .build();
373
374    view = new View(myActivity, attrs);
375    view.performClick();
376  }
377
378  @Test
379  public void shouldSetAnimation() throws Exception {
380    Animation anim = new TestAnimation();
381    view.setAnimation(anim);
382    assertThat(view.getAnimation()).isSameAs(anim);
383  }
384
385  @Test
386  public void shouldFindViewWithTag() {
387    view.setTag("tagged");
388    assertThat((View) view.findViewWithTag("tagged")).isSameAs(view);
389  }
390
391  @Test
392  public void scrollTo_shouldStoreTheScrolledCoordinates() throws Exception {
393    view.scrollTo(1, 2);
394    assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(1, 2));
395  }
396
397  @Test
398  public void shouldScrollTo() throws Exception {
399    view.scrollTo(7, 6);
400
401    assertEquals(7, view.getScrollX());
402    assertEquals(6, view.getScrollY());
403  }
404
405  @Test
406  public void shouldGetScrollXAndY() {
407    assertEquals(0, view.getScrollX());
408    assertEquals(0, view.getScrollY());
409  }
410
411  @Test
412  public void getViewTreeObserver_shouldReturnTheSameObserverFromMultipleCalls() throws Exception {
413    ViewTreeObserver observer = view.getViewTreeObserver();
414    assertThat(observer).isInstanceOf(ViewTreeObserver.class);
415    assertThat(view.getViewTreeObserver()).isSameAs(observer);
416  }
417
418  @Test
419  public void dispatchTouchEvent_sendsMotionEventToOnTouchEvent() throws Exception {
420    TouchableView touchableView = new TouchableView(RuntimeEnvironment.application);
421    MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 12f, 34f, 0);
422    touchableView.dispatchTouchEvent(event);
423    assertThat(touchableView.event).isSameAs(event);
424    view.dispatchTouchEvent(event);
425    assertThat(shadowOf(view).getLastTouchEvent()).isSameAs(event);
426  }
427
428  @Test
429  public void dispatchTouchEvent_listensToFalseFromListener() throws Exception {
430    final AtomicBoolean called = new AtomicBoolean(false);
431    view.setOnTouchListener(new View.OnTouchListener() {
432      @Override
433      public boolean onTouch(View view, MotionEvent motionEvent) {
434        called.set(true); return false;
435      }
436    });
437    MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 12f, 34f, 0);
438    view.dispatchTouchEvent(event);
439    assertThat(shadowOf(view).getLastTouchEvent()).isSameAs(event);
440    assertThat(called.get()).isTrue();
441  }
442
443  @Test
444  public void test_nextFocusDownId() throws Exception {
445    assertEquals(View.NO_ID, view.getNextFocusDownId());
446
447    view.setNextFocusDownId(R.id.icon);
448    assertEquals(R.id.icon, view.getNextFocusDownId());
449  }
450
451  @Test
452  public void startAnimation() {
453    TestView view = new TestView(buildActivity(Activity.class).create().get());
454    AlphaAnimation animation = new AlphaAnimation(0, 1);
455
456    Animation.AnimationListener listener = mock(Animation.AnimationListener.class);
457    animation.setAnimationListener(listener);
458    view.startAnimation(animation);
459
460    verify(listener).onAnimationStart(animation);
461    verify(listener).onAnimationEnd(animation);
462  }
463
464  @Test
465  public void setAnimation() {
466    TestView view = new TestView(buildActivity(Activity.class).create().get());
467    AlphaAnimation animation = new AlphaAnimation(0, 1);
468
469    Animation.AnimationListener listener = mock(Animation.AnimationListener.class);
470    animation.setAnimationListener(listener);
471    animation.setStartTime(1000);
472    view.setAnimation(animation);
473
474    verifyZeroInteractions(listener);
475
476    Robolectric.getForegroundThreadScheduler().advanceToNextPostedRunnable();
477
478    verify(listener).onAnimationStart(animation);
479    verify(listener).onAnimationEnd(animation);
480  }
481
482  @Test
483  public void setNullAnimation() {
484    TestView view = new TestView(buildActivity(Activity.class).create().get());
485    view.setAnimation(null);
486    assertThat(view.getAnimation()).isNull();
487  }
488
489  @Test
490  public void test_measuredDimension() {
491    // View does not provide its own onMeasure implementation
492    TestView view1 = new TestView(buildActivity(Activity.class).create().get());
493
494    assertThat(view1.getHeight()).isEqualTo(0);
495    assertThat(view1.getWidth()).isEqualTo(0);
496    assertThat(view1.getMeasuredHeight()).isEqualTo(0);
497    assertThat(view1.getMeasuredWidth()).isEqualTo(0);
498
499    view1.measure(MeasureSpec.makeMeasureSpec(150, MeasureSpec.AT_MOST),
500        MeasureSpec.makeMeasureSpec(300, MeasureSpec.AT_MOST));
501
502    assertThat(view1.getHeight()).isEqualTo(0);
503    assertThat(view1.getWidth()).isEqualTo(0);
504    assertThat(view1.getMeasuredHeight()).isEqualTo(300);
505    assertThat(view1.getMeasuredWidth()).isEqualTo(150);
506  }
507
508  @Test
509  public void test_measuredDimensionCustomView() {
510    // View provides its own onMeasure implementation
511    TestView2 view2 = new TestView2(buildActivity(Activity.class).create().get(), 300, 100);
512
513    assertThat(view2.getWidth()).isEqualTo(0);
514    assertThat(view2.getHeight()).isEqualTo(0);
515    assertThat(view2.getMeasuredWidth()).isEqualTo(0);
516    assertThat(view2.getMeasuredHeight()).isEqualTo(0);
517
518    view2.measure(MeasureSpec.makeMeasureSpec(200, MeasureSpec.AT_MOST),
519    MeasureSpec.makeMeasureSpec(50, MeasureSpec.AT_MOST));
520
521    assertThat(view2.getWidth()).isEqualTo(0);
522    assertThat(view2.getHeight()).isEqualTo(0);
523    assertThat(view2.getMeasuredWidth()).isEqualTo(300);
524    assertThat(view2.getMeasuredHeight()).isEqualTo(100);
525  }
526
527  @Test
528  public void shouldGetAndSetTranslations() throws Exception {
529    view = new TestView(buildActivity(Activity.class).create().get());
530    view.setTranslationX(8.9f);
531    view.setTranslationY(4.6f);
532
533    assertThat(view.getTranslationX()).isEqualTo(8.9f);
534    assertThat(view.getTranslationY()).isEqualTo(4.6f);
535  }
536
537  @Test
538  public void shouldGetAndSetAlpha() throws Exception {
539    view = new TestView(buildActivity(Activity.class).create().get());
540    view.setAlpha(9.1f);
541
542    assertThat(view.getAlpha()).isEqualTo(9.1f);
543  }
544
545  @Test
546  public void itKnowsIfTheViewIsShown() {
547    shadowOf(view).setMyParent(ReflectionHelpers.createNullProxy(ViewParent.class)); // a view is only considered visible if it is added to a view root
548    view.setVisibility(View.VISIBLE);
549    assertThat(view.isShown()).isTrue();
550  }
551
552  @Test
553  public void itKnowsIfTheViewIsNotShown() {
554    view.setVisibility(View.GONE);
555    assertThat(view.isShown()).isFalse();
556
557    view.setVisibility(View.INVISIBLE);
558    assertThat(view.isShown()).isFalse();
559  }
560
561  @Test
562  public void shouldTrackRequestLayoutCalls() throws Exception {
563    assertThat(shadowOf(view).didRequestLayout()).isFalse();
564    view.requestLayout();
565    assertThat(shadowOf(view).didRequestLayout()).isTrue();
566    shadowOf(view).setDidRequestLayout(false);
567    assertThat(shadowOf(view).didRequestLayout()).isFalse();
568  }
569
570  @Test
571  public void shouldClickAndNotClick() throws Exception {
572    assertThat(view.isClickable()).isFalse();
573    view.setClickable(true);
574    assertThat(view.isClickable()).isTrue();
575    view.setClickable(false);
576    assertThat(view.isClickable()).isFalse();
577    view.setOnClickListener(new OnClickListener() {
578      @Override
579      public void onClick(View v) {
580        ;
581      }
582    });
583    assertThat(view.isClickable()).isTrue();
584  }
585
586  @Test
587  public void shouldLongClickAndNotLongClick() throws Exception {
588    assertThat(view.isLongClickable()).isFalse();
589    view.setLongClickable(true);
590    assertThat(view.isLongClickable()).isTrue();
591    view.setLongClickable(false);
592    assertThat(view.isLongClickable()).isFalse();
593    view.setOnLongClickListener(new OnLongClickListener() {
594      @Override
595      public boolean onLongClick(View v) {
596        return false;
597      }
598    });
599    assertThat(view.isLongClickable()).isTrue();
600  }
601
602  @Test
603  public void rotationX() {
604    view.setRotationX(10f);
605    assertThat(view.getRotationX()).isEqualTo(10f);
606  }
607
608  @Test
609  public void rotationY() {
610    view.setRotationY(20f);
611    assertThat(view.getRotationY()).isEqualTo(20f);
612  }
613
614  @Test
615  public void rotation() {
616    view.setRotation(30f);
617    assertThat(view.getRotation()).isEqualTo(30f);
618  }
619
620  @Test
621  @Config(minSdk = LOLLIPOP)
622  public void cameraDistance() {
623    view.setCameraDistance(100f);
624    assertThat(view.getCameraDistance()).isEqualTo(100f);
625  }
626
627  @Test
628  public void scaleX() {
629    assertThat(view.getScaleX()).isEqualTo(1f);
630    view.setScaleX(0.5f);
631    assertThat(view.getScaleX()).isEqualTo(0.5f);
632  }
633
634  @Test
635  public void scaleY() {
636    assertThat(view.getScaleY()).isEqualTo(1f);
637    view.setScaleY(0.5f);
638    assertThat(view.getScaleY()).isEqualTo(0.5f);
639  }
640
641  @Test
642  public void pivotX() {
643    view.setPivotX(10f);
644    assertThat(view.getPivotX()).isEqualTo(10f);
645  }
646
647  @Test
648  public void pivotY() {
649    view.setPivotY(10f);
650    assertThat(view.getPivotY()).isEqualTo(10f);
651  }
652
653  @Test
654  @Config(minSdk = LOLLIPOP)
655  public void elevation() {
656    view.setElevation(10f);
657    assertThat(view.getElevation()).isEqualTo(10f);
658  }
659
660  @Test
661  public void translationX() {
662    view.setTranslationX(10f);
663    assertThat(view.getTranslationX()).isEqualTo(10f);
664  }
665
666  @Test
667  public void translationY() {
668    view.setTranslationY(10f);
669    assertThat(view.getTranslationY()).isEqualTo(10f);
670  }
671
672  @Test
673  @Config(minSdk = LOLLIPOP)
674  public void translationZ() {
675    view.setTranslationZ(10f);
676    assertThat(view.getTranslationZ()).isEqualTo(10f);
677  }
678
679  @Test
680  @Config(minSdk = LOLLIPOP)
681  public void clipToOutline() {
682    view.setClipToOutline(true);
683    assertThat(view.getClipToOutline()).isTrue();
684  }
685
686  @Test
687  public void performHapticFeedback_shouldSetLastPerformedHapticFeedback() throws Exception {
688    assertThat(shadowOf(view).lastHapticFeedbackPerformed()).isEqualTo(-1);
689    view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
690    assertThat(shadowOf(view).lastHapticFeedbackPerformed()).isEqualTo(HapticFeedbackConstants.LONG_PRESS);
691  }
692
693  @Test
694  public void canAssertThatSuperDotOnLayoutWasCalledFromViewSubclasses() throws Exception {
695    TestView2 view = new TestView2(buildActivity(Activity.class).create().get(), 1111, 1112);
696    assertThat(shadowOf(view).onLayoutWasCalled()).isFalse();
697    view.onLayout(true, 1, 2, 3, 4);
698    assertThat(shadowOf(view).onLayoutWasCalled()).isTrue();
699  }
700
701  @Test
702  public void setScrolls_canBeAskedFor() throws Exception {
703    view.setScrollX(234);
704    view.setScrollY(544);
705    assertThat(view.getScrollX()).isEqualTo(234);
706    assertThat(view.getScrollY()).isEqualTo(544);
707  }
708
709  @Test
710  public void setScrolls_firesOnScrollChanged() throws Exception {
711    TestView testView = new TestView(buildActivity(Activity.class).create().get());
712    testView.setScrollX(122);
713    testView.setScrollY(150);
714    testView.setScrollX(453);
715    assertThat(testView.oldl).isEqualTo(122);
716    testView.setScrollY(54);
717    assertThat(testView.l).isEqualTo(453);
718    assertThat(testView.t).isEqualTo(54);
719    assertThat(testView.oldt).isEqualTo(150);
720  }
721
722  private static class TestAnimation extends Animation {
723  }
724
725  private static class TouchableView extends View {
726    MotionEvent event;
727
728    public TouchableView(Context context) {
729      super(context);
730    }
731
732    @Override
733    public boolean onTouchEvent(MotionEvent event) {
734      this.event = event;
735      return false;
736    }
737  }
738
739  public static class TestView extends View {
740    boolean onAnimationEndWasCalled;
741    private int l;
742    private int t;
743    private int oldl;
744    private int oldt;
745
746    public TestView(Context context) {
747      super(context);
748    }
749
750    @Override
751    protected void onAnimationEnd() {
752      super.onAnimationEnd();
753      onAnimationEndWasCalled = true;
754    }
755
756    @Override
757    public void onScrollChanged(int l, int t, int oldl, int oldt) {
758      this.l = l;
759      this.t = t;
760      this.oldl = oldl;
761      this.oldt = oldt;
762    }
763  }
764
765  private static class TestView2 extends View {
766
767    private int minWidth;
768    private int minHeight;
769
770    public TestView2(Context context, int minWidth, int minHeight) {
771      super(context);
772      this.minWidth = minWidth;
773      this.minHeight = minHeight;
774    }
775
776    @Override
777    public void onLayout(boolean changed, int l, int t, int r, int b) {
778      super.onLayout(changed, l, t, r, b);
779    }
780
781    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
782      setMeasuredDimension(minWidth, minHeight);
783    }
784  }
785
786  @Test
787  public void shouldCallOnAttachedToAndDetachedFromWindow() throws Exception {
788    MyView parent = new MyView("parent", transcript);
789    parent.addView(new MyView("child", transcript));
790    assertThat(transcript).isEmpty();
791
792    Activity activity = Robolectric.buildActivity(ContentViewActivity.class).create().get();
793    activity.getWindowManager().addView(parent, new WindowManager.LayoutParams(100, 100));
794    assertThat(transcript).containsExactly("parent attached", "child attached");
795    transcript.clear();
796
797    parent.addView(new MyView("another child", transcript));
798    assertThat(transcript).containsExactly("another child attached");
799    transcript.clear();
800
801    MyView temporaryChild = new MyView("temporary child", transcript);
802    parent.addView(temporaryChild);
803    assertThat(transcript).containsExactly("temporary child attached");
804    transcript.clear();
805    assertTrue(shadowOf(temporaryChild).isAttachedToWindow());
806
807    parent.removeView(temporaryChild);
808    assertThat(transcript).containsExactly("temporary child detached");
809    assertFalse(shadowOf(temporaryChild).isAttachedToWindow());
810  }
811
812  @Test @Config(minSdk = JELLY_BEAN_MR2)
813  public void getWindowId_shouldReturnValidObjectWhenAttached() throws Exception {
814    MyView parent = new MyView("parent", transcript);
815    MyView child = new MyView("child", transcript);
816    parent.addView(child);
817
818    assertThat(parent.getWindowId()).isNull();
819    assertThat(child.getWindowId()).isNull();
820
821    Activity activity = Robolectric.buildActivity(ContentViewActivity.class).create().get();
822    activity.getWindowManager().addView(parent, new WindowManager.LayoutParams(100, 100));
823
824    WindowId windowId = parent.getWindowId();
825    assertThat(windowId).isNotNull();
826    assertThat(child.getWindowId()).isSameAs(windowId);
827    assertThat(child.getWindowId()).isEqualTo(windowId); // equals must work!
828
829    MyView anotherChild = new MyView("another child", transcript);
830    parent.addView(anotherChild);
831    assertThat(anotherChild.getWindowId()).isEqualTo(windowId);
832
833    parent.removeView(anotherChild);
834    assertThat(anotherChild.getWindowId()).isNull();
835  }
836
837  // todo looks like this is flaky...
838  @Test
839  public void removeAllViews_shouldCallOnAttachedToAndDetachedFromWindow() throws Exception {
840    MyView parent = new MyView("parent", transcript);
841    Activity activity = Robolectric.buildActivity(ContentViewActivity.class).create().get();
842    activity.getWindowManager().addView(parent, new WindowManager.LayoutParams(100, 100));
843
844    parent.addView(new MyView("child", transcript));
845    parent.addView(new MyView("another child", transcript));
846    ShadowLooper.runUiThreadTasks();
847    transcript.clear();
848    parent.removeAllViews();
849    ShadowLooper.runUiThreadTasks();
850    assertThat(transcript).containsExactly("another child detached", "child detached");
851  }
852
853  @Test
854  public void capturesOnSystemUiVisibilityChangeListener() throws Exception {
855    TestView testView = new TestView(buildActivity(Activity.class).create().get());
856    View.OnSystemUiVisibilityChangeListener changeListener = new View.OnSystemUiVisibilityChangeListener() {
857      @Override
858      public void onSystemUiVisibilityChange(int i) { }
859    };
860    testView.setOnSystemUiVisibilityChangeListener(changeListener);
861
862    assertThat(changeListener).isEqualTo(shadowOf(testView).getOnSystemUiVisibilityChangeListener());
863  }
864
865  @Test
866  public void capturesOnCreateContextMenuListener() throws Exception {
867    TestView testView = new TestView(buildActivity(Activity.class).create().get());
868    assertThat(shadowOf(testView).getOnCreateContextMenuListener()).isNull();
869
870    View.OnCreateContextMenuListener createListener = new View.OnCreateContextMenuListener() {
871      @Override
872      public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {}
873    };
874
875    testView.setOnCreateContextMenuListener(createListener);
876    assertThat(shadowOf(testView).getOnCreateContextMenuListener()).isEqualTo(createListener);
877
878    testView.setOnCreateContextMenuListener(null);
879    assertThat(shadowOf(testView).getOnCreateContextMenuListener()).isNull();
880  }
881
882  @Test
883  public void setsGlobalVisibleRect() {
884    Rect globalVisibleRect = new Rect();
885    shadowOf(view).setGlobalVisibleRect(new Rect());
886    assertThat(view.getGlobalVisibleRect(globalVisibleRect))
887        .isFalse();
888    assertThat(globalVisibleRect.isEmpty())
889        .isTrue();
890    assertThat(view.getGlobalVisibleRect(globalVisibleRect, new Point(1, 1)))
891        .isFalse();
892    assertThat(globalVisibleRect.isEmpty())
893        .isTrue();
894
895    shadowOf(view).setGlobalVisibleRect(new Rect(1, 2, 3, 4));
896    assertThat(view.getGlobalVisibleRect(globalVisibleRect))
897        .isTrue();
898    assertThat(globalVisibleRect)
899        .isEqualTo(new Rect(1, 2, 3, 4));
900    assertThat(view.getGlobalVisibleRect(globalVisibleRect, new Point(1, 1)))
901        .isTrue();
902    assertThat(globalVisibleRect)
903        .isEqualTo(new Rect(0, 1, 2, 3));
904  }
905
906  @Test
907  public void usesDefaultGlobalVisibleRect() {
908    final ActivityController<Activity> activityController = Robolectric.buildActivity(Activity.class);
909    final Activity activity = activityController.get();
910    activity.setContentView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
911            ViewGroup.LayoutParams.MATCH_PARENT));
912    activityController.setup();
913
914    Rect globalVisibleRect = new Rect();
915    assertThat(view.getGlobalVisibleRect(globalVisibleRect))
916        .isTrue();
917    assertThat(globalVisibleRect)
918        .isEqualTo(new Rect(0, 25,
919            DeviceConfig.DEFAULT_SCREEN_SIZE.width, DeviceConfig.DEFAULT_SCREEN_SIZE.height));
920  }
921
922  public static class MyActivity extends Activity {
923    public boolean called;
924
925    @SuppressWarnings("UnusedDeclaration")
926    public void clickMe(View view) {
927      called = true;
928    }
929  }
930
931  public static class MyView extends LinearLayout {
932    private String name;
933    private List<String> transcript;
934
935    public MyView(String name, List<String> transcript) {
936      super(RuntimeEnvironment.application);
937      this.name = name;
938      this.transcript = transcript;
939    }
940
941    @Override protected void onAttachedToWindow() {
942      transcript.add(name + " attached");
943      super.onAttachedToWindow();
944    }
945
946    @Override protected void onDetachedFromWindow() {
947      transcript.add(name + " detached");
948      super.onDetachedFromWindow();
949    }
950  }
951
952  private static class ContentViewActivity extends Activity {
953    @Override
954    protected void onCreate(Bundle savedInstanceState) {
955      super.onCreate(savedInstanceState);
956      setContentView(new FrameLayout(this));
957    }
958  }
959}
960