diff options
Diffstat (limited to 'server/src')
24 files changed, 1725 insertions, 286 deletions
diff --git a/server/src/main/java/com/vaadin/ui/AbstractListing.java b/server/src/main/java/com/vaadin/ui/AbstractListing.java index f49d8715de..ce44bb8352 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractListing.java +++ b/server/src/main/java/com/vaadin/ui/AbstractListing.java @@ -15,14 +15,25 @@ */ package com.vaadin.ui; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + import com.vaadin.data.Listing; import com.vaadin.data.SelectionModel; import com.vaadin.server.AbstractExtension; +import com.vaadin.server.Resource; import com.vaadin.server.data.DataCommunicator; import com.vaadin.server.data.DataGenerator; import com.vaadin.server.data.DataProvider; +import com.vaadin.server.data.Query; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; +import com.vaadin.ui.declarative.DesignFormatter; /** * A base class for listing components. Provides common handling for fetching @@ -37,6 +48,16 @@ import com.vaadin.server.data.DataProvider; */ public abstract class AbstractListing<T> extends AbstractComponent implements Listing<T> { + /** + * The item icon caption provider. + */ + private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; + + /** + * The item icon provider. It is up to the implementing class to support + * this or not. + */ + private IconGenerator<T> itemIconGenerator = item -> null; /** * A helper base class for creating extensions for Listing components. This @@ -143,6 +164,64 @@ public abstract class AbstractListing<T> extends AbstractComponent } /** + * Gets the item caption generator that is used to produce the strings shown + * in the combo box for each item. + * + * @return the item caption generator used, not null + */ + protected ItemCaptionGenerator<T> getItemCaptionGenerator() { + return itemCaptionGenerator; + } + + /** + * Sets the item caption generator that is used to produce the strings shown + * in the combo box for each item. By default, + * {@link String#valueOf(Object)} is used. + * + * @param itemCaptionGenerator + * the item caption provider to use, not null + */ + protected void setItemCaptionGenerator( + ItemCaptionGenerator<T> itemCaptionGenerator) { + Objects.requireNonNull(itemCaptionGenerator, + "Item caption generators must not be null"); + this.itemCaptionGenerator = itemCaptionGenerator; + getDataCommunicator().reset(); + } + + /** + * Sets the item icon generator that is used to produce custom icons for + * showing items in the popup. The generator can return null for items with + * no icon. + * + * @see IconGenerator + * + * @param itemIconGenerator + * the item icon generator to set, not null + * @throws NullPointerException + * if {@code itemIconGenerator} is {@code null} + */ + protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { + Objects.requireNonNull(itemIconGenerator, + "Item icon generator must not be null"); + this.itemIconGenerator = itemIconGenerator; + getDataCommunicator().reset(); + } + + /** + * Gets the currently used item icon generator. The default item icon + * provider returns null for all items, resulting in no icons being used. + * + * @see IconGenerator + * @see #setItemIconGenerator(IconGenerator) + * + * @return the currently used item icon generator, not null + */ + protected IconGenerator<T> getItemIconGenerator() { + return itemIconGenerator; + } + + /** * Adds the given data generator to this listing. If the generator was * already added, does nothing. * @@ -172,4 +251,183 @@ public abstract class AbstractListing<T> extends AbstractComponent public DataCommunicator<T> getDataCommunicator() { return dataCommunicator; } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + + // Write options if warranted + if (designContext.shouldWriteData(this)) { + writeItems(design, designContext); + } + + AbstractListing<T> select = designContext.getDefaultInstance(this); + Attributes attr = design.attributes(); + DesignAttributeHandler.writeAttribute("readonly", attr, isReadOnly(), + select.isReadOnly(), Boolean.class, designContext); + } + + /** + * Writes the data source items to a design. Hierarchical select components + * should override this method to only write the root items. + * + * @param design + * the element into which to insert the items + * @param context + * the DesignContext instance used in writing + */ + protected void writeItems(Element design, DesignContext context) { + getDataProvider().fetch(new Query<>()) + .forEach(item -> writeItem(design, item, context)); + } + + /** + * Writes a data source Item to a design. Hierarchical select components + * should override this method to recursively write any child items as well. + * + * @param design + * the element into which to insert the item + * @param item + * the item to write + * @param context + * the DesignContext instance used in writing + * @return a JSOUP element representing the {@code item} + */ + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = design.appendElement("option"); + + String caption = getItemCaptionGenerator().apply(item); + if (caption != null) { + element.html(DesignFormatter.encodeForTextNode(caption)); + } else { + element.html(DesignFormatter.encodeForTextNode(item.toString())); + } + element.attr("item", serializeDeclarativeRepresentation(item)); + + Resource icon = getItemIconGenerator().apply(item); + if (icon != null) { + DesignAttributeHandler.writeAttribute("icon", element.attributes(), + icon, null, Resource.class, context); + } + + return element; + } + + @Override + public void readDesign(Element design, DesignContext context) { + super.readDesign(design, context); + Attributes attr = design.attributes(); + if (attr.hasKey("readonly")) { + setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr, + Boolean.class)); + } + readItems(design, context); + } + + /** + * Reads the data source items from the {@code design}. + * + * @param design + * The element to obtain the state from + * @param context + * The DesignContext instance used for parsing the design + */ + protected void readItems(Element design, DesignContext context) { + setItemCaptionGenerator(new DeclarativeCaptionGenerator<>()); + setItemIconGenerator(new DeclarativeIconGenerator<>()); + } + + /** + * Reads an Item from a design and inserts it into the data source. + * <p> + * Doesn't care about selection/value (if any). + * + * @param child + * a child element representing the item + * @param context + * the DesignContext instance used in parsing + * @return the item id of the new item + * + * @throws DesignException + * if the tag name of the {@code child} element is not + * {@code option}. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected T readItem(Element child, DesignContext context) { + if (!"option".equals(child.tagName())) { + throw new DesignException("Unrecognized child element in " + + getClass().getSimpleName() + ": " + child.tagName()); + } + + String serializedItem = ""; + String caption = DesignFormatter.decodeFromTextNode(child.html()); + List<T> items = new ArrayList<>(); + if (child.hasAttr("item")) { + serializedItem = child.attr("item"); + } + + T item = deserializeDeclarativeRepresentation(serializedItem); + items.add(item); + + ItemCaptionGenerator<T> captionGenerator = getItemCaptionGenerator(); + if (captionGenerator instanceof DeclarativeCaptionGenerator) { + ((DeclarativeCaptionGenerator) captionGenerator).setCaption(item, + caption); + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to set caption using current caption generator '%s'", + captionGenerator.getClass().getName())); + } + + IconGenerator<T> iconGenerator = getItemIconGenerator(); + if (child.hasAttr("icon")) { + if (iconGenerator instanceof DeclarativeIconGenerator) { + ((DeclarativeIconGenerator) iconGenerator).setIcon(item, + DesignAttributeHandler.readAttribute("icon", + child.attributes(), Resource.class)); + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to set icon using current caption generator '%s'", + iconGenerator.getClass().getName())); + } + } + + return item; + } + + /** + * Deserializes a string to a data item. + * <p> + * Default implementation is able to handle only {@link String} as an item + * type. There will be a {@link ClassCastException} if {@code T } is not a + * {@link String}. + * + * @see #serializeDeclarativeRepresentation(Object) + * + * @param item + * string to deserialize + * @throws ClassCastException + * if type {@code T} is not a {@link String} + * @return deserialized item + */ + protected T deserializeDeclarativeRepresentation(String item) { + return (T) item; + } + + /** + * Serializes an {@code item} to a string for saving declarative format. + * <p> + * Default implementation delegates a call to {@code item.toString()}. + * + * @see #serializeDeclarativeRepresentation(Object) + * + * @param item + * a data item + * @return string representation of the {@code item}. + */ + protected String serializeDeclarativeRepresentation(T item) { + return item.toString(); + } } diff --git a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java index 3c85496e41..5ae0ee4ebd 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java @@ -16,7 +16,9 @@ package com.vaadin.ui; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; @@ -25,6 +27,8 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.jsoup.nodes.Element; + import com.vaadin.data.HasValue; import com.vaadin.data.SelectionModel; import com.vaadin.data.SelectionModel.Multi; @@ -38,6 +42,8 @@ import com.vaadin.shared.AbstractFieldState; import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.MultiSelectServerRpc; import com.vaadin.shared.ui.ListingJsonConstants; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.util.ReflectTools; import elemental.json.JsonObject; @@ -121,17 +127,6 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> MultiSelectionEvent.class); /** - * The item icon caption provider. - */ - private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; - - /** - * The item icon provider. It is up to the implementing class to support - * this or not. - */ - private IconGenerator<T> itemIconGenerator = item -> null; - - /** * The item enabled status provider. It is up to the implementing class to * support this or not. */ @@ -163,30 +158,15 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> SELECTION_CHANGE_METHOD); } - /** - * Gets the item caption generator that is used to produce the strings shown - * in the select for each item. - * - * @return the item caption generator used, not {@code null} - * @see #setItemCaptionGenerator(ItemCaptionGenerator) - */ + @Override public ItemCaptionGenerator<T> getItemCaptionGenerator() { - return itemCaptionGenerator; + return super.getItemCaptionGenerator(); } - /** - * Sets the item caption generator that is used to produce the strings shown - * in the select for each item. By default, {@link String#valueOf(Object)} - * is used. - * - * @param itemCaptionGenerator - * the item caption generator to use, not {@code null} - */ + @Override public void setItemCaptionGenerator( ItemCaptionGenerator<T> itemCaptionGenerator) { - Objects.requireNonNull(itemCaptionGenerator); - this.itemCaptionGenerator = itemCaptionGenerator; - getDataCommunicator().reset(); + super.setItemCaptionGenerator(itemCaptionGenerator); } /** @@ -255,40 +235,6 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> } /** - * Returns the item icon generator for this multiselect. - * <p> - * <em>Implementation note:</em> Override this method and - * {@link #setItemIconGenerator(IconGenerator)} as {@code public} and invoke - * {@code super} methods to support this feature in the multiselect - * component. - * - * @return the item icon generator, not {@code null} - * @see #setItemIconGenerator(IconGenerator) - */ - protected IconGenerator<T> getItemIconGenerator() { - return itemIconGenerator; - } - - /** - * Sets the item icon generator for this multiselect. The icon generator is - * queried for each item to optionally display an icon next to the item - * caption. If the generator returns null for an item, no icon is displayed. - * The default provider always returns null (no icons). - * <p> - * <em>Implementation note:</em> Override this method and - * {@link #getItemIconGenerator()} as {@code public} and invoke - * {@code super} methods to support this feature in the multiselect - * component. - * - * @param itemIconGenerator - * the item icon generator to set, not {@code null} - */ - protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { - Objects.requireNonNull(itemIconGenerator); - this.itemIconGenerator = itemIconGenerator; - } - - /** * Returns the item enabled provider for this multiselect. * <p> * <em>Implementation note:</em> Override this method and @@ -461,6 +407,66 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> updateSelection(set -> set.add(item), userOriginated); } + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> attributes = super.getCustomAttributes(); + // "value" is not an attribute for the component. "selected" attribute + // is used in "option"'s tag to mark selection which implies value for + // multiselect component + attributes.add("value"); + return attributes; + } + + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = super.writeItem(design, item, context); + + if (isSelected(item)) { + element.attr("selected", ""); + } + + return element; + } + + @Override + protected void readItems(Element design, DesignContext context) { + super.readItems(design, context); + Set<T> selected = new HashSet<>(); + design.children().stream() + .forEach(child -> readItem(child, selected, context)); + deselectAll(); + selected.forEach(this::select); + } + + /** + * Reads an Item from a design and inserts it into the data source. + * Hierarchical select components should override this method to recursively + * recursively read any child items as well. + * + * @param child + * a child element representing the item + * @param selected + * A set accumulating selected items. If the item that is read is + * marked as selected, its item id should be added to this set. + * @param context + * the DesignContext instance used in parsing + * @return the item id of the new item + * + * @throws DesignException + * if the tag name of the {@code child} element is not + * {@code option}. + */ + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = readItem(child, context); + + if (child.hasAttr("selected")) { + selected.add(item); + } + + return item; + } + private void updateSelection(Consumer<Set<T>> handler, boolean userOriginated) { LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection); diff --git a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java index cdd91424e0..d2602746fe 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java @@ -16,8 +16,13 @@ package com.vaadin.ui; import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; +import java.util.Set; + +import org.jsoup.nodes.Element; import com.vaadin.data.HasValue; import com.vaadin.data.SelectionModel; @@ -28,6 +33,8 @@ import com.vaadin.server.data.DataCommunicator; import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.ui.AbstractSingleSelectState; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.util.ReflectTools; /** @@ -307,6 +314,65 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> return Objects.equals(getValue(), item); } + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = super.writeItem(design, item, context); + + if (isSelected(item)) { + element.attr("selected", ""); + } + + return element; + } + + @Override + protected void readItems(Element design, DesignContext context) { + super.readItems(design, context); + Set<T> selected = new HashSet<>(); + design.children().stream() + .forEach(child -> readItem(child, selected, context)); + selected.forEach(this::setValue); + } + + /** + * Reads an Item from a design and inserts it into the data source. + * Hierarchical select components should override this method to recursively + * recursively read any child items as well. + * + * @param child + * a child element representing the item + * @param selected + * A set accumulating selected items. If the item that is read is + * marked as selected, its item id should be added to this set. + * @param context + * the DesignContext instance used in parsing + * @return the item id of the new item + * + * @throws DesignException + * if the tag name of the {@code child} element is not + * {@code option}. + */ + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = readItem(child, context); + + if (child.hasAttr("selected")) { + selected.add(item); + } + + return item; + } + + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> attributes = super.getCustomAttributes(); + // "value" is not an attribute for the component. "selected" attribute + // is used in "option"'s tag to mark selection which implies value for + // single select component + attributes.add("value"); + return attributes; + } + private void init() { registerRpc(new SelectionServerRpc() { diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java index d5df147f57..5098cca628 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java +++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java @@ -17,6 +17,9 @@ package com.vaadin.ui; import java.util.Collection; +import java.util.Set; + +import org.jsoup.nodes.Element; import com.vaadin.data.Listing; import com.vaadin.event.FieldEvents.BlurEvent; @@ -30,6 +33,8 @@ import com.vaadin.server.SerializablePredicate; import com.vaadin.server.data.DataProvider; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignFormatter; /** * A group of Checkboxes. Individual checkboxes are made from items supplied by @@ -171,4 +176,50 @@ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> public void removeBlurListener(BlurListener listener) { removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); } + + @Override + protected void readItems(Element design, DesignContext context) { + setItemEnabledProvider(new DeclarativeItemEnabledProvider<>()); + super.readItems(design, context); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = super.readItem(child, selected, context); + + SerializablePredicate<T> provider = getItemEnabledProvider(); + if (provider instanceof DeclarativeItemEnabledProvider) { + if (child.hasAttr("disabled")) { + ((DeclarativeItemEnabledProvider) provider).addDisabled(item); + } + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to disable item using current item enabled provider '%s'", + provider.getClass().getName())); + } + return item; + } + + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element elem = super.writeItem(design, item, context); + + if (!getItemEnabledProvider().test(item)) { + elem.attr("disabled", ""); + } + + if (isHtmlContentAllowed()) { + // need to unencode HTML entities. AbstractMultiSelect.writeDesign + // can't check if HTML content is allowed, so it always encodes + // entities like '>', '<' and '&'; in case HTML content is allowed + // this is undesirable so we need to unencode entities. Entities + // other than '<' and '>' will be taken care by Jsoup. + elem.html(DesignFormatter.decodeFromTextNode(elem.html())); + } + + return elem; + } } diff --git a/server/src/main/java/com/vaadin/ui/ComboBox.java b/server/src/main/java/com/vaadin/ui/ComboBox.java index 905d180a7f..a932d60423 100644 --- a/server/src/main/java/com/vaadin/ui/ComboBox.java +++ b/server/src/main/java/com/vaadin/ui/ComboBox.java @@ -18,10 +18,15 @@ package com.vaadin.ui; import java.io.Serializable; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Consumer; +import org.jsoup.nodes.Element; + import com.vaadin.data.HasValue; import com.vaadin.event.FieldEvents; import com.vaadin.event.FieldEvents.BlurEvent; @@ -40,6 +45,9 @@ import com.vaadin.shared.data.DataCommunicatorConstants; import com.vaadin.shared.ui.combobox.ComboBoxConstants; import com.vaadin.shared.ui.combobox.ComboBoxServerRpc; import com.vaadin.shared.ui.combobox.ComboBoxState; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignFormatter; import elemental.json.JsonObject; @@ -64,6 +72,36 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, } /** + * Item style generator class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its style. + * + * @param <T> + * item type + */ + protected static class DeclarativeStyleGenerator<T> + implements StyleGenerator<T> { + private Map<T, String> styles = new HashMap<>(); + + @Override + public String apply(T item) { + return styles.get(item); + } + + /** + * Sets a {@code style} for the {@code item}. + * + * @param item + * a data item + * @param style + * a style for the {@code item} + */ + protected void setStyle(T item, String style) { + styles.put(item, style); + } + } + + /** * Filter can be used to customize the filtering of items based on user * input. * @@ -107,10 +145,7 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, */ private NewItemHandler newItemHandler; - private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; - private StyleGenerator<T> itemStyleGenerator = item -> null; - private IconGenerator<T> itemIconGenerator = item -> null; private ItemFilter<T> filter = (filterText, item) -> { if (filterText == null) { @@ -200,7 +235,7 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, if (style != null) { jsonObject.put(ComboBoxConstants.STYLE, style); } - Resource icon = itemIconGenerator.apply(data); + Resource icon = getItemIconGenerator().apply(data); if (icon != null) { String iconUrl = ResourceReference .create(icon, ComboBox.this, null).getURL(); @@ -377,30 +412,15 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, return getState(false).scrollToSelectedItem; } - /** - * Gets the item caption generator that is used to produce the strings shown - * in the combo box for each item. - * - * @return the item caption generator used, not null - */ + @Override public ItemCaptionGenerator<T> getItemCaptionGenerator() { - return itemCaptionGenerator; + return super.getItemCaptionGenerator(); } - /** - * Sets the item caption generator that is used to produce the strings shown - * in the combo box for each item. By default, - * {@link String#valueOf(Object)} is used. - * - * @param itemCaptionGenerator - * the item caption provider to use, not null - */ + @Override public void setItemCaptionGenerator( ItemCaptionGenerator<T> itemCaptionGenerator) { - Objects.requireNonNull(itemCaptionGenerator, - "Item caption generators must not be null"); - this.itemCaptionGenerator = itemCaptionGenerator; - getDataCommunicator().reset(); + super.setItemCaptionGenerator(itemCaptionGenerator); } /** @@ -437,36 +457,14 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, return itemStyleGenerator; } - /** - * Sets the item icon generator that is used to produce custom icons for - * showing items in the popup. The generator can return null for items with - * no icon. - * - * @see IconGenerator - * - * @param itemIconGenerator - * the item icon generator to set, not null - * @throws NullPointerException - * if {@code itemIconGenerator} is {@code null} - */ + @Override public void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { - Objects.requireNonNull(itemIconGenerator, - "Item icon generator must not be null"); - this.itemIconGenerator = itemIconGenerator; - getDataCommunicator().reset(); + super.setItemIconGenerator(itemIconGenerator); } - /** - * Gets the currently used item icon generator. The default item icon - * provider returns null for all items, resulting in no icons being used. - * - * @see IconGenerator - * @see #setItemIconGenerator(IconGenerator) - * - * @return the currently used item icon generator, not null - */ + @Override public IconGenerator<T> getItemIconGenerator() { - return itemIconGenerator; + return super.getItemIconGenerator(); } /** @@ -548,4 +546,61 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, getState().selectedItemCaption = selectedCaption; } + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = design.appendElement("option"); + + String caption = getItemCaptionGenerator().apply(item); + if (caption != null) { + element.html(DesignFormatter.encodeForTextNode(caption)); + } else { + element.html(DesignFormatter.encodeForTextNode(item.toString())); + } + element.attr("item", item.toString()); + + Resource icon = getItemIconGenerator().apply(item); + if (icon != null) { + DesignAttributeHandler.writeAttribute("icon", element.attributes(), + icon, null, Resource.class, context); + } + + String style = getStyleGenerator().apply(item); + if (style != null) { + element.attr("style", style); + } + + if (isSelected(item)) { + element.attr("selected", ""); + } + + return element; + } + + @Override + protected void readItems(Element design, DesignContext context) { + setStyleGenerator(new DeclarativeStyleGenerator<>()); + super.readItems(design, context); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = super.readItem(child, selected, context); + + if (child.hasAttr("style")) { + StyleGenerator<T> styleGenerator = getStyleGenerator(); + if (styleGenerator instanceof DeclarativeStyleGenerator) { + ((DeclarativeStyleGenerator) styleGenerator).setStyle(item, + child.attr("style")); + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to set style using current style generator '%s'", + styleGenerator.getClass().getName())); + } + } + return item; + } + } diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java b/server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java new file mode 100644 index 0000000000..9aa6035d9d --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2016 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.ui; + +import java.util.HashMap; +import java.util.Map; + +/** + * Item caption generator class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its caption. + * + * @param <T> + * item type + */ +class DeclarativeCaptionGenerator<T> implements ItemCaptionGenerator<T> { + + private Map<T, String> captions = new HashMap<>(); + + @Override + public String apply(T item) { + return captions.get(item); + } + + /** + * Sets a {@code caption} for the {@code item}. + * + * @param item + * a data item + * @param caption + * a caption for the {@code item} + */ + protected void setCaption(T item, String caption) { + captions.put(item, caption); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java b/server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java new file mode 100644 index 0000000000..984ac4cf47 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2016 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.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.server.Resource; + +/** + * Icon generator class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its icon. + * + * @param <T> + * item type + */ +class DeclarativeIconGenerator<T> implements IconGenerator<T> { + + private Map<T, Resource> captions = new HashMap<>(); + + @Override + public Resource apply(T item) { + return captions.get(item); + } + + /** + * Sets an {@code icon} for the {@code item}. + * + * @param item + * a data item + * @param icon + * an icon for the {@code item} + */ + protected void setIcon(T item, Resource icon) { + captions.put(item, icon); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java b/server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java new file mode 100644 index 0000000000..f16e54f1b2 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2016 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.ui; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.server.SerializablePredicate; + +/** + * Item enabled provider class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its enable state. + * + * @param <T> + * item type + */ +class DeclarativeItemEnabledProvider<T> implements SerializablePredicate<T> { + + private Set<T> disabled = new HashSet<>(); + + @Override + public boolean test(T item) { + return !disabled.contains(item); + } + + /** + * Adds the {@code item} to disabled items list. + * + * @param item + * a data item + * @param caption + * a caption for the {@code item} + */ + protected void addDisabled(T item) { + disabled.add(item); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index a019476aeb..b9f5f5afbb 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -36,6 +36,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jsoup.nodes.Element; + import com.vaadin.data.Binder; import com.vaadin.data.SelectionModel; import com.vaadin.event.ConnectorEvent; @@ -65,6 +67,7 @@ import com.vaadin.ui.components.grid.Footer; import com.vaadin.ui.components.grid.Header; import com.vaadin.ui.components.grid.Header.Row; import com.vaadin.ui.components.grid.SingleSelectionModel; +import com.vaadin.ui.declarative.DesignContext; import com.vaadin.ui.renderers.AbstractRenderer; import com.vaadin.ui.renderers.Renderer; import com.vaadin.ui.renderers.TextRenderer; @@ -2668,4 +2671,15 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents { fireEvent(new ColumnResizeEvent(this, column, userOriginated)); } + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + // TODO see vaadin/framework8-issues#390 + return null; + } + + @Override + protected void readItems(Element design, DesignContext context) { + // TODO see vaadin/framework8-issues#390 + } + } diff --git a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java index 2a6ab48402..5ee3a7b807 100644 --- a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java +++ b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java @@ -18,6 +18,9 @@ package com.vaadin.ui; import java.util.Collection; import java.util.Objects; +import java.util.Set; + +import org.jsoup.nodes.Element; import com.vaadin.data.Listing; import com.vaadin.event.FieldEvents.BlurEvent; @@ -35,6 +38,8 @@ import com.vaadin.server.data.DataProvider; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignFormatter; import elemental.json.JsonObject; @@ -50,10 +55,6 @@ import elemental.json.JsonObject; public class RadioButtonGroup<T> extends AbstractSingleSelect<T> implements FocusNotifier, BlurNotifier { - private IconGenerator<T> itemIconGenerator = item -> null; - - private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; - private SerializablePredicate<T> itemEnabledProvider = item -> true; /** @@ -174,59 +175,25 @@ public class RadioButtonGroup<T> extends AbstractSingleSelect<T> return (RadioButtonGroupState) super.getState(markAsDirty); } - /** - * Returns the item icon generator. - * - * @return the currently set icon generator - * @see #setItemIconGenerator - * @see IconGenerator - */ + @Override public IconGenerator<T> getItemIconGenerator() { - return itemIconGenerator; + return super.getItemIconGenerator(); } - /** - * Sets the item icon generator for this radiobutton group. The icon - * generator is queried for each item to optionally display an icon next to - * the item caption. If the generator returns null for an item, no icon is - * displayed. The default generator always returns null (no icons). - * - * @param itemIconGenerator - * the icon generator, not null - * @see IconGenerator - */ + @Override public void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { - Objects.requireNonNull(itemIconGenerator, - "Item icon generator cannot be null."); - this.itemIconGenerator = itemIconGenerator; + super.setItemIconGenerator(itemIconGenerator); } - /** - * Returns the currently set item caption generator. - * - * @return the currently set caption generator - * @see #setItemCaptionGenerator - * @see ItemCaptionGenerator - */ + @Override public ItemCaptionGenerator<T> getItemCaptionGenerator() { - return itemCaptionGenerator; + return super.getItemCaptionGenerator(); } - /** - * Sets the item caption generator for this radiobutton group. The caption - * generator is queried for each item to optionally display an item textual - * representation. The default generator returns - * {@code String.valueOf(item)}. - * - * @param itemCaptionGenerator - * the item caption generator, not null - * @see ItemCaptionGenerator - */ + @Override public void setItemCaptionGenerator( ItemCaptionGenerator<T> itemCaptionGenerator) { - Objects.requireNonNull(itemCaptionGenerator, - "Item caption generator cannot be null."); - this.itemCaptionGenerator = itemCaptionGenerator; + super.setItemCaptionGenerator(itemCaptionGenerator); } /** @@ -278,4 +245,50 @@ public class RadioButtonGroup<T> extends AbstractSingleSelect<T> public void removeBlurListener(BlurListener listener) { removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); } + + @Override + protected void readItems(Element design, DesignContext context) { + setItemEnabledProvider(new DeclarativeItemEnabledProvider<>()); + super.readItems(design, context); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = super.readItem(child, selected, context); + + SerializablePredicate<T> provider = getItemEnabledProvider(); + if (provider instanceof DeclarativeItemEnabledProvider) { + if (child.hasAttr("disabled")) { + ((DeclarativeItemEnabledProvider) provider).addDisabled(item); + } + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to disable item using current item enabled provider '%s'", + provider.getClass().getName())); + } + return item; + } + + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element elem = super.writeItem(design, item, context); + + if (!getItemEnabledProvider().test(item)) { + elem.attr("disabled", ""); + } + + if (isHtmlContentAllowed()) { + // need to unencode HTML entities. AbstractMultiSelect.writeDesign + // can't check if HTML content is allowed, so it always encodes + // entities like '>', '<' and '&'; in case HTML content is allowed + // this is undesirable so we need to unencode entities. Entities + // other than '<' and '>' will be taken care by Jsoup. + elem.html(DesignFormatter.decodeFromTextNode(elem.html())); + } + + return elem; + } } diff --git a/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBase.java b/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBase.java index 3e8e4dccd6..168225b493 100644 --- a/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBase.java +++ b/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBase.java @@ -38,55 +38,31 @@ public abstract class DeclarativeTestBase<T extends Component> public class IntrospectorEqualsAsserter<C> implements EqualsAsserter<C> { - private final Class<C> c; + private final Class<C> clazz; - public IntrospectorEqualsAsserter(Class<C> c) { - this.c = c; + public IntrospectorEqualsAsserter(Class<C> clazz) { + this.clazz = clazz; } @Override - public void assertObjectEquals(C o1, C o2) { + public void assertObjectEquals(C object1, C object2) { try { - BeanInfo bi = Introspector.getBeanInfo(c); + BeanInfo bi = Introspector.getBeanInfo(clazz); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { Method readMethod = pd.getReadMethod(); Method writeMethod = pd.getWriteMethod(); - if (readMethod == null || writeMethod == null) { - continue; - } - // Needed to access public properties inherited from a - // nonpublic superclass, see #17425 - readMethod.setAccessible(true); - writeMethod.setAccessible(true); - if (Connector.class.isAssignableFrom(c) - && readMethod.getName().equals("getParent")) { - // Hack to break cycles in the connector hierarchy - continue; - } - try { - c.getDeclaredMethod(readMethod.getName()); - } catch (Exception e) { - // Not declared in this class, will be tested by parent - // class tester - if (debug) { - System.out.println("Skipped " + c.getSimpleName() - + "." + readMethod.getName()); - } - continue; - } - if (debug) { - System.out.println("Testing " + c.getSimpleName() + "." - + readMethod.getName()); + if (acceptProperty(clazz, readMethod, writeMethod)) { + Object property1 = readMethod.invoke(object1); + Object property2 = readMethod.invoke(object2); + assertEquals(pd.getDisplayName(), property1, property2); } - Object v1 = readMethod.invoke(o1); - Object v2 = readMethod.invoke(o2); - assertEquals(pd.getDisplayName(), v1, v2); } } catch (Exception e) { throw new RuntimeException(e); } } + } { @@ -106,6 +82,39 @@ public abstract class DeclarativeTestBase<T extends Component> }); } + protected boolean acceptProperty(Class<?> clazz, Method readMethod, + Method writeMethod) { + if (readMethod == null || writeMethod == null) { + return false; + } + // Needed to access public properties inherited from a + // nonpublic superclass, see #17425 + readMethod.setAccessible(true); + writeMethod.setAccessible(true); + if (Connector.class.isAssignableFrom(clazz) + && readMethod.getName().equals("getParent")) { + // Hack to break cycles in the connector hierarchy + return false; + } + try { + clazz.getDeclaredMethod(readMethod.getName()); + } catch (Exception e) { + // Not declared in this class, will be tested by parent + // class tester + if (debug) { + System.out.println("Skipped " + clazz.getSimpleName() + "." + + readMethod.getName()); + } + return false; + } + + if (debug) { + System.out.println("Testing " + clazz.getSimpleName() + "." + + readMethod.getName()); + } + return true; + } + @Override protected EqualsAsserter getComparator(Class c) { com.vaadin.tests.design.DeclarativeTestBaseBase.EqualsAsserter<?> comp = comparators @@ -122,4 +131,5 @@ public abstract class DeclarativeTestBase<T extends Component> } return comp; } + } diff --git a/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java b/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java index e2e8148871..161d053cca 100644 --- a/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java +++ b/server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java @@ -219,11 +219,12 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { Assert.assertEquals("", l.getMessages()); } - public void testWrite(String design, T expected, boolean writeData) { - String written = write(expected, writeData); + public void testWrite(String expectedDesign, T component, + boolean writeData) { + String written = write(component, writeData); Element producedElem = Jsoup.parse(written).body().child(0); - Element comparableElem = Jsoup.parse(design).body().child(0); + Element comparableElem = Jsoup.parse(expectedDesign).body().child(0); String produced = elementToHtml(producedElem); String comparable = elementToHtml(comparableElem); diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java b/server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java index d4b5901922..7733855e95 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java +++ b/server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java @@ -18,6 +18,8 @@ package com.vaadin.tests.server.component.abstractcomponent; import static org.junit.Assert.assertTrue; import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Locale; import org.junit.Test; @@ -71,7 +73,8 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom @Test public void abstractComponentAttributesDeserialization() - throws InstantiationException, IllegalAccessException { + throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { String id = "testId"; String caption = "testCaption"; boolean captionAsHtml = true; @@ -87,12 +90,14 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom boolean responsive = true; String styleName = "testStyleName"; boolean visible = false; + boolean requiredIndicator = true; String design = String.format( "<%s id='%s' caption='%s' caption-as-html description='%s' " + "error='%s' enabled='false' width='%s' height='%s' " + "icon='%s' locale='%s' primary-style-name='%s' " - + "readonly responsive style-name='%s' visible='false'/>", + + "readonly responsive style-name='%s' visible='false' " + + "required-indicator-visible/>", getComponentTag(), id, caption, description, error, width, height, icon, locale.toString(), primaryStyle, styleName); @@ -110,13 +115,9 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom component.setIcon(new FileResource(new File(icon))); component.setLocale(locale); component.setPrimaryStyleName(primaryStyle); - try { - component.getClass() - .getMethod("setReadOnly", new Class[] { boolean.class }) - .invoke(component, readOnly); - } catch (Exception e) { - // Ignore - } + callBooleanSetter(readOnly, "setReadOnly", component); + callBooleanSetter(requiredIndicator, "setRequiredIndicatorVisible", + component); component.setResponsive(responsive); component.setStyleName(styleName); component.setVisible(visible); @@ -125,6 +126,18 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom testWrite(design, component); } + private void callBooleanSetter(boolean value, String setterName, + T component) + throws IllegalAccessException, InvocationTargetException { + try { + Method method = component.getClass().getMethod(setterName, + new Class[] { boolean.class }); + method.invoke(component, value); + } catch (NoSuchMethodException ignore) { + // ignore if there is no such method + } + } + @Test public void externalIcon() throws InstantiationException, IllegalAccessException { diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingDeclarativeTest.java new file mode 100644 index 0000000000..c056836dc0 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingDeclarativeTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2000-2016 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.server.component.abstractlisting; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.vaadin.server.ExternalResource; +import com.vaadin.server.FileResource; +import com.vaadin.server.Resource; +import com.vaadin.server.SerializablePredicate; +import com.vaadin.server.ThemeResource; +import com.vaadin.tests.server.component.abstractcomponent.AbstractComponentDeclarativeTestBase; +import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractListing; +import com.vaadin.ui.IconGenerator; +import com.vaadin.ui.ItemCaptionGenerator; + +/** + * {@link AbstractListing} component declarative test. + * <p> + * Test ignores comparison for {@link ItemCaptionGenerator}, + * {@link IconGenerator} and {@link SerializablePredicate} "properties" since + * they are functions and it doesn't matter which implementation is chosen. But + * test checks generated item captions, item icon generation and enabled items + * generations if they are available in the component as public methods. + * <p> + * Common {@link AbstractComponent} properties are tested in + * {@link AbstractComponentDeclarativeTestBase} + * + * @see AbstractComponentDeclarativeTestBase + * + * @author Vaadin Ltd + * + * + * @param <T> + * a component type + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public abstract class AbstractListingDeclarativeTest<T extends AbstractListing> + extends AbstractComponentDeclarativeTestBase<T> { + + private static final String EXTERNAL_URL = "http://example.com/example.gif"; + + private static final String FILE_PATH = "img/example.gif"; + + private static final String THEME_PATH = "example.gif"; + + @Test + public abstract void dataSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException; + + @Test + public abstract void valueSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException; + + @Test + public void itemIconsSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + T component = getComponentClass().newInstance(); + Method setIconGenerator = getIconGeneratorMethod(component); + if (setIconGenerator == null) { + return; + } + + List<String> items = Arrays.asList("foo", "bar", "foobar", "barfoo"); + + String design = String.format( + "<%s>\n" + "<option item='foo' icon='%s'>foo</option>\n" + + "<option item='bar' icon='%s'>bar</option>" + + "<option item='foobar' icon='theme://%s'>foobar</option>" + + "<option item='barfoo'>barfoo</option>" + "</%s>", + getComponentTag(), EXTERNAL_URL, FILE_PATH, THEME_PATH, + getComponentTag()); + + component.setItems(items); + IconGenerator generator = item -> generateIcons(item, items); + setIconGenerator.invoke(component, generator); + + testRead(design, component); + testWrite(design, component, true); + } + + @Test + public void enabledItemsSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + T component = getComponentClass().newInstance(); + Method setEnabledITemsGenerator = getEnabledItemsProviderMethod( + component); + if (setEnabledITemsGenerator == null) { + return; + } + + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s>\n" + "<option item='foo'>foo</option>\n" + + "<option item='bar' disabled>bar</option>" + + "<option item='foobar'>foobar</option>", + getComponentTag(), getComponentTag()); + + component.setItems(items); + SerializablePredicate predicate = item -> !item.equals("bar"); + setEnabledITemsGenerator.invoke(component, predicate); + + testRead(design, component); + testWrite(design, component, true); + } + + @Test + public abstract void readOnlySelection() throws InstantiationException, + IllegalAccessException, InvocationTargetException; + + @Override + protected boolean acceptProperty(Class<?> clazz, Method readMethod, + Method writeMethod) { + if (readMethod != null) { + Class<?> returnType = readMethod.getReturnType(); + if (ItemCaptionGenerator.class.equals(returnType) + || IconGenerator.class.equals(returnType) + || SerializablePredicate.class.equals(returnType)) { + return false; + } + } + return super.acceptProperty(clazz, readMethod, writeMethod); + } + + private Method getIconGeneratorMethod(T component) + throws IllegalAccessException, InvocationTargetException { + try { + return component.getClass().getMethod("setItemIconGenerator", + new Class[] { IconGenerator.class }); + } catch (NoSuchMethodException ignore) { + // ignore if there is no such method + return null; + } + } + + private Method getEnabledItemsProviderMethod(T component) + throws IllegalAccessException, InvocationTargetException { + try { + return component.getClass().getMethod("setItemEnabledProvider", + new Class[] { SerializablePredicate.class }); + } catch (NoSuchMethodException ignore) { + // ignore if there is no such method + return null; + } + } + + private Resource generateIcons(Object item, List<String> items) { + int index = items.indexOf(item); + switch (index) { + case 0: + return new ExternalResource(EXTERNAL_URL); + case 1: + return new FileResource(new File(FILE_PATH)); + case 2: + return new ThemeResource(THEME_PATH); + } + return null; + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractmultiselect/AbstractMultiSelectDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstractmultiselect/AbstractMultiSelectDeclarativeTest.java new file mode 100644 index 0000000000..da8127ea8d --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/abstractmultiselect/AbstractMultiSelectDeclarativeTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2016 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.server.component.abstractmultiselect; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.junit.Test; + +import com.vaadin.tests.server.component.abstractlisting.AbstractListingDeclarativeTest; +import com.vaadin.ui.AbstractMultiSelect; + +/** + * {@link AbstractMultiSelect} component declarative test. + * <p> + * Test inherits test methods from a {@link AbstractListingDeclarativeTest} + * class providing here only common cases for {@link AbstractMultiSelect}s. + * + * @see AbstractListingDeclarativeTest + * + * @author Vaadin Ltd + * + * + * @param <T> + * a component type + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public abstract class AbstractMultiSelectDeclarativeTest<T extends AbstractMultiSelect> + extends AbstractListingDeclarativeTest<T> { + + @Override + @Test + public void dataSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s>\n" + "<option item='foo' selected>foo1</option>\n" + + "<option item='bar'>bar1</option>" + + "<option item='foobar' selected>foobar1</option></%s>", + getComponentTag(), getComponentTag()); + T component = getComponentClass().newInstance(); + component.setItems(items); + component.select("foo"); + component.select("foobar"); + component.setItemCaptionGenerator(item -> item + "1"); + + testRead(design, component); + testWrite(design, component, true); + } + + @Override + @Test + public void valueSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s>\n" + "<option item='foo' selected>foo1</option>\n" + + "<option item='bar'>bar1</option>" + + "<option item='foobar' selected>foobar1</option></%s>", + getComponentTag(), getComponentTag()); + T component = getComponentClass().newInstance(); + component.setItems(items); + component.setValue(new HashSet<>(Arrays.asList("foo", "foobar"))); + component.setItemCaptionGenerator(item -> item + "1"); + + testRead(design, component); + testWrite(design, component, true); + } + + @Override + @Test + public void readOnlySelection() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + T component = getComponentClass().newInstance(); + + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s readonly>\n" + "<option item='foo'>foo</option>\n" + + "<option item='bar'>bar</option>" + + "<option item='foobar'>foobar</option>", + getComponentTag(), getComponentTag()); + + component.setItems(items); + component.setReadOnly(true); + + testRead(design, component); + testWrite(design, component, true); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractsingleselect/AbstractSingleSelectDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstractsingleselect/AbstractSingleSelectDeclarativeTest.java new file mode 100644 index 0000000000..48b71ffcf1 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/abstractsingleselect/AbstractSingleSelectDeclarativeTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2000-2016 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.server.component.abstractsingleselect; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.vaadin.tests.server.component.abstractlisting.AbstractListingDeclarativeTest; +import com.vaadin.ui.AbstractSingleSelect; +import com.vaadin.ui.ItemCaptionGenerator; + +/** + * {@link AbstractSingleSelect} component declarative test. + * <p> + * Test inherits test methods from a {@link AbstractListingDeclarativeTest} + * class providing here only common cases for {@link AbstractSingleSelect}s. + * + * @author Vaadin Ltd + * + * + * @param <T> + * a component type + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public abstract class AbstractSingleSelectDeclarativeTest<T extends AbstractSingleSelect> + extends AbstractListingDeclarativeTest<T> { + + @Override + @Test + public void dataSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s>\n" + "<option item='foo'>foo</option>\n" + + "<option item='bar' selected>bar</option>" + + "<option item='foobar'>foobar</option></%s>", + getComponentTag(), getComponentTag()); + T component = getComponentClass().newInstance(); + component.setItems(items); + component.setSelectedItem("bar"); + + testRead(design, component); + testWrite(design, component, true); + } + + @Override + @Test + public void valueSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s>\n" + "<option item='foo'>foo</option>\n" + + "<option item='bar' selected>bar</option>" + + "<option item='foobar'>foobar</option></%s>", + getComponentTag(), getComponentTag()); + T component = getComponentClass().newInstance(); + component.setItems(items); + component.setValue("bar"); + + testRead(design, component); + testWrite(design, component, true); + } + + @Test + public void dataWithCaptionGeneratorSerialization() + throws InstantiationException, IllegalAccessException, + InvocationTargetException { + List<String> items = Arrays.asList("foo", "bar", "foobar"); + T component = getComponentClass().newInstance(); + Method setItemCaptionGenerator = getItemCaptionGeneratorMethod( + component); + if (setItemCaptionGenerator == null) { + return; + } + String design = String.format( + "<%s>\n" + "<option item='foo'>foo1</option>\n" + + "<option item='bar' selected>bar1</option>" + + "<option item='foobar'>foobar1</option></%s>", + getComponentTag(), getComponentTag()); + component.setItems(items); + component.setValue("bar"); + ItemCaptionGenerator generator = item -> item + "1"; + setItemCaptionGenerator.invoke(component, generator); + + testRead(design, component); + testWrite(design, component, true); + } + + @Override + @Test + public void readOnlySelection() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + T component = getComponentClass().newInstance(); + + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s readonly>\n" + "<option item='foo'>foo</option>\n" + + "<option item='bar'>bar</option>" + + "<option item='foobar'>foobar</option>", + getComponentTag(), getComponentTag()); + + component.setItems(items); + component.setReadOnly(true); + + testRead(design, component); + testWrite(design, component, true); + } + + private Method getItemCaptionGeneratorMethod(T component) + throws IllegalAccessException, InvocationTargetException { + try { + return component.getClass().getMethod("setItemCaptionGenerator", + new Class[] { ItemCaptionGenerator.class }); + } catch (NoSuchMethodException ignore) { + // ignore if there is no such method + return null; + } + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/checkboxgroup/CheckBoxGroupDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/checkboxgroup/CheckBoxGroupDeclarativeTest.java new file mode 100644 index 0000000000..c28776f0dc --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/checkboxgroup/CheckBoxGroupDeclarativeTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2016 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.server.component.checkboxgroup; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.vaadin.tests.server.component.abstractmultiselect.AbstractMultiSelectDeclarativeTest; +import com.vaadin.ui.CheckBoxGroup; + +/** + * Declarative support test for CheckBoxGroup. + * <p> + * Only {@link CheckBoxGroup#setHtmlContentAllowed(boolean)} is tested here + * explicitly. All other tests are in the super class ( + * {@link AbstractMultiSelectDeclarativeTest}). + * + * @see AbstractMultiSelectDeclarativeTest + * + * @author Vaadin Ltd + * + */ +@SuppressWarnings("rawtypes") +public class CheckBoxGroupDeclarativeTest + extends AbstractMultiSelectDeclarativeTest<CheckBoxGroup> { + + private static final String SIMPLE_HTML = "<span>foo</span>"; + + private static final String HTML = "<div class='wrapper'><div>bar</div></div>"; + + private static final String HTML_ENTITIES = "<b>a & b</b>"; + + @Test + public void serializeDataWithHtmlContentAllowed() { + CheckBoxGroup<String> group = new CheckBoxGroup<>(); + + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s html-content-allowed>\n" + + "<option item='foo'>%s</option>\n" + + "<option item='bar'>%s</option>" + + "<option item='foobar'>%s</option>", + getComponentTag(), SIMPLE_HTML, HTML, + HTML_ENTITIES.replace("&", "&"), getComponentTag()); + + group.setItems(items); + group.setHtmlContentAllowed(true); + group.setItemCaptionGenerator(item -> generateCaption(item, items)); + + testRead(design, group); + testWrite(design, group, true); + } + + @Override + protected String getComponentTag() { + return "vaadin-check-box-group"; + } + + @Override + protected Class<CheckBoxGroup> getComponentClass() { + return CheckBoxGroup.class; + } + + private String generateCaption(String item, List<String> items) { + int index = items.indexOf(item); + switch (index) { + case 0: + return SIMPLE_HTML; + case 1: + return HTML; + case 2: + return HTML_ENTITIES; + } + return null; + } + +}
\ No newline at end of file diff --git a/server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java new file mode 100644 index 0000000000..d0ec56e0a7 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2000-2016 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.server.component.combobox; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.vaadin.tests.server.component.abstractsingleselect.AbstractSingleSelectDeclarativeTest; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.StyleGenerator; + +/** + * Declarative support test for ComboBox. + * <p> + * There are only ComboBox specific properties explicit tests. All other tests + * are in the super class ( {@link AbstractSingleSelectDeclarativeTest}). + * + * @see AbstractSingleSelectDeclarativeTest + * + * @author Vaadin Ltd + * + */ +@SuppressWarnings("rawtypes") +public class ComboBoxDeclarativeTest + extends AbstractSingleSelectDeclarativeTest<ComboBox> { + + @Test + public void comboBoxSpecificPropertiesSerialize() { + String placeholder = "testPlaceholder"; + boolean textInputAllowed = false; + int pageLength = 7; + String popupWidth = "11%"; + boolean emptySelectionAllowed = false; + + String design = String.format( + "<%s placeholder='%s' text-input-allowed='%s' page-length='%d' " + + "popup-width='%s' empty-selection-allowed='%s' scroll-to-selected-item/>", + getComponentTag(), placeholder, textInputAllowed, pageLength, + popupWidth, emptySelectionAllowed); + + ComboBox<String> comboBox = new ComboBox<>(); + comboBox.setPlaceholder(placeholder); + comboBox.setTextInputAllowed(textInputAllowed); + comboBox.setPageLength(pageLength); + comboBox.setPopupWidth(popupWidth); + comboBox.setScrollToSelectedItem(true); + comboBox.setEmptySelectionAllowed(emptySelectionAllowed); + + testRead(design, comboBox); + testWrite(design, comboBox); + } + + @Test + public void optionStylesSerialization() throws InstantiationException, + IllegalAccessException, InvocationTargetException { + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s>\n" + "<option item='foo' style='foo-style'>foo</option>\n" + + "<option item='bar' style='bar-style'>bar</option>" + + "<option item='foobar' style='foobar-style'>foobar</option></%s>", + getComponentTag(), getComponentTag()); + ComboBox<String> comboBox = new ComboBox<>(); + comboBox.setItems(items); + comboBox.setStyleGenerator(item -> item + "-style"); + + testRead(design, comboBox); + testWrite(design, comboBox, true); + } + + @Override + protected String getComponentTag() { + return "vaadin-combo-box"; + } + + @Override + protected Class<? extends ComboBox> getComponentClass() { + return ComboBox.class; + } + + @Override + protected boolean acceptProperty(Class<?> clazz, Method readMethod, + Method writeMethod) { + if (readMethod != null) { + Class<?> returnType = readMethod.getReturnType(); + if (StyleGenerator.class.equals(returnType)) + return false; + } + return super.acceptProperty(clazz, readMethod, writeMethod); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/listselect/ListSelectDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/listselect/ListSelectDeclarativeTest.java index a3079bcdc3..a71aed674b 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/listselect/ListSelectDeclarativeTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/listselect/ListSelectDeclarativeTest.java @@ -15,59 +15,48 @@ */ package com.vaadin.tests.server.component.listselect; -import java.util.Arrays; - import org.junit.Test; -import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.tests.server.component.abstractmultiselect.AbstractMultiSelectDeclarativeTest; import com.vaadin.ui.ListSelect; +/** + * List select declarative test. + * <p> + * There is only {@link ListSelect#setRows(int)}/{@link ListSelect#getRows()} + * explicit test. All other tests are in the super class ( + * {@link AbstractMultiSelectDeclarativeTest}). + * + * @see AbstractMultiSelectDeclarativeTest + * + * @author Vaadin Ltd + * + */ +@SuppressWarnings("rawtypes") public class ListSelectDeclarativeTest - extends DeclarativeTestBase<ListSelect<String>> { - - private ListSelect<String> getWithOptionsExpected() { - ListSelect<String> ls = new ListSelect<>(null, - Arrays.asList("Male", "Female")); - ls.setRows(9); // 10 is default - return ls; - } - - private String getWithOptionsDesign() { - return "<vaadin-list-select rows=9>\n" - + " <option>Male</option>\n" - + " <option>Female</option>\n" - + "</vaadin-list-select>\n" + ""; - } + extends AbstractMultiSelectDeclarativeTest<ListSelect> { @Test - public void testReadWithOptions() { - testRead(getWithOptionsDesign(), getWithOptionsExpected()); - } + public void rowsPropertySerialization() { + int rows = 7; + String design = String.format("<%s rows='%s'/>", getComponentTag(), + rows); - @Test - public void testWriteWithOptions() { - testWrite(stripOptionTags(getWithOptionsDesign()), - getWithOptionsExpected()); - } + ListSelect<String> select = new ListSelect<>(); + select.setRows(rows); - private ListSelect<String> getBasicExpected() { - ListSelect<String> ls = new ListSelect<>(); - ls.setCaption("Hello"); - return ls; + testRead(design, select); + testWrite(design, select); } - private String getBasicDesign() { - return "<vaadin-list-select caption='Hello' />"; + @Override + protected String getComponentTag() { + return "vaadin-list-select"; } - @Test - public void testReadBasic() { - testRead(getBasicDesign(), getBasicExpected()); - } - - @Test - public void testWriteBasic() { - testWrite(getBasicDesign(), getBasicExpected()); + @Override + protected Class<? extends ListSelect> getComponentClass() { + return ListSelect.class; } } diff --git a/server/src/test/java/com/vaadin/tests/server/component/nativeselect/NativeSelectDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/nativeselect/NativeSelectDeclarativeTest.java new file mode 100644 index 0000000000..f7114394d4 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/nativeselect/NativeSelectDeclarativeTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2016 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.server.component.nativeselect; + +import com.vaadin.tests.server.component.abstractsingleselect.AbstractSingleSelectDeclarativeTest; +import com.vaadin.ui.NativeSelect; + +/** + * Declarative support tests for {@link NativeSelect}. All tests are in the + * super class ({@link AbstractSingleSelectDeclarativeTest}). This class + * declares only tag name and native select class (test parameters). + * + * @author Vaadin Ltd + * + */ +public class NativeSelectDeclarativeTest + extends AbstractSingleSelectDeclarativeTest<NativeSelect> { + + @Override + protected String getComponentTag() { + return "vaadin-native-select"; + } + + @Override + protected Class<NativeSelect> getComponentClass() { + return NativeSelect.class; + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/radiobuttongroup/RadioButtonGroupDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/radiobuttongroup/RadioButtonGroupDeclarativeTest.java new file mode 100644 index 0000000000..620fb170b6 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/radiobuttongroup/RadioButtonGroupDeclarativeTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2016 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.server.component.radiobuttongroup; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.vaadin.tests.server.component.abstractsingleselect.AbstractSingleSelectDeclarativeTest; +import com.vaadin.ui.RadioButtonGroup; + +/** + * Declarative support test for RadioButtonGroup. + * <p> + * Only {@link RadioButtonGroup#setHtmlContentAllowed(boolean)} is tested here + * explicitly. All other tests are in the super class ( + * {@link AbstractSingleSelectDeclarativeTest}). + * + * @see AbstractSingleSelectDeclarativeTest + * + * @author Vaadin Ltd + * + */ +@SuppressWarnings("rawtypes") +public class RadioButtonGroupDeclarativeTest + extends AbstractSingleSelectDeclarativeTest<RadioButtonGroup> { + + private static final String SIMPLE_HTML = "<span>foo</span>"; + + private static final String HTML = "<div class='wrapper'><div>bar</div></div>"; + + private static final String HTML_ENTITIES = "<b>a & b</b>"; + + @Test + public void serializeDataWithHtmlContentAllowed() { + RadioButtonGroup<String> group = new RadioButtonGroup<>(); + + List<String> items = Arrays.asList("foo", "bar", "foobar"); + + String design = String.format( + "<%s html-content-allowed>\n" + + "<option item='foo'>%s</option>\n" + + "<option item='bar'>%s</option>" + + "<option item='foobar'>%s</option>", + getComponentTag(), SIMPLE_HTML, HTML, + HTML_ENTITIES.replace("&", "&"), getComponentTag()); + + group.setItems(items); + group.setHtmlContentAllowed(true); + group.setItemCaptionGenerator(item -> generateCaption(item, items)); + + testRead(design, group); + testWrite(design, group, true); + } + + @Override + protected String getComponentTag() { + return "vaadin-radio-button-group"; + } + + @Override + protected Class<RadioButtonGroup> getComponentClass() { + return RadioButtonGroup.class; + } + + private String generateCaption(String item, List<String> items) { + int index = items.indexOf(item); + switch (index) { + case 0: + return SIMPLE_HTML; + case 1: + return HTML; + case 2: + return HTML_ENTITIES; + } + return null; + } + +}
\ No newline at end of file diff --git a/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java index 87d4c8c216..99841e9cf9 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java @@ -17,61 +17,70 @@ package com.vaadin.tests.server.component.twincolselect; import org.junit.Test; -import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.tests.server.component.abstractmultiselect.AbstractMultiSelectDeclarativeTest; import com.vaadin.ui.TwinColSelect; /** - * Test cases for reading the properties of selection components. - * + * TwinColSelectt declarative test. + * <p> + * There are only TwinColSelect specific properties explicit tests. All other + * tests are in the super class ( {@link AbstractMultiSelectDeclarativeTest}). + * + * @see AbstractMultiSelectDeclarativeTest + * * @author Vaadin Ltd + * */ public class TwinColSelectDeclarativeTest - extends DeclarativeTestBase<TwinColSelect<String>> { + extends AbstractMultiSelectDeclarativeTest<TwinColSelect> { - public String getBasicDesign() { - return "<vaadin-twin-col-select rows=5 right-column-caption='Selected values' left-column-caption='Unselected values'>\n" - + " <option>First item</option>\n" - + " <option selected>Second item</option>\n" - + " <option selected>Third item</option>\n" - + "</vaadin-twin-col-select>"; + @Test + public void rowsPropertySerialization() { + int rows = 7; + String design = String.format("<%s rows='%s'/>", getComponentTag(), + rows); - } + TwinColSelect<String> select = new TwinColSelect<>(); + select.setRows(rows); - public TwinColSelect<String> getBasicExpected() { - TwinColSelect<String> s = new TwinColSelect<>(); - s.setRightColumnCaption("Selected values"); - s.setLeftColumnCaption("Unselected values"); - /* - * This is broken for now : declarative doesn't read data and doesn't - * set value/selection. See #388 - * - * s.setItems("First item", "Second item", "Third item"); - * s.getSelectionModel().select("Second item"); - * s.getSelectionModel().select("Third item"); - * - */ - s.setRows(5); - return s; + testRead(design, select); + testWrite(design, select); } @Test - public void testReadBasic() { - testRead(getBasicDesign(), getBasicExpected()); + public void rightColumnCaptionPropertySerialization() { + String rightColumnCaption = "foo"; + String design = String.format("<%s right-column-caption='%s'/>", + getComponentTag(), rightColumnCaption); + + TwinColSelect<String> select = new TwinColSelect<>(); + select.setRightColumnCaption(rightColumnCaption); + + testRead(design, select); + testWrite(design, select); } @Test - public void testWriteBasic() { - testWrite(stripOptionTags(getBasicDesign()), getBasicExpected()); + public void leftColumnCaptionPropertySerialization() { + String leftColumnCaption = "foo"; + String design = String.format("<%s left-column-caption='%s'/>", + getComponentTag(), leftColumnCaption); + + TwinColSelect<String> select = new TwinColSelect<>(); + select.setLeftColumnCaption(leftColumnCaption); + + testRead(design, select); + testWrite(design, select); } - @Test - public void testReadEmpty() { - testRead("<vaadin-twin-col-select />", new TwinColSelect()); + @Override + protected String getComponentTag() { + return "vaadin-twin-col-select"; } - @Test - public void testWriteEmpty() { - testWrite("<vaadin-twin-col-select />", new TwinColSelect()); + @Override + protected Class<? extends TwinColSelect> getComponentClass() { + return TwinColSelect.class; } }
\ No newline at end of file diff --git a/server/src/test/java/com/vaadin/ui/AbstractListingTest.java b/server/src/test/java/com/vaadin/ui/AbstractListingTest.java index eca5eed854..b2f303ae24 100644 --- a/server/src/test/java/com/vaadin/ui/AbstractListingTest.java +++ b/server/src/test/java/com/vaadin/ui/AbstractListingTest.java @@ -6,15 +6,17 @@ import java.util.LinkedList; import java.util.List; import java.util.stream.Stream; -import com.vaadin.server.data.BackEndDataProvider; +import org.jsoup.nodes.Element; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.vaadin.server.data.BackEndDataProvider; import com.vaadin.server.data.DataProvider; import com.vaadin.server.data.ListDataProvider; import com.vaadin.server.data.Query; import com.vaadin.ui.AbstractListing.AbstractListingExtension; +import com.vaadin.ui.declarative.DesignContext; import elemental.json.JsonObject; @@ -28,6 +30,16 @@ public class AbstractListingTest { public void runDataGeneration() { super.getDataCommunicator().beforeClientResponse(true); } + + @Override + protected Element writeItem(Element design, String item, + DesignContext context) { + return null; + } + + @Override + protected void readItems(Element design, DesignContext context) { + } } private final class CountGenerator @@ -87,10 +99,10 @@ public class AbstractListingTest { public void testSetDataProvider() { ListDataProvider<String> dataProvider = DataProvider.create(items); listing.setDataProvider(dataProvider); - Assert.assertEquals("setDataProvider did not set data provider", dataProvider, - listing.getDataProvider()); - listing.setDataProvider(new BackEndDataProvider<>(q -> Stream.of(ITEM_ARRAY) - .skip(q.getOffset()).limit(q.getLimit()), + Assert.assertEquals("setDataProvider did not set data provider", + dataProvider, listing.getDataProvider()); + listing.setDataProvider(new BackEndDataProvider<>(q -> Stream + .of(ITEM_ARRAY).skip(q.getOffset()).limit(q.getLimit()), q -> ITEM_ARRAY.length)); Assert.assertNotEquals("setDataProvider did not replace data provider", dataProvider, listing.getDataProvider()); diff --git a/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java b/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java index 83cfbd1ee1..4ea3fdcfa0 100644 --- a/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java +++ b/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import org.jsoup.nodes.Element; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -36,6 +37,7 @@ import com.vaadin.event.selection.SingleSelectionListener; import com.vaadin.server.data.provider.bov.Person; import com.vaadin.shared.Registration; import com.vaadin.shared.data.DataCommunicatorClientRpc; +import com.vaadin.ui.declarative.DesignContext; /** * Test for {@link AbstractSingleSelect} and {@link AbstractSingleSelection} @@ -47,6 +49,16 @@ public class AbstractSingleSelectTest { private List<Person> selectionChanges; private static class PersonListing extends AbstractSingleSelect<Person> { + + @Override + protected Element writeItem(Element design, Person item, + DesignContext context) { + return null; + } + + @Override + protected void readItems(Element design, DesignContext context) { + } } @Before @@ -220,6 +232,16 @@ public class AbstractSingleSelectTest { public String getValue() { return value; } + + @Override + protected Element writeItem(Element design, String item, + DesignContext context) { + return null; + } + + @Override + protected void readItems(Element design, DesignContext context) { + } }; AtomicReference<ValueChangeEvent<?>> event = new AtomicReference<>(); |