reduxco 1.0.0

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