reduxco 1.0.0

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.
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reduxco::Context::CallableTable do
4
+
5
+ let(:identity_dynref) { Reduxco::CallableRef.new(:identity) }
6
+ let(:identity_callable) { ->(c){c} }
7
+ let(:app_callable) { ->(c){c[:identity]} }
8
+
9
+ it 'should instantiate with an array of maps' do
10
+ table = Reduxco::Context::CallableTable.new( [{:identity => identity_callable}, {:app => app_callable}] )
11
+ table.should include(identity_dynref)
12
+ table.resolve(identity_dynref).should_not be_nil
13
+ end
14
+
15
+ it 'should instantiate with an array of arrays of arrays' do
16
+ table = Reduxco::Context::CallableTable.new( [[[:identity, identity_callable]], [[:app, app_callable]]] )
17
+ table.should include(identity_dynref)
18
+ table.resolve(identity_dynref).should_not be_nil
19
+ end
20
+
21
+ describe 'introspection' do
22
+
23
+ let(:example_callable) { ->(c){c} }
24
+
25
+ before(:each) do
26
+ map1 = {:moho => example_callable, :eve => example_callable}
27
+ map2 = {:eve => example_callable, :kerbin => example_callable}
28
+ @table = Reduxco::Context::CallableTable.new([map1,map2])
29
+ end
30
+
31
+ it 'should introspect existence of static refs' do
32
+ @table.should include(Reduxco::CallableRef.new(:moho,1))
33
+ @table.should include(Reduxco::CallableRef.new(:eve,1))
34
+ @table.should include(Reduxco::CallableRef.new(:eve,2))
35
+ @table.should include(Reduxco::CallableRef.new(:kerbin,2))
36
+ end
37
+
38
+ it 'should introspect existence of dynamic refs' do
39
+ @table.should include(Reduxco::CallableRef.new(:moho))
40
+ @table.should include(Reduxco::CallableRef.new(:eve))
41
+ @table.should include(Reduxco::CallableRef.new(:kerbin))
42
+ end
43
+
44
+ it 'should introspect the non-existance of static refs' do
45
+ @table.should_not include(Reduxco::CallableRef.new(:duna,1))
46
+ @table.should_not include(Reduxco::CallableRef.new(:moho,2))
47
+ end
48
+
49
+ it 'should introspect the non-existance of dynamic refs' do
50
+ @table.should_not include(Reduxco::CallableRef.new(:duna))
51
+ end
52
+
53
+ end
54
+
55
+ describe 'resolution' do
56
+
57
+ before(:each) do
58
+ @map1 = {moho: ->(c){1}, eve: ->(c){2}, jool: ->(c){5}}
59
+ @map2 = {eve: ->(c){'second'}, kerbin: ->(c){3}}
60
+ @map3 = {jool: ->(c){'5th'}}
61
+ @table = Reduxco::Context::CallableTable.new([@map1,@map2,@map3])
62
+ end
63
+
64
+ let(:context) {double('context')}
65
+
66
+ it 'should resolve top level static ref' do
67
+ @table.resolve(Reduxco::CallableRef.new(:moho,1)).tap do |callref, callable|
68
+ callref.should == Reduxco::CallableRef.new(:moho,1)
69
+ callable.call(context).should == 1
70
+ end
71
+ end
72
+
73
+ it 'should resolve a deeper level static ref' do
74
+ @table.resolve(Reduxco::CallableRef.new(:kerbin,2)).tap do |callref, callable|
75
+ callref.should == Reduxco::CallableRef.new(:kerbin,2)
76
+ callable.call(context).should == 3
77
+ end
78
+ end
79
+
80
+ it 'should resolve a static ref that shadows higher level one' do
81
+ @table.resolve(Reduxco::CallableRef.new(:eve,2)).tap do |callref, callable|
82
+ callref.should == Reduxco::CallableRef.new(:eve,2)
83
+ callable.call(context).should == 'second'
84
+ end
85
+ end
86
+
87
+ it 'should resolve a shadowed static ref' do
88
+ @table.resolve(Reduxco::CallableRef.new(:eve,1)).tap do |callref, callable|
89
+ callref.should == Reduxco::CallableRef.new(:eve,1)
90
+ callable.call(context).should == 2
91
+ end
92
+ end
93
+
94
+ it 'should resolve simple (non-shadowed) dynamic refs to correct level' do
95
+ @table.resolve(Reduxco::CallableRef.new(:moho)).tap do |callref, callable|
96
+ callref.should == Reduxco::CallableRef.new(:moho,1)
97
+ callable.call(context).should == 1
98
+ end
99
+
100
+ @table.resolve(Reduxco::CallableRef.new(:kerbin)).tap do |callref, callable|
101
+ callref.should == Reduxco::CallableRef.new(:kerbin,2)
102
+ callable.call(context).should == 3
103
+ end
104
+ end
105
+
106
+ it 'should resolve a shadowed dynamic ref to the lowest level' do
107
+ @table.resolve(Reduxco::CallableRef.new(:eve)).tap do |callref, callable|
108
+ callref.should == Reduxco::CallableRef.new(:eve,2)
109
+ callable.call(context).should == 'second'
110
+ end
111
+ end
112
+
113
+
114
+ it 'should return a callable value of nil if a static ref cannot be resolved' do
115
+ @table.resolve(Reduxco::CallableRef.new(:eeloo, 1)).tap do |callref, callable|
116
+ callref.should be_nil
117
+ callable.should be_nil
118
+ end
119
+ end
120
+
121
+ it 'should return a callable value of nil if a dynamic ref cannot be resolved' do
122
+ @table.resolve(Reduxco::CallableRef.new(:eeloo)).tap do |callref, callable|
123
+ callref.should be_nil
124
+ callable.should be_nil
125
+ end
126
+ end
127
+
128
+ it 'should return RESOLUTION_FAILURE on resolution failure' do
129
+ @table.resolve(Reduxco::CallableRef.new(:eeloo, 1)).should == Reduxco::Context::CallableTable::RESOLUTION_FAILURE
130
+ @table.resolve(Reduxco::CallableRef.new(:eeloo)).should == Reduxco::Context::CallableTable::RESOLUTION_FAILURE
131
+ end
132
+
133
+ it 'should give nil values for multiple assignment of RESOLUTION_FAILURE' do
134
+ callref, callable = Reduxco::Context::CallableTable::RESOLUTION_FAILURE
135
+ callref.should be_nil
136
+ callable.should be_nil
137
+ end
138
+
139
+ it 'should use :[] as an alias to :resolve' do
140
+ @table.method(:[]).should == @table.method(:resolve)
141
+ end
142
+
143
+ describe 'super' do
144
+
145
+ it 'should resolve super for a static ref' do
146
+ @table.resolve_super(Reduxco::CallableRef.new(:eve,2)).tap do |callref, callable|
147
+ callref.should == Reduxco::CallableRef.new(:eve,1)
148
+ callable.call(context).should == 2
149
+ end
150
+ end
151
+
152
+ it 'should resolve super across depth gaps' do
153
+ @table.resolve_super(Reduxco::CallableRef.new(:jool,3)).tap do |callref, callable|
154
+ callref.should == Reduxco::CallableRef.new(:jool, 1)
155
+ callable.call(context).should == 5
156
+ end
157
+ end
158
+
159
+ it 'should return a callable value of nil if a super ref cannot be resolved' do
160
+ @table.resolve_super(Reduxco::CallableRef.new(:kerbin,2)).tap do |callref, callable|
161
+ callref.should be_nil
162
+ callable.should be_nil
163
+ end
164
+ end
165
+
166
+ it 'should raise an exception with a dynamic ref' do
167
+ ->{ @table.resolve_super(Reduxco::CallableRef.new(:moho)) }.should raise_error(ArgumentError)
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+
174
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reduxco::Context::Callstack do
4
+
5
+ before(:each) do
6
+ @stack = Reduxco::Context::Callstack.new([:top, :middle, :bottom])
7
+ end
8
+
9
+ it 'should return the depth' do
10
+ @stack.depth.should == 3
11
+ end
12
+
13
+ it 'should get the top frame' do
14
+ @stack.top.should == :top
15
+ end
16
+
17
+ it 'should initialize as empty' do
18
+ stack = Reduxco::Context::Callstack.new
19
+ stack.depth.should == 0
20
+ end
21
+
22
+ it 'should be able to initialize with an array, top elem first' do
23
+ @stack.depth.should == 3
24
+ @stack.top.should == :top
25
+ end
26
+
27
+ it 'should pop' do
28
+ frame = @stack.pop
29
+
30
+ @stack.depth.should == 2
31
+ frame.should == :top
32
+ end
33
+
34
+ it 'should push' do
35
+ s = @stack.push(:new_top)
36
+
37
+ @stack.depth.should == 4
38
+ @stack.top.should == :new_top
39
+
40
+ s.object_id.should == @stack.object_id
41
+ end
42
+
43
+ it 'should check for frame inclusion' do
44
+ @stack.should include(:middle)
45
+
46
+ @stack.should_not include(:eeloo)
47
+ end
48
+
49
+ it 'should return the rest of the stack' do
50
+ rest = @stack.rest
51
+
52
+ rest.depth.should == 2
53
+ rest.top.should == :middle
54
+
55
+ rest.should be_kind_of(@stack.class)
56
+ end
57
+
58
+ it 'should return a properly structured copy of the stack' do
59
+ dup = @stack.dup
60
+
61
+ dup.should be_kind_of(@stack.class)
62
+ dup.object_id.should_not == @stack.object_id
63
+
64
+ dup.top.should == @stack.top
65
+ end
66
+
67
+ it 'should not mutate the copy of the stack out from under it' do
68
+ dup = @stack.dup
69
+
70
+ @stack.push(:moho)
71
+ dup.should_not include(:moho)
72
+
73
+ dup.push(:eve)
74
+ @stack.should_not include(:eve)
75
+ end
76
+
77
+ describe 'conversion and formatting' do
78
+
79
+ let(:frame) do
80
+ double('frame').tap do |f|
81
+ f.stub(:to_s) {'frame-string'}
82
+ f.stub(:inspect) {'frame-inspect'}
83
+ end
84
+ end
85
+
86
+ it 'should output to_s with the top element first' do
87
+ str = @stack.to_s
88
+
89
+ str.index('top').should < str.index('bottom')
90
+ end
91
+
92
+ it 'should inspect with the top element first' do
93
+ str = @stack.inspect
94
+ str.index('top').should < str.index('bottom')
95
+ end
96
+
97
+ it 'should invoke inspect of frames when inspecting' do
98
+ @stack.push(frame)
99
+
100
+ frame.should_receive(:inspect)
101
+ str = @stack.inspect
102
+
103
+ str.should include('frame-inspect')
104
+ end
105
+
106
+ it 'should convert to a caller-style callstack' do
107
+ cs = @stack.to_caller
108
+
109
+ cs.each {|tr| tr.should be_kind_of(String)}
110
+ cs.first.should include('top')
111
+ cs.last.should include('bottom')
112
+ end
113
+
114
+ it 'should prepend a callstack line into the caller-style callstack, conterting it to string' do
115
+ top = Object.new
116
+ cs = @stack.to_caller(top)
117
+
118
+ cs.each {|tr| tr.should be_kind_of(String)}
119
+
120
+ head, *tail = cs
121
+
122
+ head.should == top.to_s
123
+ tail.should == @stack.to_caller
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,619 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reduxco::Context do
4
+
5
+ let(:add_map) do
6
+ {
7
+ sum: ->(c){ c[:a]+c[:b] },
8
+ a: ->(c){3},
9
+ b: ->(c){5}
10
+ }
11
+ end
12
+
13
+ let(:cyclical_map) do
14
+ {
15
+ a: ->(c){c[:b]},
16
+ b: ->(c){c[:c]},
17
+ c: ->(c){c[:a]}
18
+ }
19
+ end
20
+
21
+ let(:non_callable_map){ {moho:'foo'} }
22
+
23
+ let(:handler) do
24
+ {
25
+ handler: Proc.new do |c|
26
+ begin
27
+ c[:a]
28
+ rescue => e
29
+ [e, c.callstack]
30
+ end
31
+ end
32
+ }
33
+ end
34
+
35
+ describe 'call' do
36
+
37
+ it 'should alias :[] to call' do
38
+ context = Reduxco::Context.new
39
+ context.method(:[]).should == context.method(:call)
40
+ end
41
+
42
+ it 'should invoke the call method of the object it resolves to' do
43
+ callable = double('callable')
44
+ context = Reduxco::Context.new(:moho => callable)
45
+
46
+ callable.should_receive(:call).with(context)
47
+
48
+ context.call(:moho)
49
+ end
50
+
51
+ it 'should resolve a leaf refname to a value' do
52
+ context = Reduxco::Context.new(add_map)
53
+ context.call(:a).should == 3
54
+ end
55
+
56
+ it 'should resolve a refname with dependencies to a value via cascading calculation' do
57
+ context = Reduxco::Context.new(add_map)
58
+ context.call(:sum).should == 8
59
+ end
60
+
61
+ it 'should resolve a callref' do
62
+ context = Reduxco::Context.new(add_map)
63
+ context.call( Reduxco::CallableRef.new(:sum) ).should == 8
64
+ end
65
+
66
+ it 'should resolve a callref to a shadowed value' do
67
+ context = Reduxco::Context.new(add_map, {sum: ->(c){-101}})
68
+ context.call( Reduxco::CallableRef.new(:sum, 1) ).should == 8
69
+ context.call( Reduxco::CallableRef.new(:sum, 2) ).should == -101
70
+ end
71
+
72
+ it 'should cache the results' do
73
+ context = Reduxco::Context.new(app: ->(c){ Object.new })
74
+
75
+ generated_object = context.call(:app)
76
+ context.call(:app).should == generated_object
77
+ end
78
+
79
+ it 'should allow yielding to a block.' do
80
+ context = Reduxco::Context.new(
81
+ app: ->(c){ c.call(:outter){|x| x*x} },
82
+ outter: ->(c){ c.yield(3) + c.yield(4) }
83
+ )
84
+
85
+ context.call(:app).should == 25
86
+ end
87
+
88
+ describe 'errors' do
89
+
90
+ it 'should error if refname resolves to a non-callable' do
91
+ context = Reduxco::Context.new(non_callable_map)
92
+
93
+ ->{ context.call(:moho) }.should raise_error(Reduxco::Context::NotCallableError)
94
+
95
+ begin
96
+ context.call(:moho)
97
+ rescue => e
98
+ e.message.should include('moho')
99
+ e.backtrace.select {|tr| tr.include?('context.rb')}.should == []
100
+ end
101
+ end
102
+
103
+ it 'should error if refname is non-resolvable' do
104
+ context = Reduxco::Context.new(add_map)
105
+
106
+ ->{ context.call(:eeloo) }.should raise_error(Reduxco::Context::NameError)
107
+
108
+ begin
109
+ context.call(:eeloo)
110
+ rescue => e
111
+ e.message.should include('eeloo')
112
+ e.backtrace.select {|tr| tr =~ /context.rb.+`call'$/}.should == []
113
+ end
114
+
115
+ end
116
+
117
+ it 'should error on cyclical dependencies' do
118
+ context = Reduxco::Context.new(cyclical_map)
119
+
120
+ ->{ context.call(:a) }.should raise_error(Reduxco::Context::CyclicalError)
121
+
122
+ begin
123
+ context.call(:a)
124
+ rescue => e
125
+ e.message.should include('a:1')
126
+ e.message.should include('c:1')
127
+ e.backtrace.select {|tr| tr =~ /context.rb.+`call'$/}.should == []
128
+ end
129
+ end
130
+
131
+ it 'should not corrupt the callstack when catching a non-resolvalbe name error' do
132
+ context = Reduxco::Context.new({a:->(c){c[:eeloo]}}, handler)
133
+
134
+ error, stack = context.call(:handler)
135
+
136
+ stack.top.should == Reduxco::CallableRef.new(:handler, 2)
137
+ stack.depth.should == 1
138
+ end
139
+
140
+ it 'should not corrupt the callstack when catching a cyclical dependency' do
141
+ context = Reduxco::Context.new(cyclical_map, handler)
142
+
143
+ error, stack = context.call(:handler)
144
+
145
+ stack.top.should == Reduxco::CallableRef.new(:handler, 2)
146
+ stack.depth.should == 1
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+
153
+ describe 'super' do
154
+
155
+ it 'should resolve to the next previous ref of the same name' do
156
+ context = Reduxco::Context.new({
157
+ moho: ->(c){ 'top' }
158
+ },
159
+ {
160
+ moho: ->(c){ c.super }
161
+ })
162
+
163
+ context.call(:moho).should == 'top'
164
+ end
165
+
166
+ it 'should chain calls and their results' do
167
+ context = Reduxco::Context.new({
168
+ moho: ->(c){ 1 }
169
+ },
170
+ {
171
+ moho: ->(c){ c.super + 2 }
172
+ },
173
+ {
174
+ eve: ->(c){ 1024 }
175
+ },
176
+ {
177
+ moho: ->(c){ c.super + 4 }
178
+ })
179
+
180
+ context.call(:moho).should == 7
181
+ end
182
+
183
+ it 'should throw a NameError if super cannot be resolved' do
184
+ context = Reduxco::Context.new({
185
+ eve: ->(c){ 'eve' }
186
+ },
187
+ {
188
+ moho: ->(c){ c.super }
189
+ })
190
+
191
+ ->{ context.call(:moho) }.should raise_error(Reduxco::Context::NameError)
192
+ end
193
+
194
+ it 'should allow super to recieve a block to yield to' do
195
+
196
+ context = Reduxco::Context.new({
197
+ moho: ->(c){ c.yield(3) }
198
+ },
199
+ {
200
+ moho: ->(c){ c.super {|x| x*x} }
201
+ })
202
+
203
+ context.call(:moho).should == 9
204
+ end
205
+
206
+
207
+ it 'should forward the yield block to super' do
208
+ context = Reduxco::Context.new({
209
+ moho: ->(c){ c.yield(3) }
210
+ },
211
+ {
212
+ moho: ->(c){ c.super },
213
+ app: ->(c){ c.call(:moho){|x| x*x } }
214
+ })
215
+
216
+ context.call(:app).should == 9
217
+ end
218
+
219
+ end
220
+
221
+ describe 'callstack interface' do
222
+
223
+ it 'should return a copy of the callstack' do
224
+ context = Reduxco::Context.new
225
+
226
+ context.callstack.should be_kind_of(Reduxco::Context::Callstack)
227
+ context.callstack.should.object_id.should_not == context.callstack
228
+ end
229
+
230
+ it 'should return a copy of the callstack from inside of a call' do
231
+ context = Reduxco::Context.new({
232
+ app: ->(c){ c.callstack }
233
+ })
234
+
235
+ stack = context.call(:app)
236
+
237
+ stack.depth.should == 1
238
+ stack.top.name.should == :app
239
+ end
240
+
241
+ it 'should return the current frame' do
242
+ context = Reduxco::Context.new({
243
+ app: ->(c){ c.current_frame.should == c.callstac.top }
244
+ })
245
+ end
246
+
247
+ end
248
+
249
+ describe 'introspection interface' do
250
+
251
+ let(:map1) do
252
+ {moho: ->(c){'moho-value'}, eve: ->(c){'eve-value'}}
253
+ end
254
+
255
+ let(:map2) do
256
+ {kerbin: ->(c){'kerbin-value'}, duna: ->(c){'duna-value'}, app: ->(c){c[:kerbin] + c[:moho]}}
257
+ end
258
+
259
+ let(:context) do
260
+ Reduxco::Context.new(map1, map2)
261
+ end
262
+
263
+ describe 'include' do
264
+
265
+ it 'should introspect which refnames are valid.' do
266
+ context.include?(:moho).should be_true
267
+ context.include?(:kerbin).should be_true
268
+
269
+ context.include?(:eeloo).should be_false
270
+ end
271
+
272
+ it 'should introspect which dynamic callrefs are valid.' do
273
+ context.include?( Reduxco::CallableRef.new(:moho) ).should be_true
274
+ context.include?( Reduxco::CallableRef.new(:kerbin) ).should be_true
275
+ context.include?( Reduxco::CallableRef.new(:eeloo) ).should be_false
276
+ end
277
+
278
+ it 'should introspect which static callrefs are valid.' do
279
+ context.include?( Reduxco::CallableRef.new(:moho,1) ).should be_true
280
+ context.include?( Reduxco::CallableRef.new(:kerbin,2) ).should be_true
281
+ context.include?( Reduxco::CallableRef.new(:moho,2) ).should be_false
282
+ context.include?( Reduxco::CallableRef.new(:kerbin,1) ).should be_false
283
+ end
284
+
285
+ end
286
+
287
+ describe 'completed' do
288
+
289
+ it 'should introspect which refnames have been computed.' do
290
+ [:moho, :eve, :kerbin, :duna, :app].each do |refname|
291
+ context.completed?(refname).should be_false
292
+ end
293
+
294
+ context.call(:app)
295
+
296
+ [:moho, :kerbin, :app].each do |refname|
297
+ context.completed?(refname).should be_true
298
+ end
299
+
300
+ [:eve, :duna].each do |refname|
301
+ context.completed?(refname).should be_false
302
+ end
303
+ end
304
+
305
+ it 'should introspect which callrefs have been computed.' do
306
+ all_ref_args = [[:kerbin,1], [:kerbin,2], [:moho,1], [:moho,2], [:duna,1], [:duna,2]]
307
+
308
+ all_ref_args.each do |name, depth|
309
+ callref = Reduxco::CallableRef.new(name, depth)
310
+ context.completed?(callref).should be_false
311
+ end
312
+
313
+ context.call(:app)
314
+
315
+ completed_ref_args = [[:kerbin,2], [:moho,1]]
316
+ incomplete_ref_args = all_ref_args - completed_ref_args
317
+
318
+ completed_ref_args.each do |name, depth|
319
+ callref = Reduxco::CallableRef.new(name, depth)
320
+ context.completed?(callref).should be_true
321
+ end
322
+
323
+ incomplete_ref_args.each do |name, depth|
324
+ callref = Reduxco::CallableRef.new(name, depth)
325
+ context.completed?(callref).should be_false
326
+ end
327
+ end
328
+
329
+ it 'should report not-found refs as uncomputed.' do
330
+ context.completed?(:eeloo).should be_false
331
+ context.completed?( Reduxco::CallableRef.new(:eloo,1) ).should be_false
332
+ end
333
+
334
+ end
335
+
336
+ describe 'assert_completed' do
337
+
338
+ it 'should raise an exception if failed' do
339
+ ->{ context.assert_completed(:app) }.should raise_error(Reduxco::Context::AssertError)
340
+ end
341
+
342
+ it 'should return nil if success' do
343
+ context[:app]
344
+
345
+ context.assert_completed(:app).should be_nil
346
+ end
347
+
348
+ end
349
+
350
+ end
351
+
352
+ describe 'initialization' do
353
+
354
+ let(:map1) { {moho: ->(c){'moho1'}} }
355
+ let(:map2) { {eve: ->(c){'eve2'}} }
356
+
357
+ it 'should initialize with a single callable map' do
358
+ context = Reduxco::Context.new(map1)
359
+ context.should include(:moho)
360
+ end
361
+
362
+ it 'should initialize with multiple callable maps' do
363
+ context = Reduxco::Context.new(map1, map2)
364
+ context.should include(:moho)
365
+ context.should include(:eve)
366
+ end
367
+
368
+ it 'should only invoke :each on the map (returning name/callable pairs)' do
369
+ map = double('map')
370
+ map.should_receive(:each)
371
+
372
+ Reduxco::Context.new(map)
373
+ end
374
+
375
+ end
376
+
377
+ describe 'duplication' do
378
+
379
+ it 'should instantiate a new Context with the same callables on dup' do
380
+ map1 = double('map1')
381
+ map1.stub(:each)
382
+ map2 = double('map2')
383
+ map2.stub(:each)
384
+ context = Reduxco::Context.new(map1, map2)
385
+
386
+ dup = context.dup
387
+
388
+ dup.instance_variables.each do |ivar|
389
+ dup.instance_variable_get(ivar).object_id.should_not == context.instance_variable_get(ivar)
390
+ end
391
+
392
+ dup.instance_variable_get(:@callable_maps).tap do |maps|
393
+ maps.should == [map1, map2]
394
+ end
395
+ end
396
+
397
+ end
398
+
399
+ describe 'reduce' do
400
+
401
+ it 'should reduce with a refname and return the result' do
402
+ context = Reduxco::Context.new(app: ->(c){'app-result'})
403
+
404
+ context.reduce(:app).should == 'app-result'
405
+ end
406
+
407
+ it 'should reduce with :app by default' do
408
+ context = Reduxco::Context.new(app: ->(c){'app-result'})
409
+
410
+ context.reduce(:app).should == 'app-result'
411
+ end
412
+
413
+ end
414
+
415
+ describe 'flow helpers' do
416
+
417
+ describe 'before' do
418
+
419
+ it 'should invoke the before helper block before calling the passed refname' do
420
+ call_order = []
421
+ context = Reduxco::Context.new({
422
+ app: ->(c){ c.before(:a) {c[:b]} },
423
+ a: ->(c){ call_order << :a },
424
+ b: ->(c){ call_order << :b }
425
+ })
426
+
427
+ context.call(:app)
428
+
429
+ call_order.should == [:b, :a]
430
+ end
431
+
432
+ it 'should return the value of the passed refname' do
433
+ context = Reduxco::Context.new({
434
+ app: ->(c){ c.before(:a) {c[:b]} },
435
+ a: ->(c){ 'a-result' },
436
+ b: ->(c){ 'b-result' }
437
+ })
438
+
439
+ context.call(:app).should == 'a-result'
440
+ end
441
+
442
+ it 'should not yield anything to the block' do
443
+ block_args = nil
444
+ context = Reduxco::Context.new({
445
+ app: Proc.new do |c|
446
+ c.before(:a) do |*args|
447
+ block_args = args
448
+ c[:b]
449
+ end
450
+ end,
451
+ a: ->(c){ 'a-result' },
452
+ b: ->(c){ 'b-result' }
453
+ })
454
+
455
+ context.call(:app)
456
+
457
+ block_args.should == []
458
+ end
459
+
460
+ end
461
+
462
+ describe 'after' do
463
+
464
+ it 'should invoke the after helper block after calling the passed refname' do
465
+ call_order = []
466
+ context = Reduxco::Context.new({
467
+ app: ->(c){ c.after(:a) {c[:b]} },
468
+ a: ->(c){ call_order << :a },
469
+ b: ->(c){ call_order << :b }
470
+ })
471
+
472
+ context.call(:app)
473
+
474
+ call_order.should == [:a, :b]
475
+ end
476
+
477
+ it 'should return the value of the passed refname' do
478
+ context = Reduxco::Context.new({
479
+ app: ->(c){ c.after(:a) {c[:b]} },
480
+ a: ->(c){ 'a-result' },
481
+ b: ->(c){ 'b-result' }
482
+ })
483
+
484
+ context.call(:app).should == 'a-result'
485
+ end
486
+
487
+ it 'should not yield anything' do
488
+ block_args = nil
489
+ context = Reduxco::Context.new({
490
+ app: Proc.new do |c|
491
+ c.after(:a) do |*args|
492
+ block_args = args
493
+ c[:b]
494
+ end
495
+ end,
496
+ a: ->(c){ 'a-result' },
497
+ b: ->(c){ 'b-result' }
498
+ })
499
+
500
+ context.call(:app)
501
+
502
+ block_args.should == []
503
+ end
504
+
505
+ end
506
+
507
+ describe 'inside' do
508
+
509
+ describe 'client behavior' do
510
+
511
+ let(:table){ {} }
512
+
513
+ let(:outter) do
514
+ Proc.new do |c|
515
+ table[:yeild_result] = c.yield('arg1', 'arg2')
516
+ 'outter_result'
517
+ end
518
+ end
519
+
520
+ let(:app) do
521
+ Proc.new do |c|
522
+ table[:inside_method_result] = c.inside(:outter) do |*args|
523
+ table[:args] = args
524
+ 'yield_result'
525
+ end
526
+ 'app_result'
527
+ end
528
+ end
529
+
530
+ let(:map) do
531
+ {app:app, outter:outter}
532
+ end
533
+
534
+ let(:context) do
535
+ context = Reduxco::Context.new(map)
536
+ end
537
+
538
+ it 'should be an alias to call, as it is just call with a block provided.' do
539
+ context.method(:inside).should == context.method(:call)
540
+ end
541
+
542
+ it 'should return the block\'s value from inside the passed refname' do
543
+ context.call(:app)
544
+
545
+ table[:yeild_result].should == 'yield_result'
546
+ end
547
+
548
+ it 'should return the value of the passed refname' do
549
+ context.call(:app)
550
+
551
+
552
+ table[:inside_method_result].should == 'outter_result'
553
+ end
554
+
555
+ it 'should allow yielding of args to the block' do
556
+ context.call(:app)
557
+
558
+ table[:args].should == ['arg1', 'arg2']
559
+ end
560
+
561
+ it 'should allow taking no block' do
562
+ context = Reduxco::Context.new(app: ->(c){ c.inside(:moho) }, moho: ->(c){'moho-result'})
563
+
564
+ context.call(:app).should == 'moho-result'
565
+ end
566
+
567
+ end
568
+
569
+ describe 'yield failure modes' do
570
+
571
+ it 'should give nested insides their correct block handles' do
572
+ context = Reduxco::Context.new(
573
+ app: ->(c){ c.inside(:outter){ c.inside(:middle){ c[:inner] } } },
574
+ outter: ->(c){ c.yield << 'outter' },
575
+ middle: ->(c){ c.yield << 'middle' },
576
+ inner: ->(c){ ['inner'] }
577
+ )
578
+
579
+ context.call(:app).should == ['inner', 'middle', 'outter']
580
+ end
581
+
582
+ it 'should not corrupt the handle stack when an exception is thrown and then caught inside of nested insides' do
583
+ context = Reduxco::Context.new(
584
+ app: ->(c){ c.inside(:outter){ c.inside(:middle){ c.inside(:inner) {c[:leaf]} } } },
585
+ outter: ->(c){ c.yield << 'outter' },
586
+ middle: ->(c){ begin c.yield << 'middle' rescue ['middle-caught'] end },
587
+ inner: ->(c){ c.yield << 'inner' },
588
+ leaf: ->(c){ raise RuntimeError }
589
+ )
590
+
591
+ context.call(:app).should == ['middle-caught', 'outter']
592
+ end
593
+
594
+ it 'should not allow yielding from a nested call.' do
595
+ context = Reduxco::Context.new(
596
+ app: ->(c){ c.inside(:outter){ 2 } },
597
+ outter: ->(c){ c[:middle] },
598
+ middle: ->(c){ c.yield }
599
+ )
600
+
601
+ ->{ context.call(:app) }.should raise_error(Reduxco::Context::LocalJumpError)
602
+ end
603
+
604
+ it 'should allow a double yield.' do
605
+ context = Reduxco::Context.new(
606
+ app: ->(c){ c.inside(:outter){|x| x*x} },
607
+ outter: ->(c){ c.yield(3) + c.yield(4) }
608
+ )
609
+
610
+ context.call(:app).should == 25
611
+ end
612
+
613
+ end
614
+
615
+ end
616
+
617
+ end
618
+
619
+ end