qunit.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448
  1. /**
  2. * QUnit - A JavaScript Unit Testing Framework
  3. *
  4. * http://docs.jquery.com/QUnit
  5. *
  6. * Copyright (c) 2011 John Resig, Jörn Zaefferer
  7. * Dual licensed under the MIT (MIT-LICENSE.txt)
  8. * or GPL (GPL-LICENSE.txt) licenses.
  9. */
  10. (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.timeout ) {
  352. clearTimeout(config.timeout);
  353. }
  354. config.blocking = false;
  355. process();
  356. }, 13);
  357. } else {
  358. config.blocking = false;
  359. process();
  360. }
  361. },
  362. stop: function(timeout) {
  363. config.semaphore++;
  364. config.blocking = true;
  365. if ( timeout && defined.setTimeout ) {
  366. clearTimeout(config.timeout);
  367. config.timeout = window.setTimeout(function() {
  368. QUnit.ok( false, "Test timed out" );
  369. QUnit.start();
  370. }, timeout);
  371. }
  372. }
  373. };
  374. // Backwards compatibility, deprecated
  375. QUnit.equals = QUnit.equal;
  376. QUnit.same = QUnit.deepEqual;
  377. // Maintain internal state
  378. var config = {
  379. // The queue of tests to run
  380. queue: [],
  381. // block until document ready
  382. blocking: true,
  383. // by default, run previously failed tests first
  384. // very useful in combination with "Hide passed tests" checked
  385. reorder: true,
  386. noglobals: false,
  387. notrycatch: false
  388. };
  389. // Load paramaters
  390. (function() {
  391. var location = window.location || { search: "", protocol: "file:" },
  392. params = location.search.slice( 1 ).split( "&" ),
  393. length = params.length,
  394. urlParams = {},
  395. current;
  396. if ( params[ 0 ] ) {
  397. for ( var i = 0; i < length; i++ ) {
  398. current = params[ i ].split( "=" );
  399. current[ 0 ] = decodeURIComponent( current[ 0 ] );
  400. // allow just a key to turn on a flag, e.g., test.html?noglobals
  401. current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
  402. urlParams[ current[ 0 ] ] = current[ 1 ];
  403. if ( current[ 0 ] in config ) {
  404. config[ current[ 0 ] ] = current[ 1 ];
  405. }
  406. }
  407. }
  408. QUnit.urlParams = urlParams;
  409. config.filter = urlParams.filter;
  410. // Figure out if we're running the tests from a server or not
  411. QUnit.isLocal = !!(location.protocol === 'file:');
  412. })();
  413. // Expose the API as global variables, unless an 'exports'
  414. // object exists, in that case we assume we're in CommonJS
  415. if ( typeof exports === "undefined" || typeof require === "undefined" ) {
  416. extend(window, QUnit);
  417. window.QUnit = QUnit;
  418. } else {
  419. extend(exports, QUnit);
  420. exports.QUnit = QUnit;
  421. }
  422. // define these after exposing globals to keep them in these QUnit namespace only
  423. extend(QUnit, {
  424. config: config,
  425. // Initialize the configuration options
  426. init: function() {
  427. extend(config, {
  428. stats: { all: 0, bad: 0 },
  429. moduleStats: { all: 0, bad: 0 },
  430. started: +new Date,
  431. updateRate: 1000,
  432. blocking: false,
  433. autostart: true,
  434. autorun: false,
  435. filter: "",
  436. queue: [],
  437. semaphore: 0
  438. });
  439. var tests = id( "qunit-tests" ),
  440. banner = id( "qunit-banner" ),
  441. result = id( "qunit-testresult" );
  442. if ( tests ) {
  443. tests.innerHTML = "";
  444. }
  445. if ( banner ) {
  446. banner.className = "";
  447. }
  448. if ( result ) {
  449. result.parentNode.removeChild( result );
  450. }
  451. if ( tests ) {
  452. result = document.createElement( "p" );
  453. result.id = "qunit-testresult";
  454. result.className = "result";
  455. tests.parentNode.insertBefore( result, tests );
  456. result.innerHTML = 'Running...<br/>&nbsp;';
  457. }
  458. },
  459. /**
  460. * Resets the test setup. Useful for tests that modify the DOM.
  461. *
  462. * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
  463. */
  464. reset: function() {
  465. if ( window.jQuery ) {
  466. jQuery( "#qunit-fixture" ).html( config.fixture );
  467. } else {
  468. var main = id( 'qunit-fixture' );
  469. if ( main ) {
  470. main.innerHTML = config.fixture;
  471. }
  472. }
  473. },
  474. /**
  475. * Trigger an event on an element.
  476. *
  477. * @example triggerEvent( document.body, "click" );
  478. *
  479. * @param DOMElement elem
  480. * @param String type
  481. */
  482. triggerEvent: function( elem, type, event ) {
  483. if ( document.createEvent ) {
  484. event = document.createEvent("MouseEvents");
  485. event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
  486. 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  487. elem.dispatchEvent( event );
  488. } else if ( elem.fireEvent ) {
  489. elem.fireEvent("on"+type);
  490. }
  491. },
  492. // Safe object type checking
  493. is: function( type, obj ) {
  494. return QUnit.objectType( obj ) == type;
  495. },
  496. objectType: function( obj ) {
  497. if (typeof obj === "undefined") {
  498. return "undefined";
  499. // consider: typeof null === object
  500. }
  501. if (obj === null) {
  502. return "null";
  503. }
  504. var type = Object.prototype.toString.call( obj )
  505. .match(/^\[object\s(.*)\]$/)[1] || '';
  506. switch (type) {
  507. case 'Number':
  508. if (isNaN(obj)) {
  509. return "nan";
  510. } else {
  511. return "number";
  512. }
  513. case 'String':
  514. case 'Boolean':
  515. case 'Array':
  516. case 'Date':
  517. case 'RegExp':
  518. case 'Function':
  519. return type.toLowerCase();
  520. }
  521. if (typeof obj === "object") {
  522. return "object";
  523. }
  524. return undefined;
  525. },
  526. push: function(result, actual, expected, message) {
  527. var details = {
  528. result: result,
  529. message: message,
  530. actual: actual,
  531. expected: expected
  532. };
  533. message = escapeHtml(message) || (result ? "okay" : "failed");
  534. message = '<span class="test-message">' + message + "</span>";
  535. expected = escapeHtml(QUnit.jsDump.parse(expected));
  536. actual = escapeHtml(QUnit.jsDump.parse(actual));
  537. var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
  538. if (actual != expected) {
  539. output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
  540. output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
  541. }
  542. if (!result) {
  543. var source = sourceFromStacktrace();
  544. if (source) {
  545. details.source = source;
  546. output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
  547. }
  548. }
  549. output += "</table>";
  550. QUnit.log(details);
  551. config.current.assertions.push({
  552. result: !!result,
  553. message: output
  554. });
  555. },
  556. url: function( params ) {
  557. params = extend( extend( {}, QUnit.urlParams ), params );
  558. var querystring = "?",
  559. key;
  560. for ( key in params ) {
  561. querystring += encodeURIComponent( key ) + "=" +
  562. encodeURIComponent( params[ key ] ) + "&";
  563. }
  564. return window.location.pathname + querystring.slice( 0, -1 );
  565. },
  566. // Logging callbacks; all receive a single argument with the listed properties
  567. // run test/logs.html for any related changes
  568. begin: function() {},
  569. // done: { failed, passed, total, runtime }
  570. done: function() {},
  571. // log: { result, actual, expected, message }
  572. log: function() {},
  573. // testStart: { name }
  574. testStart: function() {},
  575. // testDone: { name, failed, passed, total }
  576. testDone: function() {},
  577. // moduleStart: { name }
  578. moduleStart: function() {},
  579. // moduleDone: { name, failed, passed, total }
  580. moduleDone: function() {}
  581. });
  582. if ( typeof document === "undefined" || document.readyState === "complete" ) {
  583. config.autorun = true;
  584. }
  585. addEvent(window, "load", function() {
  586. QUnit.begin({});
  587. // Initialize the config, saving the execution queue
  588. var oldconfig = extend({}, config);
  589. QUnit.init();
  590. extend(config, oldconfig);
  591. config.blocking = false;
  592. var userAgent = id("qunit-userAgent");
  593. if ( userAgent ) {
  594. userAgent.innerHTML = navigator.userAgent;
  595. }
  596. var banner = id("qunit-header");
  597. if ( banner ) {
  598. banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' +
  599. '<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' +
  600. '<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>';
  601. addEvent( banner, "change", function( event ) {
  602. var params = {};
  603. params[ event.target.name ] = event.target.checked ? true : undefined;
  604. window.location = QUnit.url( params );
  605. });
  606. }
  607. var toolbar = id("qunit-testrunner-toolbar");
  608. if ( toolbar ) {
  609. var filter = document.createElement("input");
  610. filter.type = "checkbox";
  611. filter.id = "qunit-filter-pass";
  612. addEvent( filter, "click", function() {
  613. var ol = document.getElementById("qunit-tests");
  614. if ( filter.checked ) {
  615. ol.className = ol.className + " hidepass";
  616. } else {
  617. var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
  618. ol.className = tmp.replace(/ hidepass /, " ");
  619. }
  620. if ( defined.sessionStorage ) {
  621. if (filter.checked) {
  622. sessionStorage.setItem("qunit-filter-passed-tests", "true");
  623. } else {
  624. sessionStorage.removeItem("qunit-filter-passed-tests");
  625. }
  626. }
  627. });
  628. if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
  629. filter.checked = true;
  630. var ol = document.getElementById("qunit-tests");
  631. ol.className = ol.className + " hidepass";
  632. }
  633. toolbar.appendChild( filter );
  634. var label = document.createElement("label");
  635. label.setAttribute("for", "qunit-filter-pass");
  636. label.innerHTML = "Hide passed tests";
  637. toolbar.appendChild( label );
  638. }
  639. var main = id('qunit-fixture');
  640. if ( main ) {
  641. config.fixture = main.innerHTML;
  642. }
  643. if (config.autostart) {
  644. QUnit.start();
  645. }
  646. });
  647. function done() {
  648. config.autorun = true;
  649. // Log the last module results
  650. if ( config.currentModule ) {
  651. QUnit.moduleDone( {
  652. name: config.currentModule,
  653. failed: config.moduleStats.bad,
  654. passed: config.moduleStats.all - config.moduleStats.bad,
  655. total: config.moduleStats.all
  656. } );
  657. }
  658. var banner = id("qunit-banner"),
  659. tests = id("qunit-tests"),
  660. runtime = +new Date - config.started,
  661. passed = config.stats.all - config.stats.bad,
  662. html = [
  663. 'Tests completed in ',
  664. runtime,
  665. ' milliseconds.<br/>',
  666. '<span class="passed">',
  667. passed,
  668. '</span> tests of <span class="total">',
  669. config.stats.all,
  670. '</span> passed, <span class="failed">',
  671. config.stats.bad,
  672. '</span> failed.'
  673. ].join('');
  674. if ( banner ) {
  675. banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
  676. }
  677. if ( tests ) {
  678. id( "qunit-testresult" ).innerHTML = html;
  679. }
  680. if ( typeof document !== "undefined" && document.title ) {
  681. // show ✖ for good, ✔ for bad suite result in title
  682. // use escape sequences in case file gets loaded with non-utf-8-charset
  683. document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title;
  684. }
  685. QUnit.done( {
  686. failed: config.stats.bad,
  687. passed: passed,
  688. total: config.stats.all,
  689. runtime: runtime
  690. } );
  691. }
  692. function validTest( name ) {
  693. var filter = config.filter,
  694. run = false;
  695. if ( !filter ) {
  696. return true;
  697. }
  698. var not = filter.charAt( 0 ) === "!";
  699. if ( not ) {
  700. filter = filter.slice( 1 );
  701. }
  702. if ( name.indexOf( filter ) !== -1 ) {
  703. return !not;
  704. }
  705. if ( not ) {
  706. run = true;
  707. }
  708. return run;
  709. }
  710. // so far supports only Firefox, Chrome and Opera (buggy)
  711. // could be extended in the future to use something like https://github.com/csnover/TraceKit
  712. function sourceFromStacktrace() {
  713. try {
  714. throw new Error();
  715. } catch ( e ) {
  716. if (e.stacktrace) {
  717. // Opera
  718. return e.stacktrace.split("\n")[6];
  719. } else if (e.stack) {
  720. // Firefox, Chrome
  721. return e.stack.split("\n")[4];
  722. }
  723. }
  724. }
  725. function escapeHtml(s) {
  726. if (!s) {
  727. return "";
  728. }
  729. s = s + "";
  730. return s.replace(/[\&"<>\\]/g, function(s) {
  731. switch(s) {
  732. case "&": return "&amp;";
  733. case "\\": return "\\\\";
  734. case '"': return '\"';
  735. case "<": return "&lt;";
  736. case ">": return "&gt;";
  737. default: return s;
  738. }
  739. });
  740. }
  741. function synchronize( callback ) {
  742. config.queue.push( callback );
  743. if ( config.autorun && !config.blocking ) {
  744. process();
  745. }
  746. }
  747. function process() {
  748. var start = (new Date()).getTime();
  749. while ( config.queue.length && !config.blocking ) {
  750. if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
  751. config.queue.shift()();
  752. } else {
  753. window.setTimeout( process, 13 );
  754. break;
  755. }
  756. }
  757. if (!config.blocking && !config.queue.length) {
  758. done();
  759. }
  760. }
  761. function saveGlobal() {
  762. config.pollution = [];
  763. if ( config.noglobals ) {
  764. for ( var key in window ) {
  765. config.pollution.push( key );
  766. }
  767. }
  768. }
  769. function checkPollution( name ) {
  770. var old = config.pollution;
  771. saveGlobal();
  772. var newGlobals = diff( config.pollution, old );
  773. if ( newGlobals.length > 0 ) {
  774. ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
  775. }
  776. var deletedGlobals = diff( old, config.pollution );
  777. if ( deletedGlobals.length > 0 ) {
  778. ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
  779. }
  780. }
  781. // returns a new Array with the elements that are in a but not in b
  782. function diff( a, b ) {
  783. var result = a.slice();
  784. for ( var i = 0; i < result.length; i++ ) {
  785. for ( var j = 0; j < b.length; j++ ) {
  786. if ( result[i] === b[j] ) {
  787. result.splice(i, 1);
  788. i--;
  789. break;
  790. }
  791. }
  792. }
  793. return result;
  794. }
  795. function fail(message, exception, callback) {
  796. if ( typeof console !== "undefined" && console.error && console.warn ) {
  797. console.error(message);
  798. console.error(exception);
  799. console.warn(callback.toString());
  800. } else if ( window.opera && opera.postError ) {
  801. opera.postError(message, exception, callback.toString);
  802. }
  803. }
  804. function extend(a, b) {
  805. for ( var prop in b ) {
  806. if ( b[prop] === undefined ) {
  807. delete a[prop];
  808. } else {
  809. a[prop] = b[prop];
  810. }
  811. }
  812. return a;
  813. }
  814. function addEvent(elem, type, fn) {
  815. if ( elem.addEventListener ) {
  816. elem.addEventListener( type, fn, false );
  817. } else if ( elem.attachEvent ) {
  818. elem.attachEvent( "on" + type, fn );
  819. } else {
  820. fn();
  821. }
  822. }
  823. function id(name) {
  824. return !!(typeof document !== "undefined" && document && document.getElementById) &&
  825. document.getElementById( name );
  826. }
  827. // Test for equality any JavaScript type.
  828. // Discussions and reference: http://philrathe.com/articles/equiv
  829. // Test suites: http://philrathe.com/tests/equiv
  830. // Author: Philippe Rathé <prathe@gmail.com>
  831. QUnit.equiv = function () {
  832. var innerEquiv; // the real equiv function
  833. var callers = []; // stack to decide between skip/abort functions
  834. var parents = []; // stack to avoiding loops from circular referencing
  835. // Call the o related callback with the given arguments.
  836. function bindCallbacks(o, callbacks, args) {
  837. var prop = QUnit.objectType(o);
  838. if (prop) {
  839. if (QUnit.objectType(callbacks[prop]) === "function") {
  840. return callbacks[prop].apply(callbacks, args);
  841. } else {
  842. return callbacks[prop]; // or undefined
  843. }
  844. }
  845. }
  846. var callbacks = function () {
  847. // for string, boolean, number and null
  848. function useStrictEquality(b, a) {
  849. if (b instanceof a.constructor || a instanceof b.constructor) {
  850. // to catch short annotaion VS 'new' annotation of a declaration
  851. // e.g. var i = 1;
  852. // var j = new Number(1);
  853. return a == b;
  854. } else {
  855. return a === b;
  856. }
  857. }
  858. return {
  859. "string": useStrictEquality,
  860. "boolean": useStrictEquality,
  861. "number": useStrictEquality,
  862. "null": useStrictEquality,
  863. "undefined": useStrictEquality,
  864. "nan": function (b) {
  865. return isNaN(b);
  866. },
  867. "date": function (b, a) {
  868. return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
  869. },
  870. "regexp": function (b, a) {
  871. return QUnit.objectType(b) === "regexp" &&
  872. a.source === b.source && // the regex itself
  873. a.global === b.global && // and its modifers (gmi) ...
  874. a.ignoreCase === b.ignoreCase &&
  875. a.multiline === b.multiline;
  876. },
  877. // - skip when the property is a method of an instance (OOP)
  878. // - abort otherwise,
  879. // initial === would have catch identical references anyway
  880. "function": function () {
  881. var caller = callers[callers.length - 1];
  882. return caller !== Object &&
  883. typeof caller !== "undefined";
  884. },
  885. "array": function (b, a) {
  886. var i, j, loop;
  887. var len;
  888. // b could be an object literal here
  889. if ( ! (QUnit.objectType(b) === "array")) {
  890. return false;
  891. }
  892. len = a.length;
  893. if (len !== b.length) { // safe and faster
  894. return false;
  895. }
  896. //track reference to avoid circular references
  897. parents.push(a);
  898. for (i = 0; i < len; i++) {
  899. loop = false;
  900. for(j=0;j<parents.length;j++){
  901. if(parents[j] === a[i]){
  902. loop = true;//dont rewalk array
  903. }
  904. }
  905. if (!loop && ! innerEquiv(a[i], b[i])) {
  906. parents.pop();
  907. return false;
  908. }
  909. }
  910. parents.pop();
  911. return true;
  912. },
  913. "object": function (b, a) {
  914. var i, j, loop;
  915. var eq = true; // unless we can proove it
  916. var aProperties = [], bProperties = []; // collection of strings
  917. // comparing constructors is more strict than using instanceof
  918. if ( a.constructor !== b.constructor) {
  919. return false;
  920. }
  921. // stack constructor before traversing properties
  922. callers.push(a.constructor);
  923. //track reference to avoid circular references
  924. parents.push(a);
  925. for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
  926. loop = false;
  927. for(j=0;j<parents.length;j++){
  928. if(parents[j] === a[i])
  929. loop = true; //don't go down the same path twice
  930. }
  931. aProperties.push(i); // collect a's properties
  932. if (!loop && ! innerEquiv(a[i], b[i])) {
  933. eq = false;
  934. break;
  935. }
  936. }
  937. callers.pop(); // unstack, we are done
  938. parents.pop();
  939. for (i in b) {
  940. bProperties.push(i); // collect b's properties
  941. }
  942. // Ensures identical properties name
  943. return eq && innerEquiv(aProperties.sort(), bProperties.sort());
  944. }
  945. };
  946. }();
  947. innerEquiv = function () { // can take multiple arguments
  948. var args = Array.prototype.slice.apply(arguments);
  949. if (args.length < 2) {
  950. return true; // end transition
  951. }
  952. return (function (a, b) {
  953. if (a === b) {
  954. return true; // catch the most you can
  955. } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
  956. return false; // don't lose time with error prone cases
  957. } else {
  958. return bindCallbacks(a, callbacks, [b, a]);
  959. }
  960. // apply transition with (1..n) arguments
  961. })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
  962. };
  963. return innerEquiv;
  964. }();
  965. /**
  966. * jsDump
  967. * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
  968. * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
  969. * Date: 5/15/2008
  970. * @projectDescription Advanced and extensible data dumping for Javascript.
  971. * @version 1.0.0
  972. * @author Ariel Flesler
  973. * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
  974. */
  975. QUnit.jsDump = (function() {
  976. function quote( str ) {
  977. return '"' + str.toString().replace(/"/g, '\\"') + '"';
  978. };
  979. function literal( o ) {
  980. return o + '';
  981. };
  982. function join( pre, arr, post ) {
  983. var s = jsDump.separator(),
  984. base = jsDump.indent(),
  985. inner = jsDump.indent(1);
  986. if ( arr.join )
  987. arr = arr.join( ',' + s + inner );
  988. if ( !arr )
  989. return pre + post;
  990. return [ pre, inner + arr, base + post ].join(s);
  991. };
  992. function array( arr ) {
  993. var i = arr.length, ret = Array(i);
  994. this.up();
  995. while ( i-- )
  996. ret[i] = this.parse( arr[i] );
  997. this.down();
  998. return join( '[', ret, ']' );
  999. };
  1000. var reName = /^function (\w+)/;
  1001. var jsDump = {
  1002. parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
  1003. var parser = this.parsers[ type || this.typeOf(obj) ];
  1004. type = typeof parser;
  1005. return type == 'function' ? parser.call( this, obj ) :
  1006. type == 'string' ? parser :
  1007. this.parsers.error;
  1008. },
  1009. typeOf:function( obj ) {
  1010. var type;
  1011. if ( obj === null ) {
  1012. type = "null";
  1013. } else if (typeof obj === "undefined") {
  1014. type = "undefined";
  1015. } else if (QUnit.is("RegExp", obj)) {
  1016. type = "regexp";
  1017. } else if (QUnit.is("Date", obj)) {
  1018. type = "date";
  1019. } else if (QUnit.is("Function", obj)) {
  1020. type = "function";
  1021. } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
  1022. type = "window";
  1023. } else if (obj.nodeType === 9) {
  1024. type = "document";
  1025. } else if (obj.nodeType) {
  1026. type = "node";
  1027. } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
  1028. type = "array";
  1029. } else {
  1030. type = typeof obj;
  1031. }
  1032. return type;
  1033. },
  1034. separator:function() {
  1035. return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
  1036. },
  1037. indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
  1038. if ( !this.multiline )
  1039. return '';
  1040. var chr = this.indentChar;
  1041. if ( this.HTML )
  1042. chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
  1043. return Array( this._depth_ + (extra||0) ).join(chr);
  1044. },
  1045. up:function( a ) {
  1046. this._depth_ += a || 1;
  1047. },
  1048. down:function( a ) {
  1049. this._depth_ -= a || 1;
  1050. },
  1051. setParser:function( name, parser ) {
  1052. this.parsers[name] = parser;
  1053. },
  1054. // The next 3 are exposed so you can use them
  1055. quote:quote,
  1056. literal:literal,
  1057. join:join,
  1058. //
  1059. _depth_: 1,
  1060. // This is the list of parsers, to modify them, use jsDump.setParser
  1061. parsers:{
  1062. window: '[Window]',
  1063. document: '[Document]',
  1064. error:'[ERROR]', //when no parser is found, shouldn't happen
  1065. unknown: '[Unknown]',
  1066. 'null':'null',
  1067. 'undefined':'undefined',
  1068. 'function':function( fn ) {
  1069. var ret = 'function',
  1070. name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
  1071. if ( name )
  1072. ret += ' ' + name;
  1073. ret += '(';
  1074. ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
  1075. return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
  1076. },
  1077. array: array,
  1078. nodelist: array,
  1079. arguments: array,
  1080. object:function( map ) {
  1081. var ret = [ ];
  1082. QUnit.jsDump.up();
  1083. for ( var key in map )
  1084. ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
  1085. QUnit.jsDump.down();
  1086. return join( '{', ret, '}' );
  1087. },
  1088. node:function( node ) {
  1089. var open = QUnit.jsDump.HTML ? '&lt;' : '<',
  1090. close = QUnit.jsDump.HTML ? '&gt;' : '>';
  1091. var tag = node.nodeName.toLowerCase(),
  1092. ret = open + tag;
  1093. for ( var a in QUnit.jsDump.DOMAttrs ) {
  1094. var val = node[QUnit.jsDump.DOMAttrs[a]];
  1095. if ( val )
  1096. ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
  1097. }
  1098. return ret + close + open + '/' + tag + close;
  1099. },
  1100. functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
  1101. var l = fn.length;
  1102. if ( !l ) return '';
  1103. var args = Array(l);
  1104. while ( l-- )
  1105. args[l] = String.fromCharCode(97+l);//97 is 'a'
  1106. return ' ' + args.join(', ') + ' ';
  1107. },
  1108. key:quote, //object calls it internally, the key part of an item in a map
  1109. functionCode:'[code]', //function calls it internally, it's the content of the function
  1110. attribute:quote, //node calls it internally, it's an html attribute value
  1111. string:quote,
  1112. date:quote,
  1113. regexp:literal, //regex
  1114. number:literal,
  1115. 'boolean':literal
  1116. },
  1117. DOMAttrs:{//attributes to dump from nodes, name=>realName
  1118. id:'id',
  1119. name:'name',
  1120. 'class':'className'
  1121. },
  1122. HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
  1123. indentChar:' ',//indentation unit
  1124. multiline:true //if true, items in a collection, are separated by a \n, else just a space.
  1125. };
  1126. return jsDump;
  1127. })();
  1128. // from Sizzle.js
  1129. function getText( elems ) {
  1130. var ret = "", elem;
  1131. for ( var i = 0; elems[i]; i++ ) {
  1132. elem = elems[i];
  1133. // Get the text from text nodes and CDATA nodes
  1134. if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
  1135. ret += elem.nodeValue;
  1136. // Traverse everything else, except comment nodes
  1137. } else if ( elem.nodeType !== 8 ) {
  1138. ret += getText( elem.childNodes );
  1139. }
  1140. }
  1141. return ret;
  1142. };
  1143. /*
  1144. * Javascript Diff Algorithm
  1145. * By John Resig (http://ejohn.org/)
  1146. * Modified by Chu Alan "sprite"
  1147. *
  1148. * Released under the MIT license.
  1149. *
  1150. * More Info:
  1151. * http://ejohn.org/projects/javascript-diff-algorithm/
  1152. *
  1153. * Usage: QUnit.diff(expected, actual)
  1154. *
  1155. * 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"
  1156. */
  1157. QUnit.diff = (function() {
  1158. function diff(o, n){
  1159. var ns = new Object();
  1160. var os = new Object();
  1161. for (var i = 0; i < n.length; i++) {
  1162. if (ns[n[i]] == null)
  1163. ns[n[i]] = {
  1164. rows: new Array(),
  1165. o: null
  1166. };
  1167. ns[n[i]].rows.push(i);
  1168. }
  1169. for (var i = 0; i < o.length; i++) {
  1170. if (os[o[i]] == null)
  1171. os[o[i]] = {
  1172. rows: new Array(),
  1173. n: null
  1174. };
  1175. os[o[i]].rows.push(i);
  1176. }
  1177. for (var i in ns) {
  1178. if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
  1179. n[ns[i].rows[0]] = {
  1180. text: n[ns[i].rows[0]],
  1181. row: os[i].rows[0]
  1182. };
  1183. o[os[i].rows[0]] = {
  1184. text: o[os[i].rows[0]],
  1185. row: ns[i].rows[0]
  1186. };
  1187. }
  1188. }
  1189. for (var i = 0; i < n.length - 1; i++) {
  1190. if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
  1191. n[i + 1] == o[n[i].row + 1]) {
  1192. n[i + 1] = {
  1193. text: n[i + 1],
  1194. row: n[i].row + 1
  1195. };
  1196. o[n[i].row + 1] = {
  1197. text: o[n[i].row + 1],
  1198. row: i + 1
  1199. };
  1200. }
  1201. }
  1202. for (var i = n.length - 1; i > 0; i--) {
  1203. if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
  1204. n[i - 1] == o[n[i].row - 1]) {
  1205. n[i - 1] = {
  1206. text: n[i - 1],
  1207. row: n[i].row - 1
  1208. };
  1209. o[n[i].row - 1] = {
  1210. text: o[n[i].row - 1],
  1211. row: i - 1
  1212. };
  1213. }
  1214. }
  1215. return {
  1216. o: o,
  1217. n: n
  1218. };
  1219. }
  1220. return function(o, n){
  1221. o = o.replace(/\s+$/, '');
  1222. n = n.replace(/\s+$/, '');
  1223. var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
  1224. var str = "";
  1225. var oSpace = o.match(/\s+/g);
  1226. if (oSpace == null) {
  1227. oSpace = [" "];
  1228. }
  1229. else {
  1230. oSpace.push(" ");
  1231. }
  1232. var nSpace = n.match(/\s+/g);
  1233. if (nSpace == null) {
  1234. nSpace = [" "];
  1235. }
  1236. else {
  1237. nSpace.push(" ");
  1238. }
  1239. if (out.n.length == 0) {
  1240. for (var i = 0; i < out.o.length; i++) {
  1241. str += '<del>' + out.o[i] + oSpace[i] + "</del>";
  1242. }
  1243. }
  1244. else {
  1245. if (out.n[0].text == null) {
  1246. for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
  1247. str += '<del>' + out.o[n] + oSpace[n] + "</del>";
  1248. }
  1249. }
  1250. for (var i = 0; i < out.n.length; i++) {
  1251. if (out.n[i].text == null) {
  1252. str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
  1253. }
  1254. else {
  1255. var pre = "";
  1256. for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
  1257. pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
  1258. }
  1259. str += " " + out.n[i].text + nSpace[i] + pre;
  1260. }
  1261. }
  1262. }
  1263. return str;
  1264. };
  1265. })();
  1266. })(this);