decorum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class DecoratedStateStub < Decorum::DecoratedState
5
+ def reaching_its_destination?
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Decorum
2
+ module Spec
3
+ module Decorator
4
+ class DecoratorStub
5
+ def method_missing(*args)
6
+ throw :deferred, self
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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