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