new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_datetime_datepicker.js
@@ -0,0 +1,186 @@
+/* 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/. */
+
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/datetimePickerHelper.js", this);
+
+const MONTH_YEAR = ".month-year",
+ WEEK_HEADER = ".week-header",
+ DAYS_VIEW = ".days-view",
+ BTN_PREV_MONTH = ".left",
+ BTN_NEXT_MONTH = ".right",
+ YEAR_SPINNER = "#year .spinner",
+ MONTH_SPINNER = "#month .spinner";
+
+const helper = new dateTimeTestHelper({
+ itemHeight: 25,
+ viewportSize: 5
+});
+const dateFormat = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long" }).format;
+const getCalendar = () => helper.getChildren(DAYS_VIEW, child => child.textContent);
+const getSpinnerCenter = spinner => spinner.scrollTop / ITEM_HEIGHT + (VIEWPORT_SIZE - 1) / 2;
+
+
+/**
+ * Test that date picker opens to today's date when input field is blank
+ */
+add_task(function* () {
+ const date = new Date();
+
+ yield helper.openTab("data:text/html, <input type='date'>");
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, dateFormat(date));
+
+ helper.tearDown();
+});
+
+/**
+ * Test that date picker opens to the correct month, with calendar days
+ * displayed correctly, given a date value is set.
+ */
+add_task(function* () {
+ const inputValue = "2016-12-01";
+
+ yield helper.openTab(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, dateFormat(new Date(inputValue)));
+ Assert.deepEqual(
+ getCalendar(),
+ [
+ "27", "28", "29", "30", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "10",
+ "11", "12", "13", "14", "15", "16", "17",
+ "18", "19", "20", "21", "22", "23", "24",
+ "25", "26", "27", "28", "29", "30", "31",
+ "1", "2", "3", "4", "5", "6", "7"
+ ],
+ "2016-12"
+ );
+
+ helper.tearDown();
+});
+
+/**
+ * When the prev month button is clicked, calendar should display the dates for
+ * the previous month.
+ */
+add_task(function* () {
+ const inputValue = "2016-12-01";
+ const prevMonth = "2016-11-01";
+
+ yield helper.openTab(`data:text/html, <input type="date" value="${inputValue}">`);
+ helper.click(helper.getElement(BTN_PREV_MONTH));
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, dateFormat(new Date(prevMonth)));
+ Assert.deepEqual(
+ getCalendar(),
+ [
+ "30", "31", "1", "2", "3", "4", "5",
+ "6", "7", "8", "9", "10", "11", "12",
+ "13", "14", "15", "16", "17", "18", "19",
+ "20", "21", "22", "23", "24", "25", "26",
+ "27", "28", "29", "30", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "10"
+ ],
+ "2016-11"
+ );
+
+ helper.tearDown();
+});
+
+/**
+ * When the next month button is clicked, calendar should display the dates for
+ * the next month.
+ */
+add_task(function* () {
+ const inputValue = "2016-12-01";
+ const nextMonth = "2017-01-01";
+
+ yield helper.openTab(`data:text/html, <input type="date" value="${inputValue}">`);
+ helper.click(helper.getElement(BTN_NEXT_MONTH));
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, dateFormat(new Date(nextMonth)));
+ Assert.deepEqual(
+ getCalendar(),
+ [
+ "25", "26", "27", "28", "29", "30", "31",
+ "1", "2", "3", "4", "5", "6", "7",
+ "8", "9", "10", "11", "12", "13", "14",
+ "15", "16", "17", "18", "19", "20", "21",
+ "22", "23", "24", "25", "26", "27", "28",
+ "29", "30", "31", "1", "2", "3", "4"
+ ],
+ "2017-01"
+ );
+
+ helper.tearDown();
+});
+
+/**
+ * When a date on the calendar is clicked, date picker should close and set
+ * value to the input box.
+ */
+add_task(function* () {
+ const inputValue = "2016-12-01";
+ const firstDayOnCalendar = "2016-11-27";
+
+ yield helper.openTab(`data:text/html, <input type="date" value="${inputValue}">`);
+ // Click the first item (top-left corner) of the calendar
+ helper.click(helper.getElement(DAYS_VIEW).children[0]);
+
+ yield BrowserTestUtils.waitForCondition(() => {
+ return helper.dateTimePickerPanel.hidden == true;
+ });
+ // XXX: Input box value won't be set until blur or receive keyboard events.
+ // This should be fixed in the future.
+ content.document.querySelector("input").blur();
+
+ Assert.equal(content.document.querySelector("input").value, firstDayOnCalendar);
+
+ helper.tearDown();
+});
+
+/**
+ * The spinners should have current month and year at the center, and changing them
+ * will update the calendar accordingly.
+ */
+add_task(function* () {
+ const inputValue = "2016-12-01";
+ yield helper.openTab(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ helper.click(helper.getElement(MONTH_YEAR));
+
+ Assert.equal(helper.getSpinnerElement(YEAR_SPINNER).textContent, "2016");
+ Assert.equal(helper.getSpinnerElement(MONTH_SPINNER).textContent, "Dec");
+
+ // Wait for the month-year picker to open
+ yield helper.waitForScrollStop(YEAR_SPINNER);
+ yield BrowserTestUtils.waitForCondition(() => true);
+
+ helper.click(helper.getSpinnerElement(YEAR_SPINNER, 2));
+ yield helper.waitForScrollStop(YEAR_SPINNER);
+
+ helper.click(helper.getSpinnerElement(MONTH_SPINNER, -2));
+ yield helper.waitForScrollStop(MONTH_SPINNER);
+
+ Assert.equal(helper.getSpinnerElement(YEAR_SPINNER).textContent, "2018");
+ Assert.equal(helper.getSpinnerElement(MONTH_SPINNER).textContent, "Oct");
+
+ // Close the month-year picker
+ helper.click(helper.getElement(MONTH_YEAR));
+
+ Assert.deepEqual(
+ getCalendar(),
+ [
+ "30", "1", "2", "3", "4", "5", "6",
+ "7", "8", "9", "10", "11", "12", "13",
+ "14", "15", "16", "17", "18", "19", "20",
+ "21", "22", "23", "24", "25", "26", "27",
+ "28", "29", "30", "31", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "10"
+ ],
+ "2018-10"
+ );
+
+ helper.tearDown();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/common/datetimePickerHelper.js
@@ -0,0 +1,65 @@
+/* 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 = 5,
+ VIEWPORT_COUNT = 5,
+ DAY_PERIOD_IN_HOURS = 12,
+ HOUR = "#hour",
+ MINUTE = "#minute",
+ DAY_PERIOD = "#day-period",
+ SPINNER = ".spinner",
+ UP = ".up",
+ DOWN = ".down";
+
+function dateTimeTestHelper({ itemHeight, viewportSize }) {
+ this.itemHeight = itemHeight;
+ this.viewportSize = viewportSize;
+ this.dateTimePickerPanel = document.getElementById("DateTimePickerPanel");
+ this.dateTimePopupFrame = this.dateTimePickerPanel.dateTimePopupFrame;
+}
+
+dateTimeTestHelper.prototype = {
+ *openTab(pageUrl) {
+ this.tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+ let popupLoadedPromise = BrowserTestUtils.waitForEvent(this.dateTimePopupFrame, "load", true);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
+ yield popupLoadedPromise;
+ yield BrowserTestUtils.waitForEvent(this.dateTimePopupFrame.contentDocument, "PickerReady");
+ },
+ closeTab() {
+ return BrowserTestUtils.removeTab(this.tab);
+ },
+ closePicker() {
+ this.dateTimePickerPanel.closePicker();
+ },
+ getSpinnerElement(selector, offset = 0) {
+ let spinner = this.getElement(selector);
+ let spinnerCenterIndex = this.getCenterElementIndex(spinner);
+ return spinner.childNodes[spinnerCenterIndex + offset];
+ },
+ getCenterElementIndex(spinner) {
+ return spinner.scrollTop / this.itemHeight + (this.viewportSize - 1) / 2;
+ },
+ getElement(selector) {
+ return this.dateTimePopupFrame.contentDocument.querySelector(selector);
+ },
+ getChildren(selector, cb) {
+ let childElements = this.getElement(selector).children;
+ return Array.prototype.map.call(childElements, cb);
+ },
+ click(element) {
+ EventUtils.synthesizeMouseAtCenter(element, {}, this.dateTimePopupFrame.contentWindow);
+ },
+ wheel(element, offsetX, offsetY, event) {
+ EventUtils.synthesizeWheel(element, offsetX, offsetY, event, this.dateTimePopupFrame.contentWindow);
+ },
+ waitForScrollStop(selector) {
+ return BrowserTestUtils.waitForEvent(this.getElement(selector), "ScrollStop");
+ },
+ tearDown() {
+ this.closePicker();
+ this.closeTab();
+ }
+};