Skip to content

Commit 7078e69

Browse files
committed
fix: preserve value on unparsable input
1 parent a8f37a0 commit 7078e69

File tree

4 files changed

+145
-2
lines changed
  • vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src
  • vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src

4 files changed

+145
-2
lines changed

vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,13 +809,21 @@ protected void setModelValue(LocalDate newModelValue, boolean fromClient) {
809809
isFallbackParserRunning = false;
810810
}
811811

812+
// Case: User enters unparsable input in a field with valid input
813+
if (fromClient && newModelValue == null && oldModelValue != null
814+
&& isInputUnparsable()) {
815+
validate();
816+
fireValidationStatusChangeEvent();
817+
return;
818+
}
819+
812820
boolean isModelValueRemainedEmpty = newModelValue == null
813821
&& oldModelValue == null;
814822

815823
// Cases:
816824
// - User modifies input but it remains unparsable
817825
// - User enters unparsable input in empty field
818-
// - User clears unparsable input
826+
// - User clears unparsable input (that was already empty)
819827
if (fromClient && isModelValueRemainedEmpty) {
820828
validate();
821829
fireValidationStatusChangeEvent();

vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/test/java/com/vaadin/flow/component/datepicker/validation/BinderValidationTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@
2626
import org.mockito.Mockito;
2727
import org.mockito.MockitoAnnotations;
2828

29+
import com.vaadin.flow.component.Component;
2930
import com.vaadin.flow.component.datepicker.DatePicker;
3031
import com.vaadin.flow.data.binder.Binder;
3132
import com.vaadin.flow.data.binder.BindingValidationStatus;
3233
import com.vaadin.flow.data.binder.BindingValidationStatusHandler;
34+
import com.vaadin.flow.dom.DomEvent;
35+
import com.vaadin.flow.internal.JacksonUtils;
36+
import com.vaadin.flow.internal.nodefeature.ElementListenerMap;
3337

3438
public class BinderValidationTest {
3539

@@ -119,6 +123,52 @@ public void elementWithConstraints_validValue_validationPasses() {
119123

120124
}
121125

126+
@Test
127+
public void validValue_enterUnparsableInput_valueIsPreserved() {
128+
var binder = attachBinderToField();
129+
var bean = new Bean();
130+
var validDate = LocalDate.of(2025, 1, 15);
131+
bean.setDate(validDate);
132+
binder.setBean(bean);
133+
134+
// Simulate setting unparsable input
135+
fakeClientPropertyChange(field, "_inputElementValue", "foobar");
136+
fakeClientPropertyChange(field, "value", null);
137+
fakeClientDomEvent("change");
138+
139+
Assert.assertEquals(
140+
"Field value should be preserved after entering unparsable input",
141+
validDate, field.getValue());
142+
Assert.assertEquals(
143+
"Binder value should be preserved after entering unparsable input",
144+
validDate, bean.getDate());
145+
}
146+
147+
@Test
148+
public void validValue_enterUnparsableInput_clearInput_binderValueChangesToNull() {
149+
var binder = attachBinderToField();
150+
var bean = new Bean();
151+
var validDate = LocalDate.of(2025, 1, 15);
152+
bean.setDate(validDate);
153+
binder.setBean(bean);
154+
155+
// Simulate setting an invalid input
156+
fakeClientPropertyChange(field, "_inputElementValue", "foobar");
157+
fakeClientPropertyChange(field, "value", null);
158+
fakeClientDomEvent("change");
159+
160+
// Simulate setting clearing the invalid input
161+
fakeClientPropertyChange(field, "_inputElementValue", "");
162+
fakeClientDomEvent("unparsable-change");
163+
164+
Assert.assertNull(
165+
"Field value should be null after clearing unparsable input",
166+
field.getValue());
167+
Assert.assertNull(
168+
"Binder value should be null after clearing unparsable input",
169+
bean.getDate());
170+
}
171+
122172
private Binder<Bean> attachBinderToField() {
123173
return attachBinderToField(false);
124174
}
@@ -140,4 +190,18 @@ private Binder<Bean> attachBinderToField(boolean isRequired) {
140190

141191
return binder;
142192
}
193+
194+
private void fakeClientDomEvent(String eventName) {
195+
var element = field.getElement();
196+
var event = new DomEvent(element, eventName,
197+
JacksonUtils.createObjectNode());
198+
element.getNode().getFeature(ElementListenerMap.class).fireEvent(event);
199+
}
200+
201+
private void fakeClientPropertyChange(Component component, String property,
202+
String value) {
203+
var element = component.getElement();
204+
element.getStateProvider().setProperty(element.getNode(), property,
205+
value, false);
206+
}
143207
}

vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src/main/java/com/vaadin/flow/component/timepicker/TimePicker.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,21 @@ protected void setModelValue(LocalTime newModelValue, boolean fromClient) {
390390
unparsableValue = null;
391391
}
392392

393+
// Case: User enters unparsable input in a field with valid input
394+
if (fromClient && newModelValue == null && oldModelValue != null
395+
&& isInputUnparsable()) {
396+
validate();
397+
fireValidationStatusChangeEvent();
398+
return;
399+
}
400+
393401
boolean isModelValueRemainedEmpty = newModelValue == null
394402
&& oldModelValue == null;
395403

396404
// Cases:
397405
// - User modifies input but it remains unparsable
398406
// - User enters unparsable input in empty field
399-
// - User clears unparsable input
407+
// - User clears unparsable input (that was already empty)
400408
if (fromClient && isModelValueRemainedEmpty) {
401409
validate();
402410
fireValidationStatusChangeEvent();

vaadin-time-picker-flow-parent/vaadin-time-picker-flow/src/test/java/com/vaadin/flow/component/timepicker/tests/validation/BinderValidationTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,52 @@ public void elementWithConstraints_validValue_validationPasses() {
117117
Assert.assertFalse(statusCaptor.getValue().isError());
118118
}
119119

120+
@Test
121+
public void validValue_enterUnparsableInput_valueIsPreserved() {
122+
var binder = attachBinderToField();
123+
var bean = new Bean();
124+
var validTime = LocalTime.of(14, 30);
125+
bean.setTime(validTime);
126+
binder.setBean(bean);
127+
128+
// Simulate setting unparsable input
129+
fakeClientPropertyChange(field, "_inputElementValue", "foobar");
130+
fakeClientPropertyChange(field, "value", "");
131+
fakeClientDomEvent("change");
132+
133+
Assert.assertEquals(
134+
"Field value should be preserved after entering unparsable input",
135+
validTime, field.getValue());
136+
Assert.assertEquals(
137+
"Binder value should be preserved after entering unparsable input",
138+
validTime, bean.getTime());
139+
}
140+
141+
@Test
142+
public void validValue_enterUnparsableInput_clearInput_binderValueChangesToNull() {
143+
var binder = attachBinderToField();
144+
var bean = new Bean();
145+
var validTime = LocalTime.of(14, 30);
146+
bean.setTime(validTime);
147+
binder.setBean(bean);
148+
149+
// Simulate setting an invalid input
150+
fakeClientPropertyChange(field, "_inputElementValue", "foobar");
151+
fakeClientPropertyChange(field, "value", "");
152+
fakeClientDomEvent("change");
153+
154+
// Simulate clearing the invalid input
155+
fakeClientPropertyChange(field, "_inputElementValue", "");
156+
fakeClientDomEvent("unparsable-change");
157+
158+
Assert.assertNull(
159+
"Field value should be null after clearing unparsable input",
160+
field.getValue());
161+
Assert.assertNull(
162+
"Binder value should be null after clearing unparsable input",
163+
bean.getTime());
164+
}
165+
120166
private Binder<Bean> attachBinderToField() {
121167
return attachBinderToField(false);
122168
}
@@ -138,4 +184,21 @@ private Binder<Bean> attachBinderToField(boolean isRequired) {
138184

139185
return binder;
140186
}
187+
188+
private void fakeClientDomEvent(String eventName) {
189+
var element = field.getElement();
190+
var event = new com.vaadin.flow.dom.DomEvent(element, eventName,
191+
com.vaadin.flow.internal.JacksonUtils.createObjectNode());
192+
element.getNode().getFeature(
193+
com.vaadin.flow.internal.nodefeature.ElementListenerMap.class)
194+
.fireEvent(event);
195+
}
196+
197+
private void fakeClientPropertyChange(
198+
com.vaadin.flow.component.Component component, String property,
199+
String value) {
200+
var element = component.getElement();
201+
element.getStateProvider().setProperty(element.getNode(), property,
202+
value, false);
203+
}
141204
}

0 commit comments

Comments
 (0)