new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_datetime_timepicker.js
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const ITEM_HEIGHT = 25,
+ VIEWPORT_SIZE = 7,
+ VIEWPORT_COUNT = 5,
+ DAY_PERIOD_IN_HOURS = 12,
+ HOUR = "#hour",
+ MINUTE = "#minute",
+ DAY_PERIOD = "#day-period",
+ SPINNER = ".spinner",
+ UP = ".up",
+ DOWN = ".down";
+
+function dateTimeTestHelper(pageUrl) {
+ this.pageUrl = pageUrl;
+ this.dateTimePickerPanel = document.getElementById("DateTimePickerPanel");
+ this.dateTimePopupFrame = this.dateTimePickerPanel.dateTimePopupFrame;
+}
+
+dateTimeTestHelper.prototype = {
+ *openTab() {
+ this.tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, this.pageUrl);
+ let popupLoadedPromise = BrowserTestUtils.waitForEvent(this.dateTimePopupFrame, "load", true);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#time-input-box", {}, gBrowser.selectedBrowser);
+ yield popupLoadedPromise;
+ yield BrowserTestUtils.waitForEvent(this.dateTimePopupFrame.contentDocument, "TimePickerReady");
+ },
+ closeTab() {
+ return BrowserTestUtils.removeTab(this.tab);
+ },
+ closePicker() {
+ this.dateTimePickerPanel.closePicker();
+ },
+ getHourStr() {
+ return this.getSpinnerElement(HOUR).textContent;
+ },
+ getMinuteStr() {
+ return this.getSpinnerElement(MINUTE).textContent;
+ },
+ getDayPeriodStr() {
+ return this.getSpinnerElement(DAY_PERIOD).textContent;
+ },
+ getSpinnerElement(id, offset = 0) {
+ let spinner = this.getElement(`${id} ${SPINNER}`);
+ let spinnerCenterIndex = this.getCenterElementIndex(spinner);
+ return spinner.childNodes[spinnerCenterIndex + offset];
+ },
+ getCenterElementIndex(spinner) {
+ return spinner.scrollTop / ITEM_HEIGHT + (VIEWPORT_SIZE - 1) / 2;
+ },
+ getElement(selector) {
+ return this.dateTimePopupFrame.contentDocument.querySelector(selector);
+ },
+ click(element) {
+ EventUtils.synthesizeMouseAtCenter(element, {}, this.dateTimePopupFrame.contentWindow);
+ },
+ wheel(element, offsetX, offsetY, event) {
+ EventUtils.synthesizeWheel(element, offsetX, offsetY, event, this.dateTimePopupFrame.contentWindow);
+ },
+ // drag(element, deltaY) {
+
+ // }
+ clickUpArrow(id) {
+ this.click(this.getElement(`${id} ${UP}`));
+ return this.waitForScrollStop(id);
+ },
+ clickDownArrow(id) {
+ this.click(this.getElement(`${id} ${DOWN}`));
+ return this.waitForScrollStop(id);
+ },
+ waitForScrollStop(id) {
+ return BrowserTestUtils.waitForEvent(this.getElement(`${id} ${SPINNER}`), "ScrollStop");
+ },
+ tearDown() {
+ this.closePicker();
+ this.closeTab();
+ }
+};
+
+function showHourIn12(hour, locale) {
+ // Hour 0 in 12 hour format is displayed as 12.
+ const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
+ return hourIn12 == 0 ? Number(12).toLocaleString(locale)
+ : hourIn12.toLocaleString(locale);
+}
+
+function showMinute(minute, locale) {
+ return Number(minute).toLocaleString(locale);
+}
+
+function showDayPeriod(hour) {
+ // TODO: day period string should have l10n support
+ return hour < DAY_PERIOD_IN_HOURS ? "AM" : "PM";
+}
+
+add_task(function*() {
+ const now = new Date();
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time'>");
+ yield helper.openTab();
+
+ is(helper.getHourStr(), showHourIn12(now.getHours()), "Current hour");
+ is(helper.getMinuteStr(), showMinute(now.getMinutes()), "Current minute");
+ is(helper.getDayPeriodStr(), showDayPeriod(now.getHours()), "Current day period");
+
+ helper.tearDown();
+});
+
+add_task(function*() {
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='00:00'>");
+ yield helper.openTab();
+
+ is(helper.getHourStr(), showHourIn12(0), "Hour should be 12");
+ is(helper.getMinuteStr(), showMinute(0), "Minute should be 0");
+ is(helper.getDayPeriodStr(), showDayPeriod(0), "Day period should be AM");
+
+ helper.tearDown();
+});
+
+add_task(function*() {
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='22:35'>");
+ yield helper.openTab();
+
+ is(helper.getHourStr(), showHourIn12(22), "Hour should be 10");
+ is(helper.getMinuteStr(), showMinute(35), "Minute should be 35");
+ is(helper.getDayPeriodStr(), showDayPeriod(12), "Day period should be PM");
+
+ helper.tearDown();
+});
+
+add_task(function*() {
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='14:35'>");
+ yield helper.openTab();
+
+ // Check initial states
+ is(helper.getHourStr(), showHourIn12(2), "Hour should be 2");
+ is(helper.getMinuteStr(), showMinute(35), "Minute should be 35");
+ is(helper.getDayPeriodStr(), showDayPeriod(12), "Day period should be PM");
+
+ // Click the up arrow for hour once
+ yield helper.clickUpArrow(HOUR);
+ is(helper.getHourStr(), showHourIn12(3), "Hour should be 3");
+
+ // Click the down arrow for hour three times
+ yield helper.clickDownArrow(HOUR);
+ yield helper.clickDownArrow(HOUR);
+ yield helper.clickDownArrow(HOUR);
+ is(helper.getHourStr(), showHourIn12(0), "Hour should be 12");
+
+ // Click the down arrow for hour once to test cycling
+ yield helper.clickDownArrow(HOUR);
+ is(helper.getHourStr(), showHourIn12(11), "Hour should be 11");
+
+ // Click the down arrow for minute once
+ yield helper.clickDownArrow(MINUTE);
+ is(helper.getMinuteStr(), showMinute(34), "Minute should be 34");
+
+ yield helper.tearDown();
+});
+
+add_task(function*() {
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='22:35'>");
+ yield helper.openTab();
+
+ helper.click(helper.getSpinnerElement(HOUR, -1));
+ yield helper.waitForScrollStop(HOUR);
+ is(helper.getHourStr(), showHourIn12(21), "Hour should be 9");
+
+ helper.click(helper.getSpinnerElement(MINUTE, 3));
+ yield helper.waitForScrollStop(MINUTE);
+ is(helper.getMinuteStr(), showMinute(38), "Minute should be 38");
+
+ helper.click(helper.getSpinnerElement(DAY_PERIOD, -1));
+ yield helper.waitForScrollStop(DAY_PERIOD);
+ is(helper.getDayPeriodStr(), showDayPeriod(0), "Day period should be AM");
+
+ yield helper.tearDown();
+});
+
+add_task(function*() {
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='18:00'>");
+ yield helper.openTab();
+
+ let hourSpinner = helper.getSpinnerElement(HOUR);
+
+ helper.wheel(hourSpinner, 10, 10, {
+ deltaY: -200, deltaMode: WheelEvent.DOM_DELTA_PIXEL
+ });
+ yield helper.waitForScrollStop(HOUR);
+ is(helper.getHourStr(), showHourIn12(2), "Hour should be 2");
+
+ helper.wheel(hourSpinner, 10, 10, {
+ deltaY: -200, deltaMode: WheelEvent.DOM_DELTA_PIXEL
+ });
+ yield helper.waitForScrollStop(HOUR);
+ is(helper.getHourStr(), showHourIn12(10), "Hour should be 10");
+
+ helper.wheel(hourSpinner, 10, 10, {
+ deltaY: 100, deltaMode: WheelEvent.DOM_DELTA_PIXEL
+ });
+ yield helper.waitForScrollStop(HOUR);
+ is(helper.getHourStr(), showHourIn12(0), "Hour should be 12");
+
+ yield helper.tearDown();
+});
+
+/*
+add_task(function*() {
+ const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='18:00'>");
+ yield helper.openTab();
+
+ let hourSpinner = helper.getElement(`${HOUR} ${SPINNER}`);
+ dump(">>>>> " + hourSpinner.scrollTop);
+ EventUtils.synthesizeMouse(hourSpinner, 1, 1, { type: "mousedown" }, helper.dateTimePopupFrame.contentWindow);
+ dump(">>>>> " + hourSpinner.scrollTop);
+ EventUtils.synthesizeMouse(hourSpinner, 1, 100, { type: "mousemove" }, helper.dateTimePopupFrame.contentWindow);
+ dump(">>>>> " + hourSpinner.scrollTop);
+ EventUtils.synthesizeMouse(hourSpinner, 1, 50, { type: "mouseup" }, helper.dateTimePopupFrame.contentWindow);
+ dump(">>>>> " + hourSpinner.scrollTop);
+ yield helper.waitForScrollStop(HOUR);
+ dump(">>>>> " + hourSpinner.scrollTop);
+ is(helper.getHourStr(), showHourIn12(4), "Hour dragged downward");
+
+ // EventUtils.synthesizeMouse(hourSpinner, 1, 1, { type: "mousedown" }, helper.dateTimePopupFrame.contentWindow);
+ // EventUtils.synthesizeMouse(hourSpinner, 1, 100, { type: "mousemove" }, helper.dateTimePopupFrame.contentWindow);
+ // EventUtils.synthesizeMouse(hourSpinner, 1, 1, { type: "mouseup" }, helper.dateTimePopupFrame.contentWindow);
+ // yield helper.waitForScrollStop(HOUR);
+ // is(helper.getHourStr(), showHourIn12(2), "Hour dragged downward");
+
+
+
+ yield helper.tearDown();
+});
+*/
\ No newline at end of file
--- a/toolkit/content/widgets/spinner.js
+++ b/toolkit/content/widgets/spinner.js
@@ -7,19 +7,19 @@
/*
* The spinner is responsible for displaying the items, and does
* not care what the values represent. The setValue function is called
* when it detects a change in value triggered by scroll event.
* Supports scrolling, clicking on up or down, clicking on item, and
* dragging.
*/
-function Spinner(props, context) {
+function Spinner(props, context, id) {
this.context = context;
- this._init(props);
+ this._init(props, id);
};
{
const debug = 0 ? console.log.bind(console, "[spinner]") : function() {};
const ITEM_HEIGHT = 25,
VIEWPORT_SIZE = 7,
VIEWPORT_COUNT = 5,
@@ -36,21 +36,21 @@ function Spinner(props, context) {
* the parent component.
* {Function} getDisplayString: Takes a value, and output it
* as localized strings.
* {Number} viewportSize [optional]: Number of items in a
* viewport.
* {Boolean} hideButtons [optional]: Hide up & down buttons
* }
*/
- _init(props) {
+ _init(props, id) {
const { setValue, getDisplayString, hideButtons } = props;
-
const spinnerTemplate = document.getElementById("spinner-template");
const spinnerElement = document.importNode(spinnerTemplate.content, true);
+ spinnerElement.querySelector(".spinner-container").id = id;
// Make sure viewportSize is an odd number because we want to have the selected
// item in the center. If it's an even number, use the default size instead.
const viewportSize = props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;
this.state = {
items: [],
isScrolling: false
--- a/toolkit/content/widgets/timepicker.js
+++ b/toolkit/content/widgets/timepicker.js
@@ -105,24 +105,24 @@ function TimePicker(context) {
return numberFormat(hour);
} else {
// Hour 0 in 12 hour format is displayed as 12.
const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
return hourIn12 == 0 ? numberFormat(12)
: numberFormat(hourIn12);
}
}
- }, this.context),
+ }, this.context, "hour"),
minute: new Spinner({
setValue: wrapSetValueFn(value => {
timeKeeper.setMinute(value);
this.state.isMinuteSet = true;
}),
getDisplayString: minute => numberFormat(minute)
- }, this.context)
+ }, this.context, "minute")
};
this._insertLayoutElement({
tag: "div",
textContent: ":",
className: "colon",
insertBefore: this.components.minute.elements.container
});
@@ -132,17 +132,17 @@ function TimePicker(context) {
if (format == "12") {
this.components.dayPeriod = new Spinner({
setValue: wrapSetValueFn(value => {
timeKeeper.setDayPeriod(value);
this.state.isDayPeriodSet = true;
}),
getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM",
hideButtons: true
- }, this.context);
+ }, this.context, "day-period");
this._insertLayoutElement({
tag: "div",
className: "spacer",
insertBefore: this.components.dayPeriod.elements.container
});
}
},
@@ -247,16 +247,17 @@ function TimePicker(context) {
handleMessage(event) {
switch (event.data.name) {
case "TimePickerSetValue": {
this.set(event.data.detail);
break;
}
case "TimePickerInit": {
this.init(event.data.detail);
+ document.dispatchEvent(new CustomEvent("TimePickerReady"));
break;
}
}
},
/**
* Set the time state and update the components with the new state.
*