1 | /** |
---|
2 | * QUnit - A JavaScript Unit Testing Framework |
---|
3 | * |
---|
4 | * http://docs.jquery.com/QUnit |
---|
5 | * |
---|
6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer |
---|
7 | * Dual licensed under the MIT (MIT-LICENSE.txt) |
---|
8 | * or GPL (GPL-LICENSE.txt) licenses. |
---|
9 | */ |
---|
10 | |
---|
11 | (function(window) { |
---|
12 | |
---|
13 | var defined = { |
---|
14 | setTimeout: typeof window.setTimeout !== "undefined", |
---|
15 | sessionStorage: (function() { |
---|
16 | try { |
---|
17 | return !!sessionStorage.getItem; |
---|
18 | } catch(e){ |
---|
19 | return false; |
---|
20 | } |
---|
21 | })() |
---|
22 | }; |
---|
23 | |
---|
24 | var testId = 0; |
---|
25 | |
---|
26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { |
---|
27 | this.name = name; |
---|
28 | this.testName = testName; |
---|
29 | this.expected = expected; |
---|
30 | this.testEnvironmentArg = testEnvironmentArg; |
---|
31 | this.async = async; |
---|
32 | this.callback = callback; |
---|
33 | this.assertions = []; |
---|
34 | }; |
---|
35 | Test.prototype = { |
---|
36 | init: function() { |
---|
37 | var tests = id("qunit-tests"); |
---|
38 | if (tests) { |
---|
39 | var b = document.createElement("strong"); |
---|
40 | b.innerHTML = "Running " + this.name; |
---|
41 | var li = document.createElement("li"); |
---|
42 | li.appendChild( b ); |
---|
43 | li.className = "running"; |
---|
44 | li.id = this.id = "test-output" + testId++; |
---|
45 | tests.appendChild( li ); |
---|
46 | } |
---|
47 | }, |
---|
48 | setup: function() { |
---|
49 | if (this.module != config.previousModule) { |
---|
50 | if ( config.previousModule ) { |
---|
51 | QUnit.moduleDone( { |
---|
52 | name: config.previousModule, |
---|
53 | failed: config.moduleStats.bad, |
---|
54 | passed: config.moduleStats.all - config.moduleStats.bad, |
---|
55 | total: config.moduleStats.all |
---|
56 | } ); |
---|
57 | } |
---|
58 | config.previousModule = this.module; |
---|
59 | config.moduleStats = { all: 0, bad: 0 }; |
---|
60 | QUnit.moduleStart( { |
---|
61 | name: this.module |
---|
62 | } ); |
---|
63 | } |
---|
64 | |
---|
65 | config.current = this; |
---|
66 | this.testEnvironment = extend({ |
---|
67 | setup: function() {}, |
---|
68 | teardown: function() {} |
---|
69 | }, this.moduleTestEnvironment); |
---|
70 | if (this.testEnvironmentArg) { |
---|
71 | extend(this.testEnvironment, this.testEnvironmentArg); |
---|
72 | } |
---|
73 | |
---|
74 | QUnit.testStart( { |
---|
75 | name: this.testName |
---|
76 | } ); |
---|
77 | |
---|
78 | // allow utility functions to access the current test environment |
---|
79 | // TODO why?? |
---|
80 | QUnit.current_testEnvironment = this.testEnvironment; |
---|
81 | |
---|
82 | try { |
---|
83 | if ( !config.pollution ) { |
---|
84 | saveGlobal(); |
---|
85 | } |
---|
86 | |
---|
87 | this.testEnvironment.setup.call(this.testEnvironment); |
---|
88 | } catch(e) { |
---|
89 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); |
---|
90 | } |
---|
91 | }, |
---|
92 | run: function() { |
---|
93 | if ( this.async ) { |
---|
94 | QUnit.stop(); |
---|
95 | } |
---|
96 | |
---|
97 | if ( config.notrycatch ) { |
---|
98 | this.callback.call(this.testEnvironment); |
---|
99 | return; |
---|
100 | } |
---|
101 | try { |
---|
102 | this.callback.call(this.testEnvironment); |
---|
103 | } catch(e) { |
---|
104 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); |
---|
105 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); |
---|
106 | // else next test will carry the responsibility |
---|
107 | saveGlobal(); |
---|
108 | |
---|
109 | // Restart the tests if they're blocking |
---|
110 | if ( config.blocking ) { |
---|
111 | start(); |
---|
112 | } |
---|
113 | } |
---|
114 | }, |
---|
115 | teardown: function() { |
---|
116 | try { |
---|
117 | this.testEnvironment.teardown.call(this.testEnvironment); |
---|
118 | checkPollution(); |
---|
119 | } catch(e) { |
---|
120 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); |
---|
121 | } |
---|
122 | }, |
---|
123 | finish: function() { |
---|
124 | if ( this.expected && this.expected != this.assertions.length ) { |
---|
125 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); |
---|
126 | } |
---|
127 | |
---|
128 | var good = 0, bad = 0, |
---|
129 | tests = id("qunit-tests"); |
---|
130 | |
---|
131 | config.stats.all += this.assertions.length; |
---|
132 | config.moduleStats.all += this.assertions.length; |
---|
133 | |
---|
134 | if ( tests ) { |
---|
135 | var ol = document.createElement("ol"); |
---|
136 | |
---|
137 | for ( var i = 0; i < this.assertions.length; i++ ) { |
---|
138 | var assertion = this.assertions[i]; |
---|
139 | |
---|
140 | var li = document.createElement("li"); |
---|
141 | li.className = assertion.result ? "pass" : "fail"; |
---|
142 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); |
---|
143 | ol.appendChild( li ); |
---|
144 | |
---|
145 | if ( assertion.result ) { |
---|
146 | good++; |
---|
147 | } else { |
---|
148 | bad++; |
---|
149 | config.stats.bad++; |
---|
150 | config.moduleStats.bad++; |
---|
151 | } |
---|
152 | } |
---|
153 | |
---|
154 | // store result when possible |
---|
155 | if ( QUnit.config.reorder && defined.sessionStorage ) { |
---|
156 | if (bad) { |
---|
157 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); |
---|
158 | } else { |
---|
159 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); |
---|
160 | } |
---|
161 | } |
---|
162 | |
---|
163 | if (bad == 0) { |
---|
164 | ol.style.display = "none"; |
---|
165 | } |
---|
166 | |
---|
167 | var b = document.createElement("strong"); |
---|
168 | b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; |
---|
169 | |
---|
170 | var a = document.createElement("a"); |
---|
171 | a.innerHTML = "Rerun"; |
---|
172 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); |
---|
173 | |
---|
174 | addEvent(b, "click", function() { |
---|
175 | var next = b.nextSibling.nextSibling, |
---|
176 | display = next.style.display; |
---|
177 | next.style.display = display === "none" ? "block" : "none"; |
---|
178 | }); |
---|
179 | |
---|
180 | addEvent(b, "dblclick", function(e) { |
---|
181 | var target = e && e.target ? e.target : window.event.srcElement; |
---|
182 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { |
---|
183 | target = target.parentNode; |
---|
184 | } |
---|
185 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { |
---|
186 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); |
---|
187 | } |
---|
188 | }); |
---|
189 | |
---|
190 | var li = id(this.id); |
---|
191 | li.className = bad ? "fail" : "pass"; |
---|
192 | li.removeChild( li.firstChild ); |
---|
193 | li.appendChild( b ); |
---|
194 | li.appendChild( a ); |
---|
195 | li.appendChild( ol ); |
---|
196 | |
---|
197 | } else { |
---|
198 | for ( var i = 0; i < this.assertions.length; i++ ) { |
---|
199 | if ( !this.assertions[i].result ) { |
---|
200 | bad++; |
---|
201 | config.stats.bad++; |
---|
202 | config.moduleStats.bad++; |
---|
203 | } |
---|
204 | } |
---|
205 | } |
---|
206 | |
---|
207 | try { |
---|
208 | QUnit.reset(); |
---|
209 | } catch(e) { |
---|
210 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); |
---|
211 | } |
---|
212 | |
---|
213 | QUnit.testDone( { |
---|
214 | name: this.testName, |
---|
215 | failed: bad, |
---|
216 | passed: this.assertions.length - bad, |
---|
217 | total: this.assertions.length |
---|
218 | } ); |
---|
219 | }, |
---|
220 | |
---|
221 | queue: function() { |
---|
222 | var test = this; |
---|
223 | synchronize(function() { |
---|
224 | test.init(); |
---|
225 | }); |
---|
226 | function run() { |
---|
227 | // each of these can by async |
---|
228 | synchronize(function() { |
---|
229 | test.setup(); |
---|
230 | }); |
---|
231 | synchronize(function() { |
---|
232 | test.run(); |
---|
233 | }); |
---|
234 | synchronize(function() { |
---|
235 | test.teardown(); |
---|
236 | }); |
---|
237 | synchronize(function() { |
---|
238 | test.finish(); |
---|
239 | }); |
---|
240 | } |
---|
241 | // defer when previous test run passed, if storage is available |
---|
242 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); |
---|
243 | if (bad) { |
---|
244 | run(); |
---|
245 | } else { |
---|
246 | synchronize(run); |
---|
247 | }; |
---|
248 | } |
---|
249 | |
---|
250 | }; |
---|
251 | |
---|
252 | var QUnit = { |
---|
253 | |
---|
254 | // call on start of module test to prepend name to all tests |
---|
255 | module: function(name, testEnvironment) { |
---|
256 | config.currentModule = name; |
---|
257 | config.currentModuleTestEnviroment = testEnvironment; |
---|
258 | }, |
---|
259 | |
---|
260 | asyncTest: function(testName, expected, callback) { |
---|
261 | if ( arguments.length === 2 ) { |
---|
262 | callback = expected; |
---|
263 | expected = 0; |
---|
264 | } |
---|
265 | |
---|
266 | QUnit.test(testName, expected, callback, true); |
---|
267 | }, |
---|
268 | |
---|
269 | test: function(testName, expected, callback, async) { |
---|
270 | var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg; |
---|
271 | |
---|
272 | if ( arguments.length === 2 ) { |
---|
273 | callback = expected; |
---|
274 | expected = null; |
---|
275 | } |
---|
276 | // is 2nd argument a testEnvironment? |
---|
277 | if ( expected && typeof expected === 'object') { |
---|
278 | testEnvironmentArg = expected; |
---|
279 | expected = null; |
---|
280 | } |
---|
281 | |
---|
282 | if ( config.currentModule ) { |
---|
283 | name = '<span class="module-name">' + config.currentModule + "</span>: " + name; |
---|
284 | } |
---|
285 | |
---|
286 | if ( !validTest(config.currentModule + ": " + testName) ) { |
---|
287 | return; |
---|
288 | } |
---|
289 | |
---|
290 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); |
---|
291 | test.module = config.currentModule; |
---|
292 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; |
---|
293 | test.queue(); |
---|
294 | }, |
---|
295 | |
---|
296 | /** |
---|
297 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. |
---|
298 | */ |
---|
299 | expect: function(asserts) { |
---|
300 | config.current.expected = asserts; |
---|
301 | }, |
---|
302 | |
---|
303 | /** |
---|
304 | * Asserts true. |
---|
305 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); |
---|
306 | */ |
---|
307 | ok: function(a, msg) { |
---|
308 | a = !!a; |
---|
309 | var details = { |
---|
310 | result: a, |
---|
311 | message: msg |
---|
312 | }; |
---|
313 | msg = escapeHtml(msg); |
---|
314 | QUnit.log(details); |
---|
315 | config.current.assertions.push({ |
---|
316 | result: a, |
---|
317 | message: msg |
---|
318 | }); |
---|
319 | }, |
---|
320 | |
---|
321 | /** |
---|
322 | * Checks that the first two arguments are equal, with an optional message. |
---|
323 | * Prints out both actual and expected values. |
---|
324 | * |
---|
325 | * Prefered to ok( actual == expected, message ) |
---|
326 | * |
---|
327 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); |
---|
328 | * |
---|
329 | * @param Object actual |
---|
330 | * @param Object expected |
---|
331 | * @param String message (optional) |
---|
332 | */ |
---|
333 | equal: function(actual, expected, message) { |
---|
334 | QUnit.push(expected == actual, actual, expected, message); |
---|
335 | }, |
---|
336 | |
---|
337 | notEqual: function(actual, expected, message) { |
---|
338 | QUnit.push(expected != actual, actual, expected, message); |
---|
339 | }, |
---|
340 | |
---|
341 | deepEqual: function(actual, expected, message) { |
---|
342 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); |
---|
343 | }, |
---|
344 | |
---|
345 | notDeepEqual: function(actual, expected, message) { |
---|
346 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); |
---|
347 | }, |
---|
348 | |
---|
349 | strictEqual: function(actual, expected, message) { |
---|
350 | QUnit.push(expected === actual, actual, expected, message); |
---|
351 | }, |
---|
352 | |
---|
353 | notStrictEqual: function(actual, expected, message) { |
---|
354 | QUnit.push(expected !== actual, actual, expected, message); |
---|
355 | }, |
---|
356 | |
---|
357 | raises: function(block, expected, message) { |
---|
358 | var actual, ok = false; |
---|
359 | |
---|
360 | if (typeof expected === 'string') { |
---|
361 | message = expected; |
---|
362 | expected = null; |
---|
363 | } |
---|
364 | |
---|
365 | try { |
---|
366 | block(); |
---|
367 | } catch (e) { |
---|
368 | actual = e; |
---|
369 | } |
---|
370 | |
---|
371 | if (actual) { |
---|
372 | // we don't want to validate thrown error |
---|
373 | if (!expected) { |
---|
374 | ok = true; |
---|
375 | // expected is a regexp |
---|
376 | } else if (QUnit.objectType(expected) === "regexp") { |
---|
377 | ok = expected.test(actual); |
---|
378 | // expected is a constructor |
---|
379 | } else if (actual instanceof expected) { |
---|
380 | ok = true; |
---|
381 | // expected is a validation function which returns true is validation passed |
---|
382 | } else if (expected.call({}, actual) === true) { |
---|
383 | ok = true; |
---|
384 | } |
---|
385 | } |
---|
386 | |
---|
387 | QUnit.ok(ok, message); |
---|
388 | }, |
---|
389 | |
---|
390 | start: function() { |
---|
391 | config.semaphore--; |
---|
392 | if (config.semaphore > 0) { |
---|
393 | // don't start until equal number of stop-calls |
---|
394 | return; |
---|
395 | } |
---|
396 | if (config.semaphore < 0) { |
---|
397 | // ignore if start is called more often then stop |
---|
398 | config.semaphore = 0; |
---|
399 | } |
---|
400 | // A slight delay, to avoid any current callbacks |
---|
401 | if ( defined.setTimeout ) { |
---|
402 | window.setTimeout(function() { |
---|
403 | if ( config.timeout ) { |
---|
404 | clearTimeout(config.timeout); |
---|
405 | } |
---|
406 | |
---|
407 | config.blocking = false; |
---|
408 | process(); |
---|
409 | }, 13); |
---|
410 | } else { |
---|
411 | config.blocking = false; |
---|
412 | process(); |
---|
413 | } |
---|
414 | }, |
---|
415 | |
---|
416 | stop: function(timeout) { |
---|
417 | config.semaphore++; |
---|
418 | config.blocking = true; |
---|
419 | |
---|
420 | if ( timeout && defined.setTimeout ) { |
---|
421 | clearTimeout(config.timeout); |
---|
422 | config.timeout = window.setTimeout(function() { |
---|
423 | QUnit.ok( false, "Test timed out" ); |
---|
424 | QUnit.start(); |
---|
425 | }, timeout); |
---|
426 | } |
---|
427 | } |
---|
428 | }; |
---|
429 | |
---|
430 | // Backwards compatibility, deprecated |
---|
431 | QUnit.equals = QUnit.equal; |
---|
432 | QUnit.same = QUnit.deepEqual; |
---|
433 | |
---|
434 | // Maintain internal state |
---|
435 | var config = { |
---|
436 | // The queue of tests to run |
---|
437 | queue: [], |
---|
438 | |
---|
439 | // block until document ready |
---|
440 | blocking: true, |
---|
441 | |
---|
442 | // by default, run previously failed tests first |
---|
443 | // very useful in combination with "Hide passed tests" checked |
---|
444 | reorder: true, |
---|
445 | |
---|
446 | noglobals: false, |
---|
447 | notrycatch: false |
---|
448 | }; |
---|
449 | |
---|
450 | // Load paramaters |
---|
451 | (function() { |
---|
452 | var location = window.location || { search: "", protocol: "file:" }, |
---|
453 | params = location.search.slice( 1 ).split( "&" ), |
---|
454 | length = params.length, |
---|
455 | urlParams = {}, |
---|
456 | current; |
---|
457 | |
---|
458 | if ( params[ 0 ] ) { |
---|
459 | for ( var i = 0; i < length; i++ ) { |
---|
460 | current = params[ i ].split( "=" ); |
---|
461 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); |
---|
462 | // allow just a key to turn on a flag, e.g., test.html?noglobals |
---|
463 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; |
---|
464 | urlParams[ current[ 0 ] ] = current[ 1 ]; |
---|
465 | if ( current[ 0 ] in config ) { |
---|
466 | config[ current[ 0 ] ] = current[ 1 ]; |
---|
467 | } |
---|
468 | } |
---|
469 | } |
---|
470 | |
---|
471 | QUnit.urlParams = urlParams; |
---|
472 | config.filter = urlParams.filter; |
---|
473 | |
---|
474 | // Figure out if we're running the tests from a server or not |
---|
475 | QUnit.isLocal = !!(location.protocol === 'file:'); |
---|
476 | })(); |
---|
477 | |
---|
478 | // Expose the API as global variables, unless an 'exports' |
---|
479 | // object exists, in that case we assume we're in CommonJS |
---|
480 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { |
---|
481 | extend(window, QUnit); |
---|
482 | window.QUnit = QUnit; |
---|
483 | } else { |
---|
484 | extend(exports, QUnit); |
---|
485 | exports.QUnit = QUnit; |
---|
486 | } |
---|
487 | |
---|
488 | // define these after exposing globals to keep them in these QUnit namespace only |
---|
489 | extend(QUnit, { |
---|
490 | config: config, |
---|
491 | |
---|
492 | // Initialize the configuration options |
---|
493 | init: function() { |
---|
494 | extend(config, { |
---|
495 | stats: { all: 0, bad: 0 }, |
---|
496 | moduleStats: { all: 0, bad: 0 }, |
---|
497 | started: +new Date, |
---|
498 | updateRate: 1000, |
---|
499 | blocking: false, |
---|
500 | autostart: true, |
---|
501 | autorun: false, |
---|
502 | filter: "", |
---|
503 | queue: [], |
---|
504 | semaphore: 0 |
---|
505 | }); |
---|
506 | |
---|
507 | var tests = id( "qunit-tests" ), |
---|
508 | banner = id( "qunit-banner" ), |
---|
509 | result = id( "qunit-testresult" ); |
---|
510 | |
---|
511 | if ( tests ) { |
---|
512 | tests.innerHTML = ""; |
---|
513 | } |
---|
514 | |
---|
515 | if ( banner ) { |
---|
516 | banner.className = ""; |
---|
517 | } |
---|
518 | |
---|
519 | if ( result ) { |
---|
520 | result.parentNode.removeChild( result ); |
---|
521 | } |
---|
522 | |
---|
523 | if ( tests ) { |
---|
524 | result = document.createElement( "p" ); |
---|
525 | result.id = "qunit-testresult"; |
---|
526 | result.className = "result"; |
---|
527 | tests.parentNode.insertBefore( result, tests ); |
---|
528 | result.innerHTML = 'Running...<br/> '; |
---|
529 | } |
---|
530 | }, |
---|
531 | |
---|
532 | /** |
---|
533 | * Resets the test setup. Useful for tests that modify the DOM. |
---|
534 | * |
---|
535 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. |
---|
536 | */ |
---|
537 | reset: function() { |
---|
538 | if ( window.jQuery ) { |
---|
539 | jQuery( "#qunit-fixture" ).html( config.fixture ); |
---|
540 | } else { |
---|
541 | var main = id( 'qunit-fixture' ); |
---|
542 | if ( main ) { |
---|
543 | main.innerHTML = config.fixture; |
---|
544 | } |
---|
545 | } |
---|
546 | }, |
---|
547 | |
---|
548 | /** |
---|
549 | * Trigger an event on an element. |
---|
550 | * |
---|
551 | * @example triggerEvent( document.body, "click" ); |
---|
552 | * |
---|
553 | * @param DOMElement elem |
---|
554 | * @param String type |
---|
555 | */ |
---|
556 | triggerEvent: function( elem, type, event ) { |
---|
557 | if ( document.createEvent ) { |
---|
558 | event = document.createEvent("MouseEvents"); |
---|
559 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, |
---|
560 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); |
---|
561 | elem.dispatchEvent( event ); |
---|
562 | |
---|
563 | } else if ( elem.fireEvent ) { |
---|
564 | elem.fireEvent("on"+type); |
---|
565 | } |
---|
566 | }, |
---|
567 | |
---|
568 | // Safe object type checking |
---|
569 | is: function( type, obj ) { |
---|
570 | return QUnit.objectType( obj ) == type; |
---|
571 | }, |
---|
572 | |
---|
573 | objectType: function( obj ) { |
---|
574 | if (typeof obj === "undefined") { |
---|
575 | return "undefined"; |
---|
576 | |
---|
577 | // consider: typeof null === object |
---|
578 | } |
---|
579 | if (obj === null) { |
---|
580 | return "null"; |
---|
581 | } |
---|
582 | |
---|
583 | var type = Object.prototype.toString.call( obj ) |
---|
584 | .match(/^\[object\s(.*)\]$/)[1] || ''; |
---|
585 | |
---|
586 | switch (type) { |
---|
587 | case 'Number': |
---|
588 | if (isNaN(obj)) { |
---|
589 | return "nan"; |
---|
590 | } else { |
---|
591 | return "number"; |
---|
592 | } |
---|
593 | case 'String': |
---|
594 | case 'Boolean': |
---|
595 | case 'Array': |
---|
596 | case 'Date': |
---|
597 | case 'RegExp': |
---|
598 | case 'Function': |
---|
599 | return type.toLowerCase(); |
---|
600 | } |
---|
601 | if (typeof obj === "object") { |
---|
602 | return "object"; |
---|
603 | } |
---|
604 | return undefined; |
---|
605 | }, |
---|
606 | |
---|
607 | push: function(result, actual, expected, message) { |
---|
608 | var details = { |
---|
609 | result: result, |
---|
610 | message: message, |
---|
611 | actual: actual, |
---|
612 | expected: expected |
---|
613 | }; |
---|
614 | |
---|
615 | message = escapeHtml(message) || (result ? "okay" : "failed"); |
---|
616 | message = '<span class="test-message">' + message + "</span>"; |
---|
617 | expected = escapeHtml(QUnit.jsDump.parse(expected)); |
---|
618 | actual = escapeHtml(QUnit.jsDump.parse(actual)); |
---|
619 | var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; |
---|
620 | if (actual != expected) { |
---|
621 | output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; |
---|
622 | output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; |
---|
623 | } |
---|
624 | if (!result) { |
---|
625 | var source = sourceFromStacktrace(); |
---|
626 | if (source) { |
---|
627 | details.source = source; |
---|
628 | output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>'; |
---|
629 | } |
---|
630 | } |
---|
631 | output += "</table>"; |
---|
632 | |
---|
633 | QUnit.log(details); |
---|
634 | |
---|
635 | config.current.assertions.push({ |
---|
636 | result: !!result, |
---|
637 | message: output |
---|
638 | }); |
---|
639 | }, |
---|
640 | |
---|
641 | url: function( params ) { |
---|
642 | params = extend( extend( {}, QUnit.urlParams ), params ); |
---|
643 | var querystring = "?", |
---|
644 | key; |
---|
645 | for ( key in params ) { |
---|
646 | querystring += encodeURIComponent( key ) + "=" + |
---|
647 | encodeURIComponent( params[ key ] ) + "&"; |
---|
648 | } |
---|
649 | return window.location.pathname + querystring.slice( 0, -1 ); |
---|
650 | }, |
---|
651 | |
---|
652 | // Logging callbacks; all receive a single argument with the listed properties |
---|
653 | // run test/logs.html for any related changes |
---|
654 | begin: function() {}, |
---|
655 | // done: { failed, passed, total, runtime } |
---|
656 | done: function() {}, |
---|
657 | // log: { result, actual, expected, message } |
---|
658 | log: function() {}, |
---|
659 | // testStart: { name } |
---|
660 | testStart: function() {}, |
---|
661 | // testDone: { name, failed, passed, total } |
---|
662 | testDone: function() {}, |
---|
663 | // moduleStart: { name } |
---|
664 | moduleStart: function() {}, |
---|
665 | // moduleDone: { name, failed, passed, total } |
---|
666 | moduleDone: function() {} |
---|
667 | }); |
---|
668 | |
---|
669 | if ( typeof document === "undefined" || document.readyState === "complete" ) { |
---|
670 | config.autorun = true; |
---|
671 | } |
---|
672 | |
---|
673 | addEvent(window, "load", function() { |
---|
674 | QUnit.begin({}); |
---|
675 | |
---|
676 | // Initialize the config, saving the execution queue |
---|
677 | var oldconfig = extend({}, config); |
---|
678 | QUnit.init(); |
---|
679 | extend(config, oldconfig); |
---|
680 | |
---|
681 | config.blocking = false; |
---|
682 | |
---|
683 | var userAgent = id("qunit-userAgent"); |
---|
684 | if ( userAgent ) { |
---|
685 | userAgent.innerHTML = navigator.userAgent; |
---|
686 | } |
---|
687 | var banner = id("qunit-header"); |
---|
688 | if ( banner ) { |
---|
689 | banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + |
---|
690 | '<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' + |
---|
691 | '<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>'; |
---|
692 | addEvent( banner, "change", function( event ) { |
---|
693 | var params = {}; |
---|
694 | params[ event.target.name ] = event.target.checked ? true : undefined; |
---|
695 | window.location = QUnit.url( params ); |
---|
696 | }); |
---|
697 | } |
---|
698 | |
---|
699 | var toolbar = id("qunit-testrunner-toolbar"); |
---|
700 | if ( toolbar ) { |
---|
701 | var filter = document.createElement("input"); |
---|
702 | filter.type = "checkbox"; |
---|
703 | filter.id = "qunit-filter-pass"; |
---|
704 | addEvent( filter, "click", function() { |
---|
705 | var ol = document.getElementById("qunit-tests"); |
---|
706 | if ( filter.checked ) { |
---|
707 | ol.className = ol.className + " hidepass"; |
---|
708 | } else { |
---|
709 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; |
---|
710 | ol.className = tmp.replace(/ hidepass /, " "); |
---|
711 | } |
---|
712 | if ( defined.sessionStorage ) { |
---|
713 | if (filter.checked) { |
---|
714 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); |
---|
715 | } else { |
---|
716 | sessionStorage.removeItem("qunit-filter-passed-tests"); |
---|
717 | } |
---|
718 | } |
---|
719 | }); |
---|
720 | if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { |
---|
721 | filter.checked = true; |
---|
722 | var ol = document.getElementById("qunit-tests"); |
---|
723 | ol.className = ol.className + " hidepass"; |
---|
724 | } |
---|
725 | toolbar.appendChild( filter ); |
---|
726 | |
---|
727 | var label = document.createElement("label"); |
---|
728 | label.setAttribute("for", "qunit-filter-pass"); |
---|
729 | label.innerHTML = "Hide passed tests"; |
---|
730 | toolbar.appendChild( label ); |
---|
731 | } |
---|
732 | |
---|
733 | var main = id('qunit-fixture'); |
---|
734 | if ( main ) { |
---|
735 | config.fixture = main.innerHTML; |
---|
736 | } |
---|
737 | |
---|
738 | if (config.autostart) { |
---|
739 | QUnit.start(); |
---|
740 | } |
---|
741 | }); |
---|
742 | |
---|
743 | function done() { |
---|
744 | config.autorun = true; |
---|
745 | |
---|
746 | // Log the last module results |
---|
747 | if ( config.currentModule ) { |
---|
748 | QUnit.moduleDone( { |
---|
749 | name: config.currentModule, |
---|
750 | failed: config.moduleStats.bad, |
---|
751 | passed: config.moduleStats.all - config.moduleStats.bad, |
---|
752 | total: config.moduleStats.all |
---|
753 | } ); |
---|
754 | } |
---|
755 | |
---|
756 | var banner = id("qunit-banner"), |
---|
757 | tests = id("qunit-tests"), |
---|
758 | runtime = +new Date - config.started, |
---|
759 | passed = config.stats.all - config.stats.bad, |
---|
760 | html = [ |
---|
761 | 'Tests completed in ', |
---|
762 | runtime, |
---|
763 | ' milliseconds.<br/>', |
---|
764 | '<span class="passed">', |
---|
765 | passed, |
---|
766 | '</span> tests of <span class="total">', |
---|
767 | config.stats.all, |
---|
768 | '</span> passed, <span class="failed">', |
---|
769 | config.stats.bad, |
---|
770 | '</span> failed.' |
---|
771 | ].join(''); |
---|
772 | |
---|
773 | if ( banner ) { |
---|
774 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); |
---|
775 | } |
---|
776 | |
---|
777 | if ( tests ) { |
---|
778 | id( "qunit-testresult" ).innerHTML = html; |
---|
779 | } |
---|
780 | |
---|
781 | if ( typeof document !== "undefined" && document.title ) { |
---|
782 | // show â for bad, â for good suite result in title |
---|
783 | // use escape sequences in case file gets loaded with non-utf-8-charset |
---|
784 | document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title; |
---|
785 | } |
---|
786 | |
---|
787 | QUnit.done( { |
---|
788 | failed: config.stats.bad, |
---|
789 | passed: passed, |
---|
790 | total: config.stats.all, |
---|
791 | runtime: runtime |
---|
792 | } ); |
---|
793 | } |
---|
794 | |
---|
795 | function validTest( name ) { |
---|
796 | var filter = config.filter, |
---|
797 | run = false; |
---|
798 | |
---|
799 | if ( !filter ) { |
---|
800 | return true; |
---|
801 | } |
---|
802 | |
---|
803 | var not = filter.charAt( 0 ) === "!"; |
---|
804 | if ( not ) { |
---|
805 | filter = filter.slice( 1 ); |
---|
806 | } |
---|
807 | |
---|
808 | if ( name.indexOf( filter ) !== -1 ) { |
---|
809 | return !not; |
---|
810 | } |
---|
811 | |
---|
812 | if ( not ) { |
---|
813 | run = true; |
---|
814 | } |
---|
815 | |
---|
816 | return run; |
---|
817 | } |
---|
818 | |
---|
819 | // so far supports only Firefox, Chrome and Opera (buggy) |
---|
820 | // could be extended in the future to use something like https://github.com/csnover/TraceKit |
---|
821 | function sourceFromStacktrace() { |
---|
822 | try { |
---|
823 | throw new Error(); |
---|
824 | } catch ( e ) { |
---|
825 | if (e.stacktrace) { |
---|
826 | // Opera |
---|
827 | return e.stacktrace.split("\n")[6]; |
---|
828 | } else if (e.stack) { |
---|
829 | // Firefox, Chrome |
---|
830 | return e.stack.split("\n")[4]; |
---|
831 | } |
---|
832 | } |
---|
833 | } |
---|
834 | |
---|
835 | function escapeHtml(s) { |
---|
836 | if (!s) { |
---|
837 | return ""; |
---|
838 | } |
---|
839 | s = s + ""; |
---|
840 | return s.replace(/[\&"<>\\]/g, function(s) { |
---|
841 | switch(s) { |
---|
842 | case "&": return "&"; |
---|
843 | case "\\": return "\\\\"; |
---|
844 | case '"': return '\"'; |
---|
845 | case "<": return "<"; |
---|
846 | case ">": return ">"; |
---|
847 | default: return s; |
---|
848 | } |
---|
849 | }); |
---|
850 | } |
---|
851 | |
---|
852 | function synchronize( callback ) { |
---|
853 | config.queue.push( callback ); |
---|
854 | |
---|
855 | if ( config.autorun && !config.blocking ) { |
---|
856 | process(); |
---|
857 | } |
---|
858 | } |
---|
859 | |
---|
860 | function process() { |
---|
861 | var start = (new Date()).getTime(); |
---|
862 | |
---|
863 | while ( config.queue.length && !config.blocking ) { |
---|
864 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { |
---|
865 | config.queue.shift()(); |
---|
866 | } else { |
---|
867 | window.setTimeout( process, 13 ); |
---|
868 | break; |
---|
869 | } |
---|
870 | } |
---|
871 | if (!config.blocking && !config.queue.length) { |
---|
872 | done(); |
---|
873 | } |
---|
874 | } |
---|
875 | |
---|
876 | function saveGlobal() { |
---|
877 | config.pollution = []; |
---|
878 | |
---|
879 | if ( config.noglobals ) { |
---|
880 | for ( var key in window ) { |
---|
881 | config.pollution.push( key ); |
---|
882 | } |
---|
883 | } |
---|
884 | } |
---|
885 | |
---|
886 | function checkPollution( name ) { |
---|
887 | var old = config.pollution; |
---|
888 | saveGlobal(); |
---|
889 | |
---|
890 | var newGlobals = diff( config.pollution, old ); |
---|
891 | if ( newGlobals.length > 0 ) { |
---|
892 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); |
---|
893 | } |
---|
894 | |
---|
895 | var deletedGlobals = diff( old, config.pollution ); |
---|
896 | if ( deletedGlobals.length > 0 ) { |
---|
897 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); |
---|
898 | } |
---|
899 | } |
---|
900 | |
---|
901 | // returns a new Array with the elements that are in a but not in b |
---|
902 | function diff( a, b ) { |
---|
903 | var result = a.slice(); |
---|
904 | for ( var i = 0; i < result.length; i++ ) { |
---|
905 | for ( var j = 0; j < b.length; j++ ) { |
---|
906 | if ( result[i] === b[j] ) { |
---|
907 | result.splice(i, 1); |
---|
908 | i--; |
---|
909 | break; |
---|
910 | } |
---|
911 | } |
---|
912 | } |
---|
913 | return result; |
---|
914 | } |
---|
915 | |
---|
916 | function fail(message, exception, callback) { |
---|
917 | if ( typeof console !== "undefined" && console.error && console.warn ) { |
---|
918 | console.error(message); |
---|
919 | console.error(exception); |
---|
920 | console.warn(callback.toString()); |
---|
921 | |
---|
922 | } else if ( window.opera && opera.postError ) { |
---|
923 | opera.postError(message, exception, callback.toString); |
---|
924 | } |
---|
925 | } |
---|
926 | |
---|
927 | function extend(a, b) { |
---|
928 | for ( var prop in b ) { |
---|
929 | if ( b[prop] === undefined ) { |
---|
930 | delete a[prop]; |
---|
931 | } else { |
---|
932 | a[prop] = b[prop]; |
---|
933 | } |
---|
934 | } |
---|
935 | |
---|
936 | return a; |
---|
937 | } |
---|
938 | |
---|
939 | function addEvent(elem, type, fn) { |
---|
940 | if ( elem.addEventListener ) { |
---|
941 | elem.addEventListener( type, fn, false ); |
---|
942 | } else if ( elem.attachEvent ) { |
---|
943 | elem.attachEvent( "on" + type, fn ); |
---|
944 | } else { |
---|
945 | fn(); |
---|
946 | } |
---|
947 | } |
---|
948 | |
---|
949 | function id(name) { |
---|
950 | return !!(typeof document !== "undefined" && document && document.getElementById) && |
---|
951 | document.getElementById( name ); |
---|
952 | } |
---|
953 | |
---|
954 | // Test for equality any JavaScript type. |
---|
955 | // Discussions and reference: http://philrathe.com/articles/equiv |
---|
956 | // Test suites: http://philrathe.com/tests/equiv |
---|
957 | // Author: Philippe Rathé <prathe@gmail.com> |
---|
958 | QUnit.equiv = function () { |
---|
959 | |
---|
960 | var innerEquiv; // the real equiv function |
---|
961 | var callers = []; // stack to decide between skip/abort functions |
---|
962 | var parents = []; // stack to avoiding loops from circular referencing |
---|
963 | |
---|
964 | // Call the o related callback with the given arguments. |
---|
965 | function bindCallbacks(o, callbacks, args) { |
---|
966 | var prop = QUnit.objectType(o); |
---|
967 | if (prop) { |
---|
968 | if (QUnit.objectType(callbacks[prop]) === "function") { |
---|
969 | return callbacks[prop].apply(callbacks, args); |
---|
970 | } else { |
---|
971 | return callbacks[prop]; // or undefined |
---|
972 | } |
---|
973 | } |
---|
974 | } |
---|
975 | |
---|
976 | var callbacks = function () { |
---|
977 | |
---|
978 | // for string, boolean, number and null |
---|
979 | function useStrictEquality(b, a) { |
---|
980 | if (b instanceof a.constructor || a instanceof b.constructor) { |
---|
981 | // to catch short annotaion VS 'new' annotation of a declaration |
---|
982 | // e.g. var i = 1; |
---|
983 | // var j = new Number(1); |
---|
984 | return a == b; |
---|
985 | } else { |
---|
986 | return a === b; |
---|
987 | } |
---|
988 | } |
---|
989 | |
---|
990 | return { |
---|
991 | "string": useStrictEquality, |
---|
992 | "boolean": useStrictEquality, |
---|
993 | "number": useStrictEquality, |
---|
994 | "null": useStrictEquality, |
---|
995 | "undefined": useStrictEquality, |
---|
996 | |
---|
997 | "nan": function (b) { |
---|
998 | return isNaN(b); |
---|
999 | }, |
---|
1000 | |
---|
1001 | "date": function (b, a) { |
---|
1002 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); |
---|
1003 | }, |
---|
1004 | |
---|
1005 | "regexp": function (b, a) { |
---|
1006 | return QUnit.objectType(b) === "regexp" && |
---|
1007 | a.source === b.source && // the regex itself |
---|
1008 | a.global === b.global && // and its modifers (gmi) ... |
---|
1009 | a.ignoreCase === b.ignoreCase && |
---|
1010 | a.multiline === b.multiline; |
---|
1011 | }, |
---|
1012 | |
---|
1013 | // - skip when the property is a method of an instance (OOP) |
---|
1014 | // - abort otherwise, |
---|
1015 | // initial === would have catch identical references anyway |
---|
1016 | "function": function () { |
---|
1017 | var caller = callers[callers.length - 1]; |
---|
1018 | return caller !== Object && |
---|
1019 | typeof caller !== "undefined"; |
---|
1020 | }, |
---|
1021 | |
---|
1022 | "array": function (b, a) { |
---|
1023 | var i, j, loop; |
---|
1024 | var len; |
---|
1025 | |
---|
1026 | // b could be an object literal here |
---|
1027 | if ( ! (QUnit.objectType(b) === "array")) { |
---|
1028 | return false; |
---|
1029 | } |
---|
1030 | |
---|
1031 | len = a.length; |
---|
1032 | if (len !== b.length) { // safe and faster |
---|
1033 | return false; |
---|
1034 | } |
---|
1035 | |
---|
1036 | //track reference to avoid circular references |
---|
1037 | parents.push(a); |
---|
1038 | for (i = 0; i < len; i++) { |
---|
1039 | loop = false; |
---|
1040 | for(j=0;j<parents.length;j++){ |
---|
1041 | if(parents[j] === a[i]){ |
---|
1042 | loop = true;//dont rewalk array |
---|
1043 | } |
---|
1044 | } |
---|
1045 | if (!loop && ! innerEquiv(a[i], b[i])) { |
---|
1046 | parents.pop(); |
---|
1047 | return false; |
---|
1048 | } |
---|
1049 | } |
---|
1050 | parents.pop(); |
---|
1051 | return true; |
---|
1052 | }, |
---|
1053 | |
---|
1054 | "object": function (b, a) { |
---|
1055 | var i, j, loop; |
---|
1056 | var eq = true; // unless we can proove it |
---|
1057 | var aProperties = [], bProperties = []; // collection of strings |
---|
1058 | |
---|
1059 | // comparing constructors is more strict than using instanceof |
---|
1060 | if ( a.constructor !== b.constructor) { |
---|
1061 | return false; |
---|
1062 | } |
---|
1063 | |
---|
1064 | // stack constructor before traversing properties |
---|
1065 | callers.push(a.constructor); |
---|
1066 | //track reference to avoid circular references |
---|
1067 | parents.push(a); |
---|
1068 | |
---|
1069 | for (i in a) { // be strict: don't ensures hasOwnProperty and go deep |
---|
1070 | loop = false; |
---|
1071 | for(j=0;j<parents.length;j++){ |
---|
1072 | if(parents[j] === a[i]) |
---|
1073 | loop = true; //don't go down the same path twice |
---|
1074 | } |
---|
1075 | aProperties.push(i); // collect a's properties |
---|
1076 | |
---|
1077 | if (!loop && ! innerEquiv(a[i], b[i])) { |
---|
1078 | eq = false; |
---|
1079 | break; |
---|
1080 | } |
---|
1081 | } |
---|
1082 | |
---|
1083 | callers.pop(); // unstack, we are done |
---|
1084 | parents.pop(); |
---|
1085 | |
---|
1086 | for (i in b) { |
---|
1087 | bProperties.push(i); // collect b's properties |
---|
1088 | } |
---|
1089 | |
---|
1090 | // Ensures identical properties name |
---|
1091 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); |
---|
1092 | } |
---|
1093 | }; |
---|
1094 | }(); |
---|
1095 | |
---|
1096 | innerEquiv = function () { // can take multiple arguments |
---|
1097 | var args = Array.prototype.slice.apply(arguments); |
---|
1098 | if (args.length < 2) { |
---|
1099 | return true; // end transition |
---|
1100 | } |
---|
1101 | |
---|
1102 | return (function (a, b) { |
---|
1103 | if (a === b) { |
---|
1104 | return true; // catch the most you can |
---|
1105 | } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) { |
---|
1106 | return false; // don't lose time with error prone cases |
---|
1107 | } else { |
---|
1108 | return bindCallbacks(a, callbacks, [b, a]); |
---|
1109 | } |
---|
1110 | |
---|
1111 | // apply transition with (1..n) arguments |
---|
1112 | })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); |
---|
1113 | }; |
---|
1114 | |
---|
1115 | return innerEquiv; |
---|
1116 | |
---|
1117 | }(); |
---|
1118 | |
---|
1119 | /** |
---|
1120 | * jsDump |
---|
1121 | * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com |
---|
1122 | * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) |
---|
1123 | * Date: 5/15/2008 |
---|
1124 | * @projectDescription Advanced and extensible data dumping for Javascript. |
---|
1125 | * @version 1.0.0 |
---|
1126 | * @author Ariel Flesler |
---|
1127 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} |
---|
1128 | */ |
---|
1129 | QUnit.jsDump = (function() { |
---|
1130 | function quote( str ) { |
---|
1131 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; |
---|
1132 | }; |
---|
1133 | function literal( o ) { |
---|
1134 | return o + ''; |
---|
1135 | }; |
---|
1136 | function join( pre, arr, post ) { |
---|
1137 | var s = jsDump.separator(), |
---|
1138 | base = jsDump.indent(), |
---|
1139 | inner = jsDump.indent(1); |
---|
1140 | if ( arr.join ) |
---|
1141 | arr = arr.join( ',' + s + inner ); |
---|
1142 | if ( !arr ) |
---|
1143 | return pre + post; |
---|
1144 | return [ pre, inner + arr, base + post ].join(s); |
---|
1145 | }; |
---|
1146 | function array( arr ) { |
---|
1147 | var i = arr.length, ret = Array(i); |
---|
1148 | this.up(); |
---|
1149 | while ( i-- ) |
---|
1150 | ret[i] = this.parse( arr[i] ); |
---|
1151 | this.down(); |
---|
1152 | return join( '[', ret, ']' ); |
---|
1153 | }; |
---|
1154 | |
---|
1155 | var reName = /^function (\w+)/; |
---|
1156 | |
---|
1157 | var jsDump = { |
---|
1158 | parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance |
---|
1159 | var parser = this.parsers[ type || this.typeOf(obj) ]; |
---|
1160 | type = typeof parser; |
---|
1161 | |
---|
1162 | return type == 'function' ? parser.call( this, obj ) : |
---|
1163 | type == 'string' ? parser : |
---|
1164 | this.parsers.error; |
---|
1165 | }, |
---|
1166 | typeOf:function( obj ) { |
---|
1167 | var type; |
---|
1168 | if ( obj === null ) { |
---|
1169 | type = "null"; |
---|
1170 | } else if (typeof obj === "undefined") { |
---|
1171 | type = "undefined"; |
---|
1172 | } else if (QUnit.is("RegExp", obj)) { |
---|
1173 | type = "regexp"; |
---|
1174 | } else if (QUnit.is("Date", obj)) { |
---|
1175 | type = "date"; |
---|
1176 | } else if (QUnit.is("Function", obj)) { |
---|
1177 | type = "function"; |
---|
1178 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { |
---|
1179 | type = "window"; |
---|
1180 | } else if (obj.nodeType === 9) { |
---|
1181 | type = "document"; |
---|
1182 | } else if (obj.nodeType) { |
---|
1183 | type = "node"; |
---|
1184 | } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { |
---|
1185 | type = "array"; |
---|
1186 | } else { |
---|
1187 | type = typeof obj; |
---|
1188 | } |
---|
1189 | return type; |
---|
1190 | }, |
---|
1191 | separator:function() { |
---|
1192 | return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; |
---|
1193 | }, |
---|
1194 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing |
---|
1195 | if ( !this.multiline ) |
---|
1196 | return ''; |
---|
1197 | var chr = this.indentChar; |
---|
1198 | if ( this.HTML ) |
---|
1199 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); |
---|
1200 | return Array( this._depth_ + (extra||0) ).join(chr); |
---|
1201 | }, |
---|
1202 | up:function( a ) { |
---|
1203 | this._depth_ += a || 1; |
---|
1204 | }, |
---|
1205 | down:function( a ) { |
---|
1206 | this._depth_ -= a || 1; |
---|
1207 | }, |
---|
1208 | setParser:function( name, parser ) { |
---|
1209 | this.parsers[name] = parser; |
---|
1210 | }, |
---|
1211 | // The next 3 are exposed so you can use them |
---|
1212 | quote:quote, |
---|
1213 | literal:literal, |
---|
1214 | join:join, |
---|
1215 | // |
---|
1216 | _depth_: 1, |
---|
1217 | // This is the list of parsers, to modify them, use jsDump.setParser |
---|
1218 | parsers:{ |
---|
1219 | window: '[Window]', |
---|
1220 | document: '[Document]', |
---|
1221 | error:'[ERROR]', //when no parser is found, shouldn't happen |
---|
1222 | unknown: '[Unknown]', |
---|
1223 | 'null':'null', |
---|
1224 | 'undefined':'undefined', |
---|
1225 | 'function':function( fn ) { |
---|
1226 | var ret = 'function', |
---|
1227 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE |
---|
1228 | if ( name ) |
---|
1229 | ret += ' ' + name; |
---|
1230 | ret += '('; |
---|
1231 | |
---|
1232 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); |
---|
1233 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); |
---|
1234 | }, |
---|
1235 | array: array, |
---|
1236 | nodelist: array, |
---|
1237 | arguments: array, |
---|
1238 | object:function( map ) { |
---|
1239 | var ret = [ ]; |
---|
1240 | QUnit.jsDump.up(); |
---|
1241 | for ( var key in map ) |
---|
1242 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); |
---|
1243 | QUnit.jsDump.down(); |
---|
1244 | return join( '{', ret, '}' ); |
---|
1245 | }, |
---|
1246 | node:function( node ) { |
---|
1247 | var open = QUnit.jsDump.HTML ? '<' : '<', |
---|
1248 | close = QUnit.jsDump.HTML ? '>' : '>'; |
---|
1249 | |
---|
1250 | var tag = node.nodeName.toLowerCase(), |
---|
1251 | ret = open + tag; |
---|
1252 | |
---|
1253 | for ( var a in QUnit.jsDump.DOMAttrs ) { |
---|
1254 | var val = node[QUnit.jsDump.DOMAttrs[a]]; |
---|
1255 | if ( val ) |
---|
1256 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); |
---|
1257 | } |
---|
1258 | return ret + close + open + '/' + tag + close; |
---|
1259 | }, |
---|
1260 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function |
---|
1261 | var l = fn.length; |
---|
1262 | if ( !l ) return ''; |
---|
1263 | |
---|
1264 | var args = Array(l); |
---|
1265 | while ( l-- ) |
---|
1266 | args[l] = String.fromCharCode(97+l);//97 is 'a' |
---|
1267 | return ' ' + args.join(', ') + ' '; |
---|
1268 | }, |
---|
1269 | key:quote, //object calls it internally, the key part of an item in a map |
---|
1270 | functionCode:'[code]', //function calls it internally, it's the content of the function |
---|
1271 | attribute:quote, //node calls it internally, it's an html attribute value |
---|
1272 | string:quote, |
---|
1273 | date:quote, |
---|
1274 | regexp:literal, //regex |
---|
1275 | number:literal, |
---|
1276 | 'boolean':literal |
---|
1277 | }, |
---|
1278 | DOMAttrs:{//attributes to dump from nodes, name=>realName |
---|
1279 | id:'id', |
---|
1280 | name:'name', |
---|
1281 | 'class':'className' |
---|
1282 | }, |
---|
1283 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) |
---|
1284 | indentChar:' ',//indentation unit |
---|
1285 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. |
---|
1286 | }; |
---|
1287 | |
---|
1288 | return jsDump; |
---|
1289 | })(); |
---|
1290 | |
---|
1291 | // from Sizzle.js |
---|
1292 | function getText( elems ) { |
---|
1293 | var ret = "", elem; |
---|
1294 | |
---|
1295 | for ( var i = 0; elems[i]; i++ ) { |
---|
1296 | elem = elems[i]; |
---|
1297 | |
---|
1298 | // Get the text from text nodes and CDATA nodes |
---|
1299 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { |
---|
1300 | ret += elem.nodeValue; |
---|
1301 | |
---|
1302 | // Traverse everything else, except comment nodes |
---|
1303 | } else if ( elem.nodeType !== 8 ) { |
---|
1304 | ret += getText( elem.childNodes ); |
---|
1305 | } |
---|
1306 | } |
---|
1307 | |
---|
1308 | return ret; |
---|
1309 | }; |
---|
1310 | |
---|
1311 | /* |
---|
1312 | * Javascript Diff Algorithm |
---|
1313 | * By John Resig (http://ejohn.org/) |
---|
1314 | * Modified by Chu Alan "sprite" |
---|
1315 | * |
---|
1316 | * Released under the MIT license. |
---|
1317 | * |
---|
1318 | * More Info: |
---|
1319 | * http://ejohn.org/projects/javascript-diff-algorithm/ |
---|
1320 | * |
---|
1321 | * Usage: QUnit.diff(expected, actual) |
---|
1322 | * |
---|
1323 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" |
---|
1324 | */ |
---|
1325 | QUnit.diff = (function() { |
---|
1326 | function diff(o, n){ |
---|
1327 | var ns = new Object(); |
---|
1328 | var os = new Object(); |
---|
1329 | |
---|
1330 | for (var i = 0; i < n.length; i++) { |
---|
1331 | if (ns[n[i]] == null) |
---|
1332 | ns[n[i]] = { |
---|
1333 | rows: new Array(), |
---|
1334 | o: null |
---|
1335 | }; |
---|
1336 | ns[n[i]].rows.push(i); |
---|
1337 | } |
---|
1338 | |
---|
1339 | for (var i = 0; i < o.length; i++) { |
---|
1340 | if (os[o[i]] == null) |
---|
1341 | os[o[i]] = { |
---|
1342 | rows: new Array(), |
---|
1343 | n: null |
---|
1344 | }; |
---|
1345 | os[o[i]].rows.push(i); |
---|
1346 | } |
---|
1347 | |
---|
1348 | for (var i in ns) { |
---|
1349 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { |
---|
1350 | n[ns[i].rows[0]] = { |
---|
1351 | text: n[ns[i].rows[0]], |
---|
1352 | row: os[i].rows[0] |
---|
1353 | }; |
---|
1354 | o[os[i].rows[0]] = { |
---|
1355 | text: o[os[i].rows[0]], |
---|
1356 | row: ns[i].rows[0] |
---|
1357 | }; |
---|
1358 | } |
---|
1359 | } |
---|
1360 | |
---|
1361 | for (var i = 0; i < n.length - 1; i++) { |
---|
1362 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && |
---|
1363 | n[i + 1] == o[n[i].row + 1]) { |
---|
1364 | n[i + 1] = { |
---|
1365 | text: n[i + 1], |
---|
1366 | row: n[i].row + 1 |
---|
1367 | }; |
---|
1368 | o[n[i].row + 1] = { |
---|
1369 | text: o[n[i].row + 1], |
---|
1370 | row: i + 1 |
---|
1371 | }; |
---|
1372 | } |
---|
1373 | } |
---|
1374 | |
---|
1375 | for (var i = n.length - 1; i > 0; i--) { |
---|
1376 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && |
---|
1377 | n[i - 1] == o[n[i].row - 1]) { |
---|
1378 | n[i - 1] = { |
---|
1379 | text: n[i - 1], |
---|
1380 | row: n[i].row - 1 |
---|
1381 | }; |
---|
1382 | o[n[i].row - 1] = { |
---|
1383 | text: o[n[i].row - 1], |
---|
1384 | row: i - 1 |
---|
1385 | }; |
---|
1386 | } |
---|
1387 | } |
---|
1388 | |
---|
1389 | return { |
---|
1390 | o: o, |
---|
1391 | n: n |
---|
1392 | }; |
---|
1393 | } |
---|
1394 | |
---|
1395 | return function(o, n){ |
---|
1396 | o = o.replace(/\s+$/, ''); |
---|
1397 | n = n.replace(/\s+$/, ''); |
---|
1398 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); |
---|
1399 | |
---|
1400 | var str = ""; |
---|
1401 | |
---|
1402 | var oSpace = o.match(/\s+/g); |
---|
1403 | if (oSpace == null) { |
---|
1404 | oSpace = [" "]; |
---|
1405 | } |
---|
1406 | else { |
---|
1407 | oSpace.push(" "); |
---|
1408 | } |
---|
1409 | var nSpace = n.match(/\s+/g); |
---|
1410 | if (nSpace == null) { |
---|
1411 | nSpace = [" "]; |
---|
1412 | } |
---|
1413 | else { |
---|
1414 | nSpace.push(" "); |
---|
1415 | } |
---|
1416 | |
---|
1417 | if (out.n.length == 0) { |
---|
1418 | for (var i = 0; i < out.o.length; i++) { |
---|
1419 | str += '<del>' + out.o[i] + oSpace[i] + "</del>"; |
---|
1420 | } |
---|
1421 | } |
---|
1422 | else { |
---|
1423 | if (out.n[0].text == null) { |
---|
1424 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { |
---|
1425 | str += '<del>' + out.o[n] + oSpace[n] + "</del>"; |
---|
1426 | } |
---|
1427 | } |
---|
1428 | |
---|
1429 | for (var i = 0; i < out.n.length; i++) { |
---|
1430 | if (out.n[i].text == null) { |
---|
1431 | str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; |
---|
1432 | } |
---|
1433 | else { |
---|
1434 | var pre = ""; |
---|
1435 | |
---|
1436 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { |
---|
1437 | pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; |
---|
1438 | } |
---|
1439 | str += " " + out.n[i].text + nSpace[i] + pre; |
---|
1440 | } |
---|
1441 | } |
---|
1442 | } |
---|
1443 | |
---|
1444 | return str; |
---|
1445 | }; |
---|
1446 | })(); |
---|
1447 | |
---|
1448 | })(this); |
---|