info@54: /**
info@54: * QUnit - A JavaScript Unit Testing Framework
info@54: *
info@54: * http://docs.jquery.com/QUnit
info@54: *
info@54: * Copyright (c) 2012 John Resig, Jörn Zaefferer
info@54: * Dual licensed under the MIT (MIT-LICENSE.txt)
info@54: * or GPL (GPL-LICENSE.txt) licenses.
info@54: */
info@54:
info@54: (function(window) {
info@54:
info@54: var defined = {
info@54: setTimeout: typeof window.setTimeout !== "undefined",
info@54: sessionStorage: (function() {
info@54: try {
info@54: return !!sessionStorage.getItem;
info@54: } catch(e) {
info@54: return false;
info@54: }
info@54: })()
info@54: };
info@54:
info@54: var testId = 0;
info@54:
info@54: var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
info@54: this.name = name;
info@54: this.testName = testName;
info@54: this.expected = expected;
info@54: this.testEnvironmentArg = testEnvironmentArg;
info@54: this.async = async;
info@54: this.callback = callback;
info@54: this.assertions = [];
info@54: };
info@54: Test.prototype = {
info@54: init: function() {
info@54: var tests = id("qunit-tests");
info@54: if (tests) {
info@54: var b = document.createElement("strong");
info@54: b.innerHTML = "Running " + this.name;
info@54: var li = document.createElement("li");
info@54: li.appendChild( b );
info@54: li.className = "running";
info@54: li.id = this.id = "test-output" + testId++;
info@54: tests.appendChild( li );
info@54: }
info@54: },
info@54: setup: function() {
info@54: if (this.module != config.previousModule) {
info@54: if ( config.previousModule ) {
info@54: QUnit.moduleDone( {
info@54: name: config.previousModule,
info@54: failed: config.moduleStats.bad,
info@54: passed: config.moduleStats.all - config.moduleStats.bad,
info@54: total: config.moduleStats.all
info@54: } );
info@54: }
info@54: config.previousModule = this.module;
info@54: config.moduleStats = { all: 0, bad: 0 };
info@54: QUnit.moduleStart( {
info@54: name: this.module
info@54: } );
info@54: }
info@54:
info@54: config.current = this;
info@54: this.testEnvironment = extend({
info@54: setup: function() {},
info@54: teardown: function() {}
info@54: }, this.moduleTestEnvironment);
info@54: if (this.testEnvironmentArg) {
info@54: extend(this.testEnvironment, this.testEnvironmentArg);
info@54: }
info@54:
info@54: QUnit.testStart( {
info@54: name: this.testName
info@54: } );
info@54:
info@54: // allow utility functions to access the current test environment
info@54: // TODO why??
info@54: QUnit.current_testEnvironment = this.testEnvironment;
info@54:
info@54: try {
info@54: if ( !config.pollution ) {
info@54: saveGlobal();
info@54: }
info@54:
info@54: this.testEnvironment.setup.call(this.testEnvironment);
info@54: } catch(e) {
info@54: QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
info@54: }
info@54: },
info@54: run: function() {
info@54: if ( this.async ) {
info@54: QUnit.stop();
info@54: }
info@54:
info@54: if ( config.notrycatch ) {
info@54: this.callback.call(this.testEnvironment);
info@54: return;
info@54: }
info@54: try {
info@54: this.callback.call(this.testEnvironment);
info@54: } catch(e) {
info@54: fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
info@54: QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
info@54: // else next test will carry the responsibility
info@54: saveGlobal();
info@54:
info@54: // Restart the tests if they're blocking
info@54: if ( config.blocking ) {
info@54: start();
info@54: }
info@54: }
info@54: },
info@54: teardown: function() {
info@54: try {
info@54: this.testEnvironment.teardown.call(this.testEnvironment);
info@54: checkPollution();
info@54: } catch(e) {
info@54: QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
info@54: }
info@54: },
info@54: finish: function() {
info@54: if ( this.expected && this.expected != this.assertions.length ) {
info@54: QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
info@54: }
info@54:
info@54: var good = 0, bad = 0,
info@54: tests = id("qunit-tests");
info@54:
info@54: config.stats.all += this.assertions.length;
info@54: config.moduleStats.all += this.assertions.length;
info@54:
info@54: if ( tests ) {
info@54: var ol = document.createElement("ol");
info@54:
info@54: for ( var i = 0; i < this.assertions.length; i++ ) {
info@54: var assertion = this.assertions[i];
info@54:
info@54: var li = document.createElement("li");
info@54: li.className = assertion.result ? "pass" : "fail";
info@54: li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
info@54: ol.appendChild( li );
info@54:
info@54: if ( assertion.result ) {
info@54: good++;
info@54: } else {
info@54: bad++;
info@54: config.stats.bad++;
info@54: config.moduleStats.bad++;
info@54: }
info@54: }
info@54:
info@54: // store result when possible
info@54: if ( QUnit.config.reorder && defined.sessionStorage ) {
info@54: if (bad) {
info@54: sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
info@54: } else {
info@54: sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
info@54: }
info@54: }
info@54:
info@54: if (bad == 0) {
info@54: ol.style.display = "none";
info@54: }
info@54:
info@54: var b = document.createElement("strong");
info@54: b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
info@54:
info@54: var a = document.createElement("a");
info@54: a.innerHTML = "Rerun";
info@54: a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
info@54:
info@54: addEvent(b, "click", function() {
info@54: var next = b.nextSibling.nextSibling,
info@54: display = next.style.display;
info@54: next.style.display = display === "none" ? "block" : "none";
info@54: });
info@54:
info@54: addEvent(b, "dblclick", function(e) {
info@54: var target = e && e.target ? e.target : window.event.srcElement;
info@54: if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
info@54: target = target.parentNode;
info@54: }
info@54: if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
info@54: window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
info@54: }
info@54: });
info@54:
info@54: var li = id(this.id);
info@54: li.className = bad ? "fail" : "pass";
info@54: li.removeChild( li.firstChild );
info@54: li.appendChild( b );
info@54: li.appendChild( a );
info@54: li.appendChild( ol );
info@54:
info@54: } else {
info@54: for ( var i = 0; i < this.assertions.length; i++ ) {
info@54: if ( !this.assertions[i].result ) {
info@54: bad++;
info@54: config.stats.bad++;
info@54: config.moduleStats.bad++;
info@54: }
info@54: }
info@54: }
info@54:
info@54: try {
info@54: QUnit.reset();
info@54: } catch(e) {
info@54: fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
info@54: }
info@54:
info@54: QUnit.testDone( {
info@54: name: this.testName,
info@54: failed: bad,
info@54: passed: this.assertions.length - bad,
info@54: total: this.assertions.length
info@54: } );
info@54: },
info@54:
info@54: queue: function() {
info@54: var test = this;
info@54: synchronize(function() {
info@54: test.init();
info@54: });
info@54: function run() {
info@54: // each of these can by async
info@54: synchronize(function() {
info@54: test.setup();
info@54: });
info@54: synchronize(function() {
info@54: test.run();
info@54: });
info@54: synchronize(function() {
info@54: test.teardown();
info@54: });
info@54: synchronize(function() {
info@54: test.finish();
info@54: });
info@54: }
info@54: // defer when previous test run passed, if storage is available
info@54: var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
info@54: if (bad) {
info@54: run();
info@54: } else {
info@54: synchronize(run);
info@54: };
info@54: }
info@54:
info@54: };
info@54:
info@54: var QUnit = {
info@54:
info@54: // call on start of module test to prepend name to all tests
info@54: module: function(name, testEnvironment) {
info@54: config.currentModule = name;
info@54: config.currentModuleTestEnviroment = testEnvironment;
info@54: },
info@54:
info@54: asyncTest: function(testName, expected, callback) {
info@54: if ( arguments.length === 2 ) {
info@54: callback = expected;
info@54: expected = 0;
info@54: }
info@54:
info@54: QUnit.test(testName, expected, callback, true);
info@54: },
info@54:
info@54: test: function(testName, expected, callback, async) {
info@54: var name = '' + testName + '', testEnvironmentArg;
info@54:
info@54: if ( arguments.length === 2 ) {
info@54: callback = expected;
info@54: expected = null;
info@54: }
info@54: // is 2nd argument a testEnvironment?
info@54: if ( expected && typeof expected === 'object') {
info@54: testEnvironmentArg = expected;
info@54: expected = null;
info@54: }
info@54:
info@54: if ( config.currentModule ) {
info@54: name = '' + config.currentModule + ": " + name;
info@54: }
info@54:
info@54: if ( !validTest(config.currentModule + ": " + testName) ) {
info@54: return;
info@54: }
info@54:
info@54: var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
info@54: test.module = config.currentModule;
info@54: test.moduleTestEnvironment = config.currentModuleTestEnviroment;
info@54: test.queue();
info@54: },
info@54:
info@54: /**
info@54: * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
info@54: */
info@54: expect: function(asserts) {
info@54: config.current.expected = asserts;
info@54: },
info@54:
info@54: /**
info@54: * Asserts true.
info@54: * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
info@54: */
info@54: ok: function(a, msg) {
info@54: a = !!a;
info@54: var details = {
info@54: result: a,
info@54: message: msg
info@54: };
info@54: msg = escapeHtml(msg);
info@54: QUnit.log(details);
info@54: config.current.assertions.push({
info@54: result: a,
info@54: message: msg
info@54: });
info@54: },
info@54:
info@54: /**
info@54: * Checks that the first two arguments are equal, with an optional message.
info@54: * Prints out both actual and expected values.
info@54: *
info@54: * Prefered to ok( actual == expected, message )
info@54: *
info@54: * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
info@54: *
info@54: * @param Object actual
info@54: * @param Object expected
info@54: * @param String message (optional)
info@54: */
info@54: equal: function(actual, expected, message) {
info@54: QUnit.push(expected == actual, actual, expected, message);
info@54: },
info@54:
info@54: notEqual: function(actual, expected, message) {
info@54: QUnit.push(expected != actual, actual, expected, message);
info@54: },
info@54:
info@54: deepEqual: function(actual, expected, message) {
info@54: QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
info@54: },
info@54:
info@54: notDeepEqual: function(actual, expected, message) {
info@54: QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
info@54: },
info@54:
info@54: strictEqual: function(actual, expected, message) {
info@54: QUnit.push(expected === actual, actual, expected, message);
info@54: },
info@54:
info@54: notStrictEqual: function(actual, expected, message) {
info@54: QUnit.push(expected !== actual, actual, expected, message);
info@54: },
info@54:
info@54: raises: function(block, expected, message) {
info@54: var actual, ok = false;
info@54:
info@54: if (typeof expected === 'string') {
info@54: message = expected;
info@54: expected = null;
info@54: }
info@54:
info@54: try {
info@54: block();
info@54: } catch (e) {
info@54: actual = e;
info@54: }
info@54:
info@54: if (actual) {
info@54: // we don't want to validate thrown error
info@54: if (!expected) {
info@54: ok = true;
info@54: // expected is a regexp
info@54: } else if (QUnit.objectType(expected) === "regexp") {
info@54: ok = expected.test(actual);
info@54: // expected is a constructor
info@54: } else if (actual instanceof expected) {
info@54: ok = true;
info@54: // expected is a validation function which returns true is validation passed
info@54: } else if (expected.call({}, actual) === true) {
info@54: ok = true;
info@54: }
info@54: }
info@54:
info@54: QUnit.ok(ok, message);
info@54: },
info@54:
info@54: start: function() {
info@54: config.semaphore--;
info@54: if (config.semaphore > 0) {
info@54: // don't start until equal number of stop-calls
info@54: return;
info@54: }
info@54: if (config.semaphore < 0) {
info@54: // ignore if start is called more often then stop
info@54: config.semaphore = 0;
info@54: }
info@54: // A slight delay, to avoid any current callbacks
info@54: if ( defined.setTimeout ) {
info@54: window.setTimeout(function() {
info@54: if (config.semaphore > 0) {
info@54: return;
info@54: }
info@54: if ( config.timeout ) {
info@54: clearTimeout(config.timeout);
info@54: }
info@54:
info@54: config.blocking = false;
info@54: process();
info@54: }, 13);
info@54: } else {
info@54: config.blocking = false;
info@54: process();
info@54: }
info@54: },
info@54:
info@54: stop: function(timeout) {
info@54: config.semaphore++;
info@54: config.blocking = true;
info@54:
info@54: if ( timeout && defined.setTimeout ) {
info@54: clearTimeout(config.timeout);
info@54: config.timeout = window.setTimeout(function() {
info@54: QUnit.ok( false, "Test timed out" );
info@54: QUnit.start();
info@54: }, timeout);
info@54: }
info@54: }
info@54: };
info@54:
info@54: // Backwards compatibility, deprecated
info@54: QUnit.equals = QUnit.equal;
info@54: QUnit.same = QUnit.deepEqual;
info@54:
info@54: // Maintain internal state
info@54: var config = {
info@54: // The queue of tests to run
info@54: queue: [],
info@54:
info@54: // block until document ready
info@54: blocking: true,
info@54:
info@54: // when enabled, show only failing tests
info@54: // gets persisted through sessionStorage and can be changed in UI via checkbox
info@54: hidepassed: false,
info@54:
info@54: // by default, run previously failed tests first
info@54: // very useful in combination with "Hide passed tests" checked
info@54: reorder: true,
info@54:
info@54: // by default, modify document.title when suite is done
info@54: altertitle: true,
info@54:
info@54: urlConfig: ['noglobals', 'notrycatch']
info@54: };
info@54:
info@54: // Load paramaters
info@54: (function() {
info@54: var location = window.location || { search: "", protocol: "file:" },
info@54: params = location.search.slice( 1 ).split( "&" ),
info@54: length = params.length,
info@54: urlParams = {},
info@54: current;
info@54:
info@54: if ( params[ 0 ] ) {
info@54: for ( var i = 0; i < length; i++ ) {
info@54: current = params[ i ].split( "=" );
info@54: current[ 0 ] = decodeURIComponent( current[ 0 ] );
info@54: // allow just a key to turn on a flag, e.g., test.html?noglobals
info@54: current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
info@54: urlParams[ current[ 0 ] ] = current[ 1 ];
info@54: }
info@54: }
info@54:
info@54: QUnit.urlParams = urlParams;
info@54: config.filter = urlParams.filter;
info@54:
info@54: // Figure out if we're running the tests from a server or not
info@54: QUnit.isLocal = !!(location.protocol === 'file:');
info@54: })();
info@54:
info@54: // Expose the API as global variables, unless an 'exports'
info@54: // object exists, in that case we assume we're in CommonJS
info@54: if ( typeof exports === "undefined" || typeof require === "undefined" ) {
info@54: extend(window, QUnit);
info@54: window.QUnit = QUnit;
info@54: } else {
info@54: extend(exports, QUnit);
info@54: exports.QUnit = QUnit;
info@54: }
info@54:
info@54: // define these after exposing globals to keep them in these QUnit namespace only
info@54: extend(QUnit, {
info@54: config: config,
info@54:
info@54: // Initialize the configuration options
info@54: init: function() {
info@54: extend(config, {
info@54: stats: { all: 0, bad: 0 },
info@54: moduleStats: { all: 0, bad: 0 },
info@54: started: +new Date,
info@54: updateRate: 1000,
info@54: blocking: false,
info@54: autostart: true,
info@54: autorun: false,
info@54: filter: "",
info@54: queue: [],
info@54: semaphore: 0
info@54: });
info@54:
info@54: var tests = id( "qunit-tests" ),
info@54: banner = id( "qunit-banner" ),
info@54: result = id( "qunit-testresult" );
info@54:
info@54: if ( tests ) {
info@54: tests.innerHTML = "";
info@54: }
info@54:
info@54: if ( banner ) {
info@54: banner.className = "";
info@54: }
info@54:
info@54: if ( result ) {
info@54: result.parentNode.removeChild( result );
info@54: }
info@54:
info@54: if ( tests ) {
info@54: result = document.createElement( "p" );
info@54: result.id = "qunit-testresult";
info@54: result.className = "result";
info@54: tests.parentNode.insertBefore( result, tests );
info@54: result.innerHTML = 'Running...
';
info@54: }
info@54: },
info@54:
info@54: /**
info@54: * Resets the test setup. Useful for tests that modify the DOM.
info@54: *
info@54: * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
info@54: */
info@54: reset: function() {
info@54: if ( window.jQuery ) {
info@54: jQuery( "#qunit-fixture" ).html( config.fixture );
info@54: } else {
info@54: var main = id( 'qunit-fixture' );
info@54: if ( main ) {
info@54: main.innerHTML = config.fixture;
info@54: }
info@54: }
info@54: },
info@54:
info@54: /**
info@54: * Trigger an event on an element.
info@54: *
info@54: * @example triggerEvent( document.body, "click" );
info@54: *
info@54: * @param DOMElement elem
info@54: * @param String type
info@54: */
info@54: triggerEvent: function( elem, type, event ) {
info@54: if ( document.createEvent ) {
info@54: event = document.createEvent("MouseEvents");
info@54: event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
info@54: 0, 0, 0, 0, 0, false, false, false, false, 0, null);
info@54: elem.dispatchEvent( event );
info@54:
info@54: } else if ( elem.fireEvent ) {
info@54: elem.fireEvent("on"+type);
info@54: }
info@54: },
info@54:
info@54: // Safe object type checking
info@54: is: function( type, obj ) {
info@54: return QUnit.objectType( obj ) == type;
info@54: },
info@54:
info@54: objectType: function( obj ) {
info@54: if (typeof obj === "undefined") {
info@54: return "undefined";
info@54:
info@54: // consider: typeof null === object
info@54: }
info@54: if (obj === null) {
info@54: return "null";
info@54: }
info@54:
info@54: var type = Object.prototype.toString.call( obj )
info@54: .match(/^\[object\s(.*)\]$/)[1] || '';
info@54:
info@54: switch (type) {
info@54: case 'Number':
info@54: if (isNaN(obj)) {
info@54: return "nan";
info@54: } else {
info@54: return "number";
info@54: }
info@54: case 'String':
info@54: case 'Boolean':
info@54: case 'Array':
info@54: case 'Date':
info@54: case 'RegExp':
info@54: case 'Function':
info@54: return type.toLowerCase();
info@54: }
info@54: if (typeof obj === "object") {
info@54: return "object";
info@54: }
info@54: return undefined;
info@54: },
info@54:
info@54: push: function(result, actual, expected, message) {
info@54: var details = {
info@54: result: result,
info@54: message: message,
info@54: actual: actual,
info@54: expected: expected
info@54: };
info@54:
info@54: message = escapeHtml(message) || (result ? "okay" : "failed");
info@54: message = '' + message + "";
info@54: expected = escapeHtml(QUnit.jsDump.parse(expected));
info@54: actual = escapeHtml(QUnit.jsDump.parse(actual));
info@54: var output = message + '
Expected: | ' + expected + ' |
';
info@54: if (actual != expected) {
info@54: output += 'Result: | ' + actual + ' |
';
info@54: output += 'Diff: | ' + QUnit.diff(expected, actual) +' |
';
info@54: }
info@54: if (!result) {
info@54: var source = sourceFromStacktrace();
info@54: if (source) {
info@54: details.source = source;
info@54: output += 'Source: | ' + escapeHtml(source) + ' |
';
info@54: }
info@54: }
info@54: output += "
";
info@54:
info@54: QUnit.log(details);
info@54:
info@54: config.current.assertions.push({
info@54: result: !!result,
info@54: message: output
info@54: });
info@54: },
info@54:
info@54: url: function( params ) {
info@54: params = extend( extend( {}, QUnit.urlParams ), params );
info@54: var querystring = "?",
info@54: key;
info@54: for ( key in params ) {
info@54: querystring += encodeURIComponent( key ) + "=" +
info@54: encodeURIComponent( params[ key ] ) + "&";
info@54: }
info@54: return window.location.pathname + querystring.slice( 0, -1 );
info@54: },
info@54:
info@54: extend: extend,
info@54: id: id,
info@54: addEvent: addEvent,
info@54:
info@54: // Logging callbacks; all receive a single argument with the listed properties
info@54: // run test/logs.html for any related changes
info@54: begin: function() {},
info@54: // done: { failed, passed, total, runtime }
info@54: done: function() {},
info@54: // log: { result, actual, expected, message }
info@54: log: function() {},
info@54: // testStart: { name }
info@54: testStart: function() {},
info@54: // testDone: { name, failed, passed, total }
info@54: testDone: function() {},
info@54: // moduleStart: { name }
info@54: moduleStart: function() {},
info@54: // moduleDone: { name, failed, passed, total }
info@54: moduleDone: function() {}
info@54: });
info@54:
info@54: if ( typeof document === "undefined" || document.readyState === "complete" ) {
info@54: config.autorun = true;
info@54: }
info@54:
info@54: QUnit.load = function() {
info@54: QUnit.begin({});
info@54:
info@54: // Initialize the config, saving the execution queue
info@54: var oldconfig = extend({}, config);
info@54: QUnit.init();
info@54: extend(config, oldconfig);
info@54:
info@54: config.blocking = false;
info@54:
info@54: var urlConfigHtml = '', len = config.urlConfig.length;
info@54: for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) {
info@54: config[val] = QUnit.urlParams[val];
info@54: urlConfigHtml += '';
info@54: }
info@54:
info@54: var userAgent = id("qunit-userAgent");
info@54: if ( userAgent ) {
info@54: userAgent.innerHTML = navigator.userAgent;
info@54: }
info@54: var banner = id("qunit-header");
info@54: if ( banner ) {
info@54: banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml;
info@54: addEvent( banner, "change", function( event ) {
info@54: var params = {};
info@54: params[ event.target.name ] = event.target.checked ? true : undefined;
info@54: window.location = QUnit.url( params );
info@54: });
info@54: }
info@54:
info@54: var toolbar = id("qunit-testrunner-toolbar");
info@54: if ( toolbar ) {
info@54: var filter = document.createElement("input");
info@54: filter.type = "checkbox";
info@54: filter.id = "qunit-filter-pass";
info@54: addEvent( filter, "click", function() {
info@54: var ol = document.getElementById("qunit-tests");
info@54: if ( filter.checked ) {
info@54: ol.className = ol.className + " hidepass";
info@54: } else {
info@54: var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
info@54: ol.className = tmp.replace(/ hidepass /, " ");
info@54: }
info@54: if ( defined.sessionStorage ) {
info@54: if (filter.checked) {
info@54: sessionStorage.setItem("qunit-filter-passed-tests", "true");
info@54: } else {
info@54: sessionStorage.removeItem("qunit-filter-passed-tests");
info@54: }
info@54: }
info@54: });
info@54: if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
info@54: filter.checked = true;
info@54: var ol = document.getElementById("qunit-tests");
info@54: ol.className = ol.className + " hidepass";
info@54: }
info@54: toolbar.appendChild( filter );
info@54:
info@54: var label = document.createElement("label");
info@54: label.setAttribute("for", "qunit-filter-pass");
info@54: label.innerHTML = "Hide passed tests";
info@54: toolbar.appendChild( label );
info@54: }
info@54:
info@54: var main = id('qunit-fixture');
info@54: if ( main ) {
info@54: config.fixture = main.innerHTML;
info@54: }
info@54:
info@54: if (config.autostart) {
info@54: QUnit.start();
info@54: }
info@54: };
info@54:
info@54: addEvent(window, "load", QUnit.load);
info@54:
info@54: function done() {
info@54: config.autorun = true;
info@54:
info@54: // Log the last module results
info@54: if ( config.currentModule ) {
info@54: QUnit.moduleDone( {
info@54: name: config.currentModule,
info@54: failed: config.moduleStats.bad,
info@54: passed: config.moduleStats.all - config.moduleStats.bad,
info@54: total: config.moduleStats.all
info@54: } );
info@54: }
info@54:
info@54: var banner = id("qunit-banner"),
info@54: tests = id("qunit-tests"),
info@54: runtime = +new Date - config.started,
info@54: passed = config.stats.all - config.stats.bad,
info@54: html = [
info@54: 'Tests completed in ',
info@54: runtime,
info@54: ' milliseconds.
',
info@54: '',
info@54: passed,
info@54: ' tests of ',
info@54: config.stats.all,
info@54: ' passed, ',
info@54: config.stats.bad,
info@54: ' failed.'
info@54: ].join('');
info@54:
info@54: if ( banner ) {
info@54: banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
info@54: }
info@54:
info@54: if ( tests ) {
info@54: id( "qunit-testresult" ).innerHTML = html;
info@54: }
info@54:
info@54: if ( config.altertitle && typeof document !== "undefined" && document.title ) {
info@54: // show ✖ for good, ✔ for bad suite result in title
info@54: // use escape sequences in case file gets loaded with non-utf-8-charset
info@54: document.title = [
info@54: (config.stats.bad ? "\u2716" : "\u2714"),
info@54: document.title.replace(/^[\u2714\u2716] /i, "")
info@54: ].join(" ");
info@54: }
info@54:
info@54: QUnit.done( {
info@54: failed: config.stats.bad,
info@54: passed: passed,
info@54: total: config.stats.all,
info@54: runtime: runtime
info@54: } );
info@54: }
info@54:
info@54: function validTest( name ) {
info@54: var filter = config.filter,
info@54: run = false;
info@54:
info@54: if ( !filter ) {
info@54: return true;
info@54: }
info@54:
info@54: var not = filter.charAt( 0 ) === "!";
info@54: if ( not ) {
info@54: filter = filter.slice( 1 );
info@54: }
info@54:
info@54: if ( name.indexOf( filter ) !== -1 ) {
info@54: return !not;
info@54: }
info@54:
info@54: if ( not ) {
info@54: run = true;
info@54: }
info@54:
info@54: return run;
info@54: }
info@54:
info@54: // so far supports only Firefox, Chrome and Opera (buggy)
info@54: // could be extended in the future to use something like https://github.com/csnover/TraceKit
info@54: function sourceFromStacktrace() {
info@54: try {
info@54: throw new Error();
info@54: } catch ( e ) {
info@54: if (e.stacktrace) {
info@54: // Opera
info@54: return e.stacktrace.split("\n")[6];
info@54: } else if (e.stack) {
info@54: // Firefox, Chrome
info@54: return e.stack.split("\n")[4];
info@54: } else if (e.sourceURL) {
info@54: // Safari, PhantomJS
info@54: // TODO sourceURL points at the 'throw new Error' line above, useless
info@54: //return e.sourceURL + ":" + e.line;
info@54: }
info@54: }
info@54: }
info@54:
info@54: function escapeHtml(s) {
info@54: if (!s) {
info@54: return "";
info@54: }
info@54: s = s + "";
info@54: return s.replace(/[\&"<>\\]/g, function(s) {
info@54: switch(s) {
info@54: case "&": return "&";
info@54: case "\\": return "\\\\";
info@54: case '"': return '\"';
info@54: case "<": return "<";
info@54: case ">": return ">";
info@54: default: return s;
info@54: }
info@54: });
info@54: }
info@54:
info@54: function synchronize( callback ) {
info@54: config.queue.push( callback );
info@54:
info@54: if ( config.autorun && !config.blocking ) {
info@54: process();
info@54: }
info@54: }
info@54:
info@54: function process() {
info@54: var start = (new Date()).getTime();
info@54:
info@54: while ( config.queue.length && !config.blocking ) {
info@54: if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
info@54: config.queue.shift()();
info@54: } else {
info@54: window.setTimeout( process, 13 );
info@54: break;
info@54: }
info@54: }
info@54: if (!config.blocking && !config.queue.length) {
info@54: done();
info@54: }
info@54: }
info@54:
info@54: function saveGlobal() {
info@54: config.pollution = [];
info@54:
info@54: if ( config.noglobals ) {
info@54: for ( var key in window ) {
info@54: config.pollution.push( key );
info@54: }
info@54: }
info@54: }
info@54:
info@54: function checkPollution( name ) {
info@54: var old = config.pollution;
info@54: saveGlobal();
info@54:
info@54: var newGlobals = diff( config.pollution, old );
info@54: if ( newGlobals.length > 0 ) {
info@54: ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
info@54: }
info@54:
info@54: var deletedGlobals = diff( old, config.pollution );
info@54: if ( deletedGlobals.length > 0 ) {
info@54: ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
info@54: }
info@54: }
info@54:
info@54: // returns a new Array with the elements that are in a but not in b
info@54: function diff( a, b ) {
info@54: var result = a.slice();
info@54: for ( var i = 0; i < result.length; i++ ) {
info@54: for ( var j = 0; j < b.length; j++ ) {
info@54: if ( result[i] === b[j] ) {
info@54: result.splice(i, 1);
info@54: i--;
info@54: break;
info@54: }
info@54: }
info@54: }
info@54: return result;
info@54: }
info@54:
info@54: function fail(message, exception, callback) {
info@54: if ( typeof console !== "undefined" && console.error && console.warn ) {
info@54: console.error(message);
info@54: console.error(exception);
info@54: console.warn(callback.toString());
info@54:
info@54: } else if ( window.opera && opera.postError ) {
info@54: opera.postError(message, exception, callback.toString);
info@54: }
info@54: }
info@54:
info@54: function extend(a, b) {
info@54: for ( var prop in b ) {
info@54: if ( b[prop] === undefined ) {
info@54: delete a[prop];
info@54: } else {
info@54: a[prop] = b[prop];
info@54: }
info@54: }
info@54:
info@54: return a;
info@54: }
info@54:
info@54: function addEvent(elem, type, fn) {
info@54: if ( elem.addEventListener ) {
info@54: elem.addEventListener( type, fn, false );
info@54: } else if ( elem.attachEvent ) {
info@54: elem.attachEvent( "on" + type, fn );
info@54: } else {
info@54: fn();
info@54: }
info@54: }
info@54:
info@54: function id(name) {
info@54: return !!(typeof document !== "undefined" && document && document.getElementById) &&
info@54: document.getElementById( name );
info@54: }
info@54:
info@54: // Test for equality any JavaScript type.
info@54: // Discussions and reference: http://philrathe.com/articles/equiv
info@54: // Test suites: http://philrathe.com/tests/equiv
info@54: // Author: Philippe Rathé
info@54: QUnit.equiv = function () {
info@54:
info@54: var innerEquiv; // the real equiv function
info@54: var callers = []; // stack to decide between skip/abort functions
info@54: var parents = []; // stack to avoiding loops from circular referencing
info@54:
info@54: // Call the o related callback with the given arguments.
info@54: function bindCallbacks(o, callbacks, args) {
info@54: var prop = QUnit.objectType(o);
info@54: if (prop) {
info@54: if (QUnit.objectType(callbacks[prop]) === "function") {
info@54: return callbacks[prop].apply(callbacks, args);
info@54: } else {
info@54: return callbacks[prop]; // or undefined
info@54: }
info@54: }
info@54: }
info@54:
info@54: var callbacks = function () {
info@54:
info@54: // for string, boolean, number and null
info@54: function useStrictEquality(b, a) {
info@54: if (b instanceof a.constructor || a instanceof b.constructor) {
info@54: // to catch short annotaion VS 'new' annotation of a
info@54: // declaration
info@54: // e.g. var i = 1;
info@54: // var j = new Number(1);
info@54: return a == b;
info@54: } else {
info@54: return a === b;
info@54: }
info@54: }
info@54:
info@54: return {
info@54: "string" : useStrictEquality,
info@54: "boolean" : useStrictEquality,
info@54: "number" : useStrictEquality,
info@54: "null" : useStrictEquality,
info@54: "undefined" : useStrictEquality,
info@54:
info@54: "nan" : function(b) {
info@54: return isNaN(b);
info@54: },
info@54:
info@54: "date" : function(b, a) {
info@54: return QUnit.objectType(b) === "date"
info@54: && a.valueOf() === b.valueOf();
info@54: },
info@54:
info@54: "regexp" : function(b, a) {
info@54: return QUnit.objectType(b) === "regexp"
info@54: && a.source === b.source && // the regex itself
info@54: a.global === b.global && // and its modifers
info@54: // (gmi) ...
info@54: a.ignoreCase === b.ignoreCase
info@54: && a.multiline === b.multiline;
info@54: },
info@54:
info@54: // - skip when the property is a method of an instance (OOP)
info@54: // - abort otherwise,
info@54: // initial === would have catch identical references anyway
info@54: "function" : function() {
info@54: var caller = callers[callers.length - 1];
info@54: return caller !== Object && typeof caller !== "undefined";
info@54: },
info@54:
info@54: "array" : function(b, a) {
info@54: var i, j, loop;
info@54: var len;
info@54:
info@54: // b could be an object literal here
info@54: if (!(QUnit.objectType(b) === "array")) {
info@54: return false;
info@54: }
info@54:
info@54: len = a.length;
info@54: if (len !== b.length) { // safe and faster
info@54: return false;
info@54: }
info@54:
info@54: // track reference to avoid circular references
info@54: parents.push(a);
info@54: for (i = 0; i < len; i++) {
info@54: loop = false;
info@54: for (j = 0; j < parents.length; j++) {
info@54: if (parents[j] === a[i]) {
info@54: loop = true;// dont rewalk array
info@54: }
info@54: }
info@54: if (!loop && !innerEquiv(a[i], b[i])) {
info@54: parents.pop();
info@54: return false;
info@54: }
info@54: }
info@54: parents.pop();
info@54: return true;
info@54: },
info@54:
info@54: "object" : function(b, a) {
info@54: var i, j, loop;
info@54: var eq = true; // unless we can proove it
info@54: var aProperties = [], bProperties = []; // collection of
info@54: // strings
info@54:
info@54: // comparing constructors is more strict than using
info@54: // instanceof
info@54: if (a.constructor !== b.constructor) {
info@54: return false;
info@54: }
info@54:
info@54: // stack constructor before traversing properties
info@54: callers.push(a.constructor);
info@54: // track reference to avoid circular references
info@54: parents.push(a);
info@54:
info@54: for (i in a) { // be strict: don't ensures hasOwnProperty
info@54: // and go deep
info@54: loop = false;
info@54: for (j = 0; j < parents.length; j++) {
info@54: if (parents[j] === a[i])
info@54: loop = true; // don't go down the same path
info@54: // twice
info@54: }
info@54: aProperties.push(i); // collect a's properties
info@54:
info@54: if (!loop && !innerEquiv(a[i], b[i])) {
info@54: eq = false;
info@54: break;
info@54: }
info@54: }
info@54:
info@54: callers.pop(); // unstack, we are done
info@54: parents.pop();
info@54:
info@54: for (i in b) {
info@54: bProperties.push(i); // collect b's properties
info@54: }
info@54:
info@54: // Ensures identical properties name
info@54: return eq
info@54: && innerEquiv(aProperties.sort(), bProperties
info@54: .sort());
info@54: }
info@54: };
info@54: }();
info@54:
info@54: innerEquiv = function() { // can take multiple arguments
info@54: var args = Array.prototype.slice.apply(arguments);
info@54: if (args.length < 2) {
info@54: return true; // end transition
info@54: }
info@54:
info@54: return (function(a, b) {
info@54: if (a === b) {
info@54: return true; // catch the most you can
info@54: } else if (a === null || b === null || typeof a === "undefined"
info@54: || typeof b === "undefined"
info@54: || QUnit.objectType(a) !== QUnit.objectType(b)) {
info@54: return false; // don't lose time with error prone cases
info@54: } else {
info@54: return bindCallbacks(a, callbacks, [ b, a ]);
info@54: }
info@54:
info@54: // apply transition with (1..n) arguments
info@54: })(args[0], args[1])
info@54: && arguments.callee.apply(this, args.splice(1,
info@54: args.length - 1));
info@54: };
info@54:
info@54: return innerEquiv;
info@54:
info@54: }();
info@54:
info@54: /**
info@54: * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
info@54: * http://flesler.blogspot.com Licensed under BSD
info@54: * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
info@54: *
info@54: * @projectDescription Advanced and extensible data dumping for Javascript.
info@54: * @version 1.0.0
info@54: * @author Ariel Flesler
info@54: * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
info@54: */
info@54: QUnit.jsDump = (function() {
info@54: function quote( str ) {
info@54: return '"' + str.toString().replace(/"/g, '\\"') + '"';
info@54: };
info@54: function literal( o ) {
info@54: return o + '';
info@54: };
info@54: function join( pre, arr, post ) {
info@54: var s = jsDump.separator(),
info@54: base = jsDump.indent(),
info@54: inner = jsDump.indent(1);
info@54: if ( arr.join )
info@54: arr = arr.join( ',' + s + inner );
info@54: if ( !arr )
info@54: return pre + post;
info@54: return [ pre, inner + arr, base + post ].join(s);
info@54: };
info@54: function array( arr, stack ) {
info@54: var i = arr.length, ret = Array(i);
info@54: this.up();
info@54: while ( i-- )
info@54: ret[i] = this.parse( arr[i] , undefined , stack);
info@54: this.down();
info@54: return join( '[', ret, ']' );
info@54: };
info@54:
info@54: var reName = /^function (\w+)/;
info@54:
info@54: var jsDump = {
info@54: parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
info@54: stack = stack || [ ];
info@54: var parser = this.parsers[ type || this.typeOf(obj) ];
info@54: type = typeof parser;
info@54: var inStack = inArray(obj, stack);
info@54: if (inStack != -1) {
info@54: return 'recursion('+(inStack - stack.length)+')';
info@54: }
info@54: //else
info@54: if (type == 'function') {
info@54: stack.push(obj);
info@54: var res = parser.call( this, obj, stack );
info@54: stack.pop();
info@54: return res;
info@54: }
info@54: // else
info@54: return (type == 'string') ? parser : this.parsers.error;
info@54: },
info@54: typeOf:function( obj ) {
info@54: var type;
info@54: if ( obj === null ) {
info@54: type = "null";
info@54: } else if (typeof obj === "undefined") {
info@54: type = "undefined";
info@54: } else if (QUnit.is("RegExp", obj)) {
info@54: type = "regexp";
info@54: } else if (QUnit.is("Date", obj)) {
info@54: type = "date";
info@54: } else if (QUnit.is("Function", obj)) {
info@54: type = "function";
info@54: } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
info@54: type = "window";
info@54: } else if (obj.nodeType === 9) {
info@54: type = "document";
info@54: } else if (obj.nodeType) {
info@54: type = "node";
info@54: } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
info@54: type = "array";
info@54: } else {
info@54: type = typeof obj;
info@54: }
info@54: return type;
info@54: },
info@54: separator:function() {
info@54: return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
info@54: },
info@54: indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
info@54: if ( !this.multiline )
info@54: return '';
info@54: var chr = this.indentChar;
info@54: if ( this.HTML )
info@54: chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
info@54: return Array( this._depth_ + (extra||0) ).join(chr);
info@54: },
info@54: up:function( a ) {
info@54: this._depth_ += a || 1;
info@54: },
info@54: down:function( a ) {
info@54: this._depth_ -= a || 1;
info@54: },
info@54: setParser:function( name, parser ) {
info@54: this.parsers[name] = parser;
info@54: },
info@54: // The next 3 are exposed so you can use them
info@54: quote:quote,
info@54: literal:literal,
info@54: join:join,
info@54: //
info@54: _depth_: 1,
info@54: // This is the list of parsers, to modify them, use jsDump.setParser
info@54: parsers:{
info@54: window: '[Window]',
info@54: document: '[Document]',
info@54: error:'[ERROR]', //when no parser is found, shouldn't happen
info@54: unknown: '[Unknown]',
info@54: 'null':'null',
info@54: 'undefined':'undefined',
info@54: 'function':function( fn ) {
info@54: var ret = 'function',
info@54: name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
info@54: if ( name )
info@54: ret += ' ' + name;
info@54: ret += '(';
info@54:
info@54: ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
info@54: return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
info@54: },
info@54: array: array,
info@54: nodelist: array,
info@54: arguments: array,
info@54: object:function( map, stack ) {
info@54: var ret = [ ];
info@54: QUnit.jsDump.up();
info@54: for ( var key in map ) {
info@54: var val = map[key];
info@54: ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
info@54: }
info@54: QUnit.jsDump.down();
info@54: return join( '{', ret, '}' );
info@54: },
info@54: node:function( node ) {
info@54: var open = QUnit.jsDump.HTML ? '<' : '<',
info@54: close = QUnit.jsDump.HTML ? '>' : '>';
info@54:
info@54: var tag = node.nodeName.toLowerCase(),
info@54: ret = open + tag;
info@54:
info@54: for ( var a in QUnit.jsDump.DOMAttrs ) {
info@54: var val = node[QUnit.jsDump.DOMAttrs[a]];
info@54: if ( val )
info@54: ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
info@54: }
info@54: return ret + close + open + '/' + tag + close;
info@54: },
info@54: functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
info@54: var l = fn.length;
info@54: if ( !l ) return '';
info@54:
info@54: var args = Array(l);
info@54: while ( l-- )
info@54: args[l] = String.fromCharCode(97+l);//97 is 'a'
info@54: return ' ' + args.join(', ') + ' ';
info@54: },
info@54: key:quote, //object calls it internally, the key part of an item in a map
info@54: functionCode:'[code]', //function calls it internally, it's the content of the function
info@54: attribute:quote, //node calls it internally, it's an html attribute value
info@54: string:quote,
info@54: date:quote,
info@54: regexp:literal, //regex
info@54: number:literal,
info@54: 'boolean':literal
info@54: },
info@54: DOMAttrs:{//attributes to dump from nodes, name=>realName
info@54: id:'id',
info@54: name:'name',
info@54: 'class':'className'
info@54: },
info@54: HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
info@54: indentChar:' ',//indentation unit
info@54: multiline:true //if true, items in a collection, are separated by a \n, else just a space.
info@54: };
info@54:
info@54: return jsDump;
info@54: })();
info@54:
info@54: // from Sizzle.js
info@54: function getText( elems ) {
info@54: var ret = "", elem;
info@54:
info@54: for ( var i = 0; elems[i]; i++ ) {
info@54: elem = elems[i];
info@54:
info@54: // Get the text from text nodes and CDATA nodes
info@54: if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
info@54: ret += elem.nodeValue;
info@54:
info@54: // Traverse everything else, except comment nodes
info@54: } else if ( elem.nodeType !== 8 ) {
info@54: ret += getText( elem.childNodes );
info@54: }
info@54: }
info@54:
info@54: return ret;
info@54: };
info@54:
info@54: //from jquery.js
info@54: function inArray( elem, array ) {
info@54: if ( array.indexOf ) {
info@54: return array.indexOf( elem );
info@54: }
info@54:
info@54: for ( var i = 0, length = array.length; i < length; i++ ) {
info@54: if ( array[ i ] === elem ) {
info@54: return i;
info@54: }
info@54: }
info@54:
info@54: return -1;
info@54: }
info@54:
info@54: /*
info@54: * Javascript Diff Algorithm
info@54: * By John Resig (http://ejohn.org/)
info@54: * Modified by Chu Alan "sprite"
info@54: *
info@54: * Released under the MIT license.
info@54: *
info@54: * More Info:
info@54: * http://ejohn.org/projects/javascript-diff-algorithm/
info@54: *
info@54: * Usage: QUnit.diff(expected, actual)
info@54: *
info@54: * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over"
info@54: */
info@54: QUnit.diff = (function() {
info@54: function diff(o, n) {
info@54: var ns = {};
info@54: var os = {};
info@54:
info@54: for (var i = 0; i < n.length; i++) {
info@54: if (ns[n[i]] == null)
info@54: ns[n[i]] = {
info@54: rows: [],
info@54: o: null
info@54: };
info@54: ns[n[i]].rows.push(i);
info@54: }
info@54:
info@54: for (var i = 0; i < o.length; i++) {
info@54: if (os[o[i]] == null)
info@54: os[o[i]] = {
info@54: rows: [],
info@54: n: null
info@54: };
info@54: os[o[i]].rows.push(i);
info@54: }
info@54:
info@54: for (var i in ns) {
info@54: if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
info@54: n[ns[i].rows[0]] = {
info@54: text: n[ns[i].rows[0]],
info@54: row: os[i].rows[0]
info@54: };
info@54: o[os[i].rows[0]] = {
info@54: text: o[os[i].rows[0]],
info@54: row: ns[i].rows[0]
info@54: };
info@54: }
info@54: }
info@54:
info@54: for (var i = 0; i < n.length - 1; i++) {
info@54: if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
info@54: n[i + 1] == o[n[i].row + 1]) {
info@54: n[i + 1] = {
info@54: text: n[i + 1],
info@54: row: n[i].row + 1
info@54: };
info@54: o[n[i].row + 1] = {
info@54: text: o[n[i].row + 1],
info@54: row: i + 1
info@54: };
info@54: }
info@54: }
info@54:
info@54: for (var i = n.length - 1; i > 0; i--) {
info@54: if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
info@54: n[i - 1] == o[n[i].row - 1]) {
info@54: n[i - 1] = {
info@54: text: n[i - 1],
info@54: row: n[i].row - 1
info@54: };
info@54: o[n[i].row - 1] = {
info@54: text: o[n[i].row - 1],
info@54: row: i - 1
info@54: };
info@54: }
info@54: }
info@54:
info@54: return {
info@54: o: o,
info@54: n: n
info@54: };
info@54: }
info@54:
info@54: return function(o, n) {
info@54: o = o.replace(/\s+$/, '');
info@54: n = n.replace(/\s+$/, '');
info@54: var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
info@54:
info@54: var str = "";
info@54:
info@54: var oSpace = o.match(/\s+/g);
info@54: if (oSpace == null) {
info@54: oSpace = [" "];
info@54: }
info@54: else {
info@54: oSpace.push(" ");
info@54: }
info@54: var nSpace = n.match(/\s+/g);
info@54: if (nSpace == null) {
info@54: nSpace = [" "];
info@54: }
info@54: else {
info@54: nSpace.push(" ");
info@54: }
info@54:
info@54: if (out.n.length == 0) {
info@54: for (var i = 0; i < out.o.length; i++) {
info@54: str += '' + out.o[i] + oSpace[i] + "";
info@54: }
info@54: }
info@54: else {
info@54: if (out.n[0].text == null) {
info@54: for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
info@54: str += '' + out.o[n] + oSpace[n] + "";
info@54: }
info@54: }
info@54:
info@54: for (var i = 0; i < out.n.length; i++) {
info@54: if (out.n[i].text == null) {
info@54: str += '' + out.n[i] + nSpace[i] + "";
info@54: }
info@54: else {
info@54: var pre = "";
info@54:
info@54: for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
info@54: pre += '' + out.o[n] + oSpace[n] + "";
info@54: }
info@54: str += " " + out.n[i].text + nSpace[i] + pre;
info@54: }
info@54: }
info@54: }
info@54:
info@54: return str;
info@54: };
info@54: })();
info@54:
info@54: })(this);