diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BasicValidationPage.java b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BasicValidationPage.java index 1db6f4ad5f9..02467a91c70 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BasicValidationPage.java +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BasicValidationPage.java @@ -36,6 +36,12 @@ public BasicValidationPage() { } protected DatePicker createTestField() { - return new DatePicker(); + return new DatePicker() { + @Override + protected void validate() { + super.validate(); + incrementServerValidationCounter(); + } + }; } } diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BinderValidationPage.java b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BinderValidationPage.java index f11e95190ff..6c2d2429b99 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BinderValidationPage.java +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/main/java/com/vaadin/flow/component/datepicker/validation/BinderValidationPage.java @@ -13,6 +13,7 @@ public class BinderValidationPage extends AbstractValidationPage { public static final String MAX_INPUT = "max-input"; public static final String CLEAR_VALUE_BUTTON = "clear-value-button"; public static final String EXPECTED_VALUE_INPUT = "expected-value-input"; + public static final String RESET_BEAN_BUTTON = "reset-bean-button"; public static final String REQUIRED_ERROR_MESSAGE = "The field is required"; public static final String UNEXPECTED_VALUE_ERROR_MESSAGE = "The field doesn't match the expected value"; @@ -41,6 +42,9 @@ public BinderValidationPage() { .withValidator(value -> value.equals(expectedValue), UNEXPECTED_VALUE_ERROR_MESSAGE) .bind("property"); + binder.addStatusChangeListener(event -> { + incrementServerValidationCounter(); + }); add(createInput(EXPECTED_VALUE_INPUT, "Set expected date", event -> { LocalDate value = LocalDate.parse(event.getValue()); @@ -60,6 +64,10 @@ public BinderValidationPage() { add(createButton(CLEAR_VALUE_BUTTON, "Clear value", event -> { testField.clear(); })); + + add(createButton(RESET_BEAN_BUTTON, "Reset bean", event -> { + binder.setBean(new Bean()); + })); } protected DatePicker createTestField() { diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BasicValidationIT.java b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BasicValidationIT.java index e2f1024e46a..1259c871648 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BasicValidationIT.java +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BasicValidationIT.java @@ -23,6 +23,7 @@ public void fieldIsInitiallyValid() { @Test public void triggerBlur_assertValidity() { testField.sendKeys(Keys.TAB); + assertValidationCount(0); assertServerValid(); assertClientValid(); } @@ -32,8 +33,9 @@ public void required_triggerBlur_assertValidity() { $("button").id(REQUIRED_BUTTON).click(); testField.sendKeys(Keys.TAB); - assertServerInvalid(); - assertClientInvalid(); + assertValidationCount(0); + assertServerValid(); + assertClientValid(); } @Test @@ -41,10 +43,14 @@ public void required_changeValue_assertValidity() { $("button").id(REQUIRED_BUTTON).click(); testField.setInputValue("1/1/2022"); + assertValidationCount(1); assertServerValid(); assertClientValid(); + resetValidationCount(); + testField.setInputValue(""); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); } @@ -54,14 +60,28 @@ public void min_changeValue_assertValidity() { $("input").id(MIN_INPUT).sendKeys("2022-03-01", Keys.ENTER); testField.setInputValue("2/1/2022"); + assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); + resetValidationCount(); + testField.setInputValue("3/1/2022"); + assertValidationCount(1); assertClientValid(); assertServerValid(); + resetValidationCount(); + testField.setInputValue("4/1/2022"); + assertValidationCount(1); + assertClientValid(); + assertServerValid(); + + resetValidationCount(); + + testField.setInputValue(""); + assertValidationCount(1); assertClientValid(); assertServerValid(); } @@ -71,14 +91,28 @@ public void max_changeValue_assertValidity() { $("input").id(MAX_INPUT).sendKeys("2022-03-01", Keys.ENTER); testField.setInputValue("4/1/2022"); + assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); + resetValidationCount(); + testField.setInputValue("3/1/2022"); + assertValidationCount(1); assertClientValid(); assertServerValid(); + resetValidationCount(); + testField.setInputValue("2/1/2022"); + assertValidationCount(1); + assertClientValid(); + assertServerValid(); + + resetValidationCount(); + + testField.setInputValue(""); + assertValidationCount(1); assertClientValid(); assertServerValid(); } @@ -86,25 +120,43 @@ public void max_changeValue_assertValidity() { @Test public void badInput_changeValue_assertValidity() { testField.setInputValue("INVALID"); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); + resetValidationCount(); + testField.setInputValue("1/1/2022"); + assertValidationCount(1); assertServerValid(); assertClientValid(); + resetValidationCount(); + testField.setInputValue("INVALID"); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); + + resetValidationCount(); + + testField.setInputValue(""); + assertValidationCount(1); + assertClientValid(); + assertServerValid(); } @Test public void badInput_setValue_clearValue_assertValidity() { testField.setInputValue("INVALID"); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); + resetValidationCount(); + $("button").id(CLEAR_VALUE_BUTTON).click(); + assertValidationCount(1); assertServerValid(); assertClientValid(); } @@ -113,7 +165,8 @@ public void badInput_setValue_clearValue_assertValidity() { public void detach_attach_preservesInvalidState() { // Make field invalid $("button").id(REQUIRED_BUTTON).click(); - testField.sendKeys(Keys.TAB); + testField.setInputValue("1/1/2022"); + testField.setInputValue(""); detachAndReattachField(); @@ -134,7 +187,8 @@ public void webComponentCanNotModifyInvalidState() { public void clientSideInvalidStateIsNotPropagatedToServer() { // Make the field invalid $("button").id(REQUIRED_BUTTON).click(); - testField.sendKeys(Keys.TAB); + testField.setInputValue("1/1/2022"); + testField.setInputValue(""); executeScript("arguments[0].invalid = false", testField); diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BinderValidationIT.java b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BinderValidationIT.java index 0282b64973c..eef715cd4de 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BinderValidationIT.java +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/src/test/java/com/vaadin/flow/component/datepicker/validation/BinderValidationIT.java @@ -13,6 +13,7 @@ import static com.vaadin.flow.component.datepicker.validation.BinderValidationPage.REQUIRED_ERROR_MESSAGE; import static com.vaadin.flow.component.datepicker.validation.BinderValidationPage.UNEXPECTED_VALUE_ERROR_MESSAGE; import static com.vaadin.flow.component.datepicker.validation.BinderValidationPage.CLEAR_VALUE_BUTTON; +import static com.vaadin.flow.component.datepicker.validation.BinderValidationPage.RESET_BEAN_BUTTON; @TestPath("vaadin-date-picker/validation/binder") public class BinderValidationIT @@ -27,9 +28,9 @@ public void fieldIsInitiallyValid() { @Test public void required_triggerBlur_assertValidity() { testField.sendKeys(Keys.TAB); - assertServerInvalid(); - assertClientInvalid(); - assertErrorMessage(REQUIRED_ERROR_MESSAGE); + assertValidationCount(0); + assertServerValid(); + assertClientValid(); } @Test @@ -37,15 +38,32 @@ public void required_changeValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2022-01-01", Keys.ENTER); testField.setInputValue("1/1/2022"); + assertValidationCount(1); assertServerValid(); assertClientValid(); + resetValidationCount(); + testField.setInputValue(""); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); } + @Test + public void required_setValue_resetBean_assertValidity() { + $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2022-01-01", Keys.ENTER); + + testField.setInputValue("1/1/2022"); + assertServerValid(); + assertClientValid(); + + $("button").id(RESET_BEAN_BUTTON).click(); + assertServerValid(); + assertClientValid(); + } + @Test public void min_changeValue_assertValidity() { $("input").id(MIN_INPUT).sendKeys("2022-03-01", Keys.ENTER); @@ -53,20 +71,36 @@ public void min_changeValue_assertValidity() { // Constraint validation fails: testField.setInputValue("2/1/2022"); + assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(""); + resetValidationCount(); + // Binder validation fails: testField.setInputValue("3/1/2022"); + assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(UNEXPECTED_VALUE_ERROR_MESSAGE); + resetValidationCount(); + // Both validations pass: testField.setInputValue("4/1/2022"); + assertValidationCount(1); assertClientValid(); assertServerValid(); + + resetValidationCount(); + + // Binder validation fails: + testField.setInputValue(""); + assertValidationCount(1); + assertClientInvalid(); + assertServerInvalid(); + assertErrorMessage(REQUIRED_ERROR_MESSAGE); } @Test @@ -76,20 +110,36 @@ public void max_changeValue_assertValidity() { // Constraint validation fails: testField.setInputValue("4/1/2022"); + assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(""); + resetValidationCount(); + // Binder validation fails: testField.setInputValue("3/1/2022"); + assertValidationCount(1); assertClientInvalid(); assertServerInvalid(); assertErrorMessage(UNEXPECTED_VALUE_ERROR_MESSAGE); + resetValidationCount(); + // Both validations pass: testField.setInputValue("2/1/2022"); + assertValidationCount(1); assertClientValid(); assertServerValid(); + + resetValidationCount(); + + // Binder validation fails: + testField.setInputValue(""); + assertValidationCount(1); + assertClientInvalid(); + assertServerInvalid(); + assertErrorMessage(REQUIRED_ERROR_MESSAGE); } @Test @@ -97,28 +147,47 @@ public void badInput_changeValue_assertValidity() { $("input").id(EXPECTED_VALUE_INPUT).sendKeys("2022-01-01", Keys.ENTER); testField.setInputValue("INVALID"); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(""); + resetValidationCount(); + testField.setInputValue("1/1/2022"); + assertValidationCount(1); assertServerValid(); assertClientValid(); + resetValidationCount(); + testField.setInputValue("INVALID"); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(""); + + resetValidationCount(); + + testField.setInputValue(""); + assertValidationCount(1); + assertClientInvalid(); + assertServerInvalid(); + assertErrorMessage(REQUIRED_ERROR_MESSAGE); } @Test public void badInput_setValue_clearValue_assertValidity() { testField.setInputValue("INVALID"); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(""); + resetValidationCount(); + $("button").id(CLEAR_VALUE_BUTTON).click(); + assertValidationCount(1); assertServerInvalid(); assertClientInvalid(); assertErrorMessage(REQUIRED_ERROR_MESSAGE); diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/vite.config.ts b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/vite.config.ts index e702cd05814..970c8b70632 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/vite.config.ts +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow-integration-tests/vite.config.ts @@ -2,7 +2,7 @@ import { UserConfigFn } from 'vite'; // @ts-ignore can not be resolved until Flow generates base Vite config import { overrideVaadinConfig } from './vite.generated'; -// import { useLocalWebComponents } from '../../shared/web-components-vite-plugin'; +import { useLocalWebComponents } from '../../shared/web-components-vite-plugin'; const customConfig: UserConfigFn = (env) => ({ // Here you can add custom Vite parameters @@ -15,15 +15,11 @@ const customConfig: UserConfigFn = (env) => ({ }, // Use local version of web-components, disabled by default - // To use this un-comment the lines below and change the path to - // the absolute path of your web-components repo's node_modules + // To use this un-comment the lines below and change the path to + // the absolute path of your web-components repo's node_modules // folder // DO NOT COMMIT THESE CHANGES! - /* - plugins: [ - useLocalWebComponents('/path/to/web-components/node_modules') - ] - */ + plugins: [useLocalWebComponents('/Users/vursen/dev/vaadin/web-components/node_modules')] }); export default overrideVaadinConfig(customConfig); diff --git a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java index 054b7d5c99c..c6107897bc5 100644 --- a/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java +++ b/vaadin-date-picker-flow-parent/vaadin-date-picker-flow/src/main/java/com/vaadin/flow/component/datepicker/DatePicker.java @@ -19,6 +19,7 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -115,6 +116,8 @@ public class DatePicker private boolean manualValidationEnabled = false; + private final Collection> validationStatusChangeListeners = new ArrayList<>(); + /** * Default constructor. */ @@ -166,7 +169,10 @@ private DatePicker(LocalDate initialDate, boolean isInitialValueOptional) { addValueChangeListener(e -> validate()); - addClientValidatedEventListener(e -> validate()); + getElement().addEventListener("unparsable-change", event -> { + validate(); + fireValidationStatusChangeEvent(); + }); getElement().addPropertyChangeListener("opened", event -> fireEvent( new OpenedChangeEvent(this, event.isUserOriginated()))); @@ -518,10 +524,21 @@ public Validator getDefaultValidator() { @Override public Registration addValidationStatusChangeListener( ValidationStatusChangeListener listener) { - return addClientValidatedEventListener( - event -> listener.validationStatusChanged( - new ValidationStatusChangeEvent(this, - !isInvalid()))); + validationStatusChangeListeners.add(listener); + return () -> validationStatusChangeListeners.remove(listener); + } + + /** + * Notifies Binder that it needs to revalidate the component since the + * component's validity state may have changed. Note, there is no need to + * notify Binder separately in the case of a ValueChangeEvent, as Binder + * already listens to this event and revalidates automatically. + */ + private void fireValidationStatusChangeEvent() { + ValidationStatusChangeEvent event = new ValidationStatusChangeEvent<>( + this, !isInvalid()); + validationStatusChangeListeners + .forEach(listener -> listener.validationStatusChanged(event)); } private ValidationResult checkValidity(LocalDate value) { @@ -589,7 +606,8 @@ && isInputValuePresent()) { getElement() .executeJs("if (!this.value) this._inputElementValue = ''"); getElement().setProperty("_hasInputValue", false); - fireEvent(new ClientValidatedEvent(this, false)); + validate(); + fireValidationStatusChangeEvent(); } }