Bug 976082 - added tests for Touch Adapter. r=eeejay
authorYura Zenevich <yzenevich@mozilla.com>
Mon, 10 Mar 2014 23:33:58 -0400
changeset 190100 a7882947aea9cfc56831e1dd059a9a7d8bd1413c
parent 190099 73d02445dc1cbee93fdb4cd153c29ee466bc1d55
child 190101 b5645f7981c3b8f58b70df55c1551f2761aae2f3
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerseeejay
bugs976082, 100644
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 976082 - added tests for Touch Adapter. r=eeejay --- accessible/tests/mochitest/jsat/a11y.ini | 3 + accessible/tests/mochitest/jsat/dom_helper.js | 157 +++++++++++++++++ accessible/tests/mochitest/jsat/gestures.json | 194 +++++++++++++++++++++ accessible/tests/mochitest/jsat/jsatcommon.js | 15 +- .../tests/mochitest/jsat/test_touch_adapter.html | 66 +++++++ 5 files changed, 430 insertions(+), 5 deletions(-) create mode 100644 accessible/tests/mochitest/jsat/dom_helper.js create mode 100644 accessible/tests/mochitest/jsat/gestures.json create mode 100644 accessible/tests/mochitest/jsat/test_touch_adapter.html
accessible/tests/mochitest/jsat/a11y.ini
accessible/tests/mochitest/jsat/dom_helper.js
accessible/tests/mochitest/jsat/gestures.json
accessible/tests/mochitest/jsat/jsatcommon.js
accessible/tests/mochitest/jsat/test_touch_adapter.html
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -1,15 +1,18 @@
 [DEFAULT]
 support-files =
+  dom_helper.js
+  gestures.json
   jsatcommon.js
   output.js
   doc_traversal.html
   doc_content_integration.html
 
 [test_alive.html]
 [test_content_integration.html]
 [test_explicit_names.html]
 [test_landmarks.html]
 [test_live_regions.html]
 [test_output.html]
 [test_tables.html]
+[test_touch_adapter.html]
 [test_traversal.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/dom_helper.js
@@ -0,0 +1,157 @@
+'use strict';
+
+/*global getMainChromeWindow, getBoundsForDOMElm, AccessFuTest, Point*/
+/* exported loadJSON*/
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+Cu.import('resource://gre/modules/Geometry.jsm');
+
+var win = getMainChromeWindow(window);
+
+var winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
+  Ci.nsIDOMWindowUtils);
+
+/**
+ * For a given list of points calculate their coordinates in relation to the
+ * document body.
+ * @param  {Array} aTouchPoints An array of objects of the following format: {
+ *   base: {String}, // Id of an element to server as a base for the touch.
+ *   x: {Number},    // An optional x offset from the base element's geometric
+ *                   // centre.
+ *   y: {Number}     // An optional y offset from the base element's geometric
+ *                   // centre.
+ * }
+ * @return {JSON} An array of {x, y} coordinations.
+ */
+function calculateTouchListCoordinates(aTouchPoints) {
+  var coords = [];
+  var dpi = winUtils.displayDPI;
+  for (var i = 0, target = aTouchPoints[i]; i < aTouchPoints.length; ++i) {
+    var bounds = getBoundsForDOMElm(target.base);
+    var parentBounds = getBoundsForDOMElm('root');
+    var point = new Point(target.x || 0, target.y || 0);
+    point.scale(dpi);
+    point.add(bounds[0], bounds[1]);
+    point.add(bounds[2] / 2, bounds[3] / 2);
+    point.subtract(parentBounds[0], parentBounds[0]);
+    coords.push({
+      x: point.x,
+      y: point.y
+    });
+  }
+  return coords;
+}
+
+/**
+ * Send a touch event with specified touchPoints.
+ * @param  {Array} aTouchPoints An array of points to be associated with
+ * touches.
+ * @param  {String} aName A name of the touch event.
+ */
+function sendTouchEvent(aTouchPoints, aName) {
+  var touchList = sendTouchEvent.touchList;
+  if (aName === 'touchend') {
+    sendTouchEvent.touchList = null;
+  } else {
+    var coords = calculateTouchListCoordinates(aTouchPoints);
+    var touches = [];
+    for (var i = 0; i < coords.length; ++i) {
+      var {x, y} = coords[i];
+      var node = document.elementFromPoint(x, y);
+      var touch = document.createTouch(window, node, aName === 'touchstart' ?
+        1 : touchList.item(i).identifier, x, y, x, y);
+      touches.push(touch);
+    }
+    touchList = document.createTouchList(touches);
+    sendTouchEvent.touchList = touchList;
+  }
+  var evt = document.createEvent('TouchEvent');
+  evt.initTouchEvent(aName, true, true, window, 0, false, false, false, false,
+    touchList, touchList, touchList);
+  document.dispatchEvent(evt);
+}
+
+sendTouchEvent.touchList = null;
+
+/**
+ * A map of event names to the functions that actually send them.
+ * @type {Object}
+ */
+var eventMap = {
+  touchstart: sendTouchEvent,
+  touchend: sendTouchEvent,
+  touchmove: sendTouchEvent
+};
+
+/**
+ * Attach a listener for the mozAccessFuGesture event that tests its
+ * type.
+ * @param  {Array} aExpectedGestures A stack of expected event types.
+ * Note: the listener is removed once the stack reaches 0.
+ */
+function testMozAccessFuGesture(aExpectedGestures) {
+  var types = typeof aExpectedGestures === "string" ?
+    [aExpectedGestures] : aExpectedGestures;
+  function handleGesture(aEvent) {
+    if (aEvent.detail.type !== types[0]) {
+      // The is not the event of interest.
+      return;
+    }
+    ok(true, 'Received correct mozAccessFuGesture: ' + types.shift() + '.');
+    if (types.length === 0) {
+      win.removeEventListener('mozAccessFuGesture', handleGesture);
+      if (AccessFuTest.sequenceCleanup) {
+        AccessFuTest.sequenceCleanup();
+      }
+      AccessFuTest.nextTest();
+    }
+  }
+  win.addEventListener('mozAccessFuGesture', handleGesture);
+}
+
+/**
+ * An extention to AccessFuTest that adds an ability to test a sequence of
+ * touch/mouse/etc events and their expected mozAccessFuGesture events.
+ * @param {Object} aSequence An object that has a list of touch/mouse/etc events
+ * to be generated and the expected mozAccessFuGesture events.
+ */
+AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) {
+  AccessFuTest.addFunc(function testSequence() {
+    testMozAccessFuGesture(aSequence.expectedGestures);
+    var events = aSequence.events;
+    function fireEvent(aEvent) {
+      eventMap[aEvent.type](aEvent.target, aEvent.type);
+      processEvents();
+    }
+    function processEvents() {
+      if (events.length === 0) {
+        return;
+      }
+      var event = events.shift();
+      if (event.delay) {
+        window.setTimeout(fireEvent, event.delay, event);
+      } else {
+        fireEvent(event);
+      }
+    }
+    processEvents();
+  });
+};
+
+/**
+ * A helper function that loads JSON files.
+ * @param {String} aPath A path to a JSON file.
+ * @param {Function} aCallback A callback to be called on success.
+ */
+function loadJSON(aPath, aCallback) {
+  var request = new XMLHttpRequest();
+  request.open('GET', aPath, true);
+  request.responseType = 'json';
+  request.onload = function onload() {
+    aCallback(request.response);
+  };
+  request.send();
+}
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/gestures.json
@@ -0,0 +1,194 @@
+[
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 50}
+    ],
+    "expectedGestures": "tap"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": 0.03, "y": 0.02}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "tap"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 525}
+    ],
+    "expectedGestures": ["dwell", "dwellend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": 0.03, "y": 0.02}], "delay": 25},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": -0.03, "y": 0.01}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["tap", "doubletap"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["tap", "doubletap", "tripletap"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 525}
+    ],
+    "expectedGestures": ["tap", "doubletap", "doubletaphold", "dwellend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 525}
+    ],
+    "expectedGestures": ["tap", "taphold", "dwellend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0.5, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeleft"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0.5}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipedown"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeup"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0.1}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0.5, "y": 0.1}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": -0.05}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeleft"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": -0.1, "y": 0.5}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipedown"
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0.1, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeup"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0},
+        {"base": "button", "x": 0, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0},
+        {"base": "button", "x": 0.5, "y": 0.5}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0},
+        {"base": "button", "x": 0, "y": 0.5},
+        {"base": "button", "x": 0, "y": 1}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0},
+        {"base": "button", "x": 0.5, "y": 0.5},
+        {"base": "button", "x": 0.5, "y": 1}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0.6, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 525},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["dwell", "explore", "exploreend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 525},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0}],
+        "delay": 125},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["dwell", "explore", "explore", "exploreend"]
+  }
+]
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -1,10 +1,15 @@
 // A common module to run tests on the AccessFu module
 
+'use strict';
+
+/*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger,
+  AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/
+
 /**
   * A global variable holding an array of test functions.
   */
 var gTestFuncs = [];
 /**
   * A global Iterator for the array of test functions.
   */
 var gIterator;
@@ -112,22 +117,22 @@ var AccessFuTest = {
       return;
     }
     testFunc();
   },
 
   runTests: function AccessFuTest_runTests() {
     if (gTestFuncs.length === 0) {
       ok(false, "No tests specified!");
-      simpleTest.finish();
+      SimpleTest.finish();
       return;
     }
 
     // Create an Iterator for gTestFuncs array.
-    gIterator = Iterator(gTestFuncs);
+    gIterator = Iterator(gTestFuncs); // jshint ignore:line
 
     // Start AccessFu and put it in stand-by.
     Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm");
 
     AccessFu.attach(getMainChromeWindow(window));
 
     AccessFu.readyCallback = function readyCallback() {
       // Enable logging to the console service.
@@ -139,17 +144,17 @@ var AccessFuTest = {
       'set': [['accessibility.accessfu.notify_output', 1],
               ['dom.mozSettings.enabled', true]]
     }, function () {
       if (AccessFuTest._waitForExplicitFinish) {
         // Run all test functions asynchronously.
         AccessFuTest.nextTest();
       } else {
         // Run all test functions synchronously.
-        [testFunc() for (testFunc of gTestFuncs)];
+        [testFunc() for (testFunc of gTestFuncs)]; // jshint ignore:line
         AccessFuTest.finish();
       }
     });
   }
 };
 
 function AccessFuContentTest(aFuncResultPairs) {
   this.queue = aFuncResultPairs;
@@ -194,17 +199,17 @@ AccessFuContentTest.prototype = {
           } else {
             elem.focus();
           }
         }
       });
     }
 
     aMessageManager.addMessageListener('AccessFu:Present', this);
-    aMessageManager.addMessageListener('AccessFu:Ready', function (aMessage) {
+    aMessageManager.addMessageListener('AccessFu:Ready', function () {
       aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback);
       aMessageManager.sendAsyncMessage('AccessFu:Start', { buildApp: 'browser' });
     });
 
     aMessageManager.loadFrameScript(
       'chrome://global/content/accessibility/content-script.js', false);
     aMessageManager.loadFrameScript(
       'data:,(' + contentScript.toString() + ')();', false);
@@ -221,17 +226,17 @@ AccessFuContentTest.prototype = {
          this.currentPair[0].json);
       }
 
       if (!this.currentPair[1]) {
        this.pump();
      }
     } else if (this.finishedCallback) {
       for (var mm of this.mms) {
-       mm.sendAsyncMessage('AccessFu:Stop');
+        mm.sendAsyncMessage('AccessFu:Stop');
       }
       this.finishedCallback();
     }
   },
 
   receiveMessage: function(aMessage) {
     if (!this.currentPair) {
       return;
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_touch_adapter.html
@@ -0,0 +1,66 @@
+<html>
+
+<head>
+  <title>AccessFu tests for touch adapters.</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="../common.js"></script>
+  <script type="application/javascript" src="../layout.js"></script>
+  <script type="application/javascript" src="./jsatcommon.js"></script>
+  <script type="application/javascript" src="./dom_helper.js"></script>
+  <script type="application/javascript">
+
+    Components.utils.import(
+      "resource://gre/modules/accessibility/TouchAdapter.jsm");
+
+    function startComponents() {
+      TouchAdapter.start();
+      AccessFuTest.nextTest();
+    }
+
+    /**
+     * Clean up all state data related to previous events/gestures.
+     */
+    function cleanupTouchAdapter() {
+      TouchAdapter.cleanupTouches();
+      TouchAdapter._touchPoints = {};
+      TouchAdapter._dwellTimeout = 0;
+      TouchAdapter._prevGestures = {};
+      TouchAdapter._lastExploreTime = 0;
+    }
+
+    function stopComponents() {
+      TouchAdapter.stop();
+      AccessFuTest.finish();
+    }
+
+    function doTest() {
+      loadJSON("./gestures.json", function onSuccess(gestures) {
+        AccessFuTest.addFunc(startComponents);
+        AccessFuTest.sequenceCleanup = cleanupTouchAdapter;
+        gestures.forEach(AccessFuTest.addSequence);
+        AccessFuTest.addFunc(stopComponents);
+        AccessFuTest.waitForExplicitFinish();
+        AccessFuTest.runTests();
+      });
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+
+</head>
+<body id="root">
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=976082"
+     title="[AccessFu] Provide tests for touch adapter.">
+    Mozilla Bug 976082
+  </a>
+  <div>
+    <button id="button">I am a button</button>
+  </div>
+</body>
+</html>