Skip to content
This repository has been archived by the owner on Apr 6, 2022. It is now read-only.

Commit

Permalink
Invalidate non-parseable date string
Browse files Browse the repository at this point in the history
- sync input value from client to server
- use input value in server-side validation
- validate on blur in addition to value change event

fix #223
  • Loading branch information
pekam committed May 7, 2020
1 parent 3db51cf commit b9be013
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.vaadin.flow.component.datepicker;

import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;

@Route("invalid-date-string")
public class InvalidDateStringView extends Div {

public InvalidDateStringView() {
DatePicker datePicker = new DatePicker();

Div value = new Div();
datePicker.addValueChangeListener(e -> value.setText(
e.getValue() == null ? "null" : e.getValue().toString()));
value.setId("value");

NativeButton checkValidity = new NativeButton("check validity",
e -> e.getSource()
.setText(datePicker.isInvalid() ? "invalid" : "valid"));
checkValidity.setId("check-validity");

add(datePicker, value, checkValidity);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,59 +15,52 @@
*/
package com.vaadin.flow.component.datepicker;

import com.vaadin.flow.component.datepicker.testbench.DatePickerElement;
import com.vaadin.flow.testutil.AbstractComponentIT;
import com.vaadin.flow.testutil.TestPath;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.time.LocalDate;
import java.util.Collections;

import com.vaadin.flow.component.datepicker.testbench.DatePickerElement;
import com.vaadin.flow.testutil.AbstractComponentIT;
import com.vaadin.flow.testutil.TestPath;

/**
* Integration tests for the {@link BinderValidationView}.
*/
@TestPath("binder-validation")
public class DatePickerBinderIT extends AbstractComponentIT {

private DatePickerElement field;

@Before
public void init() {
open();
field = $(DatePickerElement.class).waitForFirst();
}

@Test
public void selectDateOnSimpleDatePicker() {
final DatePickerElement element = $(DatePickerElement.class).waitForFirst();
Assert.assertFalse(element.getPropertyBoolean("invalid"));
public void initiallyValid() {
assertValid(field);
}

private void setInternalValidBinderInvalidValue(DatePickerElement field) {
field.setDate(LocalDate.of(2019, 1, 2));
field.dispatchEvent("change",
Collections.singletonMap("bubbles", true));
field.dispatchEvent("blur");
field.setInputValue("1/2/2019");
}

@Test
public void dateField_internalValidationPass_binderValidationFail_fieldInvalid() {
DatePickerElement field = $(DatePickerElement.class).first();
setInternalValidBinderInvalidValue(field);
assertInvalid(field);
assertInvalidatedByBinder(field);
}

@Test
public void dateField_internalValidationPass_binderValidationFail_validateClient_fieldInvalid() {
DatePickerElement field = $(DatePickerElement.class).first();

setInternalValidBinderInvalidValue(field);

field.getCommandExecutor().executeScript(
"arguments[0].validate(); arguments[0].immediateInvalid = arguments[0].invalid;",
field);

assertInvalid(field);
assertInvalidatedByBinder(field);
// State before server roundtrip (avoid flash of valid
// state)
Assert.assertTrue("Unexpected immediateInvalid state",
Expand All @@ -76,22 +69,29 @@ public void dateField_internalValidationPass_binderValidationFail_validateClient

@Test
public void dateField_internalValidationPass_binderValidationFail_setClientValid_serverFieldInvalid() {
DatePickerElement field = $(DatePickerElement.class).first();

setInternalValidBinderInvalidValue(field);

field.getCommandExecutor()
.executeScript("arguments[0].invalid = false", field);
field.getCommandExecutor().executeScript("arguments[0].invalid = false",
field);

Assert.assertEquals(field.getPropertyString("label"), "invalid");
}

private void assertInvalid(DatePickerElement field) {
Assert.assertTrue("Unexpected invalid state",
field.getPropertyBoolean("invalid"));
private void assertInvalidatedByBinder(DatePickerElement field) {
assertInvalid(field);
Assert.assertEquals(
"Expected to have error message configured in the Binder Validator",
BinderValidationView.BINDER_ERROR_MSG,
field.getPropertyString("errorMessage"));
}

private void assertInvalid(DatePickerElement field) {
Assert.assertTrue("Unexpected invalid state",
field.getPropertyBoolean("invalid"));
}

private void assertValid(DatePickerElement field) {
Assert.assertFalse("Unexpected invalid state",
field.getPropertyBoolean("invalid"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2000-2017 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.component.datepicker;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.flow.component.datepicker.testbench.DatePickerElement;
import com.vaadin.flow.testutil.AbstractComponentIT;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.testbench.TestBenchElement;

/**
* Integration tests for the {@link InvalidDateStringView}.
*/
@TestPath("invalid-date-string")
public class InvalidDateStringIT extends AbstractComponentIT {

private DatePickerElement datePicker;
private TestBenchElement checkValidity;
private TestBenchElement value;

@Before
public void init() {
open();
datePicker = $(DatePickerElement.class).first();
checkValidity = $(TestBenchElement.class).id("check-validity");
value = $(TestBenchElement.class).id("value");
}

@Test
public void setInvalidDateString_fieldInvalid() {
datePicker.setInputValue("asdf");
assertValid(false);
assertValue("");
}

@Test
public void setValidValue_setInvalidDateString_fieldInvalid() {
datePicker.setInputValue("1/1/2020");
assertValid(true);
assertValue("2020-01-01");
datePicker.setInputValue("asdf");
assertValid(false);
assertValue(null);
}

@Test
public void setInvalidDateString_clearField_fieldValid() {
datePicker.setInputValue("asdf");
datePicker.setInputValue("");
assertValid(true);
assertValue("");
}

@Test
public void setInvalidDateString_setValidValue_fieldValid() {
datePicker.setInputValue("asdf");
datePicker.setInputValue("1/1/2020");
assertValid(true);
assertValue("2020-01-01");
}

private void assertValid(boolean expectedValid) {
String expectedText = expectedValid ? "valid" : "invalid";
checkValidity.click();
Assert.assertEquals("Expected DatePicker to be " + expectedText,
expectedText, checkValidity.getText());
}

private void assertValue(String expectedValue) {
Assert.assertEquals("Unexpected DatePicker value",
expectedValue == null ? "null" : expectedValue,
value.getText());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ protected String getValue() {
public void setInputValue(String value) {
executeScript("arguments[0].open();", this);
setProperty("_inputValue", value);
executeScript("arguments[0].dispatchEvent(new Event('blur'));", this);
executeScript("arguments[0].close();", this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasValidation;
import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.Synchronize;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.function.SerializableConsumer;
Expand Down Expand Up @@ -88,6 +89,7 @@ public DatePicker(LocalDate initialDate) {
setInvalid(false);

addValueChangeListener(e -> validate());
addBlurListener(e -> validate());

FieldValidationUtil.disableClientValidation(this);
}
Expand Down Expand Up @@ -400,14 +402,17 @@ public boolean isInvalid() {
* because it is possible to circumvent the client side validation
* constraints using browser development tools.
*/
private boolean isInvalid(LocalDate value) {
private boolean isInvalid(LocalDate value, String inputValue) {
final boolean isRequiredButEmpty = required
&& Objects.equals(getEmptyValue(), value);
final boolean isGreaterThanMax = value != null && max != null
&& value.isAfter(max);
final boolean isSmallerThenMin = value != null && min != null
&& value.isBefore(min);
return isRequiredButEmpty || isGreaterThanMax || isSmallerThenMin;
final boolean hasNonParseableInputString = value == null
&& !inputValue.isEmpty();
return isRequiredButEmpty || isGreaterThanMax || isSmallerThenMin
|| hasNonParseableInputString;
}

/**
Expand Down Expand Up @@ -605,7 +610,7 @@ public String getName() {
* constraints using browser development tools.
*/
protected void validate() {
setInvalid(isInvalid(getValue()));
setInvalid(isInvalid(getValue(), getInputValue()));
}

@Override
Expand All @@ -620,6 +625,11 @@ public Registration addInvalidChangeListener(
return super.addInvalidChangeListener(listener);
}

@Synchronize(property = "_inputValue", value = "sync-input-value")
private String getInputValue() {
return getElement().getProperty("_inputValue", "");
}

/**
* The internationalization properties for {@link DatePicker}.
*/
Expand Down
Loading

0 comments on commit b9be013

Please sign in to comment.