hooker_test.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /*global require:true */
  2. var hooker = require('../lib/hooker');
  3. exports['hook'] = {
  4. setUp: function(done) {
  5. this.order = [];
  6. this.track = function() {
  7. [].push.apply(this.order, arguments);
  8. };
  9. this.prop = 1;
  10. this.add = function(a, b) {
  11. this.track("add", this.prop, a, b);
  12. return this.prop + a + b;
  13. };
  14. this.obj = {
  15. that: this,
  16. prop: 1,
  17. add1: function(a, b) {
  18. this.that.track("add1", this.prop, a, b);
  19. return this.prop + a + b;
  20. },
  21. add2: function(a, b) {
  22. this.that.track("add2", this.prop, a, b);
  23. return this.prop + a + b;
  24. },
  25. add3: function(a, b) {
  26. this.that.track("add3", this.prop, a, b);
  27. return this.prop + a + b;
  28. }
  29. };
  30. done();
  31. },
  32. 'orig': function(test) {
  33. test.expect(1);
  34. var orig = this.add;
  35. hooker.hook(this, "add", function() {});
  36. test.strictEqual(hooker.orig(this, "add"), orig, "should return a refernce to the original function.");
  37. test.done();
  38. },
  39. 'once': function(test) {
  40. test.expect(5);
  41. var orig = this.add;
  42. hooker.hook(this, "add", {
  43. once: true,
  44. pre: function(a, b) {
  45. // Arguments are passed into pre-hook as specified.
  46. this.track("before", this.prop, a, b);
  47. }
  48. });
  49. test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
  50. test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
  51. test.strictEqual(this.add, orig, "should automatically unhook when once is specified.");
  52. this.order = [];
  53. test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
  54. test.deepEqual(this.order, ["add", 1, 2, 3], "only the original function should execute.");
  55. test.done();
  56. },
  57. 'pre-hook (simple syntax)': function(test) {
  58. test.expect(3);
  59. // Pre-hook.
  60. var result = hooker.hook(this, "add", function(a, b) {
  61. // Arguments are passed into pre-hook as specified.
  62. this.track("before", this.prop, a, b);
  63. });
  64. test.deepEqual(result, ["add"], "add should have been hooked.");
  65. test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
  66. test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
  67. test.done();
  68. },
  69. 'pre-hook': function(test) {
  70. test.expect(3);
  71. // Pre-hook.
  72. var result = hooker.hook(this, "add", {
  73. pre: function(a, b) {
  74. // Arguments are passed into pre-hook as specified.
  75. this.track("before", this.prop, a, b);
  76. }
  77. });
  78. test.deepEqual(result, ["add"], "add should have been hooked.");
  79. test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
  80. test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
  81. test.done();
  82. },
  83. 'post-hook': function(test) {
  84. test.expect(3);
  85. // Post-hook.
  86. var result = hooker.hook(this, "add", {
  87. post: function(result, a, b) {
  88. // Arguments to post-hook are the original function's return value,
  89. // followed by the specified function arguments.
  90. this.track("after", this.prop, a, b, result);
  91. }
  92. });
  93. test.deepEqual(result, ["add"], "add should have been hooked.");
  94. test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
  95. test.deepEqual(this.order, ["add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  96. test.done();
  97. },
  98. 'pre- & post-hook': function(test) {
  99. test.expect(2);
  100. // Pre- & post-hook.
  101. hooker.hook(this, "add", {
  102. pre: function(a, b) {
  103. // Arguments are passed into pre-hook as specified.
  104. this.track("before", this.prop, a, b);
  105. },
  106. post: function(result, a, b) {
  107. // Arguments to post-hook are the original function's return value,
  108. // followed by the specified function arguments.
  109. this.track("after", this.prop, a, b, result);
  110. }
  111. });
  112. test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
  113. test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  114. test.done();
  115. },
  116. 'pre-hook, return value override': function(test) {
  117. test.expect(2);
  118. // Pre-hook.
  119. hooker.hook(this, "add", {
  120. pre: function(a, b) {
  121. // Arguments are passed into pre-hook as specified.
  122. this.track("before", this.prop, a, b);
  123. // This return value will override the original function's return value.
  124. return hooker.override("b" + this.prop + a + b);
  125. }
  126. });
  127. test.strictEqual(this.add(2, 3), "b123", "should return the overridden result.");
  128. test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
  129. test.done();
  130. },
  131. 'post-hook, return value override': function(test) {
  132. test.expect(2);
  133. // Post-hook.
  134. hooker.hook(this, "add", {
  135. post: function(result, a, b) {
  136. // Arguments to post-hook are the original function's return value,
  137. // followed by the specified function arguments.
  138. this.track("after", this.prop, a, b, result);
  139. // This return value will override the original function's return value.
  140. return hooker.override("a" + this.prop + a + b + result);
  141. }
  142. });
  143. test.strictEqual(this.add(2, 3), "a1236", "should return the post-hook overridden result.");
  144. test.deepEqual(this.order, ["add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  145. test.done();
  146. },
  147. 'pre- & post-hook, return value override': function(test) {
  148. test.expect(2);
  149. // Pre- & post-hook.
  150. hooker.hook(this, "add", {
  151. pre: function(a, b) {
  152. // Arguments are passed into pre-hook as specified.
  153. this.track("before", this.prop, a, b);
  154. // This return value will override the original function's return value.
  155. return hooker.override("b" + this.prop + a + b);
  156. },
  157. post: function(result, a, b) {
  158. // Arguments to post-hook are the original function's return value,
  159. // followed by the specified function arguments.
  160. this.track("after", this.prop, a, b, result);
  161. // This return value will override the original function's return value
  162. // AND the pre-hook's return value.
  163. return hooker.override("a" + this.prop + a + b + result);
  164. }
  165. });
  166. test.strictEqual(this.add(2, 3), "a1236", "should return the overridden result, and post-hook result should take precedence over pre-hook result.");
  167. test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  168. test.done();
  169. },
  170. 'pre-hook, filtering arguments': function(test) {
  171. test.expect(2);
  172. // Pre-hook.
  173. hooker.hook(this, "add", {
  174. pre: function(a, b) {
  175. // Arguments are passed into pre-hook as specified.
  176. this.track("before", this.prop, a, b);
  177. // Return hooker.filter(context, arguments) and they will be passed into
  178. // the original function. The "track" and "order" propterites are just
  179. // set here for the same of this unit test.
  180. return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
  181. }
  182. });
  183. test.strictEqual(this.add(2, 3), "xyz", "should return the original function's result, given filtered context and arguments.");
  184. test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z"], "functions should execute in-order.");
  185. test.done();
  186. },
  187. 'pre- & post-hook, filtering arguments': function(test) {
  188. test.expect(2);
  189. // Pre- & post-hook.
  190. hooker.hook(this, "add", {
  191. pre: function(a, b) {
  192. // Arguments are passed into pre-hook as specified.
  193. this.track("before", this.prop, a, b);
  194. // Return hooker.filter(context, arguments) and they will be passed into
  195. // the original function. The "track" and "order" propterites are just
  196. // set here for the same of this unit test.
  197. return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
  198. },
  199. post: function(result, a, b) {
  200. // Arguments to post-hook are the original function's return value,
  201. // followed by the specified function arguments.
  202. this.track("after", this.prop, a, b, result);
  203. }
  204. });
  205. test.strictEqual(this.add(2, 3), "xyz", "should return the original function's result, given filtered context and arguments.");
  206. test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z", "after", 1, 2, 3, "xyz"], "functions should execute in-order.");
  207. test.done();
  208. },
  209. 'pre- & post-hook, filtering arguments, return value override': function(test) {
  210. test.expect(2);
  211. // Pre- & post-hook.
  212. hooker.hook(this, "add", {
  213. pre: function(a, b) {
  214. // Arguments are passed into pre-hook as specified.
  215. this.track("before", this.prop, a, b);
  216. // Return hooker.filter(context, arguments) and they will be passed into
  217. // the original function. The "track" and "order" propterites are just
  218. // set here for the same of this unit test.
  219. return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
  220. },
  221. post: function(result, a, b) {
  222. // Arguments to post-hook are the original function's return value,
  223. // followed by the specified function arguments.
  224. this.track("after", this.prop, a, b, result);
  225. // This return value will override the original function's return value
  226. // AND the pre-hook's return value.
  227. return hooker.override("a" + this.prop + a + b + result);
  228. }
  229. });
  230. test.strictEqual(this.add(2, 3), "a123xyz", "should return the post-hook overridden result.");
  231. test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z", "after", 1, 2, 3, "xyz"], "functions should execute in-order.");
  232. test.done();
  233. },
  234. 'pre-hook, preempt original function': function(test) {
  235. test.expect(2);
  236. // Pre-hook.
  237. hooker.hook(this, "add", {
  238. pre: function(a, b) {
  239. // Arguments are passed into pre-hook as specified.
  240. this.track("before", this.prop, a, b);
  241. // Returning hooker.preempt will prevent the original function from being
  242. // invoked and optionally set a return value.
  243. return hooker.preempt();
  244. }
  245. });
  246. test.strictEqual(this.add(2, 3), undefined, "should return the value passed to preempt.");
  247. test.deepEqual(this.order, ["before", 1, 2, 3], "functions should execute in-order.");
  248. test.done();
  249. },
  250. 'pre-hook, preempt original function with value': function(test) {
  251. test.expect(2);
  252. // Pre-hook.
  253. hooker.hook(this, "add", {
  254. pre: function(a, b) {
  255. // Arguments are passed into pre-hook as specified.
  256. this.track("before", this.prop, a, b);
  257. // Returning hooker.preempt will prevent the original function from being
  258. // invoked and optionally set a return value.
  259. return hooker.preempt(9000);
  260. }
  261. });
  262. test.strictEqual(this.add(2, 3), 9000, "should return the value passed to preempt.");
  263. test.deepEqual(this.order, ["before", 1, 2, 3], "functions should execute in-order.");
  264. test.done();
  265. },
  266. 'pre- & post-hook, preempt original function with value': function(test) {
  267. test.expect(2);
  268. // Pre- & post-hook.
  269. hooker.hook(this, "add", {
  270. pre: function(a, b) {
  271. // Arguments are passed into pre-hook as specified.
  272. this.track("before", this.prop, a, b);
  273. // Returning hooker.preempt will prevent the original function from being
  274. // invoked and optionally set a return value.
  275. return hooker.preempt(9000);
  276. },
  277. post: function(result, a, b) {
  278. // Arguments to post-hook are the original function's return value,
  279. // followed by the specified function arguments.
  280. this.track("after", this.prop, a, b, result);
  281. }
  282. });
  283. test.strictEqual(this.add(2, 3), 9000, "should return the value passed to preempt.");
  284. test.deepEqual(this.order, ["before", 1, 2, 3, "after", 1, 2, 3, 9000], "functions should execute in-order.");
  285. test.done();
  286. },
  287. 'pre- & post-hook, preempt original function with value, return value override': function(test) {
  288. test.expect(2);
  289. // Pre- & post-hook.
  290. hooker.hook(this, "add", {
  291. pre: function(a, b) {
  292. // Arguments are passed into pre-hook as specified.
  293. this.track("before", this.prop, a, b);
  294. // Returning hooker.preempt will prevent the original function from being
  295. // invoked and optionally set a return value.
  296. return hooker.preempt(9000);
  297. },
  298. post: function(result, a, b) {
  299. // Arguments to post-hook are the original function's return value,
  300. // followed by the specified function arguments.
  301. this.track("after", this.prop, a, b, result);
  302. // This return value will override any preempt value set in pre-hook.
  303. return hooker.override("a" + this.prop + a + b + result);
  304. }
  305. });
  306. test.strictEqual(this.add(2, 3), "a1239000", "should return the overridden result, and post-hook result should take precedence over preempt value.");
  307. test.deepEqual(this.order, ["before", 1, 2, 3, "after", 1, 2, 3, 9000], "functions should execute in-order.");
  308. test.done();
  309. },
  310. 'pre- & post-hook, some properties': function(test) {
  311. test.expect(7);
  312. // Pre- & post-hook.
  313. var result = hooker.hook(this.obj, ["add1", "add2"], {
  314. pre: function(a, b) {
  315. // Arguments are passed into pre-hook as specified.
  316. this.that.track("before", this.prop, a, b);
  317. },
  318. post: function(result, a, b) {
  319. // Arguments to post-hook are the original function's return value,
  320. // followed by the specified function arguments.
  321. this.that.track("after", this.prop, a, b, result);
  322. }
  323. });
  324. test.deepEqual(result.sort(), ["add1", "add2"], "both functions should have been hooked.");
  325. test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
  326. test.deepEqual(this.order, ["before", 1, 2, 3, "add1", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  327. this.order = [];
  328. test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
  329. test.deepEqual(this.order, ["before", 1, 2, 3, "add2", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  330. this.order = [];
  331. test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
  332. test.deepEqual(this.order, ["add3", 1, 2, 3], "functions should execute in-order.");
  333. test.done();
  334. },
  335. 'pre- & post-hook, all properties': function(test) {
  336. test.expect(7);
  337. // Pre- & post-hook.
  338. var result = hooker.hook(this.obj, {
  339. pre: function(a, b) {
  340. // Arguments are passed into pre-hook as specified.
  341. this.that.track("before", this.prop, a, b);
  342. },
  343. post: function(result, a, b) {
  344. // Arguments to post-hook are the original function's return value,
  345. // followed by the specified function arguments.
  346. this.that.track("after", this.prop, a, b, result);
  347. }
  348. });
  349. test.deepEqual(result.sort(), ["add1", "add2", "add3"], "all functions should have been hooked.");
  350. test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
  351. test.deepEqual(this.order, ["before", 1, 2, 3, "add1", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  352. this.order = [];
  353. test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
  354. test.deepEqual(this.order, ["before", 1, 2, 3, "add2", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  355. this.order = [];
  356. test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
  357. test.deepEqual(this.order, ["before", 1, 2, 3, "add3", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
  358. test.done();
  359. },
  360. 'pre- & post-hook, all properties, passName': function(test) {
  361. test.expect(6);
  362. // Pre- & post-hook.
  363. hooker.hook(this.obj, {
  364. passName: true,
  365. pre: function(name, a, b) {
  366. // Arguments are passed into pre-hook as specified.
  367. this.that.track("before", this.prop, name, a, b);
  368. },
  369. post: function(result, name, a, b) {
  370. // Arguments to post-hook are the original function's return value,
  371. // followed by the specified function arguments.
  372. this.that.track("after", this.prop, name, a, b, result);
  373. }
  374. });
  375. test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
  376. test.deepEqual(this.order, ["before", 1, "add1", 2, 3, "add1", 1, 2, 3, "after", 1, "add1", 2, 3, 6], "functions should execute in-order.");
  377. this.order = [];
  378. test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
  379. test.deepEqual(this.order, ["before", 1, "add2", 2, 3, "add2", 1, 2, 3, "after", 1, "add2", 2, 3, 6], "functions should execute in-order.");
  380. this.order = [];
  381. test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
  382. test.deepEqual(this.order, ["before", 1, "add3", 2, 3, "add3", 1, 2, 3, "after", 1, "add3", 2, 3, 6], "functions should execute in-order.");
  383. test.done();
  384. },
  385. 'unhook one property': function(test) {
  386. test.expect(5);
  387. var orig = this.add;
  388. hooker.hook(this, "add", function() {});
  389. var result = hooker.unhook(this, "add");
  390. test.deepEqual(result, ["add"], "one function should have been unhooked.");
  391. test.strictEqual(this.add, orig, "should have unhooked, restoring the original function");
  392. result = hooker.unhook(this, "add");
  393. test.deepEqual(result, [], "nothing should have been unhooked.");
  394. test.strictEqual(this.add, orig, "shouldn't explode if already unhooked");
  395. test.strictEqual(this.add.orig, undefined, "original function shouldn't have an orig property");
  396. test.done();
  397. },
  398. 'unhook some properties': function(test) {
  399. test.expect(6);
  400. var add1 = this.obj.add1;
  401. var add2 = this.obj.add2;
  402. hooker.hook(this.obj, ["add1", "add2"], function() {});
  403. test.strictEqual(hooker.orig(this.obj, "add1"), add1, "should return a refernce to the original function");
  404. test.strictEqual(hooker.orig(this.obj, "add2"), add2, "should return a refernce to the original function");
  405. test.strictEqual(hooker.orig(this.obj, "add3"), undefined, "should not have been hooked, so should not have an original function");
  406. var result = hooker.unhook(this.obj, ["add1", "add2"]);
  407. test.deepEqual(result.sort(), ["add1", "add2"], "both functions should have been unhooked.");
  408. test.strictEqual(this.obj.add1, add1, "should have unhooked, restoring the original function");
  409. test.strictEqual(this.obj.add2, add2, "should have unhooked, restoring the original function");
  410. test.done();
  411. },
  412. 'unhook all properties': function(test) {
  413. test.expect(7);
  414. var add1 = this.obj.add1;
  415. var add2 = this.obj.add2;
  416. var add3 = this.obj.add3;
  417. hooker.hook(this.obj, function() {});
  418. test.strictEqual(hooker.orig(this.obj, "add1"), add1, "should return a refernce to the original function");
  419. test.strictEqual(hooker.orig(this.obj, "add2"), add2, "should return a refernce to the original function");
  420. test.strictEqual(hooker.orig(this.obj, "add3"), add3, "should return a refernce to the original function");
  421. var result = hooker.unhook(this.obj);
  422. test.deepEqual(result.sort(), ["add1", "add2", "add3"], "all functions should have been unhooked.");
  423. test.strictEqual(this.obj.add1, add1, "should have unhooked, restoring the original function");
  424. test.strictEqual(this.obj.add2, add2, "should have unhooked, restoring the original function");
  425. test.strictEqual(this.obj.add3, add3, "should have unhooked, restoring the original function");
  426. test.done();
  427. }
  428. };