1 //guarantee in global scope and scope protection
2 (function(/* Array? */scriptArgs) {
4 //here's the definition of doh.runner...which really defines global doh
5 var d = function(doh) {
8 // Utility Functions and Classes
13 doh._print = console.log.bind(console);
16 doh.hitch = function(/*Object*/thisObject, /*Function|String*/method /*, ...*/){
18 for(var x=2; x<arguments.length; x++){
19 args.push(arguments[x]);
21 var fcn = ((typeof method == "string") ? thisObject[method] : method) ||
24 var ta = args.concat([]); // make a copy
25 for(var x=0; x<arguments.length; x++){
26 ta.push(arguments[x]);
28 return fcn.apply(thisObject, ta); // Function
32 doh._mixin = function(/*Object*/ obj, /*Object*/ props){
34 // Adds all properties and methods of props to obj. This addition is
35 // "prototype extension safe", so that instances of objects will not
36 // pass along prototype defaults.
39 // the "tobj" condition avoid copying properties in "props"
40 // inherited from Object.prototype. For example, if obj has a custom
41 // toString() method, don't overwrite it with the toString() method
42 // that props inherited from Object.protoype
43 if(tobj[x] === undefined || tobj[x] != props[x]){
47 // IE doesn't recognize custom toStrings in for..in
50 && (typeof props["toString"] == "function")
51 && (props["toString"] != obj["toString"])
52 && (props["toString"] != tobj["toString"])
54 obj.toString = props.toString;
59 doh.mixin = function(/*Object*/obj, /*Object...*/props){
60 // summary: Adds all properties and methods of props to obj.
61 for(var i=1, l=arguments.length; i<l; i++){
62 doh._mixin(obj, arguments[i]);
67 doh.extend = function(/*Object*/ constructor, /*Object...*/ props){
69 // Adds all properties and methods of props to constructor's
70 // prototype, making them available to all instances created with
72 for(var i=1, l=arguments.length; i<l; i++){
73 doh._mixin(constructor.prototype, arguments[i]);
75 return constructor; // Object
79 doh._line = "------------------------------------------------------------";
81 doh.debug = function(){
82 var a = Array.prototype.slice.call(arguments, 0);
84 doh._print(a.join(" "))
87 doh._AssertFailure = function(msg, hint){
88 // idea for this as way of dis-ambiguating error types is from JUM.
89 // The JUM is dead! Long live the JUM!
91 if(!(this instanceof doh._AssertFailure)){
92 return new doh._AssertFailure(msg);
95 msg = (new String(msg||""))+" with hint: \n\t\t"+(new String(hint)+"\n");
97 this.message = new String(msg||"");
100 doh._AssertFailure.prototype = new Error();
101 doh._AssertFailure.prototype.constructor = doh._AssertFailure;
102 doh._AssertFailure.prototype.name = "doh._AssertFailure";
104 doh.Deferred = function(canceller){
106 this.id = this._nextId();
109 this.results = [null, null];
110 this.canceller = canceller;
111 this.silentlyCancelled = false;
114 doh.extend(doh.Deferred, {
115 getTestErrback: function(cb, scope){
117 // Replaces outer getTextCallback's in nested situations to avoid multiple
122 cb.apply(scope||doh.global||_this, arguments);
129 getTestCallback: function(cb, scope){
133 cb.apply(scope||doh.global||_this, arguments);
138 _this.callback(true);
142 getFunctionFromArgs: function(){
145 if(typeof a[0] == "function"){
147 }else if(typeof a[0] == "string"){
148 return doh.global[a[0]];
150 }else if((a[0])&&(a[1])){
151 return doh.hitch(a[0], a[1]);
156 makeCalled: function() {
157 var deferred = new doh.Deferred();
162 _nextId: (function(){
164 return function(){ return n++; };
168 if(this.fired == -1){
170 this.canceller(this);
172 this.silentlyCancelled = true;
174 if(this.fired == -1){
175 this.errback(new Error("Deferred(unfired)"));
177 }else if(this.fired == 0 &&
178 (this.results[0] instanceof doh.Deferred)){
179 this.results[0].cancel();
188 _unpause: function(){
190 if ((this.paused == 0) && (this.fired >= 0)) {
195 _continue: function(res){
200 _resback: function(res){
201 this.fired = ((res instanceof Error) ? 1 : 0);
202 this.results[this.fired] = res;
207 if(this.fired != -1){
208 if(!this.silentlyCancelled){
209 throw new Error("already called!");
211 this.silentlyCancelled = false;
216 callback: function(res){
221 errback: function(res){
223 if(!(res instanceof Error)){
224 res = new Error(res);
229 addBoth: function(cb, cbfn){
230 var enclosed = this.getFunctionFromArgs(cb, cbfn);
231 if(arguments.length > 2){
232 enclosed = doh.hitch(null, enclosed, arguments, 2);
234 return this.addCallbacks(enclosed, enclosed);
237 addCallback: function(cb, cbfn){
238 var enclosed = this.getFunctionFromArgs(cb, cbfn);
239 if(arguments.length > 2){
240 enclosed = doh.hitch(null, enclosed, arguments, 2);
242 return this.addCallbacks(enclosed, null);
245 addErrback: function(cb, cbfn){
246 var enclosed = this.getFunctionFromArgs(cb, cbfn);
247 if(arguments.length > 2){
248 enclosed = doh.hitch(null, enclosed, arguments, 2);
250 return this.addCallbacks(null, enclosed);
253 addCallbacks: function(cb, eb){
254 this.chain.push([cb, eb]);
262 var chain = this.chain;
263 var fired = this.fired;
264 var res = this.results[fired];
267 while(chain.length > 0 && this.paused == 0){
269 var pair = chain.shift();
276 fired = ((res instanceof Error) ? 1 : 0);
277 if(res instanceof doh.Deferred){
289 this.results[fired] = res;
290 if((cb)&&(this.paused)){
297 // State Keeping and Reporting
303 doh._failureCount = 0;
304 doh._currentGroup = null;
305 doh._currentTest = null;
308 doh._init = function(){
309 this._currentGroup = null;
310 this._currentTest = null;
311 this._errorCount = 0;
312 this._failureCount = 0;
313 this.debug(this._testCount, "tests to run in", this._groupCount, "groups");
323 doh.registerTestNs = function(/*String*/ group, /*Object*/ ns){
325 // adds the passed namespace object to the list of objects to be
326 // searched for test groups. Only "public" functions (not prefixed
327 // with "_") will be added as tests to be run. If you'd like to use
328 // fixtures (setUp(), tearDown(), and runTest()), please use
329 // registerTest() or registerTests().
331 if( (x.charAt(0) != "_") &&
332 (typeof ns[x] == "function") ){
333 this.registerTest(group, ns[x]);
338 doh._testRegistered = function(group, fixture){
339 // slot to be filled in
342 doh._groupStarted = function(group){
343 // slot to be filled in
346 doh._groupFinished = function(group, success){
347 // slot to be filled in
350 doh._testStarted = function(group, fixture){
351 // slot to be filled in
354 doh._testFinished = function(group, fixture, success){
355 // slot to be filled in
358 doh.registerGroup = function( /*String*/ group,
359 /*Array||Function||Object*/ tests,
361 /*Function*/ tearDown){
363 // registers an entire group of tests at once and provides a setUp and
364 // tearDown facility for groups. If you call this method with only
365 // setUp and tearDown parameters, they will replace previously
366 // installed setUp or tearDown functions for the group with the new
369 // string name of the group
371 // either a function or an object or an array of functions/objects. If
372 // an object, it must contain at *least* a "runTest" method, and may
373 // also contain "setUp" and "tearDown" methods. These will be invoked
374 // on either side of the "runTest" method (respectively) when the test
375 // is run. If an array, it must contain objects matching the above
376 // description or test functions.
377 // setUp: a function for initializing the test group
378 // tearDown: a function for initializing the test group
380 this.register(group, tests);
383 this._groups[group].setUp = setUp;
386 this._groups[group].tearDown = tearDown;
390 doh._getTestObj = function(group, test){
392 if(typeof test == "string"){
393 if(test.substr(0, 4)=="url:"){
394 return this.registerUrl(group, test);
397 name: test.replace("/\s/g", "_") // FIXME: bad escapement
399 tObj.runTest = new Function("t", test);
401 }else if(typeof test == "function"){
402 // if we didn't get a fixture, wrap the function
403 tObj = { "runTest": test };
405 tObj.name = test.name;
408 var fStr = "function ";
409 var ts = tObj.runTest+"";
410 if(0 <= ts.indexOf(fStr)){
411 tObj.name = ts.split(fStr)[1].split("(", 1)[0];
413 // doh.debug(tObj.runTest.toSource());
417 // FIXME: try harder to get the test name here
422 doh.registerTest = function(/*String*/ group, /*Function||Object*/ test){
424 // add the provided test function or fixture object to the specified
427 // string name of the group to add the test to
429 // either a function or an object. If an object, it must contain at
430 // *least* a "runTest" method, and may also contain "setUp" and
431 // "tearDown" methods. These will be invoked on either side of the
432 // "runTest" method (respectively) when the test is run.
433 if(!this._groups[group]){
435 this._groups[group] = [];
436 this._groups[group].inFlight = 0;
438 var tObj = this._getTestObj(group, test);
439 if(!tObj){ return null; }
440 this._groups[group].push(tObj);
442 this._testRegistered(group, tObj);
446 doh.registerTests = function(/*String*/ group, /*Array*/ testArr){
448 // registers a group of tests, treating each element of testArr as
449 // though it were being (along with group) passed to the registerTest
451 for(var x=0; x<testArr.length; x++){
452 this.registerTest(group, testArr[x]);
456 // FIXME: remove the doh.add alias SRTL.
457 doh.register = doh.add = function(groupOrNs, testOrNull){
459 // "magical" variant of registerTests, registerTest, and
460 // registerTestNs. Will accept the calling arguments of any of these
461 // methods and will correctly guess the right one to register with.
462 if( (arguments.length == 1)&&
463 (typeof groupOrNs == "string") ){
464 if(groupOrNs.substr(0, 4)=="url:"){
465 this.registerUrl(groupOrNs);
467 this.registerTest("ungrouped", groupOrNs);
470 if(arguments.length == 1){
471 this.debug("invalid args passed to doh.register():",
472 groupOrNs, ",", testOrNull);
475 if(typeof testOrNull == "string"){
476 if(testOrNull.substr(0, 4)=="url:"){
477 this.registerUrl(testOrNull);
479 this.registerTest(groupOrNs, testOrNull);
481 // this.registerTestNs(groupOrNs, testOrNull);
484 if(doh._isArray(testOrNull)){
485 this.registerTests(groupOrNs, testOrNull);
488 this.registerTest(groupOrNs, testOrNull);
492 // Assertions and In-Test Utilities
495 doh.t = doh.assertTrue = function(/*Object*/ condition, /*String?*/ hint){
497 // is the passed item "truthy"?
498 if(arguments.length < 1){
499 throw new doh._AssertFailure("assertTrue failed because it was not passed at least 1 argument");
501 if(!eval(condition)){
502 throw new doh._AssertFailure("assertTrue('" + condition + "') failed", hint);
506 doh.f = doh.assertFalse = function(/*Object*/ condition, /*String?*/ hint){
508 // is the passed item "falsey"?
509 if(arguments.length < 1){
510 throw new doh._AssertFailure("assertFalse failed because it was not passed at least 1 argument");
513 throw new doh._AssertFailure("assertFalse('" + condition + "') failed", hint);
517 doh.e = doh.assertError = function(/*Error object*/expectedError, /*Object*/scope, /*String*/functionName, /*Array*/args, /*String?*/ hint){
519 // Test for a certain error to be thrown by the given function.
521 // t.assertError(dojox.data.QueryReadStore.InvalidAttributeError, store, "getValue", [item, "NOT THERE"]);
522 // t.assertError(dojox.data.QueryReadStore.InvalidItemError, store, "getValue", ["not an item", "NOT THERE"]);
524 scope[functionName].apply(scope, args);
526 if(e instanceof expectedError){
529 throw new doh._AssertFailure("assertError() failed:\n\texpected error\n\t\t"+expectedError+"\n\tbut got\n\t\t"+e+"\n\n", hint);
532 throw new doh._AssertFailure("assertError() failed:\n\texpected error\n\t\t"+expectedError+"\n\tbut no error caught\n\n", hint);
536 doh.is = doh.assertEqual = function(/*Object*/ expected, /*Object*/ actual, /*String?*/ hint){
538 // are the passed expected and actual objects/values deeply
541 // Compare undefined always with three equal signs, because undefined==null
542 // is true, but undefined===null is false.
543 if((expected === undefined)&&(actual === undefined)){
546 if(arguments.length < 2){
547 throw doh._AssertFailure("assertEqual failed because it was not passed 2 arguments");
549 if((expected === actual)||(expected == actual)){
552 if( (this._isArray(expected) && this._isArray(actual))&&
553 (this._arrayEq(expected, actual)) ){
556 if( ((typeof expected == "object")&&((typeof actual == "object")))&&
557 (this._objPropEq(expected, actual)) ){
560 throw new doh._AssertFailure("assertEqual() failed:\n\texpected\n\t\t"+expected+"\n\tbut got\n\t\t"+actual+"\n\n", hint);
563 doh.isNot = doh.assertNotEqual = function(/*Object*/ notExpected, /*Object*/ actual, /*String?*/ hint){
565 // are the passed notexpected and actual objects/values deeply
568 // Compare undefined always with three equal signs, because undefined==null
569 // is true, but undefined===null is false.
570 if((notExpected === undefined)&&(actual === undefined)){
571 throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
573 if(arguments.length < 2){
574 throw doh._AssertFailure("assertEqual failed because it was not passed 2 arguments");
576 if((notExpected === actual)||(notExpected == actual)){
577 throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
579 if( (this._isArray(notExpected) && this._isArray(actual))&&
580 (this._arrayEq(notExpected, actual)) ){
581 throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
583 if( ((typeof notExpected == "object")&&((typeof actual == "object")))&&
584 (this._objPropEq(notExpected, actual)) ){
585 throw new doh._AssertFailure("assertNotEqual() failed: not expected |"+notExpected+"| but got |"+actual+"|", hint);
590 doh._arrayEq = function(expected, actual){
591 if(expected.length != actual.length){ return false; }
592 // FIXME: we're not handling circular refs. Do we care?
593 for(var x=0; x<expected.length; x++){
594 if(!doh.assertEqual(expected[x], actual[x])){ return false; }
599 doh._objPropEq = function(expected, actual){
600 // Degenerate case: if they are both null, then their "properties" are equal.
601 if(expected === null && actual === null){
604 // If only one is null, they aren't equal.
605 if(expected === null || actual === null){
608 if(expected instanceof Date){
609 return actual instanceof Date && expected.getTime()==actual.getTime();
612 // Make sure ALL THE SAME properties are in both objects!
613 for(x in actual){ // Lets check "actual" here, expected is checked below.
614 if(expected[x] === undefined){
620 if(!doh.assertEqual(expected[x], actual[x])){
627 doh._isArray = function(it){
628 return (it && it instanceof Array || typeof it == "array" ||
630 !!doh.global["dojo"] &&
631 doh.global["dojo"]["NodeList"] !== undefined &&
632 it instanceof doh.global["dojo"]["NodeList"]
641 doh._setupGroupForRun = function(/*String*/ groupName, /*Integer*/ idx){
642 var tg = this._groups[groupName];
643 this.debug(this._line);
644 this.debug("GROUP", "\""+groupName+"\"", "has", tg.length, "test"+((tg.length > 1) ? "s" : "")+" to run");
647 doh._handleFailure = function(groupName, fixture, e){
648 // this.debug("FAILED test:", fixture.name);
649 // mostly borrowed from JUM
650 this._groups[groupName].failures++;
652 if(e instanceof this._AssertFailure){
653 this._failureCount++;
654 if(e["fileName"]){ out += e.fileName + ':'; }
655 if(e["lineNumber"]){ out += e.lineNumber + ' '; }
656 out += e+": "+e.message;
657 this.debug("\t_AssertFailure:", out);
662 if(fixture.runTest["toSource"]){
663 var ss = fixture.runTest.toSource();
664 this.debug(" ", fixture.name||ss);
669 this.debug(" ", fixture.name||fixture.runTest);
675 if(e.rhinoException){
676 e.rhinoException.printStackTrace();
677 }else if(e.javaException){
678 e.javaException.printStackTrace();
683 setTimeout(function(){}, 0);
685 setTimeout = function(func){
690 doh._runFixture = function(groupName, fixture){
691 var tg = this._groups[groupName];
692 this._testStarted(groupName, fixture);
695 // run it, catching exceptions and reporting them
697 // let doh reference "this.group.thinger..." which can be set by
698 // another test or group-level setUp function
700 // only execute the parts of the fixture we've got
701 doh.debug("TEST:", fixture.name);
702 if(fixture["setUp"]){ fixture.setUp(this); }
703 if(fixture["runTest"]){ // should we error out of a fixture doesn't have a runTest?
704 fixture.startTime = new Date();
705 var ret = fixture.runTest(this);
706 fixture.endTime = new Date();
707 // if we get a deferred back from the test runner, we know we're
708 // gonna wait for an async result. It's up to the test code to trap
709 // errors and give us an errback or callback.
710 if(ret instanceof doh.Deferred){
713 ret.groupName = groupName;
714 ret.fixture = fixture;
716 ret.addErrback(function(err){
717 doh._handleFailure(groupName, fixture, err);
720 var retEnd = function(){
721 if(fixture["tearDown"]){ fixture.tearDown(doh); }
723 if((!tg.inFlight)&&(tg.iterated)){
724 doh._groupFinished(groupName, !tg.failures);
726 doh._testFinished(groupName, fixture, ret.results[0]);
732 var timer = setTimeout(function(){
735 ret.errback(new Error("test timeout in "+fixture.name.toString()));
736 }, fixture["timeout"]||1000);
738 ret.addBoth(function(arg){
748 if(fixture["tearDown"]){ fixture.tearDown(this); }
752 if(!fixture.endTime){
753 fixture.endTime = new Date();
756 var d = new doh.Deferred();
757 setTimeout(this.hitch(this, function(){
759 this._handleFailure(groupName, fixture, err);
761 this._testFinished(groupName, fixture, !threw);
763 if((!tg.inFlight)&&(tg.iterated)){
764 doh._groupFinished(groupName, !tg.failures);
765 }else if(tg.inFlight > 0){
766 setTimeout(this.hitch(this, function(){
767 doh.runGroup(groupName); // , idx);
780 doh.runGroup = function(/*String*/ groupName, /*Integer*/ idx){
782 // runs the specified test group
784 // the general structure of the algorithm is to run through the group's
785 // list of doh, checking before and after each of them to see if we're in
786 // a paused state. This can be caused by the test returning a deferred or
787 // the user hitting the pause button. In either case, we want to halt
788 // execution of the test until something external to us restarts it. This
789 // means we need to pickle off enough state to pick up where we left off.
791 // FIXME: need to make fixture execution async!!
793 var tg = this._groups[groupName];
794 if(tg.skip === true){ return; }
795 if(this._isArray(tg)){
797 if((!tg.inFlight)&&(tg.iterated == true)){
798 if(tg["tearDown"]){ tg.tearDown(this); }
799 doh._groupFinished(groupName, !tg.failures);
808 doh._groupStarted(groupName);
810 this._setupGroupForRun(groupName, idx);
811 if(tg["setUp"]){ tg.setUp(this); }
813 for(var y=(idx||0); y<tg.length; y++){
815 this._currentTest = y;
816 // this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
819 doh._runFixture(groupName, tg[y]);
821 this._currentTest = y+1;
822 if(this._currentTest == tg.length){
825 // this.debug("PAUSED at:", tg[y].name, this._currentGroup, this._currentTest);
831 if(tg["tearDown"]){ tg.tearDown(this); }
832 doh._groupFinished(groupName, !tg.failures);
837 doh._onEnd = function(){}
839 doh._report = function(){
841 // a private method to be implemented/replaced by the "locally
842 // appropriate" test runner
844 // this.debug("ERROR:");
845 // this.debug("\tNO REPORTING OUTPUT AVAILABLE.");
846 // this.debug("\tIMPLEMENT doh._report() IN YOUR TEST RUNNER");
848 this.debug(this._line);
849 this.debug("| TEST SUMMARY:");
850 this.debug(this._line);
851 this.debug("\t", this._testCount, "tests in", this._groupCount, "groups");
852 this.debug("\t", this._errorCount, "errors");
853 this.debug("\t", this._failureCount, "failures");
856 doh.togglePaused = function(){
857 this[(this._paused) ? "run" : "pause"]();
860 doh.pause = function(){
862 // halt test run. Can be resumed.
866 doh.run = function(){
868 // begins or resumes the test process.
869 // console.log("STARTING");
870 this._paused = false;
871 var cg = this._currentGroup;
872 var ct = this._currentTest;
875 this._init(); // we weren't paused
878 this._currentGroup = null;
879 this._currentTest = null;
881 for(var x in this._groups){
883 ( (!found)&&(x == cg) )||( found )
885 if(this._paused){ return; }
886 this._currentGroup = x;
889 this.runGroup(x, ct);
893 if(this._paused){ return; }
896 this._currentGroup = null;
897 this._currentTest = null;
898 this._paused = false;
904 }; //end of definition of doh/runner, which really defines global doh
906 // this is guaranteed in the global scope, not matter what kind of eval is
907 // thrown at us define global doh
908 if(typeof doh == "undefined") {
911 if(typeof define == "undefined" || define.vendor=="dojotoolkit.org") {
912 // using dojo 1.x loader or no dojo on the page
915 // using an AMD loader
916 doh.runnerFactory = d;
919 }).call(null, typeof arguments=="undefined" ?
920 [] : Array.prototype.slice.call(arguments)