package org.robolectric.shadows;
import android.app.Activity;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.Button;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.R;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.TestRunners;
import org.robolectric.android.controller.ActivityController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.robolectric.Robolectric.buildActivity;
import static org.robolectric.Shadows.shadowOf;
@RunWith(TestRunners.MultiApiSelfTest.class)
public class ShadowThemeTest {
private Resources resources;
@Before
public void setUp() throws Exception {
resources = RuntimeEnvironment.application.getResources();
}
@Test public void withEmptyTheme_returnsEmptyAttributes() throws Exception {
assertThat(resources.newTheme().obtainStyledAttributes(new int[] {R.attr.string1}).hasValue(0)).isFalse();
}
@Test public void whenExplicitlySetOnActivity_afterSetContentView_activityGetsThemeFromActivityInManifest() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
activity.setTheme(R.style.Theme_Robolectric);
Button theButton = (Button) activity.findViewById(R.id.button);
ColorDrawable background = (ColorDrawable) theButton.getBackground();
assertThat(background.getColor()).isEqualTo(0xffff0000);
}
@Test public void whenExplicitlySetOnActivity_beforeSetContentView_activityUsesNewTheme() throws Exception {
ActivityController<TestActivityWithAnotherTheme> activityController = buildActivity(TestActivityWithAnotherTheme.class);
TestActivity activity = activityController.get();
activity.setTheme(R.style.Theme_Robolectric);
activityController.create();
Button theButton = (Button) activity.findViewById(R.id.button);
ColorDrawable background = (ColorDrawable) theButton.getBackground();
assertThat(background.getColor()).isEqualTo(0xff00ff00);
}
@Test public void whenSetOnActivityInManifest_activityGetsThemeFromActivityInManifest() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
Button theButton = (Button) activity.findViewById(R.id.button);
ColorDrawable background = (ColorDrawable) theButton.getBackground();
assertThat(background.getColor()).isEqualTo(0xffff0000);
}
@Test public void whenNotSetOnActivityInManifest_activityGetsThemeFromApplicationInManifest() throws Exception {
TestActivity activity = buildActivity(TestActivity.class).create().get();
Button theButton = (Button) activity.findViewById(R.id.button);
ColorDrawable background = (ColorDrawable) theButton.getBackground();
assertThat(background.getColor()).isEqualTo(0xff00ff00);
}
@Test public void shouldResolveReferencesThatStartWithAQuestionMark() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
Button theButton = (Button) activity.findViewById(R.id.button);
assertThat(theButton.getMinWidth()).isEqualTo(8);
assertThat(theButton.getMinHeight()).isEqualTo(8);
}
@Test public void shouldLookUpStylesFromStyleResId() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedArray a = activity.obtainStyledAttributes(null, R.styleable.CustomView, 0, R.style.MyCustomView);
boolean enabled = a.getBoolean(R.styleable.CustomView_aspectRatioEnabled, false);
assertThat(enabled).isTrue();
}
@Test public void shouldApplyStylesFromResourceReference() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedArray a = activity.obtainStyledAttributes(null, R.styleable.CustomView, 0, R.attr.animalStyle);
int animalStyleId = a.getResourceId(R.styleable.CustomView_animalStyle, 0);
assertThat(animalStyleId).isEqualTo(R.style.Gastropod);
assertThat(a.getFloat(R.styleable.CustomView_aspectRatio, 0.2f)).isEqualTo(1.69f);
}
@Test public void shouldApplyStylesFromAttributeReference() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAThirdTheme.class).create().get();
TypedArray a = activity.obtainStyledAttributes(null, R.styleable.CustomView, 0, R.attr.animalStyle);
int animalStyleId = a.getResourceId(R.styleable.CustomView_animalStyle, 0);
assertThat(animalStyleId).isEqualTo(R.style.Gastropod);
assertThat(a.getFloat(R.styleable.CustomView_aspectRatio, 0.2f)).isEqualTo(1.69f);
}
@Test public void obtainStyledAttributes_findsAttributeValueDefinedInDependencyLibrary() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAThirdTheme.class).create().get();
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{org.robolectric.R.attr.attrFromLib1});
assertThat(a.getString(0)).isEqualTo("value from theme");
}
@Test public void shouldGetValuesFromAttributeReference() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAThirdTheme.class).create().get();
TypedValue value1 = new TypedValue();
TypedValue value2 = new TypedValue();
boolean resolved1 = activity.getTheme().resolveAttribute(R.attr.someLayoutOne, value1, true);
boolean resolved2 = activity.getTheme().resolveAttribute(R.attr.someLayoutTwo, value2, true);
assertThat(resolved1).isTrue();
assertThat(resolved2).isTrue();
assertThat(value1.resourceId).isEqualTo(R.layout.activity_main);
assertThat(value2.resourceId).isEqualTo(R.layout.activity_main);
assertThat(value1.coerceToString()).isEqualTo(value2.coerceToString());
}
@Test public void withResolveRefsFalse_shouldResolveValue() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedValue value = new TypedValue();
boolean resolved = activity.getTheme().resolveAttribute(R.attr.logoWidth, value, false);
assertThat(resolved).isTrue();
assertThat(value.type).isEqualTo(TypedValue.TYPE_REFERENCE);
assertThat(value.data).isEqualTo(R.dimen.test_dp_dimen);
}
@Test public void withResolveRefsFalse_shouldNotResolveResource() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedValue value = new TypedValue();
boolean resolved = activity.getTheme().resolveAttribute(R.attr.logoHeight, value, false);
assertThat(resolved).isTrue();
assertThat(value.type).isEqualTo(TypedValue.TYPE_REFERENCE);
assertThat(value.data).isEqualTo(R.dimen.test_dp_dimen);
}
@Test public void withResolveRefsTrue_shouldResolveResource() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedValue value = new TypedValue();
boolean resolved = activity.getTheme().resolveAttribute(R.attr.logoHeight, value, true);
assertThat(resolved).isTrue();
assertThat(value.type).isEqualTo(TypedValue.TYPE_DIMENSION);
assertThat(value.resourceId).isEqualTo(R.dimen.test_dp_dimen);
assertThat(value.coerceToString()).isEqualTo("8.0dip");
}
@Test public void failToResolveCircularReference() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedValue value = new TypedValue();
boolean resolved = activity.getTheme().resolveAttribute(R.attr.isSugary, value, false);
assertThat(resolved).isFalse();
}
@Test public void canResolveAttrReferenceToDifferentPackage() throws Exception {
TestActivity activity = buildActivity(TestActivityWithAnotherTheme.class).create().get();
TypedValue value = new TypedValue();
boolean resolved = activity.getTheme().resolveAttribute(R.attr.styleReference, value, false);
assertThat(resolved).isTrue();
assertThat(value.type).isEqualTo(TypedValue.TYPE_REFERENCE);
assertThat(value.data).isEqualTo(R.style.Widget_AnotherTheme_Button);
}
@Test public void forStylesWithImplicitParents_shouldInheritValuesNotDefinedInChild() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric_ImplicitChild, true);
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.string3}).getString(0))
.isEqualTo("string 3 from Theme.Robolectric.ImplicitChild");
}
@Test public void whenAThemeHasExplicitlyEmptyParentAttr_shouldHaveNoParent() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric_EmptyParent, true);
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.string1}).hasValue(0)).isFalse();
}
@Test public void shouldApplyParentStylesFromAttrs() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_AnotherTheme, true);
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.AnotherTheme");
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.string3}).getString(0))
.isEqualTo("string 3 from Theme.Robolectric");
}
@Test
public void setTo_shouldCopyAllAttributesToEmptyTheme() throws Exception {
Resources.Theme theme1 = resources.newTheme();
theme1.applyStyle(R.style.Theme_Robolectric, false);
assertThat(theme1.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
Resources.Theme theme2 = resources.newTheme();
theme2.setTo(theme1);
assertThat(theme2.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
}
@Test
public void setTo_whenDestThemeIsModified_sourceThemeShouldNotMutate() throws Exception {
Resources.Theme sourceTheme = resources.newTheme();
sourceTheme.applyStyle(R.style.Theme_Robolectric, false);
assertThat(sourceTheme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
Resources.Theme destTheme = resources.newTheme();
destTheme.setTo(sourceTheme);
destTheme.applyStyle(R.style.Theme_AnotherTheme, true);
assertThat(sourceTheme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
}
@Test
public void setTo_whenSourceThemeIsModified_destThemeShouldNotMutate() throws Exception {
Resources.Theme sourceTheme = resources.newTheme();
sourceTheme.applyStyle(R.style.Theme_Robolectric, false);
assertThat(sourceTheme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
Resources.Theme destTheme = resources.newTheme();
destTheme.setTo(sourceTheme);
sourceTheme.applyStyle(R.style.Theme_AnotherTheme, true);
assertThat(destTheme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
}
@Test
public void applyStyle_withForceFalse_shouldApplyButNotOverwriteExistingAttributeValues() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
theme.applyStyle(R.style.Theme_AnotherTheme, false);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string2}).getString(0))
.isEqualTo("string 2 from Theme.AnotherTheme");
}
@Test
public void applyStyle_withForceTrue_shouldApplyAndOverwriteExistingAttributeValues() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
theme.applyStyle(R.style.Theme_AnotherTheme, true);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.AnotherTheme");
// force apply the original theme; values should be overwritten
theme.applyStyle(R.style.Theme_Robolectric, true);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
}
@Test
public void whenStyleSpecifiesAttr_obtainStyledAttribute_findsCorrectValue() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
theme.applyStyle(R.style.Theme_ThemeContainingStyleReferences, true);
assertThat(theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().setStyleAttribute("?attr/styleReference").build(), new int[]{R.attr.string2}, 0, 0).getString(0))
.isEqualTo("string 2 from StyleReferredToByParentAttrReference");
assertThat(theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().setStyleAttribute("?styleReference").build(), new int[]{R.attr.string2}, 0, 0).getString(0))
.isEqualTo("string 2 from StyleReferredToByParentAttrReference");
}
@Test
public void whenStyleSpecifiesAttrWithoutExplicitType_obtainStyledAttribute_findsCorrectValue() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
theme.applyStyle(R.style.Theme_ThemeContainingStyleReferences, true);
assertThat(theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().setStyleAttribute("?attr/styleReferenceWithoutExplicitType").build(), new int[]{R.attr.string2}, 0, 0).getString(0))
.isEqualTo("string 2 from StyleReferredToByParentAttrReference");
assertThat(theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().setStyleAttribute("?styleReferenceWithoutExplicitType").build(), new int[]{R.attr.string2}, 0, 0).getString(0))
.isEqualTo("string 2 from StyleReferredToByParentAttrReference");
}
@Test
public void whenAttrSetAttrSpecifiesAttr_obtainStyledAttribute_returnsItsValue() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
theme.applyStyle(R.style.Theme_ThemeContainingStyleReferences, true);
assertThat(theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().addAttribute(R.attr.string2, "?attr/string1").build(), new int[]{R.attr.string2}, 0, 0).getString(0))
.isEqualTo("string 1 from Theme.Robolectric");
}
@Test
public void whenAttrSetAttrSpecifiesUnknownAttr_obtainStyledAttribute_usesNull() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
theme.applyStyle(R.style.Theme_ThemeContainingStyleReferences, true);
assertThat(theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().addAttribute(R.attr.string2, "?attr/noSuchAttr").build(),
new int[]{R.attr.string2}, 0, 0).getString(0))
.isNull();
// todo: assert that a strict warning was displayed
}
@Test
public void forStrict_whenAttrSetAttrSpecifiesUnknownAttr_obtainStyledAttribute_throwsException() throws Exception {
shadowOf(resources.getAssets()).strictErrors = true;
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.Theme_Robolectric, false);
theme.applyStyle(R.style.Theme_ThemeContainingStyleReferences, true);
try {
theme.obtainStyledAttributes(
Robolectric.buildAttributeSet().addAttribute(R.attr.string2, "?attr/noSuchAttr").build(),
new int[]{R.attr.string2}, 0, 0);
fail();
} catch (Exception e) {
assertThat(e.getMessage()).contains("no such attr ?org.robolectric:attr/noSuchAttr");
assertThat(e.getMessage()).contains("Theme_ThemeContainingStyleReferences");
assertThat(e.getMessage()).contains("Theme_Robolectric");
assertThat(e.getMessage()).contains("while resolving value for org.robolectric:attr/string2");
}
}
@Test
public void shouldFindInheritedAndroidAttributeInTheme() throws Exception {
RuntimeEnvironment.application.setTheme(R.style.Theme_AnotherTheme);
Resources.Theme theme1 = RuntimeEnvironment.application.getTheme();
// Resources.Theme theme1 = resources.newTheme();
// theme1.setTo(RuntimeEnvironment.application.getTheme());
// theme1.applyStyle(R.style.Theme_AnotherTheme, false);
TypedArray typedArray = theme1.obtainStyledAttributes(
new int[]{R.attr.typeface, android.R.attr.buttonStyle});
assertThat(typedArray.hasValue(0)).isTrue(); // animalStyle
assertThat(typedArray.hasValue(1)).isTrue(); // layout_height
}
public static class TestActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.styles_button_layout);
}
}
@Test
public void themesShouldBeApplyableAcrossResources() throws Exception {
Resources.Theme themeFromSystem = Resources.getSystem().newTheme();
themeFromSystem.applyStyle(android.R.style.Theme_Light, true);
Resources.Theme themeFromApp = RuntimeEnvironment.application.getResources().newTheme();
themeFromApp.applyStyle(android.R.style.Theme, true);
// themeFromSystem is Theme_Light, which has a white background...
assertThat(shadowOf(themeFromSystem).obtainStyledAttributes(new int[]{android.R.attr.colorBackground}).getColor(0, 123))
.isEqualTo(Color.WHITE);
// themeFromApp is Theme, which has a black background...
assertThat(shadowOf(themeFromApp).obtainStyledAttributes(new int[]{android.R.attr.colorBackground}).getColor(0, 123))
.isEqualTo(Color.BLACK);
themeFromApp.setTo(themeFromSystem);
// themeFromApp now has style values from themeFromSystem, so now it has a black background...
assertThat(shadowOf(themeFromApp).obtainStyledAttributes(new int[]{android.R.attr.colorBackground}).getColor(0, 123))
.isEqualTo(Color.WHITE);
}
@Test
public void styleResolutionShouldIgnoreThemes() throws Exception {
Resources.Theme themeFromSystem = resources.newTheme();
themeFromSystem.applyStyle(android.R.style.Theme_DeviceDefault, true);
themeFromSystem.applyStyle(R.style.ThemeWithSelfReferencingTextAttr, true);
assertThat(themeFromSystem.obtainStyledAttributes(new int[]{android.R.attr.textAppearance})
.getResourceId(0, 0)).isEqualTo(0);
}
@Test
public void dimenRef() throws Exception {
AttributeSet attributeSet = Robolectric.buildAttributeSet()
.addAttribute(android.R.attr.layout_height, "@dimen/test_px_dimen")
.build();
TypedArray typedArray = resources.newTheme().obtainStyledAttributes(
attributeSet, new int[]{android.R.attr.layout_height}, 0, 0);
assertThat(typedArray.getDimensionPixelSize(0, -1)).isEqualTo(15);
}
@Test
public void dimenRefRef() throws Exception {
AttributeSet attributeSet = Robolectric.buildAttributeSet()
.addAttribute(android.R.attr.layout_height, "@dimen/ref_to_px_dimen")
.build();
TypedArray typedArray = resources.newTheme().obtainStyledAttributes(
attributeSet, new int[]{android.R.attr.layout_height}, 0, 0);
assertThat(typedArray.getDimensionPixelSize(0, -1)).isEqualTo(15);
}
public static class TestActivityWithAnotherTheme extends TestActivity {
}
public static class TestActivityWithAThirdTheme extends TestActivity {
}
@Test public void shouldApplyFromStyleAttribute() throws Exception {
TestWithStyleAttrActivity activity = buildActivity(TestWithStyleAttrActivity.class).create().get();
View button = activity.findViewById(R.id.button);
assertThat(button.getLayoutParams().width).isEqualTo(42); // comes via style attr
}
public static class TestWithStyleAttrActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.styles_button_with_style_layout);
}
}
}