summaryrefslogtreecommitdiffstats
path: root/toolkit/content/tests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/tests')
-rw-r--r--toolkit/content/tests/browser/browser.ini1
-rw-r--r--toolkit/content/tests/browser/browser_datetime_datepicker.js284
-rw-r--r--toolkit/content/tests/browser/head.js90
3 files changed, 375 insertions, 0 deletions
diff --git a/toolkit/content/tests/browser/browser.ini b/toolkit/content/tests/browser/browser.ini
index 278b2ffe0..67ba2f850 100644
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -26,6 +26,7 @@ skip-if = !e10s
[browser_contentTitle.js]
[browser_crash_previous_frameloader.js]
run-if = e10s && crashreporter
+[browser_datetime_datepicker.js]
[browser_default_image_filename.js]
[browser_f7_caret_browsing.js]
[browser_findbar.js]
diff --git a/toolkit/content/tests/browser/browser_datetime_datepicker.js b/toolkit/content/tests/browser/browser_datetime_datepicker.js
new file mode 100644
index 000000000..966a74e7a
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_datetime_datepicker.js
@@ -0,0 +1,284 @@
+/* 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/. */
+
+"use strict";
+
+const MONTH_YEAR = ".month-year",
+ DAYS_VIEW = ".days-view",
+ BTN_PREV_MONTH = ".prev",
+ BTN_NEXT_MONTH = ".next";
+const DATE_FORMAT = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", timeZone: "UTC" }).format;
+
+// Create a list of abbreviations for calendar class names
+const W = "weekend",
+ O = "outside",
+ S = "selection",
+ R = "out-of-range",
+ T = "today",
+ P = "off-step";
+
+// Calendar classlist for 2016-12. Used to verify the classNames are correct.
+const calendarClasslist_201612 = [
+ [W, O], [O], [O], [O], [], [], [W],
+ [W], [], [], [], [], [], [W],
+ [W], [], [], [], [S], [], [W],
+ [W], [], [], [], [], [], [W],
+ [W], [], [], [], [], [], [W],
+ [W, O], [O], [O], [O], [O], [O], [W, O],
+];
+
+function getCalendarText() {
+ return helper.getChildren(DAYS_VIEW).map(child => child.textContent);
+}
+
+function getCalendarClassList() {
+ return helper.getChildren(DAYS_VIEW).map(child => Array.from(child.classList));
+}
+
+function mergeArrays(a, b) {
+ return a.map((classlist, index) => classlist.concat(b[index]));
+}
+
+let helper = new DateTimeTestHelper();
+
+registerCleanupFunction(() => {
+ helper.cleanup();
+});
+
+/**
+ * Test that date picker opens to today's date when input field is blank
+ */
+add_task(async function test_datepicker_today() {
+ const date = new Date();
+
+ await helper.openPicker("data:text/html, <input type='date'>");
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(date));
+
+ await helper.tearDown();
+});
+
+/**
+ * Test that date picker opens to the correct month, with calendar days
+ * displayed correctly, given a date value is set.
+ */
+add_task(async function test_datepicker_open() {
+ const inputValue = "2016-12-15";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "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",
+ );
+ Assert.deepEqual(
+ getCalendarClassList(),
+ calendarClasslist_201612,
+ "2016-12 classNames"
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When the prev month button is clicked, calendar should display the dates for
+ * the previous month.
+ */
+add_task(async function test_datepicker_prev_month_btn() {
+ const inputValue = "2016-12-15";
+ const prevMonth = "2016-11-01";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ helper.click(helper.getElement(BTN_PREV_MONTH));
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(prevMonth)));
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "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",
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When the next month button is clicked, calendar should display the dates for
+ * the next month.
+ */
+add_task(async function test_datepicker_next_month_btn() {
+ const inputValue = "2016-12-15";
+ const nextMonth = "2017-01-01";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ helper.click(helper.getElement(BTN_NEXT_MONTH));
+
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(nextMonth)));
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "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",
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When a date on the calendar is clicked, date picker should close and set
+ * value to the input box.
+ */
+add_task(async function test_datepicker_clicked() {
+ const inputValue = "2016-12-15";
+ const firstDayOnCalendar = "2016-11-27";
+
+ await helper.openPicker(`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]);
+ await ContentTask.spawn(helper.tab.linkedBrowser, {}, async function() {
+ let inputEl = content.document.querySelector("input");
+ await ContentTaskUtils.waitForEvent(inputEl, "input");
+ });
+
+ Assert.equal(content.document.querySelector("input").value, firstDayOnCalendar);
+
+ await helper.tearDown();
+});
+
+/**
+ * Make sure picker is in correct state when it is reopened.
+ */
+add_task(async function test_datepicker_reopen_state() {
+ const inputValue = "2016-12-15";
+ const nextMonth = "2017-01-01";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+ // Navigate to the next month but does not commit the change
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
+ helper.click(helper.getElement(BTN_NEXT_MONTH));
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(nextMonth)));
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+
+ // Ensures the picker opens to the month of the input value
+ await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
+ await helper.waitForPickerReady();
+ Assert.equal(helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT(new Date(inputValue)));
+
+ await helper.tearDown();
+});
+
+/**
+ * When min and max attributes are set, calendar should show some dates as
+ * out-of-range.
+ */
+add_task(async function test_datepicker_min_max() {
+ const inputValue = "2016-12-15";
+ const inputMin = "2016-12-05";
+ const inputMax = "2016-12-25";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}" min="${inputMin}" max="${inputMax}">`);
+
+ Assert.deepEqual(
+ getCalendarClassList(),
+ mergeArrays(calendarClasslist_201612, [
+ // R denotes out-of-range
+ [R], [R], [R], [R], [R], [R], [R],
+ [R], [], [], [], [], [], [],
+ [], [], [], [], [], [], [],
+ [], [], [], [], [], [], [],
+ [], [R], [R], [R], [R], [R], [R],
+ [R], [R], [R], [R], [R], [R], [R],
+ ]),
+ "2016-12 with min & max",
+ );
+
+ await helper.tearDown();
+});
+
+/**
+ * When step attribute is set, calendar should show some dates as off-step.
+ */
+add_task(async function test_datepicker_step() {
+ const inputValue = "2016-12-15";
+ const inputStep = "5";
+
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}" step="${inputStep}">`);
+
+ Assert.deepEqual(
+ getCalendarClassList(),
+ mergeArrays(calendarClasslist_201612, [
+ // P denotes off-step
+ [P], [P], [P], [], [P], [P], [P],
+ [P], [], [P], [P], [P], [P], [],
+ [P], [P], [P], [P], [], [P], [P],
+ [P], [P], [], [P], [P], [P], [P],
+ [], [P], [P], [P], [P], [], [P],
+ [P], [P], [P], [], [P], [P], [P],
+ ]),
+ "2016-12 with step",
+ );
+
+ await helper.tearDown();
+});
+
+add_task(async function test_datepicker_abs_min() {
+ const inputValue = "0001-01-01";
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "", "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",
+ ],
+ "0001-01",
+ );
+
+ await helper.tearDown();
+});
+
+add_task(async function test_datepicker_abs_max() {
+ const inputValue = "275760-09-13";
+ await helper.openPicker(`data:text/html, <input type="date" value="${inputValue}">`);
+
+ Assert.deepEqual(
+ getCalendarText(),
+ [
+ "31", "1", "2", "3", "4", "5", "6",
+ "7", "8", "9", "10", "11", "12", "13",
+ "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "",
+ ],
+ "275760-09",
+ );
+
+ await helper.tearDown();
+});
diff --git a/toolkit/content/tests/browser/head.js b/toolkit/content/tests/browser/head.js
index 1c6c2b54f..d7ed7a9ff 100644
--- a/toolkit/content/tests/browser/head.js
+++ b/toolkit/content/tests/browser/head.js
@@ -31,3 +31,93 @@ function pushPrefs(...aPrefs) {
SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
return deferred.promise;
}
+
+/**
+ * Helper class for testing datetime input picker widget
+ */
+class DateTimeTestHelper {
+ constructor() {
+ this.panel = document.getElementById("DateTimePickerPanel");
+ this.panel.setAttribute("animate", false);
+ this.tab = null;
+ this.frame = null;
+ }
+
+ /**
+ * Opens a new tab with the URL of the test page, and make sure the picker is
+ * ready for testing.
+ *
+ * @param {String} pageUrl
+ */
+ async openPicker(pageUrl) {
+ this.tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+ await BrowserTestUtils.synthesizeMouseAtCenter("input", {}, gBrowser.selectedBrowser);
+ // If dateTimePopupFrame doesn't exist yet, wait for the binding to be attached
+ if (!this.panel.dateTimePopupFrame) {
+ await BrowserTestUtils.waitForEvent(this.panel, "DateTimePickerBindingReady")
+ }
+ this.frame = this.panel.dateTimePopupFrame;
+ await this.waitForPickerReady();
+ }
+
+ async waitForPickerReady() {
+ await BrowserTestUtils.waitForEvent(this.frame, "load", true);
+ // Wait for picker elements to be ready
+ await BrowserTestUtils.waitForEvent(this.frame.contentDocument, "PickerReady");
+ }
+
+ /**
+ * Find an element on the picker.
+ *
+ * @param {String} selector
+ * @return {DOMElement}
+ */
+ getElement(selector) {
+ return this.frame.contentDocument.querySelector(selector);
+ }
+
+ /**
+ * Find the children of an element on the picker.
+ *
+ * @param {String} selector
+ * @return {Array<DOMElement>}
+ */
+ getChildren(selector) {
+ return Array.from(this.getElement(selector).children);
+ }
+
+ /**
+ * Click on an element
+ *
+ * @param {DOMElement} element
+ */
+ click(element) {
+ EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow);
+ }
+
+ /**
+ * Close the panel and the tab
+ */
+ async tearDown() {
+ if (!this.panel.hidden) {
+ let pickerClosePromise = new Promise(resolve => {
+ this.panel.addEventListener("popuphidden", resolve, {once: true});
+ });
+ this.panel.hidePopup();
+ this.panel.closePicker();
+ await pickerClosePromise;
+ }
+ await BrowserTestUtils.removeTab(this.tab);
+ this.tab = null;
+ }
+
+ /**
+ * Clean up after tests. Remove the frame to prevent leak.
+ */
+ cleanup() {
+ this.frame.remove();
+ this.frame = null;
+ this.panel.removeAttribute("animate");
+ this.panel = null;
+ }
+}