hookr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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