summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeif Åstrand <leif@vaadin.com>2016-09-14 13:24:28 +0300
committerVaadin Code Review <review@vaadin.com>2016-09-14 12:25:36 +0000
commit5a95ba20104d3da4d64bcdac00236436321ca8b1 (patch)
tree90dafe20cff242bfe6527e8fa32b0c621e739f38
parenta7f874e9422fe8bc7dad15dadf74a32aa4c75c8d (diff)
downloadvaadin-framework-5a95ba20104d3da4d64bcdac00236436321ca8b1.tar.gz
vaadin-framework-5a95ba20104d3da4d64bcdac00236436321ca8b1.zip
Implement support for binding multi-select components
Also updates ComponentTest.getRpcProxy to use an approach that doesn't require the component to be attached to a UI. Change-Id: Iab4603a7818cd0fd2a3410660b90a2a839fb8a76
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java93
-rw-r--r--server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java19
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java66
-rw-r--r--server/src/main/java/com/vaadin/ui/CheckBoxGroup.java62
-rw-r--r--server/src/test/java/com/vaadin/data/BinderMultiSelectTest.java180
-rw-r--r--server/src/test/java/com/vaadin/tests/data/bean/BeanWithEnums.java31
-rw-r--r--server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java59
-rw-r--r--server/src/test/java/com/vaadin/ui/ComponentTest.java34
8 files changed, 474 insertions, 70 deletions
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index 88549e12c3..d2114e8120 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -37,7 +37,9 @@ import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.UserError;
import com.vaadin.shared.Registration;
+import com.vaadin.shared.data.selection.SelectionModel.Multi;
import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.AbstractMultiSelect;
import com.vaadin.ui.AbstractSingleSelect;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
@@ -748,6 +750,45 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Creates a new binding for the given multi select component. The returned
+ * binding may be further configured before invoking
+ * {@link Binding#bind(Function, BiConsumer) Binding.bind} which completes
+ * the binding. Until {@code Binding.bind} is called, the binding has no
+ * effect.
+ *
+ * @param <SELECTVALUE>
+ * the item type of the select
+ * @param select
+ * the select to be bound, not null
+ * @return the new binding
+ */
+ public <SELECTVALUE> Binding<BEAN, Set<SELECTVALUE>, Set<SELECTVALUE>> forSelect(
+ AbstractMultiSelect<SELECTVALUE> select) {
+ return forField(new HasValue<Set<SELECTVALUE>>() {
+
+ @Override
+ public void setValue(Set<SELECTVALUE> value) {
+ Multi<SELECTVALUE> selectionModel = select.getSelectionModel();
+ selectionModel.deselectAll();
+ value.forEach(selectionModel::select);
+ }
+
+ @Override
+ public Set<SELECTVALUE> getValue() {
+ return select.getSelectionModel().getSelectedItems();
+ }
+
+ @Override
+ public Registration addValueChangeListener(
+ ValueChangeListener<? super Set<SELECTVALUE>> listener) {
+ return select.addSelectionListener(
+ e -> listener.accept(new ValueChange<>(select,
+ getValue(), e.isUserOriginated())));
+ }
+ });
+ }
+
+ /**
* Binds a field to a bean property represented by the given getter and
* setter pair. The functions are used to update the field value from the
* property and to store the field value to the property, respectively.
@@ -850,6 +891,58 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Binds a multi select to a bean property represented by the given getter
+ * and setter pair. The functions are used to update the set of selected
+ * items from the property and to store the selection to the property,
+ * respectively.
+ * <p>
+ * Use the {@link #forSelect(AbstractMultiSelect)} method instead if you
+ * want to further configure the new binding.
+ * <p>
+ * When a bean is bound with {@link Binder#bind(BEAN)}, the set of selected
+ * items are set to the return value of the given getter. The property value
+ * is then updated via the given setter whenever the selected items changes.
+ * The setter may be null; in that case the property value is never updated
+ * and the binding is said to be <i>read-only</i>.
+ * <p>
+ * If the Binder is already bound to some item, the newly bound select is
+ * associated with the corresponding bean property as described above.
+ * <p>
+ * The getter and setter can be arbitrary functions, for instance
+ * implementing user-defined conversion or validation. However, in the most
+ * basic use case you can simply pass a pair of method references to this
+ * method as follows:
+ *
+ * <pre>
+ * class Feature {
+ * public enum Browser { CHROME, EDGE, FIREFOX, IE, OPERA, SAFARI }
+
+ * public Set&lt;Browser> getSupportedBrowsers() { ... }
+ * public void setSupportedBrowsers(Set&lt;Browser> title) { ... }
+ * }
+ *
+ * CheckBoxGroup<Title> browserSelect = new CheckBoxGroup<>();
+ * browserSelect.setItems(Browser.values());
+ * binder.bind(browserSelect, Feature::getSupportedBrowsers, Feature::setSupportedBrowsers);
+ * </pre>
+ *
+ * @param <SELECTVALUE>
+ * the item type of the select
+ * @param select
+ * the select to bind, not null
+ * @param getter
+ * the function to get the set of selected items, not null
+ * @param setter
+ * the function to save the set of selected items or null if
+ * read-only
+ */
+ public <SELECTVALUE> void bind(AbstractMultiSelect<SELECTVALUE> select,
+ Function<BEAN, Set<SELECTVALUE>> getter,
+ BiConsumer<BEAN, Set<SELECTVALUE>> setter) {
+ forSelect(select).bind(getter, setter);
+ }
+
+ /**
* Binds the given bean to all the fields added to this Binder. To remove
* the binding, call {@link #unbind()}.
* <p>
diff --git a/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java b/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java
index 81c8478721..1de911f353 100644
--- a/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java
+++ b/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java
@@ -18,7 +18,7 @@ package com.vaadin.event.selection;
import java.util.Collections;
import java.util.Set;
-import com.vaadin.event.ConnectorEvent;
+import com.vaadin.data.HasValue.ValueChange;
import com.vaadin.shared.data.selection.SelectionModel;
import com.vaadin.ui.AbstractListing;
@@ -33,10 +33,9 @@ import com.vaadin.ui.AbstractListing;
* @param <T>
* the data type of the selection model
*/
-public class MultiSelectionEvent<T> extends ConnectorEvent {
+public class MultiSelectionEvent<T> extends ValueChange<Set<T>> {
- private Set<T> oldSelection;
- private Set<T> newSelection;
+ private final Set<T> oldSelection;
/**
* Creates a new event.
@@ -47,13 +46,16 @@ public class MultiSelectionEvent<T> extends ConnectorEvent {
* the old set of selected items
* @param newSelection
* the new set of selected items
+ * @param userOriginated
+ * {@code true} if this event originates from the client,
+ * {@code false} otherwise.
*/
public MultiSelectionEvent(
AbstractListing<T, SelectionModel.Multi<T>> source,
- Set<T> oldSelection, Set<T> newSelection) {
- super(source);
+ Set<T> oldSelection, Set<T> newSelection, boolean userOriginated) {
+ super(source, Collections.unmodifiableSet(newSelection),
+ userOriginated);
this.oldSelection = oldSelection;
- this.newSelection = newSelection;
}
/**
@@ -62,7 +64,7 @@ public class MultiSelectionEvent<T> extends ConnectorEvent {
* @return a set of items selected after the selection was changed
*/
public Set<T> getNewSelection() {
- return Collections.unmodifiableSet(newSelection);
+ return getValue();
}
/**
@@ -73,5 +75,4 @@ public class MultiSelectionEvent<T> extends ConnectorEvent {
public Set<T> getOldSelection() {
return Collections.unmodifiableSet(oldSelection);
}
-
}
diff --git a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java
new file mode 100644
index 0000000000..1d17841812
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java
@@ -0,0 +1,66 @@
+/*
+ * 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.lang.reflect.Method;
+import java.util.Objects;
+
+import com.vaadin.event.selection.MultiSelectionEvent;
+import com.vaadin.event.selection.MultiSelectionListener;
+import com.vaadin.shared.Registration;
+import com.vaadin.shared.data.selection.SelectionModel.Multi;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * Base class for listing components that allow selecting multiple items.
+ *
+ * @param <T>
+ * item type
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public abstract class AbstractMultiSelect<T>
+ extends AbstractListing<T, Multi<T>> {
+
+ @Deprecated
+ private static final Method SELECTION_CHANGE_METHOD = ReflectTools
+ .findMethod(MultiSelectionListener.class, "accept",
+ MultiSelectionEvent.class);
+
+ /**
+ * Creates a new multi select with an empty data source.
+ */
+ protected AbstractMultiSelect() {
+ super();
+ }
+
+ /**
+ * Adds a selection listener that will be called when the selection is
+ * changed either by the user or programmatically.
+ *
+ * @param listener
+ * the value change listener, not <code>null</code>
+ * @return a registration for the listener
+ */
+ public Registration addSelectionListener(
+ MultiSelectionListener<T> listener) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ addListener(MultiSelectionEvent.class, listener,
+ SELECTION_CHANGE_METHOD);
+ return () -> removeListener(MultiSelectionEvent.class, listener);
+ }
+
+} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java
index 8687aa2b92..8525dc6cbb 100644
--- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java
+++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java
@@ -16,7 +16,6 @@
package com.vaadin.ui;
-import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -29,17 +28,14 @@ import java.util.function.Predicate;
import com.vaadin.data.Listing;
import com.vaadin.event.selection.MultiSelectionEvent;
-import com.vaadin.event.selection.MultiSelectionListener;
import com.vaadin.server.Resource;
import com.vaadin.server.ResourceReference;
import com.vaadin.server.data.DataGenerator;
import com.vaadin.server.data.DataSource;
-import com.vaadin.shared.Registration;
import com.vaadin.shared.data.selection.SelectionModel;
import com.vaadin.shared.data.selection.SelectionServerRpc;
import com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants;
import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState;
-import com.vaadin.util.ReflectTools;
import elemental.json.JsonObject;
@@ -52,8 +48,7 @@ import elemental.json.JsonObject;
* @author Vaadin Ltd
* @since 8.0
*/
-public class CheckBoxGroup<T>
- extends AbstractListing<T, SelectionModel.Multi<T>> {
+public class CheckBoxGroup<T> extends AbstractMultiSelect<T> {
private final class SimpleMultiSelectModel
implements SelectionModel.Multi<T> {
@@ -62,11 +57,16 @@ public class CheckBoxGroup<T>
@Override
public void select(T item) {
+ // Not user originated
+ select(item, false);
+ }
+
+ private void select(T item, boolean userOriginated) {
if (selection.contains(item)) {
return;
}
- updateSelection(set -> set.add(item));
+ updateSelection(set -> set.add(item), userOriginated);
}
@Override
@@ -76,11 +76,16 @@ public class CheckBoxGroup<T>
@Override
public void deselect(T item) {
+ // Not user originated
+ deselect(item, false);
+ }
+
+ private void deselect(T item, boolean userOriginated) {
if (!selection.contains(item)) {
return;
}
- updateSelection(set -> set.remove(item));
+ updateSelection(set -> set.remove(item), userOriginated);
}
@Override
@@ -89,16 +94,17 @@ public class CheckBoxGroup<T>
return;
}
- updateSelection(Set::clear);
+ updateSelection(Set::clear, false);
}
- private void updateSelection(Consumer<Set<T>> handler) {
+ private void updateSelection(Consumer<Set<T>> handler,
+ boolean userOriginated) {
LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection);
handler.accept(selection);
LinkedHashSet<T> newSelection = new LinkedHashSet<>(selection);
fireEvent(new MultiSelectionEvent<>(CheckBoxGroup.this,
- oldSelection, newSelection));
+ oldSelection, newSelection, userOriginated));
getDataCommunicator().reset();
}
@@ -109,11 +115,6 @@ public class CheckBoxGroup<T>
}
}
- @Deprecated
- private static final Method SELECTION_CHANGE_METHOD = ReflectTools
- .findMethod(MultiSelectionListener.class, "accept",
- MultiSelectionEvent.class);
-
private Function<T, Resource> itemIconProvider = item -> null;
private Function<T, String> itemCaptionProvider = String::valueOf;
@@ -172,14 +173,14 @@ public class CheckBoxGroup<T>
@Override
public void select(String key) {
- getItemForSelectionChange(key)
- .ifPresent(getSelectionModel()::select);
+ getItemForSelectionChange(key).ifPresent(
+ item -> getSelectionModel().select(item, true));
}
@Override
public void deselect(String key) {
- getItemForSelectionChange(key)
- .ifPresent(getSelectionModel()::deselect);
+ getItemForSelectionChange(key).ifPresent(
+ item -> getSelectionModel().deselect(item, true));
}
private Optional<T> getItemForSelectionChange(String key) {
@@ -190,6 +191,11 @@ public class CheckBoxGroup<T>
return Optional.of(item);
}
+
+ private SimpleMultiSelectModel getSelectionModel() {
+ return (SimpleMultiSelectModel) CheckBoxGroup.this
+ .getSelectionModel();
+ }
});
addDataGenerator(new DataGenerator<T>() {
@@ -330,20 +336,4 @@ public class CheckBoxGroup<T>
Objects.nonNull(itemEnabledProvider);
this.itemEnabledProvider = itemEnabledProvider;
}
-
- /**
- * Adds a selection listener that will be called when the selection is
- * changed either by the user or programmatically.
- *
- * @param listener
- * the value change listener, not <code>null</code>
- * @return a registration for the listener
- */
- public Registration addSelectionListener(
- MultiSelectionListener<T> listener) {
- Objects.requireNonNull(listener, "listener cannot be null");
- addListener(MultiSelectionEvent.class, listener,
- SELECTION_CHANGE_METHOD);
- return () -> removeListener(MultiSelectionEvent.class, listener);
- }
}
diff --git a/server/src/test/java/com/vaadin/data/BinderMultiSelectTest.java b/server/src/test/java/com/vaadin/data/BinderMultiSelectTest.java
new file mode 100644
index 0000000000..93f4ccdac4
--- /dev/null
+++ b/server/src/test/java/com/vaadin/data/BinderMultiSelectTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.tests.data.bean.BeanWithEnums;
+import com.vaadin.tests.data.bean.TestEnum;
+import com.vaadin.ui.CheckBoxGroup;
+
+public class BinderMultiSelectTest
+ extends BinderTestBase<Binder<BeanWithEnums>, BeanWithEnums> {
+ public class TestEnumSetToStringConverter
+ implements Converter<Set<TestEnum>, String> {
+ @Override
+ public Result<String> convertToModel(Set<TestEnum> value,
+ Locale locale) {
+ return Result.ok(value.stream().map(TestEnum::name)
+ .collect(Collectors.joining(",")));
+ }
+
+ @Override
+ public Set<TestEnum> convertToPresentation(String value,
+ Locale locale) {
+ return Stream.of(value.split(","))
+ .filter(string -> !string.isEmpty()).map(TestEnum::valueOf)
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private Binder<AtomicReference<String>> converterBinder = new Binder<>();
+
+ private CheckBoxGroup<TestEnum> select;
+
+ @Before
+ public void setUp() {
+ binder = new Binder<>();
+ item = new BeanWithEnums();
+ select = new CheckBoxGroup<>();
+ select.setItems(TestEnum.values());
+
+ converterBinder.forSelect(select)
+ .withConverter(new TestEnumSetToStringConverter())
+ .bind(AtomicReference<String>::get,
+ AtomicReference<String>::set);
+ }
+
+ @Test
+ public void beanBound_bindSelectByShortcut_selectionUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ binder.bind(item);
+ binder.bind(select, BeanWithEnums::getEnums, BeanWithEnums::setEnums);
+
+ assertEquals(Collections.singleton(TestEnum.ONE),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void beanBound_bindSelect_selectionUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.TWO));
+ binder.bind(item);
+ binder.forSelect(select).bind(BeanWithEnums::getEnums,
+ BeanWithEnums::setEnums);
+
+ assertEquals(Collections.singleton(TestEnum.TWO),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void selectBound_bindBeanWithoutEnums_selectedItemNotPresent() {
+ bindEnum();
+
+ assertTrue(select.getSelectedItems().isEmpty());
+ }
+
+ @Test
+ public void selectBound_bindBean_selectionUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ bindEnum();
+
+ assertEquals(Collections.singleton(TestEnum.ONE),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void bound_setSelection_beanValueUpdated() {
+ bindEnum();
+
+ select.select(TestEnum.TWO);
+
+ assertEquals(Collections.singleton(TestEnum.TWO), item.getEnums());
+ }
+
+ @Test
+ public void bound_deselect_beanValueUpdatedToNull() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ bindEnum();
+
+ select.deselect(TestEnum.ONE);
+
+ assertTrue(item.getEnums().isEmpty());
+ }
+
+ @Test
+ public void unbound_changeSelection_beanValueNotUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ bindEnum();
+ binder.unbind();
+
+ select.select(TestEnum.TWO);
+
+ assertEquals(Collections.singleton(TestEnum.ONE), item.getEnums());
+ }
+
+ @Test
+ public void withConverter_load_selectUpdated() {
+ converterBinder.load(new AtomicReference<>("TWO"));
+
+ assertEquals(Collections.singleton(TestEnum.TWO),
+ select.getSelectionModel().getSelectedItems());
+ }
+
+ @Test
+ public void withConverter_save_referenceUpdated() {
+ select.select(TestEnum.ONE);
+ select.select(TestEnum.TWO);
+
+ AtomicReference<String> reference = new AtomicReference<>("");
+ converterBinder.saveIfValid(reference);
+
+ assertEquals("ONE,TWO", reference.get());
+ }
+
+ @Test
+ public void withValidator_validate_validatorUsed() {
+ binder.forSelect(select)
+ .withValidator(selection -> selection.size() % 2 == 1,
+ "Must select odd number of items")
+ .bind(BeanWithEnums::getEnums, BeanWithEnums::setEnums);
+ binder.bind(item);
+
+ assertFalse(binder.validate().isOk());
+
+ select.select(TestEnum.TWO);
+
+ assertTrue(binder.validate().isOk());
+ }
+
+ protected void bindEnum() {
+ binder.forSelect(select).bind(BeanWithEnums::getEnums,
+ BeanWithEnums::setEnums);
+ binder.bind(item);
+ }
+}
diff --git a/server/src/test/java/com/vaadin/tests/data/bean/BeanWithEnums.java b/server/src/test/java/com/vaadin/tests/data/bean/BeanWithEnums.java
new file mode 100644
index 0000000000..b0b612c200
--- /dev/null
+++ b/server/src/test/java/com/vaadin/tests/data/bean/BeanWithEnums.java
@@ -0,0 +1,31 @@
+/*
+ * 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.data.bean;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class BeanWithEnums {
+ private Set<TestEnum> enums = new HashSet<>();
+
+ public Set<TestEnum> getEnums() {
+ return enums;
+ }
+
+ public void setEnums(Set<TestEnum> enums) {
+ this.enums = enums;
+ }
+}
diff --git a/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java b/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java
index 0ad1801e8b..192dcb3d52 100644
--- a/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java
+++ b/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java
@@ -17,22 +17,31 @@ package com.vaadin.ui;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import com.vaadin.server.data.DataSource;
import com.vaadin.shared.data.selection.SelectionModel.Multi;
+import com.vaadin.shared.data.selection.SelectionServerRpc;
public class CheckBoxGroupTest {
- @Test
- public void stableSelectionOrder() {
- CheckBoxGroup<String> checkBoxGroup = new CheckBoxGroup<>();
+ private CheckBoxGroup<String> checkBoxGroup;
+ private Multi<String> selectionModel;
+
+ @Before
+ public void setUp() {
+ checkBoxGroup = new CheckBoxGroup<>();
// Intentional deviation from upcoming selection order
checkBoxGroup
.setDataSource(DataSource.create("Third", "Second", "First"));
- Multi<String> selectionModel = checkBoxGroup.getSelectionModel();
+ selectionModel = checkBoxGroup.getSelectionModel();
+ }
+ @Test
+ public void stableSelectionOrder() {
selectionModel.select("First");
selectionModel.select("Second");
selectionModel.select("Third");
@@ -46,6 +55,48 @@ public class CheckBoxGroupTest {
assertSelectionOrder(selectionModel, "Second", "Third", "First");
}
+ @Test
+ public void apiSelectionChange_notUserOriginated() {
+ AtomicInteger listenerCount = new AtomicInteger(0);
+
+ checkBoxGroup.addSelectionListener(event -> {
+ listenerCount.incrementAndGet();
+ Assert.assertFalse(event.isUserOriginated());
+ });
+
+ checkBoxGroup.select("First");
+ checkBoxGroup.select("Second");
+
+ checkBoxGroup.deselect("Second");
+ checkBoxGroup.getSelectionModel().deselectAll();
+
+ Assert.assertEquals(4, listenerCount.get());
+ }
+
+ @Test
+ public void rpcSelectionChange_userOriginated() {
+ AtomicInteger listenerCount = new AtomicInteger(0);
+
+ checkBoxGroup.addSelectionListener(event -> {
+ listenerCount.incrementAndGet();
+ Assert.assertTrue(event.isUserOriginated());
+ });
+
+ SelectionServerRpc rpc = ComponentTest.getRpcProxy(checkBoxGroup,
+ SelectionServerRpc.class);
+
+ rpc.select(getItemKey("First"));
+ rpc.select(getItemKey("Second"));
+ rpc.deselect(getItemKey("Second"));
+
+ Assert.assertEquals(3, listenerCount.get());
+ }
+
+ private String getItemKey(String dataObject) {
+ return checkBoxGroup.getDataCommunicator().getKeyMapper()
+ .key(dataObject);
+ }
+
private static void assertSelectionOrder(Multi<String> selectionModel,
String... selectionOrder) {
Assert.assertEquals(Arrays.asList(selectionOrder),
diff --git a/server/src/test/java/com/vaadin/ui/ComponentTest.java b/server/src/test/java/com/vaadin/ui/ComponentTest.java
index 8cd9afa776..cc58dbbf93 100644
--- a/server/src/test/java/com/vaadin/ui/ComponentTest.java
+++ b/server/src/test/java/com/vaadin/ui/ComponentTest.java
@@ -15,12 +15,10 @@
*/
package com.vaadin.ui;
-import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
import com.vaadin.server.ClientConnector;
-import com.vaadin.server.ServerRpcMethodInvocation;
+import com.vaadin.server.ServerRpcManager;
import com.vaadin.shared.communication.ServerRpc;
/**
@@ -67,32 +65,26 @@ public class ComponentTest {
}
/**
- * Gets a proxy object which invokes ServerRpc methods.
+ * Gets the server rpc handler registered for a component.
*
* @param component
* the component which listens to the RPC
* @param serverRpcClass
* the server RPC class
- * @return a proxy which can be used to invoke RPC methods
+ * @return the server RPC handler
*/
- @SuppressWarnings("unchecked")
public static <T extends ServerRpc> T getRpcProxy(Component component,
Class<T> serverRpcClass) {
- return (T) Proxy.newProxyInstance(component.getClass().getClassLoader(),
- new Class[] { serverRpcClass }, new InvocationHandler() {
-
- @Override
- public Object invoke(Object proxy, Method method,
- Object[] args) throws Throwable {
- ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
- component.getConnectorId(), serverRpcClass,
- method.getName(), args.length);
- invocation.setParameters(args);
- component.getRpcManager(serverRpcClass.getName())
- .applyInvocation(invocation);
- return null;
- }
- });
+ try {
+ ServerRpcManager<?> rpcManager = component
+ .getRpcManager(serverRpcClass.getName());
+ Method method = ServerRpcManager.class
+ .getDeclaredMethod("getImplementation");
+ method.setAccessible(true);
+ return serverRpcClass.cast(method.invoke(rpcManager));
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
}
}