Bug 980074 - Base Tests for text selection, r=wesj
☠☠ backed out by 17fd295f094d ☠ ☠
authorMark Capella <markcapella@twcny.rr.com>
Fri, 25 Apr 2014 17:15:50 -0400
changeset 180810 ff8ebfcf68ffa8b786f27ee1fb380027d941dc39
parent 180809 8ac8082f80298cba62a601a819016ce12c0533d1
child 180811 09d2cda1bb7394e71f5cced549fd4e9ad341d1f2
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewerswesj
bugs980074
milestone31.0a1
Bug 980074 - Base Tests for text selection, r=wesj
mobile/android/base/EventDispatcher.java
mobile/android/base/tests/StringHelper.java
mobile/android/base/tests/robocop.ini
mobile/android/base/tests/roboextender/testSelectionHandler.html
mobile/android/base/tests/testSelectionHandler.java
--- a/mobile/android/base/EventDispatcher.java
+++ b/mobile/android/base/EventDispatcher.java
@@ -1,16 +1,17 @@
 /* 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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -203,16 +204,17 @@ public final class EventDispatcher {
             for (final GeckoEventListener listener : listeners) {
                 listener.handleMessage(type, message);
             }
         } catch (final JSONException e) {
             Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
         }
     }
 
+    @RobocopTarget
     @Deprecated
     public static void sendResponse(JSONObject message, Object response) {
         sendResponseHelper(STATUS_SUCCESS, message, response);
     }
 
     @Deprecated
     public static void sendError(JSONObject message, Object response) {
         sendResponseHelper(STATUS_ERROR, message, response);
--- a/mobile/android/base/tests/StringHelper.java
+++ b/mobile/android/base/tests/StringHelper.java
@@ -87,16 +87,17 @@ public class StringHelper {
     public static final String ROBOCOP_BOXES_TITLE = "Browser Box test";
     public static final String ROBOCOP_GEOLOCATION_TITLE = "Geolocation Test Page";
     public static final String ROBOCOP_LOGIN_TITLE = "Robocop Login";
     public static final String ROBOCOP_OFFLINE_STORAGE_TITLE = "Robocop offline storage";
     public static final String ROBOCOP_PICTURE_LINK_TITLE = "Picture Link";
     public static final String ROBOCOP_SEARCH_TITLE = "Robocop Search Engine";
     public static final String ROBOCOP_TEXT_PAGE_TITLE = "Robocop Text Page";
     public static final String ROBOCOP_INPUT_TITLE = "Robocop Input";
+    public static final String ROBOCOP_SELECTION_HANDLER_TITLE = "Automated Text Selection tests for Mobile";
 
     // Settings menu strings
     // Section labels - ordered as found in the settings menu
     public static final String CUSTOMIZE_SECTION_LABEL = "Customize";
     public static final String DISPLAY_SECTION_LABEL = "Display";
     public static final String PRIVACY_SECTION_LABEL = "Privacy";
     public static final String MOZILLA_SECTION_LABEL = "Mozilla";
     public static final String DEVELOPER_TOOLS_SECTION_LABEL = "Developer tools";
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -76,16 +76,17 @@ skip-if = processor == "x86"
 [testPasswordProvider]
 # [testPermissions] # see bug 757475
 [testPictureLinkContextMenu]
 # disabled on 2.3; bug 986164
 skip-if = android_version == "10"
 [testPrefsObserver]
 [testPrivateBrowsing]
 [testPromptGridInput]
+[testSelectionHandler]
 # disabled on x86 only; bug 957185
 skip-if = processor == "x86"
 # [testReaderMode] # see bug 913254, 936224
 [testReadingListProvider]
 [testSearchSuggestions]
 # disabled on x86 and 2.3; bug 907768, 979548
 skip-if = android_version == "10" || processor == "x86"
 [testSessionOOMSave]
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/roboextender/testSelectionHandler.html
@@ -0,0 +1,301 @@
+<html>
+  <head>
+    <title>Automated Text Selection tests for Mobile</title>
+    <meta name="viewport" content="initial-scale=1.0"/>
+    <script type="application/javascript"
+      src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+    <script type="application/javascript">
+
+const DIV_POINT_TEXT = "Under";
+const INPUT_TEXT = "Text for select all in an <input>";
+const TEXTAREA_TEXT = "Text for select all in a <textarea>";
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+Cu.import("resource://gre/modules/Messaging.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+/* =================================================================================
+ *
+ * Start of all text selection tests, check initialization state.
+ *
+ */
+function startTests() {
+  testSelectAllDivs().
+    then(testSelectDivAtPoint).
+    then(testSelectInput).
+    then(testSelectTextarea).
+    then(testCloseSelection).
+    then(finishTests, function(err) {
+      ok(false, "Error in selection test " + err);
+      finishTests();
+    });
+}
+
+/* =================================================================================
+ *
+ * "Select all" text selection tests, for <div> (non-editable) fields.
+ *
+ */
+function testSelectAllDivs() {
+  var sh = getSelectionHandler();
+  var selDiv = document.getElementById("selDiv");
+  var nonSelDiv = document.getElementById("nonSelDiv");
+
+  // Check the initial state of the selection handler, and selectable/non-selectable <div>s.
+  return Promise.all([
+    ok(!sh.isSelectionActive(), "Selection should not be active at start of testSelectAllDivs"),
+    ok(sh.canSelect(selDiv), "Can select selectable <div>"),
+    ok(!sh.canSelect(nonSelDiv), "Can't select non-selectable <div>"),
+
+  ]).then(function() {
+    // Select all on a non-editable text node selects all the text in the page.
+    sh.startSelection(selDiv);
+    var selection = sh._getSelection();
+
+    return Promise.all([
+      ok(sh.isSelectionActive(), "Selection should be active now"),
+      is(selection.anchorNode, document.documentElement, "Anchor Node should be start of document"),
+      is(selection.anchorOffset, 0, "Anchor offset should be 0"),
+      is(selection.focusNode, document.body.lastChild, "Focus node should be lastChild of document"),
+      is(selection.focusOffset, document.body.lastChild.textContent.length, "Focus offset should be it's length"),
+    ]);
+  });
+}
+
+/* =================================================================================
+ *
+ * "Select word-at-point" text selection test, for <div> (non-editable) field.
+ * "collapseToStart" test closes selection (Bug 864589).
+ *
+ */
+function testSelectDivAtPoint() {
+  var sh = getSelectionHandler();
+  var selDiv = document.getElementById("selDiv");
+
+  // Select word at point in <div>
+  var rect = selDiv.getBoundingClientRect();
+  sh.startSelection(selDiv, {
+    mode: sh.SELECT_AT_POINT,
+    x: rect.left + 1,
+    y: rect.top + 1
+  });
+  var selection = sh._getSelection();
+
+  // Check the state of the selection handler after selecting at a point.
+  return Promise.all([
+    ok(sh.isSelectionActive(), "Selection should be active at start of testSelectDivAtPoint"),
+    is(selection.toString(), DIV_POINT_TEXT, "The first word in the <div> was selected"),
+
+  ]).then(function() {
+    // Check the state of the selection handler after collapsing a selection.
+    selection.collapseToStart();
+
+    return Promise.all([
+      ok(selection.getRangeAt(0).collapsed, "Selection should be collapsed"),
+      ok(!sh.isSelectionActive(), "Selection should not be active"),
+    ]);
+  });
+}
+
+/* =================================================================================
+ *
+ * "Select all" text selection test, for <input> (editable) field.
+ *
+ */
+function testSelectInput() {
+  var sh = getSelectionHandler();
+  var inputNode = document.getElementById("inputNode");
+  inputNode.value = INPUT_TEXT;
+
+  // Test that calling startSelection with an input selects all the text in the input.
+  return Promise.all([
+    ok(!sh.isSelectionActive(), "Selection should not be active at start of testSelectInput"),
+    ok(sh.canSelect(inputNode), "Can select selectable <input>"),
+
+  ]).then(function() {
+    // Check the state of the selection handler after calling startSelection on it.
+    sh.startSelection(inputNode);
+    var selection = sh._getSelection();
+
+    return Promise.all([
+      ok(sh.isSelectionActive(), "Selection should be active"),
+      ok((sh._targetElement instanceof Ci.nsIDOMNSEditableElement), "Selected element is editable"),
+      is(selection.toString(), INPUT_TEXT, "All text in the <input> was selected"),
+    ]);
+  });
+}
+
+/* =================================================================================
+ *
+ * "Select all" text selection test, for <textarea> (editable) field.
+ *
+ */
+
+function testSelectTextarea() {
+  var sh = getSelectionHandler();
+  var textareaNode = document.getElementById("textareaNode");
+  textareaNode.value = TEXTAREA_TEXT;
+
+  // Change (still-active) selection from previous <input> field to <textarea>
+  sh.startSelection(textareaNode);
+  var selection = sh._getSelection();
+
+  return Promise.all([
+    ok(sh.isSelectionActive(), "Selection should be active at start of testSelectTextarea"),
+    ok((sh._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement), "Selected element is editable, and a <textarea>"),
+    is(selection.toString(), TEXTAREA_TEXT, "All text in the <textarea> was selected"),
+
+  ]).then(function() {
+    // Collpase the selection to close it again.
+    selection.collapseToStart();
+
+    return Promise.all([
+      ok(!sh.isSelectionActive(), "Selection should not be active"),
+    ]);
+  });
+}
+
+/* =================================================================================
+ *
+ * Various text selection tests to end active selections, including:
+ *   1.) Clicking outside the selection.
+ *   2.) SelectionEnd or Tab:Selected messages from java.
+ *
+ */
+function testCloseSelection() {
+  var sh = getSelectionHandler();
+  var inputNode = document.getElementById("inputNode");
+  inputNode.value = INPUT_TEXT;
+
+  // Check the initial state of the selection handler.
+  return Promise.all([
+    ok(!sh.isSelectionActive(), "Selection should not be active at start of testCloseSelection"),
+
+  ]).then(function() {
+    // Start by selecting all in an <input>.
+    sh.startSelection(inputNode);
+    return is(sh._activeType, sh.TYPE_SELECTION, "Selection should be active in <input> before Gesture:SingleTap");
+
+  }).then(function() {
+    // Tap outside <input> to close active selection.
+    sh.observe(null, "Gesture:SingleTap", JSON.stringify({
+      x: 1,
+      y: 1
+    }));
+    return ok(!sh.isSelectionActive(), "Gesture:SingleTap outside <input> should close active selection");
+
+  // Various other ways to close an active selection.
+  }).then(function() {
+    sh.startSelection(inputNode);
+    sh.observe(null, "TextSelection:End", {});
+    return ok(!sh.isSelectionActive(), "TextSelection:End should close active selection");
+
+  }).then(function() {
+    sh.startSelection(inputNode);
+    sh.observe(null, "Tab:Selected", {});
+    return ok(!sh.isSelectionActive(), "Tab:Selected should close active selection");
+
+  }).then(function() {
+    sh.startSelection(inputNode);
+    sh.handleEvent({ type: "pagehide" });
+    return ok(!sh.isSelectionActive(), "pagehide should close active selection");
+
+  }).then(function() {
+    sh.startSelection(inputNode);
+    sh.handleEvent({ type: "blur" });
+    return ok(!sh.isSelectionActive(), "blur should close active selection");
+  });
+}
+
+/* =================================================================================
+ *
+ * After finish of all selection tests, wrap up and go home.
+ *
+ */
+function finishTests() {
+  sendMessageToJava({
+    type: "Robocop:testSelectionHandler",
+    result: true,
+    msg: "Done!",
+    done: true
+  });
+}
+
+/* ============================== Utility functions ======================
+ *
+ * Common functions available to all tests.
+ *
+ */
+function getSelectionHandler() {
+  return (!this._selectionHandler) ?
+    this._selectionHandler = Services.wm.getMostRecentWindow("navigator:browser").SelectionHandler :
+    this._selectionHandler;
+}
+
+function ok(one, msg) {
+  return new Promise(function(resolve, reject) {
+    sendMessageToJava({
+      type: "Robocop:testSelectionHandler",
+      result: one,
+      msg: msg
+    },
+    function (res, err) {
+      (err) ? reject(err) : resolve(res);
+    });
+  });
+}
+
+function is(one, two, msg) {
+  return new Promise(function(resolve, reject) {
+    sendMessageToJava({
+      type: "Robocop:testSelectionHandler",
+      result: one === two,
+      msg: msg + " : " + one + " === " + two
+    },
+    function (res, err) {
+      (err) ? reject(err) : resolve(res);
+    });
+  });
+}
+
+/* =================================================================================
+ *
+ * Page definition for all tests.
+ *
+ */
+    </script>
+  </head>
+
+  <body onload="startTests();">
+
+    <div id="selDiv">Under sufficiently extreme conditions, quarks may become
+      deconfined and exist as free particles. In the course of asymptotic freedom,
+      the strong interaction becomes weaker at higher temperatures. Eventually,
+      color confinement would be lost and an extremely hot plasma of freely moving
+      quarks and gluons would be formed. This theoretical phase of matter is called
+      quark-gluon plasma.[81] The exact conditions needed to give rise to this state
+      are unknown and have been the subject of a great deal of speculation and
+      experimentation. A recent estimate puts the needed temperature at
+      (1.90±0.02)×1012 Kelvin. While a state of entirely free quarks and gluons has
+      never been achieved (despite numerous attempts by CERN in the 1980s and 1990s),
+      recent experiments at the Relativistic Heavy Ion Collider have yielded evidence
+      for liquid-like quark matter exhibiting "nearly perfect" fluid motion.</div><br>
+
+    <div id="nonSelDiv" style="-moz-user-select: none;">Lorem ipsum dolor sit amet,
+      consectetur adipiscing elit. Proin in blandit magna, non porttitor augue.
+      Nam in neque sagittis, varius augue at, ornare velit. Vestibulum eget nisl
+      congue odio molestie scelerisque. Pellentesque ut augue orci. In hac habitasse
+      platea dictumst. Sed placerat tellus quis lacus condimentum, quis luctus elit
+      pellentesque. Mauris cursus neque diam, sit amet gravida quam porta ac.
+      Aliquam aliquam feugiat vestibulum. Proin commodo nulla ligula, in bibendum
+      massa euismod a. Ut ac lobortis dui. Ut id augue id arcu ornare suscipit eu
+      ornare lorem. Pellentesque nec dictum ante. Nam quis ligula ultricies, auctor
+      nunc vel, fringilla turpis. Nulla lacinia, leo ut egestas hendrerit, risus
+      ligula interdum enim, vel varius libero sem ut ligula.</div><br>
+
+    <input id="inputNode" type="text"><br>
+
+    <textarea id="textareaNode"></textarea><br>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testSelectionHandler.java
@@ -0,0 +1,46 @@
+package org.mozilla.gecko.tests;
+
+import org.mozilla.gecko.Actions;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.tests.helpers.GeckoHelper;
+import org.mozilla.gecko.tests.helpers.NavigationHelper;
+
+import android.util.Log;
+
+import org.json.JSONObject;
+
+
+public class testSelectionHandler extends UITest {
+
+    public void testSelectionHandler() {
+        GeckoHelper.blockForReady();
+
+        Actions.EventExpecter robocopTestExpecter = getActions().expectGeckoEvent("Robocop:testSelectionHandler");
+        NavigationHelper.enterAndLoadUrl("chrome://roboextender/content/testSelectionHandler.html");
+        mToolbar.assertTitle(StringHelper.ROBOCOP_SELECTION_HANDLER_TITLE);
+
+        while (!test(robocopTestExpecter)) {
+            // do nothing
+        }
+
+        robocopTestExpecter.unregisterListener();
+    }
+
+    private boolean test(Actions.EventExpecter expecter) {
+        final JSONObject eventData;
+        try {
+            eventData = new JSONObject(expecter.blockForEventData());
+        } catch(Exception ex) {
+            // Log and ignore
+            getAsserter().ok(false, "JS Test", "Error decoding data " + ex);
+            return false;
+        }
+
+        if (eventData.has("result")) {
+            getAsserter().ok(eventData.optBoolean("result"), "JS Test", eventData.optString("msg"));
+        }
+
+        EventDispatcher.sendResponse(eventData, new JSONObject());
+        return eventData.optBoolean("done", false);
+    }
+}