Blame | Last modification | View Log | RSS feed
/** QUnit - A JavaScript Unit Testing Framework** http://docs.jquery.com/QUnit** Copyright (c) 2009 John Resig, Jörn Zaefferer* Dual licensed under the MIT (MIT-LICENSE.txt)* and GPL (GPL-LICENSE.txt) licenses.*/(function(window) {var QUnit = {// call on start of module test to prepend name to all testsmodule: function(name, testEnvironment) {config.currentModule = name;synchronize(function() {if ( config.currentModule ) {QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );}config.currentModule = name;config.moduleTestEnvironment = testEnvironment;config.moduleStats = { all: 0, bad: 0 };QUnit.moduleStart( name, testEnvironment );});},asyncTest: function(testName, expected, callback) {if ( arguments.length === 2 ) {callback = expected;expected = 0;}QUnit.test(testName, expected, callback, true);},test: function(testName, expected, callback, async) {var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;if ( arguments.length === 2 ) {callback = expected;expected = null;}// is 2nd argument a testEnvironment?if ( expected && typeof expected === 'object') {testEnvironmentArg = expected;expected = null;}if ( config.currentModule ) {name = '<span class="module-name">' + config.currentModule + "</span>: " + name;}if ( !validTest(config.currentModule + ": " + testName) ) {return;}synchronize(function() {testEnvironment = extend({setup: function() {},teardown: function() {}}, config.moduleTestEnvironment);if (testEnvironmentArg) {extend(testEnvironment,testEnvironmentArg);}QUnit.testStart( testName, testEnvironment );// allow utility functions to access the current test environmentQUnit.current_testEnvironment = testEnvironment;config.assertions = [];config.expected = expected;var tests = id("qunit-tests");if (tests) {var b = document.createElement("strong");b.innerHTML = "Running " + name;var li = document.createElement("li");li.appendChild( b );li.id = "current-test-output";tests.appendChild( li )}try {if ( !config.pollution ) {saveGlobal();}testEnvironment.setup.call(testEnvironment);} catch(e) {QUnit.ok( false, "Setup failed on " + name + ": " + e.message );}});synchronize(function() {if ( async ) {QUnit.stop();}try {callback.call(testEnvironment);} catch(e) {fail("Test " + name + " died, exception and test follows", e, callback);QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );// else next test will carry the responsibilitysaveGlobal();// Restart the tests if they're blockingif ( config.blocking ) {start();}}});synchronize(function() {try {checkPollution();testEnvironment.teardown.call(testEnvironment);} catch(e) {QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );}});synchronize(function() {try {QUnit.reset();} catch(e) {fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);}if ( config.expected && config.expected != config.assertions.length ) {QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );}var good = 0, bad = 0,tests = id("qunit-tests");config.stats.all += config.assertions.length;config.moduleStats.all += config.assertions.length;if ( tests ) {var ol = document.createElement("ol");for ( var i = 0; i < config.assertions.length; i++ ) {var assertion = config.assertions[i];var li = document.createElement("li");li.className = assertion.result ? "pass" : "fail";li.innerHTML = assertion.message || "(no message)";ol.appendChild( li );if ( assertion.result ) {good++;} else {bad++;config.stats.bad++;config.moduleStats.bad++;}}if (bad == 0) {ol.style.display = "none";}var b = document.createElement("strong");b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";addEvent(b, "click", function() {var next = b.nextSibling, display = next.style.display;next.style.display = display === "none" ? "block" : "none";});addEvent(b, "dblclick", function(e) {var target = e && e.target ? e.target : window.event.srcElement;if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {target = target.parentNode;}if ( window.location && target.nodeName.toLowerCase() === "strong" ) {window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));}});var li = id("current-test-output");li.id = "";li.className = bad ? "fail" : "pass";li.removeChild( li.firstChild );li.appendChild( b );li.appendChild( ol );if ( bad ) {var toolbar = id("qunit-testrunner-toolbar");if ( toolbar ) {toolbar.style.display = "block";id("qunit-filter-pass").disabled = null;id("qunit-filter-missing").disabled = null;}}} else {for ( var i = 0; i < config.assertions.length; i++ ) {if ( !config.assertions[i].result ) {bad++;config.stats.bad++;config.moduleStats.bad++;}}}QUnit.testDone( testName, bad, config.assertions.length );if ( !window.setTimeout && !config.queue.length ) {done();}});synchronize( done );},/*** Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.*/expect: function(asserts) {config.expected = asserts;},/*** Asserts true.* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );*/ok: function(a, msg) {msg = escapeHtml(msg);QUnit.log(a, msg);config.assertions.push({result: !!a,message: msg});},/*** Checks that the first two arguments are equal, with an optional message.* Prints out both actual and expected values.** Prefered to ok( actual == expected, message )** @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );** @param Object actual* @param Object expected* @param String message (optional)*/equal: function(actual, expected, message) {push(expected == actual, actual, expected, message);},notEqual: function(actual, expected, message) {push(expected != actual, actual, expected, message);},deepEqual: function(actual, expected, message) {push(QUnit.equiv(actual, expected), actual, expected, message);},notDeepEqual: function(actual, expected, message) {push(!QUnit.equiv(actual, expected), actual, expected, message);},strictEqual: function(actual, expected, message) {push(expected === actual, actual, expected, message);},notStrictEqual: function(actual, expected, message) {push(expected !== actual, actual, expected, message);},raises: function(fn, message) {try {fn();ok( false, message );}catch (e) {ok( true, message );}},start: function() {// A slight delay, to avoid any current callbacksif ( window.setTimeout ) {window.setTimeout(function() {if ( config.timeout ) {clearTimeout(config.timeout);}config.blocking = false;process();}, 13);} else {config.blocking = false;process();}},stop: function(timeout) {config.blocking = true;if ( timeout && window.setTimeout ) {config.timeout = window.setTimeout(function() {QUnit.ok( false, "Test timed out" );QUnit.start();}, timeout);}}};// Backwards compatibility, deprecatedQUnit.equals = QUnit.equal;QUnit.same = QUnit.deepEqual;// Maintain internal statevar config = {// The queue of tests to runqueue: [],// block until document readyblocking: true};// Load paramaters(function() {var location = window.location || { search: "", protocol: "file:" },GETParams = location.search.slice(1).split('&');for ( var i = 0; i < GETParams.length; i++ ) {GETParams[i] = decodeURIComponent( GETParams[i] );if ( GETParams[i] === "noglobals" ) {GETParams.splice( i, 1 );i--;config.noglobals = true;} else if ( GETParams[i].search('=') > -1 ) {GETParams.splice( i, 1 );i--;}}// restrict modules/tests by get parametersconfig.filters = GETParams;// Figure out if we're running the tests from a server or notQUnit.isLocal = !!(location.protocol === 'file:');})();// Expose the API as global variables, unless an 'exports'// object exists, in that case we assume we're in CommonJSif ( typeof exports === "undefined" || typeof require === "undefined" ) {extend(window, QUnit);window.QUnit = QUnit;} else {extend(exports, QUnit);exports.QUnit = QUnit;}// define these after exposing globals to keep them in these QUnit namespace onlyextend(QUnit, {config: config,// Initialize the configuration optionsinit: function() {extend(config, {stats: { all: 0, bad: 0 },moduleStats: { all: 0, bad: 0 },started: +new Date,updateRate: 1000,blocking: false,autostart: true,autorun: false,assertions: [],filters: [],queue: []});var tests = id("qunit-tests"),banner = id("qunit-banner"),result = id("qunit-testresult");if ( tests ) {tests.innerHTML = "";}if ( banner ) {banner.className = "";}if ( result ) {result.parentNode.removeChild( result );}},/*** Resets the test setup. Useful for tests that modify the DOM.*/reset: function() {if ( window.jQuery ) {jQuery("#main, #qunit-fixture").html( config.fixture );}},/*** Trigger an event on an element.** @example triggerEvent( document.body, "click" );** @param DOMElement elem* @param String type*/triggerEvent: function( elem, type, event ) {if ( document.createEvent ) {event = document.createEvent("MouseEvents");event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,0, 0, 0, 0, 0, false, false, false, false, 0, null);elem.dispatchEvent( event );} else if ( elem.fireEvent ) {elem.fireEvent("on"+type);}},// Safe object type checkingis: function( type, obj ) {return QUnit.objectType( obj ) == type;},objectType: function( obj ) {if (typeof obj === "undefined") {return "undefined";// consider: typeof null === object}if (obj === null) {return "null";}var type = Object.prototype.toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || '';switch (type) {case 'Number':if (isNaN(obj)) {return "nan";} else {return "number";}case 'String':case 'Boolean':case 'Array':case 'Date':case 'RegExp':case 'Function':return type.toLowerCase();}if (typeof obj === "object") {return "object";}return undefined;},// Logging callbacksbegin: function() {},done: function(failures, total) {},log: function(result, message) {},testStart: function(name, testEnvironment) {},testDone: function(name, failures, total) {},moduleStart: function(name, testEnvironment) {},moduleDone: function(name, failures, total) {}});if ( typeof document === "undefined" || document.readyState === "complete" ) {config.autorun = true;}addEvent(window, "load", function() {QUnit.begin();// Initialize the config, saving the execution queuevar oldconfig = extend({}, config);QUnit.init();extend(config, oldconfig);config.blocking = false;var userAgent = id("qunit-userAgent");if ( userAgent ) {userAgent.innerHTML = navigator.userAgent;}var banner = id("qunit-header");if ( banner ) {banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>';}var toolbar = id("qunit-testrunner-toolbar");if ( toolbar ) {toolbar.style.display = "none";var filter = document.createElement("input");filter.type = "checkbox";filter.id = "qunit-filter-pass";filter.disabled = true;addEvent( filter, "click", function() {var li = document.getElementsByTagName("li");for ( var i = 0; i < li.length; i++ ) {if ( li[i].className.indexOf("pass") > -1 ) {li[i].style.display = filter.checked ? "none" : "";}}});toolbar.appendChild( filter );var label = document.createElement("label");label.setAttribute("for", "qunit-filter-pass");label.innerHTML = "Hide passed tests";toolbar.appendChild( label );var missing = document.createElement("input");missing.type = "checkbox";missing.id = "qunit-filter-missing";missing.disabled = true;addEvent( missing, "click", function() {var li = document.getElementsByTagName("li");for ( var i = 0; i < li.length; i++ ) {if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";}}});toolbar.appendChild( missing );label = document.createElement("label");label.setAttribute("for", "qunit-filter-missing");label.innerHTML = "Hide missing tests (untested code is broken code)";toolbar.appendChild( label );}var main = id('main') || id('qunit-fixture');if ( main ) {config.fixture = main.innerHTML;}if (config.autostart) {QUnit.start();}});function done() {if ( config.doneTimer && window.clearTimeout ) {window.clearTimeout( config.doneTimer );config.doneTimer = null;}if ( config.queue.length ) {config.doneTimer = window.setTimeout(function(){if ( !config.queue.length ) {done();} else {synchronize( done );}}, 13);return;}config.autorun = true;// Log the last module resultsif ( config.currentModule ) {QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );}var banner = id("qunit-banner"),tests = id("qunit-tests"),html = ['Tests completed in ',+new Date - config.started, ' milliseconds.<br/>','<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');if ( banner ) {banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");}if ( tests ) {var result = id("qunit-testresult");if ( !result ) {result = document.createElement("p");result.id = "qunit-testresult";result.className = "result";tests.parentNode.insertBefore( result, tests.nextSibling );}result.innerHTML = html;}QUnit.done( config.stats.bad, config.stats.all );}function validTest( name ) {var i = config.filters.length,run = false;if ( !i ) {return true;}while ( i-- ) {var filter = config.filters[i],not = filter.charAt(0) == '!';if ( not ) {filter = filter.slice(1);}if ( name.indexOf(filter) !== -1 ) {return !not;}if ( not ) {run = true;}}return run;}function escapeHtml(s) {s = s === null ? "" : s + "";return s.replace(/[\&"<>\\]/g, function(s) {switch(s) {case "&": return "&";case "\\": return "\\\\";case '"': return '\"';case "<": return "<";case ">": return ">";default: return s;}});}function push(result, actual, expected, message) {message = escapeHtml(message) || (result ? "okay" : "failed");message = '<span class="test-message">' + message + "</span>";expected = escapeHtml(QUnit.jsDump.parse(expected));actual = escapeHtml(QUnit.jsDump.parse(actual));var output = message + ', expected: <span class="test-expected">' + expected + '</span>';if (actual != expected) {output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);}// can't use ok, as that would double-escape messagesQUnit.log(result, output);config.assertions.push({result: !!result,message: output});}function synchronize( callback ) {config.queue.push( callback );if ( config.autorun && !config.blocking ) {process();}}function process() {var start = (new Date()).getTime();while ( config.queue.length && !config.blocking ) {if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {config.queue.shift()();} else {setTimeout( process, 13 );break;}}}function saveGlobal() {config.pollution = [];if ( config.noglobals ) {for ( var key in window ) {config.pollution.push( key );}}}function checkPollution( name ) {var old = config.pollution;saveGlobal();var newGlobals = diff( old, config.pollution );if ( newGlobals.length > 0 ) {ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );config.expected++;}var deletedGlobals = diff( config.pollution, old );if ( deletedGlobals.length > 0 ) {ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );config.expected++;}}// returns a new Array with the elements that are in a but not in bfunction diff( a, b ) {var result = a.slice();for ( var i = 0; i < result.length; i++ ) {for ( var j = 0; j < b.length; j++ ) {if ( result[i] === b[j] ) {result.splice(i, 1);i--;break;}}}return result;}function fail(message, exception, callback) {if ( typeof console !== "undefined" && console.error && console.warn ) {console.error(message);console.error(exception);console.warn(callback.toString());} else if ( window.opera && opera.postError ) {opera.postError(message, exception, callback.toString);}}function extend(a, b) {for ( var prop in b ) {a[prop] = b[prop];}return a;}function addEvent(elem, type, fn) {if ( elem.addEventListener ) {elem.addEventListener( type, fn, false );} else if ( elem.attachEvent ) {elem.attachEvent( "on" + type, fn );} else {fn();}}function id(name) {return !!(typeof document !== "undefined" && document && document.getElementById) &&document.getElementById( name );}// Test for equality any JavaScript type.// Discussions and reference: http://philrathe.com/articles/equiv// Test suites: http://philrathe.com/tests/equiv// Author: Philippe Rathé <prathe@gmail.com>QUnit.equiv = function () {var innerEquiv; // the real equiv functionvar callers = []; // stack to decide between skip/abort functionsvar parents = []; // stack to avoiding loops from circular referencing// Call the o related callback with the given arguments.function bindCallbacks(o, callbacks, args) {var prop = QUnit.objectType(o);if (prop) {if (QUnit.objectType(callbacks[prop]) === "function") {return callbacks[prop].apply(callbacks, args);} else {return callbacks[prop]; // or undefined}}}var callbacks = function () {// for string, boolean, number and nullfunction useStrictEquality(b, a) {if (b instanceof a.constructor || a instanceof b.constructor) {// to catch short annotaion VS 'new' annotation of a declaration// e.g. var i = 1;// var j = new Number(1);return a == b;} else {return a === b;}}return {"string": useStrictEquality,"boolean": useStrictEquality,"number": useStrictEquality,"null": useStrictEquality,"undefined": useStrictEquality,"nan": function (b) {return isNaN(b);},"date": function (b, a) {return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();},"regexp": function (b, a) {return QUnit.objectType(b) === "regexp" &&a.source === b.source && // the regex itselfa.global === b.global && // and its modifers (gmi) ...a.ignoreCase === b.ignoreCase &&a.multiline === b.multiline;},// - skip when the property is a method of an instance (OOP)// - abort otherwise,// initial === would have catch identical references anyway"function": function () {var caller = callers[callers.length - 1];return caller !== Object &&typeof caller !== "undefined";},"array": function (b, a) {var i, j, loop;var len;// b could be an object literal hereif ( ! (QUnit.objectType(b) === "array")) {return false;}len = a.length;if (len !== b.length) { // safe and fasterreturn false;}//track reference to avoid circular referencesparents.push(a);for (i = 0; i < len; i++) {loop = false;for(j=0;j<parents.length;j++){if(parents[j] === a[i]){loop = true;//dont rewalk array}}if (!loop && ! innerEquiv(a[i], b[i])) {parents.pop();return false;}}parents.pop();return true;},"object": function (b, a) {var i, j, loop;var eq = true; // unless we can proove itvar aProperties = [], bProperties = []; // collection of strings// comparing constructors is more strict than using instanceofif ( a.constructor !== b.constructor) {return false;}// stack constructor before traversing propertiescallers.push(a.constructor);//track reference to avoid circular referencesparents.push(a);for (i in a) { // be strict: don't ensures hasOwnProperty and go deeploop = false;for(j=0;j<parents.length;j++){if(parents[j] === a[i])loop = true; //don't go down the same path twice}aProperties.push(i); // collect a's propertiesif (!loop && ! innerEquiv(a[i], b[i])) {eq = false;break;}}callers.pop(); // unstack, we are doneparents.pop();for (i in b) {bProperties.push(i); // collect b's properties}// Ensures identical properties namereturn eq && innerEquiv(aProperties.sort(), bProperties.sort());}};}();innerEquiv = function () { // can take multiple argumentsvar args = Array.prototype.slice.apply(arguments);if (args.length < 2) {return true; // end transition}return (function (a, b) {if (a === b) {return true; // catch the most you can} else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {return false; // don't lose time with error prone cases} else {return bindCallbacks(a, callbacks, [b, a]);}// apply transition with (1..n) arguments})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));};return innerEquiv;}();/*** jsDump* Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com* Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)* Date: 5/15/2008* @projectDescription Advanced and extensible data dumping for Javascript.* @version 1.0.0* @author Ariel Flesler* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}*/QUnit.jsDump = (function() {function quote( str ) {return '"' + str.toString().replace(/"/g, '\\"') + '"';};function literal( o ) {return o + '';};function join( pre, arr, post ) {var s = jsDump.separator(),base = jsDump.indent(),inner = jsDump.indent(1);if ( arr.join )arr = arr.join( ',' + s + inner );if ( !arr )return pre + post;return [ pre, inner + arr, base + post ].join(s);};function array( arr ) {var i = arr.length, ret = Array(i);this.up();while ( i-- )ret[i] = this.parse( arr[i] );this.down();return join( '[', ret, ']' );};var reName = /^function (\w+)/;var jsDump = {parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advancevar parser = this.parsers[ type || this.typeOf(obj) ];type = typeof parser;return type == 'function' ? parser.call( this, obj ) :type == 'string' ? parser :this.parsers.error;},typeOf:function( obj ) {var type;if ( obj === null ) {type = "null";} else if (typeof obj === "undefined") {type = "undefined";} else if (QUnit.is("RegExp", obj)) {type = "regexp";} else if (QUnit.is("Date", obj)) {type = "date";} else if (QUnit.is("Function", obj)) {type = "function";} else if (obj.setInterval && obj.document && !obj.nodeType) {type = "window";} else if (obj.nodeType === 9) {type = "document";} else if (obj.nodeType) {type = "node";} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {type = "array";} else {type = typeof obj;}return type;},separator:function() {return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' ';},indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasingif ( !this.multiline )return '';var chr = this.indentChar;if ( this.HTML )chr = chr.replace(/\t/g,' ').replace(/ /g,' ');return Array( this._depth_ + (extra||0) ).join(chr);},up:function( a ) {this._depth_ += a || 1;},down:function( a ) {this._depth_ -= a || 1;},setParser:function( name, parser ) {this.parsers[name] = parser;},// The next 3 are exposed so you can use themquote:quote,literal:literal,join:join,//_depth_: 1,// This is the list of parsers, to modify them, use jsDump.setParserparsers:{window: '[Window]',document: '[Document]',error:'[ERROR]', //when no parser is found, shouldn't happenunknown: '[Unknown]','null':'null',undefined:'undefined','function':function( fn ) {var ret = 'function',name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IEif ( name )ret += ' ' + name;ret += '(';ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');return join( ret, this.parse(fn,'functionCode'), '}' );},array: array,nodelist: array,arguments: array,object:function( map ) {var ret = [ ];this.up();for ( var key in map )ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );this.down();return join( '{', ret, '}' );},node:function( node ) {var open = this.HTML ? '<' : '<',close = this.HTML ? '>' : '>';var tag = node.nodeName.toLowerCase(),ret = open + tag;for ( var a in this.DOMAttrs ) {var val = node[this.DOMAttrs[a]];if ( val )ret += ' ' + a + '=' + this.parse( val, 'attribute' );}return ret + close + open + '/' + tag + close;},functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the functionvar l = fn.length;if ( !l ) return '';var args = Array(l);while ( l-- )args[l] = String.fromCharCode(97+l);//97 is 'a'return ' ' + args.join(', ') + ' ';},key:quote, //object calls it internally, the key part of an item in a mapfunctionCode:'[code]', //function calls it internally, it's the content of the functionattribute:quote, //node calls it internally, it's an html attribute valuestring:quote,date:quote,regexp:literal, //regexnumber:literal,'boolean':literal},DOMAttrs:{//attributes to dump from nodes, name=>realNameid:'id',name:'name','class':'className'},HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )indentChar:' ',//indentation unitmultiline:false //if true, items in a collection, are separated by a \n, else just a space.};return jsDump;})();// from Sizzle.jsfunction getText( elems ) {var ret = "", elem;for ( var i = 0; elems[i]; i++ ) {elem = elems[i];// Get the text from text nodes and CDATA nodesif ( elem.nodeType === 3 || elem.nodeType === 4 ) {ret += elem.nodeValue;// Traverse everything else, except comment nodes} else if ( elem.nodeType !== 8 ) {ret += getText( elem.childNodes );}}return ret;};/** Javascript Diff Algorithm* By John Resig (http://ejohn.org/)* Modified by Chu Alan "sprite"** Released under the MIT license.** More Info:* http://ejohn.org/projects/javascript-diff-algorithm/** Usage: QUnit.diff(expected, actual)** 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"*/QUnit.diff = (function() {function diff(o, n){var ns = new Object();var os = new Object();for (var i = 0; i < n.length; i++) {if (ns[n[i]] == null)ns[n[i]] = {rows: new Array(),o: null};ns[n[i]].rows.push(i);}for (var i = 0; i < o.length; i++) {if (os[o[i]] == null)os[o[i]] = {rows: new Array(),n: null};os[o[i]].rows.push(i);}for (var i in ns) {if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {n[ns[i].rows[0]] = {text: n[ns[i].rows[0]],row: os[i].rows[0]};o[os[i].rows[0]] = {text: o[os[i].rows[0]],row: ns[i].rows[0]};}}for (var i = 0; i < n.length - 1; i++) {if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&n[i + 1] == o[n[i].row + 1]) {n[i + 1] = {text: n[i + 1],row: n[i].row + 1};o[n[i].row + 1] = {text: o[n[i].row + 1],row: i + 1};}}for (var i = n.length - 1; i > 0; i--) {if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&n[i - 1] == o[n[i].row - 1]) {n[i - 1] = {text: n[i - 1],row: n[i].row - 1};o[n[i].row - 1] = {text: o[n[i].row - 1],row: i - 1};}}return {o: o,n: n};}return function(o, n){o = o.replace(/\s+$/, '');n = n.replace(/\s+$/, '');var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));var str = "";var oSpace = o.match(/\s+/g);if (oSpace == null) {oSpace = [" "];}else {oSpace.push(" ");}var nSpace = n.match(/\s+/g);if (nSpace == null) {nSpace = [" "];}else {nSpace.push(" ");}if (out.n.length == 0) {for (var i = 0; i < out.o.length; i++) {str += '<del>' + out.o[i] + oSpace[i] + "</del>";}}else {if (out.n[0].text == null) {for (n = 0; n < out.o.length && out.o[n].text == null; n++) {str += '<del>' + out.o[n] + oSpace[n] + "</del>";}}for (var i = 0; i < out.n.length; i++) {if (out.n[i].text == null) {str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";}else {var pre = "";for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {pre += '<del>' + out.o[n] + oSpace[n] + "</del>";}str += " " + out.n[i].text + nSpace[i] + pre;}}}return str;}})();})(this);