/* 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; const DATE_FORMAT_LOCAL = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", }).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])); } async function verifyPickerPosition(browsingContext, inputId) { let inputRect = await SpecialPowers.spawn( browsingContext, [inputId], async function(inputIdChild) { let rect = content.document .getElementById(inputIdChild) .getBoundingClientRect(); return { left: content.mozInnerScreenX + rect.left, bottom: content.mozInnerScreenY + rect.bottom, }; } ); function is_close(got, exp, msg) { // on some platforms we see differences of a fraction of a pixel - so // allow any difference of < 1 pixels as being OK. ok( Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp ); } is_close(helper.panel.screenX, inputRect.left, "datepicker x position"); is_close(helper.panel.screenY, inputRect.bottom, "datepicker y position"); } 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, "); if (date.getMonth() === new Date().getMonth()) { Assert.equal( helper.getElement(MONTH_YEAR).textContent, DATE_FORMAT_LOCAL(date) ); } else { Assert.ok( true, "Skipping datepicker today test if month changes when opening picker." ); } 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, ` ); 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(); }); /** * Ensure picker closes when focus moves to a different input. */ add_task(async function test_datepicker_focus_change() { await helper.openPicker( `data:text/html,` ); let browser = helper.tab.linkedBrowser; await verifyPickerPosition(browser, "date"); isnot(helper.panel.hidden, "Panel should be visible"); await SpecialPowers.spawn(browser, [], () => { content.document.querySelector("#other").focus(); }); // NOTE: Would be cool to be able to use promisePickerClosed(), but // popuphidden isn't really triggered for this code path it seems, so oh // well. await BrowserTestUtils.waitForCondition( () => helper.panel.hidden, "waiting for close" ); await helper.tearDown(); }); /** * Ensure picker opens and closes with key bindings appropriately. */ /* disabled for bug 1676078 add_task(async function test_datepicker_keyboard_open() { const inputValue = "2016-12-15"; const prevMonth = "2016-11-01"; await helper.openPicker( `data:text/html,` ); let browser = helper.tab.linkedBrowser; await verifyPickerPosition(browser, "date"); BrowserTestUtils.synthesizeKey(" ", {}, browser); await BrowserTestUtils.waitForCondition( () => helper.panel.hidden, "waiting for close" ); BrowserTestUtils.synthesizeKey(" ", {}, browser); await BrowserTestUtils.waitForCondition( () => !helper.panel.hidden, "waiting for open" ); // NOTE: After the click, the first input field (the month one) is focused, // so down arrow will change the selected month. // // This assumes en-US locale, which seems fine for testing purposes (as // DATE_FORMAT and other bits around do the same). BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser); // It'd be good to use something else than waitForCondition for this but // there's no exposed event atm when the value changes from the child. await BrowserTestUtils.waitForCondition(() => { return ( helper.getElement(MONTH_YEAR).textContent == DATE_FORMAT(new Date(prevMonth)) ); }, "Should update date when updating months"); 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, ` ); 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, ` ); 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, ` ); let browser = helper.tab.linkedBrowser; await verifyPickerPosition(browser, "date"); // Click the first item (top-left corner) of the calendar let promise = BrowserTestUtils.waitForContentEvent( helper.tab.linkedBrowser, "input" ); helper.click(helper.getElement(DAYS_VIEW).children[0]); await promise; let value = await SpecialPowers.spawn( browser, [], () => content.document.querySelector("input").value ); Assert.equal(value, firstDayOnCalendar); await helper.tearDown(); }); /** * Ensure that the datepicker popop appears correctly positioned when * the input field has been transformed. */ add_task(async function test_datepicker_transformed_position() { const inputValue = "2016-12-15"; const style = "transform: translateX(7px) translateY(13px); border-top: 2px; border-left: 5px; margin: 30px;"; const iframeContent = ``; await helper.openPicker( "data:text/html,