Commit 59a78993 authored by Daniel Clark's avatar Daniel Clark Committed by Commit Bot

Implement live-update and double-escape for date and week input popups

This change implements live-update and double-escape behavior for
date and week control popups.  When arrow keys are used to change the
selection, the changes are immediately reflected on the in-page control
(and input events are fired).  When the user hits escape, if they have
made changes these are reverted without closing the popup.  A second
escape (or the first one, if no changes were made) closes the popup.

Behavior when clicking on a date is not changed; the popup is still
submitted immediately (as opposed to other types like datetime-local
where the user may want to go on to select other values after clicking
the date).

Initialization of these controls is changed such that if there is any
valid date, one will be selected.  This will be the nearest possible
date to the value attribute if there is one, otherwise it will be the
nearest possible to today's date.  This avoids scenarios where we don't
paint a focus ring because there is no starting selected date.

I've moved the popup submission and cancellation behavior to the
top-level key event handler to better match the style of the other
refreshed controls.

One of my new tests uncovered a potential performance issue where
selectNearestValidRangeLookingForward(Backward) could spin for a long
time if the min/max attributes were very far from Today.  These functions
now immediately clamp to within the min/max range to avoid this.

Lastly, I'm suppressing a CSS background color animation that the
refreshed calendar pickers were erroneously inheriting from the old
calendar implementation.

A corresponding update will follow to bring live-update and double-escape
to the month control.

Bug: 1045195
Change-Id: I487bc2cbaefc3056d4ee50898bb6d96f0c9c6182
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2024158Reviewed-by: default avatarMason Freed <masonfreed@chromium.org>
Reviewed-by: default avatarIonel Popescu <iopopesc@microsoft.com>
Commit-Queue: Dan Clark <daniec@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#736532}
parent 91412942
...@@ -4085,24 +4085,30 @@ function CalendarPicker(type, config) { ...@@ -4085,24 +4085,30 @@ function CalendarPicker(type, config) {
*/ */
this._height = -1; this._height = -1;
this._hadValidValueWhenOpened = false;
var initialSelection = parseDateString(config.currentValue); var initialSelection = parseDateString(config.currentValue);
if (initialSelection) { if (initialSelection) {
this.setCurrentMonth( this.setCurrentMonth(
Month.createFromDay(initialSelection.middleDay()), Month.createFromDay(initialSelection.middleDay()),
CalendarPicker.NavigationBehavior.None); CalendarPicker.NavigationBehavior.None);
this.setSelection(initialSelection);
if (global.params.isFormControlsRefreshEnabled) {
this._hadValidValueWhenOpened = this.isValid(initialSelection);
this.selectNearestValidRange(initialSelection, /*lookForwardFirst*/ true);
} else {
this.setSelection(initialSelection);
}
} else { } else {
this.setCurrentMonth( this.setCurrentMonth(
Month.createFromToday(), CalendarPicker.NavigationBehavior.None); Month.createFromToday(), CalendarPicker.NavigationBehavior.None);
if (global.params.isFormControlsRefreshEnabled &&
this.type == 'datetime-local') { if (global.params.isFormControlsRefreshEnabled) {
// When used with datetime-local, ensure that today's date (if valid) is selected // Ensure that the next date closest to today is selected to start with so that
// to start with so that if the user only edits the time, they can still submit // the user can simply submit the popup to choose it.
// the popup without also needing to edit the calendar view. this.selectValidRangeNearestToDay(
var today = Day.createFromToday(); this._dateTypeConstructor.createFromToday(),
if (this.isValid(today)) { /*lookForwardFirst*/ true);
this.setSelection(Day.createFromToday());
}
} }
} }
...@@ -4337,6 +4343,13 @@ CalendarPicker.prototype.highlightRangeContainingDay = function(day) { ...@@ -4337,6 +4343,13 @@ CalendarPicker.prototype.highlightRangeContainingDay = function(day) {
*/ */
CalendarPicker.prototype.selectNearestValidRangeLookingForward = function( CalendarPicker.prototype.selectNearestValidRangeLookingForward = function(
dayOrWeekOrMonth) { dayOrWeekOrMonth) {
if (dayOrWeekOrMonth < this.config.minimumValue) {
// Performance optimization: avoid wasting lots of time in the below
// loop if dayOrWeekOrMonth is significantly less than the min.
dayOrWeekOrMonth =
this._dateTypeConstructor.createFromValue(this.config.minimumValue);
}
while (!this.isValid(dayOrWeekOrMonth) && while (!this.isValid(dayOrWeekOrMonth) &&
dayOrWeekOrMonth < this.config.maximumValue) { dayOrWeekOrMonth < this.config.maximumValue) {
dayOrWeekOrMonth = dayOrWeekOrMonth.next(); dayOrWeekOrMonth = dayOrWeekOrMonth.next();
...@@ -4356,6 +4369,13 @@ CalendarPicker.prototype.selectNearestValidRangeLookingForward = function( ...@@ -4356,6 +4369,13 @@ CalendarPicker.prototype.selectNearestValidRangeLookingForward = function(
*/ */
CalendarPicker.prototype.selectNearestValidRangeLookingBackward = function( CalendarPicker.prototype.selectNearestValidRangeLookingBackward = function(
dayOrWeekOrMonth) { dayOrWeekOrMonth) {
if (dayOrWeekOrMonth > this.config.maximumValue) {
// Performance optimization: avoid wasting lots of time in the below
// loop if dayOrWeekOrMonth is significantly greater than the max.
dayOrWeekOrMonth =
this._dateTypeConstructor.createFromValue(this.config.maximumValue);
}
while (!this.isValid(dayOrWeekOrMonth) && while (!this.isValid(dayOrWeekOrMonth) &&
dayOrWeekOrMonth > this.config.minimumValue) { dayOrWeekOrMonth > this.config.minimumValue) {
dayOrWeekOrMonth = dayOrWeekOrMonth.previous(); dayOrWeekOrMonth = dayOrWeekOrMonth.previous();
...@@ -4373,10 +4393,8 @@ CalendarPicker.prototype.selectNearestValidRangeLookingBackward = function( ...@@ -4373,10 +4393,8 @@ CalendarPicker.prototype.selectNearestValidRangeLookingBackward = function(
* @param {!DayOrWeekOrMonth} dayOrWeekOrMonth * @param {!DayOrWeekOrMonth} dayOrWeekOrMonth
* @param {!boolean} lookForwardFirst * @param {!boolean} lookForwardFirst
*/ */
CalendarPicker.prototype.selectValidRangeNearestToDay = function( CalendarPicker.prototype.selectNearestValidRange = function(
day, lookForwardFirst) { dayOrWeekOrMonth, lookForwardFirst) {
var dayOrWeekOrMonth = this._dateTypeConstructor.createFromDay(day);
if (lookForwardFirst) { if (lookForwardFirst) {
if (!this.selectNearestValidRangeLookingForward(dayOrWeekOrMonth)) { if (!this.selectNearestValidRangeLookingForward(dayOrWeekOrMonth)) {
this.selectNearestValidRangeLookingBackward(dayOrWeekOrMonth); this.selectNearestValidRangeLookingBackward(dayOrWeekOrMonth);
...@@ -4388,6 +4406,16 @@ CalendarPicker.prototype.selectValidRangeNearestToDay = function( ...@@ -4388,6 +4406,16 @@ CalendarPicker.prototype.selectValidRangeNearestToDay = function(
} }
}; };
/**
* @param {!Day} day
* @param {!boolean} lookForwardFirst
*/
CalendarPicker.prototype.selectValidRangeNearestToDay = function(
day, lookForwardFirst) {
var dayOrWeekOrMonth = this._dateTypeConstructor.createFromDay(day);
this.selectNearestValidRange(dayOrWeekOrMonth, lookForwardFirst);
};
/** /**
* Select the specified date. * Select the specified date.
* @param {?DateType} dayOrWeekOrMonth * @param {?DateType} dayOrWeekOrMonth
...@@ -4553,7 +4581,8 @@ CalendarPicker.prototype.onCalendarTableKeyDownRefresh = function(event) { ...@@ -4553,7 +4581,8 @@ CalendarPicker.prototype.onCalendarTableKeyDownRefresh = function(event) {
if (!event.target.matches('.today-button-refresh')) { if (!event.target.matches('.today-button-refresh')) {
if (key == 't') { if (key == 't') {
this.selectRangeContainingDay(Day.createFromToday()); this.selectValidRangeNearestToDay(
Day.createFromToday(), /*lookForwardFirst*/ true);
} else if (key == 'PageUp') { } else if (key == 'PageUp') {
var previousMonth = this.currentMonth().previous(); var previousMonth = this.currentMonth().previous();
if (previousMonth && previousMonth >= this.config.minimumValue) { if (previousMonth && previousMonth >= this.config.minimumValue) {
...@@ -4583,18 +4612,11 @@ CalendarPicker.prototype.onCalendarTableKeyDownRefresh = function(event) { ...@@ -4583,18 +4612,11 @@ CalendarPicker.prototype.onCalendarTableKeyDownRefresh = function(event) {
} else if (key == 'ArrowDown') { } else if (key == 'ArrowDown') {
this.selectNearestValidRangeLookingForward( this.selectNearestValidRangeLookingForward(
this._selection.next(upOrDownArrowStepSize)); this._selection.next(upOrDownArrowStepSize));
} else if (key == 'Enter') {
this.setSelectionAndCommit(this._highlight);
} }
} else if (
key == 'ArrowLeft' || key == 'ArrowUp' || key == 'ArrowRight' ||
key == 'ArrowDown') {
// Select range near the middle. Try looking for a valid range first in the direction that
// the user has indicated.
var lookForwardFirst = (key == 'ArrowRight' || key == 'ArrowDown');
this.selectValidRangeNearestToDay(
this.currentMonth().middleDay(), lookForwardFirst);
} }
// else if there is no selection it must be the case that there are no
// valid values (because min >= max). Otherwise we would have set the selection
// during initialization. In this case there's nothing to do.
} }
}; };
...@@ -4696,13 +4718,62 @@ CalendarPicker.prototype.onBodyKeyDown = function(event) { ...@@ -4696,13 +4718,62 @@ CalendarPicker.prototype.onBodyKeyDown = function(event) {
case 'Escape': case 'Escape':
// The datetime-local control handles submission/cancellation at // The datetime-local control handles submission/cancellation at
// the top level, so if we're in a datetime-local let event bubble // the top level, so if we're in a datetime-local let event bubble
// up instead of handling it here and closing the popup. // up instead of handling it here.
if (!(global.params.isFormControlsRefreshEnabled && if (global.params.isFormControlsRefreshEnabled) {
this.type == 'datetime-local')) { if (this.type !== 'datetime-local') {
if (!this._selection ||
(this._selection.equals(this._initialSelection))) {
window.pagePopupController.closePopup();
} else {
this.resetToInitialValue();
window.pagePopupController.setValue(
this._hadValidValueWhenOpened ?
this._initialSelection.toString() :
'');
}
}
} else {
window.pagePopupController.closePopup(); window.pagePopupController.closePopup();
eventHandled = true; eventHandled = true;
} }
break; break;
case 'ArrowUp':
case 'ArrowDown':
case 'ArrowLeft':
case 'ArrowRight':
if (global.params.isFormControlsRefreshEnabled &&
this.type !== 'datetime-local' &&
event.target.matches('.calendar-table-view') && this._selection) {
window.pagePopupController.setValue(this.getSelectedValue());
}
break;
case 't':
if (global.params.isFormControlsRefreshEnabled &&
event.target.matches('.calendar-table-view') &&
this.type !== 'datetime-local' && this._selection) {
window.pagePopupController.setValueAndClosePopup(
0, this.getSelectedValue());
}
break;
case 'Enter':
// Submit the popup for an Enter keypress except when the user is
// hitting Enter to activate the month switcher button, Today button,
// or previous/next month arrows.
if (global.params.isFormControlsRefreshEnabled &&
this.type !== 'datetime-local' &&
!event.target.matches(
'.calendar-navigation-button, .month-popup-button')) {
if (this._selection) {
window.pagePopupController.setValueAndClosePopup(
0, this.getSelectedValue());
} else {
// If there is no selection it must be the case that there are no
// valid values (because min >= max). There's nothing useful to do
// with the popup in this case so just close on Enter.
window.pagePopupController.closePopup();
}
}
break;
case 'm': case 'm':
case 'M': case 'M':
offset = offset || 1; // Fall-through. offset = offset || 1; // Fall-through.
......
...@@ -100,6 +100,11 @@ body { ...@@ -100,6 +100,11 @@ body {
color: #101010; color: #101010;
} }
.week-number-cell,
.day-cell {
transition: color 0s;
}
.day-cell.highlighted, .day-cell.highlighted,
.month-button.highlighted, .month-button.highlighted,
.week-number-cell.highlighted { .week-number-cell.highlighted {
......
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../fast/forms/resources/common.js"></script>
<script src="../../../fast/forms/resources/picker-common.js"></script>
<script src="../../../fast/forms/calendar-picker/resources/calendar-picker-common.js"></script>
</head>
<body>
<input type="date" id="date0" value="2019-02-14">
<script>
promise_test(() => {
let dateElement = document.getElementById("date0");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "First escape should close popup if there were no changes.");
assert_equals(dateElement.value, "2019-02-14", "Control should remain at original value.");
});
}, "Date picker: Single escape should close popup if there were no modifications.");
promise_test(() => {
let dateElement = document.getElementById("date0");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('ArrowRight');
eventSender.keyDown('ArrowDown');
assert_equals(dateElement.value, "2019-02-22", "Using arrow keys to navigate date picker should update in-page control.");
eventSender.keyDown('Escape');
assert_not_equals(internals.pagePopupWindow, null, "First escape keypress should not close popup.");
assert_equals(dateElement.value, "2019-02-14", "First escape keypress should reset control back to original value.");
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "Second escape keypress should close popup.");
assert_equals(dateElement.value, "2019-02-14", "Control should remain at original value.");
});
}, "Date picker: After modification, first escape should reset value and second escape should close picker.");
promise_test(() => {
let dateElement = document.getElementById("date0");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('ArrowRight');
eventSender.keyDown('ArrowDown');
assert_equals(dateElement.value, "2019-02-22", "Using arrow keys to navigate date picker should update in-page control.");
eventSender.keyDown('ArrowUp');
eventSender.keyDown('ArrowLeft');
assert_equals(dateElement.value, "2019-02-14", "Expected to be back at the original value.");
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "Escape keypress should close popup.");
assert_equals(dateElement.value, "2019-02-14", "Control should remain at original value.");
});
}, "Date picker: After modification, single escape should close popup if it was changed back to the initial value.");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../fast/forms/resources/common.js"></script>
<script src="../../../fast/forms/resources/picker-common.js"></script>
<script src="../../../fast/forms/calendar-picker/resources/calendar-picker-common.js"></script>
</head>
<body>
<input type="date" id="date" value="2020-01-13" oninput="inputEventCount++;" onchange="changeEventCount++">
<script>
'use strict';
var inputEventCount = 0;
var changeEventCount = 0;
let dateElement = document.getElementById("date");
promise_test(() => {
assert_equals(internals.pagePopupWindow, null);
return openPickerWithPromise(dateElement).then(() => {
assert_equals(dateElement.value, "2020-01-13");
assert_not_equals(internals.pagePopupWindow, null);
assert_equals(inputEventCount, 0, "No input event should have not have fired before making changes.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('ArrowRight');
eventSender.keyDown('ArrowDown');
assert_equals(dateElement.value, "2020-01-21");
assert_equals(inputEventCount, 2, "One input event should have fired for each modification.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Popup should have closed from Enter key");
assert_equals(inputEventCount, 2, 'No extra input event should fire when closing popup.');
assert_equals(changeEventCount, 0, 'Change event is fired asynchronously after closing popup.');
return new Promise((resolve) => {
window.setTimeout(() => {
assert_equals(changeEventCount, 1, 'Change event should have fired (once) asynchronously after closing popup.');
resolve();
}, 0);
});
});
}, "Date picker: Test input and change event firing when popup value is modified");
promise_test(() => {
inputEventCount = changeEventCount = 0;
assert_equals(internals.pagePopupWindow, null);
return openPickerWithPromise(dateElement).then(() => {
assert_equals(dateElement.value, "2020-01-21");
assert_not_equals(internals.pagePopupWindow, null);
assert_equals(inputEventCount, 0, "No input event should have not have fired before making changes.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('ArrowRight');
assert_equals(dateElement.value, "2020-01-22");
assert_equals(inputEventCount, 1, "One input event should have fired for the modification.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('Escape');
assert_equals(dateElement.value, "2020-01-21");
assert_not_equals(internals.pagePopupWindow, null, "Popup should not have closed from single escape");
assert_equals(inputEventCount, 2, "One input event should have fired when restoring the initial value.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "Popup should have closed from Enter key");
assert_equals(inputEventCount, 2, 'No extra input event should fire when closing popup.');
return new Promise((resolve) => {
window.setTimeout(() => {
assert_equals(changeEventCount, 0, 'No change event should fire if popup was submitted with value still matching the original.');
resolve();
}, 0);
});
});
}, "Date picker: Test that change does not fire if popup value is modified and then restored to original value.");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../fast/forms/resources/common.js"></script>
<script src="../../../fast/forms/resources/picker-common.js"></script>
<script src="../../../fast/forms/calendar-picker/resources/calendar-picker-common.js"></script>
</head>
<body>
<input type="date" id="date0" min="5000-02-14">
<input type="date" id="date1" max="2019-02-21">
<input type="date" id="date2">
<input type="date" id="date3" min="2019-03-10" step="3" max="2019-04-10" value="2019-03-20">
<script>
promise_test(() => {
let dateElement = document.getElementById("date0");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
// Note: test will need to be updated in the year 5000 :)
assert_equals(dateElement.value, "5000-02-14", "Date closest to present should be selected if user hits enter without other input");
});
}, "Date picker: Picker should default to the day closest to min when it comes after the current date");
promise_test(() => {
let dateElement = document.getElementById("date1");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-02-21", "Date closest to present should be selected if user hits enter without other input");
});
}, "Date picker: Picker should default to the day closest to max when it comes before the current date");
promise_test(() => {
let dateElement = document.getElementById("date2");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
let splitDate = dateElement.value.split('-');
let actualDateString = new Date(splitDate[0], splitDate[1] - 1, [splitDate[2]]).toDateString();
let expectedDateString = new Date().toDateString();
assert_equals(actualDateString, expectedDateString, "Hitting Enter key when no value is selected should select current date");
});
}, "Date picker: Picker should select current date when starting with no selection");
promise_test(() => {
let dateElement = document.getElementById("date3");
return openPickerWithPromise(dateElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-03-22", "Valid date closest to starting value should be selected if user hits enter without other input");
});
}, "Date picker: If the input's initial value is invalid due to step attribute, should select the nearest valid date");
</script>
</body>
</html>
...@@ -13,38 +13,31 @@ ...@@ -13,38 +13,31 @@
<input type="date" id="date2" value="2019-02-14" step="30"> <input type="date" id="date2" value="2019-02-14" step="30">
<input type="date" id="date3" value="2019-02-14" min="2019-02-13"> <input type="date" id="date3" value="2019-02-14" min="2019-02-13">
<input type="date" id="date4" value="2019-02-14" max="2019-02-21"> <input type="date" id="date4" value="2019-02-14" max="2019-02-21">
<input type="date" id="date5">
<script> <script>
promise_test(() => { promise_test(() => {
let dateElement = document.getElementById("date0"); let dateElement = document.getElementById("date0");
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowRight'); eventSender.keyDown('ArrowRight');
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
assert_equals(dateElement.value, "2019-02-14", "Using arrow keys to navigate date picker should not update in-page control without pressing Enter key."); assert_equals(dateElement.value, "2019-02-22", "Using arrow keys to navigate date picker should update in-page control without pressing Enter key.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-02-22", "Enter key should submit newly selected date."); assert_equals(dateElement.value, "2019-02-22", "Updated date should remain after closing popup with Enter.");
}); });
}, "Date picker: Arrow keys should allow user to chooser month"); }, "Date picker: Arrow keys should allow user to chooser date");
promise_test(() => { promise_test(() => {
let dateElement = document.getElementById("date1"); let dateElement = document.getElementById("date1");
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowRight'); eventSender.keyDown('ArrowRight');
assert_equals(dateElement.value, "2019-02-16", "Right arrow key should skip over invalid date.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-02-16", "Right arrow key should skip over invalid date.");
}); });
}, "Date picker: Picker with step should allow keyboard user to jump over invalid values -- right arrow"); }, "Date picker: Picker with step should allow keyboard user to jump over invalid values -- right arrow");
...@@ -52,14 +45,11 @@ promise_test(() => { ...@@ -52,14 +45,11 @@ promise_test(() => {
let dateElement = document.getElementById("date1"); let dateElement = document.getElementById("date1");
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowUp'); eventSender.keyDown('ArrowUp');
assert_equals(dateElement.value, "2019-02-08", "Up arrow key should skip over invalid date.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-02-08", "Up arrow key should skip over invalid date.");
}); });
}, "Date picker: Picker with step should allow keyboard user to jump over invalid values -- up arrow"); }, "Date picker: Picker with step should allow keyboard user to jump over invalid values -- up arrow");
...@@ -67,14 +57,11 @@ promise_test(() => { ...@@ -67,14 +57,11 @@ promise_test(() => {
let dateElement = document.getElementById("date2"); let dateElement = document.getElementById("date2");
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
assert_equals(dateElement.value, "2019-03-16", "Arrow key should skip to next valid date even if it's in a different month.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-03-16", "Arrow key should skip to next valid date even if it's in a different month.");
}); });
}, "Date picker: Picker with step should allow keyboard user to skip to next month with valid date"); }, "Date picker: Picker with step should allow keyboard user to skip to next month with valid date");
...@@ -82,16 +69,13 @@ promise_test(() => { ...@@ -82,16 +69,13 @@ promise_test(() => {
let dateElement = document.getElementById("date3"); let dateElement = document.getElementById("date3");
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowLeft'); eventSender.keyDown('ArrowLeft');
eventSender.keyDown('ArrowLeft'); eventSender.keyDown('ArrowLeft');
eventSender.keyDown('ArrowUp'); eventSender.keyDown('ArrowUp');
assert_equals(dateElement.value, "2019-02-13", "Min attribute should prevent user from arrowing into invalid values");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-02-13", "Min attribute should prevent user from arrowing into invalid values");
}); });
}, "Date picker: Picker with min value should prevent user from using arrow keys to get into invalid values"); }, "Date picker: Picker with min value should prevent user from using arrow keys to get into invalid values");
...@@ -99,40 +83,15 @@ promise_test(() => { ...@@ -99,40 +83,15 @@ promise_test(() => {
let dateElement = document.getElementById("date4"); let dateElement = document.getElementById("date4");
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
eventSender.keyDown('ArrowRight'); eventSender.keyDown('ArrowRight');
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(dateElement.value, "2019-02-21", "Max attribute should prevent user from arrowing into invalid values"); assert_equals(dateElement.value, "2019-02-21", "Max attribute should prevent user from arrowing into invalid values");
});
}, "Date picker: Picker with max value should prevent user from using arrow keys to get into invalid values");
promise_test(() => {
let dateElement = document.getElementById("date5");
return openPickerWithPromise(dateElement)
.then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowRight');
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
let splitDate = dateElement.value.split('-');
let actualDateString = new Date(splitDate[0], splitDate[1] - 1, [splitDate[2]]).toDateString();
let expectedDate = new Date();
expectedDate.setDate(expectedDate.getMonth() === 1 ? 14 : 15);
let expectedDateString = expectedDate.toDateString();
assert_equals(actualDateString, expectedDateString, "Hitting arrow key when no value is selected should select day in middle of current month");
}); });
}, "Date picker: Picker should select middle day of current month when starting with no selection"); }, "Date picker: Picker with max value should prevent user from using arrow keys to get into invalid values");
</script> </script>
</body> </body>
......
...@@ -15,10 +15,6 @@ promise_test(() => { ...@@ -15,10 +15,6 @@ promise_test(() => {
let dateElement = document.getElementById('date'); let dateElement = document.getElementById('date');
return openPickerWithPromise(dateElement) return openPickerWithPromise(dateElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('t'); eventSender.keyDown('t');
let splitDate = dateElement.value.split('-'); let splitDate = dateElement.value.split('-');
......
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../fast/forms/resources/common.js"></script>
<script src="../../../fast/forms/resources/picker-common.js"></script>
<script src="../../../fast/forms/calendar-picker/resources/calendar-picker-common.js"></script>
</head>
<body>
<input type="week" id="week0" value="2019-W11">
<script>
promise_test(() => {
let weekElement = document.getElementById("week0");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "First escape should close popup if there were no changes.");
assert_equals(weekElement.value, "2019-W11", "Control should remain at original value.");
});
}, "Week picker: Single escape should close popup if there were no modifications.");
promise_test(() => {
let weekElement = document.getElementById("week0");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('ArrowDown');
eventSender.keyDown('ArrowDown');
assert_equals(weekElement.value, "2019-W13", "Using arrow keys to navigate week picker should update in-page control.");
eventSender.keyDown('Escape');
assert_not_equals(internals.pagePopupWindow, null, "First escape keypress should not close popup.");
assert_equals(weekElement.value, "2019-W11", "First escape keypress should reset control back to original value.");
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "Second escape keypress should close popup.");
assert_equals(weekElement.value, "2019-W11", "Control should remain at original value.");
});
}, "Week picker: After modification, first escape should reset value and second escape should close picker.");
promise_test(() => {
let weekElement = document.getElementById("week0");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('ArrowDown');
eventSender.keyDown('ArrowDown');
assert_equals(weekElement.value, "2019-W13", "Using arrow keys to navigate week picker should update in-page control.");
eventSender.keyDown('ArrowUp');
eventSender.keyDown('ArrowUp');
assert_equals(weekElement.value, "2019-W11", "Expected to be back at the original value.");
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "Escape keypress should close popup.");
assert_equals(weekElement.value, "2019-W11", "Control should remain at original value.");
});
}, "Week picker: After modification, single escape should close popup if it was changed back to the initial value.");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../fast/forms/resources/common.js"></script>
<script src="../../../fast/forms/resources/picker-common.js"></script>
<script src="../../../fast/forms/calendar-picker/resources/calendar-picker-common.js"></script>
</head>
<body>
<input type="week" id="week" value="2020-W02" oninput="inputEventCount++;" onchange="changeEventCount++">
<script>
'use strict';
var inputEventCount = 0;
var changeEventCount = 0;
let weekElement = document.getElementById("week");
promise_test(() => {
assert_equals(internals.pagePopupWindow, null);
return openPickerWithPromise(weekElement).then(() => {
assert_equals(weekElement.value, "2020-W02");
assert_not_equals(internals.pagePopupWindow, null);
assert_equals(inputEventCount, 0, "No input event should have not have fired before making changes.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('ArrowRight');
eventSender.keyDown('ArrowDown');
assert_equals(weekElement.value, "2020-W04");
assert_equals(inputEventCount, 2, "One input event should have fired for each modification.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Popup should have closed from Enter key");
assert_equals(inputEventCount, 2, 'No extra input event should fire when closing popup.');
assert_equals(changeEventCount, 0, 'Change event is fired asynchronously after closing popup.');
return new Promise((resolve) => {
window.setTimeout(() => {
assert_equals(changeEventCount, 1, 'Change event should have fired (once) asynchronously after closing popup.');
resolve();
}, 0);
});
});
}, "Week picker: Test input and change event firing when popup value is modified");
promise_test(() => {
inputEventCount = changeEventCount = 0;
assert_equals(internals.pagePopupWindow, null);
return openPickerWithPromise(weekElement).then(() => {
assert_equals(weekElement.value, "2020-W04");
assert_not_equals(internals.pagePopupWindow, null);
assert_equals(inputEventCount, 0, "No input event should have not have fired before making changes.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('ArrowRight');
assert_equals(weekElement.value, "2020-W05");
assert_equals(inputEventCount, 1, "One input event should have fired for the modification.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('Escape');
assert_equals(weekElement.value, "2020-W04");
assert_not_equals(internals.pagePopupWindow, null, "Popup should not have closed from single escape");
assert_equals(inputEventCount, 2, "One input event should have fired when restoring the initial value.");
assert_equals(changeEventCount, 0, "No change event should have fired before popup is closed.");
eventSender.keyDown('Escape');
assert_equals(internals.pagePopupWindow, null, "Popup should have closed from Enter key");
assert_equals(inputEventCount, 2, 'No extra input event should fire when closing popup.');
return new Promise((resolve) => {
window.setTimeout(() => {
assert_equals(changeEventCount, 0, 'No change event should fire if popup was submitted with value still matching the original.');
resolve();
}, 0);
});
});
}, "Week picker: Test that change does not fire if popup value is modified and then restored to original value.");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../fast/forms/resources/common.js"></script>
<script src="../../../fast/forms/resources/picker-common.js"></script>
<script src="../../../fast/forms/calendar-picker/resources/calendar-picker-common.js"></script>
</head>
<body>
<input type="week" id="week0" min="2019-W04" max="2019-W20">
<input type="week" id="week1" min="5000-W04" max="5000-W20">
<input type="week" id="week2" >
<input type="week" id="week3" min="2019-W04" max="2019-W20" step="3" value="2019-W08">
<script>
promise_test(() => {
let weekElement = document.getElementById("week0");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W20", "Week should be selected if user hits enter without other input");
});
}, "Week picker: Picker should default to the day closest to max when it comes before the current date");
promise_test(() => {
let weekElement = document.getElementById("week1");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
// Note: test will need to be updated in the year 5000 :)
assert_equals(weekElement.value, "5000-W04", "Week should be selected if user hits enter without other input");
});
}, "Week picker: Picker should default to the day closest to min when it comes after the current date");
promise_test(() => {
let weekElement = document.getElementById("week2");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
let weekExpr = new RegExp(`${new Date().getFullYear()}-W\\d{2}`);
assert_true(weekExpr.test(weekElement.value), "Week control should default to current week");
});
}, "Week picker: Picker should default the current week if it is valid");
promise_test(() => {
let weekElement = document.getElementById("week3");
return openPickerWithPromise(weekElement)
.then(() => {
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W10", "Valid week closest to starting value should be selected if user hits enter without other input");
});
}, "Week picker: If the input's initial value is invalid due to step attribute, should select the nearest valid week");
</script>
</body>
</html>
...@@ -14,38 +14,30 @@ ...@@ -14,38 +14,30 @@
<input type="week" id="week3" value="2019-W07" step="5"> <input type="week" id="week3" value="2019-W07" step="5">
<input type="week" id="week4" value="2019-W07" min="2019-W06"> <input type="week" id="week4" value="2019-W07" min="2019-W06">
<input type="week" id="week5" value="2019-W07" max="2019-W08"> <input type="week" id="week5" value="2019-W07" max="2019-W08">
<input type="week" id="week6" min="2019-W04" max="2019-W20">
<script> <script>
promise_test(() => { promise_test(() => {
let weekElement = document.getElementById("week0"); let weekElement = document.getElementById("week0");
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowRight'); eventSender.keyDown('ArrowRight');
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
assert_equals(weekElement.value, "2019-W07", "Using arrow keys to navigate week picker should not update in-page control without pressing Enter key."); assert_equals(weekElement.value, "2019-W09", "Using arrow keys to navigate week picker should update in-page control without pressing Enter key.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W09", "Enter key should submit newly selected week."); assert_equals(weekElement.value, "2019-W09", "Updated week should remain after closing popup with Enter.");
}); });
}, "Week picker: Arrow keys should allow user to chooser month"); }, "Week picker: Arrow keys should allow user to chooser week");
promise_test(() => { promise_test(() => {
let weekElement = document.getElementById("week1"); let weekElement = document.getElementById("week1");
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowRight'); eventSender.keyDown('ArrowRight');
assert_equals(weekElement.value, "2019-W09", "Right arrow key should skip over invalid date.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W09", "Right arrow key should skip over invalid date.");
}); });
}, "Week picker: Picker with step should allow keyboard user to jump over invalid values -- right arrow"); }, "Week picker: Picker with step should allow keyboard user to jump over invalid values -- right arrow");
...@@ -53,14 +45,11 @@ promise_test(() => { ...@@ -53,14 +45,11 @@ promise_test(() => {
let weekElement = document.getElementById("week2"); let weekElement = document.getElementById("week2");
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowUp'); eventSender.keyDown('ArrowUp');
assert_equals(weekElement.value, "2019-W05", "Up arrow key should skip over invalid week.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W05", "Up arrow key should skip over invalid week.");
}); });
}, "Week picker: Picker with step should allow keyboard user to jump over invalid values -- up arrow"); }, "Week picker: Picker with step should allow keyboard user to jump over invalid values -- up arrow");
...@@ -68,14 +57,11 @@ promise_test(() => { ...@@ -68,14 +57,11 @@ promise_test(() => {
let weekElement = document.getElementById("week3"); let weekElement = document.getElementById("week3");
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
assert_equals(weekElement.value, "2019-W12", "Arrow key should skip to next valid week even if it's in a different month.");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W12", "Arrow key should skip to next valid week even if it's in a different month.");
}); });
}, "Week picker: Picker with step should allow keyboard user to skip to next month with valid week"); }, "Week picker: Picker with step should allow keyboard user to skip to next month with valid week");
...@@ -83,16 +69,13 @@ promise_test(() => { ...@@ -83,16 +69,13 @@ promise_test(() => {
let weekElement = document.getElementById("week4"); let weekElement = document.getElementById("week4");
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowLeft'); eventSender.keyDown('ArrowLeft');
eventSender.keyDown('ArrowLeft'); eventSender.keyDown('ArrowLeft');
eventSender.keyDown('ArrowUp'); eventSender.keyDown('ArrowUp');
assert_equals(weekElement.value, "2019-W06", "Min attribute should prevent user from arrowing into invalid values");
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W06", "Min attribute should prevent user from arrowing into invalid values");
}); });
}, "Week picker: Picker with min value should prevent user from using arrow keys to get into invalid values"); }, "Week picker: Picker with min value should prevent user from using arrow keys to get into invalid values");
...@@ -100,34 +83,15 @@ promise_test(() => { ...@@ -100,34 +83,15 @@ promise_test(() => {
let weekElement = document.getElementById("week5"); let weekElement = document.getElementById("week5");
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
eventSender.keyDown('ArrowDown'); eventSender.keyDown('ArrowDown');
eventSender.keyDown('ArrowRight'); eventSender.keyDown('ArrowRight');
eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W08", "Max attribute should prevent user from arrowing into invalid values"); assert_equals(weekElement.value, "2019-W08", "Max attribute should prevent user from arrowing into invalid values");
});
}, "Week picker: Picker with max value should prevent user from using arrow keys to get into invalid values");
promise_test(() => {
let weekElement = document.getElementById("week6");
return openPickerWithPromise(weekElement)
.then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('ArrowRight');
eventSender.keyDown('Enter'); eventSender.keyDown('Enter');
assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup."); assert_equals(internals.pagePopupWindow, null, "Enter key should dismiss popup.");
assert_equals(weekElement.value, "2019-W20", "Hitting arrow key when no value is selected should cause week to be selected");
}); });
}, "Week picker: Picker should select middle day of current month when starting with no selection"); }, "Week picker: Picker with max value should prevent user from using arrow keys to get into invalid values");
</script> </script>
</body> </body>
......
...@@ -15,10 +15,6 @@ promise_test(() => { ...@@ -15,10 +15,6 @@ promise_test(() => {
let weekElement = document.getElementById('week'); let weekElement = document.getElementById('week');
return openPickerWithPromise(weekElement) return openPickerWithPromise(weekElement)
.then(() => { .then(() => {
// Make the picker dismiss synchronously so we don't need to insert
// an artificial delay in the test
internals.pagePopupWindow.CalendarPicker.commitDelayMs = 0;
eventSender.keyDown('t'); eventSender.keyDown('t');
let weekRegExp = new RegExp(`${new Date().getFullYear()}-W\\d{2}`); let weekRegExp = new RegExp(`${new Date().getFullYear()}-W\\d{2}`);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment