Skip to content
Browse files

start of project, still lots left to do.

  • Loading branch information...
0 parents commit 3c7cb7973cd814fa53fee2181ad03dbb399cc889 @jrburke committed
1 .gitignore
@@ -0,0 +1 @@
+demo-build
47 README.md
@@ -0,0 +1,47 @@
+# require-hm
+
+A simulation of some APIs that are proposed for ECMAScript Harmony for
+JavaScript modules, but done as a loader plugin that works with
+[RequireJS](http://requirejs.org), and other AMD loaders that support
+the [loader plugin API](http://requirejs.org/docs/plugins.html) supported by
+RequireJS.
+
+The APIs are taken from here:
+
+* [harmony:modules](http://wiki.ecmascript.org/doku.php?id=harmony:modules)
+* [harmony:module_loaders](http://wiki.ecmascript.org/doku.php?id=harmony:module_loaders)
+* [harmony:modules_examples](http://wiki.ecmascript.org/doku.php?id=harmony:modules_examples)
+
+Not all the APIs are supported, see further below for more details.
+
+## Goals
+
+The goal is to allow using harmony-like modules today, that work in today's
+browsers and in Node. This allows playing with the APIs to make sure
+they get some use and understanding before baking them into a standard.
+
+It is also a way for me to experiment with the API and suggest changes in a way
+that holds together in real code.
+
+## Limitations
+
+The loader plugin just uses some regular expressions, and
+it relies on existing JavaScript engines, which cannot do the fancy compilation
+and linking that native support can do.
+
+This means some things that would be early errors in a native implementation are
+not early errors with this approach, and there are probably some parsing edge
+cases that fail with this approach vs. native support.
+
+It is possible to take this code and go further with some AST tools like
+UglifyJS, and this code may expand for that purpose, but for now, the regexp
+approach allows a quicker proof of concept.
+
+## Supported APIs
+
+TODO
+module math {} NOT supported
+
+string resolution: .js and mod/name
+Mention .hm for the files that are harmony files.
+Inclusion and exclusion lists?
15 demo/build.js
@@ -0,0 +1,15 @@
+({
+ appDir: '.',
+ baseUrl: 'lib',
+ //Uncomment to turn off uglify minification.
+ //optimize: 'none',
+ dir: '../demo-build',
+ paths: {
+ cs: '../../cs'
+ },
+ modules: [
+ {
+ name: "main"
+ }
+ ]
+})
4 demo/build.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+rm -rf ../demo-build
+node ../tools/r.js -o build.js
10 demo/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>CoffeeScript Loader Plugin Demo</title>
+ <script data-main="lib/main" src="lib/require.js"></script>
+</head>
+<body>
+ <h1>CoffeeScript Loader Plugin Demo</h1>
+</body>
+</html>
7 demo/lib/controller.coffee
@@ -0,0 +1,7 @@
+
+define {
+ attach: (view) ->
+ # Just a simple demonstration of some modules cooperating.
+ require.ready () ->
+ view.render document.getElementsByTagName('body')[0]
+}
3 demo/lib/csmain.coffee
@@ -0,0 +1,3 @@
+
+define ['cs!controller', 'cs!view'], (controller, view) ->
+ controller.attach(view)
5 demo/lib/main.js
@@ -0,0 +1,5 @@
+require({
+ paths: {
+ cs: '../../cs'
+ }
+}, ['cs!csmain']);
1,998 demo/lib/require.js
1,998 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
9 demo/lib/util.coffee
@@ -0,0 +1,9 @@
+
+define {
+ toDom: (text) ->
+ # This is a contrived example, this approach is not realistic,
+ # just used as a demonstration.
+ node = document.createElement('div')
+ node.innerHTML = text
+ return node.firstChild
+}
6 demo/lib/view.coffee
@@ -0,0 +1,6 @@
+
+define ['cs!util'], (util) ->
+ return {
+ render: (body) ->
+ body.appendChild util.toDom('<b>This is a rendered view</b>')
+ }
146 hm.js
@@ -0,0 +1,146 @@
+/**
+ * @license hm 0.1.0 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/require-hm for details
+ */
+
+/*jslint strict: false, plusplus: false */
+/*global require: false, XMLHttpRequest: false, ActiveXObject: false,
+ define: false, process: false, window: false */
+
+(function () {
+
+ var fs, getXhr,
+ progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
+ fetchText = function () {
+ throw new Error('Environment unsupported.');
+ },
+ buildMap = [];
+
+ if (typeof window !== "undefined" && window.navigator && window.document) {
+ // Browser action
+ getXhr = function () {
+ //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
+ var xhr, i, progId;
+ if (typeof XMLHttpRequest !== "undefined") {
+ return new XMLHttpRequest();
+ } else {
+ for (i = 0; i < 3; i++) {
+ progId = progIds[i];
+ try {
+ xhr = new ActiveXObject(progId);
+ } catch (e) {}
+
+ if (xhr) {
+ progIds = [progId]; // so faster next time
+ break;
+ }
+ }
+ }
+
+ if (!xhr) {
+ throw new Error("getXhr(): XMLHttpRequest not available");
+ }
+
+ return xhr;
+ };
+
+ fetchText = function (url, callback) {
+ var xhr = getXhr();
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = function (evt) {
+ //Do not explicitly handle errors, those should be
+ //visible via console output in the browser.
+ if (xhr.readyState === 4) {
+ callback(xhr.responseText);
+ }
+ };
+ xhr.send(null);
+ };
+ } else if (typeof process !== "undefined" &&
+ process.versions &&
+ !!process.versions.node) {
+ //Using special require.nodeRequire, something added by r.js.
+ fs = require.nodeRequire('fs');
+ fetchText = function (path, callback) {
+ callback(fs.readFileSync(path, 'utf8'));
+ };
+ }
+
+ exportFunctionRegExp = /export\s+function\s+([A-Za-z\d\_]+)/g;
+ exportVarRegExp = /export\s+var\s+([A-Za-z\d\_]+)/g;
+
+ function compile(text, config) {
+
+ //export function foo() - > exports.foo = function
+ text = text.replace(exportFunctionRegExp, function (match, funcName) {
+ return 'exports.' + funcName + ' = function ' + funcName;
+ });
+
+ //export var foo -> exports.foo
+ text = text.replace(exportVarRegExp, function (match, varName) {
+ return 'exports.' + varName;
+ });
+
+ //?? export x (not with var or named function) means setting export
+ //value for whole module?
+
+ //import { draw: drawShape } from shape -> var shape = require(';
+
+ //import { messageBox, confirmDialog } from widgets.alert
+
+
+ //module file from 'io/File';
+
+ //HMM:
+ //import * from math;
+
+ return "define(['exports'], function (exports) {\n" +
+ text +
+ '\n});';
+ }
+
+ define({
+ version: '0.1.0',
+
+ load: function (name, require, load, config) {
+ var path = require.toUrl(name + '.hm');
+ fetchText(path, function (text) {
+
+ //Do initial transforms.
+ text = compile(text, config.hm);
+
+ //Hold on to the transformed text if a build.
+ if (config.isBuild) {
+ buildMap[name] = text;
+ }
+
+ //IE with conditional comments on cannot handle the
+ //sourceURL trick, so skip it if enabled.
+ /*@if (@_jscript) @else @*/
+ if (!config.isBuild) {
+ text += "\r\n//@ sourceURL=" + path;
+ }
+ /*@end@*/
+
+ load.fromText(name, text);
+
+ //Give result to load. Need to wait until the module
+ //is fully parsed, which will happen after this
+ //execution.
+ require([name], function (value) {
+ load(value);
+ });
+ });
+
+ },
+
+ write: function (pluginName, name, write) {
+ if (name in buildMap) {
+ var text = buildMap[name];
+ write.asModule(pluginName + "!" + name, text);
+ }
+ }
+ });
+
+}());
53 tests/all-server.js
@@ -0,0 +1,53 @@
+/**
+ * @license RequireJS Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/requirejs for details
+ */
+
+/**
+ * Use the r.js script to run these tests. Be sure require.js is updated
+ * in that script.
+ */
+
+/*jslint strict: false, evil: true */
+/*global Packages: false, process: false, require: true, define: true, doh: false */
+
+//A hack to doh to avoid dojo setup stuff in doh/runner.js
+var skipDohSetup = true,
+ fs, vm, load, env;
+ //requirejsVars = {};
+
+(function () {
+ if (typeof Packages !== 'undefined') {
+ env = 'rhino';
+ } else if (typeof process !== 'undefined') {
+ env = 'node';
+
+ fs = require('fs');
+ vm = require('vm');
+
+ load = function (path) {
+ return vm.runInThisContext(require.makeNodeWrapper(fs.readFileSync(path, 'utf8'), path));
+ };
+
+
+ }
+
+}());
+
+//Load the tests.
+load("doh/runner.js");
+load('doh/_' + env + 'Runner.js');
+load("simple-tests.js");
+load("circular-tests.js");
+load("relative/relative-tests.js");
+load("exports/exports-tests.js");
+load("exports/moduleAndExports-tests.js");
+load("anon/anon-tests.js");
+load("packages/packages-tests.js");
+load("plugins/sync-tests.js");
+load("plugins/fromText/fromText-tests.js");
+load("defineError/defineError-tests.js");
+
+//Print out the final report
+doh.run();
1 tests/all.js
@@ -0,0 +1 @@
+doh.registerUrl("export", "../export/export.html");
1 tests/allj.sh
@@ -0,0 +1 @@
+java -classpath ../../r.js/lib/rhino/js.jar:../../r.js/lib/closure.compiler.jar org.mozilla.javascript.tools.shell.Main ../../r.js/r.js all-server.js
1 tests/alln.sh
@@ -0,0 +1 @@
+node ../../r.js/r.js all-server.js
195 tests/doh/LICENSE
@@ -0,0 +1,195 @@
+Dojo is available under *either* the terms of the modified BSD license *or* the
+Academic Free License version 2.1. As a recipient of Dojo, you may choose which
+license to receive this code under (except as noted in per-module LICENSE
+files). Some modules may not be the copyright of the Dojo Foundation. These
+modules contain explicit declarations of copyright in both the LICENSE files in
+the directories in which they reside and in the code itself. No external
+contributions are allowed under licenses which are fundamentally incompatible
+with the AFL or BSD licenses that Dojo is distributed under.
+
+The text of the AFL and BSD licenses is reproduced below.
+
+-------------------------------------------------------------------------------
+The "New" BSD License:
+**********************
+
+Copyright (c) 2005-2009, The Dojo Foundation
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the Dojo Foundation nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-------------------------------------------------------------------------------
+The Academic Free License, v. 2.1:
+**********************************
+
+This Academic Free License (the "License") applies to any original work of
+authorship (the "Original Work") whose owner (the "Licensor") has placed the
+following notice immediately following the copyright notice for the Original
+Work:
+
+Licensed under the Academic Free License version 2.1
+
+1) Grant of Copyright License. Licensor hereby grants You a world-wide,
+royalty-free, non-exclusive, perpetual, sublicenseable license to do the
+following:
+
+a) to reproduce the Original Work in copies;
+
+b) to prepare derivative works ("Derivative Works") based upon the Original
+Work;
+
+c) to distribute copies of the Original Work and Derivative Works to the
+public;
+
+d) to perform the Original Work publicly; and
+
+e) to display the Original Work publicly.
+
+2) Grant of Patent License. Licensor hereby grants You a world-wide,
+royalty-free, non-exclusive, perpetual, sublicenseable license, under patent
+claims owned or controlled by the Licensor that are embodied in the Original
+Work as furnished by the Licensor, to make, use, sell and offer for sale the
+Original Work and Derivative Works.
+
+3) Grant of Source Code License. The term "Source Code" means the preferred
+form of the Original Work for making modifications to it and all available
+documentation describing how to modify the Original Work. Licensor hereby
+agrees to provide a machine-readable copy of the Source Code of the Original
+Work along with each copy of the Original Work that Licensor distributes.
+Licensor reserves the right to satisfy this obligation by placing a
+machine-readable copy of the Source Code in an information repository
+reasonably calculated to permit inexpensive and convenient access by You for as
+long as Licensor continues to distribute the Original Work, and by publishing
+the address of that information repository in a notice immediately following
+the copyright notice that applies to the Original Work.
+
+4) Exclusions From License Grant. Neither the names of Licensor, nor the names
+of any contributors to the Original Work, nor any of their trademarks or
+service marks, may be used to endorse or promote products derived from this
+Original Work without express prior written permission of the Licensor. Nothing
+in this License shall be deemed to grant any rights to trademarks, copyrights,
+patents, trade secrets or any other intellectual property of Licensor except as
+expressly stated herein. No patent license is granted to make, use, sell or
+offer to sell embodiments of any patent claims other than the licensed claims
+defined in Section 2. No right is granted to the trademarks of Licensor even if
+such marks are included in the Original Work. Nothing in this License shall be
+interpreted to prohibit Licensor from licensing under different terms from this
+License any Original Work that Licensor otherwise would have a right to
+license.
+
+5) This section intentionally omitted.
+
+6) Attribution Rights. You must retain, in the Source Code of any Derivative
+Works that You create, all copyright, patent or trademark notices from the
+Source Code of the Original Work, as well as any notices of licensing and any
+descriptive text identified therein as an "Attribution Notice." You must cause
+the Source Code for any Derivative Works that You create to carry a prominent
+Attribution Notice reasonably calculated to inform recipients that You have
+modified the Original Work.
+
+7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that
+the copyright in and to the Original Work and the patent rights granted herein
+by Licensor are owned by the Licensor or are sublicensed to You under the terms
+of this License with the permission of the contributor(s) of those copyrights
+and patent rights. Except as expressly stated in the immediately proceeding
+sentence, the Original Work is provided under this License on an "AS IS" BASIS
+and WITHOUT WARRANTY, either express or implied, including, without limitation,
+the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU.
+This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No
+license to Original Work is granted hereunder except under this disclaimer.
+
+8) Limitation of Liability. Under no circumstances and under no legal theory,
+whether in tort (including negligence), contract, or otherwise, shall the
+Licensor be liable to any person for any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License
+or the use of the Original Work including, without limitation, damages for loss
+of goodwill, work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses. This limitation of liability shall not
+apply to liability for death or personal injury resulting from Licensor's
+negligence to the extent applicable law prohibits such limitation. Some
+jurisdictions do not allow the exclusion or limitation of incidental or
+consequential damages, so this exclusion and limitation may not apply to You.
+
+9) Acceptance and Termination. If You distribute copies of the Original Work or
+a Derivative Work, You must make a reasonable effort under the circumstances to
+obtain the express assent of recipients to the terms of this License. Nothing
+else but this License (or another written agreement between Licensor and You)
+grants You permission to create Derivative Works based upon the Original Work
+or to exercise any of the rights granted in Section 1 herein, and any attempt
+to do so except under the terms of this License (or another written agreement
+between Licensor and You) is expressly prohibited by U.S. copyright law, the
+equivalent laws of other countries, and by international treaty. Therefore, by
+exercising any of the rights granted to You in Section 1 herein, You indicate
+Your acceptance of this License and all of its terms and conditions.
+
+10) Termination for Patent Action. This License shall terminate automatically
+and You may no longer exercise any of the rights granted to You by this License
+as of the date You commence an action, including a cross-claim or counterclaim,
+against Licensor or any licensee alleging that the Original Work infringes a
+patent. This termination provision shall not apply for an action alleging
+patent infringement by combinations of the Original Work with other software or
+hardware.
+
+11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this
+License may be brought only in the courts of a jurisdiction wherein the
+Licensor resides or in which Licensor conducts its primary business, and under
+the laws of that jurisdiction excluding its conflict-of-law provisions. The
+application of the United Nations Convention on Contracts for the International
+Sale of Goods is expressly excluded. Any use of the Original Work outside the
+scope of this License or after its termination shall be subject to the
+requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et
+seq., the equivalent laws of other countries, and international treaty. This
+section shall survive the termination of this License.
+
+12) Attorneys Fees. In any action to enforce the terms of this License or
+seeking damages relating thereto, the prevailing party shall be entitled to
+recover its costs and expenses, including, without limitation, reasonable
+attorneys' fees and costs incurred in connection with such action, including
+any appeal of such action. This section shall survive the termination of this
+License.
+
+13) Miscellaneous. This License represents the complete agreement concerning
+the subject matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent necessary to
+make it enforceable.
+
+14) Definition of "You" in This License. "You" throughout this License, whether
+in upper or lower case, means an individual or a legal entity exercising rights
+under, and complying with all of the terms of, this License. For legal
+entities, "You" includes any entity that controls, is controlled by, or is
+under common control with you. For purposes of this definition, "control" means
+(i) the power, direct or indirect, to cause the direction or management of such
+entity, whether by contract or otherwise, or (ii) ownership of fifty percent
+(50%) or more of the outstanding shares, or (iii) beneficial ownership of such
+entity.
+
+15) Right to Use. You may use the Original Work in all ways not otherwise
+restricted or conditioned by this License or by law, and Licensor promises not
+to interfere with or be responsible for such uses by You.
+
+This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved.
+Permission is hereby granted to copy and distribute this license without
+modification. This license may not be modified without the express written
+permission of its copyright owner.
12 tests/doh/README
@@ -0,0 +1,12 @@
+DOH may be run standalone by issuing a command like the following:
+
+java -jar ../shrinksafe/js.jar runner.js testModule=tests.colors
+
+where the testModule argument is optional and shrinksafe/js.jar is just a
+convenient copy of the Rhino JavaScript engine -- the custom patch is not
+required.
+
+Optional arguments include:
+ * dojoUrl - specifies the location of dojo.js
+ * testUrl - specifies a Javascript file to load with initialization code
+ * testModule - specifies a test module in the dojo package namespace
855 tests/doh/_browserRunner.js
@@ -0,0 +1,855 @@
+if(window["dojo"]){
+ dojo.provide("doh._browserRunner");
+}
+
+// FIXME: need to add prompting for monkey-do testing
+
+(function(){
+
+ doh.setTimeout = function (fn, time) {
+ return setTimeout(fn, time);
+ };
+
+ try{
+ var topdog = (window.parent == window) || !Boolean(window.parent.doh);
+ }catch(e){
+ //can't access window.parent.doh, then consider ourselves as topdog
+ topdog=true;
+ }
+ if(topdog){
+ // we're the top-dog window.
+
+ // borrowed from Dojo, etc.
+ var byId = function(id){
+ return document.getElementById(id);
+ };
+
+ var _addOnEvt = function( type, // string
+ refOrName, // function or string
+ scope){ // object, defaults is window
+
+ if(!scope){ scope = window; }
+
+ var funcRef = refOrName;
+ if(typeof refOrName == "string"){
+ funcRef = scope[refOrName];
+ }
+ var enclosedFunc = function(){ return funcRef.apply(scope, arguments); };
+
+ if((window["dojo"])&&(type == "load")){
+ dojo.addOnLoad(enclosedFunc);
+ }else{
+ if(window["attachEvent"]){
+ window.attachEvent("on"+type, enclosedFunc);
+ }else if(window["addEventListener"]){
+ window.addEventListener(type, enclosedFunc, false);
+ }else if(document["addEventListener"]){
+ document.addEventListener(type, enclosedFunc, false);
+ }
+ }
+ };
+
+ //
+ // Over-ride or implement base runner.js-provided methods
+ //
+ var escapeXml = function(str){
+ //summary:
+ // Adds escape sequences for special characters in XML: &<>"'
+ // Optionally skips escapes for single quotes
+ return str.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;"); // string
+ };
+
+ var _logBacklog = [], _loggedMsgLen = 0;
+ var sendToLogPane = function(args, skip){
+ var msg = "";
+ for(var x=0; x<args.length; x++){
+ msg += " "+args[x];
+ }
+
+ msg = escapeXml(msg);
+
+ // workarounds for IE. Wheeee!!!
+ msg = msg.replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;")
+ .replace(" ", "&nbsp;")
+ .replace("\n", "<br>&nbsp;");
+ if(!byId("logBody")){
+ _logBacklog.push(msg);
+ return;
+ }else if(_logBacklog.length && !skip){
+ var tm;
+ while((tm=_logBacklog.shift())){
+ sendToLogPane(tm, true);
+ }
+ }
+ var logBody=byId("logBody");
+ var tn = document.createElement("div");
+ tn.innerHTML = msg;
+ //tn.id="logmsg_"+logBody.childNodes.length;
+ logBody.appendChild(tn);
+ _loggedMsgLen++;
+ }
+
+ var findTarget = function(n){
+ while(n && !n.getAttribute('_target')){
+ n=n.parentNode;
+ if(!n.getAttribute){
+ n=null;
+ }
+ }
+ return n;
+ }
+
+ doh._jumpToLog = function(e){
+ //console.log(e);
+
+ var node = findTarget(e?e.target:window.event.srcElement);
+ if(!node){
+ return;
+ }
+ var _t = Number(node.getAttribute('_target'));
+ var lb = byId("logBody");
+ if(_t>=lb.childNodes.length){
+ return;
+ }
+ var t = lb.childNodes[_t];
+ t.scrollIntoView();
+ if(window.dojo){
+ //t.parentNode.parentNode is <div class="tabBody">, only it has a explicitly set background-color,
+ //all children of it are transparent
+ var bgColor = dojo.style(t.parentNode.parentNode,'backgroundColor');
+ //node.parentNode is the tr which has background-color set explicitly
+ var hicolor = dojo.style(node.parentNode,'backgroundColor');
+ var unhilight = dojo.animateProperty({
+ node: t,
+ duration: 500,
+ properties:
+ {
+ backgroundColor: { start:hicolor, end: bgColor }
+ },
+ onEnd: function(){
+ t.style.backgroundColor="";
+ }
+ });
+ var hilight = dojo.animateProperty({
+ node: t,
+ duration: 500,
+ properties:
+ {
+ backgroundColor: { start:bgColor, end: hicolor }
+ },
+ onEnd: function(){
+ unhilight.play();
+ }
+ });
+ hilight.play();
+ }
+ };
+
+ doh._jumpToSuite = function(e){
+ var node = findTarget(e ? e.target : window.event.srcElement);
+ if(!node){
+ return;
+ }
+ var _g = node.getAttribute('_target');
+ var gn = getGroupNode(_g);
+ if(!gn){
+ return;
+ }
+ gn.scrollIntoView();
+ };
+
+ doh._init = (function(oi){
+ return function(){
+ var lb = byId("logBody");
+ if(lb){
+ // clear the console before each run
+ while(lb.firstChild){
+ lb.removeChild(lb.firstChild);
+ }
+ _loggedMsgLen = 0;
+ }
+ this._totalTime = 0;
+ this._suiteCount = 0;
+ oi.apply(doh, arguments);
+ }
+ })(doh._init);
+
+ doh._setupGroupForRun = (function(os){
+ //overload _setupGroupForRun to record which log line to jump to when a suite is clicked
+ return function(groupName){
+ var tg = doh._groups[groupName];
+ doh._curTestCount = tg.length;
+ doh._curGroupCount = 1;
+ var gn = getGroupNode(groupName);
+ if(gn){
+ //two lines will be added, scroll the second line into view
+ gn.getElementsByTagName("td")[2].setAttribute('_target',_loggedMsgLen+1);
+ }
+ os.apply(doh,arguments);
+ }
+ })(doh._setupGroupForRun);
+
+ doh._report = (function(or){
+ //overload _report to insert a tfoot
+ return function(){
+ var tb = byId("testList");
+ if(tb){
+ var tfoots=tb.getElementsByTagName('tfoot');
+ if(tfoots.length){
+ tb.removeChild(tfoots[0]);
+ }
+ var foot = tb.createTFoot();
+ var row = foot.insertRow(-1);
+ row.className = 'inProgress';
+ var cell=row.insertCell(-1);
+ cell.colSpan=2;
+ cell.innerHTML="Result";
+ cell = row.insertCell(-1);
+ cell.innerHTML=this._testCount+" tests in "+this._groupCount+" groups /<span class='failure'>"+this._errorCount+"</span> errors, <span class='failure'>"+this._failureCount+"</span> failures";
+ cell.setAttribute('_target',_loggedMsgLen+1);
+ row.insertCell(-1).innerHTML=doh._totalTime+"ms";
+ }
+
+ //This location can do the final performance rendering for the results
+ //of any performance tests.
+ var plotResults = null;
+ var standby;
+ if(doh.perfTestResults){
+ if(window.dojo){
+ //If we have dojo and here are perf tests results,
+ //well, we'll use the dojo charting functions
+ dojo.require("dojox.charting.Chart2D");
+ dojo.require("dojox.charting.DataChart");
+ dojo.require("dojox.charting.plot2d.Scatter");
+ dojo.require("dojox.charting.plot2d.Lines");
+ dojo.require("dojo.data.ItemFileReadStore");
+ plotResults = doh._dojoPlotPerfResults;
+ }else{
+ plotResults = doh._asciiPlotPerfResults;
+ }
+ try{
+ var g;
+ var pBody = byId("perfTestsBody");
+ var chartsToRender = [];
+
+ if(doh.perfTestResults){
+ doh.showPerfTestsPage();
+ }
+ for(g in doh.perfTestResults){
+ var grp = doh.perfTestResults[g];
+ var hdr = document.createElement("h1");
+ hdr.appendChild(document.createTextNode("Group: " + g));
+ pBody.appendChild(hdr);
+ var ind = document.createElement("blockquote");
+ pBody.appendChild(ind);
+ var f;
+ for(f in grp){
+ var fResults = grp[f];
+ if(!fResults){ continue; }
+ var fhdr = document.createElement("h3");
+ fhdr.appendChild(document.createTextNode("TEST: " + f));
+ fhdr.style.textDecoration = "underline";
+ ind.appendChild(fhdr);
+ var div = document.createElement("div");
+ ind.appendChild(div);
+
+ //Figure out the basic info
+ var results = "<b>TRIAL SIZE: </b>" + fResults.trials[0].testIterations + " iterations<br>" +
+ "<b>NUMBER OF TRIALS: </b>" + fResults.trials.length + "<br>";
+
+ //Figure out the average test pass cost.
+ var i;
+ var iAvgArray = [];
+ var tAvgArray = [];
+ for(i = 0; i < fResults.trials.length; i++){
+ iAvgArray.push(fResults.trials[i].average);
+ tAvgArray.push(fResults.trials[i].executionTime);
+ }
+ results += "<b>AVERAGE TRIAL EXECUTION TIME: </b>" + doh.average(tAvgArray).toFixed(10) + "ms.<br>";
+ results += "<b>MAXIMUM TEST ITERATION TIME: </b>" + doh.max(iAvgArray).toFixed(10) + "ms.<br>";
+ results += "<b>MINIMUM TEST ITERATION TIME: </b>" + doh.min(iAvgArray).toFixed(10) + "ms.<br>";
+ results += "<b>AVERAGE TEST ITERATION TIME: </b>" + doh.average(iAvgArray).toFixed(10) + "ms.<br>";
+ results += "<b>MEDIAN TEST ITERATION TIME: </b>" + doh.median(iAvgArray).toFixed(10) + "ms.<br>";
+ results += "<b>VARIANCE TEST ITERATION TIME: </b>" + doh.variance(iAvgArray).toFixed(10) + "ms.<br>";
+ results += "<b>STANDARD DEVIATION ON TEST ITERATION TIME: </b>" + doh.standardDeviation(iAvgArray).toFixed(10) + "ms.<br>";
+
+ //Okay, attach it all in.
+ div.innerHTML = results;
+
+ div = document.createElement("div");
+ div.innerHTML = "<h3>Average Test Execution Time (in milliseconds, with median line)</h3>";
+ ind.appendChild(div);
+ div = document.createElement("div");
+ dojo.style(div, "width", "600px");
+ dojo.style(div, "height", "250px");
+ ind.appendChild(div);
+ chartsToRender.push({
+ div: div,
+ title: "Average Test Execution Time",
+ data: iAvgArray
+ });
+
+ div = document.createElement("div");
+ div.innerHTML = "<h3>Average Trial Execution Time (in milliseconds, with median line)</h3>";
+ ind.appendChild(div);
+ div = document.createElement("div");
+ dojo.style(div, "width", "600px");
+ dojo.style(div, "height", "250px");
+ ind.appendChild(div);
+ chartsToRender.push({
+ div: div,
+ title: "Average Trial Execution Time",
+ data: tAvgArray
+ });
+ }
+ }
+
+ //Lazy-render these to give the browser time and not appear locked.
+ var delayedRenders = function() {
+ if(chartsToRender.length){
+ var chartData = chartsToRender.shift();
+ plotResults(chartData.div, chartData.title, chartData.data);
+ }
+ doh.setTimeout(delayedRenders, 50);
+ };
+ doh.setTimeout(delayedRenders, 150);
+ }catch(e){
+ doh.debug(e);
+ }
+ }
+ or.apply(doh,arguments);
+ }
+ })(doh._report);
+
+ if(this["opera"] && opera.postError){
+ doh.debug = function(){
+ var msg = "";
+ for(var x=0; x<arguments.length; x++){
+ msg += " "+arguments[x];
+ }
+ sendToLogPane([msg]);
+ opera.postError("DEBUG:"+msg);
+ }
+ }else if(window["console"]){
+ doh.debug = function(){
+ var msg = "";
+ for(var x=0; x<arguments.length; x++){
+ msg += " "+arguments[x];
+ }
+ sendToLogPane([msg]);
+ console.log("DEBUG:"+msg);
+ };
+ }else{
+ doh.debug = function(){
+ sendToLogPane.call(window, arguments);
+ }
+ }
+
+ var loaded = false;
+ var groupTemplate = null;
+ var testTemplate = null;
+
+ var groupNodes = {};
+
+ var _groupTogglers = {};
+
+ var _getGroupToggler = function(group, toggle){
+ if(_groupTogglers[group]){ return _groupTogglers[group]; }
+ var rolledUp = true;
+ return (_groupTogglers[group] = function(evt, forceOpen){
+ var nodes = groupNodes[group].__items;
+ var x;
+ if(rolledUp||forceOpen){
+ rolledUp = false;
+ for(x=0; x<nodes.length; x++){
+ nodes[x].style.display = "";
+ }
+ toggle.innerHTML = "&#9660;";
+ }else{
+ rolledUp = true;
+ for(x=0; x<nodes.length; x++){
+ nodes[x].style.display = "none";
+ }
+ toggle.innerHTML = "&#9658;";
+ }
+ });
+ };
+
+ var addGroupToList = function(group){
+ if(!byId("testList")){ return; }
+ var tb = byId("testList").tBodies[0];
+ var tg = groupTemplate.cloneNode(true);
+ var tds = tg.getElementsByTagName("td");
+ var toggle = tds[0];
+ toggle.onclick = _getGroupToggler(group, toggle);
+ var cb = tds[1].getElementsByTagName("input")[0];
+ cb.group = group;
+ cb.onclick = function(evt){
+ doh._groups[group].skip = (!this.checked);
+ }
+ tds[2].innerHTML = "<div class='testGroupName'>"+group+"</div><div style='width:0;'>&nbsp;</div>";
+ tds[3].innerHTML = "";
+
+ tb.appendChild(tg);
+ return tg;
+ }
+
+ var addFixtureToList = function(group, fixture){
+ if(!testTemplate){ return; }
+ var cgn = groupNodes[group];
+ if(!cgn["__items"]){ cgn.__items = []; }
+ var tn = testTemplate.cloneNode(true);
+ var tds = tn.getElementsByTagName("td");
+
+ tds[2].innerHTML = fixture.name;
+ tds[3].innerHTML = "";
+
+ var nn = (cgn.__lastFixture||cgn.__groupNode).nextSibling;
+ if(nn){
+ nn.parentNode.insertBefore(tn, nn);
+ }else{
+ cgn.__groupNode.parentNode.appendChild(tn);
+ }
+ // FIXME: need to make group display toggleable!!
+ tn.style.display = "none";
+ cgn.__items.push(tn);
+ return (cgn.__lastFixture = tn);
+ }
+
+ var getFixtureNode = function(group, fixture){
+ if(groupNodes[group]){
+ return groupNodes[group][fixture.name];
+ }
+ return null;
+ }
+
+ var getGroupNode = function(group){
+ if(groupNodes[group]){
+ return groupNodes[group].__groupNode;
+ }
+ return null;
+ }
+
+ var updateBacklog = [];
+ doh._updateTestList = function(group, fixture, unwindingBacklog){
+ if(!loaded){
+ if(group && fixture){
+ updateBacklog.push([group, fixture]);
+ }
+ return;
+ }else if(updateBacklog.length && !unwindingBacklog){
+ var tr;
+ while((tr=updateBacklog.shift())){
+ doh._updateTestList(tr[0], tr[1], true);
+ }
+ }
+ if(group && fixture){
+ if(!groupNodes[group]){
+ groupNodes[group] = {
+ "__groupNode": addGroupToList(group)
+ };
+ }
+ if(!groupNodes[group][fixture.name]){
+ groupNodes[group][fixture.name] = addFixtureToList(group, fixture)
+ }
+ }
+ }
+
+ doh._testRegistered = doh._updateTestList;
+
+ doh._groupStarted = function(group){
+ if(this._suiteCount == 0){
+ this._runedSuite = 0;
+ this._currentGlobalProgressBarWidth = 0;
+ this._suiteCount = this._testCount;
+ }
+ // console.debug("_groupStarted", group);
+ if(doh._inGroup != group){
+ doh._groupTotalTime = 0;
+ doh._runed = 0;
+ doh._inGroup = group;
+ this._runedSuite++;
+ }
+ var gn = getGroupNode(group);
+ if(gn){
+ gn.className = "inProgress";
+ }
+ }
+
+ doh._groupFinished = function(group, success){
+ // console.debug("_groupFinished", group);
+ var gn = getGroupNode(group);
+ if(gn && doh._inGroup == group){
+ doh._totalTime += doh._groupTotalTime;
+ gn.getElementsByTagName("td")[3].innerHTML = doh._groupTotalTime+"ms";
+ gn.getElementsByTagName("td")[2].lastChild.className = "";
+ doh._inGroup = null;
+ //doh._runedSuite++;
+ var failure = doh._updateGlobalProgressBar(this._runedSuite/this._groupCount,success,group);
+ gn.className = failure ? "failure" : "success";
+ //doh._runedSuite--;
+ doh._currentGlobalProgressBarWidth = parseInt(this._runedSuite/this._groupCount*10000)/100;
+ //byId("progressOuter").style.width = parseInt(this._runedSuite/this._suiteCount*100)+"%";
+ }
+ if(doh._inGroup == group){
+ this.debug("Total time for GROUP \"",group,"\" is ",doh._groupTotalTime,"ms");
+ }
+ }
+
+ doh._testStarted = function(group, fixture){
+ // console.debug("_testStarted", group, fixture.name);
+ var fn = getFixtureNode(group, fixture);
+ if(fn){
+ fn.className = "inProgress";
+ }
+ }
+
+ var _nameTimes = {};
+ var _playSound = function(name){
+ if(byId("hiddenAudio") && byId("audio") && byId("audio").checked){
+ // console.debug("playing:", name);
+ var nt = _nameTimes[name];
+ // only play sounds once every second or so
+ if((!nt)||(((new Date)-nt) > 700)){
+ _nameTimes[name] = new Date();
+ var tc = document.createElement("span");
+ byId("hiddenAudio").appendChild(tc);
+ tc.innerHTML = '<embed src="_sounds/'+name+'.wav" autostart="true" loop="false" hidden="true" width="1" height="1"></embed>';
+ }
+ }
+ }
+
+ doh._updateGlobalProgressBar = function(p,success,group){
+ var outerContainer=byId("progressOuter");
+
+ var gdiv=outerContainer.childNodes[doh._runedSuite-1];
+ if(!gdiv){
+ gdiv=document.createElement('div');
+ outerContainer.appendChild(gdiv);
+ gdiv.className='success';
+ gdiv.setAttribute('_target',group);
+ }
+ if(!success && !gdiv._failure){
+ gdiv._failure=true;
+ gdiv.className='failure';
+ if(group){
+ gdiv.setAttribute('title','failed group '+group);
+ }
+ }
+ var tp=parseInt(p*10000)/100;
+ gdiv.style.width = (tp-doh._currentGlobalProgressBarWidth)+"%";
+ return gdiv._failure;
+ }
+ doh._testFinished = function(group, fixture, success){
+ var fn = getFixtureNode(group, fixture);
+ var elapsed = fixture.endTime-fixture.startTime;
+ if(fn){
+ fn.getElementsByTagName("td")[3].innerHTML = elapsed+"ms";
+ fn.className = (success) ? "success" : "failure";
+ fn.getElementsByTagName("td")[2].setAttribute('_target', _loggedMsgLen);
+ if(!success){
+ _playSound("doh");
+ var gn = getGroupNode(group);
+ if(gn){
+ gn.className = "failure";
+ _getGroupToggler(group)(null, true);
+ }
+ }
+ }
+ if(doh._inGroup == group){
+ var gn = getGroupNode(group);
+ doh._runed++;
+ if(gn && doh._curTestCount){
+ var p = doh._runed/doh._curTestCount;
+ var groupfail = this._updateGlobalProgressBar((doh._runedSuite+p-1)/doh._groupCount,success,group);
+
+ var pbar = gn.getElementsByTagName("td")[2].lastChild;
+ pbar.className = groupfail?"failure":"success";
+ pbar.style.width = parseInt(p*100)+"%";
+ gn.getElementsByTagName("td")[3].innerHTML = parseInt(p*10000)/100+"%";
+ }
+ }
+ this._groupTotalTime += elapsed;
+ this.debug((success ? "PASSED" : "FAILED"), "test:", fixture.name, elapsed, 'ms');
+ }
+
+ // FIXME: move implementation to _browserRunner?
+ doh.registerUrl = function( /*String*/ group,
+ /*String*/ url,
+ /*Integer*/ timeout){
+ var tg = new String(group);
+ this.register(group, {
+ name: url,
+ setUp: function(){
+ doh.currentGroupName = tg;
+ doh.currentGroup = this;
+ doh.currentUrl = url;
+ this.d = new doh.Deferred();
+ doh.currentTestDeferred = this.d;
+ doh.showTestPage();
+ byId("testBody").src = url;
+ },
+ timeout: timeout||10000, // 10s
+ // timeout: timeout||1000, // 10s
+ runTest: function(){
+ // FIXME: implement calling into the url's groups here!!
+ return this.d;
+ },
+ tearDown: function(){
+ doh.currentGroupName = null;
+ doh.currentGroup = null;
+ doh.currentTestDeferred = null;
+ doh.currentUrl = null;
+ // this.d.errback(false);
+ // byId("testBody").src = "about:blank";
+ doh.showLogPage();
+ }
+ });
+ }
+
+ //
+ // Utility code for runner.html
+ //
+ // var isSafari = navigator.appVersion.indexOf("Safari") >= 0;
+ var tabzidx = 1;
+ var _showTab = function(toShow, toHide){
+ // FIXME: I don't like hiding things this way.
+ var i;
+ for(i = 0; i < toHide.length; i++){
+ var node = byId(toHide[i]);
+ if(node){
+ node.style.display="none";
+ }
+ }
+ toShow = byId(toShow);
+ if(toShow){
+ with(toShow.style){
+ display = "";
+ zIndex = ++tabzidx;
+ }
+ }
+ }
+
+ doh.showTestPage = function(){
+ _showTab("testBody", ["logBody", "perfTestsBody"]);
+ }
+
+ doh.showLogPage = function(){
+ _showTab("logBody", ["testBody", "perfTestsBody"]);
+ }
+
+ doh.showPerfTestsPage = function(){
+ _showTab("perfTestsBody", ["testBody", "logBody"]);
+ }
+
+ var runAll = true;
+ doh.toggleRunAll = function(){
+ // would be easier w/ query...sigh
+ runAll = !runAll;
+ if(!byId("testList")){ return; }
+ var tb = byId("testList").tBodies[0];
+ var inputs = tb.getElementsByTagName("input");
+ var x=0; var tn;
+ while((tn=inputs[x++])){
+ tn.checked = runAll;
+ doh._groups[tn.group].skip = (!runAll);
+ }
+ }
+
+ var listHeightTimer = null;
+ var setListHeight = function(){
+ if(listHeightTimer){
+ clearTimeout(listHeightTimer);
+ }
+ var tl = byId("testList");
+ if(!tl){ return; }
+ listHeightTimer = doh.setTimeout(function(){
+ tl.style.display = "none";
+ tl.style.display = "";
+
+ }, 10);
+ }
+
+ _addOnEvt("resize", setListHeight);
+ _addOnEvt("load", setListHeight);
+ _addOnEvt("load", function(){
+ if(loaded){ return; }
+ loaded = true;
+ groupTemplate = byId("groupTemplate");
+ if(!groupTemplate){
+ // make sure we've got an ammenable DOM structure
+ return;
+ }
+ groupTemplate.parentNode.removeChild(groupTemplate);
+ groupTemplate.style.display = "";
+ testTemplate = byId("testTemplate");
+ testTemplate.parentNode.removeChild(testTemplate);
+ testTemplate.style.display = "";
+ doh._updateTestList();
+ });
+
+ _addOnEvt("load",
+ function(){
+ // let robot code run if it gets to this first
+ var __onEnd = doh._onEnd;
+ doh._onEnd = function(){
+ __onEnd.apply(doh, arguments);
+ if(doh._failureCount == 0){
+ doh.debug("WOOHOO!!");
+ _playSound("woohoo");
+ }else{
+ console.debug("doh._failureCount:", doh._failureCount);
+ }
+ if(byId("play")){
+ toggleRunning();
+ }
+ }
+ if(!byId("play")){
+ // make sure we've got an amenable DOM structure
+ return;
+ }
+ var isRunning = false;
+ var toggleRunning = function(){
+ // ugg, this would be so much better w/ dojo.query()
+ if(isRunning){
+ byId("play").style.display = byId("pausedMsg").style.display = "";
+ byId("playingMsg").style.display = byId("pause").style.display = "none";
+ isRunning = false;
+ }else{
+ byId("play").style.display = byId("pausedMsg").style.display = "none";
+ byId("playingMsg").style.display = byId("pause").style.display = "";
+ isRunning = true;
+ }
+ }
+ doh.run = (function(oldRun){
+ return function(){
+ if(!doh._currentGroup){
+ toggleRunning();
+ }
+ return oldRun.apply(doh, arguments);
+ }
+ })(doh.run);
+ var btns = byId("toggleButtons").getElementsByTagName("span");
+ var node; var idx=0;
+ while((node=btns[idx++])){
+ node.onclick = toggleRunning;
+ }
+
+ //Performance report generating functions!
+ doh._dojoPlotPerfResults = function(div, name, dataArray) {
+ var median = doh.median(dataArray);
+ var medarray = [];
+
+ var i;
+ for(i = 0; i < dataArray.length; i++){
+ medarray.push(median);
+ }
+
+ var data = {
+ label: "name",
+ items: [
+ {name: name, trials: dataArray},
+ {name: "Median", trials: medarray}
+ ]
+ };
+ var ifs = new dojo.data.ItemFileReadStore({data: data});
+
+ var min = Math.floor(doh.min(dataArray));
+ var max = Math.ceil(doh.max(dataArray));
+ var step = (max - min)/10;
+
+ //Lets try to pad out the bottom and top a bit
+ //Then recalc the step.
+ if(min > 0){
+ min = min - step;
+ if(min < 0){
+ min = 0;
+ }
+ min = Math.floor(min);
+ }
+ if(max > 0){
+ max = max + step;
+ max = Math.ceil(max);
+ }
+ step = (max - min)/10;
+
+ var chart = new dojox.charting.DataChart(div, {
+ type: dojox.charting.plot2d.Lines,
+ displayRange:dataArray.length,
+ xaxis: {min: 1, max: dataArray.length, majorTickStep: Math.ceil((dataArray.length - 1)/10), htmlLabels: false},
+ yaxis: {min: min, max: max, majorTickStep: step, vertical: true, htmlLabels: false}
+ });
+ chart.setStore(ifs, {name:"*"}, "trials");
+ };
+
+ doh._asciiPlotPerfResults = function(){
+ //TODO: Implement!
+ };
+ }
+ );
+ }else{
+ // we're in an iframe environment. Time to mix it up a bit.
+
+ _doh = window.parent.doh;
+ var _thisGroup = _doh.currentGroupName;
+ var _thisUrl = _doh.currentUrl;
+ if(_thisGroup){
+ doh._testRegistered = function(group, tObj){
+ _doh._updateTestList(_thisGroup, tObj);
+ }
+ doh._onEnd = function(){
+ _doh._errorCount += doh._errorCount;
+ _doh._failureCount += doh._failureCount;
+ _doh._testCount += doh._testCount;
+ // should we be really adding raw group counts?
+ //_doh._groupCount += doh._groupCount;
+ _doh.currentTestDeferred.callback(true);
+ }
+ var otr = doh._getTestObj;
+ doh._getTestObj = function(){
+ var tObj = otr.apply(doh, arguments);
+ tObj.name = _thisUrl+"::"+arguments[0]+"::"+tObj.name;
+ return tObj;
+ }
+ doh.debug = doh.hitch(_doh, "debug");
+ doh.registerUrl = doh.hitch(_doh, "registerUrl");
+ doh._testStarted = function(group, fixture){
+ _doh._testStarted(_thisGroup, fixture);
+ }
+ doh._testFinished = function(g, f, s){
+ _doh._testFinished(_thisGroup, f, s);
+
+ //Okay, there may be performance info we need to filter back
+ //to the parent, so do that here.
+ if(doh.perfTestResults){
+ try{
+ gName = g.toString();
+ var localFName = f.name;
+ while(localFName.indexOf("::") >= 0){
+ localFName = localFName.substring(localFName.indexOf("::") + 2, localFName.length);
+ }
+ if(!_doh.perfTestResults){
+ _doh.perfTestResults = {};
+ }
+ if(!_doh.perfTestResults[gName]){
+ _doh.perfTestResults[gName] = {};
+ }
+ _doh.perfTestResults[gName][f.name] = doh.perfTestResults[gName][localFName];
+ }catch (e){
+ doh.debug(e);
+ }
+ }
+ }
+ doh._groupStarted = function(g){
+ if(!this._setParent){
+ _doh._curTestCount = this._testCount;
+ _doh._curGroupCount = this._groupCount;
+ this._setParent = true;
+ }
+ }
+ doh._report = function(){
+ };
+ }
+ }
+
+})();
20 tests/doh/_nodeRunner.js
@@ -0,0 +1,20 @@
+
+/*global doh: false, process: false */
+
+var aps = Array.prototype.slice;
+
+doh.debug = function () {
+ //Could have multiple args, join them all together.
+ var msg = aps.call(arguments, 0).join(' ');
+ console.log(msg);
+};
+
+// Override the doh._report method to make it quit with an
+// appropriate exit code in case of test failures.
+var oldReport = doh._report;
+doh._report = function () {
+ oldReport.apply(doh, arguments);
+ if (this._failureCount > 0 || this._errorCount > 0) {
+ process.exit(1);
+ }
+};
17 tests/doh/_rhinoRunner.js
@@ -0,0 +1,17 @@
+if(this["dojo"]){
+ dojo.provide("doh._rhinoRunner");
+}
+
+doh.debug = print;
+
+// Override the doh._report method to make it quit with an
+// appropriate exit code in case of test failures.
+(function(){
+ var oldReport = doh._report;
+ doh._report = function(){
+ oldReport.apply(doh, arguments);
+ if(this._failureCount > 0 || this._errorCount > 0){
+ quit(1);
+ }
+ }
+})();
10 tests/doh/_sounds/LICENSE
@@ -0,0 +1,10 @@
+License Disclaimer:
+
+All contents of this directory are Copyright (c) the Dojo Foundation, with the
+following exceptions:
+-------------------------------------------------------------------------------
+
+woohoo.wav, doh.wav, dohaaa.wav:
+ * Copyright original authors.
+ Copied from:
+ http://simpson-homer.com/homer-simpson-soundboard.html
BIN tests/doh/_sounds/doh.wav
Binary file not shown.
BIN tests/doh/_sounds/dohaaa.wav
Binary file not shown.
BIN tests/doh/_sounds/woohoo.wav
Binary file not shown.
316 tests/doh/runner.html
@@ -0,0 +1,316 @@
+<html>
+ <!--
+ NOTE: we are INTENTIONALLY in quirks mode. It makes it much easier to
+ get a "full screen" UI w/ straightforward CSS.
+ -->
+ <!--
+ // TODO: provide a UI for prompted tests
+ -->
+ <head>
+ <title>RequireJS Tests Via The Dojo Unit Test Harness, $Rev: 20149 $</title>
+
+ <script type="text/javascript">
+ // workaround for bug in Safari 3. See #7189
+ if (/3[\.0-9]+ Safari/.test(navigator.appVersion))
+ {
+ window.console = {
+ origConsole: window.console,
+ log: function(s){
+ this.origConsole.log(s);
+ },
+ info: function(s){
+ this.origConsole.info(s);
+ },
+ error: function(s){
+ this.origConsole.error(s);
+ },
+ warn: function(s){
+ this.origConsole.warn(s);
+ }
+ };
+ }
+ </script>
+
+ <script type="text/javascript">
+ window.dojoUrl = "../../dojo/dojo.js";
+ window.testUrl = "";
+ window.testModule = "";
+
+ // parse out our test URL and our Dojo URL from the query string
+ var qstr = window.location.search.substr(1);
+ if(qstr.length){
+ var qparts = qstr.split("&");
+ for(var x=0; x<qparts.length; x++){
+ var tp = qparts[x].split("=");
+ if(tp[0] == "dojoUrl"){
+ window.dojoUrl = tp[1];
+ }
+ if(tp[0] == "testUrl"){
+ window.testUrl = tp[1];
+ }
+ if(tp[0] == "testModule"){
+ window.testModule = tp[1];
+ }
+ if(tp[0] == "registerModulePath"){
+ var modules = tp[1].split(";");
+ window.registerModulePath=[];
+ for (var i=0; i<modules.length;i++){
+ window.registerModulePath.push(modules[i].split(","));
+ }
+ }
+ }
+ }
+
+ //document.write("<scr"+"ipt type='text/javascript' djConfig='isDebug: true' src='"+dojoUrl+"'></scr"+"ipt>");
+ </script>
+ <script type="text/javascript" src="runner.js"></script>
+ <script type="text/javascript" src="_browserRunner.js"></script>
+
+ <script type="text/javascript">
+ if(testUrl.length){
+ document.write("<scr"+"ipt type='text/javascript' src='"+testUrl+".js'></scr"+"ipt>");
+ }
+ </script>
+ <style type="text/css">
+ /* @import "../../dojo/resources/dojo.css"; */
+ /*
+ body {
+ margin: 0px;
+ padding: 0px;
+ font-size: 13px;
+ color: #292929;
+ font-family: Myriad, Lucida Grande, Bitstream Vera Sans, Arial, Helvetica, sans-serif;
+ *font-size: small;
+ *font: x-small;
+ }
+
+ th, td {
+ font-size: 13px;
+ color: #292929;
+ font-family: Myriad, Lucida Grande, Bitstream Vera Sans, Arial, Helvetica, sans-serif;
+ font-weight: normal;
+ }
+
+ * body {
+ line-height: 1.25em;
+ }
+
+ table {
+ border-collapse: collapse;
+ }
+ */
+
+ #testLayout {
+ position: relative;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+ border: 1px solid black;
+ border: 0px;
+ }
+
+ .tabBody {
+ margin: 0px;
+ padding: 0px;
+ /*
+ border: 1px solid black;
+ */
+ background-color: #DEDEDE;
+ border: 0px;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ overflow: auto;
+ }
+
+ #logBody {
+ padding-left: 5px;
+ padding-top: 5px;
+ font-family: Monaco, monospace;
+ font-size: 11px;
+ white-space: pre;
+ }
+
+ #progressOuter {
+ background:#e9e9e9 url("http://o.aolcdn.com/dojo/1.3/dijit/themes/tundra/images/dojoTundraGradientBg.png") repeat-x 0 0;
+ height: 1em;
+ /*the following trick is necessary to prevent IE from wrapping the last piece of progress bar into a new line*/
+ _margin:1px;
+ _padding: -1px;
+
+ /*
+ border-color: #e8e8e8;
+ */
+ }
+
+ #progressOuter .success, #progressOuter .failure{
+ float: left;
+ height: 1em;
+ }
+
+ #play, #pause {
+ font-family: Arial;
+ font-size: 1.4em;
+ border: 1px solid #DEDEDE;
+ cursor: pointer;
+ padding-right: 0.5em;
+ }
+
+ .header {
+ border: 1px solid #DEDEDE;
+ }
+
+ button.tab {
+ border-width: 1px 1px 0px 1px;
+ border-style: solid;
+ border-color: #DEDEDE;
+ margin-right: 5px;
+ }
+
+ #testListContainer {
+ /*
+ border: 1px solid black;
+ */
+ position: relative;
+ height: 99%;
+ width: 100%;
+ overflow: auto;
+ }
+
+ #testList {
+ border-collapse: collapse;
+ position: absolute;
+ left: 0px;
+ width: 100%;
+ }
+
+ #testList td {
+ border-bottom: 1px solid #DEDEDE;
+ border-right : 1px solid #DEDEDE;
+ padding: 3px;
+ }
+
+ #testListHeader th {
+ border-bottom: 1px solid #DEDEDE;
+ border-right : 1px solid #DEDEDE;
+ padding: 3px;
+ font-weight: bolder;
+ font-style: italic;
+ }
+
+ #testList tfoot {
+ font-weight: bold;
+ }
+
+ #toggleButtons {
+ float: left;
+ background-color: #DEDEDE;
+ }
+
+ div.testGroupName {
+ position: absolute;
+ }
+
+ .inProgress {
+ background-color: #85afde;
+ }
+
+ .success {
+ background-color: #7cdea7;
+ }
+
+ .failure {
+ background-color: #de827b;
+ }
+ </style>
+ </head>
+ <body>
+ <table id="testLayout" cellpadding="0" cellspacing="0" style="margin: 0;">
+ <tr valign="top" height="40">
+ <td colspan="2" id="logoBar">
+ <h3 style="margin: 5px 5px 0px 5px; float: left;">RequireJS Tests Via D.O.H.: The Dojo Objective Harness</h3>
+ <img src="small_logo.png" height="40" style="margin: 0px 5px 0px 5px; float: right;">
+ <span style="margin: 10px 5px 0px 5px; float: right;">
+ <input type="checkbox" id="audio" name="audio">
+ <label for="audio">sounds?</label>
+ </span>
+ </td>
+ </tr>
+ <tr valign="top" height="10">
+ <td colspan="2"><div id="progressOuter" onclick="doh._jumpToSuite(arguments[0]);"></div></td>
+ </tr>
+ <tr valign="top" height="30">
+ <td width="30%" class="header">
+ <span id="toggleButtons" onclick="doh.togglePaused();">
+ <button id="play">&#9658;</button>
+ <button id="pause" style="display: none;">&#9553;</button>
+ </span>
+ <span id="runningStatus">
+ <span id="pausedMsg">Stopped</span>
+ <span id="playingMsg" style="display: none;">Tests Running</span>
+ </span>
+ </td>
+ <td width="*" class="header" valign="bottom">
+ <button class="tab" onclick="doh.showTestPage();">Test Page</button>
+ <button class="tab" onclick="doh.showLogPage();">Log</button>
+ <button class="tab" onclick="doh.showPerfTestsPage();">Performance Tests Results</button>
+ </td>
+ </tr>
+ <tr valign="top" style="border: 0; padding: 0; margin: 0;">
+ <td height="100%" style="border: 0; padding: 0; margin: 0;">
+ <div id="testListContainer">
+ <table cellpadding="0" cellspacing="0" border="0"
+ width="100%" id="testList" style="margin: 0;" onclick="doh._jumpToLog(arguments[0]);">
+ <thead>
+ <tr id="testListHeader" style="border: 0; padding: 0; margin: 0;" >
+ <th>&nbsp;</th>
+ <th width="20">
+ <input type="checkbox" checked
+ onclick="doh.toggleRunAll();">
+ </th>
+ <th width="*" style="text-align: left;">test</th>
+ <th width="50">time</th>
+ </tr>
+ </thead>
+ <tbody valign="top">
+ <tr id="groupTemplate" style="display: none;">
+ <td style="font-family: Arial; width: 15px;">&#9658;</td>
+ <td>
+ <input type="checkbox" checked>
+ </td>
+ <td>group name</td>
+ <td>10ms</td>
+ </tr>
+ <tr id="testTemplate" style="display: none;">
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td style="padding-left: 20px;">test name</td>
+ <td>10ms</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </td>
+ <td>
+ <div style="position: relative; width: 99%; height: 100%; top: 0px; left: 0px;">
+ <div class="tabBody"
+ style="z-index: 1;">
+ <pre id="logBody"></pre>
+ <div id="perfTestsBody" style="background-color: white;"></div>
+ </div>
+ <iframe id="testBody" class="tabBody"
+ style="z-index: -1;"></iframe>
+ <!--
+ src="http://redesign.dojotoolkit.org"></iframe>
+ -->
+ </div>
+ </td>
+ </tr>
+ </table>
+ <span id="hiddenAudio"></span>
+ </body>
+</html>
+
1,499 tests/doh/runner.js
@@ -0,0 +1,1499 @@
+// package system gunk.
+//try{
+// dojo.provide("doh.runner");
+//}catch(e){
+ if(!this["doh"]){
+ doh = {};
+ }
+//}
+
+//
+// Utility Functions and Classes
+//
+
+doh.selfTest = false;
+
+doh.global = this;
+
+doh.hitch = function(/*Object*/thisObject, /*Function|String*/method /*, ...*/){
+ var args = [];
+ for(var x=2; x<arguments.length; x++){
+ args.push(arguments[x]);
+ }
+ var fcn = ((typeof method == "string") ? thisObject[method] : method) || function(){};
+ return function(){
+ var ta = args.concat([]); // make a copy
+ for(var x=0; x<arguments.length; x++){
+ ta.push(arguments[x]);
+ }
+ return fcn.apply(thisObject, ta); // Function
+ };
+}
+
+doh._mixin = function(/*Object*/ obj, /*Object*/ props){
+ // summary:
+ // Adds all properties and methods of props to obj. This addition is
+ // "prototype extension safe", so that instances of objects will not
+ // pass along prototype defaults.
+ var tobj = {};
+ for(var x in props){
+ // the "tobj" condition avoid copying properties in "props"
+ // inherited from Object.prototype. For example, if obj has a custom
+ // toString() method, don't overwrite it with the toString() method
+ // that props inherited from Object.protoype
+ if(tobj[x] === undefined || tobj[x] != props[x]){
+ obj[x] = props[x];
+ }
+ }
+ // IE doesn't recognize custom toStrings in for..in
+ if( this["document"]
+ && document.all
+ && (typeof props["toString"] == "function")
+ && (props["toString"] != obj["toString"])
+ && (props["toString"] != tobj["toString"])
+ ){
+ obj.toString = props.toString;
+ }
+ return obj; // Object
+}
+
+doh.mixin = function(/*Object*/obj, /*Object...*/props){
+ // summary: Adds all properties and methods of props to obj.
+ for(var i=1, l=arguments.length; i<l; i++){
+ doh._mixin(obj, arguments[i]);
+ }
+ return obj; // Object
+}
+
+doh.extend = function(/*Object*/ constructor, /*Object...*/ props){
+ // summary:
+ // Adds all properties and methods of props to constructor's
+ // prototype, making them available to all instances created with
+ // constructor.
+ for(var i=1, l=arguments.length; i<l; i++){
+ doh._mixin(constructor.prototype, arguments[i]);
+ }
+ return constructor; // Object
+}
+
+
+doh._line = "------------------------------------------------------------";
+
+/*
+doh._delegate = function(obj, props){
+ // boodman-crockford delegation
+ function TMP(){};
+ TMP.prototype = obj;
+ var tmp = new TMP();
+ if(props){
+ dojo.lang.mixin(tmp, props);
+ }
+ return tmp;
+}
+*/
+
+doh.debug = function(){
+ // summary:
+ // takes any number of arguments and sends them to whatever debugging
+ // or logging facility is available in this environment
+
+ // YOUR TEST RUNNER NEEDS TO IMPLEMENT THIS
+}
+
+doh._AssertFailure = function(msg, hint){
+ // idea for this as way of dis-ambiguating error types is from JUM.
+ // The JUM is dead! Long live the JUM!
+
+ if(!(this instanceof doh._AssertFailure)){
+ return new doh._AssertFailure(msg, hint);
+ }
+ if(hint){
+ msg = (new String(msg||""))+" with hint: \n\t\t"+(new String(hint)+"\n");
+ }
+ this.message = new String(msg||"");
+ return this;
+}
+doh._AssertFailure.prototype = new Error();
+doh._AssertFailure.prototype.constructor = doh._AssertFailure;
+doh._AssertFailure.prototype.name = "doh._AssertFailure";
+
+doh.Deferred = function(canceller){
+ this.chain = [];
+ this.id = this._nextId();
+ this.fired = -1;
+ this.paused = 0;
+ this.results = [null, null];
+ this.canceller = canceller;
+ this.silentlyCancelled = false;
+};
+
+doh.extend(doh.Deferred, {
+ getTestErrback: function(cb, scope){
+ // summary: Replaces outer getTextCallback's in nested situations to avoid multiple callback(true)'s
+ var _this = this;
+ return function(){
+ try{
+ cb.apply(scope||doh.global||_this, arguments);
+ }catch(e){
+ _this.errback(e);
+ }
+ };
+ },
+
+ getTestCallback: function(cb, scope){
+ var _this = this;
+ return function(){
+ try{
+ cb.apply(scope||doh.global||_this, arguments);
+ }catch(e){
+ _this.errback(e);
+ return;
+ }
+ _this.callback(true);
+ };
+ },
+
+ getFunctionFromArgs: function(){
+ var a = arguments;
+ if((a[0])&&(!a[1])){
+ if(typeof a[0] == "function"){
+ return a[0];
+ }else if(typeof a[0] == "string"){
+ return doh.global[a[0]];
+ }
+ }else if((a[0])&&(a[1])){
+ return doh.hitch(a[0], a[1]);
+ }
+ return null;
+ },
+
+ makeCalled: function() {
+ var deferred = new doh.Deferred();
+ deferred.callback();
+ return deferred;
+ },
+
+ _nextId: (function(){
+ var n = 1;
+ return function(){ return n++; };
+ })(),
+
+ cancel: function(){
+ if(this.fired == -1){
+ if (this.canceller){
+ this.canceller(this);
+ }else{
+ this.silentlyCancelled = true;
+ }
+ if(this.fired == -1){
+ this.errback(new Error("Deferred(unfired)"));
+ }
+ }else if(this.fired == 0 &&
+ (this.results[0] instanceof doh.Deferred)){
+ this.results[0].cancel();
+ }
+ },
+
+
+ _pause: function(){
+ this.paused++;
+ },
+
+ _unpause: function(){
+ this.paused--;
+ if ((this.paused == 0) && (this.fired >= 0)) {
+ this._fire();
+ }
+ },
+
+ _continue: function(res){
+ this._resback(res);
+ this._unpause();
+ },
+
+ _resback: function(res){
+ this.fired = ((res instanceof Error) ? 1 : 0);
+ this.results[this.fired] = res;
+ this._fire();
+ },
+
+ _check: function(){
+ if(this.fired != -1){
+ if(!this.silentlyCancelled){
+ throw new Error("already called!");
+ }
+ this.silentlyCancelled = false;
+ return;
+ }
+ },
+
+ callback: function(res){
+ this._check();
+ this._resback(res);
+ },
+
+ errback: function(res){
+ this._check();
+ if(!(res instanceof Error)){
+ res = new Error(res);
+ }
+ this._resback(res);
+ },
+
+ addBoth: function(cb, cbfn){
+ var enclosed = this.getFunctionFromArgs(cb, cbfn);
+ if(arguments.length > 2){
+ enclosed = doh.hitch(null, enclosed, arguments, 2);
+ }
+ return this.addCallbacks(enclosed, enclosed);
+ },
+
+ addCallback: function(cb, cbfn){
+ var enclosed = this.getFunctionFromArgs(cb, cbfn);
+ if(arguments.length > 2){
+ enclosed = doh.hitch(null, enclosed, arguments, 2);
+ }
+ return this.addCallbacks(enclosed, null);
+ },
+
+ addErrback: function(cb, cbfn){
+ var enclosed = this.getFunctionFromArgs(cb, cbfn);
+ if(arguments.length > 2){
+ enclosed = doh.hitch(null, enclosed, arguments, 2);
+ }
+ return this.addCallbacks(null, enclosed);
+ },
+
+ addCallbacks: function(cb, eb){
+ this.chain.push([cb, eb]);
+ if(this.fired >= 0){
+ this._fire();
+ }
+ return this;
+ },
+
+ _fire: function(){
+ var chain = this.chain;
+ var fired = this.fired;
+ var res = this.results[fired];
+ var self = this;
+ var cb = null;
+ while(chain.length > 0 && this.paused == 0){
+ // Array
+ var pair = chain.shift();
+ var f = pair[fired];
+ if(f == null){
+ continue;
+ }
+ try {
+ res = f(res);
+ fired = ((res instanceof Error) ? 1 : 0);
+ if(res instanceof doh.Deferred){
+ cb = function(res){
+ self._continue(res);
+ };
+ this._pause();
+ }
+ }catch(err){
+ fired = 1;
+ res = err;
+ }
+ }
+ this.fired = fired;
+ this.results[fired] = res;
+ if((cb)&&(this.paused)){
+ res.addBoth(cb);
+ }
+ }
+});
+
+//
+// State Keeping and Reporting
+//
+
+doh._testCount = 0;
+doh._groupCount = 0;
+doh._errorCount = 0;
+doh._failureCount = 0;
+doh._currentGroup = null;
+doh._currentTest = null;
+doh._paused = true;
+
+doh._init = function(){
+ this._currentGroup = null;
+ this._currentTest = null;
+ this._errorCount = 0;
+ this._failureCount = 0;
+ this.debug(this._testCount, "tests to run in", this._groupCount, "groups");
+}
+
+// doh._urls = [];
+doh._groups = {};
+
+//
+// Test Registration
+//
+
+doh.registerTestNs = function(/*String*/ group, /*Object*/ ns){
+ // summary:
+ // adds the passed namespace object to the list of objects to be
+ // searched for test groups. Only "public" functions (not prefixed
+ // with "_") will be added as tests to be run. If you'd like to use
+ // fixtures (setUp(), tearDown(), and runTest()), please use
+ // registerTest() or registerTests().
+ for(var x in ns){
+ if( (x.charAt(0) != "_") &&
+ (typeof ns[x] == "function") ){
+ this.registerTest(group, ns[x]);
+ }
+ }
+}
+
+doh._testRegistered = function(group, fixture){
+ // slot to be filled in
+}
+
+doh._groupStarted = function(group){
+ // slot to be filled in
+}
+
+doh._groupFinished = function(group, success){
+ // slot to be filled in
+}
+
+doh._testStarted = function(group, fixture){
+ // slot to be filled in
+}
+
+doh._testFinished = function(group, fixture, success){
+ // slot to be filled in
+}
+
+doh.registerGroup = function( /*String*/ group,
+ /*Array||Function||Object*/ tests,
+ /*Function*/ setUp,
+ /*Function*/ tearDown,
+ /*String*/ type){
+ // summary:
+ // registers an entire group of tests at once and provides a setUp and
+ // tearDown facility for groups. If you call this method with only
+ // setUp and tearDown parameters, they will replace previously
+ // installed setUp or tearDown functions for the group with the new
+ // methods.
+ // group:
+ // string name of the group
+ // tests:
+ // either a function or an object or an array of functions/objects. If
+ // an object, it must contain at *least* a "runTest" method, and may
+ // also contain "setUp" and "tearDown" methods. These will be invoked
+ // on either side of the "runTest" method (respectively) when the test
+ // is run. If an array, it must contain objects matching the above
+ // description or test functions.
+ // setUp: a function for initializing the test group
+ // tearDown: a function for initializing the test group
+ // type: The type of tests these are, such as a group of performance tests
+ // null/undefied are standard DOH tests, the valye 'perf' enables
+ // registering them as performance tests.
+ if(tests){
+ this.register(group, tests, type);
+ }
+ if(setUp){
+ this._groups[group].setUp = setUp;
+ }
+ if(tearDown){
+ this._groups[group].tearDown = tearDown;
+ }
+}
+
+doh._getTestObj = function(group, test, type){
+ var tObj = test;
+ if(typeof test == "string"){
+ if(test.substr(0, 4)=="url:"){
+ return this.registerUrl(group, test);
+ }else{
+ tObj = {
+ name: test.replace("/\s/g", "_") // FIXME: bad escapement
+ };
+ tObj.runTest = new Function("t", test);
+ }
+ }else if(typeof test == "function"){
+ // if we didn't get a fixture, wrap the function
+ tObj = { "runTest": test };
+ if(test["name"]){
+ tObj.name = test.name;
+ }else{
+ try{
+ var fStr = "function ";
+ var ts = tObj.runTest+"";
+ if(0 <= ts.indexOf(fStr)){
+ tObj.name = ts.split(fStr)[1].split("(", 1)[0];
+ }
+ // doh.debug(tObj.runTest.toSource());
+ }catch(e){
+ }
+ }
+ // FIXME: try harder to get the test name here
+ }
+
+ //Augment the test with some specific options to make it identifiable as a
+ //particular type of test so it can be executed properly.
+ if(type === "perf" || tObj.testType === "perf"){
+ tObj.testType = "perf";
+
+ //Build an object on the root DOH class to contain all the test results.
+ //Cache it on the test object for quick lookup later for results storage.
+ if(!doh.perfTestResults){
+ doh.perfTestResults = {};
+ doh.perfTestResults[group] = {};
+ }
+ if(!doh.perfTestResults[group]){
+ doh.perfTestResults[group] = {};
+ }
+ if(!doh.perfTestResults[group][tObj.name]){
+ doh.perfTestResults[group][tObj.name] = {};
+ }
+ tObj.results = doh.perfTestResults[group][tObj.name];
+
+ //If it's not set, then set the trial duration
+ //default to 100ms.
+ if(!("trialDuration" in tObj)){
+ tObj.trialDuration = 100;
+ }
+
+ //If it's not set, then set the delay between trial runs to 100ms
+ //default to 100ms to allow for GC and to make IE happy.
+ if(!("trialDelay" in tObj)){
+ tObj.trialDelay = 100;
+ }
+
+ //If it's not set, then set number of times a trial is run to 10.
+ if(!("trialIterations" in tObj)){
+ tObj.trialIterations = 10;
+ }
+ }
+ return tObj;
+}
+
+doh.registerTest = function(/*String*/ group, /*Function||Object*/ test, /*String*/ type){
+ // summary:
+ // add the provided test function or fixture object to the specified
+ // test group.