ably 0.8.15 → 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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/CHANGELOG.md +6 -2
  4. data/README.md +5 -1
  5. data/SPEC.md +1473 -852
  6. data/ably.gemspec +11 -8
  7. data/lib/ably/auth.rb +90 -53
  8. data/lib/ably/exceptions.rb +37 -8
  9. data/lib/ably/logger.rb +10 -1
  10. data/lib/ably/models/auth_details.rb +42 -0
  11. data/lib/ably/models/channel_state_change.rb +18 -4
  12. data/lib/ably/models/connection_details.rb +6 -3
  13. data/lib/ably/models/connection_state_change.rb +4 -3
  14. data/lib/ably/models/error_info.rb +1 -1
  15. data/lib/ably/models/message.rb +17 -1
  16. data/lib/ably/models/message_encoders/base.rb +103 -82
  17. data/lib/ably/models/message_encoders/base64.rb +1 -1
  18. data/lib/ably/models/presence_message.rb +16 -1
  19. data/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/ably/models/token_details.rb +11 -1
  21. data/lib/ably/models/token_request.rb +16 -6
  22. data/lib/ably/modules/async_wrapper.rb +7 -3
  23. data/lib/ably/modules/encodeable.rb +51 -12
  24. data/lib/ably/modules/enum.rb +17 -7
  25. data/lib/ably/modules/event_emitter.rb +29 -14
  26. data/lib/ably/modules/model_common.rb +13 -21
  27. data/lib/ably/modules/state_emitter.rb +7 -4
  28. data/lib/ably/modules/state_machine.rb +2 -4
  29. data/lib/ably/modules/uses_state_machine.rb +7 -3
  30. data/lib/ably/realtime.rb +2 -0
  31. data/lib/ably/realtime/auth.rb +102 -42
  32. data/lib/ably/realtime/channel.rb +68 -26
  33. data/lib/ably/realtime/channel/channel_manager.rb +154 -65
  34. data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  35. data/lib/ably/realtime/client.rb +18 -3
  36. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  37. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  38. data/lib/ably/realtime/connection.rb +108 -49
  39. data/lib/ably/realtime/connection/connection_manager.rb +167 -61
  40. data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  41. data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  42. data/lib/ably/realtime/presence.rb +70 -45
  43. data/lib/ably/realtime/presence/members_map.rb +201 -36
  44. data/lib/ably/realtime/presence/presence_manager.rb +30 -6
  45. data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  46. data/lib/ably/rest.rb +2 -2
  47. data/lib/ably/rest/channel.rb +5 -5
  48. data/lib/ably/rest/client.rb +31 -27
  49. data/lib/ably/rest/middleware/exceptions.rb +1 -3
  50. data/lib/ably/rest/middleware/logger.rb +2 -2
  51. data/lib/ably/rest/presence.rb +2 -2
  52. data/lib/ably/util/pub_sub.rb +1 -1
  53. data/lib/ably/util/safe_deferrable.rb +26 -0
  54. data/lib/ably/version.rb +2 -2
  55. data/spec/acceptance/realtime/auth_spec.rb +470 -111
  56. data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  57. data/spec/acceptance/realtime/channel_spec.rb +1017 -168
  58. data/spec/acceptance/realtime/client_spec.rb +6 -6
  59. data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  60. data/spec/acceptance/realtime/connection_spec.rb +424 -105
  61. data/spec/acceptance/realtime/message_spec.rb +52 -23
  62. data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  63. data/spec/acceptance/realtime/presence_spec.rb +1110 -96
  64. data/spec/acceptance/rest/auth_spec.rb +222 -59
  65. data/spec/acceptance/rest/base_spec.rb +1 -1
  66. data/spec/acceptance/rest/channel_spec.rb +1 -2
  67. data/spec/acceptance/rest/client_spec.rb +104 -48
  68. data/spec/acceptance/rest/message_spec.rb +42 -15
  69. data/spec/acceptance/rest/presence_spec.rb +4 -11
  70. data/spec/rspec_config.rb +2 -1
  71. data/spec/shared/client_initializer_behaviour.rb +2 -2
  72. data/spec/shared/safe_deferrable_behaviour.rb +6 -2
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/support/debug_failure_helper.rb +20 -4
  75. data/spec/support/event_machine_helper.rb +32 -1
  76. data/spec/unit/auth_spec.rb +4 -11
  77. data/spec/unit/logger_spec.rb +28 -2
  78. data/spec/unit/models/auth_details_spec.rb +49 -0
  79. data/spec/unit/models/channel_state_change_spec.rb +23 -3
  80. data/spec/unit/models/connection_details_spec.rb +12 -1
  81. data/spec/unit/models/connection_state_change_spec.rb +15 -4
  82. data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  83. data/spec/unit/models/message_spec.rb +153 -0
  84. data/spec/unit/models/presence_message_spec.rb +192 -0
  85. data/spec/unit/models/protocol_message_spec.rb +64 -6
  86. data/spec/unit/models/token_details_spec.rb +75 -0
  87. data/spec/unit/models/token_request_spec.rb +74 -0
  88. data/spec/unit/modules/async_wrapper_spec.rb +2 -1
  89. data/spec/unit/modules/enum_spec.rb +69 -0
  90. data/spec/unit/modules/event_emitter_spec.rb +149 -22
  91. data/spec/unit/modules/state_emitter_spec.rb +9 -3
  92. data/spec/unit/realtime/client_spec.rb +1 -1
  93. data/spec/unit/realtime/connection_spec.rb +8 -5
  94. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  95. data/spec/unit/realtime/presence_spec.rb +4 -3
  96. data/spec/unit/rest/client_spec.rb +1 -1
  97. data/spec/unit/util/crypto_spec.rb +3 -3
  98. metadata +22 -19
@@ -146,4 +146,79 @@ describe Ably::Models::TokenDetails do
146
146
  end
147
147
  end
148
148
  end
149
+
150
+ context 'from_json (#TD7)' do
151
+ let(:issued_time) { Time.now }
152
+ let(:expires_time) { Time.now + 24*60*60 }
153
+ let(:capabilities) { { '*' => ['publish'] } }
154
+
155
+ context 'with Ruby idiomatic Hash object' do
156
+ subject { Ably::Models::TokenDetails.from_json(token_details_object) }
157
+
158
+ let(:token_details_object) do
159
+ {
160
+ token: 'val1',
161
+ key_name: 'val2',
162
+ issued: issued_time.to_i * 1000,
163
+ expires: expires_time.to_i * 1000,
164
+ capability: capabilities,
165
+ client_id: 'val3'
166
+ }
167
+ end
168
+
169
+ it 'returns a valid TokenDetails object' do
170
+ expect(subject.token).to eql('val1')
171
+ expect(subject.key_name).to eql('val2')
172
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
173
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
174
+ expect(subject.capability).to eql(capabilities)
175
+ expect(subject.client_id).to eql('val3')
176
+ end
177
+ end
178
+
179
+ context 'with JSON-like object' do
180
+ subject { Ably::Models::TokenDetails.from_json(token_details_object) }
181
+
182
+ let(:token_details_object) do
183
+ {
184
+ 'keyName' => 'val2',
185
+ 'issued' => issued_time.to_i * 1000,
186
+ 'expires' => expires_time.to_i * 1000,
187
+ 'capability' => JSON.dump(capabilities),
188
+ 'clientId' => 'val3'
189
+ }
190
+ end
191
+
192
+ it 'returns a valid TokenDetails object' do
193
+ expect(subject.token).to be_nil
194
+ expect(subject.key_name).to eql('val2')
195
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
196
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
197
+ expect(subject.capability).to eql(capabilities)
198
+ expect(subject.client_id).to eql('val3')
199
+ end
200
+ end
201
+
202
+ context 'with JSON string' do
203
+ subject { Ably::Models::TokenDetails.from_json(JSON.dump(token_details_object)) }
204
+
205
+ let(:token_details_object) do
206
+ {
207
+ 'keyName' => 'val2',
208
+ 'issued' => issued_time.to_i * 1000,
209
+ 'expires' => expires_time.to_i * 1000,
210
+ 'clientId' => 'val3'
211
+ }
212
+ end
213
+
214
+ it 'returns a valid TokenDetails object' do
215
+ expect(subject.token).to be_nil
216
+ expect(subject.key_name).to eql('val2')
217
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
218
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
219
+ expect(subject.capability).to be_nil
220
+ expect(subject.client_id).to eql('val3')
221
+ end
222
+ end
223
+ end
149
224
  end
@@ -107,4 +107,78 @@ describe Ably::Models::TokenRequest do
107
107
  end
108
108
  end
109
109
  end
110
+
111
+ context 'from_json (#TE6)' do
112
+ let(:timestamp) { Time.now }
113
+ let(:capabilities) { { '*' => ['publish'] } }
114
+ let(:ttl_seconds) { 60 * 1000 }
115
+
116
+ context 'with Ruby idiomatic Hash object' do
117
+ subject { Ably::Models::TokenRequest.from_json(token_request_object) }
118
+
119
+ let(:token_request_object) do
120
+ {
121
+ nonce: 'val1',
122
+ key_name: 'val2',
123
+ ttl: ttl_seconds * 1000,
124
+ timestamp: timestamp.to_i * 1000,
125
+ capability: capabilities,
126
+ client_id: 'val3'
127
+ }
128
+ end
129
+
130
+ it 'returns a valid TokenRequest object' do
131
+ expect(subject.nonce).to eql('val1')
132
+ expect(subject.key_name).to eql('val2')
133
+ expect(subject.timestamp.to_f).to be_within(1).of(timestamp.to_f)
134
+ expect(subject.ttl).to eql(ttl_seconds)
135
+ expect(subject.capability).to eql(capabilities)
136
+ expect(subject.client_id).to eql('val3')
137
+ end
138
+ end
139
+
140
+ context 'with JSON-like object' do
141
+ subject { Ably::Models::TokenRequest.from_json(token_request_object) }
142
+
143
+ let(:token_request_object) do
144
+ {
145
+ 'keyName' => 'val2',
146
+ 'ttl' => ttl_seconds * 1000,
147
+ 'timestamp' => timestamp.to_i * 1000,
148
+ 'clientId' => 'val3'
149
+ }
150
+ end
151
+
152
+ it 'returns a valid TokenRequest object' do
153
+ expect { subject.nonce }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
154
+ expect(subject.key_name).to eql('val2')
155
+ expect(subject.timestamp.to_f).to be_within(1).of(timestamp.to_f)
156
+ expect(subject.ttl).to eql(ttl_seconds)
157
+ expect { subject.capability }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
158
+ expect(subject.client_id).to eql('val3')
159
+ end
160
+ end
161
+
162
+ context 'with JSON string' do
163
+ subject { Ably::Models::TokenRequest.from_json(JSON.dump(token_request_object)) }
164
+
165
+ let(:token_request_object) do
166
+ {
167
+ 'nonce' => 'val1',
168
+ 'ttl' => ttl_seconds * 1000,
169
+ 'capability' => JSON.dump(capabilities),
170
+ 'clientId' => 'val3'
171
+ }
172
+ end
173
+
174
+ it 'returns a valid TokenRequest object' do
175
+ expect(subject.nonce).to eql('val1')
176
+ expect { subject.key_name }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
177
+ expect { subject.timestamp }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
178
+ expect(subject.ttl).to eql(ttl_seconds)
179
+ expect(subject.capability).to eql(capabilities)
180
+ expect(subject.client_id).to eql('val3')
181
+ end
182
+ end
183
+ end
110
184
  end
@@ -59,7 +59,8 @@ describe Ably::Modules::AsyncWrapper, :api_private do
59
59
  subject.operation do |result|
60
60
  raise 'Intentional exception'
61
61
  end
62
- expect(subject.logger).to receive(:error).with(/Intentional exception/) do
62
+ expect(subject.logger).to receive(:error) do |*args, &block|
63
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
63
64
  stop_reactor
64
65
  end
65
66
  end
@@ -41,6 +41,41 @@ describe Ably::Modules::Enum, :api_private do
41
41
  end
42
42
  end
43
43
 
44
+ context 'using include? to compare Enum values' do
45
+ subject { enum }
46
+
47
+ it 'allows same type comparison' do
48
+ expect([subject.ValueZero].include?(subject.ValueZero)).to eql(true)
49
+ end
50
+
51
+ it 'allows different type comparison 1' do
52
+ expect([subject.ValueZero].include?(:value_zero)).to eql(true)
53
+ end
54
+
55
+ it 'allows different type comparison 2' do
56
+ skip 'Unless we monkeypath Symbols, the == operator is never invoked'
57
+ expect([:value_zero].include?(subject.ValueZero)).to eql(true)
58
+ end
59
+
60
+ context '#match_any? replacement for include?' do
61
+ it 'matches any value in the arguments provided' do
62
+ expect(subject.ValueZero.match_any?(:value_foo, :value_zero)).to eql(true)
63
+ end
64
+
65
+ it 'returns false if there are no matches in any value in the arguments provided' do
66
+ expect(subject.ValueZero.match_any?(:value_x, :value_y)).to eql(false)
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'class method #to_sym_arr' do
72
+ subject { enum }
73
+
74
+ it 'returns all keys as symbols' do
75
+ expect(enum.to_sym_arr).to contain_exactly(:value_zero, :value_1, :value_snake_case_2, :sentence_case)
76
+ end
77
+ end
78
+
44
79
  context 'defined Enum from Array class' do
45
80
  subject { enum }
46
81
 
@@ -212,6 +247,40 @@ describe Ably::Modules::Enum, :api_private do
212
247
  end
213
248
  end
214
249
 
250
+ context 'two similar Enums with shared symbol values' do
251
+ class ExampleEnumOne
252
+ extend Ably::Modules::Enum
253
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', :pear, :orange, :litchi, :apple)
254
+ end
255
+
256
+ class ExampleEnumTwo
257
+ extend Ably::Modules::Enum
258
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', :pear, :grape, :apple)
259
+ end
260
+
261
+ let(:enum_one) { ExampleEnumOne::ENUMEXAMPLE }
262
+ let(:enum_two) { ExampleEnumTwo::ENUMEXAMPLE }
263
+
264
+ it 'provides compatability for the equivalent symbol values' do
265
+ expect(enum_one.Pear).to eq(enum_two.Pear)
266
+ expect(enum_two.Pear).to eq(enum_one.Pear)
267
+ expect(enum_one.Apple).to eq(enum_two.Apple)
268
+ expect(enum_two.Apple).to eq(enum_one.Apple)
269
+ end
270
+
271
+ it 'does not consider different symbol values the same' do
272
+ expect(enum_one.Orange).to_not eq(enum_two.Grape)
273
+ end
274
+
275
+ it 'matches symbols when used with a converter method' do
276
+ expect(ExampleEnumOne::ENUMEXAMPLE(enum_two.Pear)).to eq(:pear)
277
+ end
278
+
279
+ it 'fails to match when using an incompatible method with a converter method' do
280
+ expect { ExampleEnumOne::ENUMEXAMPLE(enum_two.Grape) }.to raise_error KeyError
281
+ end
282
+ end
283
+
215
284
  context 'Enum instance' do
216
285
  context '#==' do
217
286
  subject { enum.get(:value_snake_case_2) }
@@ -83,13 +83,13 @@ describe Ably::Modules::EventEmitter do
83
83
  allow(obj).to receive(:received_message)
84
84
  end
85
85
 
86
- it 'is unaffected and processes the prior event callbacks once' do
86
+ it 'is unaffected and processes the prior event callbacks once (#RTE6b)' do
87
87
  expect(obj).to receive(:received_message).with(msg).twice
88
88
  expect(obj).to_not receive(:received_message_from_new_callbacks).with(msg)
89
89
  subject.emit :message, msg
90
90
  end
91
91
 
92
- it 'adds them for the next emitted event' do
92
+ it 'adds them for the next emitted event (#RTE6b)' do
93
93
  expect(obj).to receive(:received_message_from_new_callbacks).with(msg).twice
94
94
 
95
95
  # New callbacks are added in this emit
@@ -110,12 +110,12 @@ describe Ably::Modules::EventEmitter do
110
110
  end
111
111
  end
112
112
 
113
- it 'is unaffected and processes the prior event callbacks once' do
113
+ it 'is unaffected and processes the prior event callbacks once (#RTE6b)' do
114
114
  expect(obj).to receive(:received_message).with(msg).twice
115
115
  subject.emit :message, msg
116
116
  end
117
117
 
118
- it 'removes them for the next emitted event' do
118
+ it 'removes them for the next emitted event (#RTE6b)' do
119
119
  expect(obj).to receive(:received_message).with(msg).twice
120
120
 
121
121
  # Callbacks are removed in this emit
@@ -127,7 +127,7 @@ describe Ably::Modules::EventEmitter do
127
127
  end
128
128
  end
129
129
 
130
- context '#on' do
130
+ context '#on (#RTE3)' do
131
131
  context 'with event specified' do
132
132
  it 'calls the block every time an event is emitted only' do
133
133
  block_called = 0
@@ -137,7 +137,9 @@ describe Ably::Modules::EventEmitter do
137
137
  end
138
138
 
139
139
  it 'catches exceptions in the provided block, logs the error and continues' do
140
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
140
+ expect(subject.logger).to receive(:error) do |*args, &block|
141
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
142
+ end
141
143
  subject.on(:event) { raise 'Intentional exception' }
142
144
  subject.emit :event
143
145
  end
@@ -152,7 +154,9 @@ describe Ably::Modules::EventEmitter do
152
154
  end
153
155
 
154
156
  it 'catches exceptions in the provided block, logs the error and continues' do
155
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
157
+ expect(subject.logger).to receive(:error) do |*args, &block|
158
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
159
+ end
156
160
  subject.on { raise 'Intentional exception' }
157
161
  subject.emit :event
158
162
  end
@@ -173,7 +177,7 @@ describe Ably::Modules::EventEmitter do
173
177
  end
174
178
  end
175
179
 
176
- context '#once' do
180
+ context '#once (#RTE4)' do
177
181
  context 'with event specified' do
178
182
  it 'calls the block the first time an event is emitted only' do
179
183
  block_called = 0
@@ -191,7 +195,9 @@ describe Ably::Modules::EventEmitter do
191
195
  end
192
196
 
193
197
  it 'catches exceptions in the provided block, logs the error and continues' do
194
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
198
+ expect(subject.logger).to receive(:error) do |*args, &block|
199
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
200
+ end
195
201
  subject.once(:event) { raise 'Intentional exception' }
196
202
  subject.emit :event
197
203
  end
@@ -214,7 +220,9 @@ describe Ably::Modules::EventEmitter do
214
220
  end
215
221
 
216
222
  it 'catches exceptions in the provided block, logs the error and continues' do
217
- expect(subject.logger).to receive(:error).with(/Intentional exception/)
223
+ expect(subject.logger).to receive(:error) do |*args, &block|
224
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Intentional exception/)
225
+ end
218
226
  subject.once { raise 'Intentional exception' }
219
227
  subject.emit :event
220
228
  end
@@ -248,12 +256,12 @@ describe Ably::Modules::EventEmitter do
248
256
  end
249
257
 
250
258
  context 'with event names as arguments' do
251
- it 'deletes matching callbacks' do
259
+ it 'deletes matching callbacks when a block is provided' do
252
260
  expect(obj).to_not receive(:received_message).with(msg)
253
261
  subject.off(:message, &callback)
254
262
  end
255
263
 
256
- it 'deletes all callbacks if not block given' do
264
+ it 'deletes all matching callbacks when a block is not provided' do
257
265
  expect(obj).to_not receive(:received_message).with(msg)
258
266
  subject.off(:message)
259
267
  end
@@ -277,18 +285,137 @@ describe Ably::Modules::EventEmitter do
277
285
  end
278
286
  end
279
287
 
280
- it 'removes handler added with no event specified' do
281
- subject.on(&callback)
282
- expect(obj).to_not receive(:received_message).with(msg)
283
- subject.off(&callback)
284
- subject.emit :message, msg
288
+ context 'when on callback is configured for all events' do
289
+ before do
290
+ subject.on(&callback)
291
+ end
292
+
293
+ after do
294
+ subject.emit :message, msg
295
+ end
296
+
297
+ context 'with event names as arguments' do
298
+ it 'does not remove the all events callback when a block is provided' do
299
+ expect(obj).to receive(:received_message).with(msg)
300
+ subject.off(:message, &callback)
301
+ end
302
+
303
+ it 'does not remove the all events callback when a block is not provided' do
304
+ expect(obj).to receive(:received_message).with(msg)
305
+ subject.off(:message)
306
+ end
307
+
308
+ it 'does not remove the all events callback when the block does not match' do
309
+ expect(obj).to receive(:received_message).with(msg)
310
+ subject.off(:message) { true }
311
+ end
312
+ end
313
+
314
+ context 'without any event names' do
315
+ it 'deletes all matching callbacks' do
316
+ expect(obj).to_not receive(:received_message).with(msg)
317
+ subject.off(&callback)
318
+ end
319
+
320
+ it 'deletes all callbacks if not block given' do
321
+ expect(obj).to_not receive(:received_message).with(msg)
322
+ subject.off
323
+ end
324
+ end
285
325
  end
286
326
 
287
- it 'leaves handler when event specified' do
288
- subject.on(&callback)
289
- expect(obj).to receive(:received_message).with(msg)
290
- subject.off(:foo, &callback)
291
- subject.emit :message, msg
327
+ context 'with unsafe_on subscribers' do
328
+ before do
329
+ subject.unsafe_on(&callback)
330
+ end
331
+
332
+ after do
333
+ subject.emit :message, msg
334
+ end
335
+
336
+ it 'does not deregister them' do
337
+ expect(obj).to receive(:received_message).with(msg)
338
+ subject.off
339
+ end
340
+ end
341
+
342
+ context 'with unsafe_once subscribers' do
343
+ before do
344
+ subject.unsafe_once(&callback)
345
+ end
346
+
347
+ after do
348
+ subject.emit :message, msg
349
+ end
350
+
351
+ it 'does not deregister them' do
352
+ expect(obj).to receive(:received_message).with(msg)
353
+ subject.off
354
+ end
355
+ end
356
+ end
357
+
358
+ context '#unsafe_off' do
359
+ let(:callback) { Proc.new { |msg| obj.received_message msg } }
360
+
361
+ context 'with unsafe_on subscribers' do
362
+ before do
363
+ subject.unsafe_on(&callback)
364
+ end
365
+
366
+ after do
367
+ subject.emit :message, msg
368
+ end
369
+
370
+ it 'deregisters them' do
371
+ expect(obj).to_not receive(:received_message).with(msg)
372
+ subject.unsafe_off
373
+ end
374
+ end
375
+
376
+ context 'with unsafe_once subscribers' do
377
+ before do
378
+ subject.unsafe_once(&callback)
379
+ end
380
+
381
+ after do
382
+ subject.emit :message, msg
383
+ end
384
+
385
+ it 'deregister them' do
386
+ expect(obj).to_not receive(:received_message).with(msg)
387
+ subject.unsafe_off
388
+ end
389
+ end
390
+
391
+ context 'with on subscribers' do
392
+ before do
393
+ subject.on(&callback)
394
+ end
395
+
396
+ after do
397
+ subject.emit :message, msg
398
+ end
399
+
400
+ it 'does not deregister them' do
401
+ expect(obj).to receive(:received_message).with(msg)
402
+ subject.unsafe_off
403
+ end
404
+ end
405
+
406
+ context 'with once subscribers' do
407
+ before do
408
+ subject.once(&callback)
409
+ end
410
+
411
+ after do
412
+ subject.emit :message, msg
413
+ end
414
+
415
+ it 'does not deregister them' do
416
+ expect(obj).to receive(:received_message).with(msg)
417
+ subject.unsafe_off
418
+ end
292
419
  end
293
420
  end
294
421
  end