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é <[email protected]>
|
---|
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); |
---|