Mozdev
experimental/edit-template/test/PendingEventsTest.js
author Alex Vincent <ajvincent@gmail.com>
Sun Feb 07 15:29:04 2010 -0800 (5 months ago)
changeset 88 7a6be990317b
permissions -rw-r--r--
Implement support for <markup:children/> elements, from initial import to showing the imported contents to the user, to final export back to the data document.
     1 /* ***** BEGIN LICENSE BLOCK *****
     2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
     3  *
     4  * The contents of this file are subject to the Mozilla Public License Version
     5  * 1.1 (the "License"); you may not use this file except in compliance with
     6  * the License. You may obtain a copy of the License at
     7  * http://www.mozilla.org/MPL/
     8  *
     9  * Software distributed under the License is distributed on an "AS IS" basis,
    10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
    11  * for the specific language governing rights and limitations under the
    12  * License.
    13  *
    14  * The Original Code is Pending Events Test Harness.
    15  *
    16  * The Initial Developer of the Original Code is
    17  * Alexander J. Vincent <ajvincent@gmail.com>.
    18  * Portions created by the Initial Developer are Copyright (C) 2010
    19  * the Initial Developer. All Rights Reserved.
    20  *
    21  * Contributor(s):
    22  *
    23  * Alternatively, the contents of this file may be used under the terms of
    24  * either the GNU General Public License Version 2 or later (the "GPL"), or
    25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    26  * in which case the provisions of the GPL or the LGPL are applicable instead
    27  * of those above. If you wish to allow use of your version of this file only
    28  * under the terms of either the GPL or the LGPL, and not to allow others to
    29  * use your version of this file under the terms of the MPL, indicate your
    30  * decision by deleting the provisions above and replace them with the notice
    31  * and other provisions required by the GPL or the LGPL. If you do not delete
    32  * the provisions above, a recipient may use your version of this file under
    33  * the terms of any one of the MPL, the GPL or the LGPL.
    34  *
    35  * ***** END LICENSE BLOCK ***** */
    36 
    37 /*
    38 This object exists to manage tests which depend on asynchronous events firing
    39 before the test completes.  For example, if you wish to test properties of a XBL
    40 binding on an element, but the element hasn't been inserted into a live user
    41 document, you would want to pause the test and allow the binding to attach.  You
    42 would also want to specify a "resume ordinary testing" point.
    43 
    44 This test harness accepts test parts as individual functions.  Add a test
    45 function by calling PendingEventsTest.addTest(yourFunction).  At all exit points
    46 for a test, you should call PendingEventsTest.asyncRun().  To start the test
    47 sequence, call PendingEventsTest.run().
    48 
    49 To block one test function from running because you're waiting for a DOM event,
    50 call PendingEventsTest.addEventLock.  Tests will not proceed until the right
    51 DOM event arrives.  If the right DOM event does not arrive in the time you
    52 specify, the test will fail.
    53 
    54 To transfer local variables from one test function to another, use the
    55 setPayload(), getPayload() and clearPayload() methods.
    56 
    57 You can add functions which execute after the overall test script finishes
    58 (with failure or success) using the executeAfterRun() method.
    59 
    60 Finally, to abort a test run with a failure (and prevent other asynchronous test
    61 functions from running), use the abortFail() method.  Once this method is called,
    62 the test script is effectively done.  The abortFail() method will also trigger
    63 for exceptions not caught in the test script.
    64 */
    65 
    66 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    67 SimpleTest.waitForExplicitFinish();
    68 
    69 const PendingEventsTest = {
    70   _tests: [],
    71   _locks: [],
    72   _payload: null,
    73   _aborted: false,
    74   _postRunCallbacks: [],
    75 
    76   _endTests: function() {
    77     for each (var callback in this._postRunCallbacks)
    78     {
    79       try {
    80         callback();
    81       } catch (e) {
    82         ok(false, e);
    83       }
    84     }
    85 
    86     SimpleTest.finish();
    87     window.onerror = gOldOnError;
    88   },
    89 
    90   /**
    91    * Prevent the test from proceeding until an event has been received.
    92    *
    93    * @param node      The DOM node expecting an event.
    94    * @param eventName The name of the event.
    95    * @param capturing True if the event should be in the capturing phase.
    96    * @param timeLimit The number of milliseconds to wait for the event.  If not
    97    *                  received in that time, the test has failed.
    98    * @param filter    (optional) A JS function which filters events passed to
    99    *                  the event listener.  The function takes the event as its
   100    *                  first argument.  If the function returns false, the lock
   101    *                  ignores the event.
   102    * @param subtest   (optional) A custom test to run in handleEvent.  The
   103    *                  function takes the event as its first argument.
   104    */
   105   addEventLock: function(node, eventName, capturing, timeLimit, filter, subtest)
   106   {
   107     if (!(node instanceof C_i.nsIDOMNode) ||
   108         (typeof eventName != "string") ||
   109         (typeof capturing != "boolean") ||
   110         (typeof timeLimit != "number") ||
   111         (filter && (typeof filter != "function")) ||
   112         (subtest && (typeof subtest != "function")))
   113     {
   114       throw new Components.Exception("Event lock argument is invalid",
   115                                      Components.results.NS_ERROR_ILLEGAL_VALUE);
   116     }
   117     if (isNaN(timeLimit) ||
   118         (timeLimit <= 0) ||
   119         (timeLimit == Infinity))
   120     {
   121       throw new Components.Exception("timeLimit is an invalid value",
   122                                      Components.results.NS_ERROR_ILLEGAL_VALUE);
   123     }
   124 
   125     const lock = {
   126       timeout: function()
   127       {
   128         if (PendingEventsTest._aborted)
   129           return;
   130         PendingEventsTest.abortFail("Expected " + eventName + " event, timed out");
   131       },
   132 
   133       // nsIDOMEventListener
   134       handleEvent: function(event)
   135       {
   136         if (PendingEventsTest._aborted)
   137           return;
   138 
   139         // Does the event apply to this lock?
   140         try {
   141           if (filter && !filter(event))
   142             return;
   143         }
   144         catch (e) {
   145           PendingEventsTest.abortFail("exception caught in lock filter: " + e);
   146           return;
   147         }
   148 
   149         window.clearTimeout(this._timer);
   150         event.currentTarget.removeEventListener(eventName, this, capturing);
   151 
   152         try {
   153           if (subtest)
   154           {
   155             subtest();
   156           }
   157         }
   158         catch (e) {
   159           PendingEventsTest.abortFail("exception caught in subtest: " + e);
   160           if (typeof e.stack == "string")
   161           {
   162             dump(e.stack + "\n\n");
   163           }
   164           throw e;
   165         }
   166 
   167         var index = PendingEventsTest._locks.indexOf(this);
   168         PendingEventsTest._locks.splice(index, 1);
   169         PendingEventsTest.run();
   170       },
   171 
   172       // nsISupports
   173       QueryInterface:
   174       XPCOMUtils.generateQI(["nsIDOMEventListener"])
   175     };
   176 
   177     PendingEventsTest._locks.push(lock);
   178     lock._timer = window.setTimeout(function() {
   179       lock.timeout();
   180     }, timeLimit);
   181     node.addEventListener(eventName, lock, capturing);
   182   },
   183 
   184   /**
   185    * Add a test to the execution sequence.
   186    */
   187   addTest: function(aTest)
   188   {
   189     this._tests.push(aTest);
   190   },
   191 
   192   /**
   193    * Abort the test run and log a failure.
   194    *
   195    * @param msg The failure message to log.
   196    */
   197   abortFail: function(msg) {
   198     this._aborted = true;
   199     ok(false, msg);
   200     this._endTests();
   201   },
   202 
   203   /**
   204    * Get an object which a previous test stored, after checking its name.
   205    *
   206    * @param nameCheck The name to match against the previous payload store.
   207    *
   208    * @returns The object the user cached.
   209    */
   210   getPayload: function(nameCheck)
   211   {
   212     if (!this._payload)
   213     {
   214       throw new Components.Exception(
   215         "No payload!",
   216         Components.results.NS_ERROR_NOT_INITIALIZED
   217       );
   218     }
   219 
   220     if (nameCheck != this._payload.name)
   221     {
   222       throw new Components.Exception(
   223         "Payload name didn't match: " + this._payload.name,
   224         Components.results.NS_ERROR_UNEXPECTED
   225       )
   226     }
   227 
   228     return this._payload.data;
   229   },
   230 
   231   /**
   232    * Store an object for a later test to pick up.
   233    *
   234    * @param name A value to confirm the right parties get the right payload.
   235    * @param data The object to store.
   236    */
   237   setPayload: function(name, data)
   238   {
   239     if (this._payload)
   240     {
   241       throw new Components.Exception(
   242         "We already have a payload!",
   243         Components.results.NS_ERROR_ALREADY_INITIALIZED
   244       );
   245     }
   246 
   247     this._payload = {
   248       name: name,
   249       data: data
   250     };
   251   },
   252 
   253   /**
   254    * Clear the stored object, after checking its name.
   255    *
   256    * @param nameCheck The name to match against the previous payload store.
   257    */
   258   clearPayload: function(nameCheck)
   259   {
   260     if (!this._payload)
   261     {
   262       throw new Components.Exception(
   263         "No payload!",
   264         Components.results.NS_ERROR_NOT_INITIALIZED
   265       );
   266     }
   267 
   268     if (nameCheck != this._payload.name)
   269     {
   270       throw new Components.Exception(
   271         "Payload name didn't match: " + this._payload.name,
   272         Components.results.NS_ERROR_UNEXPECTED
   273       )
   274     }
   275 
   276     this._payload = null;
   277   },
   278 
   279   /**
   280    * Run the next test, if there are no locks and we haven't aborted.
   281    */
   282   run: function PendingEventsTest_run()
   283   {
   284     if ((this._locks.length > 0) || (this._aborted))
   285     {
   286       return;
   287     }
   288 
   289     if (this._tests.length > 0)
   290     {
   291       var test = this._tests.shift();
   292       try {
   293         test();
   294       } catch (e) {
   295         this.abortFail(e);
   296       }
   297       return;
   298     }
   299 
   300     this._endTests();
   301   },
   302 
   303   /**
   304    * Run the next test asynchronously, so as to allow currently executing
   305    * functions to exit cleanly (and not show up on the stack).
   306    */
   307   asyncRun: function() {
   308     var delay = (arguments.length > 0) ? arguments[0] : 0;
   309     if (isNaN(delay) || (delay < 0) || (delay == Infinity))
   310     {
   311       throw new Components.Exception(
   312         "PendingEventsTest.asyncRun called with bad delay",
   313         Components.results.NS_ERROR_ILLEGAL_VALUE
   314       );
   315     }
   316     window.setTimeout("PendingEventsTest.run()", delay);
   317   },
   318 
   319   /**
   320    * Execute a function after the test run finishes (successfully or not).
   321    */
   322   executeAfterRun: function(callback)
   323   {
   324     if (typeof callback != "function")
   325       throw new Components.Exception("executeAfterRun expects a function",
   326                                      Components.results.NS_ERROR_ILLEGAL_VALUE);
   327     this._postRunCallbacks.push(callback);
   328   }
   329 };