qunit.js 37 KB

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