extlib 0.9.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of extlib might be problematic. Click here for more details.

@@ -0,0 +1,897 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ describe Extlib::Hook do
4
+
5
+ before(:each) do
6
+ @module = Module.new do
7
+ def greet; greetings_from_module; end;
8
+ end
9
+
10
+ @class = Class.new do
11
+ include Extlib::Hook
12
+
13
+ def hookable; end;
14
+ def self.clakable; end;
15
+ def ambiguous; hi_mom!; end;
16
+ def self.ambiguous; hi_dad!; end;
17
+ end
18
+
19
+ @another_class = Class.new do
20
+ include Extlib::Hook
21
+ end
22
+
23
+ @other = Class.new do
24
+ include Extlib::Hook
25
+
26
+ def hookable; end
27
+ def self.clakable; end;
28
+ end
29
+
30
+ @class.register_instance_hooks :hookable
31
+ @class.register_class_hooks :clakable
32
+ end
33
+
34
+ #
35
+ # Specs out how hookable methods are registered
36
+ #
37
+ describe "explicit hookable method registration" do
38
+
39
+ describe "for class methods" do
40
+
41
+ it "shouldn't confuse instance method hooks and class method hooks" do
42
+ @class.register_instance_hooks :ambiguous
43
+ @class.register_class_hooks :ambiguous
44
+
45
+ @class.should_receive(:hi_dad!)
46
+ @class.ambiguous
47
+ end
48
+
49
+ it "should be able to register multiple hookable methods at once" do
50
+ %w(method_one method_two method_three).each do |method|
51
+ @another_class.class_eval %(def self.#{method}; end;)
52
+ end
53
+
54
+ @another_class.register_class_hooks :method_one, :method_two, :method_three
55
+ @another_class.class_hooks.keys.should include(:method_one)
56
+ @another_class.class_hooks.keys.should include(:method_two)
57
+ @another_class.class_hooks.keys.should include(:method_three)
58
+ end
59
+
60
+ it "should not allow a method that does not exist to be registered as hookable" do
61
+ lambda { @another_class.register_class_hooks :method_one }.should raise_error(ArgumentError)
62
+ end
63
+
64
+ it "should allow hooks to be registered on methods from module extensions" do
65
+ @class.extend(@module)
66
+ @class.register_class_hooks :greet
67
+ @class.class_hooks[:greet].should_not be_nil
68
+ end
69
+
70
+ it "should allow modules to register hooks in the self.extended method" do
71
+ @module.class_eval do
72
+ def self.extended(base)
73
+ base.register_class_hooks :greet
74
+ end
75
+ end
76
+ @class.extend(@module)
77
+ @class.class_hooks[:greet].should_not be_nil
78
+ end
79
+
80
+ it "should be able to register protected methods as hooks" do
81
+ @class.class_eval %{protected; def self.protected_hookable; end;}
82
+ lambda { @class.register_class_hooks(:protected_hookable) }.should_not raise_error(ArgumentError)
83
+ end
84
+
85
+ it "should not be able to register private methods as hooks" do
86
+ @class.class_eval %{class << self; private; def private_hookable; end; end;}
87
+ lambda { @class.register_class_hooks(:private_hookable) }.should raise_error(ArgumentError)
88
+ end
89
+
90
+ it "should allow advising methods ending in ? or !" do
91
+ @class.class_eval do
92
+ def self.hookable!; two!; end;
93
+ def self.hookable?; three!; end;
94
+ register_class_hooks :hookable!, :hookable?
95
+ end
96
+ @class.before_class_method(:hookable!) { one! }
97
+ @class.after_class_method(:hookable?) { four! }
98
+
99
+ @class.should_receive(:one!).once.ordered
100
+ @class.should_receive(:two!).once.ordered
101
+ @class.should_receive(:three!).once.ordered
102
+ @class.should_receive(:four!).once.ordered
103
+
104
+ @class.hookable!
105
+ @class.hookable?
106
+ end
107
+
108
+ it "should allow hooking methods ending in ?, ! or = with method hooks" do
109
+ @class.class_eval do
110
+ def self.before_hookable!; one!; end;
111
+ def self.hookable!; two!; end;
112
+ def self.hookable?; three!; end;
113
+ def self.after_hookable?; four!; end;
114
+ register_class_hooks :hookable!, :hookable?
115
+ end
116
+ @class.before_class_method(:hookable!, :before_hookable!)
117
+ @class.after_class_method(:hookable?, :after_hookable?)
118
+
119
+ @class.should_receive(:one!).once.ordered
120
+ @class.should_receive(:two!).once.ordered
121
+ @class.should_receive(:three!).once.ordered
122
+ @class.should_receive(:four!).once.ordered
123
+
124
+ @class.hookable!
125
+ @class.hookable?
126
+ end
127
+ end
128
+
129
+ describe "for instance methods" do
130
+
131
+ it "shouldn't confuse instance method hooks and class method hooks" do
132
+ @class.register_instance_hooks :ambiguous
133
+ @class.register_class_hooks :ambiguous
134
+
135
+ inst = @class.new
136
+ inst.should_receive(:hi_mom!)
137
+ inst.ambiguous
138
+ end
139
+
140
+ it "should be able to register multiple hookable methods at once" do
141
+ %w(method_one method_two method_three).each do |method|
142
+ @another_class.send(:define_method, method) {}
143
+ end
144
+
145
+ @another_class.register_instance_hooks :method_one, :method_two, :method_three
146
+ @another_class.instance_hooks.keys.should include(:method_one)
147
+ @another_class.instance_hooks.keys.should include(:method_two)
148
+ @another_class.instance_hooks.keys.should include(:method_three)
149
+ end
150
+
151
+ it "should not allow a method that does not exist to be registered as hookable" do
152
+ lambda { @another_class.register_instance_hooks :method_one }.should raise_error(ArgumentError)
153
+ end
154
+
155
+ it "should allow hooks to be registered on included module methods" do
156
+ @class.send(:include, @module)
157
+ @class.register_instance_hooks :greet
158
+ @class.instance_hooks[:greet].should_not be_nil
159
+ end
160
+
161
+ it "should allow modules to register hooks in the self.included method" do
162
+ @module.class_eval do
163
+ def self.included(base)
164
+ base.register_instance_hooks :greet
165
+ end
166
+ end
167
+ @class.send(:include, @module)
168
+ @class.instance_hooks[:greet].should_not be_nil
169
+ end
170
+
171
+ it "should be able to register protected methods as hooks" do
172
+ @class.class_eval %{protected; def protected_hookable; end;}
173
+ lambda { @class.register_instance_hooks(:protected_hookable) }.should_not raise_error(ArgumentError)
174
+ end
175
+
176
+ it "should not be able to register private methods as hooks" do
177
+ @class.class_eval %{private; def private_hookable; end;}
178
+ lambda { @class.register_instance_hooks(:private_hookable) }.should raise_error(ArgumentError)
179
+ end
180
+
181
+ it "should allow hooking methods ending in ? or ! with block hooks" do
182
+ @class.class_eval do
183
+ def hookable!; two!; end;
184
+ def hookable?; three!; end;
185
+ register_instance_hooks :hookable!, :hookable?
186
+ end
187
+ @class.before(:hookable!) { one! }
188
+ @class.after(:hookable?) { four! }
189
+
190
+ inst = @class.new
191
+ inst.should_receive(:one!).once.ordered
192
+ inst.should_receive(:two!).once.ordered
193
+ inst.should_receive(:three!).once.ordered
194
+ inst.should_receive(:four!).once.ordered
195
+
196
+ inst.hookable!
197
+ inst.hookable?
198
+ end
199
+
200
+ it "should allow hooking methods ending in ?, ! or = with method hooks" do
201
+ @class.class_eval do
202
+ def before_hookable(val); one!; end;
203
+ def hookable=(val); two!; end;
204
+ def hookable?; three!; end;
205
+ def after_hookable?; four!; end;
206
+ register_instance_hooks :hookable=, :hookable?
207
+ end
208
+ @class.before(:hookable=, :before_hookable)
209
+ @class.after(:hookable?, :after_hookable?)
210
+
211
+ inst = @class.new
212
+ inst.should_receive(:one!).once.ordered
213
+ inst.should_receive(:two!).once.ordered
214
+ inst.should_receive(:three!).once.ordered
215
+ inst.should_receive(:four!).once.ordered
216
+
217
+ inst.hookable = 'hello'
218
+ inst.hookable?
219
+ end
220
+ end
221
+
222
+ end
223
+
224
+ describe "implicit hookable method registration" do
225
+
226
+ describe "for class methods" do
227
+ it "should implicitly register the method as hookable" do
228
+ @class.class_eval %{def self.implicit_hook; end;}
229
+ @class.before_class_method(:implicit_hook) { hello }
230
+
231
+ @class.should_receive(:hello)
232
+ @class.implicit_hook
233
+ end
234
+ end
235
+
236
+ describe "for instance methods" do
237
+ it "should implicitly register the method as hookable" do
238
+ @class.class_eval %{def implicit_hook; end;}
239
+ @class.before(:implicit_hook) { hello }
240
+
241
+ inst = @class.new
242
+ inst.should_receive(:hello)
243
+ inst.implicit_hook
244
+ end
245
+
246
+ it 'should not overwrite methods included by modules after the hook is declared' do
247
+ my_module = Module.new do
248
+ # Just another module
249
+ @another_module = Module.new do
250
+ def some_method; "Hello " + super; end;
251
+ end
252
+
253
+ def some_method; "world"; end;
254
+
255
+ def self.included(base)
256
+ base.before(:some_method, :a_method)
257
+ base.send(:include, @another_module)
258
+ end
259
+ end
260
+
261
+ @class.class_eval { include my_module }
262
+
263
+ inst = @class.new
264
+ inst.should_receive(:a_method)
265
+ inst.some_method.should == "Hello world"
266
+ end
267
+ end
268
+
269
+ end
270
+
271
+ describe "hook method registration" do
272
+
273
+ describe "for class methods" do
274
+ it "should complain when only one argument is passed" do
275
+ lambda { @class.before_class_method(:clakable) }.should raise_error(ArgumentError)
276
+ lambda { @class.after_class_method(:clakable) }.should raise_error(ArgumentError)
277
+ end
278
+
279
+ it "should complain when target_method is not a symbol" do
280
+ lambda { @class.before_class_method("clakable", :ambiguous) }.should raise_error(ArgumentError)
281
+ lambda { @class.after_class_method("clakable", :ambiguous) }.should raise_error(ArgumentError)
282
+ end
283
+
284
+ it "should complain when method_sym is not a symbol" do
285
+ lambda { @class.before_class_method(:clakable, "ambiguous") }.should raise_error(ArgumentError)
286
+ lambda { @class.after_class_method(:clakable, "ambiguous") }.should raise_error(ArgumentError)
287
+ end
288
+
289
+ it "should not allow methods ending in = to be hooks" do
290
+ lambda { @class.before_class_method(:clakable, :annoying=) }.should raise_error(ArgumentError)
291
+ lambda { @class.after_class_method(:clakable, :annoying=) }.should raise_error(ArgumentError)
292
+ end
293
+ end
294
+
295
+ describe "for instance methods" do
296
+ it "should complain when only one argument is passed" do
297
+ lambda { @class.before(:hookable) }.should raise_error(ArgumentError)
298
+ lambda { @class.after(:hookable) }.should raise_error(ArgumentError)
299
+ end
300
+
301
+ it "should complain when target_method is not a symbol" do
302
+ lambda { @class.before("hookable", :ambiguous) }.should raise_error(ArgumentError)
303
+ lambda { @class.after("hookable", :ambiguous) }.should raise_error(ArgumentError)
304
+ end
305
+
306
+ it "should complain when method_sym is not a symbol" do
307
+ lambda { @class.before(:hookable, "ambiguous") }.should raise_error(ArgumentError)
308
+ lambda { @class.after(:hookable, "ambiguous") }.should raise_error(ArgumentError)
309
+ end
310
+
311
+ it "should not allow methods ending in = to be hooks" do
312
+ lambda { @class.before(:hookable, :annoying=) }.should raise_error(ArgumentError)
313
+ lambda { @class.after(:hookable, :annoying=) }.should raise_error(ArgumentError)
314
+ end
315
+ end
316
+
317
+ end
318
+
319
+ #
320
+ # Specs out how hook methods / blocks are invoked when there is no inheritance
321
+ # involved
322
+ #
323
+ describe "hook invocation without inheritance" do
324
+
325
+ describe "for class methods" do
326
+ it 'should run an advice block' do
327
+ @class.before_class_method(:clakable) { hi_mom! }
328
+ @class.should_receive(:hi_mom!)
329
+ @class.clakable
330
+ end
331
+
332
+ it 'should run an advice method' do
333
+ @class.class_eval %{def self.before_method; hi_mom!; end;}
334
+ @class.before_class_method(:clakable, :before_method)
335
+
336
+ @class.should_receive(:hi_mom!)
337
+ @class.clakable
338
+ end
339
+
340
+ it 'should pass the hookable method arguments to the hook method' do
341
+ @class.class_eval %{def self.hook_this(word, lol); end;}
342
+ @class.register_class_hooks(:hook_this)
343
+ @class.before_class_method(:hook_this, :before_hook_this)
344
+
345
+ @class.should_receive(:before_hook_this).with("omg", "hi2u")
346
+ @class.hook_this("omg", "hi2u")
347
+ end
348
+
349
+ it 'should work with glob arguments (or whatever you call em)' do
350
+ @class.class_eval %{def self.hook_this(*args); end;}
351
+ @class.register_class_hooks(:hook_this)
352
+ @class.before_class_method(:hook_this, :before_hook_this)
353
+
354
+ @class.should_receive(:before_hook_this).with("omg", "hi2u", "lolercoaster")
355
+ @class.hook_this("omg", "hi2u", "lolercoaster")
356
+ end
357
+
358
+ it 'should allow the use of before and after together' do
359
+ @class.class_eval %{def self.before_hook; first!; end;}
360
+ @class.before_class_method(:clakable, :before_hook)
361
+ @class.after_class_method(:clakable) { last! }
362
+
363
+ @class.should_receive(:first!).once.ordered
364
+ @class.should_receive(:last!).once.ordered
365
+ @class.clakable
366
+ end
367
+ end
368
+
369
+ describe "for instance methods" do
370
+ it 'should run an advice block' do
371
+ @class.before(:hookable) { hi_mom! }
372
+
373
+ inst = @class.new
374
+ inst.should_receive(:hi_mom!)
375
+ inst.hookable
376
+ end
377
+
378
+ it 'should run an advice method' do
379
+ @class.send(:define_method, :before_method) { hi_mom! }
380
+ @class.before(:hookable, :before_method)
381
+
382
+ inst = @class.new
383
+ inst.should_receive(:hi_mom!)
384
+ inst.hookable
385
+ end
386
+
387
+ it 'should pass the hookable method arguments to the hook method' do
388
+ @class.class_eval %{def hook_this(word, lol); end;}
389
+ @class.register_instance_hooks(:hook_this)
390
+ @class.before(:hook_this, :before_hook_this)
391
+
392
+ inst = @class.new
393
+ inst.should_receive(:before_hook_this).with("omg", "hi2u")
394
+ inst.hook_this("omg", "hi2u")
395
+ end
396
+
397
+ it 'should work with glob arguments (or whatever you call em)' do
398
+ @class.class_eval %{def hook_this(*args); end;}
399
+ @class.register_instance_hooks(:hook_this)
400
+ @class.before(:hook_this, :before_hook_this)
401
+
402
+ inst = @class.new
403
+ inst.should_receive(:before_hook_this).with("omg", "hi2u", "lolercoaster")
404
+ inst.hook_this("omg", "hi2u", "lolercoaster")
405
+ end
406
+
407
+ it 'should allow the use of before and after together' do
408
+ @class.class_eval %{def after_hook; last!; end;}
409
+ @class.before(:hookable) { first! }
410
+ @class.after(:hookable, :after_hook)
411
+
412
+ inst = @class.new
413
+ inst.should_receive(:first!).once.ordered
414
+ inst.should_receive(:last!).once.ordered
415
+ inst.hookable
416
+ end
417
+
418
+ it 'should be able to use private methods as hooks' do
419
+ @class.class_eval %{private; def nike; doit!; end;}
420
+ @class.before(:hookable, :nike)
421
+
422
+ inst = @class.new
423
+ inst.should_receive(:doit!)
424
+ inst.hookable
425
+ end
426
+ end
427
+
428
+ end
429
+
430
+ describe "hook invocation with class inheritance" do
431
+
432
+ describe "for class methods" do
433
+ it 'should run an advice block when the class is inherited' do
434
+ @class.before_class_method(:clakable) { hi_mom! }
435
+ @child = Class.new(@class)
436
+ @child.should_receive(:hi_mom!)
437
+ @child.clakable
438
+ end
439
+
440
+ it 'should run an advice block on child class when hook is registered in parent after inheritance' do
441
+ @child = Class.new(@class)
442
+ @class.before_class_method(:clakable) { hi_mom! }
443
+ @child.should_receive(:hi_mom!)
444
+ @child.clakable
445
+ end
446
+
447
+ it 'should be able to declare advice methods in child classes' do
448
+ @class.class_eval %{def self.before_method; hi_dad!; end;}
449
+ @class.before_class_method(:clakable, :before_method)
450
+
451
+ @child = Class.new(@class) do
452
+ def self.child; hi_mom!; end;
453
+ before_class_method(:clakable, :child)
454
+ end
455
+
456
+ @child.should_receive(:hi_dad!).once.ordered
457
+ @child.should_receive(:hi_mom!).once.ordered
458
+ @child.clakable
459
+ end
460
+
461
+ it "should not execute hooks added in the child classes when in the parent class" do
462
+ @child = Class.new(@class) { def self.child; hi_mom!; end; }
463
+ @child.before_class_method(:clakable, :child)
464
+ @class.should_not_receive(:hi_mom!)
465
+ @class.clakable
466
+ end
467
+
468
+ it 'should not call the hook stack if the hookable method is overwritten and does not call super' do
469
+ @class.before_class_method(:clakable) { hi_mom! }
470
+ @child = Class.new(@class) do
471
+ def self.clakable; end;
472
+ end
473
+
474
+ @child.should_not_receive(:hi_mom!)
475
+ @child.clakable
476
+ end
477
+
478
+ it 'should not call hooks defined in the child class for a hookable method in a parent if the child overwrites the hookable method without calling super' do
479
+ @child = Class.new(@class) do
480
+ before_class_method(:clakable) { hi_mom! }
481
+ def self.clakable; end;
482
+ end
483
+
484
+ @child.should_not_receive(:hi_mom!)
485
+ @child.clakable
486
+ end
487
+
488
+ it 'should not call hooks defined in child class even if hook method exists in parent' do
489
+ @class.class_eval %{def self.hello_world; hello_world!; end;}
490
+ @child = Class.new(@class) do
491
+ before_class_method(:clakable, :hello_world)
492
+ end
493
+
494
+ @class.should_not_receive(:hello_world!)
495
+ @class.clakable
496
+ end
497
+ end
498
+
499
+ describe "for instance methods" do
500
+ it 'should run an advice block when the class is inherited' do
501
+ @inherited_class = Class.new(@class)
502
+ @class.before(:hookable) { hi_dad! }
503
+
504
+ inst = @inherited_class.new
505
+ inst.should_receive(:hi_dad!)
506
+ inst.hookable
507
+ end
508
+
509
+ it 'should run an advice block on child class when hook is registered in parent after inheritance' do
510
+ @child = Class.new(@class)
511
+ @class.before(:hookable) { hi_mom! }
512
+
513
+ inst = @child.new
514
+ inst.should_receive(:hi_mom!)
515
+ inst.hookable
516
+ end
517
+
518
+ it 'should be able to declare advice methods in child classes' do
519
+ @class.send(:define_method, :before_method) { hi_dad! }
520
+ @class.before(:hookable, :before_method)
521
+
522
+ @child = Class.new(@class) do
523
+ def child; hi_mom!; end;
524
+ before :hookable, :child
525
+ end
526
+
527
+ inst = @child.new
528
+ inst.should_receive(:hi_dad!).once.ordered
529
+ inst.should_receive(:hi_mom!).once.ordered
530
+ inst.hookable
531
+ end
532
+
533
+ it "should not execute hooks added in the child classes when in parent class" do
534
+ @child = Class.new(@class)
535
+ @child.send(:define_method, :child) { hi_mom! }
536
+ @child.before(:hookable, :child)
537
+
538
+ inst = @class.new
539
+ inst.should_not_receive(:hi_mom!)
540
+ inst.hookable
541
+ end
542
+
543
+
544
+
545
+ it 'should not call the hook stack if the hookable method is overwritten and does not call super' do
546
+ @class.before(:hookable) { hi_mom! }
547
+ @child = Class.new(@class) do
548
+ def hookable; end;
549
+ end
550
+
551
+ inst = @child.new
552
+ inst.should_not_receive(:hi_mom!)
553
+ inst.hookable
554
+ end
555
+
556
+ it 'should not call hooks defined in the child class for a hookable method in a parent if the child overwrites the hookable method without calling super' do
557
+ @child = Class.new(@class) do
558
+ before(:hookable) { hi_mom! }
559
+ def hookable; end;
560
+ end
561
+
562
+ inst = @child.new
563
+ inst.should_not_receive(:hi_mom!)
564
+ inst.hookable
565
+ end
566
+
567
+ it 'should not call hooks defined in child class even if hook method exists in parent' do
568
+ @class.send(:define_method, :hello_world) { hello_world! }
569
+ @child = Class.new(@class) do
570
+ before(:hookable, :hello_world)
571
+ end
572
+
573
+ inst = @class.new
574
+ inst.should_not_receive(:hello_world!)
575
+ inst.hookable
576
+ end
577
+ end
578
+
579
+ end
580
+
581
+ describe "hook invocation with module inclusions / extensions" do
582
+
583
+ describe "for class methods" do
584
+ it "should not overwrite methods included by extensions after the hook is declared" do
585
+ @module.class_eval do
586
+ @another_module = Module.new do
587
+ def greet; greetings_from_another_module; super; end;
588
+ end
589
+
590
+ def self.extended(base)
591
+ base.before_class_method(:clakable, :greet)
592
+ base.extend(@another_module)
593
+ end
594
+ end
595
+
596
+ @class.extend(@module)
597
+ @class.should_receive(:greetings_from_another_module).once.ordered
598
+ @class.should_receive(:greetings_from_module).once.ordered
599
+ @class.clakable
600
+ end
601
+ end
602
+
603
+ describe "for instance methods" do
604
+ it 'should not overwrite methods included by modules after the hook is declared' do
605
+ @module.class_eval do
606
+ @another_module = Module.new do
607
+ def greet; greetings_from_another_module; super; end;
608
+ end
609
+
610
+ def self.included(base)
611
+ base.before(:hookable, :greet)
612
+ base.send(:include, @another_module)
613
+ end
614
+ end
615
+
616
+ @class.send(:include, @module)
617
+
618
+ inst = @class.new
619
+ inst.should_receive(:greetings_from_another_module).once.ordered
620
+ inst.should_receive(:greetings_from_module).once.ordered
621
+ inst.hookable
622
+ end
623
+ end
624
+
625
+ end
626
+
627
+ describe "hook invocation with unrelated classes" do
628
+
629
+ describe "for class methods" do
630
+ it "should not execute hooks registered in an unrelated class" do
631
+ @class.before_class_method(:clakable) { hi_mom! }
632
+
633
+ @other.should_not_receive(:hi_mom!)
634
+ @other.clakable
635
+ end
636
+ end
637
+
638
+ describe "for instance methods" do
639
+ it "should not execute hooks registered in an unrelated class" do
640
+ @class.before(:hookable) { hi_mom! }
641
+
642
+ inst = @other.new
643
+ inst.should_not_receive(:hi_mom!)
644
+ inst.hookable
645
+ end
646
+ end
647
+
648
+ end
649
+
650
+ describe "using before hook" do
651
+
652
+ describe "for class methods" do
653
+
654
+ it 'should run the advice before the advised method' do
655
+ @class.class_eval %{def self.hook_me; second!; end;}
656
+ @class.register_class_hooks(:hook_me)
657
+ @class.before_class_method(:hook_me, :first!)
658
+
659
+ @class.should_receive(:first!).ordered
660
+ @class.should_receive(:second!).ordered
661
+ @class.hook_me
662
+ end
663
+
664
+ it 'should execute all advices once in order' do
665
+ @class.before_class_method(:clakable, :hook_1)
666
+ @class.before_class_method(:clakable, :hook_2)
667
+ @class.before_class_method(:clakable, :hook_3)
668
+
669
+ @class.should_receive(:hook_1).once.ordered
670
+ @class.should_receive(:hook_2).once.ordered
671
+ @class.should_receive(:hook_3).once.ordered
672
+ @class.clakable
673
+ end
674
+ end
675
+
676
+ describe "for instance methods" do
677
+
678
+ it 'should run the advice before the advised method' do
679
+ @class.class_eval %{
680
+ def hook_me; second!; end;
681
+ }
682
+ @class.register_instance_hooks(:hook_me)
683
+ @class.before(:hook_me, :first!)
684
+
685
+ inst = @class.new
686
+ inst.should_receive(:first!).ordered
687
+ inst.should_receive(:second!).ordered
688
+ inst.hook_me
689
+ end
690
+
691
+ it 'should execute all advices once in order' do
692
+ @class.before(:hookable, :hook_1)
693
+ @class.before(:hookable, :hook_2)
694
+ @class.before(:hookable, :hook_3)
695
+
696
+ inst = @class.new
697
+ inst.should_receive(:hook_1).once.ordered
698
+ inst.should_receive(:hook_2).once.ordered
699
+ inst.should_receive(:hook_3).once.ordered
700
+ inst.hookable
701
+ end
702
+ end
703
+
704
+ end
705
+
706
+ describe 'using after hook' do
707
+
708
+ describe "for class methods" do
709
+
710
+ it 'should run the advice after the advised method' do
711
+ @class.class_eval %{def self.hook_me; first!; end;}
712
+ @class.register_class_hooks(:hook_me)
713
+ @class.after_class_method(:hook_me, :second!)
714
+
715
+ @class.should_receive(:first!).ordered
716
+ @class.should_receive(:second!).ordered
717
+ @class.hook_me
718
+ end
719
+
720
+ it 'should execute all advices once in order' do
721
+ @class.after_class_method(:clakable, :hook_1)
722
+ @class.after_class_method(:clakable, :hook_2)
723
+ @class.after_class_method(:clakable, :hook_3)
724
+
725
+ @class.should_receive(:hook_1).once.ordered
726
+ @class.should_receive(:hook_2).once.ordered
727
+ @class.should_receive(:hook_3).once.ordered
728
+ @class.clakable
729
+ end
730
+
731
+ it "the advised method should still return its normal value" do
732
+ @class.class_eval %{def self.hello; "hello world"; end;}
733
+ @class.register_class_hooks(:hello)
734
+ @class.after_class_method(:hello) { "BAM" }
735
+
736
+ @class.hello.should == "hello world"
737
+ end
738
+
739
+ end
740
+
741
+ describe "for instance methods" do
742
+
743
+ it 'should run the advice after the advised method' do
744
+ @class.class_eval %{def hook_me; first!; end;}
745
+ @class.register_instance_hooks(:hook_me)
746
+ @class.after(:hook_me, :second!)
747
+
748
+ inst = @class.new
749
+ inst.should_receive(:first!).ordered
750
+ inst.should_receive(:second!).ordered
751
+ inst.hook_me
752
+ end
753
+
754
+ it 'should execute all advices once in order' do
755
+ @class.after(:hookable, :hook_1)
756
+ @class.after(:hookable, :hook_2)
757
+ @class.after(:hookable, :hook_3)
758
+
759
+ inst = @class.new
760
+ inst.should_receive(:hook_1).once.ordered
761
+ inst.should_receive(:hook_2).once.ordered
762
+ inst.should_receive(:hook_3).once.ordered
763
+ inst.hookable
764
+ end
765
+
766
+ it "the advised method should still return its normal value" do
767
+ @class.class_eval %{def hello; "hello world"; end;}
768
+ @class.register_instance_hooks(:hello)
769
+ @class.after(:hello) { "BAM" }
770
+
771
+ @class.new.hello.should == "hello world"
772
+ end
773
+
774
+ end
775
+
776
+ end
777
+
778
+ describe 'aborting' do
779
+
780
+ describe "for class methods" do
781
+ it "should catch :halt from a before hook and abort the advised method" do
782
+ @class.class_eval %{def self.no_love; love_me!; end;}
783
+ @class.register_class_hooks :no_love
784
+ @class.before_class_method(:no_love) { maybe! }
785
+ @class.before_class_method(:no_love) { throw :halt }
786
+ @class.before_class_method(:no_love) { what_about_me? }
787
+
788
+ @class.should_receive(:maybe!)
789
+ @class.should_not_receive(:what_about_me?)
790
+ @class.should_not_receive(:love_me!)
791
+ lambda { @class.no_love }.should_not throw_symbol(:halt)
792
+ end
793
+
794
+ it "should not run after hooks if a before hook throws :halt" do
795
+ @class.before_class_method(:clakable) { throw :halt }
796
+ @class.after_class_method(:clakable) { bam! }
797
+
798
+ @class.should_not_receive(:bam!)
799
+ lambda { @class.clakable }.should_not throw_symbol(:halt)
800
+ end
801
+
802
+ it "should return nil from the hookable method if a before hook throws :halt" do
803
+ @class.class_eval %{def self.with_value; "hello"; end;}
804
+ @class.register_class_hooks(:with_value)
805
+ @class.before_class_method(:with_value) { throw :halt }
806
+
807
+ @class.with_value.should be_nil
808
+ end
809
+
810
+ it "should catch :halt from an after hook and cease the advice" do
811
+ @class.after_class_method(:clakable) { throw :halt }
812
+ @class.after_class_method(:clakable) { never_see_me! }
813
+
814
+ @class.should_not_receive(:never_see_me!)
815
+ lambda { @class.clakable }.should_not throw_symbol(:halt)
816
+ end
817
+
818
+ it "should still return the hookable methods return value if an after hook throws :halt" do
819
+ @class.class_eval %{def self.with_value; "hello"; end;}
820
+ @class.register_class_hooks(:with_value)
821
+ @class.after_class_method(:with_value) { throw :halt }
822
+
823
+ @class.with_value.should == "hello"
824
+ end
825
+ end
826
+
827
+ describe "for instance methods" do
828
+ it "should catch :halt from a before hook and abort the advised method" do
829
+ @class.class_eval %{def no_love; love_me!; end;}
830
+ @class.register_instance_hooks :no_love
831
+ @class.before(:no_love) { maybe! }
832
+ @class.before(:no_love) { throw :halt }
833
+ @class.before(:no_love) { what_about_me? }
834
+
835
+ inst = @class.new
836
+ inst.should_receive(:maybe!)
837
+ inst.should_not_receive(:what_about_me?)
838
+ inst.should_not_receive(:love_me!)
839
+ lambda { inst.no_love }.should_not throw_symbol(:halt)
840
+ end
841
+
842
+ it "should not run after hooks if a before hook throws :halt" do
843
+ @class.before(:hookable) { throw :halt }
844
+ @class.after(:hookable) { bam! }
845
+
846
+ inst = @class.new
847
+ inst.should_not_receive(:bam!)
848
+ lambda { inst.hookable }.should_not throw_symbol(:halt)
849
+ end
850
+
851
+ it "should return nil from the hookable method if a before hook throws :halt" do
852
+ @class.class_eval %{def with_value; "hello"; end;}
853
+ @class.register_instance_hooks(:with_value)
854
+ @class.before(:with_value) { throw :halt }
855
+
856
+ @class.new.with_value.should be_nil
857
+ end
858
+
859
+ it "should catch :halt from an after hook and cease the advice" do
860
+ @class.after(:hookable) { throw :halt }
861
+ @class.after(:hookable) { never_see_me! }
862
+
863
+ inst = @class.new
864
+ inst.should_not_receive(:never_see_me!)
865
+ lambda { inst.hookable }.should_not throw_symbol(:halt)
866
+ end
867
+
868
+ it "should still return the hookable methods return value if an after hook throws :halt" do
869
+ @class.class_eval %{def with_value; "hello"; end;}
870
+ @class.register_instance_hooks(:with_value)
871
+ @class.after(:with_value) { throw :halt }
872
+
873
+ @class.new.with_value.should == "hello"
874
+ end
875
+ end
876
+
877
+ end
878
+
879
+ describe "helper methods" do
880
+ it 'should generate the correct argument signature' do
881
+ @class.class_eval do
882
+ def some_method(a, b, c)
883
+ [a, b, c]
884
+ end
885
+
886
+ def yet_another(a, *heh)p
887
+ [a, *heh]
888
+ end
889
+ end
890
+
891
+ @class.args_for(@class.instance_method(:hookable)).should == ""
892
+ @class.args_for(@class.instance_method(:some_method)).should == "_1, _2, _3"
893
+ @class.args_for(@class.instance_method(:yet_another)).should == "_1, *args"
894
+ end
895
+ end
896
+
897
+ end