]> source.dussan.org Git - vaadin-framework.git/commitdiff
DateField value should actively adjust to the set resolution. (#12183) 7.7.23
authorAnna Koskinen <Ansku@users.noreply.github.com>
Fri, 22 Jan 2021 11:08:02 +0000 (13:08 +0200)
committerGitHub <noreply@github.com>
Fri, 22 Jan 2021 11:08:02 +0000 (13:08 +0200)
server/src/main/java/com/vaadin/ui/AbstractDateField.java
server/src/main/java/com/vaadin/ui/AbstractLocalDateField.java
server/src/main/java/com/vaadin/ui/AbstractLocalDateTimeField.java
server/src/test/java/com/vaadin/tests/data/converter/LocalDateTimeToDateConverterTest.java
server/src/test/java/com/vaadin/tests/server/component/abstractdatefield/AbstractLocalDateTimeFieldDeclarativeTest.java
server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java
uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldResolutionChange.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldResolutionChange.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldResolutionChangeTest.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldResolutionChangeTest.java [new file with mode: 0644]

index 84a26436c01226c89e222d3e4fbf758c3506b73d..dbbc19b673c8dd1e762f1531659342a43d2bf919 100644 (file)
@@ -38,9 +38,9 @@ import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import com.googlecode.gentyref.GenericTypeReflector;
 import org.jsoup.nodes.Element;
 
+import com.googlecode.gentyref.GenericTypeReflector;
 import com.vaadin.data.Result;
 import com.vaadin.data.ValidationResult;
 import com.vaadin.data.Validator;
@@ -179,7 +179,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
     };
 
     /**
-     * The default start year (inclusive) from which to calculate the 
+     * The default start year (inclusive) from which to calculate the
      * daylight-saving time zone transition dates.
      */
     private static final int DEFAULT_START_YEAR = 1980;
@@ -323,8 +323,9 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
      * <p>
      * Note: Negative, i.e. BC dates are not supported.
      * <p>
-     * Note: It's usually recommended to use only one of the following at the same
-     * time: Range validator with Binder or DateField's setRangeStart check.
+     * Note: It's usually recommended to use only one of the following at the
+     * same time: Range validator with Binder or DateField's setRangeStart
+     * check.
      *
      * @param startDate
      *            - the allowed range's start date
@@ -377,8 +378,11 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
      *            the resolution to set, not {@code null}
      */
     public void setResolution(R resolution) {
-        this.resolution = resolution;
-        updateResolutions();
+        if (!resolution.equals(this.resolution)) {
+            this.resolution = resolution;
+            setValue(adjustToResolution(getValue(), resolution));
+            updateResolutions();
+        }
     }
 
     /**
@@ -387,8 +391,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
      * validate. If {@code endDate} is set to {@code null}, any value after
      * {@code startDate} will be accepted by the range.
      * <p>
-     * Note: It's usually recommended to use only one of the following at the same
-     * time: Range validator with Binder or DateField's setRangeEnd check.
+     * Note: It's usually recommended to use only one of the following at the
+     * same time: Range validator with Binder or DateField's setRangeEnd check.
      *
      * @param endDate
      *            the allowed range's end date (inclusive, based on the current
@@ -545,8 +549,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
      * inclusive) between which to calculate the daylight-saving time zone
      * transition dates. Both parameters are used when '{@code z}' is included
      * inside the {@link #setDateFormat(String)}, they would have no effect
-     * otherwise. Specifically, these parameters determine the range of years in 
-     * which zone names are are adjusted to show the daylight saving names. 
+     * otherwise. Specifically, these parameters determine the range of years in
+     * which zone names are are adjusted to show the daylight saving names.
      *
      * If no values are provided, by default {@link startYear} is set to
      * {@value #DEFAULT_START_YEAR}, and {@link endYear} is set to
@@ -704,12 +708,13 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
      * @param value
      *            the new value, may be {@code null}
      * @throws IllegalArgumentException
-     *            if the value is not within range bounds
+     *             if the value is not within range bounds
      */
     @Override
     public void setValue(T value) {
+        T adjusted = adjustToResolution(value, getResolution());
         RangeValidator<T> validator = getRangeValidator();
-        ValidationResult result = validator.apply(value,
+        ValidationResult result = validator.apply(adjusted,
                 new ValueContext(this, this));
 
         if (result.isError()) {
@@ -718,25 +723,40 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
         } else {
             currentErrorMessage = null;
             /*
-             * First handle special case when the client side component has a date
-             * string but value is null (e.g. unparsable date string typed in by the
-             * user). No value changes should happen, but we need to do some
-             * internal housekeeping.
+             * First handle special case when the client side component has a
+             * date string but value is null (e.g. unparsable date string typed
+             * in by the user). No value changes should happen, but we need to
+             * do some internal housekeeping.
              */
-            if (value == null && !getState(false).parsable) {
+            if (adjusted == null && !getState(false).parsable) {
                 /*
-                 * Side-effects of doSetValue clears possible previous strings and
-                 * flags about invalid input.
+                 * Side-effects of doSetValue clears possible previous strings
+                 * and flags about invalid input.
                  */
                 doSetValue(null);
 
                 markAsDirty();
                 return;
             }
-            super.setValue(value);
+            super.setValue(adjusted);
         }
     }
 
+    /**
+     * Adjusts the given date to the given resolution. Any values that are more
+     * specific than the given resolution are truncated to their default values.
+     *
+     * @param date
+     *            the date to adjust, can be {@code null}
+     * @param resolution
+     *            the resolution to be used in the adjustment, can be
+     *            {@code null}
+     * @return an adjusted date that matches the given resolution, or
+     *         {@code null} if the given date, resolution, or both were
+     *         {@code null}
+     */
+    protected abstract T adjustToResolution(T date, R resolution);
+
     /**
      * Checks whether ISO 8601 week numbers are shown in the date selector.
      *
@@ -820,7 +840,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
                             .info("cannot parse " + design.attr("value")
                                     + " as date");
                 }
-                doSetValue(date);
+                doSetValue(adjustToResolution(date, getResolution()));
             } else {
                 throw new RuntimeException("Cannot detect resoluton type "
                         + Optional.ofNullable(dateType).map(Type::getTypeName)
index 9314f5d9b9ab19ad3fbab0c03bbed0e5c1673675..a2d4b0f3aa283a3672c8d56d0b375b11df7e260e 100644 (file)
@@ -103,8 +103,8 @@ public abstract class AbstractLocalDateField
     @Override
     protected RangeValidator<LocalDate> getRangeValidator() {
         return new DateRangeValidator(getDateOutOfRangeMessage(),
-                getDate(getRangeStart(), getResolution()),
-                getDate(getRangeEnd(), getResolution()));
+                adjustToResolution(getRangeStart(), getResolution()),
+                adjustToResolution(getRangeEnd(), getResolution()));
     }
 
     @Override
@@ -134,7 +134,9 @@ public abstract class AbstractLocalDateField
         return Date.from(date.atStartOfDay(ZoneOffset.UTC).toInstant());
     }
 
-    private LocalDate getDate(LocalDate date, DateResolution forResolution) {
+    @Override
+    protected LocalDate adjustToResolution(LocalDate date,
+            DateResolution forResolution) {
         if (date == null) {
             return null;
         }
@@ -171,19 +173,21 @@ public abstract class AbstractLocalDateField
     protected Result<LocalDate> handleUnparsableDateString(String dateString) {
         // Handle possible week number, which cannot be parsed client side due
         // limitations in GWT
-        if (this.getDateFormat() != null && this.getDateFormat().contains("w")) {
+        if (getDateFormat() != null && getDateFormat().contains("w")) {
             Date parsedDate;
-            SimpleDateFormat df = new SimpleDateFormat(this.getDateFormat(),this.getLocale());
+            SimpleDateFormat df = new SimpleDateFormat(getDateFormat(),
+                    getLocale());
             try {
                 parsedDate = df.parse(dateString);
             } catch (ParseException e) {
                 return super.handleUnparsableDateString(dateString);
             }
-            ZoneId zi = this.getZoneId();
-            if (zi ==  null) {
+            ZoneId zi = getZoneId();
+            if (zi == null) {
                 zi = ZoneId.systemDefault();
             }
-            LocalDate date = Instant.ofEpochMilli(parsedDate.getTime()).atZone(zi).toLocalDate();
+            LocalDate date = Instant.ofEpochMilli(parsedDate.getTime())
+                    .atZone(zi).toLocalDate();
             return Result.ok(date);
         } else {
             return super.handleUnparsableDateString(dateString);
index 6f7037d7328c47b656f807eb85be4551aa1b5f9f..39f16735a912baee55c741cdfbcf91a7c489e647 100644 (file)
@@ -111,8 +111,8 @@ public abstract class AbstractLocalDateTimeField
     @Override
     protected RangeValidator<LocalDateTime> getRangeValidator() {
         return new DateTimeRangeValidator(getDateOutOfRangeMessage(),
-                getDate(getRangeStart(), getResolution()),
-                getDate(getRangeEnd(), getResolution()));
+                adjustToResolution(getRangeStart(), getResolution()),
+                adjustToResolution(getRangeEnd(), getResolution()));
     }
 
     @Override
@@ -143,7 +143,8 @@ public abstract class AbstractLocalDateTimeField
         return Date.from(date.toInstant(ZoneOffset.UTC));
     }
 
-    private LocalDateTime getDate(LocalDateTime date,
+    @Override
+    protected LocalDateTime adjustToResolution(LocalDateTime date,
             DateTimeResolution forResolution) {
         if (date == null) {
             return null;
index 6851761cfc896829abe0898a85818a33c01b17af..a9dca56c15c0b71e9defb0c69b6721ad9cbbcfee 100644 (file)
@@ -14,6 +14,7 @@ import com.vaadin.data.Binder;
 import com.vaadin.data.ValidationException;
 import com.vaadin.data.ValueContext;
 import com.vaadin.data.converter.LocalDateTimeToDateConverter;
+import com.vaadin.shared.ui.datefield.DateTimeResolution;
 import com.vaadin.ui.DateTimeField;
 
 public class LocalDateTimeToDateConverterTest extends AbstractConverterTest {
@@ -59,7 +60,15 @@ public class LocalDateTimeToDateConverterTest extends AbstractConverterTest {
         BeanWithDate bean = new BeanWithDate();
         binder.writeBean(bean);
 
-        assertEquals(DATE, bean.getDate());
+        assertEquals(DateTimeResolution.MINUTE, dateField.getResolution());
+
+        // create a comparison date that matches the resolution
+        Calendar calendar = Calendar
+                .getInstance(TimeZone.getTimeZone(ZoneOffset.UTC));
+        calendar.clear();
+        calendar.set(2017, Calendar.JANUARY, 1, 1, 1, 0);
+        Date date = calendar.getTime();
+        assertEquals(date, bean.getDate());
     }
 
     public static class BeanWithDate {
index d4d2c397b721900c706e351182a4a1d8a4744cd9..de6dc322add71021c276fe619802c2d1fd597b83 100644 (file)
@@ -25,15 +25,19 @@ import com.vaadin.ui.AbstractLocalDateTimeField;
 public abstract class AbstractLocalDateTimeFieldDeclarativeTest<T extends AbstractLocalDateTimeField>
         extends AbstractFieldDeclarativeTest<T, LocalDateTime> {
 
-    protected DateTimeFormatter DATE_FORMATTER = DateTimeFormatter
-            .ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
+    // field initialised with DateTimeResolution.MINUTE, seconds get truncated
+    protected DateTimeFormatter VALUE_DATE_FORMATTER = DateTimeFormatter
+            .ofPattern("yyyy-MM-dd HH:mm:00", Locale.ROOT);
+    // only field value conforms to resolution, range keeps the initial values
+    protected DateTimeFormatter RANGE_DATE_FORMATTER = DateTimeFormatter
+            .ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
 
     @Override
     public void valueDeserialization()
             throws InstantiationException, IllegalAccessException {
         LocalDateTime value = LocalDateTime.of(2003, 02, 27, 10, 37, 43);
         String design = String.format("<%s value='%s'/>", getComponentTag(),
-                DATE_FORMATTER.format(value));
+                VALUE_DATE_FORMATTER.format(value));
 
         T component = getComponentClass().newInstance();
         component.setValue(value);
@@ -57,8 +61,8 @@ public abstract class AbstractLocalDateTimeFieldDeclarativeTest<T extends Abstra
                 "<%s show-iso-week-numbers range-end='%s' range-start='%s' "
                         + "date-out-of-range-message='%s' resolution='%s' "
                         + "date-format='%s' lenient parse-error-message='%s'/>",
-                getComponentTag(), DATE_FORMATTER.format(end),
-                DATE_FORMATTER.format(start), dateOutOfRange,
+                getComponentTag(), RANGE_DATE_FORMATTER.format(end),
+                RANGE_DATE_FORMATTER.format(start), dateOutOfRange,
                 resolution.name().toLowerCase(Locale.ROOT), dateFormat,
                 parseErrorMsg);
 
@@ -82,7 +86,7 @@ public abstract class AbstractLocalDateTimeFieldDeclarativeTest<T extends Abstra
             throws InstantiationException, IllegalAccessException {
         LocalDateTime value = LocalDateTime.of(2003, 02, 27, 23, 12, 34);
         String design = String.format("<%s value='%s' readonly/>",
-                getComponentTag(), DATE_FORMATTER.format(value));
+                getComponentTag(), VALUE_DATE_FORMATTER.format(value));
 
         T component = getComponentClass().newInstance();
         component.setValue(value);
index 91d7384421166fa63a56326499f14f8fead94fb7..da7306e0259174acaac2bc15f7747d23125dd8a9 100644 (file)
@@ -1,12 +1,14 @@
 package com.vaadin.tests.server.component.datefield;
 
 import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAccessor;
 import java.util.Date;
 import java.util.Map;
 
 import org.junit.Test;
 
+import com.vaadin.data.validator.DateTimeRangeValidator;
 import com.vaadin.data.validator.RangeValidator;
 import com.vaadin.event.FieldEvents.BlurEvent;
 import com.vaadin.event.FieldEvents.BlurListener;
@@ -39,7 +41,9 @@ public class DateFieldListenersTest extends AbstractListenerMethodsTestBase {
 
         @Override
         protected RangeValidator<LocalDateTime> getRangeValidator() {
-            return null;
+            return new DateTimeRangeValidator(getDateOutOfRangeMessage(),
+                    adjustToResolution(getRangeStart(), getResolution()),
+                    adjustToResolution(getRangeEnd(), getResolution()));
         }
 
         @Override
@@ -61,6 +65,32 @@ public class DateFieldListenersTest extends AbstractListenerMethodsTestBase {
         protected LocalDateTime toType(TemporalAccessor temporalAccessor) {
             return LocalDateTime.from(temporalAccessor);
         }
+
+        @Override
+        protected LocalDateTime adjustToResolution(LocalDateTime date,
+                DateTimeResolution forResolution) {
+            if (date == null) {
+                return null;
+            }
+            switch (forResolution) {
+            case YEAR:
+                return date.withDayOfYear(1).toLocalDate().atStartOfDay();
+            case MONTH:
+                return date.withDayOfMonth(1).toLocalDate().atStartOfDay();
+            case DAY:
+                return date.toLocalDate().atStartOfDay();
+            case HOUR:
+                return date.truncatedTo(ChronoUnit.HOURS);
+            case MINUTE:
+                return date.truncatedTo(ChronoUnit.MINUTES);
+            case SECOND:
+                return date.truncatedTo(ChronoUnit.SECONDS);
+            default:
+                assert false : "Unexpected resolution argument "
+                        + forResolution;
+                return null;
+            }
+        }
     }
 
     @Test
diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldResolutionChange.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldResolutionChange.java
new file mode 100644 (file)
index 0000000..a3635ea
--- /dev/null
@@ -0,0 +1,114 @@
+package com.vaadin.tests.components.datefield;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+import com.vaadin.data.Binder;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.datefield.DateResolution;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.DateField;
+import com.vaadin.ui.HorizontalLayout;
+
+public class DateFieldResolutionChange extends AbstractTestUIWithLog {
+
+    protected DateTimeFormatter DATE_FORMATTER = DateTimeFormatter
+            .ofPattern("yyyy-MM-dd", Locale.ROOT);
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        Binder<Pojo> binder = new Binder<>(Pojo.class);
+
+        HorizontalLayout horizontalLayout = new HorizontalLayout();
+
+        final DateField monthField = new DateField() {
+            @Override
+            public void setValue(LocalDate value) {
+                if (value != null) {
+                    log("MonthField set value " + DATE_FORMATTER.format(value));
+                }
+                super.setValue(value);
+            }
+        };
+        monthField.setResolution(DateResolution.MONTH);
+        monthField.setId("MonthField");
+        monthField.addValueChangeListener(
+                event -> log("MonthField value change event: "
+                        + DATE_FORMATTER.format(event.getValue())));
+        binder.bind(monthField, "value1");
+
+        final DateField dayField = new DateField() {
+            @Override
+            public void setValue(LocalDate value) {
+                if (value != null) {
+                    log("DayField set value " + DATE_FORMATTER.format(value));
+                }
+                super.setValue(value);
+            }
+        };
+        dayField.setResolution(DateResolution.DAY);
+        dayField.setId("DayField");
+        dayField.addValueChangeListener(
+                event -> log("DayField value change event: "
+                        + DATE_FORMATTER.format(event.getValue())));
+        binder.bind(dayField, "value2");
+
+        Pojo pojo = new Pojo();
+        binder.setBean(pojo);
+
+        Button monthButton = new Button("month", e -> {
+            monthField.setResolution(DateResolution.MONTH);
+            dayField.setResolution(DateResolution.MONTH);
+        });
+
+        Button dayButton = new Button("day", e -> {
+            monthField.setResolution(DateResolution.DAY);
+            dayField.setResolution(DateResolution.DAY);
+        });
+
+        Button logButton = new Button("log", e -> {
+            log("MonthField current value: "
+                    + DATE_FORMATTER.format(pojo.getValue1()));
+            log("DayField current value: "
+                    + DATE_FORMATTER.format(pojo.getValue2()));
+        });
+
+        Button setButton = new Button("set", e -> {
+            LocalDate newDate = LocalDate.of(2021, 2, 14);
+            pojo.setValue1(newDate);
+            pojo.setValue2(newDate);
+            binder.setBean(pojo);
+        });
+
+        horizontalLayout.addComponents(monthField, dayField, monthButton,
+                dayButton, logButton, setButton);
+        addComponent(horizontalLayout);
+    }
+
+    public class Pojo {
+        private LocalDate value1, value2 = null;
+
+        public LocalDate getValue1() {
+            return value1;
+        }
+
+        public void setValue1(LocalDate value1) {
+            this.value1 = value1;
+        }
+
+        public LocalDate getValue2() {
+            return value2;
+        }
+
+        public void setValue2(LocalDate value2) {
+            this.value2 = value2;
+        }
+    }
+
+    @Override
+    protected String getTestDescription() {
+        return "Date field value should immediately update to match resolution.";
+    }
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldResolutionChange.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateTimeFieldResolutionChange.java
new file mode 100644 (file)
index 0000000..c8066af
--- /dev/null
@@ -0,0 +1,114 @@
+package com.vaadin.tests.components.datefield;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+import com.vaadin.data.Binder;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.shared.ui.datefield.DateTimeResolution;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.DateTimeField;
+import com.vaadin.ui.HorizontalLayout;
+
+public class DateTimeFieldResolutionChange extends AbstractTestUIWithLog {
+
+    protected DateTimeFormatter DATE_FORMATTER = DateTimeFormatter
+            .ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        Binder<Pojo> binder = new Binder<>(Pojo.class);
+
+        HorizontalLayout horizontalLayout = new HorizontalLayout();
+
+        final DateTimeField monthField = new DateTimeField() {
+            @Override
+            public void setValue(LocalDateTime value) {
+                if (value != null) {
+                    log("MonthField set value " + DATE_FORMATTER.format(value));
+                }
+                super.setValue(value);
+            }
+        };
+        monthField.setResolution(DateTimeResolution.MONTH);
+        monthField.setId("MonthField");
+        monthField.addValueChangeListener(
+                event -> log("MonthField value change event: "
+                        + DATE_FORMATTER.format(event.getValue())));
+        binder.bind(monthField, "value1");
+
+        final DateTimeField dayField = new DateTimeField() {
+            @Override
+            public void setValue(LocalDateTime value) {
+                if (value != null) {
+                    log("DayField set value " + DATE_FORMATTER.format(value));
+                }
+                super.setValue(value);
+            }
+        };
+        dayField.setResolution(DateTimeResolution.DAY);
+        dayField.setId("DayField");
+        dayField.addValueChangeListener(
+                event -> log("DayField value change event: "
+                        + DATE_FORMATTER.format(event.getValue())));
+        binder.bind(dayField, "value2");
+
+        Pojo pojo = new Pojo();
+        binder.setBean(pojo);
+
+        Button monthButton = new Button("month", e -> {
+            monthField.setResolution(DateTimeResolution.MONTH);
+            dayField.setResolution(DateTimeResolution.MONTH);
+        });
+
+        Button dayButton = new Button("day", e -> {
+            monthField.setResolution(DateTimeResolution.DAY);
+            dayField.setResolution(DateTimeResolution.DAY);
+        });
+
+        Button logButton = new Button("log", e -> {
+            log("MonthField current value: "
+                    + DATE_FORMATTER.format(pojo.getValue1()));
+            log("DayField current value: "
+                    + DATE_FORMATTER.format(pojo.getValue2()));
+        });
+
+        Button setButton = new Button("set", e -> {
+            LocalDateTime newDate = LocalDateTime.of(2021, 2, 14, 16, 17);
+            pojo.setValue1(newDate);
+            pojo.setValue2(newDate);
+            binder.setBean(pojo);
+        });
+
+        horizontalLayout.addComponents(monthField, dayField, monthButton,
+                dayButton, logButton, setButton);
+        addComponent(horizontalLayout);
+    }
+
+    public class Pojo {
+        private LocalDateTime value1, value2 = null;
+
+        public LocalDateTime getValue1() {
+            return value1;
+        }
+
+        public void setValue1(LocalDateTime value1) {
+            this.value1 = value1;
+        }
+
+        public LocalDateTime getValue2() {
+            return value2;
+        }
+
+        public void setValue2(LocalDateTime value2) {
+            this.value2 = value2;
+        }
+    }
+
+    @Override
+    protected String getTestDescription() {
+        return "Date field value should immediately update to match resolution.";
+    }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldResolutionChangeTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldResolutionChangeTest.java
new file mode 100644 (file)
index 0000000..c2a6b8a
--- /dev/null
@@ -0,0 +1,50 @@
+package com.vaadin.tests.components.datefield;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class DateFieldResolutionChangeTest extends MultiBrowserTest {
+
+    @Test
+    public void testValueAndResolutionChange() {
+        openTestURL();
+
+        // set a fixed date
+        $(ButtonElement.class).caption("set").first().click();
+
+        // both fields should trigger a value change event but for MonthField
+        // the day value should be truncated to default
+        assertEquals("Unexpected log row", "1. MonthField set value 2021-02-14",
+                getLogRow(3));
+        assertEquals("Unexpected log row",
+                "2. MonthField value change event: 2021-02-01", getLogRow(2));
+        assertEquals("Unexpected log row", "3. DayField set value 2021-02-14",
+                getLogRow(1));
+        assertEquals("Unexpected log row",
+                "4. DayField value change event: 2021-02-14", getLogRow(0));
+
+        // change both to day resolution
+        $(ButtonElement.class).caption("day").first().click();
+
+        // DayField shouldn't react, MonthField should check that the value
+        // matches resolution but not trigger a ValueChangeEvent
+        assertEquals("Unexpected log row", "5. MonthField set value 2021-02-01",
+                getLogRow(0));
+
+        // change both to month resolution
+        $(ButtonElement.class).caption("month").first().click();
+
+        // both fields should check that the value matches resolution but only
+        // DayField should trigger a ValueChangeEvent
+        assertEquals("Unexpected log row", "6. MonthField set value 2021-02-01",
+                getLogRow(2));
+        assertEquals("Unexpected log row", "7. DayField set value 2021-02-01",
+                getLogRow(1));
+        assertEquals("Unexpected log row",
+                "8. DayField value change event: 2021-02-01", getLogRow(0));
+    }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldResolutionChangeTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateTimeFieldResolutionChangeTest.java
new file mode 100644 (file)
index 0000000..4fdd0a5
--- /dev/null
@@ -0,0 +1,53 @@
+package com.vaadin.tests.components.datefield;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.vaadin.testbench.elements.ButtonElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class DateTimeFieldResolutionChangeTest extends MultiBrowserTest {
+
+    @Test
+    public void testValueAndResolutionChange() {
+        openTestURL();
+
+        // set a fixed date
+        $(ButtonElement.class).caption("set").first().click();
+
+        // both fields should trigger a value change event but the value should
+        // be truncated according to each field's resolution
+        assertEquals("Unexpected log row",
+                "1. MonthField set value 2021-02-14 16:17:00", getLogRow(3));
+        assertEquals("Unexpected log row",
+                "2. MonthField value change event: 2021-02-01 00:00:00",
+                getLogRow(2));
+        assertEquals("Unexpected log row",
+                "3. DayField set value 2021-02-14 16:17:00", getLogRow(1));
+        assertEquals("Unexpected log row",
+                "4. DayField value change event: 2021-02-14 00:00:00",
+                getLogRow(0));
+
+        // change both to day resolution
+        $(ButtonElement.class).caption("day").first().click();
+
+        // DayField shouldn't react, MonthField should check that the value
+        // matches resolution but not trigger a ValueChangeEvent
+        assertEquals("Unexpected log row",
+                "5. MonthField set value 2021-02-01 00:00:00", getLogRow(0));
+
+        // change both to month resolution
+        $(ButtonElement.class).caption("month").first().click();
+
+        // both fields should check that the value matches resolution but only
+        // DayField should trigger a ValueChangeEvent
+        assertEquals("Unexpected log row",
+                "6. MonthField set value 2021-02-01 00:00:00", getLogRow(2));
+        assertEquals("Unexpected log row",
+                "7. DayField set value 2021-02-01 00:00:00", getLogRow(1));
+        assertEquals("Unexpected log row",
+                "8. DayField value change event: 2021-02-01 00:00:00",
+                getLogRow(0));
+    }
+}