aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTatu Lund <tatu@vaadin.com>2019-12-02 11:14:58 +0200
committerGitHub <noreply@github.com>2019-12-02 11:14:58 +0200
commitd389e77e9f2c7a38143732daf697252495b95e7c (patch)
treea8a9f8b346a75a9210f5bc65a5dc3a42469fac0a
parent7955d2c9aaa50b9f1d35176ba47fcdbe976cef86 (diff)
parent252ef116342a6b0363b48d157de33a932d44752f (diff)
downloadvaadin-framework-d389e77e9f2c7a38143732daf697252495b95e7c.tar.gz
vaadin-framework-d389e77e9f2c7a38143732daf697252495b95e7c.zip
Merge branch 'master' into fix11576
-rwxr-xr-xREADME-TESTS.md2
-rw-r--r--client/src/main/java/com/vaadin/client/communication/MessageHandler.java18
-rw-r--r--client/src/main/java/com/vaadin/client/communication/MessageSender.java1
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java2
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VComboBox.java33
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java35
-rw-r--r--client/src/main/java/com/vaadin/client/ui/treegrid/TreeGridConnector.java2
-rw-r--r--client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java8
-rw-r--r--client/src/main/java/com/vaadin/client/widgets/Escalator.java34
-rwxr-xr-xclient/src/main/java/com/vaadin/client/widgets/Grid.java7
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java4
-rw-r--r--documentation/addons/addons-maven.asciidoc2
-rw-r--r--server/src/main/java/com/vaadin/data/BeanValidationBinder.java20
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java146
-rw-r--r--server/src/main/java/com/vaadin/server/VaadinPortletService.java38
-rw-r--r--server/src/main/java/com/vaadin/server/VaadinService.java4
-rw-r--r--server/src/main/java/com/vaadin/server/VaadinServlet.java48
-rw-r--r--server/src/main/java/com/vaadin/ui/Composite.java10
-rw-r--r--server/src/main/java/com/vaadin/ui/CustomLayout.java3
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java2
-rw-r--r--server/src/main/java/com/vaadin/ui/MultiSelect.java3
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java27
-rw-r--r--server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java7
-rw-r--r--server/src/main/java/com/vaadin/ui/themes/ValoTheme.java10
-rw-r--r--server/src/test/java/com/vaadin/data/BinderComponentTest.java6
-rw-r--r--server/src/test/java/com/vaadin/data/BinderTest.java184
-rw-r--r--server/src/test/java/com/vaadin/server/VaadinPortletServiceTest.java6
-rw-r--r--server/src/test/java/com/vaadin/server/VaadinServletTest.java2
-rw-r--r--test/servlet-containers/generic/build.xml4
-rw-r--r--test/spring-boot-subcontext/src/test/java/com/example/VaadinSpringBootSmokeIT.java14
-rw-r--r--uitest/eclipse-run-selected-test.properties2
-rw-r--r--uitest/src/main/java/com/vaadin/screenshotbrowser/ScreenshotBrowser.java53
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxPasteFilter.java66
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/GridSelectAllFiltering.java71
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/tree/TreeBasicFeatures.java9
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/tree/TreeInitiallyDisabled.java51
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/GridScrollDestinationTest.java92
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectAllFilteringTest.java62
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/tree/TreeBasicFeaturesTest.java22
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/tree/TreeInitiallyDisabledTest.java18
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java24
-rw-r--r--uitest/src/test/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java5
-rw-r--r--uitest/src/test/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java2
43 files changed, 1050 insertions, 109 deletions
diff --git a/README-TESTS.md b/README-TESTS.md
index d79dc8c1ed..d9fef4071d 100755
--- a/README-TESTS.md
+++ b/README-TESTS.md
@@ -53,7 +53,7 @@ We're going to do the same as with the command-line approach - launch the `mvn j
Before running any tests from your IDE you need to
1. copy `uitest/eclipse-run-selected-test.properties` to `work/eclipse-run-selected-test.properties`
2. edit `work/eclipse-run-selected-test.properties`
- 1. Define `com.vaadin.testbench.screenshot.directory` as the directory where you checked out the screenshots repository (this directory contains the “references” subdirectory)
+ 1. Define `com.vaadin.testbench.screenshot.directory` as the `uitest` repository (this directory contains the “reference-screenshots” subdirectory)
2. Set `com.vaadin.testbench.deployment.url=http://localhost:8888/`
3. Set `com.vaadin.testbench.runLocally=chrome` to only run tests on Chrome. On Ubuntu you can then install Chrome driver easily:
`sudo apt install chromium-chromedriver`
diff --git a/client/src/main/java/com/vaadin/client/communication/MessageHandler.java b/client/src/main/java/com/vaadin/client/communication/MessageHandler.java
index a622bf1614..8e16293423 100644
--- a/client/src/main/java/com/vaadin/client/communication/MessageHandler.java
+++ b/client/src/main/java/com/vaadin/client/communication/MessageHandler.java
@@ -193,6 +193,7 @@ public class MessageHandler {
private int lastSeenServerSyncId = UNDEFINED_SYNC_ID;
private ApplicationConnection connection;
+ private boolean resyncInProgress;
/**
* Data structure holding information about pending UIDL messages.
@@ -258,7 +259,17 @@ public class MessageHandler {
protected void handleJSON(final ValueMap json) {
final int serverId = getServerId(json);
- if (isResynchronize(json) && !isNextExpectedMessage(serverId)) {
+ boolean hasResynchronize = isResynchronize(json);
+
+ if (!hasResynchronize && resyncInProgress) {
+ Logger.getLogger(MessageHandler.class.getName())
+ .warning("Dropping the response of a request before a resync request.");
+ return;
+ }
+
+ resyncInProgress = false;
+
+ if (hasResynchronize && !isNextExpectedMessage(serverId)) {
// Resynchronize request. We must remove any old pending
// messages and ensure this is handled next. Otherwise we
// would keep waiting for an older message forever (if this
@@ -321,7 +332,7 @@ public class MessageHandler {
int serverNextExpected = json
.getInt(ApplicationConstants.CLIENT_TO_SERVER_ID);
getMessageSender().setClientToServerMessageId(serverNextExpected,
- isResynchronize(json));
+ hasResynchronize);
}
if (serverId != -1) {
@@ -1822,4 +1833,7 @@ public class MessageHandler {
}
}-*/;
+ public void onResynchronize() {
+ resyncInProgress = true;
+ }
}
diff --git a/client/src/main/java/com/vaadin/client/communication/MessageSender.java b/client/src/main/java/com/vaadin/client/communication/MessageSender.java
index 5864ee5ee7..cde6657d1a 100644
--- a/client/src/main/java/com/vaadin/client/communication/MessageSender.java
+++ b/client/src/main/java/com/vaadin/client/communication/MessageSender.java
@@ -349,6 +349,7 @@ public class MessageSender {
* state from the server
*/
public void resynchronize() {
+ getMessageHandler().onResynchronize();
getLogger().info("Resynchronizing from server");
JsonObject resyncParam = Json.createObject();
resyncParam.put(ApplicationConstants.RESYNCHRONIZE_ID, true);
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java
index 7bbc66d271..de003e6daf 100644
--- a/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java
+++ b/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java
@@ -250,7 +250,7 @@ public class MultiSelectionModelConnector
@Override
protected boolean isSelected(JsonObject item) {
- return getState().allSelected || super.isSelected(item);
+ return super.isSelected(item);
}
/**
diff --git a/client/src/main/java/com/vaadin/client/ui/VComboBox.java b/client/src/main/java/com/vaadin/client/ui/VComboBox.java
index 81453bfade..438234d30b 100644
--- a/client/src/main/java/com/vaadin/client/ui/VComboBox.java
+++ b/client/src/main/java/com/vaadin/client/ui/VComboBox.java
@@ -28,6 +28,7 @@ import java.util.logging.Logger;
import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
@@ -427,10 +428,15 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,
// Add TT anchor point
getElement().setId("VAADIN_COMBOBOX_OPTIONLIST");
- leftPosition = getDesiredLeftPosition();
- topPosition = getDesiredTopPosition();
+ // Set the default position if the popup isn't already visible,
+ // the setPopupPositionAndShow call later on can deal with any
+ // adjustments that might be needed
+ if (!popup.isShowing()) {
+ leftPosition = getDesiredLeftPosition();
+ topPosition = getDesiredTopPosition();
- setPopupPosition(leftPosition, topPosition);
+ setPopupPosition(leftPosition, topPosition);
+ }
int nullOffset = getNullSelectionItemShouldBeVisible() ? 1 : 0;
boolean firstPage = currentPage == 0;
@@ -893,16 +899,24 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,
}
}
+ if (offsetWidth + menuMarginBorderPaddingWidth
+ + left < VComboBox.this.getAbsoluteLeft()
+ + VComboBox.this.getOffsetWidth()) {
+ // Popup doesn't reach all the way to the end of the input
+ // field, filtering may have changed the popup width.
+ left = VComboBox.this.getAbsoluteLeft();
+ }
if (offsetWidth + menuMarginBorderPaddingWidth + left > Window
.getClientWidth()) {
+ // Popup doesn't fit the view, needs to be opened to the left
+ // instead.
left = VComboBox.this.getAbsoluteLeft()
+ VComboBox.this.getOffsetWidth() - offsetWidth
- (int) menuMarginBorderPaddingWidth;
- if (left < 0) {
- left = 0;
- menu.setWidth(Window.getClientWidth() + "px");
-
- }
+ }
+ if (left < 0) {
+ left = 0;
+ menu.setWidth(Window.getClientWidth() + "px");
}
setPopupPosition(left, top);
@@ -1850,7 +1864,8 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,
if (event.getTypeInt() == Event.ONPASTE) {
if (textInputEnabled && connector.isEnabled()
&& !connector.isReadOnly()) {
- filterOptions(currentPage);
+ Scheduler.get()
+ .scheduleDeferred(() -> filterOptions(currentPage));
}
}
}
diff --git a/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java b/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java
index 6cbdb944ec..8618f9b52d 100644
--- a/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java
+++ b/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java
@@ -25,6 +25,7 @@ import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -37,6 +38,7 @@ import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
@@ -80,6 +82,7 @@ public class VTwinColSelect extends Composite implements MultiSelectWidget,
private static final int VISIBLE_COUNT = 10;
private static final int DEFAULT_COLUMN_COUNT = 10;
+ private static int scheduledScrollToItem = -1;
private final DoubleClickListBox optionsListBox;
@@ -327,15 +330,28 @@ public class VTwinColSelect extends Composite implements MultiSelectWidget,
private static void updateListBox(ListBox listBox,
List<JsonObject> options) {
+ List<String> selected = new ArrayList<String>();
+ // Retain right visible selection, see #11287
+ for (int i = 0; i < listBox.getItemCount(); ++i) {
+ if (listBox.isItemSelected(i)) {
+ selected.add(listBox.getItemText(i));
+ }
+ }
for (int i = 0; i < options.size(); i++) {
final JsonObject item = options.get(i);
// reuse existing option if possible
+ String caption = MultiSelectWidget.getCaption(item);
if (i < listBox.getItemCount()) {
- listBox.setItemText(i, MultiSelectWidget.getCaption(item));
+ listBox.setItemText(i, caption);
listBox.setValue(i, MultiSelectWidget.getKey(item));
} else {
- listBox.addItem(MultiSelectWidget.getCaption(item),
- MultiSelectWidget.getKey(item));
+ listBox.addItem(caption, MultiSelectWidget.getKey(item));
+ }
+ boolean isSelected = selected.contains(caption);
+ listBox.setItemSelected(i, isSelected);
+ if (isSelected) {
+ // Ensure that last selected item is visible
+ scrollToView(listBox,i);
}
}
// remove extra
@@ -344,6 +360,19 @@ public class VTwinColSelect extends Composite implements MultiSelectWidget,
}
}
+ private static void scrollToView(ListBox listBox, int i) {
+ if (scheduledScrollToItem == -1) {
+ scheduledScrollToItem = i;
+ Scheduler.get().scheduleDeferred(() -> {
+ Element el = (Element) listBox.getElement().getChild(scheduledScrollToItem);
+ el.scrollIntoView();
+ scheduledScrollToItem = -1;
+ });
+ } else {
+ scheduledScrollToItem = i;
+ }
+ }
+
private static boolean[] getSelectionBitmap(ListBox listBox) {
final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
for (int i = 0; i < listBox.getItemCount(); i++) {
diff --git a/client/src/main/java/com/vaadin/client/ui/treegrid/TreeGridConnector.java b/client/src/main/java/com/vaadin/client/ui/treegrid/TreeGridConnector.java
index c220057508..253e03a80c 100644
--- a/client/src/main/java/com/vaadin/client/ui/treegrid/TreeGridConnector.java
+++ b/client/src/main/java/com/vaadin/client/ui/treegrid/TreeGridConnector.java
@@ -288,7 +288,7 @@ public class TreeGridConnector extends GridConnector {
*/
private void setCollapsed(int rowIndex, boolean collapsed,
boolean userOriginated) {
- if (isAwaitingRowChange()) {
+ if (isAwaitingRowChange() || !getState().enabled) {
return;
}
if (collapsed) {
diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java b/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java
index d0ae71e4d4..8042e1b264 100644
--- a/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java
+++ b/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java
@@ -587,10 +587,12 @@ public abstract class ScrollbarBundle implements DeferredWorker {
* @return the new scroll position in pixels
*/
public final double getScrollPos() {
- assert internalGetScrollPos() == toInt32(
- scrollPos) : "calculated scroll position (" + scrollPos
+ int internalScrollPos = internalGetScrollPos();
+ assert Math.abs(internalScrollPos
+ - toInt32(scrollPos)) <= 1 : "calculated scroll position ("
+ + scrollPos
+ ") did not match the DOM element scroll position ("
- + internalGetScrollPos() + ")";
+ + internalScrollPos + ")";
return scrollPos;
}
diff --git a/client/src/main/java/com/vaadin/client/widgets/Escalator.java b/client/src/main/java/com/vaadin/client/widgets/Escalator.java
index 5c3bf593fb..e98ae2c578 100644
--- a/client/src/main/java/com/vaadin/client/widgets/Escalator.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Escalator.java
@@ -5059,9 +5059,10 @@ public class Escalator extends Widget
break;
case END:
// target row at the bottom of the viewport
- newTopRowLogicalIndex = Math.min(
- lastVisibleIndexIfScrollingDown + 1, getRowCount() - 1)
+ newTopRowLogicalIndex = lastVisibleIndexIfScrollingDown + 1
- visualRangeLength + 1;
+ newTopRowLogicalIndex = ensureTopRowLogicalIndexSanity(
+ newTopRowLogicalIndex);
if ((newTopRowLogicalIndex > oldTopRowLogicalIndex)
&& (newTopRowLogicalIndex
- oldTopRowLogicalIndex < visualRangeLength)) {
@@ -5078,10 +5079,8 @@ public class Escalator extends Widget
// target row at the middle of the viewport, padding has to be
// zero or we never would have reached this far
newTopRowLogicalIndex = targetRowIndex - visualRangeLength / 2;
- // ensure we don't attempt to go beyond the bottom row
- if (newTopRowLogicalIndex + visualRangeLength > getRowCount()) {
- newTopRowLogicalIndex = getRowCount() - visualRangeLength;
- }
+ newTopRowLogicalIndex = ensureTopRowLogicalIndexSanity(
+ newTopRowLogicalIndex);
if (newTopRowLogicalIndex < oldTopRowLogicalIndex) {
logicalTargetIndex = newTopRowLogicalIndex;
} else if (newTopRowLogicalIndex > oldTopRowLogicalIndex) {
@@ -5102,8 +5101,9 @@ public class Escalator extends Widget
case START:
// target row at the top of the viewport, include buffer
// row if there is room for one
- newTopRowLogicalIndex = Math
- .max(firstVisibleIndexIfScrollingUp - 1, 0);
+ newTopRowLogicalIndex = firstVisibleIndexIfScrollingUp - 1;
+ newTopRowLogicalIndex = ensureTopRowLogicalIndexSanity(
+ newTopRowLogicalIndex);
if (getVisibleRowRange().contains(newTopRowLogicalIndex)) {
logicalTargetIndex = oldTopRowLogicalIndex
+ visualRangeLength;
@@ -5141,6 +5141,24 @@ public class Escalator extends Widget
}
/**
+ * Modifies the proposed top row logical index to fit within the logical
+ * range and to not leave gaps if it is avoidable.
+ *
+ * @param proposedTopRowLogicalIndex
+ * @return an adjusted index, or the original if no changes were
+ * necessary
+ */
+ private int ensureTopRowLogicalIndexSanity(
+ int proposedTopRowLogicalIndex) {
+ int newTopRowLogicalIndex = Math.max(proposedTopRowLogicalIndex, 0);
+ int visualRangeLength = visualRowOrder.size();
+ if (newTopRowLogicalIndex + visualRangeLength > getRowCount()) {
+ newTopRowLogicalIndex = getRowCount() - visualRangeLength;
+ }
+ return newTopRowLogicalIndex;
+ }
+
+ /**
* Checks that scrolling is allowed and resets the scroll position if
* it's not.
*
diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java
index 2cbeb9cd21..977935c9bf 100755
--- a/client/src/main/java/com/vaadin/client/widgets/Grid.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java
@@ -7809,8 +7809,11 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}
}
- assert cell != null : "received " + eventType
- + "-event with a null cell target";
+ if (cell == null) {
+ getLogger().log(Level.WARNING,
+ "received " + eventType + "-event with a null cell target");
+ return;
+ }
eventCell.set(cell, getSectionFromContainer(container));
GridEvent<T> gridEvent = new GridEvent<>(event, eventCell);
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java
index 088c8a2bd6..36b1468bbd 100644
--- a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java
@@ -6348,15 +6348,15 @@ public class VScrollTable extends FlowPanel
if (targetCellOrRowFound) {
setRowFocus(this);
- ensureFocus();
if (dragmode != 0 && (event
.getButton() == NativeEvent.BUTTON_LEFT)) {
+ ensureFocus();
startRowDrag(event, type, targetTdOrTr);
} else if (event.getCtrlKey() || event.getShiftKey()
|| event.getMetaKey()
&& isMultiSelectModeDefault()) {
-
+ ensureFocus();
// Prevent default text selection in Firefox
event.preventDefault();
diff --git a/documentation/addons/addons-maven.asciidoc b/documentation/addons/addons-maven.asciidoc
index 7a1cfe99f3..8161894a47 100644
--- a/documentation/addons/addons-maven.asciidoc
+++ b/documentation/addons/addons-maven.asciidoc
@@ -280,7 +280,7 @@ It is also useful if you maintain your source code in GitHub or a similar servic
For production, especially in intranet applications, you should normally use the `local` or `fetch` modes.
This ensures that separating the availability of the Vaadin CDN service from availability of the application server does not add an extra point of failure.
-_They can be be used for production_ if your application is intended as globally available, you want to gain the global delivery benefit of the Vaadin CDN, and the availability tradeoff is not significant.
+_They can be used for production_ if your application is intended as globally available, you want to gain the global delivery benefit of the Vaadin CDN, and the availability tradeoff is not significant.
====
=== Serving Remotely Compiled Widget Set Locally
diff --git a/server/src/main/java/com/vaadin/data/BeanValidationBinder.java b/server/src/main/java/com/vaadin/data/BeanValidationBinder.java
index e98ce96b37..de67f922bf 100644
--- a/server/src/main/java/com/vaadin/data/BeanValidationBinder.java
+++ b/server/src/main/java/com/vaadin/data/BeanValidationBinder.java
@@ -49,7 +49,25 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
* the bean type to use, not <code>null</code>
*/
public BeanValidationBinder(Class<BEAN> beanType) {
- super(beanType);
+ this(beanType,false);
+ }
+
+ /**
+ * Creates a new binder that uses reflection based on the provided bean type
+ * to resolve bean properties. It assumes that JSR-303 bean validation
+ * implementation is present on the classpath. If there is no such
+ * implementation available then {@link Binder} class should be used instead
+ * (this constructor will throw an exception). Otherwise
+ * {@link BeanValidator} is added to each binding that is defined using a
+ * property name.
+ *
+ * @param beanType
+ * the bean type to use, not {@code null}
+ * @param scanNestedDefinitions
+ * if {@code true}, scan for nested property definitions as well
+ */
+ public BeanValidationBinder(Class<BEAN> beanType, boolean scanNestedDefinitions) {
+ super(beanType, scanNestedDefinitions);
if (!BeanUtil.checkBeanValidationAvailable()) {
throw new IllegalStateException(BeanValidationBinder.class
.getSimpleName()
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index 22ef7d7782..cd7b834571 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -226,6 +226,30 @@ public class Binder<BEAN> implements Serializable {
* @since 8.4
*/
public Setter<BEAN, TARGET> getSetter();
+
+ /**
+ * Enable or disable asRequired validator.
+ * The validator is enabled by default.
+ *
+ * @see #asRequired(String)
+ * @see #asRequired(ErrorMessageProvider)
+ *
+ * @param asRequiredEnabled
+ * {@code false} if asRequired validator should
+ * be disabled, {@code true} otherwise (default)
+ */
+ public void setAsRequiredEnabled(boolean asRequiredEnabled);
+
+ /**
+ * Returns whether asRequired validator is currently enabled or not
+ *
+ * @see #asRequired(String)
+ * @see #asRequired(ErrorMessageProvider)
+ *
+ * @return {@code false} if asRequired validator is disabled
+ * {@code true} otherwise (default)
+ */
+ public boolean isAsRequiredEnabled();
}
/**
@@ -781,6 +805,8 @@ public class Binder<BEAN> implements Serializable {
*/
private Converter<FIELDVALUE, ?> converterValidatorChain;
+ private boolean asRequiredSet;
+
/**
* Creates a new binding builder associated with the given field.
* Initializes the builder with the given converter chain and status
@@ -816,7 +842,7 @@ public class Binder<BEAN> implements Serializable {
getBinder().bindings.add(binding);
if (getBinder().getBean() != null) {
- binding.initFieldValue(getBinder().getBean());
+ binding.initFieldValue(getBinder().getBean(), true);
}
if (setter == null) {
binding.getField().setReadOnly(true);
@@ -917,8 +943,14 @@ public class Binder<BEAN> implements Serializable {
public BindingBuilder<BEAN, TARGET> asRequired(
Validator<TARGET> customRequiredValidator) {
checkUnbound();
+ this.asRequiredSet = true;
field.setRequiredIndicatorVisible(true);
- return withValidator(customRequiredValidator);
+ return withValidator((value, context) -> {
+ if (!field.isRequiredIndicatorVisible())
+ return ValidationResult.ok();
+ else
+ return customRequiredValidator.apply(value, context);
+ });
}
/**
@@ -1020,12 +1052,15 @@ public class Binder<BEAN> implements Serializable {
*/
private final Converter<FIELDVALUE, TARGET> converterValidatorChain;
+ private boolean asRequiredSet;
+
public BindingImpl(BindingBuilderImpl<BEAN, FIELDVALUE, TARGET> builder,
ValueProvider<BEAN, TARGET> getter,
Setter<BEAN, TARGET> setter) {
this.binder = builder.getBinder();
this.field = builder.field;
this.statusHandler = builder.statusHandler;
+ this.asRequiredSet = builder.asRequiredSet;
converterValidatorChain = ((Converter<FIELDVALUE, TARGET>) builder.converterValidatorChain);
onValueChange = getField()
@@ -1146,20 +1181,33 @@ public class Binder<BEAN> implements Serializable {
*
* @param bean
* the bean to fetch the property value from
+ * @param writeBackChangedValues
+ * <code>true</code> if the bean value should be updated if
+ * the value is different after converting to and from the
+ * presentation value; <code>false</code> to avoid updating
+ * the bean value
*/
- private void initFieldValue(BEAN bean) {
+ private void initFieldValue(BEAN bean, boolean writeBackChangedValues) {
assert bean != null;
assert onValueChange != null;
valueInit = true;
try {
- getField().setValue(convertDataToFieldType(bean));
+ TARGET originalValue = getter.apply(bean);
+ convertAndSetFieldValue(originalValue);
+
+ if (writeBackChangedValues && setter != null) {
+ doConversion().ifOk(convertedValue -> {
+ if (!Objects.equals(originalValue, convertedValue)) {
+ setter.accept(bean, convertedValue);
+ }
+ });
+ }
} finally {
valueInit = false;
}
}
- private FIELDVALUE convertDataToFieldType(BEAN bean) {
- TARGET target = getter.apply(bean);
+ private FIELDVALUE convertToFieldType(TARGET target) {
ValueContext valueContext = createValueContext();
return converterValidatorChain.convertToPresentation(target,
valueContext);
@@ -1218,7 +1266,31 @@ public class Binder<BEAN> implements Serializable {
@Override
public void read(BEAN bean) {
- getField().setValue(convertDataToFieldType(bean));
+ convertAndSetFieldValue(getter.apply(bean));
+ }
+
+ private void convertAndSetFieldValue(TARGET modelValue) {
+ FIELDVALUE convertedValue = convertToFieldType(modelValue);
+ try {
+ getField().setValue(convertedValue);
+ } catch (RuntimeException e) {
+ /*
+ * Add an additional hint to the exception for the typical case
+ * with a field that doesn't accept null values. The non-null
+ * empty value is used as a heuristic to determine that the
+ * field doesn't accept null rather than throwing for some other
+ * reason.
+ */
+ if (convertedValue == null && getField().getEmptyValue() != null) {
+ throw new IllegalStateException(String.format(
+ "A field of type %s didn't accept a null value."
+ + " If null values are expected, then configure a null representation for the binding.",
+ field.getClass().getName()), e);
+ } else {
+ // Otherwise, let the original exception speak for itself
+ throw e;
+ }
+ }
}
@Override
@@ -1245,6 +1317,24 @@ public class Binder<BEAN> implements Serializable {
public Setter<BEAN, TARGET> getSetter() {
return setter;
}
+
+ @Override
+ public void setAsRequiredEnabled(boolean asRequiredEnabled) {
+ if (!asRequiredSet) {
+ throw new IllegalStateException(
+ "Unable to toggle asRequired validation since "
+ + "asRequired has not been set.");
+ }
+ if (asRequiredEnabled != isAsRequiredEnabled()) {
+ field.setRequiredIndicatorVisible(asRequiredEnabled);
+ validate();
+ }
+ }
+
+ @Override
+ public boolean isAsRequiredEnabled() {
+ return field.isRequiredIndicatorVisible();
+ }
}
/**
@@ -1639,6 +1729,10 @@ public class Binder<BEAN> implements Serializable {
* Any change made in the fields also runs validation for the field
* {@link Binding} and bean level validation for this binder (bean level
* validators are added using {@link Binder#withValidator(Validator)}.
+ * <p>
+ * After updating each field, the value is read back from the field and the
+ * bean's property value is updated if it has been changed from the original
+ * value by the field or a converter.
*
* @see #readBean(Object)
* @see #writeBean(Object)
@@ -1658,7 +1752,7 @@ public class Binder<BEAN> implements Serializable {
} else {
doRemoveBean(false);
this.bean = bean;
- getBindings().forEach(b -> b.initFieldValue(bean));
+ getBindings().forEach(b -> b.initFieldValue(bean, true));
// if there has been field value change listeners that trigger
// validation, need to make sure the validation errors are cleared
getValidationStatusHandler().statusChange(
@@ -1706,7 +1800,7 @@ public class Binder<BEAN> implements Serializable {
// we unbind a binding in valueChangeListener of another
// field.
if (binding.getField() != null)
- binding.initFieldValue(bean);
+ binding.initFieldValue(bean, false);
});
getValidationStatusHandler().statusChange(
BinderValidationStatus.createUnresolvedStatus(this));
@@ -1746,6 +1840,23 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Writes successfully converted and validated changes from the bound fields
+ * to the bean even if there are other fields with non-validated changes.
+ *
+ * @see #writeBean(Object)
+ * @see #writeBeanIfValid(Object)
+ * @see #readBean(Object)
+ * @see #setBean(Object)
+ *
+ * @param bean
+ * the object to which to write the field values, not
+ * {@code null}
+ */
+ public void writeBeanAsDraft(BEAN bean) {
+ doWriteDraft(bean, new ArrayList<>(bindings));
+ }
+
+ /**
* Writes changes from the bound fields to the given bean if all validators
* (binding and bean level) pass.
* <p>
@@ -1833,6 +1944,23 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Writes the successfully converted and validated field values into the
+ * given bean.
+ *
+ * @param bean
+ * the bean to write field values into
+ * @param bindings
+ * the set of bindings to write to the bean
+ */
+ @SuppressWarnings({ "unchecked" })
+ private void doWriteDraft(BEAN bean, Collection<Binding<BEAN, ?>> bindings) {
+ Objects.requireNonNull(bean, "bean cannot be null");
+
+ bindings.forEach(binding -> ((BindingImpl<BEAN, ?, ?>) binding)
+ .writeFieldValue(bean));
+ }
+
+ /**
* Restores the state of the bean from the given values. This method is used
* together with {@link #getBeanState(Object, Collection)} to provide a way
* to revert changes in case the bean validation fails after save.
diff --git a/server/src/main/java/com/vaadin/server/VaadinPortletService.java b/server/src/main/java/com/vaadin/server/VaadinPortletService.java
index 1b804ad18c..0b2d28acc6 100644
--- a/server/src/main/java/com/vaadin/server/VaadinPortletService.java
+++ b/server/src/main/java/com/vaadin/server/VaadinPortletService.java
@@ -22,6 +22,8 @@ import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -366,4 +368,40 @@ public class VaadinPortletService extends VaadinService {
getWrappedPortletSession(wrappedSession).removeAttribute(
getSessionAttributeName(), PortletSession.APPLICATION_SCOPE);
}
+
+ @Override
+ protected void setSessionLock(WrappedSession wrappedSession, Lock lock) {
+ if (wrappedSession == null) {
+ throw new IllegalArgumentException(
+ "Can't set a lock for a null session");
+ }
+ Object currentSessionLock = getWrappedPortletSession(wrappedSession)
+ .getAttribute(getLockAttributeName(),
+ PortletSession.APPLICATION_SCOPE);
+ assert (currentSessionLock == null
+ || currentSessionLock == lock) : "Changing the lock for a session is not allowed";
+
+ getWrappedPortletSession(wrappedSession).setAttribute(
+ getLockAttributeName(), lock,
+ PortletSession.APPLICATION_SCOPE);
+ }
+
+ @Override
+ protected Lock getSessionLock(WrappedSession wrappedSession) {
+ Object lock = getWrappedPortletSession(wrappedSession)
+ .getAttribute(getLockAttributeName(),
+ PortletSession.APPLICATION_SCOPE);
+
+ if (lock instanceof ReentrantLock) {
+ return (ReentrantLock) lock;
+ }
+
+ if (lock == null) {
+ return null;
+ }
+
+ throw new RuntimeException(
+ "Something else than a ReentrantLock was stored in the "
+ + getLockAttributeName() + " in the session");
+ }
}
diff --git a/server/src/main/java/com/vaadin/server/VaadinService.java b/server/src/main/java/com/vaadin/server/VaadinService.java
index 1e67db708b..9503db113f 100644
--- a/server/src/main/java/com/vaadin/server/VaadinService.java
+++ b/server/src/main/java/com/vaadin/server/VaadinService.java
@@ -622,7 +622,7 @@ public abstract class VaadinService implements Serializable {
* @param lock
* The lock object
*/
- private void setSessionLock(WrappedSession wrappedSession, Lock lock) {
+ protected void setSessionLock(WrappedSession wrappedSession, Lock lock) {
if (wrappedSession == null) {
throw new IllegalArgumentException(
"Can't set a lock for a null session");
@@ -640,7 +640,7 @@ public abstract class VaadinService implements Serializable {
*
* @return The attribute name for the lock
*/
- private String getLockAttributeName() {
+ protected String getLockAttributeName() {
return getServiceName() + ".lock";
}
diff --git a/server/src/main/java/com/vaadin/server/VaadinServlet.java b/server/src/main/java/com/vaadin/server/VaadinServlet.java
index b0d270accb..794cfdd1d6 100644
--- a/server/src/main/java/com/vaadin/server/VaadinServlet.java
+++ b/server/src/main/java/com/vaadin/server/VaadinServlet.java
@@ -28,11 +28,13 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
+import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -46,6 +48,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
@@ -759,6 +762,13 @@ public class VaadinServlet extends HttpServlet implements Constants {
private static boolean scssCompileWarWarningEmitted = false;
/**
+ * Pattern for matching request paths that start with /VAADIN/, multiple
+ * slashes allowed on either side.
+ */
+ private static Pattern staticFileRequestPathPatternVaadin = Pattern
+ .compile("^/+VAADIN/.*");
+
+ /**
* Returns the default theme. Must never return null.
*
* @return
@@ -1348,16 +1358,44 @@ public class VaadinServlet extends HttpServlet implements Constants {
* @since 8.0
*/
protected String getStaticFilePath(HttpServletRequest request) {
- String pathInfo = request.getPathInfo();
- if (pathInfo == null) {
+ if (request.getPathInfo() == null) {
return null;
}
+ String decodedPath = null;
+ String contextPath = null;
+ try {
+ // pathInfo should be already decoded, but some containers do not
+ // decode it, hence we use getRequestURI instead.
+ decodedPath = URLDecoder.decode(request.getRequestURI(),
+ StandardCharsets.UTF_8.name());
+ contextPath = URLDecoder.decode(request.getContextPath(),
+ StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("An error occurred during decoding URL.",
+ e);
+ }
+ // Possible context path needs to be removed
+ String filePath = decodedPath.substring(contextPath.length());
+ String servletPath = request.getServletPath();
+ // Possible servlet path needs to be removed
+ if (!servletPath.isEmpty() && !servletPath.equals("/VAADIN")
+ && filePath.startsWith(servletPath)) {
+ filePath = filePath.substring(servletPath.length());
+ }
// Servlet mapped as /* serves at /VAADIN
// Servlet mapped as /foo/bar/* serves at /foo/bar/VAADIN
- if (pathInfo.startsWith("/VAADIN/")) {
- return pathInfo;
+
+ // Matches request paths /VAADIN/*, //VAADIN/* etc.
+ if (staticFileRequestPathPatternVaadin.matcher(filePath).matches()) {
+ // Remove any extra slashes from the beginning,
+ // later occurrences don't interfere
+ while (filePath.startsWith("//")) {
+ filePath = filePath.substring(1);
+ }
+ return filePath;
}
- String servletPrefixedPath = request.getServletPath() + pathInfo;
+
+ String servletPrefixedPath = servletPath + filePath;
// Servlet mapped as /VAADIN/*
if (servletPrefixedPath.startsWith("/VAADIN/")) {
return servletPrefixedPath;
diff --git a/server/src/main/java/com/vaadin/ui/Composite.java b/server/src/main/java/com/vaadin/ui/Composite.java
index 8c9379f3f7..0d3418c4b1 100644
--- a/server/src/main/java/com/vaadin/ui/Composite.java
+++ b/server/src/main/java/com/vaadin/ui/Composite.java
@@ -216,6 +216,16 @@ public class Composite extends AbstractComponent implements HasComponents {
}
@Override
+ public void setEnabled(boolean enabled) {
+ getRootOrThrow().setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return getRootOrThrow().isEnabled();
+ }
+
+ @Override
public float getWidth() {
return getRootOrThrow().getWidth();
}
diff --git a/server/src/main/java/com/vaadin/ui/CustomLayout.java b/server/src/main/java/com/vaadin/ui/CustomLayout.java
index 7764846065..4fd331c462 100644
--- a/server/src/main/java/com/vaadin/ui/CustomLayout.java
+++ b/server/src/main/java/com/vaadin/ui/CustomLayout.java
@@ -292,6 +292,9 @@ public class CustomLayout extends AbstractLayout implements LegacyComponent {
/**
* Set the contents of the template used to draw the custom layout.
*
+ * Note: setTemplateContents can be applied only before CustomLayout
+ * instance has been attached.
+ *
* @param templateContents
*/
public void setTemplateContents(String templateContents) {
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index 8d19d89a7e..1ac71ff984 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -4390,7 +4390,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
Objects.requireNonNull(destination,
"ScrollDestination can not be null");
- if (row > getDataCommunicator().getDataProviderSize()) {
+ if (row >= getDataCommunicator().getDataProviderSize()) {
throw new IllegalArgumentException("Row outside dataProvider size");
}
diff --git a/server/src/main/java/com/vaadin/ui/MultiSelect.java b/server/src/main/java/com/vaadin/ui/MultiSelect.java
index d2cd7def59..0e75e8377d 100644
--- a/server/src/main/java/com/vaadin/ui/MultiSelect.java
+++ b/server/src/main/java/com/vaadin/ui/MultiSelect.java
@@ -86,7 +86,8 @@ public interface MultiSelect<T> extends HasValue<Set<T>> {
* If all the added items were already selected and the removed items were
* not selected, this is a NO-OP.
* <p>
- * Duplicate items (in both add & remove sets) are ignored.
+ * Duplicate items (in both add & remove sets) are ignored and removed from
+ * the sets.
*
* @param addedItems
* the items to add, not {@code null}
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java b/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
index f51744c96e..0ef1d1a5f7 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
@@ -15,7 +15,24 @@
*/
package com.vaadin.ui.components.grid;
-import com.vaadin.data.provider.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.vaadin.data.provider.DataCommunicator;
+import com.vaadin.data.provider.DataProvider;
+import com.vaadin.data.provider.HierarchicalDataProvider;
+import com.vaadin.data.provider.HierarchicalQuery;
+import com.vaadin.data.provider.Query;
import com.vaadin.event.selection.MultiSelectionEvent;
import com.vaadin.event.selection.MultiSelectionListener;
import com.vaadin.shared.Registration;
@@ -23,11 +40,6 @@ import com.vaadin.shared.data.selection.GridMultiSelectServerRpc;
import com.vaadin.shared.ui.grid.MultiSelectionModelState;
import com.vaadin.ui.MultiSelect;
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
/**
* Multiselection model for grid.
* <p>
@@ -134,8 +146,7 @@ public class MultiSelectionModelImpl<T> extends AbstractSelectionModel<T>
@Override
public boolean isSelected(T item) {
- return isAllSelected()
- || selectionContainsId(getGrid().getDataProvider().getId(item));
+ return selectionContainsId(getGrid().getDataProvider().getId(item));
}
/**
diff --git a/server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java
index 94fbef613d..5c074d774c 100644
--- a/server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java
+++ b/server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java
@@ -52,9 +52,12 @@ public class TextRenderer extends AbstractRenderer<Object, Object> {
public JsonValue encode(Object value) {
if (value == null) {
return super.encode(null);
- } else {
- return Json.create(value.toString());
}
+ String stringValue = value.toString();
+ if (stringValue == null) {
+ return super.encode(null);
+ }
+ return Json.create(stringValue);
}
@Override
diff --git a/server/src/main/java/com/vaadin/ui/themes/ValoTheme.java b/server/src/main/java/com/vaadin/ui/themes/ValoTheme.java
index 0f5a09c1b9..3ec3b66c85 100644
--- a/server/src/main/java/com/vaadin/ui/themes/ValoTheme.java
+++ b/server/src/main/java/com/vaadin/ui/themes/ValoTheme.java
@@ -100,13 +100,13 @@ public class ValoTheme {
/**
* Success notification style. Adds a border around the notification and an
- * icon next to the title. Can be combined with any other Label style.
+ * icon next to the title. Can be combined with any other Notification style.
*/
public static final String NOTIFICATION_SUCCESS = "success";
/**
* Failure notification style. Adds a border around the notification and an
- * icon next to the title. Can be combined with any other Label style.
+ * icon next to the title. Can be combined with any other Notification style.
*/
public static final String NOTIFICATION_FAILURE = "failure";
@@ -117,6 +117,12 @@ public class ValoTheme {
*/
public static final String NOTIFICATION_CRITICAL_ERROR = "critical-error";
+ /**
+ * Styles the notification to be dark variant. Can be combined with any
+ * other Notification style.
+ */
+ public static final String NOTIFICATION_DARK = "dark";
+
/***************************************************************************
*
* Label styles
diff --git a/server/src/test/java/com/vaadin/data/BinderComponentTest.java b/server/src/test/java/com/vaadin/data/BinderComponentTest.java
index 776816be5e..ab5122cc76 100644
--- a/server/src/test/java/com/vaadin/data/BinderComponentTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderComponentTest.java
@@ -66,17 +66,13 @@ public class BinderComponentTest
private <T> void testFieldNullRepresentation(T initialValue,
HasValue<T> field) {
- binder.bind(field, t -> null, (str, val) -> {
- assertEquals("Value update with initial value failed.",
- initialValue, field.getValue());
- });
+ binder.bind(field, t -> null, (str, val) -> {});
field.setValue(initialValue);
assertEquals("Initial value of field unexpected", initialValue,
field.getValue());
binder.setBean(item);
assertEquals("Null representation for field failed",
field.getEmptyValue(), field.getValue());
- field.setValue(initialValue);
}
}
diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java
index f4515dc211..b3fe5a8194 100644
--- a/server/src/test/java/com/vaadin/data/BinderTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderTest.java
@@ -8,16 +8,20 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.Binder.BindingBuilder;
@@ -31,9 +35,17 @@ import com.vaadin.tests.data.bean.Person;
import com.vaadin.tests.data.bean.Sex;
import com.vaadin.ui.TextField;
import org.apache.commons.lang.StringUtils;
+import org.hamcrest.CoreMatchers;
public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
+ @Rule
+ /*
+ * transient to avoid interfering with serialization tests that capture a
+ * test instance in a closure
+ */
+ public transient ExpectedException exceptionRule = ExpectedException.none();
+
@Before
public void setUp() {
binder = new Binder<>();
@@ -258,6 +270,39 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
}
@Test
+ public void save_bound_beanAsDraft() {
+ Binder<Person> binder = new Binder<>();
+ binder.forField(nameField)
+ .withValidator((value,context) -> {
+ if (value.equals("Mike")) return ValidationResult.ok();
+ else return ValidationResult.error("value must be Mike");
+ })
+ .bind(Person::getFirstName, Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+
+ Person person = new Person();
+
+ String fieldValue = "John";
+ nameField.setValue(fieldValue);
+
+ int age = 10;
+ ageField.setValue("10");
+
+ person.setFirstName("Mark");
+
+ binder.writeBeanAsDraft(person);
+
+ // name is not written to draft as validation / conversion
+ // does not pass
+ assertNotEquals(fieldValue, person.getFirstName());
+ // age is written to draft even if firstname validation
+ // fails
+ assertEquals(age, person.getAge());
+ }
+
+ @Test
public void load_bound_fieldValueIsUpdated() {
binder.bind(nameField, Person::getFirstName, Person::setFirstName);
@@ -333,7 +378,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
binder.setBean(namelessPerson);
assertTrue(nullTextField.isEmpty());
- assertEquals(null, namelessPerson.getFirstName());
+ assertEquals("null", namelessPerson.getFirstName());
// Change value, see that textfield is not empty and bean is updated.
nullTextField.setValue("");
@@ -428,13 +473,13 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
TextField textField = new TextField();
assertFalse(textField.isRequiredIndicatorVisible());
- BindingBuilder<Person, String> binding = binder.forField(textField);
+ BindingBuilder<Person, String> bindingBuilder = binder.forField(textField);
assertFalse(textField.isRequiredIndicatorVisible());
- binding.asRequired("foobar");
+ bindingBuilder.asRequired("foobar");
assertTrue(textField.isRequiredIndicatorVisible());
- binding.bind(Person::getFirstName, Person::setFirstName);
+ Binding<Person, String> binding = bindingBuilder.bind(Person::getFirstName, Person::setFirstName);
binder.setBean(item);
assertNull(textField.getErrorMessage());
@@ -446,6 +491,9 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
textField.setValue("value");
assertNull(textField.getErrorMessage());
assertTrue(textField.isRequiredIndicatorVisible());
+
+ binding.setAsRequiredEnabled(false);
+ assertFalse(textField.isRequiredIndicatorVisible());
}
@Test
@@ -525,7 +573,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
binding.bind(Person::getFirstName, Person::setFirstName);
binder.setBean(item);
assertNull(textField.getErrorMessage());
- assertEquals(0, invokes.get());
+ assertEquals(1, invokes.get());
textField.setValue(" ");
ErrorMessage errorMessage = textField.getErrorMessage();
@@ -533,7 +581,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
assertEquals("Input&#32;is&#32;required&#46;",
errorMessage.getFormattedHtmlMessage());
// validation is done for all changed bindings once.
- assertEquals(1, invokes.get());
+ assertEquals(2, invokes.get());
textField.setValue("value");
assertNull(textField.getErrorMessage());
@@ -582,7 +630,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
binder.setBean(item);
assertNull(textField.getErrorMessage());
- assertEquals(0, invokes.get());
+ assertEquals(1, invokes.get());
textField.setValue(" ");
ErrorMessage errorMessage = textField.getErrorMessage();
@@ -590,7 +638,7 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
assertEquals("Input&#32;required&#46;",
errorMessage.getFormattedHtmlMessage());
// validation is done for all changed bindings once.
- assertEquals(1, invokes.get());
+ assertEquals(2, invokes.get());
textField.setValue("value");
assertNull(textField.getErrorMessage());
@@ -1099,12 +1147,12 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
binder.setBean(item);
ageField.setValue("3");
- Assert.assertEquals(infoMessage,
+ assertEquals(infoMessage,
ageField.getComponentError().getFormattedHtmlMessage());
- Assert.assertEquals(ErrorLevel.INFO,
+ assertEquals(ErrorLevel.INFO,
ageField.getComponentError().getErrorLevel());
- Assert.assertEquals(3, item.getAge());
+ assertEquals(3, item.getAge());
}
@Test
@@ -1246,4 +1294,118 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
nameField.setValue("Foo");
}
+
+ @Test
+ public void nonSymetricValue_setBean_writtenToBean() {
+ binder.bind(nameField, Person::getLastName, Person::setLastName);
+
+ assertNull(item.getLastName());
+
+ binder.setBean(item);
+
+ assertEquals("", item.getLastName());
+ }
+
+ @Test
+ public void nonSymmetricValue_readBean_beanNotTouched() {
+ binder.bind(nameField, Person::getLastName, Person::setLastName);
+ binder.addValueChangeListener(
+ event -> fail("No value change event should be fired"));
+
+ assertNull(item.getLastName());
+
+ binder.readBean(item);
+
+ assertNull(item.getLastName());
+ }
+
+ @Test
+ public void symetricValue_setBean_beanNotUpdated() {
+ binder.bind(nameField, Person::getFirstName, Person::setFirstName);
+
+ binder.setBean(new Person() {
+ @Override
+ public String getFirstName() {
+ return "First";
+ }
+
+ @Override
+ public void setFirstName(String firstName) {
+ fail("Setter should not be called");
+ }
+ });
+ }
+
+ @Test
+ public void nullRejetingField_nullValue_wrappedExceptionMentionsNullRepresentation() {
+ TextField field = createNullAnd42RejectingFieldWithEmptyValue("");
+
+ Binder<AtomicReference<Integer>> binder = createIntegerConverterBinder(
+ field);
+
+ exceptionRule.expect(IllegalStateException.class);
+ exceptionRule.expectMessage("null representation");
+ exceptionRule.expectCause(CoreMatchers.isA(NullPointerException.class));
+
+ binder.readBean(new AtomicReference<>());
+ }
+
+
+ @Test
+ public void nullRejetingField_otherRejectedValue_originalExceptionIsThrown() {
+ TextField field = createNullAnd42RejectingFieldWithEmptyValue("");
+
+ Binder<AtomicReference<Integer>> binder = createIntegerConverterBinder(
+ field);
+
+ exceptionRule.expect(IllegalArgumentException.class);
+ exceptionRule.expectMessage("42");
+
+ binder.readBean(new AtomicReference<>(Integer.valueOf(42)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullAcceptingField_nullValue_originalExceptionIsThrown() {
+ /*
+ * Edge case with a field that throws for null but has null as the empty
+ * value. This is most likely the case if the field doesn't explicitly
+ * reject null values but is instead somehow broken so that any value is
+ * rejected.
+ */
+ TextField field = createNullAnd42RejectingFieldWithEmptyValue(null);
+
+ Binder<AtomicReference<Integer>> binder = createIntegerConverterBinder(
+ field);
+
+ binder.readBean(new AtomicReference<>(null));
+ }
+
+ private TextField createNullAnd42RejectingFieldWithEmptyValue(
+ String emptyValue) {
+ return new TextField() {
+ @Override
+ public void setValue(String value) {
+ if (value == null) {
+ throw new NullPointerException("Null value");
+ } else if ("42".equals(value)) {
+ throw new IllegalArgumentException("42 is not allowed");
+ }
+ super.setValue(value);
+ }
+
+ @Override
+ public String getEmptyValue() {
+ return emptyValue;
+ }
+ };
+ }
+
+ private Binder<AtomicReference<Integer>> createIntegerConverterBinder(
+ TextField field) {
+ Binder<AtomicReference<Integer>> binder = new Binder<>();
+ binder.forField(field)
+ .withConverter(new StringToIntegerConverter("Must have number"))
+ .bind(AtomicReference::get, AtomicReference::set);
+ return binder;
+ }
}
diff --git a/server/src/test/java/com/vaadin/server/VaadinPortletServiceTest.java b/server/src/test/java/com/vaadin/server/VaadinPortletServiceTest.java
index 2d7da64d20..2280e7d4f5 100644
--- a/server/src/test/java/com/vaadin/server/VaadinPortletServiceTest.java
+++ b/server/src/test/java/com/vaadin/server/VaadinPortletServiceTest.java
@@ -9,6 +9,8 @@ import static org.mockito.Mockito.when;
import java.util.concurrent.locks.ReentrantLock;
+import javax.portlet.PortletSession;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@@ -185,9 +187,9 @@ public class VaadinPortletServiceTest {
ReentrantLock mockLock = Mockito.mock(ReentrantLock.class);
when(mockLock.isHeldByCurrentThread()).thenReturn(true);
- WrappedSession emptyWrappedSession = Mockito
+ WrappedPortletSession emptyWrappedSession = Mockito
.mock(WrappedPortletSession.class);
- when(emptyWrappedSession.getAttribute("null.lock"))
+ when(emptyWrappedSession.getAttribute("null.lock",PortletSession.APPLICATION_SCOPE))
.thenReturn(mockLock);
VaadinRequest requestWithUIIDSet = Mockito
.mock(VaadinRequest.class);
diff --git a/server/src/test/java/com/vaadin/server/VaadinServletTest.java b/server/src/test/java/com/vaadin/server/VaadinServletTest.java
index f1490208d4..652dc30665 100644
--- a/server/src/test/java/com/vaadin/server/VaadinServletTest.java
+++ b/server/src/test/java/com/vaadin/server/VaadinServletTest.java
@@ -116,6 +116,8 @@ public class VaadinServletTest {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getServletPath()).thenReturn(servletPath);
Mockito.when(request.getPathInfo()).thenReturn(pathInfo);
+ Mockito.when(request.getRequestURI()).thenReturn("/context"+pathInfo);
+ Mockito.when(request.getContextPath()).thenReturn("/context");
return request;
}
}
diff --git a/test/servlet-containers/generic/build.xml b/test/servlet-containers/generic/build.xml
index 61f915c833..e5af1c85bf 100644
--- a/test/servlet-containers/generic/build.xml
+++ b/test/servlet-containers/generic/build.xml
@@ -39,10 +39,10 @@
<target name="clean-testbench-errors"><!--todo remove when have got rid of screenshots-->
<fail unless="com.vaadin.testbench.screenshot.directory"
message="Define screenshot directory using -Dcom.vaadin.testbench.screenshot.directory" />
- <mkdir dir="${com.vaadin.testbench.screenshot.directory}/errors" />
+ <mkdir dir="${com.vaadin.testbench.screenshot.directory}/error-screenshots" />
<delete>
<fileset
- dir="${com.vaadin.testbench.screenshot.directory}/errors">
+ dir="${com.vaadin.testbench.screenshot.directory}/error-screenshots">
<include name="*" />
</fileset>
</delete>
diff --git a/test/spring-boot-subcontext/src/test/java/com/example/VaadinSpringBootSmokeIT.java b/test/spring-boot-subcontext/src/test/java/com/example/VaadinSpringBootSmokeIT.java
index f6f3db6243..bbd0bc628f 100644
--- a/test/spring-boot-subcontext/src/test/java/com/example/VaadinSpringBootSmokeIT.java
+++ b/test/spring-boot-subcontext/src/test/java/com/example/VaadinSpringBootSmokeIT.java
@@ -40,12 +40,26 @@ public class VaadinSpringBootSmokeIT extends TestBenchTestCase {
@Test
public void testPageLoadsAndButtonWorks() {
getDriver().navigate()
+ .to("http://localhost:" + port + DemoApplication.CONTEXT);
+ runSmokeTest();
+ }
+
+ @Test
+ public void testPageLoadsAndButtonWorksWithExtraSlash() {
+ getDriver().navigate()
.to("http://localhost:" + port + "/" + DemoApplication.CONTEXT);
runSmokeTest();
}
@Test
public void testSubPathPageLoadsAndButtonWorks() {
+ getDriver().navigate().to("http://localhost:" + port
+ + DemoApplication.CONTEXT + "/" + SubPathUI.SUBPATH);
+ runSmokeTest();
+ }
+
+ @Test
+ public void testSubPathPageLoadsAndButtonWorksWithExtraSlash() {
getDriver().navigate().to("http://localhost:" + port + "/"
+ DemoApplication.CONTEXT + "/" + SubPathUI.SUBPATH);
runSmokeTest();
diff --git a/uitest/eclipse-run-selected-test.properties b/uitest/eclipse-run-selected-test.properties
index 0315b46a6d..1d257d1773 100644
--- a/uitest/eclipse-run-selected-test.properties
+++ b/uitest/eclipse-run-selected-test.properties
@@ -10,7 +10,7 @@
# Location of the screenshot directory. This is mutually exclusive with the folder settings for XVFB testing.
# This is the directory that contains the "references" directory
-com.vaadin.testbench.screenshot.directory=<enter the full path to the screenshots directory, parent of "references" directory>
+com.vaadin.testbench.screenshot.directory=<enter the full path to the "uitest" directory, parent of "reference-screenshots" directory>
# Deployment url to use for testing. Context path must be /
diff --git a/uitest/src/main/java/com/vaadin/screenshotbrowser/ScreenshotBrowser.java b/uitest/src/main/java/com/vaadin/screenshotbrowser/ScreenshotBrowser.java
index 089a981b1f..37916493fa 100644
--- a/uitest/src/main/java/com/vaadin/screenshotbrowser/ScreenshotBrowser.java
+++ b/uitest/src/main/java/com/vaadin/screenshotbrowser/ScreenshotBrowser.java
@@ -9,6 +9,10 @@ import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.DirectoryFileFilter;
+import org.apache.commons.io.filefilter.SuffixFileFilter;
+
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.ExternalResource;
@@ -35,11 +39,11 @@ public class ScreenshotBrowser extends UI {
* 3 - platform
* 4 - browser name
* 5 - browser version
- * 6 - additional qualifiers
- * 7 - identifier
+ * 6 - identifier
+ * 7 - additional identifiers
*/
private static final Pattern screenshotNamePattern = Pattern
- .compile("(.+?)-(.+?)_(.+?)_(.+?)_(.+?)(_.+)?_(.+?)\\.png\\.html");
+ .compile("(.+?)-(.+?)_(.+?)_(.+?)_(.*?)_(.+?)(_.+)?\\.png\\.html");
public static enum Action {
ACCEPT {
@@ -97,7 +101,7 @@ public class ScreenshotBrowser extends UI {
}
private static File getReferenceDir() {
- return new File(screenshotDir, "reference");
+ return new File(screenshotDir, "reference-screenshots");
}
private static File getAlternative(File baseFile,
@@ -146,12 +150,12 @@ public class ScreenshotBrowser extends UI {
return matcher.group(4) + " " + matcher.group(5);
}
- public String getQualifiers() {
- return matcher.group(6);
- }
-
public String getIdentifier() {
- return matcher.group(7);
+ String additional = matcher.group(7);
+ if (additional != null) {
+ return matcher.group(6) + additional;
+ }
+ return matcher.group(6);
}
public void setAction(Action action) {
@@ -176,7 +180,8 @@ public class ScreenshotBrowser extends UI {
left.setMargin(true);
left.setSpacing(true);
- left.setSizeFull();
+ left.setSizeUndefined();
+ left.setWidth("270px");
left.addComponent(
createActionButton("Accept changes", 'j', Action.ACCEPT));
@@ -199,15 +204,17 @@ public class ScreenshotBrowser extends UI {
left.addComponent(expandSpacer);
left.setExpandRatio(expandSpacer, 1);
- left.addComponent(new Label(
- "Press the j, k or l keys to quickly select an action for the selected item."));
+ Label instructions = new Label(
+ "Press the j, k or l keys to quickly select an action for the selected item.");
+ instructions.setWidth("100%");
+ left.addComponent(instructions);
root.setExpandRatio(left, 1);
root.setSizeFull();
setCompositionRoot(root);
setHeight("850px");
- setWidth("100%");
+ setWidth("1800px");
}
private Button createActionButton(String caption, char shortcut,
@@ -216,6 +223,11 @@ public class ScreenshotBrowser extends UI {
caption + " <strong>" + shortcut + "</strong>",
createSetActionListener(action));
button.setCaptionAsHtml(true);
+ if (!Action.IGNORE.equals(action)) {
+ // other actions disabled for now since the functionality was
+ // designed for a different directory structure, needs reworking
+ button.setEnabled(false);
+ }
return button;
}
@@ -253,7 +265,7 @@ public class ScreenshotBrowser extends UI {
@Override
protected void init(VaadinRequest request) {
table.setWidth("100%");
- table.setHeight("100%");
+ table.setPageLength(10);
table.setMultiSelect(true);
table.addValueChangeListener(event -> {
@@ -274,9 +286,8 @@ public class ScreenshotBrowser extends UI {
refreshTableContainer();
- VerticalLayout mainLayout = new VerticalLayout(table, viewer);
- mainLayout.setExpandRatio(table, 1);
- mainLayout.setSizeFull();
+ VerticalLayout mainLayout = new VerticalLayout(viewer, table);
+ mainLayout.setSizeUndefined();
setSizeFull();
setContent(mainLayout);
@@ -339,10 +350,10 @@ public class ScreenshotBrowser extends UI {
}
private void refreshTableContainer() {
- File errorsDir = new File(screenshotDir, "errors");
+ File errorsDir = new File(screenshotDir, "error-screenshots");
- File[] failures = errorsDir
- .listFiles((dir, name) -> name.endsWith(".html"));
+ Collection<File> failures = FileUtils.listFiles(errorsDir,
+ new SuffixFileFilter(".html"), DirectoryFileFilter.DIRECTORY);
BeanItemContainer<ComparisonFailure> container = new BeanItemContainer<>(
ComparisonFailure.class);
@@ -352,7 +363,7 @@ public class ScreenshotBrowser extends UI {
table.setContainerDataSource(container);
table.setVisibleColumns("testClass", "testMethod", "browser",
- "qualifiers", "identifier", "action");
+ "identifier", "action");
if (container.size() > 0) {
table.select(container.firstItemId());
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxPasteFilter.java b/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxPasteFilter.java
new file mode 100644
index 0000000000..ba36c69d73
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxPasteFilter.java
@@ -0,0 +1,66 @@
+package com.vaadin.tests.components.combobox;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.vaadin.data.provider.ListDataProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.Label;
+
+public class ComboBoxPasteFilter extends AbstractTestUI {
+
+ private static final String WORDS = "loutish offer popcorn bitter buzz "
+ + "change mass boy erect aquatic donkey gentle colorful zippy "
+ + "soup pocket bathe fear supreme pan present knife quartz shy "
+ + "conscious tested thumb snow evasive reason dusty bridge giddy "
+ + "smooth bomb endurable tiger red gun fix regret quizzical income "
+ + "careless owe sleet loss silent serious play consider messy "
+ + "retire reduce shaky shiny low suggest preach bleach drunk "
+ + "talk instruct peck hungry improve meat chop title encourage "
+ + "marry island romantic fabulous kneel guarantee dock complain "
+ + "mate tour intend geese hole swing mine superb level slip "
+ + "spoon sky live nine open playground guard possible hate "
+ + "spotless apparatus bow";
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ ComboBox<String> box = new ComboBox<String>("Paste a word from below");
+ Collection<String> massiveData = massiveData();
+ box.setDataProvider(new ListDataProvider<String>(massiveData));
+ addComponent(box);
+
+ Label label = new Label(WORDS);
+ label.setSizeFull();
+ addComponent(label);
+ }
+
+ private Collection<String> massiveData() {
+ return find(WORDS, "([\\w]+)");
+ }
+
+ public List<String> find(String text, String patternStr) {
+ Pattern pattern = Pattern.compile(patternStr);
+ Matcher matcher = pattern.matcher(text);
+ List<String> result = new ArrayList<>();
+ while (matcher.find()) {
+ result.add(matcher.group());
+ }
+ return result;
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 11779;
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "ComboBox should filter regardless of whether the value is "
+ + "pasted with keyboard or with mouse";
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridSelectAllFiltering.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridSelectAllFiltering.java
new file mode 100644
index 0000000000..3c85e9c435
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridSelectAllFiltering.java
@@ -0,0 +1,71 @@
+package com.vaadin.tests.components.grid;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import com.vaadin.data.provider.DataProvider;
+import com.vaadin.data.provider.ListDataProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.SelectionMode;
+import com.vaadin.ui.renderers.NumberRenderer;
+
+public class GridSelectAllFiltering extends SimpleGridUI {
+ private String filterText = "Johannes";
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ Grid<Person> grid = new Grid<>();
+ grid.setSelectionMode(SelectionMode.MULTI);
+ grid.setHeightByRows(3);
+ grid.addColumn(Person::getFirstName);
+ grid.addColumn(Person::getAge, new NumberRenderer());
+
+ ListDataProvider<Person> dataProvider = DataProvider
+ .ofCollection(createPersons());
+ dataProvider.setFilter(person -> {
+ if (person.getFirstName().contains(filterText)) {
+ return false;
+ }
+ return true;
+ });
+ grid.setDataProvider(dataProvider);
+
+ Button toggleButton = new Button("Toggle filter", e -> {
+ if ("Johannes".equals(filterText)) {
+ filterText = "Galileo";
+ } else {
+ filterText = "Johannes";
+ }
+ dataProvider.refreshAll();
+ });
+ toggleButton.setId("toggle");
+
+ Button checkButton = new Button("Check selection", e -> {
+ Set<Person> selected = grid.getSelectedItems();
+ Iterator<Person> i = selected.iterator();
+ log("selected " + selected.size()
+ + (i.hasNext() ? ": " + i.next().getFirstName() : "")
+ + (i.hasNext() ? ", " + i.next().getFirstName() : "")
+ + (i.hasNext() ? ", " + i.next().getFirstName() : "")
+ + (i.hasNext() ? "... " : ""));
+ });
+ checkButton.setId("check");
+
+ addComponents(grid, toggleButton, checkButton);
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 11479;
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "Selecting all does not select items that have been "
+ + "filtered out, they should not be shown selected "
+ + "after the filter changes.";
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/tree/TreeBasicFeatures.java b/uitest/src/main/java/com/vaadin/tests/components/tree/TreeBasicFeatures.java
index f429a6102c..98709f00fa 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/tree/TreeBasicFeatures.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/tree/TreeBasicFeatures.java
@@ -115,6 +115,15 @@ public class TreeBasicFeatures extends AbstractTestUIWithLog {
? t -> "level" + t.getDepth()
: t -> null))
.setCheckable(true);
+ MenuItem enabled = componentMenu.addItem("Enabled",
+ menuItem -> tree.setEnabled(!tree.isEnabled()));
+ enabled.setCheckable(true);
+ enabled.setChecked(true);
+
+ componentMenu.addItem("Expand 0 | 0",
+ menuItem -> tree.expand(new HierarchicalTestBean(null, 0, 0)));
+ componentMenu.addItem("Collapse 0 | 0", menuItem -> tree
+ .collapse(new HierarchicalTestBean(null, 0, 0)));
return menu;
}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/tree/TreeInitiallyDisabled.java b/uitest/src/main/java/com/vaadin/tests/components/tree/TreeInitiallyDisabled.java
new file mode 100644
index 0000000000..ca4a6bdb70
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/tree/TreeInitiallyDisabled.java
@@ -0,0 +1,51 @@
+package com.vaadin.tests.components.tree;
+
+import com.vaadin.data.TreeData;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Tree;
+
+public class TreeInitiallyDisabled extends AbstractTestUI {
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ Tree<String> tree = new Tree<>();
+ TreeData<String> treeData = new TreeData<>();
+ String parent1 = "Parent 1";
+ treeData.addItem(null, parent1);
+ treeData.addItem(parent1, "Child 1.1");
+ treeData.addItem(parent1, "Child 1.2");
+ treeData.addItem(parent1, "Child 1.3");
+ String parent2 = "Parent 2";
+ treeData.addItem(null, parent2);
+ treeData.addItem(parent2, "Child 2.1");
+ treeData.addItem(parent2, "Child 2.2");
+ treeData.addItem(parent2, "Child 2.3");
+ String parent3 = "Parent 3";
+ treeData.addItem(null, parent3);
+ treeData.addItem(parent3, "Child 3.1");
+ treeData.addItem(parent3, "Child 3.2");
+ treeData.addItem(parent3, "Child 3.3");
+ tree.setTreeData(treeData);
+
+ Button button = new Button("Toggle enabled/disabled");
+ button.addClickListener(event -> {
+ tree.setEnabled(!tree.isEnabled());
+ });
+
+ addComponents(tree, button);
+
+ tree.setEnabled(false);
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 11831;
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "Initially disabled Tree should have disabled styles.";
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridScrollDestinationTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridScrollDestinationTest.java
index 67a831557d..302ea741a4 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/grid/GridScrollDestinationTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridScrollDestinationTest.java
@@ -15,10 +15,12 @@ import com.vaadin.testbench.TestBenchElement;
import com.vaadin.testbench.elements.ButtonElement;
import com.vaadin.testbench.elements.GridElement;
import com.vaadin.testbench.elements.NativeSelectElement;
+import com.vaadin.testbench.elements.TextFieldElement;
import com.vaadin.tests.tb3.SingleBrowserTest;
public class GridScrollDestinationTest extends SingleBrowserTest {
+ private TextFieldElement textField;
private ButtonElement button;
private GridElement grid;
private TestBenchElement header;
@@ -28,6 +30,7 @@ public class GridScrollDestinationTest extends SingleBrowserTest {
public void setup() throws Exception {
super.setup();
openTestURL();
+ textField = $(TextFieldElement.class).first();
button = $(ButtonElement.class).first();
grid = $(GridElement.class).first();
header = grid.getHeader();
@@ -110,6 +113,28 @@ public class GridScrollDestinationTest extends SingleBrowserTest {
rows = grid.getBody().findElements(By.className("v-grid-row"));
row = rows.get(6);
assertEquals("50", row.getText());
+
+ // scroll to beginning using scroll destination
+ textField.setValue("0");
+ button.click();
+
+ // expect to be scrolled all the way up
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(0);
+ assertEquals("0", row.getText());
+
+ assertElementAtTop(row);
+
+ // scroll to end using scroll destination
+ textField.setValue("99");
+ button.click();
+
+ // expect to be scrolled all the way down
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(rows.size() - 1);
+ assertEquals("99", row.getText());
+
+ assertElementAtBottom(row);
}
@Test
@@ -168,6 +193,29 @@ public class GridScrollDestinationTest extends SingleBrowserTest {
assertEquals("50", row.getText());
assertElementAtBottom(row);
+
+ // scroll to beginning using scroll destination
+ textField.setValue("0");
+ button.click();
+ button.click();
+
+ // expect to be scrolled all the way up
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(0);
+ assertEquals("0", row.getText());
+
+ assertElementAtTop(row);
+
+ // scroll to end using scroll destination
+ textField.setValue("99");
+ button.click();
+
+ // expect to be scrolled all the way down
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(rows.size() - 1);
+ assertEquals("99", row.getText());
+
+ assertElementAtBottom(row);
}
@Test
@@ -226,6 +274,28 @@ public class GridScrollDestinationTest extends SingleBrowserTest {
assertEquals("50", row.getText());
assertElementAtTop(row);
+
+ // scroll to beginning using scroll destination
+ textField.setValue("0");
+ button.click();
+
+ // expect to be scrolled all the way up
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(0);
+ assertEquals("0", row.getText());
+
+ assertElementAtTop(row);
+
+ // scroll to end using scroll destination
+ textField.setValue("99");
+ button.click();
+
+ // expect to be scrolled all the way down
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(rows.size() - 1);
+ assertEquals("99", row.getText());
+
+ assertElementAtBottom(row);
}
@Test
@@ -287,5 +357,27 @@ public class GridScrollDestinationTest extends SingleBrowserTest {
assertEquals("50", row.getText());
assertElementAtMiddle(row);
+
+ // scroll to beginning using scroll destination
+ textField.setValue("0");
+ button.click();
+
+ // expect to be scrolled all the way up
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(0);
+ assertEquals("0", row.getText());
+
+ assertElementAtTop(row);
+
+ // scroll to end using scroll destination
+ textField.setValue("99");
+ button.click();
+
+ // expect to be scrolled all the way down
+ rows = grid.getBody().findElements(By.className("v-grid-row"));
+ row = rows.get(rows.size() - 1);
+ assertEquals("99", row.getText());
+
+ assertElementAtBottom(row);
}
}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectAllFilteringTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectAllFilteringTest.java
new file mode 100644
index 0000000000..37c7d6281c
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectAllFilteringTest.java
@@ -0,0 +1,62 @@
+package com.vaadin.tests.components.grid;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class GridSelectAllFilteringTest extends MultiBrowserTest {
+
+ @Test
+ public void checkSelectAll() {
+ openTestURL();
+
+ GridElement grid = $(GridElement.class).first();
+ ButtonElement toggleButton = $(ButtonElement.class).id("toggle");
+ ButtonElement checkButton = $(ButtonElement.class).id("check");
+
+ // ensure no initial selection
+ checkButton.click();
+ assertEquals("Unexpected log entry,", "1. selected 0", getLogRow(0));
+ assertEquals("Unexpected amount of visually selected rows,", 0,
+ grid.findElements(By.className("v-grid-row-selected")).size());
+
+ // select all
+ WebElement selectAllCheckbox = grid
+ .findElement(By.className("v-grid-select-all-checkbox"));
+ selectAllCheckbox.click();
+
+ // ensure only the two visible rows get selected
+ checkButton.click();
+ assertEquals("Unexpected log entry,",
+ "2. selected 2: Nicolaus Copernicus, Galileo Galilei",
+ getLogRow(0));
+ assertEquals("Unexpected amount of visually selected rows,", 2,
+ grid.findElements(By.className("v-grid-row-selected")).size());
+
+ // toggle filter
+ toggleButton.click();
+
+ // ensure selection did not change but only one selected row is visible
+ checkButton.click();
+ assertEquals("Unexpected log entry,",
+ "3. selected 2: Nicolaus Copernicus, Galileo Galilei",
+ getLogRow(0));
+ assertEquals("Unexpected amount of visually selected rows,", 1,
+ grid.findElements(By.className("v-grid-row-selected")).size());
+
+ // remove all selections
+ selectAllCheckbox.click();
+
+ // ensure all selections got removed whether they were visible or not
+ checkButton.click();
+ assertEquals("Unexpected log entry,", "4. selected 0", getLogRow(0));
+ assertEquals("Unexpected amount of visually selected rows,", 0,
+ grid.findElements(By.className("v-grid-row-selected")).size());
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/tree/TreeBasicFeaturesTest.java b/uitest/src/test/java/com/vaadin/tests/components/tree/TreeBasicFeaturesTest.java
index 4e133015b3..bc8771aa91 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/tree/TreeBasicFeaturesTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/tree/TreeBasicFeaturesTest.java
@@ -252,4 +252,26 @@ public class TreeBasicFeaturesTest extends MultiBrowserTest {
$(TreeElement.class).first().getItem(0).showTooltip();
assertEquals("", "0 | 0", getTooltipElement().getText());
}
+
+ @Test
+ public void tree_disable_enable_expand_collapse() {
+ TreeElement tree = $(TreeElement.class).first();
+ selectMenuPath("Component", "Enabled");
+ assertTrue(tree.hasClassName("v-disabled"));
+ // ensure expanding doesn't work
+ tree.expand(0);
+ assertEquals("0 | 1", tree.getItem(1).getText());
+ selectMenuPath("Component", "Enabled");
+ assertFalse(tree.hasClassName("v-disabled"));
+ // ensure expanding and collapsing works again
+ tree.expand(0);
+ assertEquals("1 | 0", tree.getItem(1).getText());
+ tree.collapse(0);
+ assertEquals("0 | 1", tree.getItem(1).getText());
+ // same test for server-side expanding and collapsing
+ selectMenuPath("Component", "Expand 0 | 0");
+ assertEquals("1 | 0", tree.getItem(1).getText());
+ selectMenuPath("Component", "Collapse 0 | 0");
+ assertEquals("0 | 1", tree.getItem(1).getText());
+ }
}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/tree/TreeInitiallyDisabledTest.java b/uitest/src/test/java/com/vaadin/tests/components/tree/TreeInitiallyDisabledTest.java
new file mode 100644
index 0000000000..93cb1633f1
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/tree/TreeInitiallyDisabledTest.java
@@ -0,0 +1,18 @@
+package com.vaadin.tests.components.tree;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.TreeElement;
+import com.vaadin.tests.tb3.SingleBrowserTest;
+
+public class TreeInitiallyDisabledTest extends SingleBrowserTest {
+
+ @Test
+ public void checkDisabledStyleAdded() {
+ openTestURL();
+ TreeElement tree = $(TreeElement.class).first();
+ assertTrue(tree.hasClassName("v-disabled"));
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
index 39d40438fc..04ca0003b9 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/treegrid/TreeGridBasicFeaturesTest.java
@@ -348,6 +348,30 @@ public class TreeGridBasicFeaturesTest extends MultiBrowserTest {
assertEquals("Not expanded", "1 | 0", grid.getCell(1, 0).getText());
}
+ @Test
+ public void disable_enable_expand_collapse() {
+ TreeGridElement treeGrid = $(TreeGridElement.class).first();
+ selectMenuPath("Component", "State", "Enabled");
+ assertTrue(treeGrid.hasClassName("v-disabled"));
+ // ensure expanding doesn't work
+ treeGrid.expandWithClick(0);
+ assertCellTexts(1, 0, new String[] { "0 | 1" });
+ selectMenuPath("Component", "State", "Enabled");
+ assertFalse(treeGrid.hasClassName("v-disabled"));
+ // ensure expanding and collapsing works again
+ treeGrid.expandWithClick(0);
+ assertCellTexts(1, 0, new String[] { "1 | 0" });
+ treeGrid.collapseWithClick(0);
+ assertCellTexts(1, 0, new String[] { "0 | 1" });
+ // same test for server-side expanding and collapsing
+ selectMenuPath("Component", "Features", "Server-side expand",
+ "Expand 0 | 0");
+ assertCellTexts(1, 0, new String[] { "1 | 0" });
+ selectMenuPath("Component", "Features", "Server-side collapse",
+ "Collapse 0 | 0");
+ assertCellTexts(1, 0, new String[] { "0 | 1" });
+ }
+
private void assertCellTexts(int startRowIndex, int cellIndex,
String[] cellTexts) {
int index = startRowIndex;
diff --git a/uitest/src/test/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java b/uitest/src/test/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java
index 1f4d113021..0390931968 100644
--- a/uitest/src/test/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java
+++ b/uitest/src/test/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java
@@ -80,8 +80,9 @@ public abstract class PrivateTB3Configuration extends ScreenshotTB3Test {
String dir = System.getProperty(SCREENSHOT_DIRECTORY,
properties.getProperty(SCREENSHOT_DIRECTORY));
if (dir != null && !dir.isEmpty()) {
- String reference = Paths.get(dir, "reference").toString();
- String errors = Paths.get(dir, "errors").toString();
+ String reference = Paths.get(dir, "reference-screenshots")
+ .toString();
+ String errors = Paths.get(dir, "error-screenshots").toString();
Parameters.setScreenshotReferenceDirectory(reference);
Parameters.setScreenshotErrorDirectory(errors);
} else {
diff --git a/uitest/src/test/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java b/uitest/src/test/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java
index e113e7783b..82db9898d2 100644
--- a/uitest/src/test/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java
+++ b/uitest/src/test/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java
@@ -43,7 +43,7 @@ public class VaadinBrowserFactory extends DefaultBrowserFactory {
case PHANTOMJS:
return create(browser, "1", Platform.LINUX);
case CHROME:
- return create(browser, "40", Platform.VISTA);
+ return create(browser, "", Platform.ANY);
case FIREFOX:
default:
DesiredCapabilities dc = create(Browser.FIREFOX, "45",