diff options
Diffstat (limited to 'toolkit/content/tests')
-rw-r--r-- | toolkit/content/tests/browser/browser.ini | 1 | ||||
-rw-r--r-- | toolkit/content/tests/browser/browser_datetime_datepicker.js | 284 | ||||
-rw-r--r-- | toolkit/content/tests/browser/head.js | 90 |
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; + } +} |