blob: 89ee4a558ceccba2f2eb43be8deda06b0f42cff1 [file] [log] [blame]
package org.robolectric.shadows;
import android.app.Activity;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.TypedValue;
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.annotation.Config;
import org.robolectric.shadows.ShadowActivityTest.TestActivityWithAnotherTheme;
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)
@Config(sdk = VERSION_CODES.O) // TODO: unpin sdk
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 obtainStyledAttributes_findsAttributeValueDefinedInDependencyLibrary() throws Exception {
Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleWithReferenceInLib1, false);
TypedArray a = theme.obtainStyledAttributes(new int[]{org.robolectric.R.attr.attrFromLib1});
assertThat(a.getString(0)).isEqualTo("value for attribute defined in lib1");
}
@Test public void shouldGetValuesFromAttributeReference() throws Exception {
Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleWithAttributeReference, false);
TypedValue value1 = new TypedValue();
TypedValue value2 = new TypedValue();
boolean resolved1 = theme.resolveAttribute(R.attr.anAttribute, value1, true);
boolean resolved2 = theme.resolveAttribute(R.attr.attributeReferencingAnAttribute, value2, true);
assertThat(resolved1).isTrue();
assertThat(resolved2).isTrue();
assertThat(value1.resourceId).isEqualTo(R.string.hello);
assertThat(value2.resourceId).isEqualTo(R.string.hello);
assertThat(value1.coerceToString()).isEqualTo(value2.coerceToString());
}
@Test public void withResolveRefsFalse_shouldNotResolveResource() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleWithReference, true);
TypedValue value = new TypedValue();
boolean resolved = theme.resolveAttribute(R.attr.stringReference, value, false);
assertThat(resolved).isTrue();
assertThat(value.type).isEqualTo(TypedValue.TYPE_REFERENCE);
assertThat(value.data).isEqualTo(R.string.hello);
}
@Test public void withResolveRefsTrue_shouldResolveResource() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleWithReference, true);
TypedValue value = new TypedValue();
boolean resolved = theme.resolveAttribute(R.attr.stringReference, value, true);
assertThat(resolved).isTrue();
assertThat(value.type).isEqualTo(TypedValue.TYPE_STRING);
assertThat(value.resourceId).isEqualTo(R.string.hello);
assertThat(value.string).isEqualTo("Hello");
}
@Test public void failToResolveCircularReference() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleWithCircularReference, true);
TypedValue value = new TypedValue();
boolean resolved = theme.resolveAttribute(R.attr.circularReference, 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 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 applyStyle_shouldOverrideParentAttrs() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.SimpleChildWithOverride, true);
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.parent_string}).getString(0))
.isEqualTo("parent string overridden by child");
}
@Test public void applyStyle_shouldOverrideImplicitParentAttrs() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.SimpleParent_ImplicitChild, true);
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.parent_string}).getString(0))
.isEqualTo("parent string overridden by child");
}
@Test public void applyStyle_shouldInheritParentAttrs() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.SimpleChildWithAdditionalAttributes, true);
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.child_string}).getString(0))
.isEqualTo("child string");
assertThat(theme.obtainStyledAttributes(new int[] {R.attr.parent_string}).getString(0))
.isEqualTo("parent string");
}
@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.StyleA, false);
Resources.Theme destTheme = resources.newTheme();
destTheme.setTo(sourceTheme);
destTheme.applyStyle(R.style.StyleB, true);
assertThat(sourceTheme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from style A");
}
@Test
public void setTo_whenSourceThemeIsModified_destThemeShouldNotMutate() throws Exception {
Resources.Theme sourceTheme = resources.newTheme();
sourceTheme.applyStyle(R.style.StyleA, false);
Resources.Theme destTheme = resources.newTheme();
destTheme.setTo(sourceTheme);
sourceTheme.applyStyle(R.style.StyleB, true);
assertThat(destTheme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from style A");
}
@Test
public void applyStyle_withForceFalse_shouldApplyButNotOverwriteExistingAttributeValues() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleA, false);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from style A");
theme.applyStyle(R.style.StyleB, false);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from style A");
}
@Test
public void applyStyle_withForceTrue_shouldApplyAndOverwriteExistingAttributeValues() throws Exception {
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleA, false);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from style A");
theme.applyStyle(R.style.StyleB, true);
assertThat(theme.obtainStyledAttributes(new int[]{R.attr.string1}).getString(0))
.isEqualTo("string 1 from style B");
}
@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 {
ShadowAssetManager.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);
}
@Test public void obtainStyledAttributes_shouldFindAttributeInDefaultStyle() throws Exception {
Theme theme = resources.newTheme();
TypedArray typedArray = theme.obtainStyledAttributes(R.style.StyleA, new int[]{R.attr.string1});
assertThat(typedArray.getString(0)).isEqualTo("string 1 from style A");
}
@Test public void obtainStyledAttributes_shouldFindAttributeInDefaultStyleBeforeTheme() throws Exception {
Theme theme = resources.newTheme();
theme.applyStyle(R.style.StyleB, false); // string1 is also defined in StyleB but default style gets precedence over themes
TypedArray typedArray = theme.obtainStyledAttributes(R.style.StyleA, new int[]{R.attr.string1});
assertThat(typedArray.getString(0)).isEqualTo("string 1 from style A");
}
@Test public void shouldApplyFromStyleAttributes() throws Exception {
// TODO: Instead of using activity create a AttributeSet with a style attribute to keep the test more focused.
// 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);
}
}
}