wisper-compat 4.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +19 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +4 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +152 -0
  7. data/CONTRIBUTING.md +57 -0
  8. data/Gemfile +10 -0
  9. data/README.md +358 -0
  10. data/Rakefile +6 -0
  11. data/lib/wisper/broadcasters/logger_broadcaster.rb +41 -0
  12. data/lib/wisper/broadcasters/send_broadcaster.rb +9 -0
  13. data/lib/wisper/configuration.rb +44 -0
  14. data/lib/wisper/global_listeners.rb +72 -0
  15. data/lib/wisper/publisher.rb +89 -0
  16. data/lib/wisper/registration/block.rb +11 -0
  17. data/lib/wisper/registration/object.rb +43 -0
  18. data/lib/wisper/registration/registration.rb +18 -0
  19. data/lib/wisper/temporary_listeners.rb +41 -0
  20. data/lib/wisper/value_objects/events.rb +61 -0
  21. data/lib/wisper/value_objects/prefix.rb +29 -0
  22. data/lib/wisper/version.rb +3 -0
  23. data/lib/wisper.rb +65 -0
  24. data/spec/lib/global_listeners_spec.rb +82 -0
  25. data/spec/lib/integration_spec.rb +56 -0
  26. data/spec/lib/simple_example_spec.rb +21 -0
  27. data/spec/lib/temporary_global_listeners_spec.rb +103 -0
  28. data/spec/lib/wisper/broadcasters/logger_broadcaster_spec.rb +129 -0
  29. data/spec/lib/wisper/broadcasters/send_broadcaster_spec.rb +68 -0
  30. data/spec/lib/wisper/configuration/broadcasters_spec.rb +11 -0
  31. data/spec/lib/wisper/configuration_spec.rb +36 -0
  32. data/spec/lib/wisper/publisher_spec.rb +311 -0
  33. data/spec/lib/wisper/registrations/object_spec.rb +14 -0
  34. data/spec/lib/wisper/value_objects/events_spec.rb +107 -0
  35. data/spec/lib/wisper/value_objects/prefix_spec.rb +46 -0
  36. data/spec/lib/wisper_spec.rb +99 -0
  37. data/spec/spec_helper.rb +21 -0
  38. data/wisper-compat.gemspec +29 -0
  39. metadata +102 -0
@@ -0,0 +1,103 @@
1
+ describe Wisper::TemporaryListeners do
2
+ let(:listener_1) { double('listener', :to_a => nil) } # [1]
3
+ let(:listener_2) { double('listener', :to_a => nil) }
4
+
5
+ let(:publisher) { publisher_class.new }
6
+
7
+ describe '.subscribe' do
8
+ it 'globally subscribes listener for duration of given block' do
9
+
10
+ expect(listener_1).to receive(:success)
11
+ expect(listener_1).to_not receive(:failure)
12
+
13
+ Wisper::TemporaryListeners.subscribe(listener_1) do
14
+ publisher.instance_eval { broadcast(:success) }
15
+ end
16
+
17
+ publisher.instance_eval { broadcast(:failure) }
18
+ end
19
+
20
+ it 'globally subscribes listeners for duration of given block' do
21
+
22
+ expect(listener_1).to receive(:success)
23
+ expect(listener_1).to_not receive(:failure)
24
+
25
+ expect(listener_2).to receive(:success)
26
+ expect(listener_2).to_not receive(:failure)
27
+
28
+ Wisper::TemporaryListeners.subscribe(listener_1, listener_2) do
29
+ publisher.instance_eval { broadcast(:success) }
30
+ end
31
+
32
+ publisher.instance_eval { broadcast(:failure) }
33
+ end
34
+
35
+ it 'globally subscribes listeners for duration of nested block' do
36
+
37
+ expect(listener_1).to receive(:success)
38
+ expect(listener_1).to receive(:failure)
39
+
40
+ expect(listener_2).to receive(:success)
41
+ expect(listener_2).to_not receive(:failure)
42
+
43
+ Wisper::TemporaryListeners.subscribe(listener_1) do
44
+ Wisper::TemporaryListeners.subscribe(listener_2) do
45
+ publisher.instance_eval { broadcast(:success) }
46
+ end
47
+ publisher.instance_eval { broadcast(:failure) }
48
+ end
49
+ end
50
+
51
+ it 'clears registrations for the block which exits' do
52
+
53
+ # listener_1 is subscribed twice hence it's supposed to get the message twice
54
+ expect(listener_1).to receive(:success).twice
55
+ expect(listener_1).to receive(:failure)
56
+ expect(listener_1).to_not receive(:ignored)
57
+
58
+ expect(listener_2).to receive(:success)
59
+ expect(listener_2).to_not receive(:failure)
60
+ expect(listener_2).to_not receive(:ignored)
61
+
62
+ Wisper::TemporaryListeners.subscribe(listener_1) do
63
+ Wisper::TemporaryListeners.subscribe(listener_1, listener_2) do
64
+ publisher.instance_eval { broadcast(:success) }
65
+ end
66
+ publisher.instance_eval { broadcast(:failure) }
67
+ end
68
+ publisher.instance_eval { broadcast(:ignored) }
69
+ end
70
+
71
+ it 'is thread safe' do
72
+ num_threads = 20
73
+ (1..num_threads).to_a.map do
74
+ Thread.new do
75
+ Wisper::TemporaryListeners.registrations << Object.new
76
+ expect(Wisper::TemporaryListeners.registrations.size).to eq 1
77
+ end
78
+ end.each(&:join)
79
+
80
+ expect(Wisper::TemporaryListeners.registrations).to be_empty
81
+ end
82
+
83
+ it 'clears registrations when an exception occurs' do
84
+ MyError = Class.new(StandardError)
85
+
86
+ begin
87
+ Wisper::TemporaryListeners.subscribe(listener_1) do
88
+ raise MyError
89
+ end
90
+ rescue MyError
91
+ end
92
+
93
+ expect(Wisper::TemporaryListeners.registrations).to be_empty
94
+ end
95
+
96
+ it 'returns self' do
97
+ expect(Wisper::TemporaryListeners.subscribe {}).to be_an_instance_of(Wisper::TemporaryListeners)
98
+ end
99
+ end
100
+ end
101
+
102
+ # [1] stubbing `to_a` prevents `Double "listener" received unexpected message
103
+ # :to_a with (no args)` on MRI 1.9.2 when a double is passed to `Array()`.
@@ -0,0 +1,129 @@
1
+ module Wisper
2
+ module Broadcasters
3
+
4
+ describe LoggerBroadcaster do
5
+
6
+ describe 'integration tests:' do
7
+ let(:publisher) { publisher_class.new }
8
+ let(:listener) { double }
9
+ let(:logger) { double.as_null_object }
10
+
11
+ context 'with only positional arguments' do
12
+ it 'broadcasts the event to the listener' do
13
+ publisher.subscribe(listener, :broadcaster => LoggerBroadcaster.new(logger, Wisper::Broadcasters::SendBroadcaster.new))
14
+ if RUBY_VERSION < '3.0'
15
+ # Ruby 2.7 receives **{} as a positional argument
16
+ expect(listener).to receive(:it_happened).with(1, 2, {})
17
+ else
18
+ # Ruby 3.0 doesn't pass **empty_hash
19
+ expect(listener).to receive(:it_happened).with(1, 2)
20
+ end
21
+ publisher.send(:broadcast, :it_happened, 1, 2)
22
+ end
23
+ end
24
+
25
+ context 'with only keyword arguments' do
26
+ it 'broadcasts the event to the listener' do
27
+ publisher.subscribe(listener, :broadcaster => LoggerBroadcaster.new(logger, Wisper::Broadcasters::SendBroadcaster.new))
28
+ expect(listener).to receive(:it_happened).with(key: 'value')
29
+ publisher.send(:broadcast, :it_happened, key: 'value')
30
+ end
31
+ end
32
+
33
+ context 'with positional and keyword arguments' do
34
+ it 'broadcasts the event to the listener' do
35
+ publisher.subscribe(listener, :broadcaster => LoggerBroadcaster.new(logger, Wisper::Broadcasters::SendBroadcaster.new))
36
+ expect(listener).to receive(:it_happened).with(1, 2, key: 'value')
37
+ publisher.send(:broadcast, :it_happened, 1, 2, key: 'value')
38
+ end
39
+ end
40
+ end
41
+
42
+ describe 'unit tests:' do
43
+ let(:publisher) { classy_double('Publisher', id: 1) }
44
+ let(:listener) { classy_double('Listener', id: 2) }
45
+ let(:logger) { double('Logger').as_null_object }
46
+ let(:broadcaster) { double('Broadcaster').as_null_object }
47
+ let(:event) { 'thing_created' }
48
+
49
+ subject { LoggerBroadcaster.new(logger, broadcaster) }
50
+
51
+ describe '#broadcast' do
52
+ context 'without arguments' do
53
+ let(:args) { [] }
54
+ let(:kwargs) { {} }
55
+
56
+ it 'logs published event' do
57
+ expect(logger).to receive(:info).with('[WISPER] Publisher#1 published thing_created to Listener#2 with no arguments and no keyword arguments')
58
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
59
+ end
60
+
61
+ it 'delegates broadcast to a given broadcaster' do
62
+ expect(broadcaster).to receive(:broadcast).with(listener, publisher, event, *args, **kwargs)
63
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
64
+ end
65
+ end
66
+
67
+ context 'with arguments' do
68
+ let(:args) { [arg_double(id: 3), arg_double(id: 4)] }
69
+ let(:kwargs) { {x: :y} }
70
+
71
+ it 'logs published event and arguments' do
72
+ expect(logger).to receive(:info).with("[WISPER] Publisher#1 published thing_created to Listener#2 with Argument#3, Argument#4 and keyword arguments {:x=>:y}")
73
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
74
+ end
75
+
76
+ it 'delegates broadcast to a given broadcaster' do
77
+ expect(broadcaster).to receive(:broadcast).with(listener, publisher, event, *args, **kwargs)
78
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
79
+ end
80
+
81
+ context 'when argument is a hash' do
82
+ let(:args) { [hash] }
83
+ let(:hash) { {key: 'value'} }
84
+ let(:kwargs) { {x: :y} }
85
+
86
+ it 'logs published event and arguments' do
87
+ expect(logger).to receive(:info).with("[WISPER] Publisher#1 published thing_created to Listener#2 with Hash##{hash.object_id}: #{hash.inspect} and keyword arguments {:x=>:y}")
88
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
89
+ end
90
+ end
91
+
92
+ context 'when argument is an integer' do
93
+ let(:args) { [number] }
94
+ let(:number) { 10 }
95
+
96
+ it 'logs published event and arguments' do
97
+ expect(logger).to receive(:info).with("[WISPER] Publisher#1 published thing_created to Listener#2 with #{number.class.name}##{number.object_id}: 10 and keyword arguments {:x=>:y}")
98
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
99
+ end
100
+ end
101
+
102
+ context 'when only keyword arguments are present' do
103
+ let(:args) { [] }
104
+
105
+ it 'logs published event and arguments' do
106
+ expect(logger).to receive(:info).with("[WISPER] Publisher#1 published thing_created to Listener#2 with no arguments and keyword arguments {:x=>:y}")
107
+ subject.broadcast(listener, publisher, event, *args, **kwargs)
108
+ end
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ # provides a way to specify `double.class.name` easily
115
+ def classy_double(klass, options)
116
+ double(klass, options.merge(class: double_class(klass)))
117
+ end
118
+
119
+ def arg_double(options)
120
+ classy_double('Argument', options)
121
+ end
122
+
123
+ def double_class(name)
124
+ double(name: name)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,68 @@
1
+ module Wisper
2
+ module Broadcasters
3
+ describe SendBroadcaster do
4
+ let(:listener) { double('listener') }
5
+ let(:event) { 'thing_created' }
6
+
7
+ describe '#broadcast' do
8
+ context 'without arguments' do
9
+ it 'sends event to listener without any arguments' do
10
+ if RUBY_VERSION < '3.0'
11
+ expect(listener).to receive(event).with({})
12
+ else
13
+ expect(listener).to receive(event).with(no_args)
14
+ end
15
+ subject.broadcast(listener, anything, event)
16
+ end
17
+ end
18
+
19
+ context 'with empty arguments' do
20
+ let(:args) { [] }
21
+
22
+ it 'sends event to listener without any arguments' do
23
+ if RUBY_VERSION < '3.0'
24
+ expect(listener).to receive(event).with({})
25
+ else
26
+ expect(listener).to receive(event).with(no_args)
27
+ end
28
+ subject.broadcast(listener, anything, event, *args)
29
+ end
30
+ end
31
+
32
+ context 'with arguments' do
33
+ context 'with only positional arguments' do
34
+ let(:args) { [1,2,3] }
35
+
36
+ it 'sends event to listener with arguments' do
37
+ if RUBY_VERSION < '3.0'
38
+ expect(listener).to receive(event).with(1, 2, 3, {})
39
+ else
40
+ expect(listener).to receive(event).with(1, 2, 3)
41
+ end
42
+ subject.broadcast(listener, anything, event, *args)
43
+ end
44
+ end
45
+
46
+ context 'with only keyword arguments' do
47
+ let(:kwargs) { { key: 'value' } }
48
+
49
+ it 'sends event to listener with arguments' do
50
+ expect(listener).to receive(event).with({key: 'value'})
51
+ subject.broadcast(listener, anything, event, **kwargs)
52
+ end
53
+ end
54
+
55
+ context 'with positional and keyword arguments' do
56
+ let(:args) { [1,2,3] }
57
+ let(:kwargs) { { key: 'value' } }
58
+
59
+ it 'sends event to listener with arguments' do
60
+ expect(listener).to receive(event).with(1,2,3, {key: 'value'})
61
+ subject.broadcast(listener, anything, event, *args, **kwargs)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,11 @@
1
+ module Wisper
2
+ describe Configuration::Broadcasters do
3
+ describe 'broadcasters' do
4
+ describe '#to_h' do
5
+ it 'returns a Hash' do
6
+ expect(subject.to_h).to be_instance_of(Hash)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module Wisper
2
+ describe Configuration do
3
+ describe 'broadcasters' do
4
+ let(:broadcaster) { double }
5
+ let(:key) { :default }
6
+
7
+ it '#broadcasters returns empty collection' do
8
+ expect(subject.broadcasters).to be_empty
9
+ end
10
+
11
+ describe '#broadcaster' do
12
+ it 'adds given broadcaster' do
13
+ subject.broadcaster(key, broadcaster)
14
+ expect(subject.broadcasters).to include key
15
+ expect(subject.broadcasters[key]).to eql broadcaster
16
+ end
17
+
18
+ it 'returns the configuration' do
19
+ expect(subject.broadcaster(key, broadcaster)).to eq subject
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#default_prefix=' do
25
+ let(:prefix_class) { ValueObjects::Prefix }
26
+ let(:default_value) { double }
27
+
28
+ before { allow(prefix_class).to receive(:default=) }
29
+
30
+ it 'sets the default value for prefixes' do
31
+ expect(prefix_class).to receive(:default=).with(default_value)
32
+ subject.default_prefix = default_value
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,311 @@
1
+ describe Wisper::Publisher do
2
+ let(:listener) { double('listener') }
3
+ let(:publisher) { publisher_class.new }
4
+
5
+ describe '.subscribe' do
6
+ it 'subscribes given listener to all published events' do
7
+ expect(listener).to receive(:this_happened)
8
+ expect(listener).to receive(:so_did_this)
9
+
10
+ publisher.subscribe(listener)
11
+
12
+ publisher.send(:broadcast, 'this_happened')
13
+ publisher.send(:broadcast, 'so_did_this')
14
+ end
15
+
16
+ describe ':on argument' do
17
+ before do
18
+ allow(listener).to receive(:something_a_happened)
19
+ allow(listener).to receive(:and_this)
20
+ allow(listener).to receive(:so_did_this)
21
+ end
22
+
23
+ describe 'given a string' do
24
+ it 'subscribes listener to an event' do
25
+ expect(listener).to receive(:this_happened)
26
+ expect(listener).not_to receive(:so_did_this)
27
+
28
+ publisher.subscribe(listener, on: 'this_happened')
29
+
30
+ publisher.send(:broadcast, 'this_happened')
31
+ publisher.send(:broadcast, 'so_did_this')
32
+ end
33
+ end
34
+
35
+ describe 'given a symbol' do
36
+ it 'subscribes listener to an event' do
37
+ expect(listener).to receive(:this_happened)
38
+ expect(listener).not_to receive(:so_did_this)
39
+
40
+ publisher.subscribe(listener, on: :this_happened)
41
+
42
+ publisher.send(:broadcast, 'this_happened')
43
+ publisher.send(:broadcast, 'so_did_this')
44
+ end
45
+ end
46
+
47
+ describe 'given an array' do
48
+ it 'subscribes listener to events' do
49
+ expect(listener).to receive(:this_happened)
50
+ expect(listener).to receive(:and_this)
51
+ expect(listener).not_to receive(:so_did_this)
52
+
53
+ publisher.subscribe(listener, on: ['this_happened', 'and_this'])
54
+
55
+ publisher.send(:broadcast, 'this_happened')
56
+ publisher.send(:broadcast, 'so_did_this')
57
+ publisher.send(:broadcast, 'and_this')
58
+ end
59
+ end
60
+
61
+ describe 'given a regex' do
62
+ it 'subscribes listener to matching events' do
63
+ expect(listener).to receive(:something_a_happened)
64
+ expect(listener).not_to receive(:so_did_this)
65
+
66
+ publisher.subscribe(listener, on: /something_._happened/)
67
+
68
+ publisher.send(:broadcast, 'something_a_happened')
69
+ publisher.send(:broadcast, 'so_did_this')
70
+ end
71
+ end
72
+
73
+ describe 'given an unsupported argument' do
74
+ it 'raises an error' do
75
+ publisher.subscribe(listener, on: Object.new)
76
+ expect { publisher.send(:broadcast, 'something_a_happened') }.to raise_error(ArgumentError)
77
+ end
78
+ end
79
+ end
80
+
81
+ describe ':with argument' do
82
+ it 'sets method to call listener with on event' do
83
+ expect(listener).to receive(:different_method).twice
84
+
85
+ publisher.subscribe(listener, :with => :different_method)
86
+
87
+ publisher.send(:broadcast, 'this_happened')
88
+ publisher.send(:broadcast, 'so_did_this')
89
+ end
90
+ end
91
+
92
+ describe ':prefix argument' do
93
+ it 'prefixes broadcast events with given symbol' do
94
+ expect(listener).to receive(:after_it_happened)
95
+ expect(listener).not_to receive(:it_happened)
96
+
97
+ publisher.subscribe(listener, :prefix => :after)
98
+
99
+ publisher.send(:broadcast, 'it_happened')
100
+ end
101
+
102
+ it 'prefixes broadcast events with "on" when given true' do
103
+ expect(listener).to receive(:on_it_happened)
104
+ expect(listener).not_to receive(:it_happened)
105
+
106
+ publisher.subscribe(listener, :prefix => true)
107
+
108
+ publisher.send(:broadcast, 'it_happened')
109
+ end
110
+ end
111
+
112
+ describe ':scope argument' do
113
+ let(:listener_1) { double('Listener') }
114
+ let(:listener_2) { double('Listener') }
115
+
116
+ it 'scopes listener to given class' do
117
+ expect(listener_1).to receive(:it_happended)
118
+ expect(listener_2).not_to receive(:it_happended)
119
+ publisher.subscribe(listener_1, :scope => publisher.class)
120
+ publisher.subscribe(listener_2, :scope => Class.new)
121
+ publisher.send(:broadcast, 'it_happended')
122
+ end
123
+
124
+ it 'scopes listener to given class string' do
125
+ expect(listener_1).to receive(:it_happended)
126
+ expect(listener_2).not_to receive(:it_happended)
127
+ publisher.subscribe(listener_1, :scope => publisher.class.to_s)
128
+ publisher.subscribe(listener_2, :scope => Class.new.to_s)
129
+ publisher.send(:broadcast, 'it_happended')
130
+ end
131
+
132
+ it 'includes all subclasses of given class' do
133
+ publisher_super_klass = publisher_class
134
+ publisher_sub_klass = Class.new(publisher_super_klass)
135
+
136
+ listener = double('Listener')
137
+ expect(listener).to receive(:it_happended).once
138
+
139
+ publisher = publisher_sub_klass.new
140
+
141
+ publisher.subscribe(listener, :scope => publisher_super_klass)
142
+ publisher.send(:broadcast, 'it_happended')
143
+ end
144
+ end
145
+
146
+ describe ':broadcaster argument'do
147
+ let(:broadcaster) { double('broadcaster') }
148
+ let(:listener) { double('listener') }
149
+ let(:event_name) { 'it_happened' }
150
+
151
+ before do
152
+ Wisper.configuration.broadcasters.clear
153
+ allow(listener).to receive(event_name)
154
+ allow(broadcaster).to receive(:broadcast)
155
+ end
156
+
157
+ after { Wisper.setup } # restore default configuration
158
+
159
+ it 'given an object which responds_to broadcast it uses object' do
160
+ publisher.subscribe(listener, broadcaster: broadcaster)
161
+ expect(broadcaster).to receive('broadcast')
162
+ publisher.send(:broadcast, event_name)
163
+ end
164
+
165
+ it 'given a key it uses a configured broadcaster' do
166
+ Wisper.configure { |c| c.broadcaster(:foobar, broadcaster) }
167
+ publisher.subscribe(listener, broadcaster: :foobar)
168
+ expect(broadcaster).to receive('broadcast')
169
+ publisher.send(:broadcast, event_name)
170
+ end
171
+
172
+ it 'given an unknown key it raises error' do
173
+ expect { publisher.subscribe(listener, broadcaster: :foobar) }.to raise_error(KeyError, /broadcaster not found/)
174
+ end
175
+
176
+ it 'given nothing it uses the default broadcaster' do
177
+ Wisper.configure { |c| c.broadcaster(:default, broadcaster) }
178
+ publisher.subscribe(listener)
179
+ expect(broadcaster).to receive('broadcast')
180
+ publisher.send(:broadcast, event_name)
181
+ end
182
+
183
+ describe 'async alias' do
184
+ it 'given an object which responds_to broadcast it uses object' do
185
+ publisher.subscribe(listener, async: broadcaster)
186
+ expect(broadcaster).to receive('broadcast')
187
+ publisher.send(:broadcast, event_name)
188
+ end
189
+
190
+ it 'given true it uses configured async broadcaster' do
191
+ Wisper.configure { |c| c.broadcaster(:async, broadcaster) }
192
+ publisher.subscribe(listener, async: true)
193
+ expect(broadcaster).to receive('broadcast')
194
+ publisher.send(:broadcast, event_name)
195
+ end
196
+
197
+ it 'given false it uses configured default broadcaster' do
198
+ Wisper.configure { |c| c.broadcaster(:default, broadcaster) }
199
+ publisher.subscribe(listener, async: false)
200
+ expect(broadcaster).to receive('broadcast')
201
+ publisher.send(:broadcast, event_name)
202
+ end
203
+ end
204
+ end
205
+
206
+ it 'returns publisher so methods can be chained' do
207
+ expect(publisher.subscribe(listener, :on => 'so_did_this')).to \
208
+ eq publisher
209
+ end
210
+
211
+ it 'is aliased to .subscribe' do
212
+ expect(publisher).to respond_to(:subscribe)
213
+ end
214
+
215
+ it 'raises a helpful error if trying to pass a block' do
216
+ invalid = ->{
217
+ publisher.subscribe(:success) do
218
+ puts
219
+ end
220
+ }
221
+ expect{ invalid.call }.to raise_error(ArgumentError)
222
+ end
223
+ end
224
+
225
+ describe '.on' do
226
+ it 'returns publisher so methods can be chained' do
227
+ expect(publisher.on('this_thing_happened') {}).to eq publisher
228
+ end
229
+
230
+ it 'raise an error if no events given' do
231
+ expect { publisher.on() {} }.to raise_error(ArgumentError)
232
+ end
233
+
234
+ it 'raises an error of no block given' do
235
+ expect { publisher.on(:something) }.to raise_error(ArgumentError)
236
+ end
237
+
238
+ it 'returns publisher so methods can be chained' do
239
+ expect(publisher.on(:foo) {}).to eq publisher
240
+ end
241
+ end
242
+
243
+ describe '.broadcast' do
244
+
245
+ it 'does not publish events which cannot be responded to' do
246
+ expect(listener).not_to receive(:so_did_this)
247
+ allow(listener).to receive(:respond_to?).and_return(false)
248
+
249
+ publisher.subscribe(listener, :on => 'so_did_this')
250
+
251
+ publisher.send(:broadcast, 'so_did_this')
252
+ end
253
+
254
+ describe ':event argument' do
255
+ it 'is indifferent to string and symbol' do
256
+ expect(listener).to receive(:this_happened).twice
257
+
258
+ publisher.subscribe(listener)
259
+
260
+ publisher.send(:broadcast, 'this_happened')
261
+ publisher.send(:broadcast, :this_happened)
262
+ end
263
+
264
+ it 'is indifferent to dasherized and underscored strings' do
265
+ expect(listener).to receive(:this_happened).twice
266
+
267
+ publisher.subscribe(listener)
268
+
269
+ publisher.send(:broadcast, 'this_happened')
270
+ publisher.send(:broadcast, 'this-happened')
271
+ end
272
+ end
273
+
274
+ it 'returns publisher' do
275
+ expect(publisher.send(:broadcast, :foo)).to eq publisher
276
+ end
277
+
278
+ it 'is not public' do
279
+ expect(publisher).not_to respond_to(:broadcast)
280
+ end
281
+
282
+ it 'is alised as .publish' do
283
+ expect(publisher.method(:broadcast)).to eq publisher.method(:publish)
284
+ end
285
+ end
286
+
287
+ describe '.listeners' do
288
+ it 'returns an immutable collection' do
289
+ expect(publisher.listeners).to be_frozen
290
+ expect { publisher.listeners << listener }.to raise_error(RuntimeError)
291
+ end
292
+
293
+ it 'returns local listeners' do
294
+ publisher.subscribe(listener)
295
+ expect(publisher.listeners).to eq [listener]
296
+ expect(publisher.listeners.size).to eq 1
297
+ end
298
+ end
299
+
300
+ describe '#subscribe' do
301
+ let(:publisher_klass_1) { publisher_class }
302
+ let(:publisher_klass_2) { publisher_class }
303
+
304
+ it 'subscribes listener to all instances of publisher' do
305
+ publisher_klass_1.subscribe(listener)
306
+ expect(listener).to receive(:it_happened).once
307
+ publisher_klass_1.new.send(:broadcast, 'it_happened')
308
+ publisher_klass_2.new.send(:broadcast, 'it_happened')
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,14 @@
1
+ describe Wisper::ObjectRegistration do
2
+
3
+ describe 'broadcaster' do
4
+ it 'defaults to SendBroadcaster' do
5
+ subject = Wisper::ObjectRegistration.new(double('listener'))
6
+ expect(subject.broadcaster).to be_instance_of(Wisper::Broadcasters::SendBroadcaster)
7
+ end
8
+
9
+ it 'default is lazily evaluated' do
10
+ expect(Wisper::Broadcasters::SendBroadcaster).to_not receive :new
11
+ Wisper::ObjectRegistration.new(double('listener'), broadcaster: double('DifferentBroadcaster').as_null_object)
12
+ end
13
+ end
14
+ end