Bug 1309471 - Add browser chrome tests for time picker draft
authorScott Wu <scottcwwu@gmail.com>
Tue, 11 Oct 2016 14:11:03 +0800
changeset 425962 7f67f8f9a0136427c02933d0f190a9736ca3c4e7
parent 425848 fb2d17be7e03ad7ca38859ca722db922c801b366
child 534026 0c4acb2a543bbbf83c2cacf9df90962674fb8343
push id32547
push userbmo:scwwu@mozilla.com
push dateMon, 17 Oct 2016 11:12:10 +0000
bugs1309471
milestone52.0a1
Bug 1309471 - Add browser chrome tests for time picker MozReview-Commit-ID: 4u2nNGEaEJ
toolkit/content/tests/browser/browser.ini
toolkit/content/tests/browser/browser_datetime_timepicker.js
toolkit/content/widgets/spinner.js
toolkit/content/widgets/timepicker.js
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -17,16 +17,17 @@ support-files =
 [browser_bug451286.js]
 skip-if = !e10s
 [browser_bug594509.js]
 [browser_bug982298.js]
 [browser_bug1198465.js]
 [browser_contentTitle.js]
 [browser_crash_previous_frameloader.js]
 run-if = e10s && crashreporter
+[browser_datetime_timepicker.js]
 [browser_default_image_filename.js]
 [browser_f7_caret_browsing.js]
 [browser_findbar.js]
 [browser_label_textlink.js]
 [browser_isSynthetic.js]
 support-files =
   empty.png
 [browser_keyevents_during_autoscrolling.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_datetime_timepicker.js
@@ -0,0 +1,235 @@
+/* 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 = 7,
+      VIEWPORT_COUNT = 5,
+      DAY_PERIOD_IN_HOURS = 12,
+      HOUR = "#hour",
+      MINUTE = "#minute",
+      DAY_PERIOD = "#day-period",
+      SPINNER = ".spinner",
+      UP = ".up",
+      DOWN = ".down";
+
+function dateTimeTestHelper(pageUrl) {
+  this.pageUrl = pageUrl;
+  this.dateTimePickerPanel = document.getElementById("DateTimePickerPanel");
+  this.dateTimePopupFrame = this.dateTimePickerPanel.dateTimePopupFrame;
+}
+
+dateTimeTestHelper.prototype = {
+  *openTab() {
+    this.tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, this.pageUrl);
+    let popupLoadedPromise = BrowserTestUtils.waitForEvent(this.dateTimePopupFrame, "load", true);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#time-input-box", {}, gBrowser.selectedBrowser);
+    yield popupLoadedPromise;
+    yield BrowserTestUtils.waitForEvent(this.dateTimePopupFrame.contentDocument, "TimePickerReady");
+  },
+  closeTab() {
+    return BrowserTestUtils.removeTab(this.tab);
+  },
+  closePicker() {
+    this.dateTimePickerPanel.closePicker();
+  },
+  getHourStr() {
+    return this.getSpinnerElement(HOUR).textContent;
+  },
+  getMinuteStr() {
+    return this.getSpinnerElement(MINUTE).textContent;
+  },
+  getDayPeriodStr() {
+    return this.getSpinnerElement(DAY_PERIOD).textContent;
+  },
+  getSpinnerElement(id, offset = 0) {
+    let spinner = this.getElement(`${id} ${SPINNER}`);
+    let spinnerCenterIndex = this.getCenterElementIndex(spinner);
+    return spinner.childNodes[spinnerCenterIndex + offset];
+  },
+  getCenterElementIndex(spinner) {
+    return spinner.scrollTop / ITEM_HEIGHT + (VIEWPORT_SIZE - 1) / 2;
+  },
+  getElement(selector) {
+    return this.dateTimePopupFrame.contentDocument.querySelector(selector);
+  },
+  click(element) {
+    EventUtils.synthesizeMouseAtCenter(element, {}, this.dateTimePopupFrame.contentWindow);
+  },
+  wheel(element, offsetX, offsetY, event) {
+    EventUtils.synthesizeWheel(element, offsetX, offsetY, event, this.dateTimePopupFrame.contentWindow);
+  },
+  // drag(element, deltaY) {
+
+  // }
+  clickUpArrow(id) {
+    this.click(this.getElement(`${id} ${UP}`));
+    return this.waitForScrollStop(id);
+  },
+  clickDownArrow(id) {
+    this.click(this.getElement(`${id} ${DOWN}`));
+    return this.waitForScrollStop(id);
+  },
+  waitForScrollStop(id) {
+    return BrowserTestUtils.waitForEvent(this.getElement(`${id} ${SPINNER}`), "ScrollStop");
+  },
+  tearDown() {
+    this.closePicker();
+    this.closeTab();
+  }
+};
+
+function showHourIn12(hour, locale) {
+  // Hour 0 in 12 hour format is displayed as 12.
+  const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
+  return hourIn12 == 0 ? Number(12).toLocaleString(locale)
+    : hourIn12.toLocaleString(locale);
+}
+
+function showMinute(minute, locale) {
+  return Number(minute).toLocaleString(locale);
+}
+
+function showDayPeriod(hour) {
+  // TODO: day period string should have l10n support
+  return hour < DAY_PERIOD_IN_HOURS ? "AM" : "PM";
+}
+
+add_task(function*() {
+  const now = new Date();
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time'>");
+  yield helper.openTab();
+
+  is(helper.getHourStr(), showHourIn12(now.getHours()), "Current hour");
+  is(helper.getMinuteStr(), showMinute(now.getMinutes()), "Current minute");
+  is(helper.getDayPeriodStr(), showDayPeriod(now.getHours()), "Current day period");
+
+  helper.tearDown();
+});
+
+add_task(function*() {
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='00:00'>");
+  yield helper.openTab();
+
+  is(helper.getHourStr(), showHourIn12(0), "Hour should be 12");
+  is(helper.getMinuteStr(), showMinute(0), "Minute should be 0");
+  is(helper.getDayPeriodStr(), showDayPeriod(0), "Day period should be AM");
+
+  helper.tearDown();
+});
+
+add_task(function*() {
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='22:35'>");
+  yield helper.openTab();
+
+  is(helper.getHourStr(), showHourIn12(22), "Hour should be 10");
+  is(helper.getMinuteStr(), showMinute(35), "Minute should be 35");
+  is(helper.getDayPeriodStr(), showDayPeriod(12), "Day period should be PM");
+
+  helper.tearDown();
+});
+
+add_task(function*() {
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='14:35'>");
+  yield helper.openTab();
+
+  // Check initial states
+  is(helper.getHourStr(), showHourIn12(2), "Hour should be 2");
+  is(helper.getMinuteStr(), showMinute(35), "Minute should be 35");
+  is(helper.getDayPeriodStr(), showDayPeriod(12), "Day period should be PM");
+
+  // Click the up arrow for hour once
+  yield helper.clickUpArrow(HOUR);
+  is(helper.getHourStr(), showHourIn12(3), "Hour should be 3");
+
+  // Click the down arrow for hour three times
+  yield helper.clickDownArrow(HOUR);
+  yield helper.clickDownArrow(HOUR);
+  yield helper.clickDownArrow(HOUR);
+  is(helper.getHourStr(), showHourIn12(0), "Hour should be 12");
+
+  // Click the down arrow for hour once to test cycling
+  yield helper.clickDownArrow(HOUR);
+  is(helper.getHourStr(), showHourIn12(11), "Hour should be 11");
+
+  // Click the down arrow for minute once
+  yield helper.clickDownArrow(MINUTE);
+  is(helper.getMinuteStr(), showMinute(34), "Minute should be 34");
+
+  yield helper.tearDown();
+});
+
+add_task(function*() {
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='22:35'>");
+  yield helper.openTab();
+
+  helper.click(helper.getSpinnerElement(HOUR, -1));
+  yield helper.waitForScrollStop(HOUR);
+  is(helper.getHourStr(), showHourIn12(21), "Hour should be 9");
+
+  helper.click(helper.getSpinnerElement(MINUTE, 3));
+  yield helper.waitForScrollStop(MINUTE);
+  is(helper.getMinuteStr(), showMinute(38), "Minute should be 38");
+
+  helper.click(helper.getSpinnerElement(DAY_PERIOD, -1));
+  yield helper.waitForScrollStop(DAY_PERIOD);
+  is(helper.getDayPeriodStr(), showDayPeriod(0), "Day period should be AM");
+
+  yield helper.tearDown();
+});
+
+add_task(function*() {
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='18:00'>");
+  yield helper.openTab();
+
+  let hourSpinner = helper.getSpinnerElement(HOUR);
+
+  helper.wheel(hourSpinner, 10, 10, {
+    deltaY: -200, deltaMode: WheelEvent.DOM_DELTA_PIXEL
+  });
+  yield helper.waitForScrollStop(HOUR);
+  is(helper.getHourStr(), showHourIn12(2), "Hour should be 2");
+
+  helper.wheel(hourSpinner, 10, 10, {
+    deltaY: -200, deltaMode: WheelEvent.DOM_DELTA_PIXEL
+  });
+  yield helper.waitForScrollStop(HOUR);
+  is(helper.getHourStr(), showHourIn12(10), "Hour should be 10");
+
+  helper.wheel(hourSpinner, 10, 10, {
+    deltaY: 100, deltaMode: WheelEvent.DOM_DELTA_PIXEL
+  });
+  yield helper.waitForScrollStop(HOUR);
+  is(helper.getHourStr(), showHourIn12(0), "Hour should be 12");
+
+  yield helper.tearDown();
+});
+
+/*
+add_task(function*() {
+  const helper = new dateTimeTestHelper("data:text/html, <input id='time-input-box' type='time' value='18:00'>");
+  yield helper.openTab();
+
+  let hourSpinner = helper.getElement(`${HOUR} ${SPINNER}`);
+  dump(">>>>> " + hourSpinner.scrollTop);
+  EventUtils.synthesizeMouse(hourSpinner, 1, 1, { type: "mousedown" }, helper.dateTimePopupFrame.contentWindow);
+  dump(">>>>> " + hourSpinner.scrollTop);
+  EventUtils.synthesizeMouse(hourSpinner, 1, 100, { type: "mousemove" }, helper.dateTimePopupFrame.contentWindow);
+  dump(">>>>> " + hourSpinner.scrollTop);
+  EventUtils.synthesizeMouse(hourSpinner, 1, 50, { type: "mouseup" }, helper.dateTimePopupFrame.contentWindow);
+  dump(">>>>> " + hourSpinner.scrollTop);
+  yield helper.waitForScrollStop(HOUR);
+  dump(">>>>> " + hourSpinner.scrollTop);
+  is(helper.getHourStr(), showHourIn12(4), "Hour dragged downward");
+
+  // EventUtils.synthesizeMouse(hourSpinner, 1, 1, { type: "mousedown" }, helper.dateTimePopupFrame.contentWindow);
+  // EventUtils.synthesizeMouse(hourSpinner, 1, 100, { type: "mousemove" }, helper.dateTimePopupFrame.contentWindow);
+  // EventUtils.synthesizeMouse(hourSpinner, 1, 1, { type: "mouseup" }, helper.dateTimePopupFrame.contentWindow);
+  // yield helper.waitForScrollStop(HOUR);
+  // is(helper.getHourStr(), showHourIn12(2), "Hour dragged downward");
+
+
+
+  yield helper.tearDown();
+});
+*/
\ No newline at end of file
--- a/toolkit/content/widgets/spinner.js
+++ b/toolkit/content/widgets/spinner.js
@@ -7,19 +7,19 @@
 /*
  * The spinner is responsible for displaying the items, and does
  * not care what the values represent. The setValue function is called
  * when it detects a change in value triggered by scroll event.
  * Supports scrolling, clicking on up or down, clicking on item, and
  * dragging.
  */
 
-function Spinner(props, context) {
+function Spinner(props, context, id) {
   this.context = context;
-  this._init(props);
+  this._init(props, id);
 };
 
 {
   const debug = 0 ? console.log.bind(console, "[spinner]") : function() {};
 
   const ITEM_HEIGHT = 25,
         VIEWPORT_SIZE = 7,
         VIEWPORT_COUNT = 5,
@@ -36,21 +36,21 @@ function Spinner(props, context) {
      *             the parent component.
      *           {Function} getDisplayString: Takes a value, and output it
      *             as localized strings.
      *           {Number} viewportSize [optional]: Number of items in a
      *             viewport.
      *           {Boolean} hideButtons [optional]: Hide up & down buttons
      *         }
      */
-    _init(props) {
+    _init(props, id) {
       const { setValue, getDisplayString, hideButtons } = props;
-
       const spinnerTemplate = document.getElementById("spinner-template");
       const spinnerElement = document.importNode(spinnerTemplate.content, true);
+      spinnerElement.querySelector(".spinner-container").id = id;
 
       // Make sure viewportSize is an odd number because we want to have the selected
       // item in the center. If it's an even number, use the default size instead.
       const viewportSize = props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;
 
       this.state = {
         items: [],
         isScrolling: false
--- a/toolkit/content/widgets/timepicker.js
+++ b/toolkit/content/widgets/timepicker.js
@@ -105,24 +105,24 @@ function TimePicker(context) {
               return numberFormat(hour);
             } else {
               // Hour 0 in 12 hour format is displayed as 12.
               const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
               return hourIn12 == 0 ? numberFormat(12)
                 : numberFormat(hourIn12);
             }
           }
-        }, this.context),
+        }, this.context, "hour"),
         minute: new Spinner({
           setValue: wrapSetValueFn(value => {
             timeKeeper.setMinute(value);
             this.state.isMinuteSet = true;
           }),
           getDisplayString: minute => numberFormat(minute)
-        }, this.context)
+        }, this.context, "minute")
       };
 
       this._insertLayoutElement({
         tag: "div",
         textContent: ":",
         className: "colon",
         insertBefore: this.components.minute.elements.container
       });
@@ -132,17 +132,17 @@ function TimePicker(context) {
       if (format == "12") {
         this.components.dayPeriod = new Spinner({
           setValue: wrapSetValueFn(value => {
             timeKeeper.setDayPeriod(value);
             this.state.isDayPeriodSet = true;
           }),
           getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM",
           hideButtons: true
-        }, this.context);
+        }, this.context, "day-period");
 
         this._insertLayoutElement({
           tag: "div",
           className: "spacer",
           insertBefore: this.components.dayPeriod.elements.container
         });
       }
     },
@@ -247,16 +247,17 @@ function TimePicker(context) {
     handleMessage(event) {
       switch (event.data.name) {
         case "TimePickerSetValue": {
           this.set(event.data.detail);
           break;
         }
         case "TimePickerInit": {
           this.init(event.data.detail);
+          document.dispatchEvent(new CustomEvent("TimePickerReady"));
           break;
         }
       }
     },
 
     /**
      * Set the time state and update the components with the new state.
      *