From 86fb07068fb15c9f6fa27ce9cc2e0b0266c2f068 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Wed, 24 Jun 2015 21:03:26 +0300 Subject: [PATCH] Update theme resource references for legacy components (#17027) Change-Id: Id4f119b22d44f6abf63e730442e22a34e7c1953f --- .../com/vaadin/client/ui/VFilterSelect.java | 15 +- .../com/vaadin/client/ui/VScrollTable.java | 5 +- .../com/vaadin/client/ui/ui/UIConnector.java | 51 ++++++ .../themes/LegacyComponentThemeChange.java | 101 +++++++++++ .../LegacyComponentThemeChangeTest.java | 163 ++++++++++++++++++ 5 files changed, 325 insertions(+), 10 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java create mode 100644 uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index d99779b7ec..7951759fa2 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -79,6 +79,7 @@ import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.EventId; import com.vaadin.shared.ui.ComponentStateUtil; import com.vaadin.shared.ui.combobox.FilteringMode; +import com.vaadin.shared.util.SharedUtil; /** * Client side implementation of the Select component. @@ -98,7 +99,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, private final String key; private final String caption; - private String iconUri; + private String untranslatedIconUri; /** * Constructor @@ -110,8 +111,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, key = uidl.getStringAttribute("key"); caption = uidl.getStringAttribute("caption"); if (uidl.hasAttribute("icon")) { - iconUri = client.translateVaadinUri(uidl - .getStringAttribute("icon")); + untranslatedIconUri = uidl.getStringAttribute("icon"); } } @@ -124,7 +124,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, @Override public String getDisplayString() { final StringBuffer sb = new StringBuffer(); - final Icon icon = client.getIcon(iconUri); + final Icon icon = client.getIcon(client + .translateVaadinUri(untranslatedIconUri)); if (icon != null) { sb.append(icon.getElement().getString()); } @@ -164,7 +165,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * @return */ public String getIconUri() { - return iconUri; + return client.translateVaadinUri(untranslatedIconUri); } /** @@ -190,8 +191,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, || (caption != null && !caption.equals(other.caption))) { return false; } - if ((iconUri == null && other.iconUri != null) - || (iconUri != null && !iconUri.equals(other.iconUri))) { + if (!SharedUtil.equals(untranslatedIconUri, + other.untranslatedIconUri)) { return false; } return true; diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index 0ef0cb6949..6bb3199b08 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -1647,8 +1647,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, actionMap.put(key + "_c", caption); if (action.hasAttribute("icon")) { // TODO need some uri handling ?? - actionMap.put(key + "_i", client.translateVaadinUri(action - .getStringAttribute("icon"))); + actionMap.put(key + "_i", action.getStringAttribute("icon")); } else { actionMap.remove(key + "_i"); } @@ -1661,7 +1660,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } public String getActionIcon(String actionKey) { - return actionMap.get(actionKey + "_i"); + return client.translateVaadinUri(actionMap.get(actionKey + "_i")); } private void updateHeader(String[] strings) { diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java index 264b2de0e1..cfb444ada6 100644 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java @@ -1005,6 +1005,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector activeTheme); } + String oldThemeBase = getConnection().translateVaadinUri("theme://"); + activeTheme = newTheme; if (newTheme != null) { @@ -1013,12 +1015,61 @@ public class UIConnector extends AbstractSingleComponentContainerConnector activeTheme); updateVaadinFavicon(newTheme); + } forceStateChangeRecursively(UIConnector.this); + // UIDL has no stored URL which we can repaint so we do some find and + // replace magic... + String newThemeBase = getConnection().translateVaadinUri("theme://"); + replaceThemeAttribute(oldThemeBase, newThemeBase); + getLayoutManager().forceLayout(); } + /** + * Finds all attributes where theme:// urls have possibly been used and + * replaces any old theme url with a new one + * + * @param oldPrefix + * The start of the old theme URL + * @param newPrefix + * The start of the new theme URL + */ + private void replaceThemeAttribute(String oldPrefix, String newPrefix) { + // Images + replaceThemeAttribute("src", oldPrefix, newPrefix); + // Embedded flash + replaceThemeAttribute("value", oldPrefix, newPrefix); + replaceThemeAttribute("movie", oldPrefix, newPrefix); + } + + /** + * Finds any attribute of the given type where theme:// urls have possibly + * been used and replaces any old theme url with a new one + * + * @param attributeName + * The name of the attribute, e.g. "src" + * @param oldPrefix + * The start of the old theme URL + * @param newPrefix + * The start of the new theme URL + */ + private void replaceThemeAttribute(String attributeName, String oldPrefix, + String newPrefix) { + // Find all "attributeName=" which start with "oldPrefix" using e.g. + // [^src='http://oldpath'] + NodeList elements = querySelectorAll("[" + attributeName + + "^='" + oldPrefix + "']"); + for (int i = 0; i < elements.getLength(); i++) { + Element element = elements.getItem(i); + element.setAttribute( + attributeName, + element.getAttribute(attributeName).replace(oldPrefix, + newPrefix)); + } + } + /** * Force a full recursive recheck of every connector's state variables. * diff --git a/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java new file mode 100644 index 0000000000..4582123f5f --- /dev/null +++ b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.themes; + +import com.vaadin.annotations.Theme; +import com.vaadin.event.Action; +import com.vaadin.event.Action.Handler; +import com.vaadin.server.ThemeResource; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.tests.util.PersonContainer; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Embedded; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.MenuBar; +import com.vaadin.ui.Table; +import com.vaadin.ui.VerticalLayout; + +@Theme("reindeer") +public class LegacyComponentThemeChange extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + VerticalLayout vl = new VerticalLayout(); + vl.setCaption("Change theme by clicking a button"); + HorizontalLayout hl = new HorizontalLayout(); + for (final String theme : new String[] { "reindeer", "runo" }) { + Button b = new Button(theme); + b.setId(theme + ""); + b.addClickListener(new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + getUI().setTheme(theme); + } + }); + hl.addComponent(b); + } + vl.addComponent(hl); + + // Always wants to use icon from Runo, even if we change theme + ThemeResource alwaysTheSameIconImage = new ThemeResource( + "../runo/icons/16/ok.png"); + ThemeResource varyingIcon = new ThemeResource("menubar-theme-icon.png"); + MenuBar bar = new MenuBar(); + bar.addItem("runo", alwaysTheSameIconImage, null); + bar.addItem("seletedtheme", varyingIcon, null); + + vl.addComponent(bar); + + ComboBox cb = new ComboBox("ComboBox"); + cb.addItem("No icon"); + cb.addItem("Icon"); + cb.setItemIcon("Icon", new ThemeResource("comboboxicon.png")); + cb.setValue("Icon"); + + vl.addComponent(cb); + + Embedded e = new Embedded("embedded"); + e.setMimeType("application/x-shockwave-flash"); + e.setType(Embedded.TYPE_OBJECT); + e.setSource(new ThemeResource("embedded.src")); + vl.addComponent(e); + + Table t = new Table(); + t.addActionHandler(new Handler() { + @Override + public void handleAction(Action action, Object sender, Object target) { + } + + @Override + public Action[] getActions(Object target, Object sender) { + return new Action[] { new Action("Theme icon", + new ThemeResource("action-icon.png")) }; + } + }); + PersonContainer pc = PersonContainer.createWithTestData(); + pc.addNestedContainerBean("address"); + t.setContainerDataSource(pc); + vl.addComponent(t); + + addComponent(vl); + } + +} diff --git a/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java new file mode 100644 index 0000000000..c6593104da --- /dev/null +++ b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * 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.vaadin.tests.themes; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.ui.ExpectedCondition; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.ComboBoxElement; +import com.vaadin.testbench.elements.EmbeddedElement; +import com.vaadin.testbench.elements.MenuBarElement; +import com.vaadin.testbench.elements.TableElement; +import com.vaadin.testbench.parallel.Browser; +import com.vaadin.testbench.parallel.BrowserUtil; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class LegacyComponentThemeChangeTest extends MultiBrowserTest { + + @Override + public List getBrowsersToTest() { + // Seems like stylesheet onload is not fired on PhantomJS + // https://github.com/ariya/phantomjs/issues/12332 + List l = getBrowsersExcludingPhantomJS(); + + // For some reason, IE times out when trying to open the combobox, + // #18341 + l.remove(Browser.IE11.getDesiredCapabilities()); + return l; + } + + @Test + public void legacyComponentThemeResourceChange() { + openTestURL(); + String theme = "reindeer"; + assertMenubarTheme(theme); + assertCombobBoxTheme(theme); + assertTableTheme(theme); + assertEmbeddedTheme(theme); + + theme = "runo"; + changeTheme(theme); + assertMenubarTheme(theme); + assertCombobBoxTheme(theme); + assertTableTheme(theme); + assertEmbeddedTheme(theme); + + theme = "reindeer"; + changeTheme(theme); + assertMenubarTheme(theme); + assertCombobBoxTheme(theme); + assertTableTheme(theme); + assertEmbeddedTheme(theme); + + } + + private void assertEmbeddedTheme(String theme) { + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + // IE8 won't initialize the dummy flash properly + return; + } + EmbeddedElement e = $(EmbeddedElement.class).first(); + WebElement movieParam = e.findElement(By + .xpath(".//param[@name='movie']")); + WebElement embed = e.findElement(By.xpath(".//embed")); + assertAttributePrefix(movieParam, "value", theme); + assertAttributePrefix(embed, "src", theme); + assertAttributePrefix(embed, "movie", theme); + } + + private void assertTableTheme(String theme) { + TableElement t = $(TableElement.class).first(); + t.getRow(0).contextClick(); + WebElement popup = findElement(By.className("v-contextmenu")); + + WebElement actionImage = popup.findElement(By.xpath(".//img")); + assertAttributePrefix(actionImage, "src", theme); + } + + private void assertCombobBoxTheme(String theme) { + ComboBoxElement cb = $(ComboBoxElement.class).first(); + WebElement selectedImage = cb.findElement(By.xpath("./img")); + assertAttributePrefix(selectedImage, "src", theme); + + cb.openPopup(); + WebElement popup = findElement(By + .className("v-filterselect-suggestpopup")); + WebElement itemImage = popup.findElement(By.xpath(".//img")); + assertAttributePrefix(itemImage, "src", theme); + } + + private void assertMenubarTheme(String theme) { + // The runoImage must always come from Runo + WebElement runoImage = $(MenuBarElement.class).first().findElement( + By.xpath(".//span[text()='runo']/img")); + String runoImageSrc = runoImage.getAttribute("src"); + + // Something in Selenium normalizes the image so it becomes + // "/themes/runo/icons/16/ok.png" here although it is + // "/themes//../runo/icons/16/ok.png" in the browser + Assert.assertEquals(getThemeURL("runo") + "icons/16/ok.png", + runoImageSrc); + + // The other image should change with the theme + WebElement themeImage = $(MenuBarElement.class).first().findElement( + By.xpath(".//span[text()='seletedtheme']/img")); + assertAttributePrefix(themeImage, "src", theme); + } + + private void assertAttributePrefix(WebElement element, String attribute, + String theme) { + String value = element.getAttribute(attribute); + String expectedPrefix = getThemeURL(theme); + Assert.assertTrue("Attribute " + attribute + "='" + value + + "' does not start with " + expectedPrefix, + value.startsWith(expectedPrefix)); + + } + + private String getThemeURL(String theme) { + return getBaseURL() + "/VAADIN/themes/" + theme + "/"; + } + + private void changeTheme(String theme) { + $(ButtonElement.class).id(theme).click(); + waitForThemeToChange(theme); + } + + private void waitForThemeToChange(final String theme) { + + final WebElement rootDiv = findElement(By + .xpath("//div[contains(@class,'v-app')]")); + waitUntil(new ExpectedCondition() { + + @Override + public Boolean apply(WebDriver input) { + String rootClass = rootDiv.getAttribute("class").trim(); + + return rootClass.contains(theme); + } + }, 30); + } + +} -- 2.39.5