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.
- data/History.txt +4 -0
- data/Manifest.txt +22 -0
- data/README.txt +491 -0
- data/Rakefile +28 -0
- data/bin/hookr +8 -0
- data/lib/hookr.rb +653 -0
- data/spec/hookr_spec.rb +1041 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +192 -0
- data/tasks/git.rake +40 -0
- data/tasks/manifest.rake +48 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +39 -0
- data/tasks/rdoc.rake +50 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +279 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/test/test_hookr.rb +0 -0
- metadata +77 -0
data/spec/hookr_spec.rb
ADDED
@@ -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
|