Bug 617273 - make editable text tests async, r=marcoz, fer, a=test
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 15 Dec 2010 13:23:19 -0800
changeset 59275 19427aebdace2d7233ff9546894e040e0367fcd1
parent 59274 469dc5e3d5b4044f0f07efc1277cc996023658ff
child 59279 193be5701a2ca196cb4339d1440de1308705a6f2
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersmarcoz, fer, test
bugs617273
milestone2.0b9pre
Bug 617273 - make editable text tests async, r=marcoz, fer, a=test
accessible/tests/mochitest/Makefile.in
accessible/tests/mochitest/common.js
accessible/tests/mochitest/editabletext.js
accessible/tests/mochitest/editabletext/Makefile.in
accessible/tests/mochitest/editabletext/editabletext.js
accessible/tests/mochitest/editabletext/test_1.html
accessible/tests/mochitest/editabletext/test_2.html
accessible/tests/mochitest/events.js
accessible/tests/mochitest/events/test_text.html
accessible/tests/mochitest/test_editabletext_1.html
accessible/tests/mochitest/test_editabletext_2.html
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -40,16 +40,17 @@ DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible
 
 DIRS	= \
   actions \
   attributes \
+  editabletext \
   events \
   hyperlink \
   name \
   relations \
   selectable \
   states \
   table \
   text \
@@ -64,17 +65,16 @@ include $(topsrcdir)/config/rules.mk
 		formimage.png \
 		letters.gif \
 		moz.png \
 		$(topsrcdir)/content/media/test/bug461281.ogg \
 		longdesc_src.html \
 		actions.js \
 		attributes.js \
 		common.js \
-		editabletext.js \
 		events.js \
 		grid.js \
 		layout.js \
 		name.js \
 		nsIAccessible_selects.js \
 		relations.js \
 		role.js \
 		selectable.js \
@@ -86,18 +86,16 @@ include $(topsrcdir)/config/rules.mk
 		test_aria_role_equation.html \
 		test_aria_roles.html \
 		test_aria_roles.xul \
 		test_aria_token_attrs.html \
 		test_bug420863.html \
 	$(warning   test_childAtPoint.html temporarily disabled) \
 	$(warning	test_childAtPoint.xul temporarily disabled) \
 		test_descr.html \
-		test_editabletext_1.html \
-		test_editabletext_2.html \
 		test_elm_landmarks.html \
 		test_elm_listbox.xul \
 	$(warning   test_elm_media.html temporarily disabled) \
 		test_elm_nsApplicationAcc.html \
 		test_elm_plugin.html \
 		test_keys.html \
 	$(warning test_nsIAccessible_comboboxes.xul temporarily disabled) \
  		test_nsIAccessible_selects.html \
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -535,16 +535,46 @@ function eventTypeToString(aEventType)
  * Convert relation type to human readable string.
  */
 function relationTypeToString(aRelationType)
 {
   return gAccRetrieval.getStringRelationType(aRelationType);
 }
 
 /**
+ * Return text from clipboard.
+ */
+function getTextFromClipboard()
+{
+  var clip = Components.classes["@mozilla.org/widget/clipboard;1"].
+    getService(Components.interfaces.nsIClipboard);
+  if (!clip)
+    return;
+
+  var trans = Components.classes["@mozilla.org/widget/transferable;1"].
+    createInstance(Components.interfaces.nsITransferable);
+  if (!trans)
+    return;
+
+  trans.addDataFlavor("text/unicode");
+  clip.getData(trans, clip.kGlobalClipboard);
+
+  var str = new Object();
+  var strLength = new Object();
+  trans.getTransferData("text/unicode", str, strLength);
+
+  if (str)
+    str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
+  if (str)
+    return str.data.substring(0, strLength.value / 2);
+
+  return "";
+}
+
+/**
  * Return pretty name for identifier, it may be ID, DOM node or accessible.
  */
 function prettyName(aIdentifier)
 {
   if (aIdentifier instanceof nsIAccessible) {
     var acc = getAccessible(aIdentifier, [nsIAccessNode]);
     var msg = "[" + getNodePrettyName(acc.DOMNode);
     try {
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/Makefile.in
@@ -0,0 +1,55 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Alexander Surkov <surkov.alexander@gmail.com> (original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = accessible/editabletext
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES =\
+		editabletext.js \
+		test_1.html \
+		test_2.html \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
rename from accessible/tests/mochitest/editabletext.js
rename to accessible/tests/mochitest/editabletext/editabletext.js
--- a/accessible/tests/mochitest/editabletext.js
+++ b/accessible/tests/mochitest/editabletext/editabletext.js
@@ -1,178 +1,310 @@
-function nsEditableText(aElmOrID)
+/**
+ * Perform all editable text tests.
+ */
+function editableTextTestRun()
 {
+  this.add = function add(aTest)
+  {
+    this.seq.push(aTest);
+  }
+
+  this.run = function run()
+  {
+    this.iterate();
+  }
+
+  this.index = 0;
+  this.seq = new Array();
+
+  this.iterate = function iterate()
+  {
+    if (this.index < this.seq.length) {
+      this.seq[this.index++].startTest(this);
+      return;
+    }
+
+    this.seq = null;
+    SimpleTest.finish();
+  }
+}
+
+/**
+ * Used to test nsIEditableTextAccessible methods.
+ */
+function editableTextTest(aID)
+{
+  /**
+   * setTextContents test.
+   */
   this.setTextContents = function setTextContents(aStr)
   {
-    try {
-      this.mAcc.setTextContents(aStr);
-      
-      is(this.getValue(), aStr,
-         "setTextContents: Can't set " + aStr +
-         " to element with ID '" + this.mID + "'");
-    } catch (e) {
-      ok(false,
-         "setTextContents: Can't set " + aStr +
-         "to element with ID '" + this.mID +
-         "', value '" + this.getValue() + "', exception" + e);
+    var testID = "setTextContents '" + aStr + "' for " + prettyName(aID);
+
+    function setTextContentsInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.setTextContents(aStr);
     }
+
+    this.sheduleTest(aID, null, [0, aStr.length, aStr],
+                     setTextContentsInvoke, getValueChecker(aID, aResValue),
+                     testID);
   }
-  
+
+  /**
+   * insertText test.
+   */
   this.insertText = function insertText(aStr, aPos, aResStr)
   {
-    try {
-      this.mAcc.insertText(aStr, aPos);
-      
-      is(this.getValue(), aResStr,
-         "insertText: Can't insert " + aStr + " at " + aPos +
-         " to element with ID '" + this.mID + "'");
-    } catch (e) {
-      ok(false,
-         "insertText: Can't insert " + aStr + " at " + aPos +
-         " to element with ID '" + this.mID +
-         "', value '" + this.getValue() + "', exception " + e);
+    var testID = "insertText '" + aStr + "' at " + aPos + " for " +
+      prettyName(aID);
+
+    function insertTextInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.insertText(aStr, aPos);
     }
+
+    this.scheduleTest(aID, null, [aPos, aPos + aStr.length, aStr],
+                      insertTextInvoke, getValueChecker(aID, aResStr), testID);
   }
 
+  /**
+   * copyText test.
+   */
   this.copyText = function copyText(aStartPos, aEndPos, aClipboardStr)
   {
-    var msg = "copyText from " + aStartPos + " to " + aEndPos +
-      " for element " + prettyName(this.mID) + ": ";
+    var testID = "copyText from " + aStartPos + " to " + aEndPos + " for " +
+      prettyName(aID);
 
-    try {
-      this.mAcc.copyText(aStartPos, aEndPos);
-      is(this.pasteFromClipboard(), aClipboardStr, msg);
+    function copyTextInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.copyText(aStartPos, aEndPos);
+    }
 
-    } catch(e) {
-      ok(false, msg + e);
-    }
+    this.scheduleTest(aID, null, null, copyTextInvoke,
+                      getClipboardChecker(aID, aClipboardStr), testID);
   }
 
+  /**
+   * copyText and pasteText test.
+   */
   this.copyNPasteText = function copyNPasteText(aStartPos, aEndPos,
                                                 aPos, aResStr)
   {
-    try {
-      this.mAcc.copyText(aStartPos, aEndPos);
-      this.mAcc.pasteText(aPos);
-      
-      is(this.getValue(), aResStr,
-         "copyText & pasteText: Can't copy text from " + aStartPos +
-         " to " + aEndPos + " and paste to " + aPos +
-         " for element with ID '" + this.mID + "'");
-    } catch (e) {
-      ok(false,
-         "copyText & pasteText: Can't copy text from " + aStartPos +
-         " to " + aEndPos + " and paste to " + aPos +
-         " for element with ID '" + this.mID +
-         "', value '" + this.getValue() + "', exception " + e);
+    var testID = "copyText from " + aStartPos + " to " + aEndPos +
+      "and pasteText at " + aPos + " for " + prettyName(aID);
+
+    function copyNPasteTextInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.copyText(aStartPos, aEndPos);
+      acc.pasteText(aPos);
     }
+
+    this.scheduleTest(aID, null, [aStartPos, aEndPos, getTextFromClipboard],
+                      copyNPasteInvoke, getValueChecker(aID, aResStr), testID);
   }
 
-  this.cutText = function cutText(aStartPos, aEndPos, aClipboardStr, aResStr)
+  /**
+   * cutText test.
+   */
+  this.cutText = function cutText(aStartPos, aEndPos, aResStr)
   {
-    var msg = "cutText from " + aStartPos + " to " + aEndPos +
-      " for element " + prettyName(this.mID) + ": ";
-
-    try {
-      this.mAcc.cutText(aStartPos, aEndPos);
+    var testID = "cutText from " + aStartPos + " to " + aEndPos + " for " +
+      prettyName(aID);
 
-      is(this.pasteFromClipboard(), aClipboardStr,
-         msg + "wrong clipboard value");
-      is(this.getValue(), aResStr, msg + "wrong control value");
+    function cutTextInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.cutText(aStartPos, aEndPos);
+    }
 
-    } catch(e) {
-      ok(false, msg + e);
-    }
+    this.scheduleTest(aID, [aStartPos, aEndPos, getTextFromClipboard], null,
+                      cutTextInvoke, getValueChecker(aID, aResStr), testID);
   }
 
+  /**
+   * cutText and pasteText test.
+   */
   this.cutNPasteText = function copyNPasteText(aStartPos, aEndPos,
                                                aPos, aResStr)
   {
-    try {
-      this.mAcc.cutText(aStartPos, aEndPos);
-      this.mAcc.pasteText(aPos);
-      
-      is(this.getValue(), aResStr,
-         "cutText & pasteText: Can't cut text from " + aStartPos +
-         " to " + aEndPos + " and paste to " + aPos +
-         " for element with ID '" + this.mID + "'");
-    } catch (e) {
-      ok(false,
-         "cutText & pasteText: Can't cut text from " + aStartPos +
-         " to " + aEndPos + " and paste to " + aPos +
-         " for element with ID '" + this.mID +
-         "', value '" + this.getValue() + "', exception " + e);
+    var testID = "cutText from " + aStartPos + " to " + aEndPos +
+      " and pasteText at " + aPos + " for " + prettyName(aID);
+
+    function cutNPasteTextInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.cutText(aStartPos, aEndPos);
+      acc.pasteText(aPos);
     }
+
+    this.scheduleTest(aID, [aStartPos, aEndPos, getTextFromClipboard],
+                      [aPos, -1, getTextFromClipboard],
+                      cutNPasteTextInvoke, getValueChecker(aID, aResStr),
+                      testID);
   }
 
+  /**
+   * pasteText test.
+   */
   this.pasteText = function pasteText(aPos, aResStr)
   {
-    var msg = "pasteText to " + aPos + " position for element " +
-      prettyName(this.mID) + ": ";
-
-    try {
-      this.mAcc.pasteText(aPos);
+    var testID = "pasteText at " + aPos + " for " + prettyName(aID);
 
-      is(this.getValue(), aResStr, msg + "wrong control value");
+    function pasteTextInvoke()
+    {
+      var acc = getAccessible(aID, nsIAccessibleEditableText);
+      acc.pasteText(aPos);
+    }
 
-    } catch(e) {
-      ok(false, msg + e);
-    }
+    this.scheduleTest(aID, null, [aPos, -1, getTextFromClipboard],
+                      pasteTextInvoke, getValueChecker(aID, aResStr), testID);
   }
 
+  /**
+   * deleteText test.
+   */
   this.deleteText = function deleteText(aStartPos, aEndPos, aResStr)
   {
-    try {
-      this.mAcc.deleteText(aStartPos, aEndPos);
+    function getRemovedText() { return invoker.removedText; }
+
+    var invoker = {
+      eventSeq: [
+        new textChangeChecker(aID, aStartPos, aEndPos, getRemovedText, false)
+      ],
+
+      invoke: function invoke()
+      {
+        var acc = getAccessible(aID,
+                               [nsIAccessibleText, nsIAccessibleEditableText]);
+
+        this.removedText = acc.getText(aStartPos, aEndPos);
 
-      is(this.getValue(), aResStr,
-         "deleteText: Can't delete text from " + aStartPos +
-         " to " + aEndPos + " for element with ID '" + this.mID + "'");
-    } catch (e) {
-      ok(false,
-         "deleteText: Can't delete text from " + aStartPos +
-         " to " + aEndPos + " for element with ID '" + this.mID +
-         "', value " + this.getValue() + ", exception " + e);
-    }
+        acc.deleteText(aStartPos, aEndPos);
+      },
+
+      finalCheck: function finalCheck()
+      {
+        getValueChecker(aID, aResStr).check();
+      },
+
+      getID: function getID()
+      {
+        return "deleteText from " + aStartPos + " to " + aEndPos + " for " +
+          prettyName(aID);
+      }
+    };
+    this.mEventQueue.push(invoker);
   }
 
-  this.getValue = function getValue()
+  //////////////////////////////////////////////////////////////////////////////
+  // Implementation details.
+
+  /**
+   * Common checkers.
+   */
+  function getValueChecker(aID, aValue)
   {
-    if (this.mElm instanceof Components.interfaces.nsIDOMNSEditableElement)
-      return this.mElm.value;
-    if (this.mElm instanceof Components.interfaces.nsIDOMHTMLDocument)
-      return this.mElm.body.textContent;
-    return this.mElm.textContent;
+    var checker = {
+      check: function valueChecker_check()
+      {
+        var value = "";
+        var elm = getNode(aID);
+        if (elm instanceof Components.interfaces.nsIDOMNSEditableElement)
+          value = elm.value;
+        else if (elm instanceof Components.interfaces.nsIDOMHTMLDocument)
+          value = elm.body.textContent;
+        else
+          value = elm.textContent;
+
+        is(value, aValue, "Wrong value " + aValue);
+      }
+    };
+    return checker;
+  }
+
+  function getClipboardChecker(aID, aText)
+  {
+    var checker = {
+      check: function clipboardChecker_check()
+      {
+        is(getTextFromClipboard(), aText, "Wrong text in clipboard.");
+      }
+    };
+    return checker;
   }
 
-  this.pasteFromClipboard = function pasteFromClipboard()
+  function getValueNClipboardChecker(aID, aValue, aText)
   {
-    var clip = Components.classes["@mozilla.org/widget/clipboard;1"].
-      getService(Components.interfaces.nsIClipboard);
-    if (!clip)
-      return;
-
-    var trans = Components.classes["@mozilla.org/widget/transferable;1"].
-    createInstance(Components.interfaces.nsITransferable);
-    if (!trans)
-      return;
+    var valueChecker = getValueChecker(aID, aValue);
+    var clipboardChecker = getClipboardChecker(aID, aText);
 
-    trans.addDataFlavor("text/unicode");
-    clip.getData(trans, clip.kGlobalClipboard);
-
-    var str = new Object();
-    var strLength = new Object();
-    trans.getTransferData("text/unicode", str, strLength);
-
-    if (str)
-      str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
-    if (str)
-      return str.data.substring(0, strLength.value / 2);
-
-    return "";
+    var checker = {
+      check: function()
+      {
+        valueChecker.check();
+        clipboardChecker.check();
+      }
+    };
+    return checker;
   }
 
-  var elmObj = { value: null };
-  this.mAcc = getAccessible(aElmOrID, nsIAccessibleEditableText, elmObj);
+  /**
+   * Create an invoker for the test and push it into event queue.
+   */
+  this.scheduleTest = function scheduleTest(aID, aRemoveTriple, aInsertTriple,
+                                            aInvokeFunc, aChecker, aInvokerID)
+  {
+    var invoker = {
+      eventSeq: [],
+
+      invoke: aInvokeFunc,
+      finalCheck: function finalCheck() { aChecker.check(); },
+      getID: function getID() { return aInvokerID; }
+    };
+
+    if (aRemoveTriple) {
+      var checker = new textChangeChecker(aID, aRemoveTriple[0],
+                                          aRemoveTriple[1], aRemoveTriple[2],
+                                          false);
+      invoker.eventSeq.push(checker);
+    }
+
+    if (aInsertTriple) {
+      var checker = new textChangeChecker(aID, aInsertTriple[0],
+                                          aInsertTriple[1], aInsertTriple[2],
+                                          true);
+      invoker.eventSeq.push(checker);
+    }
 
-  this.mElm = elmObj.value;
-  this.mID = aElmOrID;
+    this.mEventQueue.push(invoker);
+  }
+
+  /**
+   * Run the tests.
+   */
+  this.startTest = function startTest(aTestRun)
+  {
+    var testRunObj = aTestRun;
+    var thisObj = this;
+    this.mEventQueue.onFinish = function finishCallback()
+    {
+      // Notify textRun object that all tests were finished.
+      testRunObj.iterate();
+
+      // Help GC to avoid leaks (refer to aTestRun from local variable, drop
+      // onFinish function).
+      thisObj.mEventQueue.onFinish = null;
+
+      return DO_NOT_FINISH_TEST;
+    }
+
+    this.mEventQueue.invoke();
+  }
+
+  this.mEventQueue = new eventQueue();
 }
+
rename from accessible/tests/mochitest/test_editabletext_1.html
rename to accessible/tests/mochitest/editabletext/test_1.html
--- a/accessible/tests/mochitest/test_editabletext_1.html
+++ b/accessible/tests/mochitest/editabletext/test_1.html
@@ -9,28 +9,26 @@ https://bugzilla.mozilla.org/show_bug.cg
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
   <script type="application/javascript"
           src="editabletext.js"></script>
-  <script type="application/javascript"
-          src="events.js"></script>
 
   <script type="application/javascript">
-    var gParagraphAcc;
-
-    function testEditable(aID)
+    function addTestEditable(aID, aTestRun)
     {
-      var et = new nsEditableText(aID);
+      var et = new editableTextTest(aID);
 
       //////////////////////////////////////////////////////////////////////////
       // insertText
       et.insertText("hello", 0, "hello");
       et.insertText("ma ", 0, "ma hello");
       et.insertText("ma", 2, "mama hello");
       et.insertText(" hello", 10, "mama hello hello");
 
@@ -57,33 +55,38 @@ https://bugzilla.mozilla.org/show_bug.cg
 //      et.copyNPasteText(5, 6, 6, "hhelloo");
 //      et.copyNPasteText(3, 4, 1, "hehelloo");
 
       //////////////////////////////////////////////////////////////////////////
 //      // cutNPasteText
 //      et.cutNPasteText(0, 1, 1, "ehhelloo");
 //      et.cutNPasteText(1, 2, 0, "hehelloo");
 //      et.cutNPasteText(7, 8, 8, "hehelloo");
+
+      aTestRun.add(et);
     }
 
-    function testDocEditableNFinishTest(aDoc)
+    function runTest()
     {
-      testEditable(aDoc);
-      SimpleTest.finish();
+      var testRun = new editableTextTestRun();
+
+      addTestEditable("input", testRun);
+      // addTestEditable("div"); XXX: bug 452599
+      addTestEditable(getNode("frame").contentDocument, testRun);
+
+      testRun.run(); // Will call SimpleTest.finish();
     }
 
     function doTest()
     {
-      testEditable("input");
-      // testEditable("div"); XXX: bug 452599
+      // Prepare tested elements.
 
       // Design mode on/off trigger document accessible subtree recreation.
       var frame = getNode("frame");
-      waitForEvent(EVENT_REORDER, frame.contentDocument,
-                   testDocEditableNFinishTest, null, frame.contentDocument);
+      waitForEvent(EVENT_REORDER, frame.contentDocument, runTest);
       frame.contentDocument.designMode = "on";
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
rename from accessible/tests/mochitest/test_editabletext_2.html
rename to accessible/tests/mochitest/editabletext/test_2.html
--- a/accessible/tests/mochitest/test_editabletext_2.html
+++ b/accessible/tests/mochitest/editabletext/test_2.html
@@ -1,38 +1,41 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>nsIAccessibleEditableText chrome tests</title>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
-          src="common.js"></script>
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
   <script type="application/javascript"
           src="editabletext.js"></script>
 
   <script type="application/javascript">
-    var gParagraphAcc;
-
     function doTest()
     {
-      var et = new nsEditableText("input");
+      var et = new editableTextTest("input");
 
       et.insertText("ee", 1, "heeello");
       et.copyText(1, 3, "ee");
-      et.cutText(1, 3, "ee", "hello");
+      et.cutText(1, 3, "hello");
       et.deleteText(1, 3, "hlo");
       et.pasteText(1, "heelo");
 
-      SimpleTest.finish();
+      var testRun = new editableTextTestRun();
+      testRun.add(et);
+      testRun.run(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
 
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -894,16 +894,41 @@ function invokerChecker(aEventType, aTar
     this.mTarget = aValue;
     return this.mTarget;
   }
 
   this.mTarget = aTargetOrFunc;
   this.mTargetFuncArg = aTargetFuncArg;
 }
 
+/**
+ * Text inserted/removed events checker.
+ */
+function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted)
+{
+  this.target = getNode(aID);
+  this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+
+  this.check = function textChangeChecker_check(aEvent)
+  {
+    aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
+
+    var modifiedText = (typeof aTextOrFunc == "function") ?
+      aTextOrFunc() : aTextOrFunc;
+    var modifiedTextLen = (aEnd == -1) ? modifiedText.length : aEnd - aStart;
+
+    is(aEvent.start, aStart, "Wrong start offset for " + prettyName(aID));
+    is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
+    var changeInfo = (aIsInserted ? "inserted" : "removed");
+    is(aEvent.isInserted(), aIsInserted,
+       "Text was " + changeInfo + " for " + prettyName(aID));
+    is(aEvent.modifiedText, modifiedText,
+       "Wrong " + changeInfo + " text for " + prettyName(aID));
+  }
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // Private implementation details.
 ////////////////////////////////////////////////////////////////////////////////
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // General
--- a/accessible/tests/mochitest/events/test_text.html
+++ b/accessible/tests/mochitest/events/test_text.html
@@ -20,34 +20,16 @@
 
   <script type="application/javascript">
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
     /**
      * Base text remove invoker and checker.
      */
-    function textChangeChecker(aID, aStart, aEnd, aText, aIsInserted)
-    {
-      this.target = getNode(aID);
-      this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
-
-      this.check = function textRemoveChecker_check(aEvent)
-      {
-        aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
-        is(aEvent.start, aStart, "Wrong start offset for " + prettyName(aID));
-        is(aEvent.length, aEnd - aStart, "Wrong length for " + prettyName(aID));
-        var changeInfo = (aIsInserted ? "inserted" : "removed");
-        is(aEvent.isInserted(), aIsInserted,
-           "Text was " + changeInfo + " for " + prettyName(aID));
-        is(aEvent.modifiedText, aText,
-           "Wrong " + changeInfo + " text for " + prettyName(aID));
-      }
-    }
-
     function textRemoveInvoker(aID, aStart, aEnd, aText)
     {
       this.DOMNode = getNode(aID);
 
       this.eventSeq = [
         new textChangeChecker(aID, aStart, aEnd, aText, false)
       ];
     }