Bug 1328219 - (Part 2) Add browser chrome test for date picker. r=mconley draft
authorScott Wu <scottcwwu@gmail.com>
Thu, 03 Aug 2017 18:26:23 +0800
changeset 643035 150b5fb09736c5e5e0f0bede463a1a4e0358074e
parent 643034 60597a0980c0a1b4c44af83c6c28a59d760307ea
child 725180 bef5d45c92c895107734da0660427578b4cb86d9
push id72960
push userbmo:scwwu@mozilla.com
push dateWed, 09 Aug 2017 06:44:48 +0000
reviewersmconley
bugs1328219
milestone57.0a1
Bug 1328219 - (Part 2) Add browser chrome test for date picker. r=mconley MozReview-Commit-ID: 99zfNaXqbjc
toolkit/content/tests/browser/browser.ini
toolkit/content/tests/browser/browser_datetime_datepicker.js
toolkit/content/tests/browser/common/datetimePickerHelper.js
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -53,16 +53,19 @@ skip-if = !e10s
 [browser_bug594509.js]
 [browser_bug982298.js]
 [browser_charsetMenu_swapBrowsers.js]
 [browser_content_url_annotation.js]
 skip-if = !e10s || !crashreporter
 [browser_contentTitle.js]
 [browser_crash_previous_frameloader.js]
 run-if = e10s && crashreporter
+[browser_datetime_datepicker.js]
+support-files =
+  common/datetimePickerHelper.js
 [browser_default_image_filename.js]
 [browser_f7_caret_browsing.js]
 [browser_findbar.js]
 [browser_isSynthetic.js]
 [browser_keyevents_during_autoscrolling.js]
 [browser_label_textlink.js]
 [browser_mediaPlayback.js]
 tags = audiochannel
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_datetime_datepicker.js
@@ -0,0 +1,229 @@
+/* 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/. */
+
+/* import-globals-from common/datetimePickerHelper.js */
+
+"use strict";
+
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/datetimePickerHelper.js", this);
+
+const MONTH_YEAR = ".month-year",
+      DAYS_VIEW = ".days-view",
+      BTN_PREV_MONTH = ".prev",
+      BTN_NEXT_MONTH = ".next";
+const dateFormat = 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.removeFrame();
+});
+
+/**
+ * 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, dateFormat(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, dateFormat(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}">`);
+  await new Promise(resolve => executeSoon(resolve));
+  helper.click(helper.getElement(BTN_PREV_MONTH));
+
+  Assert.equal(helper.getElement(MONTH_YEAR).textContent, dateFormat(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}">`);
+  await new Promise(resolve => executeSoon(resolve));
+  helper.click(helper.getElement(BTN_NEXT_MONTH));
+
+  Assert.equal(helper.getElement(MONTH_YEAR).textContent, dateFormat(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
+  let inputChangePromise = new Promise(resolve => {
+    content.document.querySelector("input").addEventListener("input", resolve, {once: true});
+  });
+  await new Promise(resolve => executeSoon(resolve));
+  helper.click(helper.getElement(DAYS_VIEW).children[0]);
+  await inputChangePromise;
+
+  Assert.equal(content.document.querySelector("input").value, firstDayOnCalendar);
+
+  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();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/common/datetimePickerHelper.js
@@ -0,0 +1,51 @@
+/* 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";
+
+function DateTimeTestHelper() {
+  this.panel = document.getElementById("DateTimePickerPanel");
+}
+
+DateTimeTestHelper.prototype = {
+  tab: null,
+  frame: null,
+  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 BrowserTestUtils.waitForEvent(this.frame, "load", true);
+    // Wait for picker elements to be ready
+    await BrowserTestUtils.waitForEvent(this.frame.contentDocument, "PickerReady");
+  },
+  closeTab() {
+    return BrowserTestUtils.removeTab(this.tab);
+  },
+  getElement(selector) {
+    return this.frame.contentDocument.querySelector(selector);
+  },
+  getChildren(selector) {
+    return Array.from(this.getElement(selector).children);
+  },
+  click(element) {
+    EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow);
+  },
+  async tearDown() {
+    if (!this.panel.hidden) {
+      let pickerClosePromise = new Promise(resolve => {
+        this.panel.addEventListener("popuphidden", resolve, {once: true});
+      });
+      this.panel.closePicker();
+      await pickerClosePromise;
+    }
+    return this.closeTab();
+  },
+  removeFrame() {
+    this.frame.remove();
+  },
+};