brainguy 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.erb +345 -0
- data/README.markdown +579 -0
- data/Rakefile +21 -0
- data/brainguy.gemspec +28 -0
- data/examples/include_manifestly_observable.rb +22 -0
- data/examples/include_observable.rb +18 -0
- data/examples/include_observer.rb +36 -0
- data/examples/manual_observable.rb +22 -0
- data/examples/open_observer.rb +31 -0
- data/examples/proc_observer.rb +10 -0
- data/examples/scoped_subscription.rb +39 -0
- data/examples/synopsis.rb +56 -0
- data/lib/brainguy.rb +34 -0
- data/lib/brainguy/basic_notifier.rb +19 -0
- data/lib/brainguy/emitter.rb +110 -0
- data/lib/brainguy/error_collecting_notifier.rb +26 -0
- data/lib/brainguy/error_handling_notifier.rb +63 -0
- data/lib/brainguy/event.rb +13 -0
- data/lib/brainguy/fluent_emitter.rb +30 -0
- data/lib/brainguy/full_subscription.rb +8 -0
- data/lib/brainguy/idempotent_emitter.rb +40 -0
- data/lib/brainguy/manifest_emitter.rb +78 -0
- data/lib/brainguy/manifestly_observable.rb +62 -0
- data/lib/brainguy/observable.rb +33 -0
- data/lib/brainguy/observer.rb +71 -0
- data/lib/brainguy/open_observer.rb +65 -0
- data/lib/brainguy/single_event_subscription.rb +31 -0
- data/lib/brainguy/subscription.rb +59 -0
- data/lib/brainguy/subscription_scope.rb +62 -0
- data/lib/brainguy/version.rb +4 -0
- data/scripts/benchmark_listener_dispatch.rb +222 -0
- data/spec/brainguy/emitter_spec.rb +25 -0
- data/spec/brainguy/error_collecting_notifier_spec.rb +19 -0
- data/spec/brainguy/error_handling_notifier_spec.rb +63 -0
- data/spec/brainguy/manifest_emitter_spec.rb +68 -0
- data/spec/brainguy/manifestly_observable_spec.rb +43 -0
- data/spec/brainguy/observable_spec.rb +9 -0
- data/spec/brainguy/observer_spec.rb +72 -0
- data/spec/brainguy/open_observer_spec.rb +57 -0
- data/spec/brainguy/single_event_subscription_spec.rb +16 -0
- data/spec/brainguy/subscription_scope_spec.rb +72 -0
- data/spec/brainguy/subscription_spec.rb +46 -0
- data/spec/features/basics_spec.rb +153 -0
- data/spec/features/idempotent_events_spec.rb +69 -0
- data/spec/features/method_scoped_events_spec.rb +90 -0
- data/spec/support/shared_examples_for_eventful_modules.rb +36 -0
- metadata +196 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/manifest_emitter"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
RSpec.describe ManifestEmitter do
|
6
|
+
it "starts with an empty set of known events" do
|
7
|
+
set = ManifestEmitter.new(instance_double(Emitter))
|
8
|
+
expect(set.known_types).to be_empty
|
9
|
+
end
|
10
|
+
|
11
|
+
it "warns when a handler is added for an unknown event" do
|
12
|
+
set = ManifestEmitter.new(
|
13
|
+
instance_double(Emitter, on: nil))
|
14
|
+
expect { set.on(:foo) {} }
|
15
|
+
.to output(/unknown event type 'foo'/i).to_stderr
|
16
|
+
end
|
17
|
+
|
18
|
+
it "warns when an unknown event is emitted" do
|
19
|
+
set = ManifestEmitter.new(
|
20
|
+
instance_double(Emitter, emit: nil))
|
21
|
+
expect { set.emit(:foo) {} }
|
22
|
+
.to output(/unknown event type 'foo'/i).to_stderr
|
23
|
+
end
|
24
|
+
|
25
|
+
it "does not warn when a handler is added for an known event" do
|
26
|
+
set = ManifestEmitter.new(
|
27
|
+
instance_double(Emitter, on: nil))
|
28
|
+
set.known_types << :foo
|
29
|
+
expect { set.on(:foo) {} }
|
30
|
+
.not_to output.to_stderr
|
31
|
+
end
|
32
|
+
|
33
|
+
it "does not warn when an known event is emitted" do
|
34
|
+
set = ManifestEmitter.new(
|
35
|
+
instance_double(Emitter, emit: nil))
|
36
|
+
set.known_types << :foo
|
37
|
+
expect { set.emit(:foo) {} }
|
38
|
+
.not_to output.to_stderr
|
39
|
+
end
|
40
|
+
|
41
|
+
context "configured to raise errors" do
|
42
|
+
it "fails when a handler is added for an unknown event" do
|
43
|
+
set = ManifestEmitter.new(
|
44
|
+
instance_double(Emitter, on: nil),
|
45
|
+
policy: :raise_error)
|
46
|
+
expect { set.on(:foo) {} }
|
47
|
+
.to raise_error(UnknownEvent, /unknown event type 'foo'/i)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "fails when an unknown event is emitted" do
|
51
|
+
set = ManifestEmitter.new(
|
52
|
+
instance_double(Emitter, emit: nil),
|
53
|
+
policy: :raise_error)
|
54
|
+
expect { set.emit(:foo) {} }
|
55
|
+
.to raise_error(UnknownEvent, /unknown event type 'foo'/i)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "reports exception from the send point" do
|
59
|
+
set = ManifestEmitter.new(
|
60
|
+
instance_double(Emitter, emit: nil),
|
61
|
+
policy: :raise_error)
|
62
|
+
error = set.emit(:foo) rescue $!; line = __LINE__
|
63
|
+
file = File.basename(__FILE__)
|
64
|
+
expect(error.backtrace[0]).to include("#{file}:#{line}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/manifestly_observable"
|
3
|
+
require "support/shared_examples_for_eventful_modules"
|
4
|
+
|
5
|
+
module Brainguy
|
6
|
+
RSpec.describe ManifestlyObservable do
|
7
|
+
include_examples "an eventful module",
|
8
|
+
ManifestlyObservable.new(:heat, :drip, :done)
|
9
|
+
|
10
|
+
it "adds a ManifestEmitter to the class" do
|
11
|
+
klass = Class.new do
|
12
|
+
include ManifestlyObservable.new(:red, :green)
|
13
|
+
end
|
14
|
+
obj = klass.new
|
15
|
+
expect(obj.events).to be_a(ManifestEmitter)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "adds the specified event names to the known list" do
|
19
|
+
klass = Class.new do
|
20
|
+
include ManifestlyObservable.new(:red, :green)
|
21
|
+
end
|
22
|
+
obj = klass.new
|
23
|
+
expect(obj.events.known_types).to eq([:red, :green])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "inherits event names" do
|
27
|
+
parent = Class.new do
|
28
|
+
include ManifestlyObservable.new(:red, :green)
|
29
|
+
end
|
30
|
+
child = Class.new(parent) do
|
31
|
+
include ManifestlyObservable.new(:yellow)
|
32
|
+
end
|
33
|
+
obj = child.new
|
34
|
+
expect(obj.events.known_types).to eq([:red, :green, :yellow])
|
35
|
+
end
|
36
|
+
|
37
|
+
it "stringifies meaningfully" do
|
38
|
+
mod = ManifestlyObservable.new(:red, :green)
|
39
|
+
expect(mod.to_s).to eq("ManifestlyObservable(:red, :green)")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/observer"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
RSpec.describe Observer do
|
6
|
+
class AccountListener
|
7
|
+
def on_open(event)
|
8
|
+
end
|
9
|
+
|
10
|
+
include Observer
|
11
|
+
|
12
|
+
def on_deposit(event)
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_withdrawal(event)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "routes events to their appropriate handler methods" do
|
20
|
+
listener = AccountListener.new
|
21
|
+
allow(listener).to receive(:on_deposit)
|
22
|
+
allow(listener).to receive(:on_withdrawal)
|
23
|
+
|
24
|
+
listener.call(e1 = Event[:deposit])
|
25
|
+
expect(listener).to have_received(:on_deposit).with(e1)
|
26
|
+
|
27
|
+
listener.call(e2 = Event[:withdrawal])
|
28
|
+
expect(listener).to have_received(:on_withdrawal).with(e2)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "picks up handler methods defined before module inclusion" do
|
32
|
+
klass = Class.new do
|
33
|
+
def on_open(event)
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
include Observer
|
38
|
+
end
|
39
|
+
listener = klass.new
|
40
|
+
allow(listener).to receive(:on_open)
|
41
|
+
listener.call(e2 = Event[:open])
|
42
|
+
expect(listener).to have_received(:on_open).with(e2)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "ignores events which lack handler methods" do
|
46
|
+
listener = AccountListener.new
|
47
|
+
expect { listener.call(Event[:not_defined]) }.to_not raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
it "doesn't blow up on a handler-less class" do
|
51
|
+
expect do
|
52
|
+
Class.new do
|
53
|
+
include Observer
|
54
|
+
end
|
55
|
+
end.to_not raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "passes unknown events to a catch-all method" do
|
59
|
+
events = []
|
60
|
+
klass = Class.new do
|
61
|
+
include Observer
|
62
|
+
define_method :event_handler_missing do |event|
|
63
|
+
events << event
|
64
|
+
end
|
65
|
+
end
|
66
|
+
listener = klass.new
|
67
|
+
listener.call(unknown_event = Event[:unknown_event])
|
68
|
+
expect(events).to eq([unknown_event])
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/open_observer"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
RSpec.describe OpenObserver do
|
6
|
+
it "can be constructed from a hash" do
|
7
|
+
probe = spy("probe")
|
8
|
+
ol = OpenObserver.new(foo: ->(e) { probe.handle_foo(e) },
|
9
|
+
bar: ->(e) { probe.handle_bar(e) })
|
10
|
+
source = double("source")
|
11
|
+
ol.call(e1 = Event[:foo])
|
12
|
+
ol.call(e2 = Event[:bar])
|
13
|
+
expect(probe).to have_received(:handle_foo).with(e1)
|
14
|
+
expect(probe).to have_received(:handle_bar).with(e2)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can be constructed using on_* methods" do
|
18
|
+
probe = spy("probe")
|
19
|
+
ol = OpenObserver.new
|
20
|
+
ol.on_foo do |e|
|
21
|
+
probe.handle_foo(e)
|
22
|
+
end
|
23
|
+
.on_bar do |e|
|
24
|
+
probe.handle_bar(e)
|
25
|
+
end
|
26
|
+
source = double("source")
|
27
|
+
ol.call(e1 = Event[:foo])
|
28
|
+
ol.call(e2 = Event[:bar])
|
29
|
+
expect(probe).to have_received(:handle_foo).with(e1)
|
30
|
+
expect(probe).to have_received(:handle_bar).with(e2)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "yields self on init" do
|
34
|
+
probe = spy("probe")
|
35
|
+
ol = OpenObserver.new do |l|
|
36
|
+
l.on_foo do |e|
|
37
|
+
probe.handle_foo(e)
|
38
|
+
end
|
39
|
+
l.on_bar do |e|
|
40
|
+
probe.handle_bar(e)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
source = double("source")
|
44
|
+
ol.call(e1 = Event[:foo])
|
45
|
+
ol.call(e2 = Event[:bar])
|
46
|
+
expect(probe).to have_received(:handle_foo).with(e1)
|
47
|
+
expect(probe).to have_received(:handle_bar).with(e2)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "has correct #respond_to? behavior" do
|
51
|
+
ol = OpenObserver.new
|
52
|
+
expect(ol).to respond_to(:on_blub)
|
53
|
+
expect(ol).to_not respond_to(:blub)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/single_event_subscription"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
RSpec.describe SingleEventSubscription do
|
6
|
+
it "is not equal to another subscription with a different event" do
|
7
|
+
owner = double("owner")
|
8
|
+
listener = double("listener")
|
9
|
+
sub1 = SingleEventSubscription.new(owner, listener, double)
|
10
|
+
sub2 = SingleEventSubscription.new(owner, listener, double)
|
11
|
+
expect(sub1).not_to eq(sub2)
|
12
|
+
expect(sub1.hash).not_to eq(sub2.hash)
|
13
|
+
expect(sub1).not_to eql(sub2)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/subscription_scope"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
RSpec.describe SubscriptionScope do
|
6
|
+
it "proxies subscriptions to an underlying subscription set" do
|
7
|
+
scope = SubscriptionScope.new(set = instance_double(Emitter))
|
8
|
+
allow(set).to receive(:on).and_return(sub1 = double("sub1"))
|
9
|
+
allow(set).to receive(:attach).and_return(sub2 = double("sub2"))
|
10
|
+
expect(scope.on(:foo)).to be(sub1)
|
11
|
+
expect(scope.attach(arg2 = double)).to be(sub2)
|
12
|
+
expect(set).to have_received(:on).with(:foo)
|
13
|
+
expect(set).to have_received(:attach).with(arg2)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "removes subscriptions on close" do
|
17
|
+
scope = SubscriptionScope.new(set = instance_double(Emitter))
|
18
|
+
allow(set).to receive(:on)
|
19
|
+
.and_return(sub1 = instance_spy(Subscription))
|
20
|
+
allow(set).to receive(:attach)
|
21
|
+
.and_return(sub2 = instance_spy(Subscription))
|
22
|
+
scope.on(:foo)
|
23
|
+
scope.attach(double)
|
24
|
+
scope.close
|
25
|
+
expect(sub1).to have_received(:cancel)
|
26
|
+
expect(sub2).to have_received(:cancel)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "removes subscriptions only once" do
|
30
|
+
scope = SubscriptionScope.new(set = instance_double(Emitter))
|
31
|
+
allow(set).to receive(:on)
|
32
|
+
.and_return(sub1 = instance_spy(Subscription))
|
33
|
+
scope.on(:foo)
|
34
|
+
scope.close
|
35
|
+
scope.close
|
36
|
+
expect(sub1).to have_received(:cancel).once
|
37
|
+
end
|
38
|
+
|
39
|
+
it "supports a block form" do
|
40
|
+
set = instance_double(Emitter)
|
41
|
+
allow(set).to receive(:on)
|
42
|
+
.and_return(sub1 = instance_spy(Subscription))
|
43
|
+
allow(set).to receive(:attach)
|
44
|
+
.and_return(sub2 = instance_spy(Subscription))
|
45
|
+
|
46
|
+
SubscriptionScope.open(set) do |scope|
|
47
|
+
scope.on(:foo)
|
48
|
+
scope.attach(double)
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(sub1).to have_received(:cancel)
|
52
|
+
expect(sub2).to have_received(:cancel)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "ensures subscriptions are cancelled despite errors in block" do
|
56
|
+
set = instance_double(Emitter)
|
57
|
+
allow(set).to receive(:on)
|
58
|
+
.and_return(sub1 = instance_spy(Subscription))
|
59
|
+
allow(set).to receive(:attach)
|
60
|
+
.and_return(sub2 = instance_spy(Subscription))
|
61
|
+
|
62
|
+
SubscriptionScope.open(set) do |scope|
|
63
|
+
scope.on(:foo)
|
64
|
+
scope.attach(double)
|
65
|
+
fail "Whoopsie"
|
66
|
+
end rescue nil
|
67
|
+
|
68
|
+
expect(sub1).to have_received(:cancel)
|
69
|
+
expect(sub2).to have_received(:cancel)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy/subscription"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
RSpec.describe Subscription do
|
6
|
+
|
7
|
+
it "is equal to another subscription with the same owner and listener" do
|
8
|
+
listener = double("listener")
|
9
|
+
owner = double("owner")
|
10
|
+
sub1 = Subscription.new(owner, listener)
|
11
|
+
sub2 = Subscription.new(owner, listener)
|
12
|
+
expect(sub1).to eq(sub2)
|
13
|
+
expect(sub1.hash).to eq(sub2.hash)
|
14
|
+
expect(sub1).to eql(sub2)
|
15
|
+
|
16
|
+
set = Set.new([sub1])
|
17
|
+
expect(set.add?(sub2)).to be_nil
|
18
|
+
expect(set.size).to eq(1)
|
19
|
+
set.delete(sub2)
|
20
|
+
expect(set.size).to eq(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "is not equal to another subscription with a different owner" do
|
24
|
+
listener = double("listener")
|
25
|
+
sub1 = Subscription.new(double, listener)
|
26
|
+
sub2 = Subscription.new(double, listener)
|
27
|
+
expect(sub1).not_to eq(sub2)
|
28
|
+
expect(sub1.hash).not_to eq(sub2.hash)
|
29
|
+
expect(sub1).not_to eql(sub2)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is not equal to another subscription with a different listener" do
|
33
|
+
owner = double("owner")
|
34
|
+
sub1 = Subscription.new(owner, double)
|
35
|
+
sub2 = Subscription.new(owner, double)
|
36
|
+
expect(sub1).not_to eq(sub2)
|
37
|
+
expect(sub1.hash).not_to eq(sub2.hash)
|
38
|
+
expect(sub1).not_to eql(sub2)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "is frozen" do
|
42
|
+
s = Subscription.new(double, double)
|
43
|
+
expect(s).to be_frozen
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "brainguy"
|
3
|
+
|
4
|
+
module Brainguy
|
5
|
+
class AuctionBid
|
6
|
+
attr_reader :events
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@events = Brainguy::Emitter.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def reject_bid
|
13
|
+
events.emit(:rejected)
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_counter_offer
|
17
|
+
events.emit(:rejected, counter_amount: 101, valid_for_minutes: 60)
|
18
|
+
end
|
19
|
+
|
20
|
+
def accept_bid
|
21
|
+
events.emit(:accepted)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
RSpec.describe Brainguy do
|
26
|
+
it "enables objects to publish simple events" do
|
27
|
+
mike = spy("mike")
|
28
|
+
bid = AuctionBid.new
|
29
|
+
bid.events.on(:rejected) do
|
30
|
+
mike.handle_rejection
|
31
|
+
end
|
32
|
+
bid.reject_bid
|
33
|
+
expect(mike).to have_received(:handle_rejection)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "allows multiple listeners to attach" do
|
37
|
+
mike = spy("mike")
|
38
|
+
crow = spy("crow")
|
39
|
+
bid = AuctionBid.new
|
40
|
+
bid.events.on(:rejected) do
|
41
|
+
mike.handle_rejection
|
42
|
+
end
|
43
|
+
bid.events.on(:rejected) do
|
44
|
+
crow.handle_rejection
|
45
|
+
end
|
46
|
+
bid.reject_bid
|
47
|
+
expect(mike).to have_received(:handle_rejection)
|
48
|
+
expect(crow).to have_received(:handle_rejection)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "allows multiple events to be emitted" do
|
52
|
+
crow = spy("crow")
|
53
|
+
bid = AuctionBid.new
|
54
|
+
bid.events.on(:rejected) do
|
55
|
+
crow.handle_rejection
|
56
|
+
end
|
57
|
+
bid.events.on(:accepted) do
|
58
|
+
crow.handle_acceptance
|
59
|
+
end
|
60
|
+
bid.reject_bid
|
61
|
+
expect(crow).to have_received(:handle_rejection)
|
62
|
+
expect(crow).to_not have_received(:handle_acceptance)
|
63
|
+
bid.accept_bid
|
64
|
+
expect(crow).to have_received(:handle_acceptance)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "allows for zero subscribers" do
|
68
|
+
bid = AuctionBid.new
|
69
|
+
expect { bid.reject_bid }.to_not raise_error
|
70
|
+
end
|
71
|
+
|
72
|
+
it "passes extra event args on to the handler block" do
|
73
|
+
bid = AuctionBid.new
|
74
|
+
expect do |b|
|
75
|
+
bid.events.on(:rejected, &b)
|
76
|
+
bid.make_counter_offer
|
77
|
+
end.to yield_with_args({counter_amount: 101,
|
78
|
+
valid_for_minutes: 60})
|
79
|
+
end
|
80
|
+
|
81
|
+
it "allows a subscription to be removed" do
|
82
|
+
bid = AuctionBid.new
|
83
|
+
expect do |b|
|
84
|
+
subscription = bid.events.on(:rejected, &b)
|
85
|
+
subscription.cancel
|
86
|
+
bid.reject_bid
|
87
|
+
end.to_not yield_control
|
88
|
+
end
|
89
|
+
|
90
|
+
it "allows an object to listen to all events" do
|
91
|
+
bid = AuctionBid.new
|
92
|
+
listener = spy("listener")
|
93
|
+
bid.events.attach(listener)
|
94
|
+
|
95
|
+
bid.reject_bid
|
96
|
+
expect(listener).to have_received(:call).with(Event[:rejected, bid])
|
97
|
+
|
98
|
+
bid.accept_bid
|
99
|
+
expect(listener).to have_received(:call).with(Event[:accepted, bid])
|
100
|
+
end
|
101
|
+
|
102
|
+
it "allows a listener to be unsubscribed" do
|
103
|
+
bid = AuctionBid.new
|
104
|
+
listener = spy("listener")
|
105
|
+
subscription = bid.events.attach(listener)
|
106
|
+
|
107
|
+
bid.reject_bid
|
108
|
+
expect(listener).to have_received(:call).with(Event[:rejected, bid])
|
109
|
+
|
110
|
+
subscription.cancel
|
111
|
+
|
112
|
+
bid.accept_bid
|
113
|
+
expect(listener).not_to have_received(:call).with(Event[:accepted, bid])
|
114
|
+
end
|
115
|
+
|
116
|
+
it "allows a listener to be unsubscribed by identity" do
|
117
|
+
bid = AuctionBid.new
|
118
|
+
listener = spy("listener")
|
119
|
+
bid.events.attach(listener)
|
120
|
+
|
121
|
+
bid.reject_bid
|
122
|
+
expect(listener).to have_received(:call).with(Event[:rejected, bid])
|
123
|
+
|
124
|
+
bid.events.detach(listener)
|
125
|
+
expect(bid.events).to be_empty
|
126
|
+
|
127
|
+
bid.accept_bid
|
128
|
+
expect(listener).not_to have_received(:call).with(Event[:accepted, bid])
|
129
|
+
end
|
130
|
+
|
131
|
+
it "does not allow the same listener to be subscribed twice" do
|
132
|
+
bid = AuctionBid.new
|
133
|
+
listener = spy("listener")
|
134
|
+
bid.events.attach(listener)
|
135
|
+
bid.events.attach(listener)
|
136
|
+
bid.reject_bid
|
137
|
+
expect(listener).to have_received(:call).once
|
138
|
+
end
|
139
|
+
|
140
|
+
it "provides scoped subscriptions" do
|
141
|
+
bid = AuctionBid.new
|
142
|
+
probe = spy("probe")
|
143
|
+
bid.events.with_subscription_scope do |scope|
|
144
|
+
scope.on(:rejected) do
|
145
|
+
probe.handle_rejection
|
146
|
+
end
|
147
|
+
bid.reject_bid
|
148
|
+
end
|
149
|
+
bid.reject_bid
|
150
|
+
expect(probe).to have_received(:handle_rejection).once
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|