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.assertNotNull; 7 import static org.junit.Assert.assertNull; 8 import static org.junit.Assert.assertSame; 9 import static org.junit.Assert.assertTrue; 10 import static org.robolectric.Robolectric.buildActivity; 11 import static org.robolectric.Shadows.shadowOf; 12 13 import android.app.Activity; 14 import android.content.Context; 15 import android.content.pm.ActivityInfo; 16 import android.graphics.drawable.BitmapDrawable; 17 import android.graphics.drawable.Drawable; 18 import android.os.Bundle; 19 import android.util.AttributeSet; 20 import android.view.LayoutInflater; 21 import android.view.View; 22 import android.view.ViewGroup; 23 import android.view.ViewParent; 24 import android.webkit.WebView; 25 import android.widget.Button; 26 import android.widget.CheckBox; 27 import android.widget.EditText; 28 import android.widget.ImageView; 29 import android.widget.LinearLayout; 30 import android.widget.ListView; 31 import android.widget.RelativeLayout; 32 import android.widget.TextView; 33 import org.junit.Before; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 import org.robolectric.R; 37 import org.robolectric.R.layout; 38 import org.robolectric.RobolectricTestRunner; 39 import org.robolectric.RuntimeEnvironment; 40 import org.robolectric.android.CustomStateView; 41 import org.robolectric.android.CustomView; 42 import org.robolectric.android.CustomView2; 43 import org.robolectric.annotation.Config; 44 import org.robolectric.util.ReflectionHelpers; 45 46 @RunWith(RobolectricTestRunner.class) 47 public class ShadowLayoutInflaterTest { 48 private Context context; 49 50 @Before 51 public void setUp() throws Exception { 52 context = RuntimeEnvironment.application; 53 } 54 55 @Test 56 public void testCreatesCorrectClasses() throws Exception { 57 ViewGroup view = inflate(R.layout.media); 58 assertThat(view).isInstanceOf((Class<? extends ViewGroup>) LinearLayout.class); 59 60 assertSame(context, view.getContext()); 61 } 62 63 @Test 64 public void testChoosesLayoutBasedOnDefaultScreenSize() throws Exception { 65 ViewGroup view = inflate(R.layout.different_screen_sizes); 66 TextView textView = view.findViewById(android.R.id.text1); 67 assertThat(textView.getText().toString()).isEqualTo("default"); 68 } 69 70 @Test 71 @Config(qualifiers = "xlarge") 72 public void testChoosesLayoutBasedOnScreenSize() throws Exception { 73 ViewGroup view = inflate(R.layout.different_screen_sizes); 74 TextView textView = view.findViewById(android.R.id.text1); 75 assertThat(textView.getText().toString()).isEqualTo("xlarge"); 76 } 77 78 @Test 79 @Config(qualifiers = "land") 80 public void testChoosesLayoutBasedOnQualifiers() throws Exception { 81 ViewGroup view = inflate(R.layout.different_screen_sizes); 82 TextView textView = view.findViewById(android.R.id.text1); 83 assertThat(textView.getText().toString()).isEqualTo("land"); 84 } 85 86 @Test 87 public void testWebView() throws Exception { 88 ViewGroup view = inflate(R.layout.webview_holder); 89 WebView webView = view.findViewById(R.id.web_view); 90 91 webView.loadUrl("www.example.com"); 92 93 assertThat(shadowOf(webView).getLastLoadedUrl()).isEqualTo("www.example.com"); 94 } 95 96 @Test 97 public void testAddsChildren() throws Exception { 98 ViewGroup view = inflate(R.layout.media); 99 assertTrue(view.getChildCount() > 0); 100 101 assertSame(context, view.getChildAt(0).getContext()); 102 } 103 104 @Test 105 public void testFindsChildrenById() throws Exception { 106 ViewGroup mediaView = inflate(R.layout.media); 107 assertThat(mediaView.<TextView>findViewById(R.id.title)) 108 .isInstanceOf((Class<? extends TextView>) TextView.class); 109 110 ViewGroup mainView = inflate(R.layout.main); 111 assertThat(mainView.<View>findViewById(R.id.title)) 112 .isInstanceOf((Class<? extends View>) View.class); 113 } 114 115 @Test 116 public void testInflatingConflictingSystemAndLocalViewsWorks() throws Exception { 117 ViewGroup view = inflate(R.layout.activity_list_item); 118 assertThat(view.<ImageView>findViewById(R.id.icon)) 119 .isInstanceOf((Class<? extends ImageView>) ImageView.class); 120 121 view = inflate(android.R.layout.activity_list_item); 122 assertThat(view.<ImageView>findViewById(android.R.id.icon)) 123 .isInstanceOf((Class<? extends ImageView>) ImageView.class); 124 } 125 126 @Test 127 public void testInclude() throws Exception { 128 ViewGroup mediaView = inflate(R.layout.media); 129 assertThat(mediaView.<TextView>findViewById(R.id.include_id)) 130 .isInstanceOf((Class<? extends TextView>) TextView.class); 131 } 132 133 @Test 134 public void testIncludeShouldRetainAttributes() throws Exception { 135 ViewGroup mediaView = inflate(R.layout.media); 136 assertThat(mediaView.findViewById(R.id.include_id).getVisibility()).isEqualTo(View.GONE); 137 } 138 139 @Test 140 public void shouldOverwriteIdOnIncludedNonMerge() throws Exception { 141 ViewGroup mediaView = inflate(R.layout.media); 142 assertNull(mediaView.findViewById(R.id.snippet_text)); 143 } 144 145 @Test 146 public void shouldRetainIdOnIncludedMergeWhenIncludeSpecifiesNoId() throws Exception { 147 ViewGroup mediaView = inflate(R.layout.override_include); 148 assertThat(mediaView.<TextView>findViewById(R.id.inner_text)) 149 .isInstanceOf((Class<? extends TextView>) TextView.class); 150 } 151 152 @Test 153 public void shouldRetainIdOnIncludedNonMergeWhenIncludeSpecifiesNoId() throws Exception { 154 ViewGroup mediaView = inflate(R.layout.override_include); 155 assertThat(mediaView.<TextView>findViewById(R.id.snippet_text)) 156 .isInstanceOf((Class<? extends TextView>) TextView.class); 157 } 158 159 @Test 160 public void testIncludedIdShouldNotBeFoundWhenIncludedIsMerge() throws Exception { 161 ViewGroup overrideIncludeView = inflate(R.layout.outer); 162 assertThat(overrideIncludeView.<LinearLayout>findViewById(R.id.outer_merge)) 163 .isInstanceOf((Class<? extends LinearLayout>) LinearLayout.class); 164 assertThat(overrideIncludeView.<TextView>findViewById(R.id.inner_text)) 165 .isInstanceOf((Class<? extends TextView>) TextView.class); 166 assertNull(overrideIncludeView.findViewById(R.id.include_id)); 167 assertEquals(1, overrideIncludeView.getChildCount()); 168 } 169 170 @Test 171 public void testIncludeShouldOverrideAttributesOfIncludedRootNode() throws Exception { 172 ViewGroup overrideIncludeView = inflate(R.layout.override_include); 173 assertThat(overrideIncludeView.findViewById(R.id.snippet_text).getVisibility()) 174 .isEqualTo(View.INVISIBLE); 175 } 176 177 @Test 178 public void shouldNotCountRequestFocusElementAsChild() throws Exception { 179 ViewGroup viewGroup = inflate(R.layout.request_focus); 180 ViewGroup frameLayout = (ViewGroup) viewGroup.getChildAt(1); 181 assertEquals(0, frameLayout.getChildCount()); 182 } 183 184 @Test 185 public void focusRequest_shouldNotExplodeOnViewRootImpl() throws Exception { 186 LinearLayout parent = new LinearLayout(context); 187 shadowOf(parent).setMyParent(ReflectionHelpers.createNullProxy(ViewParent.class)); 188 LayoutInflater.from(context).inflate(R.layout.request_focus, parent); 189 } 190 191 @Test 192 public void shouldGiveFocusToElementContainingRequestFocusElement() throws Exception { 193 ViewGroup viewGroup = inflate(R.layout.request_focus); 194 EditText editText = viewGroup.findViewById(R.id.edit_text); 195 assertFalse(editText.isFocused()); 196 } 197 198 @Test 199 public void testMerge() throws Exception { 200 ViewGroup mediaView = inflate(R.layout.outer); 201 assertThat(mediaView.<TextView>findViewById(R.id.inner_text)) 202 .isInstanceOf((Class<? extends TextView>) TextView.class); 203 } 204 205 @Test 206 public void mergeIncludesShouldNotCreateAncestryLoops() throws Exception { 207 ViewGroup mediaView = inflate(R.layout.outer); 208 mediaView.hasFocus(); 209 } 210 211 @Test 212 public void testViewGroupsLooksAtItsOwnId() throws Exception { 213 TextView mediaView = inflate(layout.snippet); 214 assertSame(mediaView, mediaView.findViewById(R.id.snippet_text)); 215 } 216 217 @Test 218 public void shouldConstructCustomViewsWithAttributesConstructor() throws Exception { 219 CustomView view = inflate(layout.custom_layout); 220 assertThat(view.attributeResourceValue).isEqualTo(R.string.hello); 221 } 222 223 @Test 224 public void shouldConstructCustomViewsWithCustomState() throws Exception { 225 CustomStateView view = inflate(layout.custom_layout6); 226 assertThat(view.getDrawableState()).doesNotContain(R.attr.stateFoo); 227 228 view.isFoo = true; 229 view.refreshDrawableState(); 230 231 assertThat(view.getDrawableState()).contains(R.attr.stateFoo); 232 } 233 234 @Test 235 public void shouldConstructCustomViewsWithAttributesInResAutoNamespace() throws Exception { 236 CustomView view = inflate(layout.custom_layout5); 237 assertThat(view.attributeResourceValue).isEqualTo(R.string.hello); 238 } 239 240 @Test 241 public void shouldConstructCustomViewsWithAttributesWithURLEncodedNamespaces() throws Exception { 242 CustomView view = inflate(layout.custom_layout4).findViewById(R.id.custom_view); 243 assertThat(view.namespacedResourceValue).isEqualTo(R.layout.text_views); 244 } 245 246 @Test 247 public void testViewVisibilityIsSet() throws Exception { 248 View mediaView = inflate(layout.media); 249 assertThat(mediaView.findViewById(R.id.title).getVisibility()).isEqualTo(View.VISIBLE); 250 assertThat(mediaView.findViewById(R.id.subtitle).getVisibility()).isEqualTo(View.GONE); 251 } 252 253 @Test 254 public void testTextViewTextIsSet() throws Exception { 255 View mediaView = inflate(layout.main); 256 assertThat(((TextView) mediaView.findViewById(R.id.title)).getText().toString()) 257 .isEqualTo("Main Layout"); 258 assertThat(((TextView) mediaView.findViewById(R.id.subtitle)).getText().toString()) 259 .isEqualTo("Hello"); 260 } 261 262 @Test 263 public void testTextViewCompoundDrawablesAreSet() throws Exception { 264 View mediaView = inflate(layout.main); 265 TextView view = mediaView.findViewById(R.id.title); 266 267 assertThat(view.getCompoundDrawables()[0]).isEqualTo(drawable(R.drawable.fourth_image)); 268 assertThat(view.getCompoundDrawables()[1]).isEqualTo(drawable(R.drawable.an_image)); 269 assertThat(view.getCompoundDrawables()[2]).isEqualTo(drawable(R.drawable.an_other_image)); 270 assertThat(view.getCompoundDrawables()[3]).isEqualTo(drawable(R.drawable.third_image)); 271 } 272 273 @Test 274 public void testCheckBoxCheckedIsSet() throws Exception { 275 View mediaView = inflate(layout.main); 276 assertThat(((CheckBox) mediaView.findViewById(R.id.true_checkbox)).isChecked()).isTrue(); 277 assertThat(((CheckBox) mediaView.findViewById(R.id.false_checkbox)).isChecked()).isFalse(); 278 assertThat(((CheckBox) mediaView.findViewById(R.id.default_checkbox)).isChecked()).isFalse(); 279 } 280 281 @Test 282 public void testImageViewSrcIsSet() throws Exception { 283 View mediaView = inflate(layout.main); 284 ImageView imageView = mediaView.findViewById(R.id.image); 285 BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable(); 286 assertThat(shadowOf(drawable.getBitmap()).getCreatedFromResId()).isEqualTo(R.drawable.an_image); 287 } 288 289 @Test 290 public void testImageViewSrcIsSetFromMipmap() throws Exception { 291 View mediaView = inflate(layout.main); 292 ImageView imageView = mediaView.findViewById(R.id.mipmapImage); 293 BitmapDrawable drawable = (BitmapDrawable) imageView.getDrawable(); 294 assertThat(shadowOf(drawable.getBitmap()).getCreatedFromResId()) 295 .isEqualTo(R.mipmap.robolectric); 296 } 297 298 @Test 299 public void shouldInflateMergeLayoutIntoParent() throws Exception { 300 LinearLayout linearLayout = new LinearLayout(context); 301 LayoutInflater.from(context).inflate(R.layout.inner_merge, linearLayout); 302 assertThat(linearLayout.getChildAt(0)).isInstanceOf(TextView.class); 303 } 304 305 @Test 306 public void testMultiOrientation() throws Exception { 307 Activity activity = buildActivity(Activity.class).create().start().resume().get(); 308 309 // Default screen orientation should be portrait. 310 ViewGroup view = 311 (ViewGroup) LayoutInflater.from(activity).inflate(layout.multi_orientation, null); 312 assertThat(view).isInstanceOf((Class<? extends ViewGroup>) LinearLayout.class); 313 assertThat(view.getId()).isEqualTo(R.id.portrait); 314 assertSame(activity, view.getContext()); 315 316 // Confirm explicit "orientation = portrait" works. 317 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 318 int layoutResId = R.layout.multi_orientation; 319 view = (ViewGroup) LayoutInflater.from(activity).inflate(layoutResId, null); 320 assertThat(view).isInstanceOf((Class<? extends ViewGroup>) LinearLayout.class); 321 assertThat(view.getId()).isEqualTo(R.id.portrait); 322 assertSame(activity, view.getContext()); 323 } 324 325 @Test 326 @Config(qualifiers = "land") 327 public void testMultiOrientation_explicitLandscape() throws Exception { 328 Activity activity = buildActivity(Activity.class).create().start().resume().get(); 329 330 // Confirm explicit "orientation = landscape" works. 331 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 332 ViewGroup view = 333 (ViewGroup) LayoutInflater.from(activity).inflate(layout.multi_orientation, null); 334 assertThat(view.getId()).isEqualTo(R.id.landscape); 335 assertThat(view).isInstanceOf((Class<? extends ViewGroup>) LinearLayout.class); 336 } 337 338 @Test 339 @Config(qualifiers = "w0dp") 340 public void testSetContentViewByItemResource() throws Exception { 341 Activity activity = buildActivity(Activity.class).create().get(); 342 activity.setContentView(R.layout.main_layout); 343 344 TextView tv1 = activity.findViewById(R.id.hello); 345 TextView tv2 = activity.findViewById(R.id.world); 346 assertNotNull(tv1); 347 assertNull(tv2); 348 } 349 350 @Test 351 @Config(qualifiers = "w820dp") 352 public void testSetContentViewByItemResourceWithW820dp() throws Exception { 353 Activity activity = buildActivity(Activity.class).create().get(); 354 activity.setContentView(R.layout.main_layout); 355 356 TextView tv1 = activity.findViewById(R.id.hello); 357 TextView tv2 = activity.findViewById(R.id.world); 358 assertNotNull(tv1); 359 assertNotNull(tv2); 360 } 361 362 @Test 363 public void testViewEnabled() throws Exception { 364 View mediaView = inflate(layout.main); 365 assertThat(mediaView.findViewById(R.id.time).isEnabled()).isFalse(); 366 } 367 368 @Test 369 public void testContentDescriptionIsSet() throws Exception { 370 View mediaView = inflate(layout.main); 371 assertThat(mediaView.findViewById(R.id.time).getContentDescription().toString()) 372 .isEqualTo("Howdy"); 373 } 374 375 @Test 376 public void testAlphaIsSet() throws Exception { 377 View mediaView = inflate(layout.main); 378 assertThat(mediaView.findViewById(R.id.time).getAlpha()).isEqualTo(.3f); 379 } 380 381 @Test 382 public void testViewBackgroundIdIsSet() throws Exception { 383 View mediaView = inflate(layout.main); 384 ImageView imageView = mediaView.findViewById(R.id.image); 385 386 assertThat(shadowOf(imageView.getBackground()).getCreatedFromResId()) 387 .isEqualTo(R.drawable.image_background); 388 } 389 390 @Test 391 public void testOnClickAttribute() throws Exception { 392 ClickActivity activity = buildActivity(ClickActivity.class).create().get(); 393 394 assertThat(activity.clicked).isFalse(); 395 396 Button button = activity.findViewById(R.id.button); 397 button.performClick(); 398 399 assertThat(activity.clicked).isTrue(); 400 } 401 402 @Test 403 public void testInvalidOnClickAttribute() throws Exception { 404 Activity activity = buildActivity(Activity.class).create().get(); 405 activity.setContentView(R.layout.with_invalid_onclick); 406 407 Button button = activity.findViewById(R.id.invalid_onclick_button); 408 409 IllegalStateException exception = null; 410 try { 411 button.performClick(); 412 } catch (IllegalStateException e) { 413 exception = e; 414 } 415 assertNotNull(exception); 416 assertThat(exception.getMessage()) 417 .as("The error message should contain the id name of the faulty button") 418 .contains("invalid_onclick_button"); 419 } 420 421 @Test 422 public void shouldInvokeOnFinishInflate() throws Exception { 423 int layoutResId = R.layout.custom_layout2; 424 CustomView2 outerCustomView = inflate(layoutResId); 425 CustomView2 innerCustomView = (CustomView2) outerCustomView.getChildAt(0); 426 assertThat(outerCustomView.childCountAfterInflate).isEqualTo(1); 427 assertThat(innerCustomView.childCountAfterInflate).isEqualTo(3); 428 } 429 430 @SuppressWarnings("UnusedDeclaration") 431 public static class CustomView3 extends TextView { 432 public CustomView3(Context context) { 433 super(context); 434 } 435 436 public CustomView3(Context context, AttributeSet attrs) { 437 super(context, attrs); 438 } 439 440 public CustomView3(Context context, AttributeSet attrs, int defStyle) { 441 super(context, attrs, defStyle); 442 } 443 } 444 445 @Test 446 public void shouldInflateViewsWithClassAttr() throws Exception { 447 CustomView3 outerCustomView = inflate(layout.custom_layout3); 448 assertThat(outerCustomView.getText().toString()).isEqualTo("Hello bonjour"); 449 } 450 451 @Test 452 public void testIncludesLinearLayoutsOnlyOnce() throws Exception { 453 ViewGroup parentView = inflate(R.layout.included_layout_parent); 454 assertEquals(1, parentView.getChildCount()); 455 } 456 457 @Test 458 public void testConverterAcceptsEnumOrdinal() throws Exception { 459 ViewGroup view = inflate(R.layout.ordinal_scrollbar); 460 assertThat(view).isInstanceOf((Class<? extends ViewGroup>) RelativeLayout.class); 461 ListView listView = 462 (ListView) view.findViewById(org.robolectric.R.id.list_view_with_enum_scrollbar); 463 assertThat(listView).isInstanceOf((Class<? extends ListView>) ListView.class); 464 } 465 466 ///////////////////////// 467 468 @SuppressWarnings("TypeParameterUnusedInFormals") 469 private <T extends View> T inflate(int layoutResId) { 470 return (T) LayoutInflater.from(context).inflate(layoutResId, null); 471 } 472 473 private Drawable drawable(int id) { 474 Drawable drawable = context.getResources().getDrawable(id); 475 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 476 return drawable; 477 } 478 479 public static class ClickActivity extends Activity { 480 public boolean clicked = false; 481 482 @Override 483 protected void onCreate(Bundle savedInstanceState) { 484 super.onCreate(savedInstanceState); 485 setContentView(R.layout.main); 486 } 487 488 public void onButtonClick(View v) { 489 clicked = true; 490 } 491 } 492 } 493