test.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. var hooks = require('./hooks')
  2. , should = require('should')
  3. , assert = require('assert')
  4. , _ = require('underscore');
  5. // TODO Add in test for making sure all pres get called if pre is defined directly on an instance.
  6. // TODO Test for calling `done` twice or `next` twice in the same function counts only once
  7. module.exports = {
  8. 'should be able to assign multiple hooks at once': function () {
  9. var A = function () {};
  10. _.extend(A, hooks);
  11. A.$hook({
  12. hook1: function (a) {},
  13. hook2: function (b) {}
  14. });
  15. var a = new A();
  16. assert.equal(typeof a.hook1, 'function');
  17. assert.equal(typeof a.hook2, 'function');
  18. },
  19. 'should run without pres and posts when not present': function () {
  20. var A = function () {};
  21. _.extend(A, hooks);
  22. A.$hook('save', function () {
  23. this.value = 1;
  24. });
  25. var a = new A();
  26. a.save();
  27. a.value.should.equal(1);
  28. },
  29. 'should run with pres when present': function () {
  30. var A = function () {};
  31. _.extend(A, hooks);
  32. A.$hook('save', function () {
  33. this.value = 1;
  34. });
  35. A.pre('save', function (next) {
  36. this.preValue = 2;
  37. next();
  38. });
  39. var a = new A();
  40. a.save();
  41. a.value.should.equal(1);
  42. a.preValue.should.equal(2);
  43. },
  44. 'should run with posts when present': function () {
  45. var A = function () {};
  46. _.extend(A, hooks);
  47. A.$hook('save', function () {
  48. this.value = 1;
  49. });
  50. A.post('save', function (next) {
  51. this.value = 2;
  52. next();
  53. });
  54. var a = new A();
  55. a.save();
  56. a.value.should.equal(2);
  57. },
  58. 'should run pres and posts when present': function () {
  59. var A = function () {};
  60. _.extend(A, hooks);
  61. A.$hook('save', function () {
  62. this.value = 1;
  63. });
  64. A.pre('save', function (next) {
  65. this.preValue = 2;
  66. next();
  67. });
  68. A.post('save', function (next) {
  69. this.value = 3;
  70. next();
  71. });
  72. var a = new A();
  73. a.save();
  74. a.value.should.equal(3);
  75. a.preValue.should.equal(2);
  76. },
  77. 'should run posts after pres': function () {
  78. var A = function () {};
  79. _.extend(A, hooks);
  80. A.$hook('save', function () {
  81. this.value = 1;
  82. });
  83. A.pre('save', function (next) {
  84. this.override = 100;
  85. next();
  86. });
  87. A.post('save', function (next) {
  88. this.override = 200;
  89. next();
  90. });
  91. var a = new A();
  92. a.save();
  93. a.value.should.equal(1);
  94. a.override.should.equal(200);
  95. },
  96. 'should not run a hook if a pre fails': function () {
  97. var A = function () {};
  98. _.extend(A, hooks);
  99. var counter = 0;
  100. A.$hook('save', function () {
  101. this.value = 1;
  102. }, function (err) {
  103. counter++;
  104. });
  105. A.pre('save', true, function (next, done) {
  106. next(new Error());
  107. });
  108. var a = new A();
  109. a.save();
  110. counter.should.equal(1);
  111. assert.equal(typeof a.value, 'undefined');
  112. },
  113. 'should be able to run multiple pres': function () {
  114. var A = function () {};
  115. _.extend(A, hooks);
  116. A.$hook('save', function () {
  117. this.value = 1;
  118. });
  119. A.pre('save', function (next) {
  120. this.v1 = 1;
  121. next();
  122. }).pre('save', function (next) {
  123. this.v2 = 2;
  124. next();
  125. });
  126. var a = new A();
  127. a.save();
  128. a.v1.should.equal(1);
  129. a.v2.should.equal(2);
  130. },
  131. 'should run multiple pres until a pre fails and not call the hook': function () {
  132. var A = function () {};
  133. _.extend(A, hooks);
  134. A.$hook('save', function () {
  135. this.value = 1;
  136. }, function (err) {});
  137. A.pre('save', function (next) {
  138. this.v1 = 1;
  139. next();
  140. }).pre('save', function (next) {
  141. next(new Error());
  142. }).pre('save', function (next) {
  143. this.v3 = 3;
  144. next();
  145. });
  146. var a = new A();
  147. a.save();
  148. a.v1.should.equal(1);
  149. assert.equal(typeof a.v3, 'undefined');
  150. assert.equal(typeof a.value, 'undefined');
  151. },
  152. 'should be able to run multiple posts': function () {
  153. var A = function () {};
  154. _.extend(A, hooks);
  155. A.$hook('save', function () {
  156. this.value = 1;
  157. });
  158. A.post('save', function (next) {
  159. this.value = 2;
  160. next();
  161. }).post('save', function (next) {
  162. this.value = 3.14;
  163. next();
  164. }).post('save', function (next) {
  165. this.v3 = 3;
  166. next();
  167. });
  168. var a = new A();
  169. a.save();
  170. assert.equal(a.value, 3.14);
  171. assert.equal(a.v3, 3);
  172. },
  173. 'should run only posts up until an error': function () {
  174. var A = function () {};
  175. _.extend(A, hooks);
  176. A.$hook('save', function () {
  177. this.value = 1;
  178. }, function (err) {});
  179. A.post('save', function (next) {
  180. this.value = 2;
  181. next();
  182. }).post('save', function (next) {
  183. this.value = 3;
  184. next(new Error());
  185. }).post('save', function (next) {
  186. this.value = 4;
  187. next();
  188. });
  189. var a = new A();
  190. a.save();
  191. a.value.should.equal(3);
  192. },
  193. "should fall back first to the hook method's last argument as the error handler if it is a function of arity 1 or 2": function () {
  194. var A = function () {};
  195. _.extend(A, hooks);
  196. var counter = 0;
  197. A.$hook('save', function (callback) {
  198. this.value = 1;
  199. });
  200. A.pre('save', true, function (next, done) {
  201. next(new Error());
  202. });
  203. var a = new A();
  204. a.save( function (err) {
  205. if (err instanceof Error) counter++;
  206. });
  207. counter.should.equal(1);
  208. should.deepEqual(undefined, a.value);
  209. },
  210. 'should fall back second to the default error handler if specified': function () {
  211. var A = function () {};
  212. _.extend(A, hooks);
  213. var counter = 0;
  214. A.$hook('save', function (callback) {
  215. this.value = 1;
  216. }, function (err) {
  217. if (err instanceof Error) counter++;
  218. });
  219. A.pre('save', true, function (next, done) {
  220. next(new Error());
  221. });
  222. var a = new A();
  223. a.save();
  224. counter.should.equal(1);
  225. should.deepEqual(undefined, a.value);
  226. },
  227. 'fallback default error handler should scope to the object': function () {
  228. var A = function () {
  229. this.counter = 0;
  230. };
  231. _.extend(A, hooks);
  232. var counter = 0;
  233. A.$hook('save', function (callback) {
  234. this.value = 1;
  235. }, function (err) {
  236. if (err instanceof Error) this.counter++;
  237. });
  238. A.pre('save', true, function (next, done) {
  239. next(new Error());
  240. });
  241. var a = new A();
  242. a.save();
  243. a.counter.should.equal(1);
  244. should.deepEqual(undefined, a.value);
  245. },
  246. 'should fall back last to throwing the error': function () {
  247. var A = function () {};
  248. _.extend(A, hooks);
  249. var counter = 0;
  250. A.$hook('save', function (err) {
  251. if (err instanceof Error) return counter++;
  252. this.value = 1;
  253. });
  254. A.pre('save', true, function (next, done) {
  255. next(new Error());
  256. });
  257. var a = new A();
  258. var didCatch = false;
  259. try {
  260. a.save();
  261. } catch (e) {
  262. didCatch = true;
  263. e.should.be.an.instanceof(Error);
  264. counter.should.equal(0);
  265. assert.equal(typeof a.value, 'undefined');
  266. }
  267. didCatch.should.be.true;
  268. },
  269. "should proceed without mutating arguments if `next(null|undefined)` is called in a serial pre, and the last argument of the target method is a callback with node-like signature function (err, obj) {...} or function (err) {...}": function () {
  270. var A = function () {};
  271. _.extend(A, hooks);
  272. var counter = 0;
  273. A.prototype.save = function (callback) {
  274. this.value = 1;
  275. callback();
  276. };
  277. A.pre('save', function (next) {
  278. next(null);
  279. });
  280. A.pre('save', function (next) {
  281. next(undefined);
  282. });
  283. var a = new A();
  284. a.save( function (err) {
  285. if (err instanceof Error) counter++;
  286. else counter--;
  287. });
  288. counter.should.equal(-1);
  289. a.value.should.eql(1);
  290. },
  291. "should proceed with mutating arguments if `next(null|undefined)` is callback in a serial pre, and the last argument of the target method is not a function": function () {
  292. var A = function () {};
  293. _.extend(A, hooks);
  294. A.prototype.set = function (v) {
  295. this.value = v;
  296. };
  297. A.pre('set', function (next) {
  298. next(undefined);
  299. });
  300. A.pre('set', function (next) {
  301. next(null);
  302. });
  303. var a = new A();
  304. a.set(1);
  305. should.strictEqual(null, a.value);
  306. },
  307. 'should not run any posts if a pre fails': function () {
  308. var A = function () {};
  309. _.extend(A, hooks);
  310. A.$hook('save', function () {
  311. this.value = 2;
  312. }, function (err) {});
  313. A.pre('save', function (next) {
  314. this.value = 1;
  315. next(new Error());
  316. }).post('save', function (next) {
  317. this.value = 3;
  318. next();
  319. });
  320. var a = new A();
  321. a.save();
  322. a.value.should.equal(1);
  323. },
  324. "can pass the hook's arguments verbatim to pres": function () {
  325. var A = function () {};
  326. _.extend(A, hooks);
  327. A.$hook('set', function (path, val) {
  328. this[path] = val;
  329. });
  330. A.pre('set', function (next, path, val) {
  331. path.should.equal('hello');
  332. val.should.equal('world');
  333. next();
  334. });
  335. var a = new A();
  336. a.set('hello', 'world');
  337. a.hello.should.equal('world');
  338. },
  339. // "can pass the hook's arguments as an array to pres": function () {
  340. // // Great for dynamic arity - e.g., slice(...)
  341. // var A = function () {};
  342. // _.extend(A, hooks);
  343. // A.hook('set', function (path, val) {
  344. // this[path] = val;
  345. // });
  346. // A.pre('set', function (next, hello, world) {
  347. // hello.should.equal('hello');
  348. // world.should.equal('world');
  349. // next();
  350. // });
  351. // var a = new A();
  352. // a.set('hello', 'world');
  353. // assert.equal(a.hello, 'world');
  354. // },
  355. "can pass the hook's arguments verbatim to posts": function () {
  356. var A = function () {};
  357. _.extend(A, hooks);
  358. A.$hook('set', function (path, val) {
  359. this[path] = val;
  360. });
  361. A.post('set', function (next, path, val) {
  362. path.should.equal('hello');
  363. val.should.equal('world');
  364. next();
  365. });
  366. var a = new A();
  367. a.set('hello', 'world');
  368. assert.equal(a.hello, 'world');
  369. },
  370. // "can pass the hook's arguments as an array to posts": function () {
  371. // var A = function () {};
  372. // _.extend(A, hooks);
  373. // A.hook('set', function (path, val) {
  374. // this[path] = val;
  375. // });
  376. // A.post('set', function (next, halt, args) {
  377. // assert.equal(args[0], 'hello');
  378. // assert.equal(args[1], 'world');
  379. // next();
  380. // });
  381. // var a = new A();
  382. // a.set('hello', 'world');
  383. // assert.equal(a.hello, 'world');
  384. // },
  385. "pres should be able to modify and pass on a modified version of the hook's arguments": function () {
  386. var A = function () {};
  387. _.extend(A, hooks);
  388. A.$hook('set', function (path, val) {
  389. this[path] = val;
  390. assert.equal(arguments[2], 'optional');
  391. });
  392. A.pre('set', function (next, path, val) {
  393. next('foo', 'bar');
  394. });
  395. A.pre('set', function (next, path, val) {
  396. assert.equal(path, 'foo');
  397. assert.equal(val, 'bar');
  398. next('rock', 'says', 'optional');
  399. });
  400. A.pre('set', function (next, path, val, opt) {
  401. assert.equal(path, 'rock');
  402. assert.equal(val, 'says');
  403. assert.equal(opt, 'optional');
  404. next();
  405. });
  406. var a = new A();
  407. a.set('hello', 'world');
  408. assert.equal(typeof a.hello, 'undefined');
  409. a.rock.should.equal('says');
  410. },
  411. 'posts should see the modified version of arguments if the pres modified them': function () {
  412. var A = function () {};
  413. _.extend(A, hooks);
  414. A.$hook('set', function (path, val) {
  415. this[path] = val;
  416. });
  417. A.pre('set', function (next, path, val) {
  418. next('foo', 'bar');
  419. });
  420. A.post('set', function (next, path, val) {
  421. path.should.equal('foo');
  422. val.should.equal('bar');
  423. });
  424. var a = new A();
  425. a.set('hello', 'world');
  426. assert.equal(typeof a.hello, 'undefined');
  427. a.foo.should.equal('bar');
  428. },
  429. 'should pad missing arguments (relative to expected arguments of the hook) with null': function () {
  430. // Otherwise, with hookFn = function (a, b, next, ),
  431. // if we use hookFn(a), then because the pre functions are of the form
  432. // preFn = function (a, b, next, ), then it actually gets executed with
  433. // preFn(a, next, ), so when we call next() from within preFn, we are actually
  434. // calling ()
  435. var A = function () {};
  436. _.extend(A, hooks);
  437. A.$hook('set', function (path, val, opts) {
  438. this[path] = val;
  439. });
  440. A.pre('set', function (next, path, val, opts) {
  441. next('foo', 'bar');
  442. assert.equal(typeof opts, 'undefined');
  443. });
  444. var a = new A();
  445. a.set('hello', 'world');
  446. },
  447. 'should not invoke the target method until all asynchronous middleware have invoked dones': function () {
  448. var counter = 0;
  449. var A = function () {};
  450. _.extend(A, hooks);
  451. A.$hook('set', function (path, val) {
  452. counter++;
  453. this[path] = val;
  454. counter.should.equal(7);
  455. });
  456. A.pre('set', function (next, path, val) {
  457. counter++;
  458. next();
  459. });
  460. A.pre('set', true, function (next, done, path, val) {
  461. counter++;
  462. setTimeout(function () {
  463. counter++;
  464. done();
  465. }, 1000);
  466. next();
  467. });
  468. A.pre('set', function (next, path, val) {
  469. counter++;
  470. next();
  471. });
  472. A.pre('set', true, function (next, done, path, val) {
  473. counter++;
  474. setTimeout(function () {
  475. counter++;
  476. done();
  477. }, 500);
  478. next();
  479. });
  480. var a = new A();
  481. a.set('hello', 'world');
  482. },
  483. 'invoking a method twice should run its async middleware twice': function () {
  484. var counter = 0;
  485. var A = function () {};
  486. _.extend(A, hooks);
  487. A.$hook('set', function (path, val) {
  488. this[path] = val;
  489. if (path === 'hello') counter.should.equal(1);
  490. if (path === 'foo') counter.should.equal(2);
  491. });
  492. A.pre('set', true, function (next, done, path, val) {
  493. setTimeout(function () {
  494. counter++;
  495. done();
  496. }, 1000);
  497. next();
  498. });
  499. var a = new A();
  500. a.set('hello', 'world');
  501. a.set('foo', 'bar');
  502. },
  503. 'calling the same done multiple times should have the effect of only calling it once': function () {
  504. var A = function () {
  505. this.acked = false;
  506. };
  507. _.extend(A, hooks);
  508. A.$hook('ack', function () {
  509. console.log("UH OH, YOU SHOULD NOT BE SEEING THIS");
  510. this.acked = true;
  511. });
  512. A.pre('ack', true, function (next, done) {
  513. next();
  514. done();
  515. done();
  516. });
  517. A.pre('ack', true, function (next, done) {
  518. next();
  519. // Notice that done() is not invoked here
  520. });
  521. var a = new A();
  522. a.ack();
  523. setTimeout( function () {
  524. a.acked.should.be.false;
  525. }, 1000);
  526. },
  527. 'calling the same next multiple times should have the effect of only calling it once': function (beforeExit) {
  528. var A = function () {
  529. this.acked = false;
  530. };
  531. _.extend(A, hooks);
  532. A.$hook('ack', function () {
  533. console.log("UH OH, YOU SHOULD NOT BE SEEING THIS");
  534. this.acked = true;
  535. });
  536. A.pre('ack', function (next) {
  537. // force a throw to re-exec next()
  538. try {
  539. next(new Error('bam'));
  540. } catch (err) {
  541. next();
  542. }
  543. });
  544. A.pre('ack', function (next) {
  545. next();
  546. });
  547. var a = new A();
  548. a.ack();
  549. beforeExit( function () {
  550. a.acked.should.be.false;
  551. });
  552. },
  553. 'asynchronous middleware should be able to pass an error via `done`, stopping the middleware chain': function () {
  554. var counter = 0;
  555. var A = function () {};
  556. _.extend(A, hooks);
  557. A.$hook('set', function (path, val, fn) {
  558. counter++;
  559. this[path] = val;
  560. fn(null);
  561. });
  562. A.pre('set', true, function (next, done, path, val, fn) {
  563. setTimeout(function () {
  564. counter++;
  565. done(new Error);
  566. }, 1000);
  567. next();
  568. });
  569. var a = new A();
  570. a.set('hello', 'world', function (err) {
  571. err.should.be.an.instanceof(Error);
  572. should.strictEqual(undefined, a['hello']);
  573. counter.should.eql(1);
  574. });
  575. },
  576. 'should be able to remove a particular pre': function () {
  577. var A = function () {}
  578. , preTwo;
  579. _.extend(A, hooks);
  580. A.$hook('save', function () {
  581. this.value = 1;
  582. });
  583. A.pre('save', function (next) {
  584. this.preValueOne = 2;
  585. next();
  586. });
  587. A.pre('save', preTwo = function (next) {
  588. this.preValueTwo = 4;
  589. next();
  590. });
  591. A.removePre('save', preTwo);
  592. var a = new A();
  593. a.save();
  594. a.value.should.equal(1);
  595. a.preValueOne.should.equal(2);
  596. should.strictEqual(undefined, a.preValueTwo);
  597. },
  598. 'should be able to remove all pres associated with a hook': function () {
  599. var A = function () {};
  600. _.extend(A, hooks);
  601. A.$hook('save', function () {
  602. this.value = 1;
  603. });
  604. A.pre('save', function (next) {
  605. this.preValueOne = 2;
  606. next();
  607. });
  608. A.pre('save', function (next) {
  609. this.preValueTwo = 4;
  610. next();
  611. });
  612. A.removePre('save');
  613. var a = new A();
  614. a.save();
  615. a.value.should.equal(1);
  616. should.strictEqual(undefined, a.preValueOne);
  617. should.strictEqual(undefined, a.preValueTwo);
  618. },
  619. 'should be able to remove a particular post': function () {
  620. var A = function () {}
  621. , postTwo;
  622. _.extend(A, hooks);
  623. A.$hook('save', function () {
  624. this.value = 1;
  625. });
  626. A.post('save', function (next) {
  627. this.postValueOne = 2;
  628. next();
  629. });
  630. A.post('save', postTwo = function (next) {
  631. this.postValueTwo = 4;
  632. next();
  633. });
  634. A.removePost('save', postTwo);
  635. var a = new A();
  636. a.save();
  637. a.value.should.equal(1);
  638. a.postValueOne.should.equal(2);
  639. should.strictEqual(undefined, a.postValueTwo);
  640. },
  641. 'should be able to remove all posts associated with a hook': function () {
  642. var A = function () {};
  643. _.extend(A, hooks);
  644. A.$hook('save', function () {
  645. this.value = 1;
  646. });
  647. A.post('save', function (next) {
  648. this.postValueOne = 2;
  649. next();
  650. });
  651. A.post('save', function (next) {
  652. this.postValueTwo = 4;
  653. next();
  654. });
  655. A.removePost('save');
  656. var a = new A();
  657. a.save();
  658. a.value.should.equal(1);
  659. should.strictEqual(undefined, a.postValueOne);
  660. should.strictEqual(undefined, a.postValueTwo);
  661. },
  662. '#pre should lazily make a method hookable': function () {
  663. var A = function () {};
  664. _.extend(A, hooks);
  665. A.prototype.save = function () {
  666. this.value = 1;
  667. };
  668. A.pre('save', function (next) {
  669. this.preValue = 2;
  670. next();
  671. });
  672. var a = new A();
  673. a.save();
  674. a.value.should.equal(1);
  675. a.preValue.should.equal(2);
  676. },
  677. '#pre lazily making a method hookable should be able to provide a default errorHandler as the last argument': function () {
  678. var A = function () {};
  679. var preValue = "";
  680. _.extend(A, hooks);
  681. A.prototype.save = function () {
  682. this.value = 1;
  683. };
  684. A.pre('save', function (next) {
  685. next(new Error);
  686. }, function (err) {
  687. preValue = 'ERROR';
  688. });
  689. var a = new A();
  690. a.save();
  691. should.strictEqual(undefined, a.value);
  692. preValue.should.equal('ERROR');
  693. },
  694. '#post should lazily make a method hookable': function () {
  695. var A = function () {};
  696. _.extend(A, hooks);
  697. A.prototype.save = function () {
  698. this.value = 1;
  699. };
  700. A.post('save', function (next) {
  701. this.value = 2;
  702. next();
  703. });
  704. var a = new A();
  705. a.save();
  706. a.value.should.equal(2);
  707. },
  708. "a lazy hooks setup should handle errors via a method's last argument, if it's a callback": function () {
  709. var A = function () {};
  710. _.extend(A, hooks);
  711. A.prototype.save = function (fn) {};
  712. A.pre('save', function (next) {
  713. next(new Error("hi there"));
  714. });
  715. var a = new A();
  716. a.save( function (err) {
  717. err.should.be.an.instanceof(Error);
  718. });
  719. },
  720. 'should intercept method callbacks for post handlers': function () {
  721. var A = function () {};
  722. _.extend(A, hooks);
  723. A.$hook('save', function (val, callback) {
  724. this.value = val;
  725. callback();
  726. });
  727. A.post('save', function (next) {
  728. assert.equal(a.value, 2);
  729. this.value += 2;
  730. setTimeout(next, 10);
  731. }).post('save', function (next) {
  732. assert.equal(a.value, 4);
  733. this.value += 3;
  734. setTimeout(next, 10);
  735. }).post('save', function (next) {
  736. assert.equal(a.value, 7);
  737. this.value2 = 3;
  738. setTimeout(next, 10);
  739. });
  740. var a = new A();
  741. a.save(2, function(){
  742. assert.equal(a.value, 7);
  743. assert.equal(a.value2, 3);
  744. });
  745. },
  746. 'should handle parallel followed by serial': function () {
  747. var A = function () {};
  748. _.extend(A, hooks);
  749. A.$hook('save', function (val, callback) {
  750. this.value = val;
  751. callback();
  752. });
  753. A.pre('save', true, function(next, done) {
  754. process.nextTick(function() {
  755. done();
  756. });
  757. next();
  758. }).pre('save', function(done) {
  759. process.nextTick(function() {
  760. done();
  761. });
  762. });
  763. var a = new A();
  764. a.save(2, function(){
  765. assert.ok(true);
  766. });
  767. }
  768. };