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