| | 15 | |
| | 16 | tests.hitch = function(/*Object*/thisObject, /*Function|String*/method /*, ...*/){ |
| | 17 | var args = []; |
| | 18 | for(var x=2; x<arguments.length; x++){ |
| | 19 | args.push(arguments[x]); |
| | 20 | } |
| | 21 | var fcn = ((typeof method == "string") ? thisObject[method] : method) || function(){}; |
| | 22 | return function(){ |
| | 23 | var ta = args.concat([]); // make a copy |
| | 24 | for(var x=0; x<arguments.length; x++){ |
| | 25 | ta.push(arguments[x]); |
| | 26 | } |
| | 27 | return fcn.apply(thisObject, ta); // Function |
| | 28 | }; |
| | 29 | } |
| | 30 | |
| | 31 | tests._mixin = function(/*Object*/ obj, /*Object*/ props){ |
| | 32 | // summary: |
| | 33 | // Adds all properties and methods of props to obj. This addition is |
| | 34 | // "prototype extension safe", so that instances of objects will not |
| | 35 | // pass along prototype defaults. |
| | 36 | var tobj = {}; |
| | 37 | for(var x in props){ |
| | 38 | // the "tobj" condition avoid copying properties in "props" |
| | 39 | // inherited from Object.prototype. For example, if obj has a custom |
| | 40 | // toString() method, don't overwrite it with the toString() method |
| | 41 | // that props inherited from Object.protoype |
| | 42 | if((typeof tobj[x] == "undefined") || (tobj[x] != props[x])){ |
| | 43 | obj[x] = props[x]; |
| | 44 | } |
| | 45 | } |
| | 46 | // IE doesn't recognize custom toStrings in for..in |
| | 47 | if( this["document"] |
| | 48 | && document.all |
| | 49 | && (typeof props["toString"] == "function") |
| | 50 | && (props["toString"] != obj["toString"]) |
| | 51 | && (props["toString"] != tobj["toString"]) |
| | 52 | ){ |
| | 53 | obj.toString = props.toString; |
| | 54 | } |
| | 55 | return obj; // Object |
| | 56 | } |
| | 57 | |
| | 58 | tests.mixin = function(/*Object*/obj, /*Object...*/props){ |
| | 59 | // summary: Adds all properties and methods of props to obj. |
| | 60 | for(var i=1, l=arguments.length; i<l; i++){ |
| | 61 | tests._mixin(obj, arguments[i]); |
| | 62 | } |
| | 63 | return obj; // Object |
| | 64 | } |
| | 65 | |
| | 66 | tests.extend = function(/*Object*/ constructor, /*Object...*/ props){ |
| | 67 | // summary: |
| | 68 | // Adds all properties and methods of props to constructor's |
| | 69 | // prototype, making them available to all instances created with |
| | 70 | // constructor. |
| | 71 | for(var i=1, l=arguments.length; i<l; i++){ |
| | 72 | tests._mixin(constructor.prototype, arguments[i]); |
| | 73 | } |
| | 74 | return constructor; // Object |
| | 75 | } |
| | 76 | |
| | 115 | tests.Deferred = function(canceller){ |
| | 116 | this.chain = []; |
| | 117 | this.id = this._nextId(); |
| | 118 | this.fired = -1; |
| | 119 | this.paused = 0; |
| | 120 | this.results = [null, null]; |
| | 121 | this.canceller = canceller; |
| | 122 | this.silentlyCancelled = false; |
| | 123 | }; |
| | 124 | |
| | 125 | tests.extend(tests.Deferred, { |
| | 126 | getFunctionFromArgs: function(){ |
| | 127 | var a = arguments; |
| | 128 | if((a[0])&&(!a[1])){ |
| | 129 | if(typeof a[0] == "function"){ |
| | 130 | return a[0]; |
| | 131 | }else if(typeof a[0] == "string"){ |
| | 132 | return dj_global[a[0]]; |
| | 133 | } |
| | 134 | }else if((a[0])&&(a[1])){ |
| | 135 | return tests.hitch(a[0], a[1]); |
| | 136 | } |
| | 137 | return null; |
| | 138 | }, |
| | 139 | |
| | 140 | makeCalled: function() { |
| | 141 | var deferred = new tests.Deferred(); |
| | 142 | deferred.callback(); |
| | 143 | return deferred; |
| | 144 | }, |
| | 145 | |
| | 146 | _nextId: (function(){ |
| | 147 | var n = 1; |
| | 148 | return function(){ return n++; }; |
| | 149 | })(), |
| | 150 | |
| | 151 | cancel: function(){ |
| | 152 | if(this.fired == -1){ |
| | 153 | if (this.canceller){ |
| | 154 | this.canceller(this); |
| | 155 | }else{ |
| | 156 | this.silentlyCancelled = true; |
| | 157 | } |
| | 158 | if(this.fired == -1){ |
| | 159 | this.errback(new Error("Deferred(unfired)")); |
| | 160 | } |
| | 161 | }else if( (this.fired == 0)&& |
| | 162 | (this.results[0] instanceof tests.Deferred)){ |
| | 163 | this.results[0].cancel(); |
| | 164 | } |
| | 165 | }, |
| | 166 | |
| | 167 | |
| | 168 | _pause: function(){ |
| | 169 | this.paused++; |
| | 170 | }, |
| | 171 | |
| | 172 | _unpause: function(){ |
| | 173 | this.paused--; |
| | 174 | if ((this.paused == 0) && (this.fired >= 0)) { |
| | 175 | this._fire(); |
| | 176 | } |
| | 177 | }, |
| | 178 | |
| | 179 | _continue: function(res){ |
| | 180 | this._resback(res); |
| | 181 | this._unpause(); |
| | 182 | }, |
| | 183 | |
| | 184 | _resback: function(res){ |
| | 185 | this.fired = ((res instanceof Error) ? 1 : 0); |
| | 186 | this.results[this.fired] = res; |
| | 187 | this._fire(); |
| | 188 | }, |
| | 189 | |
| | 190 | _check: function(){ |
| | 191 | if(this.fired != -1){ |
| | 192 | if(!this.silentlyCancelled){ |
| | 193 | tests.raise("already called!"); |
| | 194 | } |
| | 195 | this.silentlyCancelled = false; |
| | 196 | return; |
| | 197 | } |
| | 198 | }, |
| | 199 | |
| | 200 | callback: function(res){ |
| | 201 | this._check(); |
| | 202 | this._resback(res); |
| | 203 | }, |
| | 204 | |
| | 205 | errback: function(res){ |
| | 206 | this._check(); |
| | 207 | if(!(res instanceof Error)){ |
| | 208 | res = new Error(res); |
| | 209 | } |
| | 210 | this._resback(res); |
| | 211 | }, |
| | 212 | |
| | 213 | addBoth: function(cb, cbfn){ |
| | 214 | var enclosed = this.getFunctionFromArgs(cb, cbfn); |
| | 215 | if(arguments.length > 2){ |
| | 216 | enclosed = tests.hitch(null, enclosed, arguments, 2); |
| | 217 | } |
| | 218 | return this.addCallbacks(enclosed, enclosed); |
| | 219 | }, |
| | 220 | |
| | 221 | addCallback: function(cb, cbfn){ |
| | 222 | var enclosed = this.getFunctionFromArgs(cb, cbfn); |
| | 223 | if(arguments.length > 2){ |
| | 224 | enclosed = tests.hitch(null, enclosed, arguments, 2); |
| | 225 | } |
| | 226 | return this.addCallbacks(enclosed, null); |
| | 227 | }, |
| | 228 | |
| | 229 | addErrback: function(cb, cbfn){ |
| | 230 | var enclosed = this.getFunctionFromArgs(cb, cbfn); |
| | 231 | if(arguments.length > 2){ |
| | 232 | enclosed = tests.hitch(null, enclosed, arguments, 2); |
| | 233 | } |
| | 234 | return this.addCallbacks(null, enclosed); |
| | 235 | }, |
| | 236 | |
| | 237 | addCallbacks: function(cb, eb){ |
| | 238 | this.chain.push([cb, eb]) |
| | 239 | if(this.fired >= 0){ |
| | 240 | this._fire(); |
| | 241 | } |
| | 242 | return this; |
| | 243 | }, |
| | 244 | |
| | 245 | _fire: function(){ |
| | 246 | var chain = this.chain; |
| | 247 | var fired = this.fired; |
| | 248 | var res = this.results[fired]; |
| | 249 | var self = this; |
| | 250 | var cb = null; |
| | 251 | while (chain.length > 0 && this.paused == 0) { |
| | 252 | // Array |
| | 253 | var pair = chain.shift(); |
| | 254 | var f = pair[fired]; |
| | 255 | if (f == null) { |
| | 256 | continue; |
| | 257 | } |
| | 258 | try { |
| | 259 | res = f(res); |
| | 260 | fired = ((res instanceof Error) ? 1 : 0); |
| | 261 | if(res instanceof tests.Deferred) { |
| | 262 | cb = function(res){ |
| | 263 | self._continue(res); |
| | 264 | } |
| | 265 | this._pause(); |
| | 266 | } |
| | 267 | }catch(err){ |
| | 268 | fired = 1; |
| | 269 | res = err; |
| | 270 | } |
| | 271 | } |
| | 272 | this.fired = fired; |
| | 273 | this.results[fired] = res; |
| | 274 | if((cb)&&(this.paused)){ |
| | 275 | res.addBoth(cb); |
| | 276 | } |
| | 277 | } |
| | 278 | }); |
| | 279 | |
| 258 | | } |
| 259 | | |
| | 484 | var tg = this._groups[groupName]; |
| | 485 | this.debug(this._line, "\nGROUP", "\""+groupName+"\"", "has", tg.length, "test"+((tg.length > 1) ? "s" : "")+" to run"); |
| | 486 | } |
| | 487 | |
| | 488 | tests._handleFailure = function(groupName, fixture, e){ |
| | 489 | // this.debug("FAILED test:", fixture.name); |
| | 490 | // mostly borrowed from JUM |
| | 491 | this._groups[groupName].failures++; |
| | 492 | var out = ""; |
| | 493 | if(e instanceof this._AssertFailure){ |
| | 494 | this._failureCount++; |
| | 495 | if(e["fileName"]){ out += e.fileName + ':'; } |
| | 496 | if(e["lineNumber"]){ out += e.lineNumber + ' '; } |
| | 497 | out += e.message; |
| | 498 | this.debug("\t_AssertFailure:", out); |
| | 499 | }else{ |
| | 500 | this._errorCount++; |
| | 501 | } |
| | 502 | if(fixture.runTest["toSource"]){ |
| | 503 | var ss = fixture.runTest.toSource(); |
| | 504 | this.debug("\tERROR IN:\n\t\t", ss); |
| | 505 | } |
| | 506 | } |
| | 507 | |
| | 508 | tests._runFixture = function(groupName, fixture){ |
| | 509 | var tg = this._groups[groupName]; |
| | 510 | this._testStarted(groupName, fixture); |
| | 511 | var threw = false; |
| | 512 | // run it, catching exceptions and reporting them |
| | 513 | try{ |
| | 514 | if(fixture["setUp"]){ fixture.setUp(this); } |
| | 515 | if(fixture["runTest"]){ |
| | 516 | var ret = fixture.runTest(this); |
| | 517 | // if we get a deferred back from the test runner, we know we're |
| | 518 | // gonna wait for an async result. It's up to the test code to trap |
| | 519 | // errors and give us an errback or callback. |
| | 520 | if(ret instanceof this.Deferred){ |
| | 521 | |
| | 522 | tg.inFlight++; |
| | 523 | |
| | 524 | ret.addErrback(function(err){ |
| | 525 | tests._handleFailure(groupName, fixture, err); |
| | 526 | }); |
| | 527 | |
| | 528 | var retEnd = function(){ |
| | 529 | if(fixture["tearDown"]){ fixture.tearDown(tests); } |
| | 530 | tg.inFlight--; |
| | 531 | if((!tg.inFlight)&&(tg.iterated)){ |
| | 532 | tests._groupFinished(groupName, (!tg.failures)); |
| | 533 | } |
| | 534 | tests._testFinished(groupName, fixture, ret.results[0]); |
| | 535 | } |
| | 536 | |
| | 537 | var timer = setTimeout(function(){ |
| | 538 | ret.cancel(); |
| | 539 | retEnd(); |
| | 540 | }, fixture["timeout"]||500); |
| | 541 | |
| | 542 | ret.addBoth(function(arg){ |
| | 543 | clearTimeout(timer); |
| | 544 | retEnd(); |
| | 545 | }); |
| | 546 | return ret; |
| | 547 | } |
| | 548 | } |
| | 549 | if(fixture["tearDown"]){ fixture.tearDown(this); } |
| | 550 | }catch(e){ |
| | 551 | threw = true; |
| | 552 | this._handleFailure(groupName, fixture, e); |
| | 553 | } |
| | 554 | this._testFinished(groupName, fixture, (!threw)); |
| | 555 | } |
| | 556 | |
| | 557 | tests._testId = 0; |
| 275 | | var tt = tg[y]; |
| 276 | | var threw = false; |
| 277 | | // run it, catching exceptions and reporting them |
| 278 | | try{ |
| 279 | | if(tt["setUp"]){ tt.setUp(this); } |
| 280 | | if(tt["runTest"]){ tt.runTest(this); } |
| 281 | | if(tt["tearDown"]){ tt.tearDown(this); } |
| 282 | | this._passedCount++; |
| 283 | | }catch(e){ |
| 284 | | var threw = true; |
| 285 | | this.debug("FAILED test:", y); |
| 286 | | // mostly borrowed from JUM |
| 287 | | var out = ""; |
| 288 | | if(e instanceof this._AssertFailure){ |
| 289 | | this._failureCount++; |
| 290 | | if(e["fileName"]){ out += e.fileName + ':'; } |
| 291 | | if(e["lineNumber"]){ out += e.lineNumber + ' '; } |
| 292 | | out += e.message; |
| 293 | | this.debug("\t_AssertFailure:", out); |
| 294 | | }else{ |
| 295 | | this._errorCount++; |
| 296 | | } |
| 297 | | // this.debug(e); |
| 298 | | if(tt.runTest["toSource"]){ |
| 299 | | var ss = tt.runTest.toSource(); |
| 300 | | // var ss = tt.runTest.toSource().split("{", 2)[1]; |
| 301 | | // ss = ss.substr(0, ss.lastIndexOf("}")); |
| 302 | | this.debug("\tERROR IN:\n\t\t", ss); |
| 303 | | } |
| 304 | | /* |
| 305 | | for(var x in e){ |
| 306 | | this.debug("\t", x, ":", x[e]); |
| 307 | | } |
| 308 | | throw e; |
| 309 | | */ |
| 310 | | } |
| 311 | | if(!threw){ |
| 312 | | this.debug("PASSED test:", y); |
| 313 | | } |
| | 579 | tests._runFixture(groupName, tg[y]); |
| | 580 | } |
| | 581 | tg.iterated = true; |
| | 582 | if(!tg.inFlight){ |
| | 583 | tests._groupFinished(groupName, (!tg.failures)); |