]> source.dussan.org Git - vaadin-framework.git/commitdiff
Provide declarative support for listing components.
authorDenis Anisimov <denis@vaadin.com>
Mon, 19 Sep 2016 13:26:32 +0000 (16:26 +0300)
committerDenis Anisimov <denis@vaadin.com>
Thu, 24 Nov 2016 08:03:23 +0000 (11:03 +0300)
Fixes vaadin/framework8-issues#388

Change-Id: I4f8045bba51d308f4343a1f6d01b3f1ddca63e69

26 files changed:
server/src/main/java/com/vaadin/ui/AbstractListing.java
server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java
server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java
server/src/main/java/com/vaadin/ui/CheckBoxGroup.java
server/src/main/java/com/vaadin/ui/ComboBox.java
server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/Grid.java
server/src/main/java/com/vaadin/ui/RadioButtonGroup.java
server/src/test/java/com/vaadin/tests/design/DeclarativeTestBase.java
server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java
server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java
server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/abstractmultiselect/AbstractMultiSelectDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/abstractsingleselect/AbstractSingleSelectDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/checkboxgroup/CheckBoxGroupDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/listselect/ListSelectDeclarativeTest.java
server/src/test/java/com/vaadin/tests/server/component/nativeselect/NativeSelectDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/radiobuttongroup/RadioButtonGroupDeclarativeTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java
server/src/test/java/com/vaadin/ui/AbstractListingTest.java
server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java
uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java [deleted file]
uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java [new file with mode: 0644]

index f49d8715de3213072c22d86a855420eea529eb65..ce44bb8352f277a8db31b26d1a60dbdad63d8d34 100644 (file)
  */
 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
@@ -142,6 +163,64 @@ public abstract class AbstractListing<T> extends AbstractComponent
         return getDataCommunicator().getDataProvider();
     }
 
+    /**
+     * 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();
+    }
 }
index 3c85496e41e05584224c965b861813a6ffcbccb0..5ae0ee4ebd6da98374ae0d8b589733bbfe1cdcf0 100644 (file)
@@ -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;
@@ -120,17 +126,6 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T>
             .findMethod(MultiSelectionListener.class, "accept",
                     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);
     }
 
     /**
@@ -254,40 +234,6 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T>
                 new ValueChangeEvent<>(this, event.isUserOriginated())));
     }
 
-    /**
-     * 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>
@@ -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);
index cdd91424e0d9b81d22f944382a0f91783df39db0..d2602746fe188afe4a88b783f40d7206fd4ae837 100644 (file)
 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() {
 
index d5df147f575dee3a4b26b8a8bf80c099566bb6a0..5098cca628f2aefa1ba20a3f9b093e1bf1731f01 100644 (file)
@@ -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;
+    }
 }
index 905d180a7f1d3e8fdc3f1fca34327baedb887bd7..a932d60423be9044f864dba0434489987db961ce 100644 (file)
@@ -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;
 
@@ -63,6 +71,36 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>,
     public interface NewItemHandler extends Consumer<String>, Serializable {
     }
 
+    /**
+     * 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 (file)
index 0000000..9aa6035
--- /dev/null
@@ -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 (file)
index 0000000..984ac4c
--- /dev/null
@@ -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 (file)
index 0000000..f16e54f
--- /dev/null
@@ -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);
+    }
+
+}
index a019476aeb04fb3880f0bfb097b307e8a3f15fae..b9f5f5afbbfd4766a97858b03fc06b8db7da07c2 100644 (file)
@@ -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
+    }
+
 }
index 2a6ab4840269f2ea288052402f147191b3269e7b..5ee3a7b80705db599ab1c1c3727a2bff15c2919b 100644 (file)
@@ -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;
+    }
 }
index 3e8e4dccd6b4830ac377eb2d769f49f785f9ef91..168225b493d82b69a875e71addb0005e32d83f13 100644 (file)
@@ -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;
     }
+
 }
index e2e81488710f97c421965a86b66ab94b75c66f76..161d053cca6c7d06b619a2c3519e73b7d9d878df 100644 (file)
@@ -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);
index d4b59019229241b2d08c9d9ec572aef359dee1ca..7733855e95fd6d4cde3728d07c80caba761e1d2d 100644 (file)
@@ -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 (file)
index 0000000..c056836
--- /dev/null
@@ -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 (file)
index 0000000..da8127e
--- /dev/null
@@ -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 (file)
index 0000000..48b71ff
--- /dev/null
@@ -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 (file)
index 0000000..c28776f
--- /dev/null
@@ -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("&", "&amp;"), 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 (file)
index 0000000..d0ec56e
--- /dev/null
@@ -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);
+    }
+
+}
index a3079bcdc3000805748d976b9cf9ee58355d11ac..a71aed674b16257ff8d3caffa18e5e2610fb850f 100644 (file)
  */
 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 (file)
index 0000000..f711439
--- /dev/null
@@ -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 (file)
index 0000000..620fb17
--- /dev/null
@@ -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("&", "&amp;"), 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
index 87d4c8c216e26b61905f62548cbe6cf9511f4c0c..99841e9cf995ac9d198b08668948ce61d52e0374 100644 (file)
@@ -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
index eca5eed854e54752371269daaf9f93222dcc6726..b2f303ae246818bf496876d86428835d7e4aba96 100644 (file)
@@ -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());
index 83cfbd1ee1d09e70bbd420aea4b324da4134e906..4ea3fdcfa0dc568470d08ce10223811426ed6ef3 100644 (file)
@@ -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<>();
diff --git a/uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java b/uitest/src/test/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTest.java
deleted file mode 100644 (file)
index bff6e85..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright 2000-2014 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.components.radiobutton;
-
-import com.vaadin.server.FontAwesome;
-import com.vaadin.testbench.By;
-import com.vaadin.testbench.customelements.RadioButtonGroupElement;
-import com.vaadin.tests.tb3.MultiBrowserTest;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.openqa.selenium.WebElement;
-
-import java.util.Arrays;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test for RadioButtonGroup
- *
- * @author Vaadin Ltd
- * @since 8.0
- */
-public class RadioButtonGroupTest extends MultiBrowserTest {
-
-    @Before
-    public void setUp() throws Exception {
-        openTestURL();
-    }
-
-    @Test
-    public void initialLoad_containsCorrectItems() {
-        assertItems(20);
-    }
-
-    @Test
-    public void initialItems_reduceItemCount_containsCorrectItems() {
-        selectMenuPath("Component", "Data provider", "Items", "5");
-        assertItems(5);
-    }
-
-    @Test
-    public void initialItems_increaseItemCount_containsCorrectItems() {
-        selectMenuPath("Component", "Data provider", "Items", "100");
-        assertItems(100);
-    }
-
-    @Test
-    public void clickToSelect() {
-        selectMenuPath("Component", "Listeners", "Selection listener");
-
-        getSelect().selectByText("Item 4");
-        Assert.assertEquals("1. Selected: Optional[Item 4]", getLogRow(0));
-
-        getSelect().selectByText("Item 2");
-        Assert.assertEquals("2. Selected: Optional[Item 2]", getLogRow(0));
-
-        getSelect().selectByText("Item 4");
-        Assert.assertEquals("3. Selected: Optional[Item 4]", getLogRow(0));
-    }
-
-    @Test
-    public void disabled_clickToSelect() {
-        selectMenuPath("Component", "State", "Enabled");
-
-        Assert.assertTrue(getSelect().findElements(By.tagName("input")).stream()
-                .allMatch(element -> element.getAttribute("disabled") != null));
-
-        selectMenuPath("Component", "Listeners", "Selection listener");
-
-        String lastLogRow = getLogRow(0);
-
-        getSelect().selectByText("Item 4");
-        Assert.assertEquals(lastLogRow, getLogRow(0));
-
-        getSelect().selectByText("Item 2");
-        Assert.assertEquals(lastLogRow, getLogRow(0));
-
-        getSelect().selectByText("Item 4");
-        Assert.assertEquals(lastLogRow, getLogRow(0));
-    }
-
-    @Test
-    public void itemIconGenerator() {
-        selectMenuPath("Component", "Item Icon Generator", "Use Item Icon Generator");
-        assertItemsSuffices(20);
-
-        List<WebElement> icons = getSelect().findElements(By.
-                cssSelector(".v-select-optiongroup .v-icon"));
-
-        assertEquals(20,icons.size());
-
-        for (int i = 0; i < icons.size(); i++) {
-            Assert.assertEquals(FontAwesome.values()[i + 1].getCodepoint(),
-                    icons.get(i).getText().charAt(0));
-        }
-    }
-
-
-    @Test
-    public void clickToSelect_reenable() {
-        selectMenuPath("Component", "State", "Enabled");
-        selectMenuPath("Component", "Listeners", "Selection listener");
-
-        getSelect().selectByText("Item 4");
-
-        selectMenuPath("Component", "State", "Enabled");
-
-        getSelect().selectByText("Item 5");
-        Assert.assertEquals("3. Selected: Optional[Item 5]", getLogRow(0));
-
-        getSelect().selectByText("Item 2");
-        Assert.assertEquals("4. Selected: Optional[Item 2]", getLogRow(0));
-
-        getSelect().selectByText("Item 4");
-        Assert.assertEquals("5. Selected: Optional[Item 4]", getLogRow(0));
-    }
-
-    @Test
-    public void itemCaptionGenerator() {
-        selectMenuPath("Component", "Item Caption Generator", "Item Caption Generator",
-                "Custom Caption Generator");
-        assertItems(20, " Caption");
-    }
-
-    @Test
-    public void nullItemCaptionGenerator() {
-        selectMenuPath("Component", "Item Caption Generator", "Item Caption Generator",
-                "Null Caption Generator");
-        for (String text : getSelect().getOptions()) {
-            Assert.assertEquals("", text);
-        }
-    }
-
-    @Test
-    public void selectProgramatically() {
-        selectMenuPath("Component", "Listeners", "Selection listener");
-
-        selectMenuPath("Component", "Selection", "Toggle Item 5");
-        Assert.assertEquals("2. Selected: Optional[Item 5]", getLogRow(0));
-        assertSelected("Item 5");
-
-        selectMenuPath("Component", "Selection", "Toggle Item 1");
-        Assert.assertEquals("4. Selected: Optional[Item 1]", getLogRow(0));
-        // DOM order
-        assertSelected("Item 1");
-
-        selectMenuPath("Component", "Selection", "Toggle Item 5");
-        Assert.assertEquals("6. Selected: Optional[Item 5]", getLogRow(0));
-        assertSelected("Item 5");
-    }
-
-    private void assertSelected(String... expectedSelection) {
-        Assert.assertEquals(Arrays.asList(expectedSelection),
-                getSelect().getSelection());
-    }
-
-    @Override
-    protected Class<?> getUIClass() {
-        return RadioButtonGroupTestUI.class;
-    }
-
-    protected RadioButtonGroupElement getSelect() {
-        return $(RadioButtonGroupElement.class).first();
-    }
-
-    protected void assertItems(int count) {
-        assertItems(count, "");
-    }
-
-    protected void assertItems(int count, String suffix) {
-        int i = 0;
-        for (String text : getSelect().getOptions()) {
-            assertEquals("Item " + i + suffix, text);
-            i++;
-        }
-        assertEquals("Number of items", count, i);
-    }
-
-    protected void assertItemsSuffices(int count) {
-        int i = 0;
-        for (String text : getSelect().getOptions()) {
-            assertTrue(text.endsWith("Item " + i));
-            i++;
-        }
-        assertEquals("Number of items", count, i);
-    }
-}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java b/uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java
new file mode 100644 (file)
index 0000000..0b204aa
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2000-2014 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.components.radiobuttongroup;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.server.FontAwesome;
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.customelements.RadioButtonGroupElement;
+import com.vaadin.tests.components.radiobutton.RadioButtonGroupTestUI;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+/**
+ * Test for RadioButtonGroup
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public class RadioButtonGroupTest extends MultiBrowserTest {
+
+    @Before
+    public void setUp() throws Exception {
+        openTestURL();
+    }
+
+    @Test
+    public void initialLoad_containsCorrectItems() {
+        assertItems(20);
+    }
+
+    @Test
+    public void initialItems_reduceItemCount_containsCorrectItems() {
+        selectMenuPath("Component", "Data provider", "Items", "5");
+        assertItems(5);
+    }
+
+    @Test
+    public void initialItems_increaseItemCount_containsCorrectItems() {
+        selectMenuPath("Component", "Data provider", "Items", "100");
+        assertItems(100);
+    }
+
+    @Test
+    public void clickToSelect() {
+        selectMenuPath("Component", "Listeners", "Selection listener");
+
+        getSelect().selectByText("Item 4");
+        Assert.assertEquals("1. Selected: Optional[Item 4]", getLogRow(0));
+
+        getSelect().selectByText("Item 2");
+        Assert.assertEquals("2. Selected: Optional[Item 2]", getLogRow(0));
+
+        getSelect().selectByText("Item 4");
+        Assert.assertEquals("3. Selected: Optional[Item 4]", getLogRow(0));
+    }
+
+    @Test
+    public void disabled_clickToSelect() {
+        selectMenuPath("Component", "State", "Enabled");
+
+        Assert.assertTrue(getSelect().findElements(By.tagName("input")).stream()
+                .allMatch(element -> element.getAttribute("disabled") != null));
+
+        selectMenuPath("Component", "Listeners", "Selection listener");
+
+        String lastLogRow = getLogRow(0);
+
+        getSelect().selectByText("Item 4");
+        Assert.assertEquals(lastLogRow, getLogRow(0));
+
+        getSelect().selectByText("Item 2");
+        Assert.assertEquals(lastLogRow, getLogRow(0));
+
+        getSelect().selectByText("Item 4");
+        Assert.assertEquals(lastLogRow, getLogRow(0));
+    }
+
+    @Test
+    public void itemIconGenerator() {
+        selectMenuPath("Component", "Item Icon Generator",
+                "Use Item Icon Generator");
+        assertItemsSuffices(20);
+
+        List<WebElement> icons = getSelect()
+                .findElements(By.cssSelector(".v-select-optiongroup .v-icon"));
+
+        assertEquals(20, icons.size());
+
+        for (int i = 0; i < icons.size(); i++) {
+            Assert.assertEquals(FontAwesome.values()[i + 1].getCodepoint(),
+                    icons.get(i).getText().charAt(0));
+        }
+    }
+
+    @Test
+    public void clickToSelect_reenable() {
+        selectMenuPath("Component", "State", "Enabled");
+        selectMenuPath("Component", "Listeners", "Selection listener");
+
+        getSelect().selectByText("Item 4");
+
+        selectMenuPath("Component", "State", "Enabled");
+
+        getSelect().selectByText("Item 5");
+        Assert.assertEquals("3. Selected: Optional[Item 5]", getLogRow(0));
+
+        getSelect().selectByText("Item 2");
+        Assert.assertEquals("4. Selected: Optional[Item 2]", getLogRow(0));
+
+        getSelect().selectByText("Item 4");
+        Assert.assertEquals("5. Selected: Optional[Item 4]", getLogRow(0));
+    }
+
+    @Test
+    public void itemCaptionGenerator() {
+        selectMenuPath("Component", "Item Caption Generator",
+                "Item Caption Generator", "Custom Caption Generator");
+        assertItems(20, " Caption");
+    }
+
+    @Test
+    public void nullItemCaptionGenerator() {
+        selectMenuPath("Component", "Item Caption Generator",
+                "Item Caption Generator", "Null Caption Generator");
+        for (String text : getSelect().getOptions()) {
+            Assert.assertEquals("", text);
+        }
+    }
+
+    @Test
+    public void selectProgramatically() {
+        selectMenuPath("Component", "Listeners", "Selection listener");
+
+        selectMenuPath("Component", "Selection", "Toggle Item 5");
+        Assert.assertEquals("2. Selected: Optional[Item 5]", getLogRow(0));
+        assertSelected("Item 5");
+
+        selectMenuPath("Component", "Selection", "Toggle Item 1");
+        Assert.assertEquals("4. Selected: Optional[Item 1]", getLogRow(0));
+        // DOM order
+        assertSelected("Item 1");
+
+        selectMenuPath("Component", "Selection", "Toggle Item 5");
+        Assert.assertEquals("6. Selected: Optional[Item 5]", getLogRow(0));
+        assertSelected("Item 5");
+    }
+
+    private void assertSelected(String... expectedSelection) {
+        Assert.assertEquals(Arrays.asList(expectedSelection),
+                getSelect().getSelection());
+    }
+
+    @Override
+    protected Class<?> getUIClass() {
+        return RadioButtonGroupTestUI.class;
+    }
+
+    protected RadioButtonGroupElement getSelect() {
+        return $(RadioButtonGroupElement.class).first();
+    }
+
+    protected void assertItems(int count) {
+        assertItems(count, "");
+    }
+
+    protected void assertItems(int count, String suffix) {
+        int i = 0;
+        for (String text : getSelect().getOptions()) {
+            assertEquals("Item " + i + suffix, text);
+            i++;
+        }
+        assertEquals("Number of items", count, i);
+    }
+
+    protected void assertItemsSuffices(int count) {
+        int i = 0;
+        for (String text : getSelect().getOptions()) {
+            assertTrue(text.endsWith("Item " + i));
+            i++;
+        }
+        assertEquals("Number of items", count, i);
+    }
+}