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'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