decorum 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.
- 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
|