Skip to content

Commit 985c10c

Browse files
authored
refactor: do not rely on _hasInputValue for detecting bad input (#6846)
1 parent f22ec00 commit 985c10c

File tree

5 files changed

+76
-72
lines changed

5 files changed

+76
-72
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ public void setValue(LocalDate value) {
759759
boolean isOldValueEmpty = valueEquals(oldValue, getEmptyValue());
760760
boolean isNewValueEmpty = valueEquals(value, getEmptyValue());
761761
boolean isValueRemainedEmpty = isOldValueEmpty && isNewValueEmpty;
762-
boolean isInputValuePresent = isInputValuePresent();
762+
String oldInputElementValue = getInputElementValue();
763763

764764
// When the value is cleared programmatically, there is no change event
765765
// that would synchronize _inputElementValue, so we reset it ourselves
@@ -772,7 +772,7 @@ public void setValue(LocalDate value) {
772772

773773
// Revalidate if setValue(null) didn't result in a value change but
774774
// cleared bad input
775-
if (isValueRemainedEmpty && isInputValuePresent) {
775+
if (isValueRemainedEmpty && !oldInputElementValue.isEmpty()) {
776776
validate();
777777
fireValidationStatusChangeEvent();
778778
}

vaadin-text-field-flow-parent/vaadin-text-field-flow/src/main/java/com/vaadin/flow/component/textfield/BigDecimalField.java

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.function.Function;
2626

2727
import com.vaadin.flow.component.AttachEvent;
28-
import com.vaadin.flow.component.Synchronize;
2928
import com.vaadin.flow.component.Tag;
3029
import com.vaadin.flow.component.UI;
3130
import com.vaadin.flow.component.dependency.JsModule;
@@ -123,7 +122,7 @@ public class BigDecimalField extends TextFieldBase<BigDecimalField, BigDecimal>
123122
boolean fromComponent = context == null;
124123

125124
boolean hasBadInput = valueEquals(value, getEmptyValue())
126-
&& isInputValuePresent();
125+
&& !getInputElementValue().isEmpty();
127126
if (hasBadInput) {
128127
return ValidationResult.error(getI18nErrorMessage(
129128
BigDecimalFieldI18n::getBadInputErrorMessage));
@@ -311,27 +310,16 @@ public void setValue(BigDecimal value) {
311310
boolean isOldValueEmpty = valueEquals(oldValue, getEmptyValue());
312311
boolean isNewValueEmpty = valueEquals(value, getEmptyValue());
313312
boolean isValueRemainedEmpty = isOldValueEmpty && isNewValueEmpty;
314-
boolean isInputValuePresent = isInputValuePresent();
315-
316-
// When the value is cleared programmatically, reset hasInputValue
317-
// so that the following validation doesn't treat this as bad input.
318-
if (isNewValueEmpty) {
319-
getElement().setProperty("_hasInputValue", false);
320-
}
313+
String oldInputElementValue = getInputElementValue();
321314

322315
super.setValue(value);
323316

324-
if (isValueRemainedEmpty && isInputValuePresent) {
325-
// Clear the input element from possible bad input.
326-
getElement().executeJs("this._inputElementValue = ''");
317+
// Revalidate and clear input element value if setValue(null) didn't
318+
// result in a value change but there was bad input.
319+
if (isValueRemainedEmpty && !oldInputElementValue.isEmpty()) {
320+
setInputElementValue("");
327321
validate();
328322
fireValidationStatusChangeEvent();
329-
} else {
330-
// Restore the input element's value in case it was cleared
331-
// in the above branch. That can happen when setValue(null)
332-
// and setValue(...) are subsequently called within one round-trip
333-
// and there was bad input.
334-
getElement().executeJs("this._inputElementValue = this.value");
335323
}
336324
}
337325

@@ -402,15 +390,12 @@ private void fireValidationStatusChangeEvent() {
402390
.forEach(listener -> listener.validationStatusChanged(event));
403391
}
404392

405-
/**
406-
* Returns whether the input element has a value or not.
407-
*
408-
* @return <code>true</code> if the input element's value is populated,
409-
* <code>false</code> otherwise
410-
*/
411-
@Synchronize(property = "_hasInputValue", value = "has-input-value-changed")
412-
private boolean isInputValuePresent() {
413-
return getElement().getProperty("_hasInputValue", false);
393+
private String getInputElementValue() {
394+
return getElement().getProperty("value", "");
395+
}
396+
397+
private void setInputElementValue(String value) {
398+
getElement().setProperty("value", value);
414399
}
415400

416401
/**

vaadin-text-field-flow-parent/vaadin-text-field-flow/src/test/java/com/vaadin/flow/component/textfield/validation/BigDecimalFieldBasicValidationTest.java

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
*/
1616
package com.vaadin.flow.component.textfield.validation;
1717

18-
import java.lang.reflect.Method;
1918
import java.math.BigDecimal;
2019

2120
import org.junit.Assert;
2221
import org.junit.Test;
2322

23+
import com.vaadin.flow.component.Component;
2424
import com.vaadin.flow.component.textfield.BigDecimalField;
25+
import com.vaadin.flow.dom.Element;
2526
import com.vaadin.tests.validation.AbstractBasicValidationTest;
2627

2728
public class BigDecimalFieldBasicValidationTest
@@ -34,23 +35,21 @@ public void addValidationStatusChangeListener_addAnotherListenerOnInvocation_noE
3435
});
3536

3637
// Trigger ValidationStatusChangeEvent
37-
simulateBadInput();
38+
fakeClientPropertyChange(testField, "value", "foo");
3839
testField.clear();
3940
}
4041

4142
@Test
4243
public void badInput_validate_emptyErrorMessageDisplayed() {
43-
testField.getElement().setProperty("_hasInputValue", true);
44-
simulateBadInput();
44+
fakeClientPropertyChange(testField, "value", "foo");
4545
Assert.assertEquals("", testField.getErrorMessage());
4646
}
4747

4848
@Test
4949
public void badInput_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() {
5050
testField.setI18n(new BigDecimalField.BigDecimalFieldI18n()
5151
.setBadInputErrorMessage("Value has invalid format"));
52-
testField.getElement().setProperty("_hasInputValue", true);
53-
simulateBadInput();
52+
fakeClientPropertyChange(testField, "value", "foo");
5453
Assert.assertEquals("Value has invalid format",
5554
testField.getErrorMessage());
5655
}
@@ -104,16 +103,10 @@ protected BigDecimalField createTestField() {
104103
return new BigDecimalField();
105104
}
106105

107-
private void simulateBadInput() {
108-
testField.getElement().setProperty("_hasInputValue", true);
109-
110-
try {
111-
Method validateMethod = BigDecimalField.class
112-
.getDeclaredMethod("validate");
113-
validateMethod.setAccessible(true);
114-
validateMethod.invoke(testField);
115-
} catch (Exception e) {
116-
throw new RuntimeException(e);
117-
}
106+
private void fakeClientPropertyChange(Component component, String property,
107+
String value) {
108+
Element element = component.getElement();
109+
element.getStateProvider().setProperty(element.getNode(), property,
110+
value, false);
118111
}
119112
}

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

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -366,27 +366,20 @@ public void setValue(LocalTime value) {
366366
boolean isOldValueEmpty = valueEquals(oldValue, getEmptyValue());
367367
boolean isNewValueEmpty = valueEquals(value, getEmptyValue());
368368
boolean isValueRemainedEmpty = isOldValueEmpty && isNewValueEmpty;
369-
boolean isInputValuePresent = isInputValuePresent();
369+
String oldInputElementValue = getInputElementValue();
370370

371-
// When the value is cleared programmatically, reset hasInputValue
372-
// so that the following validation doesn't treat this as bad input.
371+
// When the value is cleared programmatically, there is no change event
372+
// that would synchronize _inputElementValue, so we reset it ourselves
373+
// to prevent the following validation from treating this as bad input.
373374
if (isNewValueEmpty) {
374-
getElement().setProperty("_hasInputValue", false);
375+
setInputElementValue("");
375376
}
376377

377378
super.setValue(value);
378379

379-
// Clear the input element from possible bad input.
380-
if (isValueRemainedEmpty && isInputValuePresent) {
381-
// The check for value presence guarantees that a non-empty value
382-
// won't get cleared when setValue(null) and setValue(...) are
383-
// subsequently called within one round-trip.
384-
// Flow only sends the final component value to the client
385-
// when you update the value multiple times during a round-trip
386-
// and the final value is sent in place of the first one, so
387-
// `executeJs` can end up invoked after a non-empty value is set.
388-
getElement()
389-
.executeJs("if (!this.value) this._inputElementValue = ''");
380+
// Revalidate if setValue(null) didn't result in a value change but
381+
// cleared bad input
382+
if (isValueRemainedEmpty && !oldInputElementValue.isEmpty()) {
390383
validate();
391384
fireValidationStatusChangeEvent();
392385
}
@@ -453,9 +446,33 @@ private void fireValidationStatusChangeEvent() {
453446
* @return <code>true</code> if the input element's value is populated,
454447
* <code>false</code> otherwise
455448
*/
456-
@Synchronize(property = "_hasInputValue", value = "has-input-value-changed")
457449
protected boolean isInputValuePresent() {
458-
return getElement().getProperty("_hasInputValue", false);
450+
return !getInputElementValue().isEmpty();
451+
}
452+
453+
/**
454+
* Gets the value of the input element. This value is updated on the server
455+
* when the web component dispatches a `change` or `unparsable-change`
456+
* event. Except when clearing the value, {@link #setValue(LocalTime)} does
457+
* not update the input element value on the server because it requires date
458+
* formatting, which is implemented on the web component's side.
459+
*
460+
* @return the value of the input element
461+
*/
462+
@Synchronize(property = "_inputElementValue", value = { "change",
463+
"unparsable-change" })
464+
private String getInputElementValue() {
465+
return getElement().getProperty("_inputElementValue", "");
466+
}
467+
468+
/**
469+
* Sets the value of the input element.
470+
*
471+
* @param value
472+
* the value to set
473+
*/
474+
private void setInputElementValue(String value) {
475+
getElement().setProperty("_inputElementValue", value);
459476
}
460477

461478
/**

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import org.junit.Assert;
2121
import org.junit.Test;
2222

23+
import com.vaadin.flow.component.Component;
2324
import com.vaadin.flow.component.timepicker.TimePicker;
2425
import com.vaadin.flow.dom.DomEvent;
26+
import com.vaadin.flow.dom.Element;
2527
import com.vaadin.flow.internal.nodefeature.ElementListenerMap;
2628
import com.vaadin.tests.validation.AbstractBasicValidationTest;
2729

@@ -37,23 +39,24 @@ public void addValidationStatusChangeListener_addAnotherListenerOnInvocation_noE
3739
});
3840

3941
// Trigger ValidationStatusChangeEvent
40-
testField.getElement().setProperty("_hasInputValue", true);
42+
fakeClientPropertyChange(testField, "_inputElementValue", "foo");
4143
testField.clear();
4244
}
4345

4446
@Test
4547
public void badInput_validate_emptyErrorMessageDisplayed() {
4648
testField.getElement().setProperty("_hasInputValue", true);
47-
fireUnparsableChangeDomEvent();
49+
fakeClientPropertyChange(testField, "_inputElementValue", "foo");
50+
fakeClientDomEvent(testField, "unparsable-change");
4851
Assert.assertEquals("", testField.getErrorMessage());
4952
}
5053

5154
@Test
5255
public void badInput_setI18nErrorMessage_validate_i18nErrorMessageDisplayed() {
5356
testField.setI18n(new TimePicker.TimePickerI18n()
5457
.setBadInputErrorMessage("Time has invalid format"));
55-
testField.getElement().setProperty("_hasInputValue", true);
56-
fireUnparsableChangeDomEvent();
58+
fakeClientPropertyChange(testField, "_inputElementValue", "foo");
59+
fakeClientDomEvent(testField, "unparsable-change");
5760
Assert.assertEquals("Time has invalid format",
5861
testField.getErrorMessage());
5962
}
@@ -138,10 +141,16 @@ protected TimePicker createTestField() {
138141
return new TimePicker();
139142
}
140143

141-
private void fireUnparsableChangeDomEvent() {
142-
DomEvent unparsableChangeDomEvent = new DomEvent(testField.getElement(),
143-
"unparsable-change", Json.createObject());
144-
testField.getElement().getNode().getFeature(ElementListenerMap.class)
145-
.fireEvent(unparsableChangeDomEvent);
144+
private void fakeClientDomEvent(Component component, String eventName) {
145+
Element element = component.getElement();
146+
DomEvent event = new DomEvent(element, eventName, Json.createObject());
147+
element.getNode().getFeature(ElementListenerMap.class).fireEvent(event);
148+
}
149+
150+
private void fakeClientPropertyChange(Component component, String property,
151+
String value) {
152+
Element element = component.getElement();
153+
element.getStateProvider().setProperty(element.getNode(), property,
154+
value, false);
146155
}
147156
}

0 commit comments

Comments
 (0)