Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static org.assertj.core.api.Assertions.assertThat;
      4 import static org.junit.Assert.assertEquals;
      5 import static org.junit.Assert.assertFalse;
      6 import static org.junit.Assert.assertNotSame;
      7 import static org.junit.Assert.assertSame;
      8 import static org.junit.Assert.assertTrue;
      9 import static org.junit.Assert.fail;
     10 import static org.robolectric.Shadows.shadowOf;
     11 
     12 import android.app.Application;
     13 import android.view.MotionEvent;
     14 import android.view.View;
     15 import android.view.ViewGroup;
     16 import android.view.ViewParent;
     17 import android.view.animation.Animation;
     18 import android.view.animation.Animation.AnimationListener;
     19 import android.view.animation.LayoutAnimationController;
     20 import android.widget.FrameLayout;
     21 import android.widget.TextView;
     22 import java.io.ByteArrayOutputStream;
     23 import java.io.PrintStream;
     24 import org.junit.After;
     25 import org.junit.Before;
     26 import org.junit.Test;
     27 import org.junit.runner.RunWith;
     28 import org.robolectric.R;
     29 import org.robolectric.RobolectricTestRunner;
     30 import org.robolectric.RuntimeEnvironment;
     31 
     32 @RunWith(RobolectricTestRunner.class)
     33 public class ShadowViewGroupTest {
     34   private String defaultLineSeparator;
     35   private ViewGroup root;
     36   private View child1;
     37   private View child2;
     38   private ViewGroup child3;
     39   private View child3a;
     40   private View child3b;
     41   private Application context;
     42 
     43   @Before
     44   public void setUp() throws Exception {
     45     context = RuntimeEnvironment.application;
     46 
     47     root = new FrameLayout(context);
     48 
     49     child1 = new View(context);
     50     child2 = new View(context);
     51     child3 = new FrameLayout(context);
     52     child3a = new View(context);
     53     child3b = new View(context);
     54 
     55     root.addView(child1);
     56     root.addView(child2);
     57     root.addView(child3);
     58 
     59     child3.addView(child3a);
     60     child3.addView(child3b);
     61 
     62     defaultLineSeparator = System.getProperty("line.separator");
     63     System.setProperty("line.separator", "\n");
     64   }
     65 
     66   @After
     67   public void tearDown() throws Exception {
     68     System.setProperty("line.separator", defaultLineSeparator);
     69   }
     70 
     71   @Test
     72   public void removeNullView_doesNothing() {
     73     root.removeView(null);
     74   }
     75 
     76   @Test
     77   public void testLayoutAnimationListener() {
     78     assertThat(root.getLayoutAnimationListener()).isNull();
     79 
     80     AnimationListener animationListener = new AnimationListener() {
     81       @Override
     82       public void onAnimationEnd(Animation a) {
     83       }
     84 
     85       @Override
     86       public void onAnimationRepeat(Animation a) {
     87       }
     88 
     89       @Override
     90       public void onAnimationStart(Animation a) {
     91       }
     92     };
     93     root.setLayoutAnimationListener(animationListener);
     94 
     95     assertThat(root.getLayoutAnimationListener()).isSameAs(animationListener);
     96   }
     97 
     98   @Test
     99   public void testLayoutAnimation() {
    100     assertThat(root.getLayoutAnimation()).isNull();
    101     LayoutAnimationController layoutAnim = new LayoutAnimationController(context, null);
    102     root.setLayoutAnimation(layoutAnim);
    103     assertThat(root.getLayoutAnimation()).isSameAs(layoutAnim);
    104   }
    105 
    106   @Test
    107   public void testRemoveChildAt() throws Exception {
    108     root.removeViewAt(1);
    109 
    110     assertThat(root.getChildCount()).isEqualTo(2);
    111     assertThat(root.getChildAt(0)).isSameAs(child1);
    112     assertThat(root.getChildAt(1)).isSameAs((View) child3);
    113 
    114     assertThat(child2.getParent()).isNull();
    115   }
    116 
    117   @Test
    118   public void testAddViewAt() throws Exception {
    119     root.removeAllViews();
    120     root.addView(child1);
    121     root.addView(child2);
    122     root.addView(child3, 1);
    123     assertThat(root.getChildAt(0)).isSameAs(child1);
    124     assertThat(root.getChildAt(1)).isSameAs((View) child3);
    125     assertThat(root.getChildAt(2)).isSameAs(child2);
    126   }
    127 
    128   @Test
    129   public void shouldFindViewWithTag() {
    130     root.removeAllViews();
    131     child1.setTag("tag1");
    132     child2.setTag("tag2");
    133     child3.setTag("tag3");
    134     root.addView(child1);
    135     root.addView(child2);
    136     root.addView(child3, 1);
    137     assertThat((View) root.findViewWithTag("tag1")).isSameAs(child1);
    138     assertThat((View) root.findViewWithTag("tag2")).isSameAs((View) child2);
    139     assertThat((ViewGroup) root.findViewWithTag("tag3")).isSameAs(child3);
    140   }
    141 
    142   @Test
    143   public void shouldNotFindViewWithTagReturnNull() {
    144     root.removeAllViews();
    145     child1.setTag("tag1");
    146     child2.setTag("tag2");
    147     child3.setTag("tag3");
    148     root.addView(child1);
    149     root.addView(child2);
    150     root.addView(child3, 1);
    151     assertThat((View) root.findViewWithTag("tag21")).isNull();
    152     assertThat((ViewGroup) root.findViewWithTag("tag23")).isNull();
    153   }
    154 
    155   @Test
    156   public void shouldfindViewWithTagFromCorrectViewGroup() {
    157     root.removeAllViews();
    158     child1.setTag("tag1");
    159     child2.setTag("tag2");
    160     child3.setTag("tag3");
    161     root.addView(child1);
    162     root.addView(child2);
    163     root.addView(child3);
    164 
    165     child3a.setTag("tag1");
    166     child3b.setTag("tag2");
    167 
    168     //can find views by tag from root
    169     assertThat((View) root.findViewWithTag("tag1")).isSameAs(child1);
    170     assertThat((View) root.findViewWithTag("tag2")).isSameAs((View) child2);
    171     assertThat((ViewGroup) root.findViewWithTag("tag3")).isSameAs(child3);
    172 
    173     //can find views by tag from child3
    174     assertThat((View) child3.findViewWithTag("tag1")).isSameAs(child3a);
    175     assertThat((View) child3.findViewWithTag("tag2")).isSameAs(child3b);
    176   }
    177 
    178   @Test
    179   public void hasFocus_shouldReturnTrueIfAnyChildHasFocus() throws Exception {
    180     makeFocusable(root, child1, child2, child3, child3a, child3b);
    181     assertFalse(root.hasFocus());
    182 
    183     child1.requestFocus();
    184     assertTrue(root.hasFocus());
    185 
    186     child1.clearFocus();
    187     assertFalse(child1.hasFocus());
    188     assertTrue(root.hasFocus());
    189 
    190     child3b.requestFocus();
    191     assertTrue(root.hasFocus());
    192 
    193     child3b.clearFocus();
    194     assertFalse(child3b.hasFocus());
    195     assertFalse(child3.hasFocus());
    196     assertTrue(root.hasFocus());
    197 
    198     child2.requestFocus();
    199     assertFalse(child3.hasFocus());
    200     assertTrue(child2.hasFocus());
    201     assertTrue(root.hasFocus());
    202 
    203     root.requestFocus();
    204     assertTrue(root.hasFocus());
    205   }
    206 
    207   @Test
    208   public void clearFocus_shouldRecursivelyClearTheFocusOfAllChildren() throws Exception {
    209     child3a.requestFocus();
    210 
    211     root.clearFocus();
    212 
    213     assertFalse(child3a.hasFocus());
    214     assertFalse(child3.hasFocus());
    215     assertFalse(root.hasFocus());
    216 
    217     root.requestFocus();
    218     root.clearFocus();
    219     assertFalse(root.hasFocus());
    220   }
    221 
    222   @Test
    223   public void dump_shouldDumpStructure() throws Exception {
    224     child3.setId(R.id.snippet_text);
    225     child3b.setVisibility(View.GONE);
    226     TextView textView = new TextView(context);
    227     textView.setText("Here's some text!");
    228     textView.setVisibility(View.INVISIBLE);
    229     child3.addView(textView);
    230 
    231     ByteArrayOutputStream out = new ByteArrayOutputStream();
    232     shadowOf(root).dump(new PrintStream(out), 0);
    233     assertEquals("<FrameLayout>\n" +
    234         "  <View/>\n" +
    235         "  <View/>\n" +
    236         "  <FrameLayout id=\"org.robolectric:id/snippet_text\">\n" +
    237         "    <View/>\n" +
    238         "    <View visibility=\"GONE\"/>\n" +
    239         "    <TextView visibility=\"INVISIBLE\" text=\"Here&#39;s some text!\"/>\n" +
    240         "  </FrameLayout>\n" +
    241         "</FrameLayout>\n", out.toString());
    242   }
    243 
    244   @Test
    245   public void addViewWithLayoutParams_shouldStoreLayoutParams() throws Exception {
    246     FrameLayout.LayoutParams layoutParams1 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    247     FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    248     View child1 = new View(RuntimeEnvironment.application);
    249     View child2 = new View(RuntimeEnvironment.application);
    250     root.addView(child1, layoutParams1);
    251     root.addView(child2, 1, layoutParams2);
    252     assertSame(layoutParams1, child1.getLayoutParams());
    253     assertSame(layoutParams2, child2.getLayoutParams());
    254   }
    255 
    256 //  todo: re-enable this
    257 //  @Test @Config(minSdk = FROYO)
    258 //  public void getChildAt_shouldThrowIndexOutOfBoundsForInvalidIndices() { // 'cause that's what Android does
    259 //    assertThat(root.getChildCount()).isEqualTo(3);
    260 //    assertThrowsExceptionForBadIndex(13);
    261 //    assertThrowsExceptionForBadIndex(3);
    262 //    assertThrowsExceptionForBadIndex(-1);
    263 //  }
    264 //
    265 //  private void assertThrowsExceptionForBadIndex(int index) {
    266 //    try {
    267 //      assertThat(root.getChildAt(index)).isNull();
    268 //      fail("no exception");
    269 //    } catch (IndexOutOfBoundsException ex) {
    270 //      //noinspection UnnecessaryReturnStatement
    271 //      return;
    272 //    } catch (Exception ex) {
    273 //      fail("wrong exception type");
    274 //    }
    275 //  }
    276 
    277   @Test
    278   public void layoutParams_shouldBeViewGroupLayoutParams() {
    279     assertThat(child1.getLayoutParams()).isInstanceOf(FrameLayout.LayoutParams.class);
    280     assertThat(child1.getLayoutParams()).isInstanceOf(ViewGroup.LayoutParams.class);
    281   }
    282 
    283   @Test
    284   public void removeView_removesView() throws Exception {
    285     assertThat(root.getChildCount()).isEqualTo(3);
    286     root.removeView(child1);
    287     assertThat(root.getChildCount()).isEqualTo(2);
    288     assertThat(root.getChildAt(0)).isSameAs(child2);
    289     assertThat(root.getChildAt(1)).isSameAs((View) child3);
    290     assertThat(child1.getParent()).isNull();
    291   }
    292 
    293   @Test
    294   public void removeView_resetsParentOnlyIfViewIsInViewGroup() throws Exception {
    295     assertThat(root.getChildCount()).isEqualTo(3);
    296     assertNotSame(child3a.getParent(), root);
    297     root.removeView(child3a);
    298     assertThat(root.getChildCount()).isEqualTo(3);
    299     assertThat(child3a.getParent()).isSameAs((ViewParent) child3);
    300   }
    301 
    302   @Test
    303   public void addView_whenChildAlreadyHasAParent_shouldThrow() throws Exception {
    304     ViewGroup newRoot = new FrameLayout(context);
    305     try {
    306       newRoot.addView(child1);
    307       fail("Expected IllegalStateException");
    308     } catch (IllegalStateException e) {
    309       // pass
    310     }
    311   }
    312 
    313   @Test
    314   public void shouldKnowWhenOnInterceptTouchEventWasCalled() throws Exception {
    315     ViewGroup viewGroup = new FrameLayout(context);
    316 
    317     MotionEvent touchEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
    318     viewGroup.onInterceptTouchEvent(touchEvent);
    319 
    320     assertThat(shadowOf(viewGroup).getInterceptedTouchEvent()).isEqualTo(touchEvent);
    321   }
    322 
    323   @Test
    324   public void removeView_shouldRequestLayout() throws Exception {
    325     View view = new View(context);
    326     ViewGroup viewGroup = new FrameLayout(context);
    327     viewGroup.addView(view);
    328     shadowOf(viewGroup).setDidRequestLayout(false);
    329 
    330     viewGroup.removeView(view);
    331     assertThat(shadowOf(viewGroup).didRequestLayout()).isTrue();
    332   }
    333 
    334   @Test
    335   public void removeViewAt_shouldRequestLayout() throws Exception {
    336     View view = new View(context);
    337     ViewGroup viewGroup = new FrameLayout(context);
    338     viewGroup.addView(view);
    339     shadowOf(viewGroup).setDidRequestLayout(false);
    340 
    341     viewGroup.removeViewAt(0);
    342     assertThat(shadowOf(viewGroup).didRequestLayout()).isTrue();
    343   }
    344 
    345   @Test
    346   public void removeAllViews_shouldRequestLayout() throws Exception {
    347     View view = new View(context);
    348     ViewGroup viewGroup = new FrameLayout(context);
    349     viewGroup.addView(view);
    350     shadowOf(viewGroup).setDidRequestLayout(false);
    351 
    352     viewGroup.removeAllViews();
    353     assertThat(shadowOf(viewGroup).didRequestLayout()).isTrue();
    354   }
    355 
    356   @Test
    357   public void addView_shouldRequestLayout() throws Exception {
    358     View view = new View(context);
    359     ViewGroup viewGroup = new FrameLayout(context);
    360     viewGroup.addView(view);
    361 
    362     assertThat(shadowOf(viewGroup).didRequestLayout()).isTrue();
    363   }
    364 
    365   @Test
    366   public void addView_withIndex_shouldRequestLayout() throws Exception {
    367     View view = new View(context);
    368     ViewGroup viewGroup = new FrameLayout(context);
    369     viewGroup.addView(view, 0);
    370 
    371     assertThat(shadowOf(viewGroup).didRequestLayout()).isTrue();
    372   }
    373 
    374   @Test
    375   public void removeAllViews_shouldCallOnChildViewRemovedWithEachChild() throws Exception {
    376     View view = new View(context);
    377     ViewGroup viewGroup = new FrameLayout(context);
    378     viewGroup.addView(view);
    379 
    380     TestOnHierarchyChangeListener testListener = new TestOnHierarchyChangeListener();
    381 
    382     viewGroup.setOnHierarchyChangeListener(testListener);
    383     viewGroup.removeAllViews();
    384     assertTrue(testListener.wasCalled());
    385   }
    386 
    387   private void makeFocusable(View... views) {
    388     for (View view : views) {
    389       view.setFocusable(true);
    390     }
    391   }
    392 
    393   static class TestOnHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener {
    394     boolean wasCalled = false;
    395 
    396     @Override
    397     public void onChildViewAdded(View parent, View child) {
    398     }
    399 
    400     @Override
    401     public void onChildViewRemoved(View parent, View child) {
    402       wasCalled = true;
    403     }
    404 
    405     public boolean wasCalled() {
    406       return wasCalled;
    407     }
    408   }
    409 }
    410