decorum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +356 -0
- data/Rakefile +1 -0
- data/decorum.gemspec +24 -0
- data/examples/coffee.rb +7 -0
- data/examples/fibonacci_decorator.rb +24 -0
- data/examples/milk_decorator.rb +29 -0
- data/examples/sugar_decorator.rb +12 -0
- data/lib/decorum.rb +14 -0
- data/lib/decorum/bare_particular.rb +5 -0
- data/lib/decorum/chain_stop.rb +7 -0
- data/lib/decorum/decorated_state.rb +29 -0
- data/lib/decorum/decorations.rb +101 -0
- data/lib/decorum/decorator.rb +85 -0
- data/lib/decorum/version.rb +3 -0
- data/spec/integration/coffee_spec.rb +41 -0
- data/spec/integration/fibonacci_spec.rb +20 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/decorated_state/shared_state_stub.rb +15 -0
- data/spec/support/decorations/decorated_object_stub.rb +17 -0
- data/spec/support/decorations/first_decorator.rb +20 -0
- data/spec/support/decorations/second_decorator.rb +20 -0
- data/spec/support/decorations/third_decorator.rb +20 -0
- data/spec/support/decorator/basic_decorator.rb +20 -0
- data/spec/support/decorator/decorated_object_stub.rb +17 -0
- data/spec/support/decorator/decorated_state_stub.rb +11 -0
- data/spec/support/decorator/decorator_stub.rb +11 -0
- data/spec/unit/bare_particular_spec.rb +13 -0
- data/spec/unit/chain_stop_spec.rb +12 -0
- data/spec/unit/decorated_state_spec.rb +31 -0
- data/spec/unit/decorations_spec.rb +233 -0
- data/spec/unit/decorator_spec.rb +166 -0
- metadata +146 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module Decorum
|
2
|
+
module Spec
|
3
|
+
module Decorations
|
4
|
+
class ThirdDecorator < Decorum::Decorator
|
5
|
+
share :shared_attribute
|
6
|
+
attr_accessor :local_attribute
|
7
|
+
|
8
|
+
def third_decorator_method
|
9
|
+
"third"
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :current_decorator_method, :third_decorator_method
|
13
|
+
|
14
|
+
def respect_previously_defined_methods?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Decorum
|
2
|
+
module Spec
|
3
|
+
module Decorator
|
4
|
+
class BasicDecorator < Decorum::Decorator
|
5
|
+
default_attributes first_default: "default value", overridden_default: false
|
6
|
+
|
7
|
+
share :shared_attribute, :unused_shared_attribute
|
8
|
+
attr_accessor :name, :unused_personal_attribute
|
9
|
+
|
10
|
+
def basic_decorator_method
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def respect_previously_defined_methods?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Decorum
|
2
|
+
module Spec
|
3
|
+
module Decorator
|
4
|
+
class DecoratedObjectStub
|
5
|
+
include Decorum::Decorations
|
6
|
+
|
7
|
+
def respect_previously_defined_methods?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def decorated_state(*args)
|
12
|
+
@decorated_state ||= Decorum::Spec::Decorator::DecoratedStateStub.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::BareParticular do
|
4
|
+
let(:bp) { Decorum::BareParticular.new }
|
5
|
+
|
6
|
+
it 'is decoratable' do
|
7
|
+
expect(bp).to be_a(Decorum::Decorations)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'black holes undefined methods' do
|
11
|
+
expect(bp.nonexistent_method).to be_nil
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::ChainStop do
|
4
|
+
let(:chainstop) { Decorum::ChainStop.new }
|
5
|
+
|
6
|
+
it 'throws self via :chain_stop on undefined method' do
|
7
|
+
response = catch :chain_stop do
|
8
|
+
chainstop.nonexistent_method
|
9
|
+
end
|
10
|
+
expect(response).to be_equal(chainstop)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::DecoratedState do
|
4
|
+
let(:ds) { Decorum::DecoratedState.new(foo: "bar") }
|
5
|
+
|
6
|
+
it 'assigns initialized options' do
|
7
|
+
expect(ds.foo).to eq("bar")
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when forwarding messages' do
|
11
|
+
before(:each) do
|
12
|
+
# need to pop it open first...
|
13
|
+
fake_shared_state = Decorum::Spec::DecoratedState::SharedStateStub.new
|
14
|
+
ds.instance_variable_set(:@shared_state, fake_shared_state)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'forwards getter methods' do
|
18
|
+
expect(ds.marker).to eq("retrieved")
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'forwards setter methods' do
|
22
|
+
expect(ds.send(:"marker=", 'foo')).to eq('assigned')
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#respond_to?' do
|
26
|
+
it 'is false for forwarded messages' do
|
27
|
+
expect(ds.respond_to?(:marker)).to be_false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::Decorations do
|
4
|
+
let(:decorated) { Decorum::Spec::Decorations::DecoratedObjectStub.new }
|
5
|
+
let(:deco_class_1) { Decorum::Spec::Decorations::FirstDecorator }
|
6
|
+
let(:deco_class_2) { Decorum::Spec::Decorations::SecondDecorator }
|
7
|
+
let(:deco_class_3) { Decorum::Spec::Decorations::ThirdDecorator }
|
8
|
+
|
9
|
+
|
10
|
+
context 'as-yet-undecorated' do
|
11
|
+
# assert some basic assumptions
|
12
|
+
it 'is decoratable' do
|
13
|
+
expect(decorated.is_a?(Decorum::Decorations)).to be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'does not respond to first decorator test method' do
|
17
|
+
expect(decorated.respond_to?(:first_decorator_method)).to be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'responds to its own methods' do
|
21
|
+
expect(decorated.undecorated_method).to be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#decorated_state' do
|
25
|
+
it 'returns a hash' do
|
26
|
+
blank_state = decorated.decorated_state
|
27
|
+
expect(blank_state.is_a?(Hash)).to be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns an empty hash' do
|
31
|
+
blank_state = decorated.decorated_state
|
32
|
+
expect(blank_state.empty?).to be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns nil given an argument' do
|
36
|
+
expect(decorated.decorated_state(:not_gonna_work)).to be_nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'undecorate' do
|
41
|
+
it 'returns self on symbol' do
|
42
|
+
expect(decorated.undecorate(:symbol_arg)).to be_equal(decorated)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns self on spurious Decorator' do
|
46
|
+
expect(decorated.undecorate(deco_class_1)).to be_equal(decorated)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'decorated' do
|
52
|
+
describe '#decorate' do
|
53
|
+
it 'returns self on decoration' do
|
54
|
+
real_decorated = decorated
|
55
|
+
expect(decorated.decorate(deco_class_1)).to be_equal(real_decorated)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'yields decorator if block_given?' do
|
59
|
+
decorator = nil
|
60
|
+
decorated.decorate(deco_class_1) { |dec| decorator = dec }
|
61
|
+
actual_decorator = decorated.instance_variable_get(:@_decorator_chain)
|
62
|
+
expect(decorator).to be(actual_decorator)
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'success' do
|
66
|
+
before(:each) do
|
67
|
+
decorator_options = { decorator_handle: "first", shared_attribute: "shared 1", local_attribute: "local 1" }
|
68
|
+
decorated.decorate(deco_class_1, decorator_options)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'installs intercept' do
|
72
|
+
expect(decorated.is_a?(Decorum::Decorations::Intercept)).to be_true
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'sets internal state' do
|
76
|
+
internal_state = decorated.instance_variable_get(:@_decorator_chain)
|
77
|
+
expect(internal_state.is_a?(deco_class_1)).to be_true
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does not lose its own methods' do
|
81
|
+
expect(decorated.respect_previously_defined_methods?).to be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'gains the methods of the decorator' do
|
85
|
+
expect(decorated.first_decorator_method).to eq('first')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'raises NoMethodError on nonexistent method' do
|
89
|
+
expect { decorated.nonexistent_method }.to raise_error(NoMethodError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'is decorated by local attributes' do
|
93
|
+
expect(decorated.local_attribute).to eq("local 1")
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'is decorated by shared attributes' do
|
97
|
+
expect(decorated.shared_attribute).to eq("shared 1")
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'creates decorated state for decorator class' do
|
101
|
+
expect(decorated.decorated_state(deco_class_1).shared_attribute).to eq('shared 1')
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'with multiple decorators' do
|
105
|
+
before(:each) do
|
106
|
+
3.times do |i|
|
107
|
+
klass = send(:"deco_class_#{i + 1}")
|
108
|
+
decorated.decorate(klass)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'retains decorated methods' do
|
113
|
+
responses = [:first, :second, :third].map do |prefix|
|
114
|
+
decorated.send(:"#{prefix}_decorator_method")
|
115
|
+
end
|
116
|
+
expect(responses).to eq(["first", "second", "third"])
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'prefers the most recent decorator' do
|
120
|
+
expect(decorated.current_decorator_method).to eq("third")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'failure' do
|
126
|
+
it 'rejects classes that are not Decorators' do
|
127
|
+
expect { decorated.decorate(Class,{}) }.to raise_error(RuntimeError)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'rejects non unique decorator handles' do
|
131
|
+
3.times do |i|
|
132
|
+
klass = send(:"deco_class_#{i + 1}")
|
133
|
+
decorated.decorate(klass, decorator_handle: "deco-#{i + 1}")
|
134
|
+
end
|
135
|
+
expect { decorated.decorate(deco_class_1, decorator_handle: "deco-2") }.to raise_error(RuntimeError)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#respond_to?' do
|
141
|
+
before(:each) { decorated.decorate(deco_class_1) }
|
142
|
+
|
143
|
+
it 'returns true for decorated methods' do
|
144
|
+
expect(decorated.respond_to?(:first_decorator_method)).to be_true
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'returns true for undecorated methods' do
|
148
|
+
expect(decorated.respond_to?(:undecorated_method)).to be_true
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'returns false for undefined method' do
|
152
|
+
expect(decorated.respond_to?(:nonexistent_method)).to be_false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'monitoring and unloading' do
|
157
|
+
before(:each) do
|
158
|
+
3.times do |x|
|
159
|
+
klass = send(:"deco_class_#{x + 1}")
|
160
|
+
decorated.decorate(klass, decorator_handle: "deco-#{x + 1}")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe '#decorators' do
|
165
|
+
it 'accurately reflects loaded decorators and in order' do
|
166
|
+
expected_map = ["deco-3", "deco-2", "deco-1"]
|
167
|
+
expect(decorated.decorators.map { |d| d.decorator_handle }).to eq(expected_map)
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'memoizes decorators' do
|
171
|
+
decorated.decorators[0].next_link = decorated.decorators[2]
|
172
|
+
expect(decorated.decorators.length).to be(3)
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'refreshes via #decorators!' do
|
176
|
+
decorated.decorators[0].next_link = decorated.decorators[2]
|
177
|
+
decorated.send(:decorators!)
|
178
|
+
expect(decorated.decorators.length).to be(2)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#undecorate' do
|
183
|
+
before(:each) do
|
184
|
+
@undec = decorated.decorators.detect { |d| d.decorator_handle == "deco-2" }
|
185
|
+
# just to make sure...
|
186
|
+
unless @undec.is_a?(Decorum::Decorator)
|
187
|
+
raise "broken test---no such decorator deco-2; undec was #{@undec.inspect}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'undecorates' do
|
192
|
+
decorated.undecorate(@undec)
|
193
|
+
expect(decorated.decorators.length).to be(2)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'undecorates the right one' do
|
197
|
+
decorated.undecorate(@undec)
|
198
|
+
expected_map = ["deco-3", "deco-1"]
|
199
|
+
expect(decorated.decorators.map { |d| d.decorator_handle }).to eq(expected_map)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'returns self on success' do
|
203
|
+
real_decorated = decorated
|
204
|
+
expect(decorated.undecorate(@undec)).to be_equal(real_decorated)
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'once undecorated' do
|
208
|
+
before(:each) { decorated.undecorate(@undec) }
|
209
|
+
|
210
|
+
it 'no longer responds to removed decorated method' do
|
211
|
+
expect(decorated.respond_to?(:second_decorator_method)).to be_false
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'still responds to other decorated methods' do
|
215
|
+
expect(decorated.respond_to?(:third_decorator_method)).to be_true
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'doesn\'t mind if we check one just to be sure' do
|
219
|
+
expect(decorated.first_decorator_method).to eq('first')
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'destroys shared state when last class member is gone' do
|
223
|
+
expect(decorated.decorated_state(deco_class_2)).to be_nil
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'does not destroy other shared state' do
|
227
|
+
expect(decorated.decorated_state(deco_class_1)).to be_a(Decorum::DecoratedState)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Decorum::Decorator do
|
4
|
+
let(:decorator) do
|
5
|
+
@next_link = Decorum::ChainStop.new
|
6
|
+
@root = Decorum::Spec::Decorator::DecoratedObjectStub.new
|
7
|
+
@options_for_decorator = { name: 'bob', shared_attribute: 'shared', overridden_default: true }
|
8
|
+
Decorum::Spec::Decorator::BasicDecorator.new(@next_link, @root, @options_for_decorator)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'responds to .share' do
|
12
|
+
expect(Decorum::Decorator.respond_to?(:share)).to be_true
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'responds to .accumulator' do
|
16
|
+
expect(Decorum::Decorator.respond_to?(:accumulator)).to be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#decorated_state' do
|
20
|
+
it 'defers to the root object' do
|
21
|
+
expect(decorator.decorated_state.reaching_its_destination?).to be_true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#next_link' do
|
26
|
+
it 'returns the next decorator' do
|
27
|
+
expect(decorator.next_link).to be_equal(@next_link)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#next_link=' do
|
32
|
+
it 'sets the next decorator' do
|
33
|
+
@new_next_link = Decorum::Spec::Decorator::DecoratorStub.new
|
34
|
+
decorator.next_link = @new_next_link
|
35
|
+
response = catch :deferred do
|
36
|
+
decorator.send(:nonexistent_method)
|
37
|
+
end
|
38
|
+
|
39
|
+
expect(response).to be_equal(@new_next_link)
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#root' do
|
43
|
+
it 'returns the root object' do
|
44
|
+
expect(decorator.root).to be_equal(@root)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'is aliased as #object' do
|
48
|
+
expect(decorator.object).to be_equal(@root)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#
|
53
|
+
|
54
|
+
describe '#decorated_tail' do
|
55
|
+
let(:value) { "howdy" }
|
56
|
+
|
57
|
+
it 'catches :chain_stop' do
|
58
|
+
response = decorator.decorated_tail(value) { throw :chain_stop, "caught" }
|
59
|
+
expect(response).to eq("caught")
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'preserves return value' do
|
63
|
+
response = decorator.decorated_tail(value) { decorator.send(:nonexistent_method) }
|
64
|
+
expect(response).to eq(value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when attributes are declared default' do
|
69
|
+
describe '#getter' do
|
70
|
+
it 'gets default value' do
|
71
|
+
expect(decorator.first_default).to eq("default value")
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'is overridden by options passed to initialize' do
|
75
|
+
expect(decorator.overridden_default).to equal(true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#setter' do
|
80
|
+
it 'gets defined' do
|
81
|
+
decorator.first_default = "new value"
|
82
|
+
expect(decorator.first_default).to eq("new value")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when attributes are declared via share' do
|
88
|
+
describe '#setter' do
|
89
|
+
it 'does not set attribute locally' do
|
90
|
+
expect(decorator.instance_variable_get(:@shared_attribute)).to be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'sets attribute in shared state' do
|
94
|
+
expect(decorator.decorated_state.shared_attribute).to eq('shared')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#getter' do
|
99
|
+
it 'sets attribute' do
|
100
|
+
expect(decorator.shared_attribute).to eq('shared')
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'gets nil for unused attribute' do
|
104
|
+
expect(decorator.unused_shared_attribute).to be_nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#boolean' do
|
109
|
+
it 'returns true on set attribute' do
|
110
|
+
expect(decorator.shared_attribute?).to equal(true)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'returns false on unset attribute' do
|
114
|
+
expect(decorator.unused_shared_attribute?).to equal(false)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#resetter' do
|
119
|
+
before(:each) { decorator.reset_shared_attribute }
|
120
|
+
|
121
|
+
it 'sets attribute to nil' do
|
122
|
+
expect(decorator.shared_attribute).to be_nil
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'sets attribute to nil in shared state' do
|
126
|
+
decorator.instance_variable_set(:@shared_attribute, "for real not nil")
|
127
|
+
expect(decorator.shared_attribute).to be_nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'when attributes via declared personally' do
|
133
|
+
describe '#setter' do
|
134
|
+
it 'sets local attribute via initialize' do
|
135
|
+
expect(decorator.name).to eq('bob')
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'sets local attribute locally by initialize' do
|
139
|
+
expect(decorator.instance_variable_get(:@name)).to eq('bob')
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'does not set local attribute in shared state via initialize' do
|
143
|
+
expect(decorator.decorated_state.name).to be_nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe '#getter' do
|
148
|
+
it 'gets nil for unused attribute' do
|
149
|
+
expect(decorator.unused_personal_attribute).to be_nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'when calling decorator methods' do
|
155
|
+
it 'picks up methods it has' do
|
156
|
+
expect(decorator.basic_decorator_method).to be_true
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'defers methods it doesn\'t have' do
|
160
|
+
response = catch :chain_stop do
|
161
|
+
decorator.send(:nonexistent_method)
|
162
|
+
end
|
163
|
+
expect(response).to be_a(Decorum::ChainStop)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|