test.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. function getPreviousTests( rTestName, rModuleName ) {
  2. var testSpan, moduleSpan,
  3. matches = [],
  4. i = 0,
  5. rModule = /(^| )module-name( |$)/,
  6. testNames = typeof document.getElementsByClassName !== "undefined" ?
  7. document.getElementsByClassName("test-name") :
  8. (function( spans ) {
  9. var span,
  10. tests = [],
  11. i = 0,
  12. rTest = /(^| )test-name( |$)/;
  13. for ( ; (span = spans[i]); i++ ) {
  14. if ( rTest.test( span.className ) ) {
  15. tests.push( span );
  16. }
  17. }
  18. return tests;
  19. })( document.getElementsByTagName("span") );
  20. for ( ; (testSpan = testNames[i]); i++ ) {
  21. moduleSpan = testSpan;
  22. while ( (moduleSpan = moduleSpan.previousSibling) ) {
  23. if ( rModule.test( moduleSpan.className ) ) {
  24. break;
  25. }
  26. }
  27. if ( (!rTestName || rTestName.test( testSpan.innerHTML )) &&
  28. (!rModuleName || moduleSpan && rModuleName.test( moduleSpan.innerHTML )) ) {
  29. while ( (testSpan = testSpan.parentNode) ) {
  30. if ( testSpan.nodeName.toLowerCase() === "li" ) {
  31. matches.push( testSpan );
  32. }
  33. }
  34. }
  35. }
  36. return matches;
  37. }
  38. test("module without setup/teardown (default)", function() {
  39. expect(1);
  40. ok(true);
  41. });
  42. test("expect in test", 3, function() {
  43. ok(true);
  44. ok(true);
  45. ok(true);
  46. });
  47. test("expect in test", 1, function() {
  48. ok(true);
  49. });
  50. test("expect query and multiple issue", function() {
  51. expect(2);
  52. ok(true);
  53. var expected = expect();
  54. equal(expected, 2);
  55. expect(expected + 1);
  56. ok(true);
  57. });
  58. QUnit.module("assertion helpers");
  59. QUnit.test( "QUnit.assert compatibility", 5, function( assert ) {
  60. assert.ok( true, "Calling method on `assert` argument to test() callback" );
  61. // Should also work, although discouraged and not documented
  62. QUnit.assert.ok( true, "Calling method on QUnit.assert object" );
  63. // Test compatibility aliases
  64. QUnit.ok( true, "Calling aliased method in QUnit root object" );
  65. ok( true, "Calling aliased function in global namespace" );
  66. // Regression fix for #341
  67. // The assert-context way of testing discouraged global variables,
  68. // it doesn't make sense of it itself to be a global variable.
  69. // Only allows for mistakes (e.g. forgetting to list 'assert' as parameter)
  70. assert.notStrictEqual( window.assert, QUnit.assert, "Assert does not get exposed as a global variable" );
  71. });
  72. module("setup test", {
  73. setup: function() {
  74. ok(true);
  75. }
  76. });
  77. test("module with setup", function() {
  78. expect(2);
  79. ok(true);
  80. });
  81. test("module with setup, expect in test call", 2, function() {
  82. ok(true);
  83. });
  84. module("<script id='qunit-unescaped-module'>'module';</script>", {
  85. setup: function() {
  86. },
  87. teardown: function() {
  88. // We can't use ok(false) inside script tags since some browsers
  89. // don't evaluate script tags inserted through innerHTML after domready.
  90. // Counting them before/after doesn't cover everything either as qunit-modulefilter
  91. // is created before any test is ran. So use ids instead.
  92. if (document.getElementById('qunit-unescaped-module')) {
  93. // This can either be from in #qunit-modulefilter or #qunit-testresult
  94. ok(false, 'Unscaped module name');
  95. }
  96. if (document.getElementById('qunit-unescaped-test')) {
  97. ok(false, 'Unscaped test name');
  98. }
  99. if (document.getElementById('qunit-unescaped-assertion')) {
  100. ok(false, 'Unscaped test name');
  101. }
  102. }
  103. });
  104. test("<script id='qunit-unescaped-test'>'test';</script>", 1, function() {
  105. ok(true, "<script id='qunit-unescaped-asassertionsert'>'assertion';</script>");
  106. });
  107. var state;
  108. module("setup/teardown test", {
  109. setup: function() {
  110. state = true;
  111. ok(true);
  112. // Assert that we can introduce and delete globals in setup/teardown
  113. // without noglobals sounding any alarm.
  114. // Using an implied global variable instead of explicit window property
  115. // because there is no way to delete a window.property in IE6-8
  116. // `delete x` only works for `x = 1, and `delete window.x` throws exception.
  117. // No one-code fits all solution possible afaic. Resort to @cc.
  118. /*@cc_on
  119. @if (@_jscript_version < 9)
  120. x = 1;
  121. @else @*/
  122. window.x = 1;
  123. /*@end
  124. @*/
  125. },
  126. teardown: function() {
  127. ok(true);
  128. /*@cc_on
  129. @if (@_jscript_version < 9)
  130. delete x;
  131. @else @*/
  132. delete window.x;
  133. /*@end
  134. @*/
  135. }
  136. });
  137. test("module with setup/teardown", function() {
  138. expect(3);
  139. ok(true);
  140. });
  141. module("setup/teardown test 2");
  142. test("module without setup/teardown", function() {
  143. expect(1);
  144. ok(true);
  145. });
  146. var orgDate;
  147. module("Date test", {
  148. setup: function() {
  149. orgDate = Date;
  150. window.Date = function () {
  151. ok( false, 'QUnit should internally be independant from Date-related manipulation and testing' );
  152. return new orgDate();
  153. };
  154. },
  155. teardown: function() {
  156. window.Date = orgDate;
  157. }
  158. });
  159. test("sample test for Date test", function () {
  160. expect(1);
  161. ok(true);
  162. });
  163. if (typeof setTimeout !== 'undefined') {
  164. state = 'fail';
  165. module("teardown and stop", {
  166. teardown: function() {
  167. equal(state, "done", "Test teardown.");
  168. }
  169. });
  170. test("teardown must be called after test ended", function() {
  171. expect(1);
  172. stop();
  173. setTimeout(function() {
  174. state = "done";
  175. start();
  176. }, 13);
  177. });
  178. test("parameter passed to stop increments semaphore n times", function() {
  179. expect(1);
  180. stop(3);
  181. setTimeout(function() {
  182. state = "not enough starts";
  183. start();
  184. start();
  185. }, 13);
  186. setTimeout(function() {
  187. state = "done";
  188. start();
  189. }, 15);
  190. });
  191. test("parameter passed to start decrements semaphore n times", function() {
  192. expect(1);
  193. stop();
  194. stop();
  195. stop();
  196. setTimeout(function() {
  197. state = "done";
  198. start(3);
  199. }, 18);
  200. });
  201. module("async setup test", {
  202. setup: function() {
  203. stop();
  204. setTimeout(function() {
  205. ok(true);
  206. start();
  207. }, 500);
  208. }
  209. });
  210. asyncTest("module with async setup", function() {
  211. expect(2);
  212. ok(true);
  213. start();
  214. });
  215. module("async teardown test", {
  216. teardown: function() {
  217. stop();
  218. setTimeout(function() {
  219. ok(true);
  220. start();
  221. }, 500);
  222. }
  223. });
  224. asyncTest("module with async teardown", function() {
  225. expect(2);
  226. ok(true);
  227. start();
  228. });
  229. module("asyncTest");
  230. asyncTest("asyncTest", function() {
  231. expect(2);
  232. ok(true);
  233. setTimeout(function() {
  234. state = "done";
  235. ok(true);
  236. start();
  237. }, 13);
  238. });
  239. asyncTest("asyncTest", 2, function() {
  240. ok(true);
  241. setTimeout(function() {
  242. state = "done";
  243. ok(true);
  244. start();
  245. }, 13);
  246. });
  247. test("sync", 2, function() {
  248. stop();
  249. setTimeout(function() {
  250. ok(true);
  251. start();
  252. }, 13);
  253. stop();
  254. setTimeout(function() {
  255. ok(true);
  256. start();
  257. }, 125);
  258. });
  259. test("test synchronous calls to stop", 2, function() {
  260. stop();
  261. setTimeout(function() {
  262. ok(true, 'first');
  263. start();
  264. stop();
  265. setTimeout(function() {
  266. ok(true, 'second');
  267. start();
  268. }, 150);
  269. }, 150);
  270. });
  271. }
  272. module("save scope", {
  273. setup: function() {
  274. this.foo = "bar";
  275. },
  276. teardown: function() {
  277. deepEqual(this.foo, "bar");
  278. }
  279. });
  280. test("scope check", function() {
  281. expect(2);
  282. deepEqual(this.foo, "bar");
  283. });
  284. module("simple testEnvironment setup", {
  285. foo: "bar",
  286. // example of meta-data
  287. bugid: "#5311"
  288. });
  289. test("scope check", function() {
  290. deepEqual(this.foo, "bar");
  291. });
  292. test("modify testEnvironment",function() {
  293. expect(0);
  294. this.foo="hamster";
  295. });
  296. test("testEnvironment reset for next test",function() {
  297. deepEqual(this.foo, "bar");
  298. });
  299. module("testEnvironment with object", {
  300. options:{
  301. recipe:"soup",
  302. ingredients:["hamster","onions"]
  303. }
  304. });
  305. test("scope check", function() {
  306. deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions"]}) ;
  307. });
  308. test("modify testEnvironment",function() {
  309. expect(0);
  310. // since we do a shallow copy, the testEnvironment can be modified
  311. this.options.ingredients.push("carrots");
  312. });
  313. test("testEnvironment reset for next test",function() {
  314. deepEqual(this.options, {recipe:"soup",ingredients:["hamster","onions","carrots"]}, "Is this a bug or a feature? Could do a deep copy") ;
  315. });
  316. module("testEnvironment tests");
  317. function makeurl() {
  318. var testEnv = QUnit.current_testEnvironment;
  319. var url = testEnv.url || 'http://example.com/search';
  320. var q = testEnv.q || 'a search test';
  321. return url + '?q='+encodeURIComponent(q);
  322. }
  323. test("makeurl working",function() {
  324. equal( QUnit.current_testEnvironment, this, 'The current testEnvironment is global');
  325. equal( makeurl(), 'http://example.com/search?q=a%20search%20test', 'makeurl returns a default url if nothing specified in the testEnvironment');
  326. });
  327. module("testEnvironment with makeurl settings", {
  328. url: 'http://google.com/',
  329. q: 'another_search_test'
  330. });
  331. test("makeurl working with settings from testEnvironment", function() {
  332. equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to from the url');
  333. });
  334. module("jsDump");
  335. test("jsDump output", function() {
  336. equal( QUnit.jsDump.parse([1, 2]), "[\n 1,\n 2\n]" );
  337. equal( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"left\": 0,\n \"top\": 5\n}" );
  338. if (typeof document !== 'undefined' && document.getElementById("qunit-header")) {
  339. equal( QUnit.jsDump.parse(document.getElementById("qunit-header")), "<h1 id=\"qunit-header\"></h1>" );
  340. equal( QUnit.jsDump.parse(document.getElementsByTagName("h1")), "[\n <h1 id=\"qunit-header\"></h1>\n]" );
  341. }
  342. });
  343. module("assertions");
  344. test("propEqual", 5, function( assert ) {
  345. var objectCreate = Object.create || function ( origin ) {
  346. function O() {}
  347. O.prototype = origin;
  348. var r = new O();
  349. return r;
  350. };
  351. function Foo( x, y, z ) {
  352. this.x = x;
  353. this.y = y;
  354. this.z = z;
  355. }
  356. Foo.prototype.doA = function () {};
  357. Foo.prototype.doB = function () {};
  358. Foo.prototype.bar = 'prototype';
  359. function Bar() {
  360. }
  361. Bar.prototype = objectCreate( Foo.prototype );
  362. Bar.prototype.constructor = Bar;
  363. assert.propEqual(
  364. new Foo( 1, '2', [] ),
  365. {
  366. x: 1,
  367. y: '2',
  368. z: []
  369. }
  370. );
  371. assert.notPropEqual(
  372. new Foo( '1', 2, 3 ),
  373. {
  374. x: 1,
  375. y: '2',
  376. z: 3
  377. },
  378. 'Primitive values are strictly compared'
  379. );
  380. assert.notPropEqual(
  381. new Foo( 1, '2', [] ),
  382. {
  383. x: 1,
  384. y: '2',
  385. z: {}
  386. },
  387. 'Array type is preserved'
  388. );
  389. assert.notPropEqual(
  390. new Foo( 1, '2', {} ),
  391. {
  392. x: 1,
  393. y: '2',
  394. z: []
  395. },
  396. 'Empty array is not the same as empty object'
  397. );
  398. assert.propEqual(
  399. new Foo( 1, '2', new Foo( [ 3 ], new Bar(), null ) ),
  400. {
  401. x: 1,
  402. y: '2',
  403. z: {
  404. x: [ 3 ],
  405. y: {},
  406. z: null
  407. }
  408. },
  409. 'Complex nesting of different types, inheritance and constructors'
  410. );
  411. });
  412. test("raises", 9, function() {
  413. function CustomError( message ) {
  414. this.message = message;
  415. }
  416. CustomError.prototype.toString = function() {
  417. return this.message;
  418. };
  419. throws(
  420. function() {
  421. throw "my error";
  422. }
  423. );
  424. throws(
  425. function() {
  426. throw "my error";
  427. },
  428. "simple string throw, no 'expected' value given"
  429. );
  430. // This test is for IE 7 and prior which does not properly
  431. // implement Error.prototype.toString
  432. throws(
  433. function() {
  434. throw new Error("error message");
  435. },
  436. /error message/,
  437. "use regexp against instance of Error"
  438. );
  439. throws(
  440. function() {
  441. throw new CustomError();
  442. },
  443. CustomError,
  444. 'thrown error is an instance of CustomError'
  445. );
  446. throws(
  447. function() {
  448. throw new CustomError("some error description");
  449. },
  450. /description/,
  451. "use a regex to match against the stringified error"
  452. );
  453. throws(
  454. function() {
  455. throw new CustomError("some error description");
  456. },
  457. function( err ) {
  458. if ( (err instanceof CustomError) && /description/.test(err) ) {
  459. return true;
  460. }
  461. },
  462. "custom validation function"
  463. );
  464. throws(
  465. function() {
  466. /*jshint evil:true */
  467. ( window.execScript || function( data ) {
  468. window["eval"].call( window, data );
  469. })( "throw 'error';" );
  470. },
  471. 'globally-executed errors caught'
  472. );
  473. this.CustomError = CustomError;
  474. throws(
  475. function() {
  476. throw new this.CustomError("some error description");
  477. },
  478. /description/,
  479. "throw error from property of 'this' context"
  480. );
  481. raises(
  482. function() {
  483. throw "error";
  484. },
  485. "simple throw, asserting with deprecated raises() function"
  486. );
  487. });
  488. if (typeof document !== "undefined") {
  489. module("fixture");
  490. test("setup", function() {
  491. expect(0);
  492. document.getElementById("qunit-fixture").innerHTML = "foobar";
  493. });
  494. test("basics", function() {
  495. equal( document.getElementById("qunit-fixture").innerHTML, "test markup", "automatically reset" );
  496. });
  497. test("running test name displayed", function() {
  498. expect(2);
  499. var displaying = document.getElementById("qunit-testresult");
  500. ok( /running test name displayed/.test(displaying.innerHTML), "Expect test name to be found in displayed text" );
  501. ok( /fixture/.test(displaying.innerHTML), "Expect module name to be found in displayed text" );
  502. });
  503. (function() {
  504. var delayNextSetup,
  505. sleep = function( n ) {
  506. stop();
  507. setTimeout( function() { start(); }, n );
  508. };
  509. module("timing", {
  510. setup: function() {
  511. if ( delayNextSetup ) {
  512. delayNextSetup = false;
  513. sleep( 250 );
  514. }
  515. }
  516. });
  517. test("setup", 0, function() {
  518. delayNextSetup = true;
  519. });
  520. test("basics", 2, function() {
  521. var previous = getPreviousTests(/^setup$/, /^timing$/)[0],
  522. runtime = previous.lastChild.previousSibling;
  523. ok( /(^| )runtime( |$)/.test( runtime.className ), "Runtime element exists" );
  524. ok( /^\d+ ms$/.test( runtime.innerHTML ), "Runtime reported in ms" );
  525. });
  526. test("values", 2, function() {
  527. var basics = getPreviousTests(/^setup$/, /^timing$/)[0],
  528. slow = getPreviousTests(/^basics$/, /^timing$/)[0];
  529. ok( parseInt( basics.lastChild.previousSibling.innerHTML, 10 ) < 50, "Fast runtime for trivial test" );
  530. ok( parseInt( slow.lastChild.previousSibling.innerHTML, 10 ) > 250, "Runtime includes setup" );
  531. });
  532. })();
  533. }
  534. module("custom assertions");
  535. (function() {
  536. function mod2(value, expected, message) {
  537. var actual = value % 2;
  538. QUnit.push(actual == expected, actual, expected, message);
  539. }
  540. test("mod2", function() {
  541. mod2(2, 0, "2 % 2 == 0");
  542. mod2(3, 1, "3 % 2 == 1");
  543. });
  544. })();
  545. module("recursions");
  546. function Wrap(x) {
  547. this.wrap = x;
  548. if (x === undefined) {
  549. this.first = true;
  550. }
  551. }
  552. function chainwrap(depth, first, prev) {
  553. depth = depth || 0;
  554. var last = prev || new Wrap();
  555. first = first || last;
  556. if (depth == 1) {
  557. first.wrap = last;
  558. }
  559. if (depth > 1) {
  560. last = chainwrap(depth-1, first, new Wrap(last));
  561. }
  562. return last;
  563. }
  564. test("check jsDump recursion", function() {
  565. expect(4);
  566. var noref = chainwrap(0);
  567. var nodump = QUnit.jsDump.parse(noref);
  568. equal(nodump, '{\n "first": true,\n "wrap": undefined\n}');
  569. var selfref = chainwrap(1);
  570. var selfdump = QUnit.jsDump.parse(selfref);
  571. equal(selfdump, '{\n "first": true,\n "wrap": recursion(-1)\n}');
  572. var parentref = chainwrap(2);
  573. var parentdump = QUnit.jsDump.parse(parentref);
  574. equal(parentdump, '{\n "wrap": {\n "first": true,\n "wrap": recursion(-2)\n }\n}');
  575. var circref = chainwrap(10);
  576. var circdump = QUnit.jsDump.parse(circref);
  577. ok(new RegExp("recursion\\(-10\\)").test(circdump), "(" +circdump + ") should show -10 recursion level");
  578. });
  579. test("check (deep-)equal recursion", function() {
  580. var noRecursion = chainwrap(0);
  581. equal(noRecursion, noRecursion, "I should be equal to me.");
  582. deepEqual(noRecursion, noRecursion, "... and so in depth.");
  583. var selfref = chainwrap(1);
  584. equal(selfref, selfref, "Even so if I nest myself.");
  585. deepEqual(selfref, selfref, "... into the depth.");
  586. var circref = chainwrap(10);
  587. equal(circref, circref, "Or hide that through some levels of indirection.");
  588. deepEqual(circref, circref, "... and checked on all levels!");
  589. });
  590. test('Circular reference with arrays', function() {
  591. // pure array self-ref
  592. var arr = [];
  593. arr.push(arr);
  594. var arrdump = QUnit.jsDump.parse(arr);
  595. equal(arrdump, '[\n recursion(-1)\n]');
  596. equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref');
  597. // mix obj-arr circular ref
  598. var obj = {};
  599. var childarr = [obj];
  600. obj.childarr = childarr;
  601. var objdump = QUnit.jsDump.parse(obj);
  602. var childarrdump = QUnit.jsDump.parse(childarr);
  603. equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}');
  604. equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]');
  605. equal(obj.childarr, childarr, 'no endless stack when trying to dump array/object mix with circular ref');
  606. equal(childarr[0], obj, 'no endless stack when trying to dump array/object mix with circular ref');
  607. });
  608. test('Circular reference - test reported by soniciq in #105', function() {
  609. var MyObject = function() {};
  610. MyObject.prototype.parent = function(obj) {
  611. if (obj === undefined) { return this._parent; }
  612. this._parent = obj;
  613. };
  614. MyObject.prototype.children = function(obj) {
  615. if (obj === undefined) { return this._children; }
  616. this._children = obj;
  617. };
  618. var a = new MyObject(),
  619. b = new MyObject();
  620. var barr = [b];
  621. a.children(barr);
  622. b.parent(a);
  623. equal(a.children(), barr);
  624. deepEqual(a.children(), [b]);
  625. });
  626. (function() {
  627. var reset = QUnit.reset;
  628. module("reset");
  629. test("reset runs assertions", function() {
  630. expect(0);
  631. QUnit.reset = function() {
  632. ok( false, "reset should not modify test status" );
  633. reset.apply( this, arguments );
  634. };
  635. });
  636. test("reset runs assertions, cleanup", function() {
  637. expect(0);
  638. QUnit.reset = reset;
  639. });
  640. })();
  641. function testAfterDone() {
  642. var testName = "ensure has correct number of assertions";
  643. function secondAfterDoneTest() {
  644. QUnit.config.done = [];
  645. // Because when this does happen, the assertion count parameter doesn't actually
  646. // work we use this test to check the assertion count.
  647. module("check previous test's assertion counts");
  648. test('count previous two test\'s assertions', function () {
  649. var tests = getPreviousTests(/^ensure has correct number of assertions/, /^Synchronous test after load of page$/);
  650. equal(tests[0].firstChild.lastChild.getElementsByTagName("b")[1].innerHTML, "99");
  651. equal(tests[1].firstChild.lastChild.getElementsByTagName("b")[1].innerHTML, "99");
  652. });
  653. }
  654. QUnit.config.done = [];
  655. QUnit.done(secondAfterDoneTest);
  656. module("Synchronous test after load of page");
  657. asyncTest('Async test', function() {
  658. start();
  659. for (var i = 1; i < 100; i++) {
  660. ok(i);
  661. }
  662. });
  663. test(testName, 99, function() {
  664. for (var i = 1; i < 100; i++) {
  665. ok(i);
  666. }
  667. });
  668. // We need two of these types of tests in order to ensure that assertions
  669. // don't move between tests.
  670. test(testName + ' 2', 99, function() {
  671. for (var i = 1; i < 100; i++) {
  672. ok(i);
  673. }
  674. });
  675. }
  676. if (typeof setTimeout !== 'undefined') {
  677. QUnit.done(testAfterDone);
  678. }