| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.idea.editors.theme; |
| |
| import com.android.SdkConstants; |
| import com.android.ide.common.rendering.api.ItemResourceValue; |
| import com.android.ide.common.resources.configuration.FolderConfiguration; |
| import com.android.tools.idea.configurations.Configuration; |
| import com.android.tools.idea.configurations.ConfigurationManager; |
| import com.android.tools.idea.editors.theme.datamodels.ConfiguredItemResourceValue; |
| import com.android.tools.idea.editors.theme.datamodels.EditedStyleItem; |
| import com.android.tools.idea.editors.theme.datamodels.ThemeEditorStyle; |
| import com.android.tools.idea.rendering.multi.CompatibilityRenderTarget; |
| import com.android.tools.idea.tests.gui.theme.ThemeEditorTestUtils; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import org.jetbrains.android.AndroidTestCase; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.annotation.Nullable; |
| import java.util.Collection; |
| import java.util.Set; |
| |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| |
| public class ThemeEditorStyleTest extends AndroidTestCase { |
| |
| public ThemeEditorStyleTest() { |
| super(false); |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| myFixture.copyFileToProject("themeEditor/manifestWithApi.xml", SdkConstants.FN_ANDROID_MANIFEST_XML); |
| } |
| |
| @Override |
| protected boolean requireRecentSdk() { |
| return true; |
| } |
| |
| public void testHasItem() { |
| VirtualFile myFile = myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/attrs.xml", "res/values/attrs.xml"); |
| |
| Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); |
| |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| ThemeEditorStyle theme = themeResolver.getTheme("@style/AppTheme"); |
| assertNotNull(theme); |
| ThemeEditorStyle parent = theme.getParent(); |
| assertNotNull(parent); |
| |
| FolderConfiguration defaultConfig = new FolderConfiguration(); |
| ConfiguredItemResourceValue hasItem = new ConfiguredItemResourceValue(defaultConfig, new ItemResourceValue("myColor", false, "?android:attr/colorBackground", false), theme); |
| ConfiguredItemResourceValue hasNotItem = new ConfiguredItemResourceValue(defaultConfig, new ItemResourceValue("myHasNot", false, "?android:attr/colorBackground", false), theme); |
| ConfiguredItemResourceValue hasInParent = new ConfiguredItemResourceValue(defaultConfig, new ItemResourceValue("editTextStyle", true, "?android:attr/colorBackground", true), theme); |
| assertTrue(theme.hasItem(new EditedStyleItem(hasItem, theme))); |
| assertFalse(theme.hasItem(new EditedStyleItem(hasNotItem, theme))); |
| assertTrue(theme.getParent().hasItem(new EditedStyleItem(hasInParent, parent))); |
| assertFalse(theme.hasItem(new EditedStyleItem(hasInParent, parent))); |
| } |
| |
| private void doTestForAttributeValuePairApi(@NotNull String attribute, @NotNull String value, @NotNull String afterDirectoryName) { |
| VirtualFile virtualFile = myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi.xml", "res/values/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v14.xml", "res/values-v14/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v19.xml", "res/values-v19/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v21.xml", "res/values-v21/styles.xml"); |
| |
| ConfigurationManager configurationManager = myFacet.getConfigurationManager(); |
| Configuration configuration = configurationManager.getConfiguration(virtualFile); |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| ThemeEditorStyle theme = themeResolver.getTheme("@style/Theme.MyTheme"); |
| |
| assertNotNull(theme); |
| theme.setValue(attribute, value); |
| |
| myFixture.checkResultByFile("res/values/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi.xml", true); |
| myFixture.checkResultByFile("res/values-v14/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi-v14.xml", true); |
| myFixture.checkResultByFile("res/values-v19/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi-v19.xml", true); |
| myFixture.checkResultByFile("res/values-v21/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi-v21.xml", true); |
| } |
| |
| private void doTestForAttributeApi(@NotNull String attribute, @NotNull String afterDirectoryName) { |
| doTestForAttributeValuePairApi(attribute, "myValue", afterDirectoryName); |
| } |
| |
| private void doTestForParentApi(@NotNull String newParent, @NotNull String afterDirectoryName) { |
| VirtualFile virtualFile = myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi.xml", "res/values/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v14.xml", "res/values-v14/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v19.xml", "res/values-v19/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v21.xml", "res/values-v21/styles.xml"); |
| |
| ConfigurationManager configurationManager = myFacet.getConfigurationManager(); |
| Configuration configuration = configurationManager.getConfiguration(virtualFile); |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| ThemeEditorStyle theme = themeResolver.getTheme("@style/Theme.MyTheme"); |
| |
| assertNotNull(theme); |
| theme.setParent(newParent); |
| |
| myFixture.checkResultByFile("res/values/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi.xml", true); |
| myFixture.checkResultByFile("res/values-v14/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi-v14.xml", true); |
| myFixture.checkResultByFile("res/values-v19/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi-v19.xml", true); |
| myFixture.checkResultByFile("res/values-v21/styles.xml", "themeEditor/" + afterDirectoryName + "/stylesApi-v21.xml", true); |
| } |
| |
| private void doTestForValueApi(@NotNull String value, @NotNull String afterDirectoryName) { |
| doTestForAttributeValuePairApi("android:colorBackground", value, afterDirectoryName); |
| } |
| |
| /** |
| * Tests setting a non-framework attribute |
| */ |
| public void testNonFrameworkAttribute() { |
| doTestForAttributeApi("myAttribute", "apiTestAfter1"); |
| } |
| |
| /** |
| * Tests modifying an attribute defined for api < projectMinApi |
| */ |
| public void testSmallApiAttribute() { |
| doTestForAttributeApi("android:windowIsFloating", "apiTestAfter2"); |
| } |
| |
| /** |
| * Tests setting an attribute = projectMinApi |
| */ |
| public void testMinApiAttribute() { |
| doTestForAttributeApi("android:actionBarSize", "apiTestAfter3"); |
| } |
| |
| /** |
| * Tests setting an attribute with api that has no associated values folder |
| */ |
| public void testHighNewApiAttribute() { |
| doTestForAttributeApi("android:windowOverscan", "apiTestAfter4"); |
| myFixture.checkResultByFile("res/values-v18/styles.xml", "themeEditor/apiTestAfter4/stylesApi-v18.xml", true); |
| } |
| |
| /** |
| * Tests setting an attribute with api that has an associated values folder |
| */ |
| public void testHighExistingApiAttribute() { |
| doTestForAttributeApi("android:windowTranslucentStatus", "apiTestAfter5"); |
| } |
| |
| /** |
| * Tests modifying an attribute overridden in values folder with api > attribute api |
| * but that is not overridden in values folder corresponding to attribute api. |
| * The test should modify the attribute where it is overridden but not override it |
| * where it is not. |
| */ |
| public void testModifyingOverriddenAttribute() { |
| doTestForAttributeApi("android:checkedTextViewStyle", "apiTestAfter6"); |
| } |
| |
| /** |
| * Tests modifying an attribute defined for api > projectMinApi |
| */ |
| public void testModifyingHighApiAttribute() { |
| doTestForAttributeApi("android:actionModeStyle", "apiTestAfter7"); |
| } |
| |
| /** |
| * Test setting a low-api attributes and parent in a theme defined only in higher api files |
| */ |
| public void testSettingInHighApiTheme() { |
| VirtualFile virtualFile = myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi.xml", "res/values/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v14.xml", "res/values-v14/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v19.xml", "res/values-v19/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/apiTestBefore/stylesApi-v21.xml", "res/values-v21/styles.xml"); |
| |
| ConfigurationManager configurationManager = myFacet.getConfigurationManager(); |
| Configuration configuration = configurationManager.getConfiguration(virtualFile); |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| ThemeEditorStyle theme = themeResolver.getTheme("@style/Theme.MyOtherTheme"); |
| |
| assertNotNull(theme); |
| theme.setValue("android:windowIsFloating", "holo_purple"); |
| theme.setValue("android:actionBarDivider", "myValue"); |
| theme.setParent("@android:style/Theme.Holo.Light.DarkActionBar"); |
| |
| |
| myFixture.checkResultByFile("res/values/styles.xml", "themeEditor/apiTestAfter8/stylesApi.xml", true); |
| myFixture.checkResultByFile("res/values-v14/styles.xml", "themeEditor/apiTestAfter8/stylesApi-v14.xml", true); |
| myFixture.checkResultByFile("res/values-v19/styles.xml", "themeEditor/apiTestAfter8/stylesApi-v19.xml", true); |
| myFixture.checkResultByFile("res/values-v21/styles.xml", "themeEditor/apiTestAfter8/stylesApi-v21.xml", true); |
| } |
| |
| /** |
| * Tests setting a non-framework parent |
| */ |
| public void testNonFrameworkParent() { |
| doTestForParentApi("@style/MyStyle", "apiParentTestAfter1"); |
| } |
| |
| /** |
| * Tests setting a parent defined for api < projectMinApi |
| */ |
| public void testSmallApiParent() { |
| doTestForParentApi("@android:style/Theme.Light", "apiParentTestAfter2"); |
| } |
| |
| /** |
| * Tests setting a parent with api = projectMinApi |
| */ |
| public void testMinApiParent() { |
| doTestForParentApi("@android:style/Theme.Holo", "apiParentTestAfter3"); |
| } |
| |
| /** |
| * Tests setting a parent with api that has no associated values folder |
| */ |
| public void testHighNewApiParent() { |
| doTestForParentApi("@android:style/Theme.Holo.NoActionBar.Overscan", "apiParentTestAfter4"); |
| myFixture.checkResultByFile("res/values-v18/styles.xml", "themeEditor/apiParentTestAfter4/stylesApi-v18.xml", true); |
| } |
| |
| /** |
| * Tests setting a parent with api that has an associated values folder |
| */ |
| public void testHighExistingApiParent() { |
| doTestForParentApi("@android:style/Theme.Holo.NoActionBar.TranslucentDecor", "apiParentTestAfter5"); |
| } |
| |
| /** |
| * Tests setting a value defined for api < projectMinApi |
| */ |
| public void testSmallApiValue() { |
| doTestForValueApi("@android:color/darker_gray", "apiValueTestAfter1"); |
| } |
| |
| /** |
| * Tests setting a value with api = projectMinApi |
| */ |
| public void testMinApiValue() { |
| doTestForValueApi("@android:drawable/dialog_holo_dark_frame", "apiValueTestAfter2"); |
| } |
| |
| /** |
| * Tests setting a value with api that has no associated values folder |
| */ |
| public void testHighNewApiValue() { |
| doTestForValueApi("@android:style/Theme.Holo.NoActionBar.Overscan", "apiValueTestAfter3"); |
| myFixture.checkResultByFile("res/values-v18/styles.xml", "themeEditor/apiValueTestAfter3/stylesApi-v18.xml", true); |
| } |
| |
| /** |
| * Tests setting a value with api that has an associated values folder |
| */ |
| public void testHighExistingApiValue() { |
| doTestForValueApi("@android:style/Theme.Holo.NoActionBar.TranslucentDecor", "apiValueTestAfter4"); |
| } |
| |
| /** |
| * Tests with attribute api < value api |
| */ |
| public void testSmallerAttribute() { |
| doTestForAttributeValuePairApi("android:mediaRouteButtonStyle", |
| "@android:style/Theme.Holo.NoActionBar.TranslucentDecor", "apiPairTestAfter1"); |
| } |
| |
| /** |
| * Tests with attribute api > value api |
| */ |
| public void testSmallerValue() { |
| doTestForAttributeValuePairApi("android:windowOverscan", "@android:color/holo_red_dark", "apiPairTestAfter2"); |
| } |
| |
| /** |
| * Tests the isPublic() method |
| */ |
| public void testIsPublic() { |
| VirtualFile myFile = myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values/styles.xml"); |
| |
| Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); |
| |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| |
| // Non-framework themes are always public |
| ThemeEditorStyle projectTheme = themeResolver.getTheme("@style/AppTheme"); |
| assertNotNull(projectTheme); |
| assertTrue(projectTheme.isPublic()); |
| |
| ThemeEditorStyle frameworkPublicTheme = themeResolver.getTheme("@android:style/Theme.Material"); |
| assertNotNull(frameworkPublicTheme); |
| assertTrue(frameworkPublicTheme.isPublic()); |
| |
| ThemeEditorStyle frameworkPrivateTheme = themeResolver.getTheme("@android:style/Theme.Material.Dialog.NoFrame"); |
| assertNotNull(frameworkPrivateTheme); |
| assertFalse(frameworkPrivateTheme.isPublic()); |
| } |
| |
| /** |
| * Tests attributes present in multiple qualifiers |
| */ |
| public void testQualifiers() { |
| myFixture.copyFileToProject("themeEditor/qualifiers/stylesApi-v14.xml", "res/values-v14/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/qualifiers/stylesApi-v19.xml", "res/values-v19/styles.xml"); |
| VirtualFile myFile = myFixture.copyFileToProject("themeEditor/qualifiers/stylesApi-v21.xml", "res/values-v21/styles.xml"); |
| |
| Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); |
| assertNotNull(configuration.getTarget()); |
| configuration.setTarget(new CompatibilityRenderTarget(configuration.getTarget(), 22, null)); |
| |
| ThemeEditorStyle myTheme = ResolutionUtils.getStyle(configuration, "@style/Theme.MyTheme", null); |
| assertNotNull(myTheme); |
| Set<String> expectedAttributes = Sets.newHashSet("actionModeStyle", "windowIsFloating", "checkedTextViewStyle"); |
| for(EditedStyleItem item : ThemeEditorTestUtils.getStyleLocalValues(myTheme)) { |
| assertTrue(expectedAttributes.remove(item.getName())); |
| |
| if ("windowIsFloating".equals(item.getName())) { |
| Set<String> seenConfigurations = Sets.newHashSet(); |
| // We should have selected the highest |
| assertEquals("-v21", item.getSelectedValueConfiguration().getUniqueKey()); |
| // In the non-selected list we have 2 from the local theme and 1 inherited from @android:style/Theme for "default" |
| assertSize(3, item.getNonSelectedItemResourceValues()); |
| seenConfigurations.add(item.getSelectedValueConfiguration().toString()); |
| // The other values can not be default or repeated |
| for(ConfiguredItemResourceValue value : item.getNonSelectedItemResourceValues()) { |
| String configName = value.getConfiguration().toString(); |
| assertFalse(seenConfigurations.contains(configName)); |
| seenConfigurations.add(configName); |
| } |
| } |
| else if ("actionModeStyle".equals(item.getName())) { |
| // actionModeStyle is only in two configurations v21 and v14 but it has different values |
| assertSize(3, item.getNonSelectedItemResourceValues()); |
| assertEquals("-v21", item.getSelectedValueConfiguration().getUniqueKey()); |
| assertEquals("@null", item.getValue()); |
| assertTrue(Iterables.any(item.getNonSelectedItemResourceValues(), new Predicate<ConfiguredItemResourceValue>() { |
| @Override |
| public boolean apply(@Nullable ConfiguredItemResourceValue input) { |
| assert input != null; |
| return "-v14".equals(input.getConfiguration().getUniqueKey()) && |
| "@style/ActionModeStyle".equals(input.getItemResourceValue().getValue()); |
| } |
| })); |
| } |
| } |
| assertEmpty(expectedAttributes); |
| |
| // Test with a v14 configuration |
| configuration.setTarget(new CompatibilityRenderTarget(configuration.getTarget(), 14, null)); |
| myTheme = ResolutionUtils.getStyle(configuration, "@style/Theme.MyTheme", null); |
| assertNotNull(myTheme); |
| |
| Collection<EditedStyleItem> values = ThemeEditorTestUtils.getStyleLocalValues(myTheme); |
| |
| assertSize(3, values); |
| for(EditedStyleItem item : values) { |
| if ("windowIsFloating".equals(item.getName())) { |
| // We should have selected the v14 |
| assertEquals("-v14", item.getSelectedValueConfiguration().getUniqueKey()); |
| assertSize(3, item.getNonSelectedItemResourceValues()); |
| |
| for(ConfiguredItemResourceValue value : item.getNonSelectedItemResourceValues()) { |
| assertNotEquals("-v14", value.getConfiguration().getUniqueKey()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Test that setValue only affects the specified folders |
| */ |
| public void testSetValueFolderFiltering() { |
| VirtualFile myFile = myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values-v14/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values-land/styles.xml"); |
| |
| Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| |
| ThemeEditorStyle style = themeResolver.getTheme("@style/AppTheme"); |
| assertNotNull(style); |
| |
| // This should modify not modify the default folder |
| //noinspection ConstantConditions |
| style.setValue( |
| ImmutableList.of(FolderConfiguration.getConfigForQualifierString("land"), FolderConfiguration.getConfigForQualifierString("v14")), |
| "myColor", "modified"); |
| myFixture.checkResultByFile("res/values/styles.xml", "themeEditor/styles_1.xml", true); |
| myFixture.checkResultByFile("res/values-v14/styles.xml", "themeEditor/setValueAfter/styles_myColor_modified.xml", true); |
| myFixture.checkResultByFile("res/values-land/styles.xml", "themeEditor/setValueAfter/styles_myColor_modified.xml", true); |
| } |
| |
| /** |
| * Test that setParent only affects the specified folders |
| */ |
| public void testSetParent() { |
| VirtualFile myFile = myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values-v14/styles.xml"); |
| myFixture.copyFileToProject("themeEditor/styles_1.xml", "res/values-land/styles.xml"); |
| |
| Configuration configuration = myFacet.getConfigurationManager().getConfiguration(myFile); |
| ThemeResolver themeResolver = new ThemeResolver(configuration); |
| |
| ThemeEditorStyle style = themeResolver.getTheme("@style/AppTheme"); |
| assertNotNull(style); |
| |
| // This should modify not modify the default folder |
| //noinspection ConstantConditions |
| style.setParent( |
| ImmutableList.of(FolderConfiguration.getConfigForQualifierString("land"), FolderConfiguration.getConfigForQualifierString("v14")), |
| "@android:style/Theme.Modified"); |
| myFixture.checkResultByFile("res/values/styles.xml", "themeEditor/styles_1.xml", true); |
| myFixture.checkResultByFile("res/values-v14/styles.xml", "themeEditor/setValueAfter/styles_parent_modified.xml", true); |
| myFixture.checkResultByFile("res/values-land/styles.xml", "themeEditor/setValueAfter/styles_parent_modified.xml", true); |
| } |
| } |