hookr 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1041 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ class CustomHookType < HookR::Hook
4
+ end
5
+
6
+ describe HookR::Hooks do
7
+ describe "included in a class" do
8
+ before :each do
9
+ @class = Class.new
10
+ @class.instance_eval do
11
+ include HookR::Hooks
12
+ end
13
+ end
14
+
15
+ specify { @class.should have(0).hooks }
16
+
17
+ describe "and instantiated" do
18
+ before :each do
19
+ @it = @class.new
20
+ end
21
+
22
+ specify { @it.should have(0).hooks }
23
+ end
24
+
25
+ describe "with an alternate .make_hook() defined and a hook :foo" do
26
+ before :each do
27
+ @class.module_eval do
28
+ def self.make_hook(name, parent, params)
29
+ CustomHookType.new(name, parent, params)
30
+ end
31
+ end
32
+ @class.instance_eval do
33
+ define_hook(:foo)
34
+ end
35
+ end
36
+
37
+ specify "the hook type should be defined by .make_hook()" do
38
+ @class.hooks[:foo].should be_a_kind_of(CustomHookType)
39
+ end
40
+
41
+ describe "and instantiated" do
42
+ before :each do
43
+ @it = @class.new
44
+ end
45
+
46
+ specify "the instance hook type should be defined by .make_hook()" do
47
+ @it.hooks[:foo].should be_a_kind_of(CustomHookType)
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+
54
+ describe "with a hook :foo defined" do
55
+ before :each do
56
+ @class.instance_eval do
57
+ define_hook(:foo)
58
+ end
59
+ end
60
+
61
+ specify { @class.should have(1).hooks }
62
+
63
+ specify "the hooks should be a HookR::Hook" do
64
+ @class.hooks[:foo].should be_a_kind_of(HookR::Hook)
65
+ end
66
+
67
+ describe "and then subclassed" do
68
+ before :each do
69
+ @subclass = Class.new(@class)
70
+ end
71
+
72
+ specify "subclass should also have hook :foo" do
73
+ @subclass.hooks[:foo].should_not be_nil
74
+ end
75
+
76
+ specify "adding subclass hooks should not change superclass" do
77
+ @subclass.instance_eval do
78
+ define_hook(:bar)
79
+ end
80
+ @subclass.should have(2).hooks
81
+ @class.should have(1).hooks
82
+ end
83
+ end
84
+
85
+ describe "and instantiated" do
86
+ before :each do
87
+ @it = @class.new
88
+ end
89
+
90
+ specify { @it.should have(1).hooks }
91
+ end
92
+
93
+ describe "and then redefined" do
94
+ before :each do
95
+ @class.instance_eval do
96
+ define_hook(:foo)
97
+ end
98
+ end
99
+
100
+ it "should still have only one hook" do
101
+ @class.should have(1).hooks
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "with hooks :foo and :bar defined" do
107
+ before :each do
108
+ @class.instance_eval do
109
+ define_hook :foo
110
+ define_hook :bar
111
+ end
112
+ end
113
+
114
+ specify { @class.should have(2).hooks }
115
+
116
+ it "should have a hook named :bar" do
117
+ @class.hooks[:bar].should_not be_nil
118
+ end
119
+
120
+ it "should have a hook named :foo" do
121
+ @class.hooks[:foo].should_not be_nil
122
+ end
123
+
124
+ it "should have a hook macro for :foo" do
125
+ @class.should respond_to(:foo)
126
+ end
127
+
128
+ it "should have a hook macro for :bar" do
129
+ @class.should respond_to(:bar)
130
+ end
131
+
132
+ specify "hooks should be instances of Hook" do
133
+ @class.hooks[:foo].should be_a_kind_of(HookR::Hook)
134
+ @class.hooks[:bar].should be_a_kind_of(HookR::Hook)
135
+ end
136
+
137
+ describe "given a wildcard callback" do
138
+ before :each do
139
+ @sensor = stub("Sensor")
140
+ sensor = @sensor
141
+ @class.instance_eval do
142
+ add_wildcard_callback :joker do |event|
143
+ sensor.ping(event)
144
+ end
145
+ end
146
+ @it = @class.new
147
+ end
148
+
149
+ it "should call back for both hooks" do
150
+ @sensor.should_receive(:ping).twice
151
+ @it.send(:execute_hook, :foo)
152
+ @it.send(:execute_hook, :bar)
153
+ end
154
+
155
+ it "should be able to remove the callback" do
156
+ @it.instance_eval do
157
+ remove_wildcard_callback(:joker)
158
+ end
159
+ @sensor.should_not_receive(:ping).with(:foo)
160
+ @sensor.should_not_receive(:ping).with(:bar)
161
+ @it.send(:execute_hook, :foo)
162
+ @it.send(:execute_hook, :bar)
163
+ end
164
+ end
165
+
166
+ describe "and instantiated" do
167
+ before :each do
168
+ @it = @class.new
169
+ end
170
+
171
+ specify { @it.should have(2).hooks }
172
+ end
173
+
174
+ it "should define a Listener class responding to #foo and #bar" do
175
+ @listener_class = @class::Listener
176
+ @listener_class.instance_methods(false).should include("foo", "bar")
177
+ end
178
+
179
+ describe "given a subscribed Listener" do
180
+ before :each do
181
+ @listener = stub("Listener", :foo => nil, :bar => nil)
182
+ @it = @class.new
183
+ @it.add_listener(@listener)
184
+ end
185
+
186
+ it "should call listener.foo on :foo execution" do
187
+ @listener.should_receive(:foo)
188
+ @it.send(:execute_hook, :foo)
189
+ end
190
+
191
+ it "should call listener.bar on :bar execution" do
192
+ @listener.should_receive(:bar)
193
+ @it.send(:execute_hook, :bar)
194
+ end
195
+
196
+ specify "the listener should be removable" do
197
+ @it.remove_listener(@listener)
198
+ @listener.should_not_receive(:bar)
199
+ @it.send(:execute_hook, :bar)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ describe "a no-param hook named :on_signal" do
207
+ before :each do
208
+ @class = Class.new
209
+ @class.instance_eval do
210
+ include HookR::Hooks
211
+ define_hook :on_signal
212
+ end
213
+ @instance = @class.new
214
+ @instance2 = @class.new
215
+ @class_hook = @class.hooks[:on_signal]
216
+ @instance_hook = @instance.hooks[:on_signal]
217
+ @event = stub("Event", :to_args => [], :source => @instance)
218
+ end
219
+
220
+ it "should have no callbacks at the class level" do
221
+ @class_hook.should have(0).callbacks
222
+ end
223
+
224
+ it "should have no callbacks at the instance level" do
225
+ @instance_hook.should have(0).callbacks
226
+ end
227
+
228
+ specify "the instance hook's parent should be the class hook" do
229
+ @instance_hook.parent.should equal(@class_hook)
230
+ end
231
+
232
+ describe "given an class level and instance level callbacks" do
233
+ before :each do
234
+ @class.instance_eval do
235
+ on_signal do
236
+ 1 + 1
237
+ end
238
+ end
239
+ @instance_hook.add_external_callback do
240
+ # whatever
241
+ end
242
+ end
243
+
244
+ it "should have one callback at the class level" do
245
+ @class_hook.should have(1).callbacks
246
+ end
247
+
248
+ it "should have one callback at the instance level" do
249
+ @instance_hook.should have(1).callbacks
250
+ end
251
+
252
+ specify "there should be two callbacks total" do
253
+ @instance_hook.total_callbacks.should == 2
254
+ end
255
+
256
+ end
257
+
258
+ describe "given some named class-level block callbacks" do
259
+ before :each do
260
+ @class.instance_eval do
261
+ on_signal :callback1 do
262
+ 1 + 1
263
+ end
264
+ on_signal :callback2 do
265
+ 2 + 2
266
+ end
267
+ end
268
+ end
269
+
270
+ it "should have two callbacks at the class level" do
271
+ @class_hook.should have(2).callbacks
272
+ end
273
+
274
+ it "should have no callbacks at the instance level" do
275
+ @instance_hook.should have(0).callbacks
276
+ end
277
+
278
+ specify ":callback1 should be the first callback" do
279
+ @class_hook.callbacks[0].handle.should == :callback1
280
+ end
281
+
282
+ specify ":callback2 should be the second callback" do
283
+ @class_hook.callbacks[1].handle.should == :callback2
284
+ end
285
+
286
+ specify ":callback1 should execute the given code" do
287
+ @class_hook.callbacks[:callback1].call(@event).should == 2
288
+ end
289
+
290
+ specify ":callback2 should execute the given code" do
291
+ @class_hook.callbacks[:callback2].call(@event).should == 4
292
+ end
293
+ end
294
+ end
295
+
296
+ describe "a two-param hook named :on_signal" do
297
+ before :each do
298
+ @class = Class.new
299
+ @class.instance_eval do
300
+ include HookR::Hooks
301
+ define_hook :on_signal, :color, :flavor
302
+ end
303
+ @instance = @class.new
304
+ @instance2 = @class.new
305
+ @class_hook = @class.hooks[:on_signal]
306
+ @instance_hook = @instance.hooks[:on_signal]
307
+ @event = stub("Event", :to_args => [])
308
+ @sensor = stub("Sensor")
309
+ end
310
+
311
+ describe "given a four-arg callback" do
312
+ before :each do
313
+ sensor = @sensor
314
+ @class.instance_eval do
315
+ on_signal do |event, color, flavor|
316
+ sensor.ping(event, color, flavor)
317
+ end
318
+ end
319
+ @callback = @class_hook.callbacks[0]
320
+ end
321
+
322
+ it "should call back with the correct arguments" do
323
+ @sensor.should_receive(:ping) do |event, color, flavor|
324
+ event.source.should equal(@instance)
325
+ event.name.should == :on_signal
326
+ color.should == :purple
327
+ flavor.should == :grape
328
+ end
329
+ @instance.send(:execute_hook, :on_signal, :purple, :grape)
330
+ end
331
+
332
+ specify "the callback should be an external callback" do
333
+ @callback.should be_a_kind_of(HookR::ExternalCallback)
334
+ end
335
+ end
336
+
337
+ describe "given a one-arg callback" do
338
+ before :each do
339
+ sensor = @sensor
340
+ end
341
+
342
+ it "should raise an exception" do
343
+ lambda do
344
+ @class.instance_eval do
345
+ on_signal do |flavor|
346
+ end
347
+ end
348
+ end.should raise_error(ArgumentError)
349
+ end
350
+ end
351
+
352
+ describe "given an implicit no-arg callback" do
353
+ before :each do
354
+ @class.instance_eval do
355
+ on_signal do
356
+ # yadda yadda
357
+ end
358
+ end
359
+ @callback = @class_hook.callbacks[0]
360
+ end
361
+
362
+ specify "the callback should be an internal callback" do
363
+ @callback.should be_a_kind_of(HookR::InternalCallback)
364
+ end
365
+
366
+ end
367
+
368
+ describe "given an explicit no-arg callback" do
369
+ before :each do
370
+ @class.instance_eval do ||
371
+ on_signal do
372
+ # yadda yadda
373
+ end
374
+ end
375
+ @callback = @class_hook.callbacks[0]
376
+ end
377
+
378
+ specify "the callback should be an internal callback" do
379
+ @callback.should be_a_kind_of(HookR::InternalCallback)
380
+ end
381
+ end
382
+
383
+ describe "given a method callback :do_stuff" do
384
+ before :each do
385
+ @class.module_eval do
386
+ def do_stuff(sensor)
387
+ sensor.ping(:do_stuff)
388
+ end
389
+ end
390
+ @class.instance_eval do
391
+ on_signal :do_stuff
392
+ end
393
+ @callback = @class_hook.callbacks[0]
394
+ end
395
+
396
+ specify "the callback should be a method callback" do
397
+ @callback.should be_a_kind_of(HookR::MethodCallback)
398
+ end
399
+
400
+ specify "executing the callback should execute :do_stuff" do
401
+ @sensor.should_receive(:ping).with(:do_stuff)
402
+ @instance.send(:execute_hook, :on_signal, @sensor)
403
+ end
404
+
405
+ specify "the callback handle should be :do_stuff" do
406
+ @callback.handle.should == :do_stuff
407
+ end
408
+ end
409
+
410
+ describe "given a named class-level callback" do
411
+ before :each do
412
+ sensor = @sensor
413
+ @class.instance_eval do
414
+ on_signal :my_callback do
415
+ end
416
+ end
417
+ end
418
+
419
+ specify "the callback should be removable" do
420
+ @class.send(:remove_callback, :on_signal, :my_callback)
421
+ @class_hook.should have(0).callbacks
422
+ end
423
+ end
424
+
425
+ describe "given an anonymous instance-level callback" do
426
+ before :each do
427
+ @instance.on_signal do |event, arg1, arg2|
428
+ @sensor.ping(event, arg1, arg2)
429
+ end
430
+ end
431
+
432
+ specify "the instance should have 1 callback" do
433
+ @instance_hook.should have(1).callbacks
434
+ end
435
+
436
+ specify "the callback should be external" do
437
+ @instance_hook.callbacks[0].should be_a_kind_of(HookR::ExternalCallback)
438
+ end
439
+
440
+ specify "the callback should call back" do
441
+ @sensor.should_receive(:ping) do |event, arg1, arg2|
442
+ event.source.should == @instance
443
+ event.name.should == :on_signal
444
+ arg1.should == :apple
445
+ arg2.should == :orange
446
+ end
447
+ @instance.send(:execute_hook, :on_signal, :apple, :orange)
448
+ end
449
+
450
+ specify "the callback should be removable" do
451
+ @instance.remove_callback(:on_signal, 0)
452
+ @instance_hook.should have(0).callbacks
453
+ end
454
+ end
455
+
456
+ describe "given a named instance-level callback" do
457
+ before :each do
458
+ @instance.on_signal :xyzzy do |event, arg1, arg2|
459
+ @sensor.ping(event, arg1, arg2)
460
+ end
461
+ end
462
+
463
+ specify "the callback should be removable" do
464
+ @instance.remove_callback(:on_signal, :xyzzy)
465
+ @instance_hook.should have(0).callbacks
466
+ end
467
+
468
+ end
469
+
470
+ describe "given an instance-level internal callback" do
471
+ before :each do
472
+ sensor = @sensor
473
+ @class.module_eval do
474
+ private
475
+ define_method :secret do
476
+ sensor.ping
477
+ end
478
+ end
479
+ @instance.instance_eval do
480
+ add_callback(:on_signal) do
481
+ secret
482
+ end
483
+ end
484
+ end
485
+
486
+ specify "the callback should call back in the instance context" do
487
+ @sensor.should_receive(:ping)
488
+ @instance.send(:execute_hook, :on_signal)
489
+ end
490
+ end
491
+
492
+ describe "given three callbacks" do
493
+ def log(*args)
494
+ @log << args
495
+ end
496
+
497
+ before :each do
498
+ @log = []
499
+ @instance.on_signal(:cb1) do |event, arg1, arg2|
500
+ log(:cb1_before, event.name, arg1, arg2)
501
+ event.next
502
+ log(:cb1_after, event.name, arg1, arg2)
503
+ end
504
+ @instance.on_signal(:cb2) do |event, arg1, arg2|
505
+ log(:cb2_before, event.name, arg1, arg2)
506
+ event.next(:pish, :tosh)
507
+ log(:cb2_after, event.name, arg1, arg2)
508
+ end
509
+ @instance.on_signal(:cb3) do |event, arg1, arg2|
510
+ log(:cb3_before, event.name, arg1, arg2)
511
+ event.next
512
+ log(:cb3_after, event.name, arg1, arg2)
513
+ end
514
+ end
515
+
516
+ it "should be able to execute callbacks recursively" do
517
+ @instance.send(:execute_hook, :on_signal, :fizz, :buzz) do |arg1, arg2|
518
+ log(:inner, arg1, arg2)
519
+ end
520
+
521
+ @log.should == [
522
+ [:cb3_before, :on_signal, :fizz, :buzz],
523
+ [:cb2_before, :on_signal, :fizz, :buzz],
524
+ [:cb1_before, :on_signal, :pish, :tosh],
525
+ [:inner, :pish, :tosh],
526
+ [:cb1_after, :on_signal, :pish, :tosh],
527
+ [:cb2_after, :on_signal, :fizz, :buzz],
528
+ [:cb3_after, :on_signal, :fizz, :buzz]
529
+ ]
530
+ end
531
+
532
+
533
+ end
534
+
535
+ describe "given a Listener" do
536
+ before :each do
537
+ @listener = stub("Listener")
538
+ @instance.add_listener(@listener)
539
+ end
540
+
541
+ it "should pass arguments to listener" do
542
+ @listener.should_receive(:on_signal).with("red", "green")
543
+ @instance.send(:execute_hook, :on_signal, "red", "green")
544
+ end
545
+ end
546
+
547
+ end
548
+
549
+ describe HookR::Hook do
550
+ before :each do
551
+ @class = HookR::Hook
552
+ @sensor = stub("Sensor")
553
+ sensor = @sensor
554
+ @source_class = Class.new do
555
+ define_method :my_method do
556
+ sensor.ping(:my_method)
557
+ end
558
+ end
559
+ @source = @source_class.new
560
+ @event = stub("Event", :source => @source, :to_args => [])
561
+ end
562
+
563
+ it "should require name to be a symbol" do
564
+ lambda do
565
+ @class.new("foo")
566
+ end.should raise_error(FailFast::AssertionFailureError)
567
+ end
568
+
569
+ describe "named :foo" do
570
+ before :each do
571
+ @it = @class.new(:foo)
572
+ @callback = stub("Callback", :handle => 123)
573
+ @block = lambda {}
574
+ end
575
+
576
+ specify { @it.name.should == :foo }
577
+
578
+ specify { @it.should have(0).callbacks }
579
+
580
+ it "should be equal to any other hook named :foo" do
581
+ @parent = HookR::Hook.new(:parent)
582
+ @other = HookR::Hook.new(:foo, @parent)
583
+ @it.should == @other
584
+ @it.should eql(@other)
585
+ end
586
+
587
+ describe "when adding a callback" do
588
+ it "should return the handle of the added callback" do
589
+ @it.add_callback(@callback).should == 123
590
+ end
591
+ end
592
+
593
+ describe "given an anonymous external callback" do
594
+ before :each do
595
+ @it.add_external_callback(&@block)
596
+ end
597
+
598
+ specify { @it.should have(1).callbacks }
599
+
600
+ end
601
+
602
+ describe "given a selection of callbacks" do
603
+ before :each do
604
+ sensor = @sensor
605
+ @anon_external_cb = @it.add_external_callback do
606
+ @sensor.ping(:anon_external)
607
+ end
608
+ @named_external_cb = @it.add_external_callback(:my_external) do
609
+ @sensor.ping(:named_external)
610
+ end
611
+ @anon_internal_cb = @it.add_internal_callback do ||
612
+ sensor.ping(:anon_internal)
613
+ end
614
+ @named_internal_cb = @it.add_internal_callback(:my_internal) do ||
615
+ sensor.ping(:named_internal)
616
+ end
617
+ @method_cb = @it.add_method_callback(@source_class, :my_method)
618
+ end
619
+
620
+ specify { @it.should have(5).callbacks }
621
+
622
+ specify "the handles of the anonymous callbacks should be their indexes" do
623
+ @it.callbacks[0].handle.should == 0
624
+ @it.callbacks[2].handle.should == 2
625
+ end
626
+
627
+ specify "the add methods should return handles" do
628
+ @anon_external_cb.should == 0
629
+ @named_external_cb.should == :my_external
630
+ @anon_internal_cb.should == 2
631
+ @named_internal_cb.should == :my_internal
632
+ @method_cb.should == :my_method
633
+ end
634
+
635
+ specify "the callbacks should have the intended types" do
636
+ @it.callbacks[@anon_external_cb].should be_a_kind_of(HookR::ExternalCallback)
637
+ @it.callbacks[@named_external_cb].should be_a_kind_of(HookR::ExternalCallback)
638
+ @it.callbacks[@anon_internal_cb].should be_a_kind_of(HookR::InternalCallback)
639
+ @it.callbacks[@named_internal_cb].should be_a_kind_of(HookR::InternalCallback)
640
+ @it.callbacks[@method_cb].should be_a_kind_of(HookR::MethodCallback)
641
+ end
642
+
643
+ specify "the callbacks should execute in order of addition" do
644
+ @sensor.should_receive(:ping).with(:anon_external).ordered
645
+ @sensor.should_receive(:ping).with(:named_external).ordered
646
+ @sensor.should_receive(:ping).with(:anon_internal).ordered
647
+ @sensor.should_receive(:ping).with(:named_internal).ordered
648
+ @sensor.should_receive(:ping).with(:my_method).ordered
649
+
650
+ @it.execute_callbacks(@event)
651
+ end
652
+
653
+ it "should be able to iterate through callbacks" do
654
+ callbacks = []
655
+ @it.each_callback do |callback|
656
+ callbacks << callback.handle
657
+ end
658
+ callbacks.should == [@anon_external_cb, @named_external_cb,
659
+ @anon_internal_cb, @named_internal_cb, @method_cb]
660
+ end
661
+
662
+ it "should be able to iterate through callbacks in reverse" do
663
+ callbacks = []
664
+ @it.each_callback_reverse do |callback|
665
+ callbacks << callback.handle
666
+ end
667
+ callbacks.should == [@method_cb, @named_internal_cb, @anon_internal_cb,
668
+ @named_external_cb, @anon_external_cb]
669
+ end
670
+
671
+ it "should be able to clear its own callbacks" do
672
+ @it.clear_callbacks!
673
+ @it.callbacks.should be_empty
674
+ end
675
+
676
+ it "should be able to clear all callbacks" do
677
+ @it.clear_all_callbacks!
678
+ @it.callbacks.should be_empty
679
+ end
680
+ end
681
+ end
682
+
683
+ describe "with no parent given" do
684
+ before :each do
685
+ @it = HookR::Hook.new(:root_hook)
686
+ end
687
+
688
+ it "should have a null parent" do
689
+ @it.parent.should be_a_kind_of(HookR::NullHook)
690
+ end
691
+ end
692
+
693
+ describe "given a parent" do
694
+ before :each do
695
+ @event = stub("Event")
696
+ @parent_callback = stub("Parent callback", :handle => :parent)
697
+ @child_callback = stub("Child callback", :handle => :child)
698
+ @parent = HookR::Hook.new(:parent_hook)
699
+ @parent.add_callback(@parent_callback)
700
+ @it = HookR::Hook.new(:child_hook, @parent)
701
+ @it.add_callback(@child_callback)
702
+ end
703
+
704
+ it "should have a parent" do
705
+ @it.parent.should equal(@parent)
706
+ end
707
+
708
+ it "should not be a root hook" do
709
+ @it.should_not be_root
710
+ end
711
+
712
+ it "should call parent callbacks before calling own callbacks" do
713
+ @parent_callback.should_receive(:call).with(@event)
714
+ @child_callback.should_receive(:call).with(@event)
715
+ @it.execute_callbacks(@event)
716
+ end
717
+
718
+ it "should report 2 total callbacks" do
719
+ @it.total_callbacks.should == 2
720
+ end
721
+
722
+ it "should be able to iterate over own and parent callbacks" do
723
+ callbacks = []
724
+ @it.each_callback do |callback|
725
+ callbacks << callback
726
+ end
727
+ callbacks.should == [@parent_callback, @child_callback]
728
+ end
729
+
730
+ it "should be able to reverse-iterate over own and parent callbacks" do
731
+ callbacks = []
732
+ @it.each_callback_reverse do |callback|
733
+ callbacks << callback
734
+ end
735
+ callbacks.should == [@child_callback, @parent_callback]
736
+ end
737
+
738
+ it "should be able to clear its own callbacks, leaving parent callbacks" do
739
+ @it.clear_callbacks!
740
+ @it.callbacks.should be_empty
741
+ callbacks = []
742
+ @it.each_callback do |callback|
743
+ callbacks << callback
744
+ end
745
+ callbacks.should == [@parent_callback]
746
+ end
747
+
748
+ it "should be able to clear all callbacks" do
749
+ @it.clear_all_callbacks!
750
+ @it.callbacks.should be_empty
751
+ callbacks = []
752
+ @it.each_callback do |callback|
753
+ callbacks << callback
754
+ end
755
+ callbacks.should == []
756
+ end
757
+
758
+ it "should leave parent callbacks alone when clearing all" do
759
+ @it.clear_all_callbacks!
760
+ @parent.should have(1).callbacks
761
+ end
762
+
763
+ it "should be detached from parent after clearing all callbacks" do
764
+ @it.clear_all_callbacks!
765
+ @it.should be_root
766
+ end
767
+ end
768
+
769
+ describe "duplicated" do
770
+ before :each do
771
+ @callback = stub("Callback", :handle => :foo)
772
+ @parent = HookR::Hook.new(:parent)
773
+ @parent.add_callback(@callback)
774
+ @child = @parent.dup
775
+ end
776
+
777
+ it "should have the original as its parent" do
778
+ @child.parent.should equal(@parent)
779
+ end
780
+
781
+ it "should have no callbacks of its own" do
782
+ @child.should have(0).callbacks
783
+ end
784
+
785
+ specify "parent should still have a callback" do
786
+ @parent.should have(1).callbacks
787
+ end
788
+ end
789
+ end
790
+
791
+ describe HookR::CallbackSet do
792
+ before :each do
793
+ @it = HookR::CallbackSet.new
794
+ @cb1 = HookR::Callback.new(:cb1, 1)
795
+ @cb2 = HookR::Callback.new(:cb2, 2)
796
+ @cb3 = HookR::Callback.new(:cb3, 3)
797
+ end
798
+
799
+ describe "given three callbacks" do
800
+ before :each do
801
+ @it << @cb1
802
+ @it << @cb3
803
+ @it << @cb2
804
+ end
805
+
806
+ it "should sort the callbacks" do
807
+ @it.to_a.should == [@cb1, @cb2, @cb3]
808
+ end
809
+
810
+ it "should be able to locate callbacks by index" do
811
+ @it[1].should equal(@cb1)
812
+ @it[2].should equal(@cb2)
813
+ @it[3].should equal(@cb3)
814
+ end
815
+
816
+ it "should return nil if a callback cannot be found" do
817
+ @it[4].should be_nil
818
+ end
819
+
820
+ it "should be able to locate callbacks by handle" do
821
+ @it[:cb1].should equal(@cb1)
822
+ @it[:cb2].should equal(@cb2)
823
+ @it[:cb3].should equal(@cb3)
824
+ end
825
+ end
826
+ end
827
+
828
+ describe HookR::Callback, "with handle :cb1 and an index of 1" do
829
+ before :each do
830
+ @block = stub("block", :call => nil)
831
+ @it = HookR::Callback.new(:cb1, 1)
832
+ end
833
+
834
+ it "should sort as greater than a callback with index of 0" do
835
+ @other = HookR::Callback.new(:cb2, 0)
836
+ (@it <=> @other).should == 1
837
+ end
838
+
839
+ it "should sort as less than a callback with index of 2" do
840
+ @other = HookR::Callback.new(:cb2, 2)
841
+ (@it <=> @other).should == -1
842
+ end
843
+
844
+ it "should sort as equal to a callback with index of 1" do
845
+ @other = HookR::Callback.new(:cb2, 1)
846
+ (@it <=> @other).should == 0
847
+ end
848
+
849
+ it "should sort as equal to any callback with the same handle" do
850
+ @other = HookR::Callback.new(:cb1, 2)
851
+ (@it <=> @other).should == 0
852
+ end
853
+ end
854
+
855
+ describe "Callbacks: " do
856
+ before :each do
857
+ @handle = :foo
858
+ @sensor = stub("Sensor")
859
+ @index = 1
860
+ @source = stub("Source")
861
+ @name = :we_get_signal!
862
+ @arguments = []
863
+ @event = stub("Event", :source => @source)
864
+ end
865
+
866
+ describe HookR::ExternalCallback do
867
+ describe "with a no-param block" do
868
+ before :each do
869
+ @block = stub("Block", :arity => 0, :call => nil)
870
+ @it = HookR::ExternalCallback.new(@handle, @block, @index)
871
+ end
872
+
873
+ it "should take 0 args from event and call block with no args" do
874
+ @event.should_receive(:to_args).with(0).and_return([])
875
+ @block.should_receive(:call).with()
876
+ @it.call(@event)
877
+ end
878
+ end
879
+
880
+ describe "with a two-param block" do
881
+ before :each do
882
+ @block = stub("Block", :arity => 2, :call => nil)
883
+ @it = HookR::ExternalCallback.new(@handle, @block, @index)
884
+ end
885
+
886
+ it "should take 2 args from event and call block with 2 args" do
887
+ @event.should_receive(:to_args).with(2).and_return([:a, :b])
888
+ @block.should_receive(:call).with(:a, :b)
889
+ @it.call(@event)
890
+ end
891
+ end
892
+ end
893
+
894
+ describe HookR::InternalCallback do
895
+ describe "with a no-param block" do
896
+ before :each do
897
+ source = @source
898
+ @block = lambda do
899
+ source.ping
900
+ end
901
+ @it = HookR::InternalCallback.new(@handle, @block, @index)
902
+ end
903
+
904
+ it "should instance eval the block on the event source" do
905
+ @source.should_receive(:instance_eval).and_yield
906
+ @source.should_receive(:ping)
907
+ @it.call(@event)
908
+ end
909
+ end
910
+
911
+ describe "with a one-param block" do
912
+ it "should raise error" do
913
+ @block = stub("Block", :arity => 1, :call => nil)
914
+ lambda do
915
+ @it = HookR::InternalCallback.new(@handle, @block, @index)
916
+ end.should raise_error
917
+ end
918
+ end
919
+ end
920
+
921
+ describe HookR::MethodCallback do
922
+ describe "with a no-param method" do
923
+ before :each do
924
+ @method = stub("Method", :arity => 0, :call => nil)
925
+ @bound_method = stub("Bound Method", :call => nil)
926
+ @it = HookR::MethodCallback.new(@handle, @method, @index)
927
+ end
928
+
929
+ it "should take 0 args from event and call method with no args" do
930
+ @event.should_receive(:to_args).with(0).and_return([])
931
+ @method.should_receive(:bind).with(@source).and_return(@bound_method)
932
+ @bound_method.should_receive(:call).with()
933
+ @it.call(@event)
934
+ end
935
+ end
936
+
937
+ describe "with a two-param block" do
938
+ before :each do
939
+ @method = stub("Method", :arity => 2, :call => nil)
940
+ @bound_method = stub("Bound Method", :call => nil)
941
+ @it = HookR::MethodCallback.new(@handle, @method, @index)
942
+ end
943
+
944
+ it "should take 2 args from event and call method with 2 args" do
945
+ @event.should_receive(:to_args).with(2).and_return([:a, :b])
946
+ @method.should_receive(:bind).with(@source).and_return(@bound_method)
947
+ @bound_method.should_receive(:call).with(:a, :b)
948
+ @it.call(@event)
949
+ end
950
+ end
951
+ end
952
+
953
+ describe HookR::BasicCallback, "handling a two-arg event" do
954
+ before :each do
955
+ @event.stub!(:arguments => [:a, :b])
956
+ end
957
+
958
+ describe "given a zero-param block" do
959
+ before :each do
960
+ @block = stub("Block", :arity => 0)
961
+ end
962
+
963
+ it "should bitch about arity" do
964
+ lambda do
965
+ @it = HookR::BasicCallback.new(@handle, @block, @index)
966
+ end.should raise_error(ArgumentError)
967
+ end
968
+ end
969
+
970
+ describe "given a one-param block" do
971
+ before :each do
972
+ @block = stub("Block", :arity => 1)
973
+ @it = HookR::BasicCallback.new(@handle, @block, @index)
974
+ end
975
+
976
+ it "should pass the event as the only argument to the block" do
977
+ @block.should_receive(:call).with(@event)
978
+ @it.call(@event)
979
+ end
980
+ end
981
+
982
+ describe "given a two-param block" do
983
+ before :each do
984
+ @block = stub("Block", :arity => 2)
985
+ end
986
+
987
+ it "should bitch about arity" do
988
+ lambda do
989
+ @it = HookR::BasicCallback.new(@handle, @block, @index)
990
+ end.should raise_error(ArgumentError)
991
+ end
992
+ end
993
+ end
994
+ end
995
+
996
+ describe HookR::Event do
997
+ describe "with three arguments" do
998
+ before :each do
999
+ @source = stub("Source")
1000
+ @name = :on_signal
1001
+ @arguments = ["arg1", "arg2", "arg3"]
1002
+ @it = HookR::Event.new(@source,
1003
+ @name,
1004
+ @arguments)
1005
+ end
1006
+
1007
+ describe "given an arity of -1" do
1008
+ it "should convert to four arguments" do
1009
+ @it.to_args(-1).should == [@it, *@arguments]
1010
+ end
1011
+ end
1012
+
1013
+ describe "given an arity of 2" do
1014
+ it "should raise an error" do
1015
+ lambda do
1016
+ @it.to_args(2)
1017
+ end.should raise_error
1018
+ end
1019
+ end
1020
+
1021
+ describe "given an arity of 3" do
1022
+ it "should convert to three arguments" do
1023
+ @it.to_args(3).should == @arguments
1024
+ end
1025
+ end
1026
+
1027
+ describe "given an arity of 4" do
1028
+ it "should convert to four arguments" do
1029
+ @it.to_args(4).should == [@it, *@arguments]
1030
+ end
1031
+ end
1032
+
1033
+ describe "given an arity of 5" do
1034
+ it "should raise an error" do
1035
+ lambda do
1036
+ @it.to_args(5)
1037
+ end.should raise_error
1038
+ end
1039
+ end
1040
+ end
1041
+ end