From 277b1a5c2884291ea9312d555dc45545430c1d9c Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Wed, 24 Aug 2016 11:26:12 +0300 Subject: Implement SingleSelection on client and server Change-Id: I48192de092c6b6e6be7ca2580720d2765962e167 --- server/src/main/java/com/vaadin/data/HasValue.java | 45 +++-- server/src/main/java/com/vaadin/data/Listing.java | 2 +- .../data/selection/AbstractSelectionModel.java | 47 +++++ .../com/vaadin/data/selection/SelectionModel.java | 141 --------------- .../com/vaadin/data/selection/SingleSelection.java | 187 ++++++++++++++++++++ .../main/java/com/vaadin/event/EventListener.java | 50 ++++++ .../main/java/com/vaadin/ui/AbstractListing.java | 55 +++--- server/src/main/java/com/vaadin/ui/Grid.java | 19 +-- .../abstractlisting/AbstractListingTest.java | 190 --------------------- .../java/com/vaadin/ui/AbstractListingTest.java | 160 +++++++++++++++++ 10 files changed, 513 insertions(+), 383 deletions(-) create mode 100644 server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java delete mode 100644 server/src/main/java/com/vaadin/data/selection/SelectionModel.java create mode 100644 server/src/main/java/com/vaadin/data/selection/SingleSelection.java create mode 100644 server/src/main/java/com/vaadin/event/EventListener.java delete mode 100644 server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java create mode 100644 server/src/test/java/com/vaadin/ui/AbstractListingTest.java (limited to 'server') diff --git a/server/src/main/java/com/vaadin/data/HasValue.java b/server/src/main/java/com/vaadin/data/HasValue.java index cbd6bc390f..86b97be9c0 100644 --- a/server/src/main/java/com/vaadin/data/HasValue.java +++ b/server/src/main/java/com/vaadin/data/HasValue.java @@ -16,10 +16,9 @@ package com.vaadin.data; import java.io.Serializable; -import java.util.function.Consumer; import com.vaadin.event.ConnectorEvent; -import com.vaadin.event.ConnectorEventListener; +import com.vaadin.event.EventListener; import com.vaadin.server.ClientConnector; import com.vaadin.shared.Registration; @@ -28,9 +27,12 @@ import com.vaadin.shared.Registration; * that have a user-editable value. Emits change events whenever the value is * changed, either by the user or programmatically. * - * @since + * @author Vaadin Ltd. + * * @param * the value type + * + * @since */ public interface HasValue extends Serializable { @@ -49,7 +51,7 @@ public interface HasValue extends Serializable { * Creates a new {@code ValueChange} event containing the current value * of the given value-bearing source connector. * - * @param + * @param * the type of the source connector * @param source * the source connector bearing the value, not null @@ -57,10 +59,27 @@ public interface HasValue extends Serializable { * {@code true} if this event originates from the client, * {@code false} otherwise. */ - public > ValueChange(C source, + public > ValueChange( + CONNECTOR source, boolean userOriginated) { + this(source, source.getValue(), userOriginated); + } + + /** + * Creates a new {@code ValueChange} event containing the given value, + * originating from the given source connector. + * + * @param source + * the source connector, not null + * @param value + * the new value, may be null + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + public ValueChange(ClientConnector source, V value, boolean userOriginated) { super(source); - this.value = source.getValue(); + this.value = value; this.userOriginated = userOriginated; } @@ -95,8 +114,8 @@ public interface HasValue extends Serializable { * @see Registration */ @FunctionalInterface - public interface ValueChangeListener - extends Consumer>, ConnectorEventListener { + public interface ValueChangeListener extends + EventListener> { /** * Invoked when this listener receives a value change event from an @@ -105,11 +124,6 @@ public interface HasValue extends Serializable { * @param event * the received event, not null */ - // In addition to customizing the Javadoc, this override is needed - // to make ReflectTools.findMethod work as expected. It uses - // Class.getDeclaredMethod, but even if it used getMethod instead, the - // superinterface argument type is Object, not Event, after type - // erasure. @Override public void accept(ValueChange event); } @@ -140,9 +154,8 @@ public interface HasValue extends Serializable { public V getValue(); /** - * Adds an {@link ValueChangeListener}. The listener is called when the - * value of this {@code hasValue} is changed either by the user or - * programmatically. + * Adds a value change listener. The listener is called when the value of + * this {@code hasValue} is changed either by the user or programmatically. * * @param listener * the value change listener, not null diff --git a/server/src/main/java/com/vaadin/data/Listing.java b/server/src/main/java/com/vaadin/data/Listing.java index 818389f8b7..38cfecb9bb 100644 --- a/server/src/main/java/com/vaadin/data/Listing.java +++ b/server/src/main/java/com/vaadin/data/Listing.java @@ -19,8 +19,8 @@ import java.io.Serializable; import java.util.Collection; import java.util.Set; -import com.vaadin.data.selection.SelectionModel; import com.vaadin.server.data.DataSource; +import com.vaadin.shared.data.selection.SelectionModel; /** * A generic interface for components that show a list of data. diff --git a/server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java b/server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java new file mode 100644 index 0000000000..8294df0918 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java @@ -0,0 +1,47 @@ +/* + * 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.data.selection; + +import com.vaadin.shared.data.DataCommunicatorConstants; +import com.vaadin.shared.data.selection.SelectionModel; +import com.vaadin.ui.AbstractListing.AbstractListingExtension; + +import elemental.json.JsonObject; + +/** + * An astract base class for {@code SelectionModel}s. + * + * @author Vaadin Ltd. + * + * @param + * type of selected data + * + * @since + */ +public abstract class AbstractSelectionModel extends + AbstractListingExtension implements SelectionModel { + + @Override + public void generateData(T data, JsonObject jsonObject) { + if (isSelected(data)) { + jsonObject.put(DataCommunicatorConstants.SELECTED, true); + } + } + + @Override + public void destroyData(T data) { + } +} diff --git a/server/src/main/java/com/vaadin/data/selection/SelectionModel.java b/server/src/main/java/com/vaadin/data/selection/SelectionModel.java deleted file mode 100644 index 1730d26319..0000000000 --- a/server/src/main/java/com/vaadin/data/selection/SelectionModel.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.data.selection; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import com.vaadin.data.HasValue; -import com.vaadin.data.Listing; - -/** - * Models the selection logic of a {@code Listing} component. Determines how - * items can be selected and deselected. - * - * @author Vaadin Ltd. - * - * @param - * the type of the items to select - * @since - * - * @see Listing - */ -public interface SelectionModel extends Serializable { - - /** - * A selection model in which a single item can be selected at a time. - * Selecting another item deselects the originally selected item. - * - * @param - * the type of the items to select - */ - public interface Single extends SelectionModel, HasValue { - - /** - * Selects the given item. If another item was already selected, that - * item is deselected. - */ - @Override - public void select(T item); - - /** - * Returns the currently selected item, or an empty optional if no item - * is selected. - * - * @return an optional of the selected item if any, an empty optional - * otherwise - */ - public Optional getSelectedItem(); - - /** - * Returns a singleton set of the currently selected item or an empty - * set if no item is selected. - * - * @return a singleton set of the selected item if any, an empty set - * otherwise - */ - @Override - default Set getSelectedItems() { - return getSelectedItem().map(Collections::singleton) - .orElse(Collections.emptySet()); - } - } - - /** - * A selection model in which multiple items can be selected at the same - * time. Selecting an item adds it to the selection. - * - * @param - * the type of the items to select - */ - public interface Multi extends SelectionModel, - HasValue> { - - /** - * Adds the given items to the set of currently selected items. - */ - @Override - public void select(T item); - - /** - * Adds the given items to the set of currently selected items. - */ - public void select(@SuppressWarnings("unchecked") T... items); - } - - /** - * Returns an immutable set of the currently selected item. - *

- * Implementation note: the iteration order of the items in the - * returned set should be well-defined and documented by the implementing - * class. - * - * @return the items in the current selection, not null - */ - public Set getSelectedItems(); - - /** - * Selects the given item. Depending on the implementation, may cause other - * items to be deselected. If the item is already selected, does nothing. - * - * @param item - * the item to select, not null - */ - public void select(T item); - - /** - * Deselects the given item. If the item is not currently selected, does - * nothing. - * - * @param item - * the item to deselect, not null - */ - public void deselect(T item); - - /** - * Returns whether the given item is currently selected. - * - * @param item - * the item to check, not null - * @return {@code true} if the item is selected, {@code false} otherwise - */ - public default boolean isSelected(T item) { - return getSelectedItems().contains(item); - } -} diff --git a/server/src/main/java/com/vaadin/data/selection/SingleSelection.java b/server/src/main/java/com/vaadin/data/selection/SingleSelection.java new file mode 100644 index 0000000000..6bb0ac5f66 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/selection/SingleSelection.java @@ -0,0 +1,187 @@ +/* + * 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.data.selection; + +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.Optional; + +import com.vaadin.data.HasValue.ValueChange; +import com.vaadin.event.EventListener; +import com.vaadin.shared.Registration; +import com.vaadin.shared.data.selection.SelectionModel.Single; +import com.vaadin.shared.data.selection.SelectionServerRpc; +import com.vaadin.ui.AbstractListing; +import com.vaadin.util.ReflectTools; + +/** + * A {@code SelectionModel} for selecting a single value. Implements + * {@code Extension} to provide the communication logic for single selection for + * the listing it extends. + * + * @author Vaadin Ltd. + * + * @param + * the type of the items to select + * + * @since + */ +public class SingleSelection extends AbstractSelectionModel + implements Single { + + /** + * Fired when the selection changes. + * + * @param + * the type of the selected item + */ + public static class SingleSelectionChange extends ValueChange { + + /** + * Creates a new selection change event. + * + * @param source + * the listing that fired the event + * @param selectedItem + * the selected item or {@code null} if deselected + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + public SingleSelectionChange(AbstractListing source, + T selectedItem, boolean userOriginated) { + super(source, selectedItem, userOriginated); + } + + /** + * Returns an optional of the item that was selected, or an empty + * optional if a previously selected item was deselected. + * + * @return the selected item or an empty optional if deselected + * + * @see SelectionModel.Single#getSelectedItem() + */ + public Optional getSelectedItem() { + return Optional.ofNullable(getValue()); + } + } + + /** + * A listener for selection events. + * + * @param + * the type of the selected item + * + * @see SingleSelectionChange + */ + @FunctionalInterface + public interface SingleSelectionListener extends + EventListener> { + + @Override + public void accept(SingleSelectionChange event); + } + + @Deprecated + private static final Method SELECTION_CHANGE_METHOD = ReflectTools + .findMethod(SingleSelectionListener.class, "accept", + SingleSelectionChange.class); + + /** + * Creates a new {@code SingleSelection} extending the given parent listing. + * + * @param parent + * the parent listing + */ + public SingleSelection( + AbstractListing> parent) { + registerRpc(new SelectionServerRpc() { + + @Override + public void select(String key) { + doSelect(getData(key), true); + } + + @Override + public void deselect(String key) { + if (getData(key).equals(selectedItem)) { + doSelect(null, true); + } + } + }); + extend(parent); + } + + private T selectedItem = null; + + @Override + public Optional getSelectedItem() { + return Optional.ofNullable(selectedItem); + } + + @Override + public void select(T value) { + doSelect(value, false); + } + + @Override + public void deselect(T value) { + this.selectedItem = null; + } + + @Override + public void remove() { + if (selectedItem != null) { + refresh(selectedItem); + } + super.remove(); + } + + /** + * Adds a selection listener. The listener is called when the value of this + * {@code SingleSelection} is changed either by the user or + * programmatically. + * + * @param listener + * the value change listener, not null + * @return a registration for the listener + */ + public Registration addSelectionListener( + SingleSelectionListener listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + addListener(SingleSelectionChange.class, listener, + SELECTION_CHANGE_METHOD); + return () -> removeListener(SingleSelectionChange.class, listener); + } + + /** + * Selects the given item or deselects the current one if given + * {@code null}. + * + * @param value + * the item to select or {@code null} to deselect + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + protected void doSelect(T value, boolean userOriginated) { + if (!Objects.equals(value, this.selectedItem)) { + this.selectedItem = value; + fireEvent(new SingleSelectionChange<>(getParent(), value, + userOriginated)); + } + } +} diff --git a/server/src/main/java/com/vaadin/event/EventListener.java b/server/src/main/java/com/vaadin/event/EventListener.java new file mode 100644 index 0000000000..c5bb811712 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/EventListener.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.event; + +import java.util.function.Consumer; + +/** + * A generic interface for connector event listeners. + * + * @author Vaadin Ltd. + * + * @param + * the event type + * + * @since 8.0 + */ +@FunctionalInterface +public interface EventListener + extends Consumer, ConnectorEventListener { + + /** + * Invoked when this listener receives an event from the event source to + * which it has been added. + *

+ * Implementation note:In addition to customizing the + * Javadoc, this override is needed in all extending interfaces to make + * ReflectTools.findMethod work as expected. It uses + * Class.getDeclaredMethod, but even if it used getMethod instead, the + * superinterface argument type is ConnectorEvent, not the actual event + * type, after type erasure. + * + * @param event + * the received event, not null + */ + @Override + public void accept(EVENT event); +} diff --git a/server/src/main/java/com/vaadin/ui/AbstractListing.java b/server/src/main/java/com/vaadin/ui/AbstractListing.java index 58117e6df8..45c81fc7bd 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractListing.java +++ b/server/src/main/java/com/vaadin/ui/AbstractListing.java @@ -18,20 +18,24 @@ package com.vaadin.ui; import java.util.Objects; import com.vaadin.data.Listing; -import com.vaadin.data.selection.SelectionModel; import com.vaadin.server.AbstractExtension; import com.vaadin.server.data.DataCommunicator; import com.vaadin.server.data.DataGenerator; import com.vaadin.server.data.DataSource; +import com.vaadin.shared.data.selection.SelectionModel; /** * A base class for listing components. Provides common handling for fetching * backend data items, selection logic, and server-client communication. * + * @author Vaadin Ltd. + * * @param * the item data type * @param * the selection logic supported by this listing + * + * @since */ public abstract class AbstractListing> extends AbstractComponent implements Listing { @@ -99,35 +103,35 @@ public abstract class AbstractListing + * Note: This constructor does not set a selection model + * for the new listing. The invoking constructor must explicitly call + * {@link #setSelectionModel(SelectionModel)}. */ - protected AbstractListing(SELECTIONMODEL selectionModel) { - this(selectionModel, new DataCommunicator<>()); + protected AbstractListing() { + this(new DataCommunicator<>()); } /** - * Creates a new {@code AbstractListing} with the given selection model and - * data communicator. + * Creates a new {@code AbstractListing} with the given custom data + * communicator. *

* Note: This method is for creating an - * {@link AbstractListing} with a custom {@link DataCommunicator}. In the - * common case {@link AbstractListing#AbstractListing()} should be used. - * - * @param selectionModel - * the selection model to use, not null + * {@code AbstractListing} with a custom communicator. In the common case + * {@link AbstractListing#AbstractListing()} should be used. + *

+ * Note: This constructor does not set a selection model + * for the new listing. The invoking constructor must explicitly call + * {@link #setSelectionModel(SelectionModel)}. + * * @param dataCommunicator - * the custom data communicator to use, not null + * the data communicator to use, not null */ - protected AbstractListing(SELECTIONMODEL selectionModel, - DataCommunicator dataCommunicator) { - Objects.requireNonNull(selectionModel, "selectionModel cannot be null"); + protected AbstractListing(DataCommunicator dataCommunicator) { Objects.requireNonNull(dataCommunicator, "dataCommunicator cannot be null"); - this.selectionModel = selectionModel; this.dataCommunicator = dataCommunicator; addExtension(dataCommunicator); } @@ -144,9 +148,22 @@ public abstract class AbstractListing extends AbstractListing> * Constructor for the {@link Grid} component. */ public Grid() { - super(new SelectionModel() { - // Stub no-op selection model until selection models are implemented - @Override - public Set getSelectedItems() { - return Collections.emptySet(); - } - - @Override - public void select(T item) { - } - - @Override - public void deselect(T item) { - } - }); + setSelectionModel(new SingleSelection<>(this)); setDataSource(DataSource.create()); registerRpc(new GridServerRpcImpl()); detailsManager = new DetailsManager<>(); diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java deleted file mode 100644 index f29d1dd00b..0000000000 --- a/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.vaadin.tests.server.component.abstractlisting; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.data.selection.SelectionModel; -import com.vaadin.server.data.BackEndDataSource; -import com.vaadin.server.data.DataGenerator; -import com.vaadin.server.data.DataSource; -import com.vaadin.server.data.ListDataSource; -import com.vaadin.server.data.Query; -import com.vaadin.ui.AbstractListing; -import com.vaadin.ui.AbstractListing.AbstractListingExtension; - -import elemental.json.JsonObject; - -public class AbstractListingTest { - - private final class TestListing extends - AbstractListing> { - - protected TestListing() { - // Stub for now, implement (and test) when adding concrete - // SelectionModels - super(new SelectionModel() { - - @Override - public Set getSelectedItems() { - return Collections.emptySet(); - } - - @Override - public void select(String item) { - } - - @Override - public void deselect(String item) { - } - }); - } - - @Override - public void addDataGenerator(DataGenerator generator) { - super.addDataGenerator(generator); - } - - @Override - public void removeDataGenerator(DataGenerator generator) { - super.removeDataGenerator(generator); - } - - /** - * Used to execute data generation - */ - public void runDataGeneration() { - super.getDataCommunicator().beforeClientResponse(true); - } - } - - private final class CountGenerator - extends AbstractListingExtension { - - int callCount = 0; - - @Override - public void generateData(String data, JsonObject jsonObject) { - ++callCount; - } - - @Override - public void destroyData(String data) { - } - - @Override - public void refresh(String data) { - super.refresh(data); - } - } - - private static final String[] ITEM_ARRAY = new String[] { "Foo", "Bar", - "Baz" }; - - private TestListing listing; - private List items; - - @Before - public void setUp() { - items = new ArrayList<>(Arrays.asList(ITEM_ARRAY)); - listing = new TestListing(); - } - - @Test - public void testSetItemsWithCollection() { - listing.setItems(items); - listing.getDataSource().apply(new Query()).forEach( - str -> Assert.assertTrue("Unexpected item in data source", - items.remove(str))); - Assert.assertTrue("Not all items from list were in data source", - items.isEmpty()); - } - - @Test - public void testSetItemsWithVarargs() { - listing.setItems(ITEM_ARRAY); - listing.getDataSource().apply(new Query()).forEach( - str -> Assert.assertTrue("Unexpected item in data source", - items.remove(str))); - Assert.assertTrue("Not all items from list were in data source", - items.isEmpty()); - } - - @Test - public void testSetDataSource() { - ListDataSource dataSource = DataSource.create(items); - listing.setDataSource(dataSource); - Assert.assertEquals("setDataSource did not set data source", dataSource, - listing.getDataSource()); - listing.setDataSource(new BackEndDataSource<>(q -> Stream.of(ITEM_ARRAY) - .skip(q.getOffset()).limit(q.getLimit()), - q -> ITEM_ARRAY.length)); - Assert.assertNotEquals("setDataSource did not replace data source", - dataSource, listing.getDataSource()); - } - - @Test - public void testAddDataGeneratorBeforeDataSource() { - CountGenerator generator = new CountGenerator(); - generator.extend(listing); - listing.setItems("Foo"); - listing.runDataGeneration(); - Assert.assertEquals("Generator should have been called once", 1, - generator.callCount); - } - - @Test - public void testAddDataGeneratorAfterDataSource() { - CountGenerator generator = new CountGenerator(); - listing.setItems("Foo"); - generator.extend(listing); - listing.runDataGeneration(); - Assert.assertEquals("Generator should have been called once", 1, - generator.callCount); - } - - @Test - public void testDataNotGeneratedTwice() { - listing.setItems("Foo"); - CountGenerator generator = new CountGenerator(); - generator.extend(listing); - listing.runDataGeneration(); - Assert.assertEquals("Generator should have been called once", 1, - generator.callCount); - listing.runDataGeneration(); - Assert.assertEquals("Generator should not have been called again", 1, - generator.callCount); - } - - @Test - public void testRemoveDataGenerator() { - listing.setItems("Foo"); - CountGenerator generator = new CountGenerator(); - generator.extend(listing); - generator.remove(); - listing.runDataGeneration(); - Assert.assertEquals("Generator should not have been called", 0, - generator.callCount); - } - - @Test - public void testDataRefresh() { - listing.setItems("Foo"); - CountGenerator generator = new CountGenerator(); - generator.extend(listing); - listing.runDataGeneration(); - Assert.assertEquals("Generator should have been called once", 1, - generator.callCount); - generator.refresh("Foo"); - listing.runDataGeneration(); - Assert.assertEquals("Generator should have been called again", 2, - generator.callCount); - } -} diff --git a/server/src/test/java/com/vaadin/ui/AbstractListingTest.java b/server/src/test/java/com/vaadin/ui/AbstractListingTest.java new file mode 100644 index 0000000000..cbab429748 --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/AbstractListingTest.java @@ -0,0 +1,160 @@ +package com.vaadin.ui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.selection.SingleSelection; +import com.vaadin.server.data.BackEndDataSource; +import com.vaadin.server.data.DataSource; +import com.vaadin.server.data.ListDataSource; +import com.vaadin.server.data.Query; +import com.vaadin.ui.AbstractListing.AbstractListingExtension; + +import elemental.json.JsonObject; + +public class AbstractListingTest { + + private final class TestListing extends + AbstractListing> { + + protected TestListing() { + setSelectionModel(new SingleSelection<>(this)); + } + + /** + * Used to execute data generation + */ + public void runDataGeneration() { + super.getDataCommunicator().beforeClientResponse(true); + } + } + + private final class CountGenerator + extends AbstractListingExtension { + + int callCount = 0; + + @Override + public void generateData(String data, JsonObject jsonObject) { + ++callCount; + } + + @Override + public void destroyData(String data) { + } + + @Override + public void refresh(String data) { + super.refresh(data); + } + } + + private static final String[] ITEM_ARRAY = new String[] { "Foo", "Bar", + "Baz" }; + + private TestListing listing; + private List items; + + @Before + public void setUp() { + items = new ArrayList<>(Arrays.asList(ITEM_ARRAY)); + listing = new TestListing(); + } + + @Test + public void testSetItemsWithCollection() { + listing.setItems(items); + listing.getDataSource().apply(new Query()).forEach( + str -> Assert.assertTrue("Unexpected item in data source", + items.remove(str))); + Assert.assertTrue("Not all items from list were in data source", + items.isEmpty()); + } + + @Test + public void testSetItemsWithVarargs() { + listing.setItems(ITEM_ARRAY); + listing.getDataSource().apply(new Query()).forEach( + str -> Assert.assertTrue("Unexpected item in data source", + items.remove(str))); + Assert.assertTrue("Not all items from list were in data source", + items.isEmpty()); + } + + @Test + public void testSetDataSource() { + ListDataSource dataSource = DataSource.create(items); + listing.setDataSource(dataSource); + Assert.assertEquals("setDataSource did not set data source", dataSource, + listing.getDataSource()); + listing.setDataSource(new BackEndDataSource<>(q -> Stream.of(ITEM_ARRAY) + .skip(q.getOffset()).limit(q.getLimit()), + q -> ITEM_ARRAY.length)); + Assert.assertNotEquals("setDataSource did not replace data source", + dataSource, listing.getDataSource()); + } + + @Test + public void testAddDataGeneratorBeforeDataSource() { + CountGenerator generator = new CountGenerator(); + generator.extend(listing); + listing.setItems("Foo"); + listing.runDataGeneration(); + Assert.assertEquals("Generator should have been called once", 1, + generator.callCount); + } + + @Test + public void testAddDataGeneratorAfterDataSource() { + CountGenerator generator = new CountGenerator(); + listing.setItems("Foo"); + generator.extend(listing); + listing.runDataGeneration(); + Assert.assertEquals("Generator should have been called once", 1, + generator.callCount); + } + + @Test + public void testDataNotGeneratedTwice() { + listing.setItems("Foo"); + CountGenerator generator = new CountGenerator(); + generator.extend(listing); + listing.runDataGeneration(); + Assert.assertEquals("Generator should have been called once", 1, + generator.callCount); + listing.runDataGeneration(); + Assert.assertEquals("Generator should not have been called again", 1, + generator.callCount); + } + + @Test + public void testRemoveDataGenerator() { + listing.setItems("Foo"); + CountGenerator generator = new CountGenerator(); + generator.extend(listing); + generator.remove(); + listing.runDataGeneration(); + Assert.assertEquals("Generator should not have been called", 0, + generator.callCount); + } + + @Test + public void testDataRefresh() { + listing.setItems("Foo"); + CountGenerator generator = new CountGenerator(); + generator.extend(listing); + listing.runDataGeneration(); + Assert.assertEquals("Generator should have been called once", 1, + generator.callCount); + generator.refresh("Foo"); + listing.runDataGeneration(); + Assert.assertEquals("Generator should have been called again", 2, + generator.callCount); + } +} -- cgit v1.2.3