roda 1.0.0 → 1.1.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +18 -13
  4. data/Rakefile +8 -0
  5. data/doc/conventions.rdoc +163 -0
  6. data/doc/release_notes/1.1.0.txt +226 -0
  7. data/lib/roda.rb +51 -22
  8. data/lib/roda/plugins/assets.rb +613 -0
  9. data/lib/roda/plugins/caching.rb +215 -0
  10. data/lib/roda/plugins/chunked.rb +278 -0
  11. data/lib/roda/plugins/error_email.rb +112 -0
  12. data/lib/roda/plugins/flash.rb +3 -3
  13. data/lib/roda/plugins/hooks.rb +1 -1
  14. data/lib/roda/plugins/indifferent_params.rb +3 -3
  15. data/lib/roda/plugins/middleware.rb +3 -8
  16. data/lib/roda/plugins/multi_route.rb +110 -18
  17. data/lib/roda/plugins/not_allowed.rb +3 -3
  18. data/lib/roda/plugins/path.rb +38 -0
  19. data/lib/roda/plugins/render.rb +18 -16
  20. data/lib/roda/plugins/render_each.rb +0 -2
  21. data/lib/roda/plugins/streaming.rb +1 -2
  22. data/lib/roda/plugins/view_subdirs.rb +7 -1
  23. data/lib/roda/version.rb +1 -1
  24. data/spec/assets/css/app.scss +1 -0
  25. data/spec/assets/css/no_access.css +1 -0
  26. data/spec/assets/css/raw.css +1 -0
  27. data/spec/assets/js/head/app.js +1 -0
  28. data/spec/integration_spec.rb +95 -3
  29. data/spec/matchers_spec.rb +2 -2
  30. data/spec/plugin/assets_spec.rb +413 -0
  31. data/spec/plugin/caching_spec.rb +335 -0
  32. data/spec/plugin/chunked_spec.rb +182 -0
  33. data/spec/plugin/default_headers_spec.rb +6 -5
  34. data/spec/plugin/error_email_spec.rb +76 -0
  35. data/spec/plugin/multi_route_spec.rb +120 -0
  36. data/spec/plugin/not_allowed_spec.rb +14 -3
  37. data/spec/plugin/path_spec.rb +29 -0
  38. data/spec/plugin/render_each_spec.rb +6 -1
  39. data/spec/plugin/symbol_matchers_spec.rb +7 -2
  40. data/spec/request_spec.rb +10 -0
  41. data/spec/response_spec.rb +47 -0
  42. data/spec/views/about.erb +1 -0
  43. data/spec/views/about.str +1 -0
  44. data/spec/views/content-yield.erb +1 -0
  45. data/spec/views/home.erb +2 -0
  46. data/spec/views/home.str +2 -0
  47. data/spec/views/layout-alternative.erb +2 -0
  48. data/spec/views/layout-yield.erb +3 -0
  49. data/spec/views/layout.erb +2 -0
  50. data/spec/views/layout.str +2 -0
  51. metadata +57 -2
@@ -0,0 +1,335 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe 'response.cache_control' do
4
+ it 'sets the Cache-Control header' do
5
+ app(:caching) do |r|
6
+ response.cache_control :public=>true, :no_cache=>true, :max_age => 60
7
+ end
8
+ header('Cache-Control').split(', ').sort.should == ['max-age=60', 'no-cache', 'public']
9
+ end
10
+
11
+ it 'does not add a Cache-Control header if it would be empty' do
12
+ app(:caching) do |r|
13
+ response.cache_control({})
14
+ end
15
+ header('Cache-Control').should == nil
16
+ end
17
+ end
18
+
19
+ describe 'response.expires' do
20
+ it 'sets the Cache-Control and Expires header' do
21
+ app(:caching) do |r|
22
+ response.expires 60, :public=>true, :no_cache=>true
23
+ end
24
+ header('Cache-Control').split(', ').sort.should == ['max-age=60', 'no-cache', 'public']
25
+ ((Time.httpdate(header('Expires')) - Time.now).round - 60).abs.should <= 1
26
+ end
27
+
28
+ it 'can be called with only one argument' do
29
+ app(:caching) do |r|
30
+ response.expires 60
31
+ end
32
+ header('Cache-Control').split(', ').sort.should == ['max-age=60']
33
+ ((Time.httpdate(header('Expires')) - Time.now).round - 60).abs.should <= 1
34
+ end
35
+ end
36
+
37
+ describe 'response.finish' do
38
+ it 'removes Content-Type and Content-Length for 304 responses' do
39
+ app(:caching) do |r|
40
+ response.status = 304
41
+ end
42
+ header('Content-Type').should == nil
43
+ header('Content-Length').should == nil
44
+ end
45
+
46
+ it 'does not change non-304 responses' do
47
+ app(:caching) do |r|
48
+ response.status = 200
49
+ end
50
+ header('Content-Type').should == 'text/html'
51
+ header('Content-Length').should == '0'
52
+ end
53
+ end
54
+
55
+ describe 'request.last_modified' do
56
+ it 'ignores nil' do
57
+ app(:caching) do |r|
58
+ r.last_modified nil
59
+ end
60
+ header('Last-Modified').should == nil
61
+ end
62
+
63
+ it 'does not change a status other than 200' do
64
+ app(:caching) do |r|
65
+ response.status = 201
66
+ r.last_modified Time.now
67
+ end
68
+ status.should == 201
69
+ status('HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT').should == 201
70
+ status('HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2000 23:43:52 GMT').should == 201
71
+ end
72
+ end
73
+
74
+ describe 'request.last_modified' do
75
+ def res(a={})
76
+ s, h, b = req(a)
77
+ h['Last-Modified'].should == @last_modified.httpdate
78
+ [s, b.join]
79
+ end
80
+
81
+ before(:all) do
82
+ lm = @last_modified = Time.now
83
+ app(:caching) do |r|
84
+ r.last_modified lm
85
+ 'ok'
86
+ end
87
+ end
88
+
89
+ it 'just sets Last-Modified if no If-Modified-Since header' do
90
+ res.should == [200, 'ok']
91
+ end
92
+
93
+ it 'just sets Last-Modified if bogus If-Modified-Since header' do
94
+ res('HTTP_IF_MODIFIED_SINCE' => 'a really weird date').should == [200, 'ok']
95
+ end
96
+
97
+ it 'just sets Last-Modified if modified since If-Modified-Since header' do
98
+ res('HTTP_IF_MODIFIED_SINCE' => (@last_modified - 1).httpdate).should == [200, 'ok']
99
+ end
100
+
101
+ it 'sets Last-Modified and returns 304 if modified on If-Modified-Since header' do
102
+ res('HTTP_IF_MODIFIED_SINCE' => @last_modified.httpdate).should == [304, '']
103
+ end
104
+
105
+ it 'sets Last-Modified and returns 304 if modified before If-Modified-Since header' do
106
+ res('HTTP_IF_MODIFIED_SINCE' => (@last_modified + 1).httpdate).should == [304, '']
107
+ end
108
+
109
+ it 'sets Last-Modified if If-None-Match header present' do
110
+ res('HTTP_IF_NONE_MATCH' => '*', 'HTTP_IF_MODIFIED_SINCE' => (@last_modified + 1).httpdate).should == [200, 'ok']
111
+ end
112
+
113
+ it 'sets Last-Modified if modified before If-Unmodified-Since header' do
114
+ res('HTTP_IF_UNMODIFIED_SINCE' => (@last_modified + 1).httpdate).should == [200, 'ok']
115
+ end
116
+
117
+ it 'sets Last-Modified if modified on If-Unmodified-Since header' do
118
+ res('HTTP_IF_UNMODIFIED_SINCE' => @last_modified.httpdate).should == [200, 'ok']
119
+ end
120
+
121
+ it 'sets Last-Modified and returns 412 if modified after If-Unmodified-Since header' do
122
+ res('HTTP_IF_UNMODIFIED_SINCE' => (@last_modified - 1).httpdate).should == [412, '']
123
+ end
124
+ end
125
+
126
+ describe 'request.etag' do
127
+ before(:all) do
128
+ app(:caching) do |r|
129
+ r.is "" do
130
+ response.status = r.env['status'] if r.env['status']
131
+ etag_opts = {}
132
+ etag_opts[:new_resource] = r.env['new_resource'] if r.env.has_key?('new_resource')
133
+ etag_opts[:weak] = r.env['weak'] if r.env.has_key?('weak')
134
+ r.etag 'foo', etag_opts
135
+ 'ok'
136
+ end
137
+ end
138
+ end
139
+
140
+ it 'uses a weak etag with the :weak option' do
141
+ header('ETag', 'weak'=>true).should == 'W/"foo"'
142
+ end
143
+
144
+ describe 'for GET requests' do
145
+ def res(a={})
146
+ s, h, b = req(a)
147
+ h['ETag'].should == '"foo"'
148
+ [s, b.join]
149
+ end
150
+
151
+ it "sets etag if no If-None-Match" do
152
+ res.should == [200, 'ok']
153
+ end
154
+
155
+ it "sets etag and returns 304 if If-None-Match is *" do
156
+ res('HTTP_IF_NONE_MATCH' => '*').should == [304, '']
157
+ end
158
+
159
+ it "sets etag and if If-None-Match is * and it is a new resource" do
160
+ res('HTTP_IF_NONE_MATCH' => '*', 'new_resource'=>true).should == [200, 'ok']
161
+ end
162
+
163
+ it "sets etag and returns 304 if If-None-Match is etag" do
164
+ res('HTTP_IF_NONE_MATCH' => '"foo"').should == [304, '']
165
+ end
166
+
167
+ it "sets etag and returns 304 if If-None-Match includes etag" do
168
+ res('HTTP_IF_NONE_MATCH' => '"bar", "foo"').should == [304, '']
169
+ end
170
+
171
+ it "sets etag if If-None-Match does not include etag" do
172
+ res('HTTP_IF_NONE_MATCH' => '"bar", "baz"').should == [200, 'ok']
173
+ end
174
+
175
+ it "sets etag and does not change status code if status code set and not 2xx or 304 if If-None-Match is etag" do
176
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>499).should == [499, 'ok']
177
+ end
178
+
179
+ it "sets etag and returns 304 if status code set to 2xx if If-None-Match is etag" do
180
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>201).should == [304, '']
181
+ end
182
+
183
+ it "sets etag and returns 304 if status code is already 304 if If-None-Match is etag" do
184
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>304).should == [304, '']
185
+ end
186
+
187
+ it "sets etag if If-Match is *" do
188
+ res('HTTP_IF_MATCH' => '*').should == [200, 'ok']
189
+ end
190
+
191
+ it "sets etag if If-Match is etag" do
192
+ res('HTTP_IF_MATCH' => '"foo"').should == [200, 'ok']
193
+ end
194
+
195
+ it "sets etag if If-Match includes etag" do
196
+ res('HTTP_IF_MATCH' => '"bar", "foo"').should == [200, 'ok']
197
+ end
198
+
199
+ it "sets etag and returns 412 if If-Match is * for new resources" do
200
+ res('HTTP_IF_MATCH' => '*', 'new_resource'=>true).should == [412, '']
201
+ end
202
+
203
+ it "sets etag if If-Match does not include etag" do
204
+ res('HTTP_IF_MATCH' => '"bar", "baz"', 'new_resource'=>true).should == [412, '']
205
+ end
206
+ end
207
+
208
+ describe 'for PUT requests' do
209
+ def res(a={})
210
+ s, h, b = req(a.merge('REQUEST_METHOD'=>'PUT'))
211
+ h['ETag'].should == '"foo"'
212
+ [s, b.join]
213
+ end
214
+
215
+ it "sets etag if no If-None-Match" do
216
+ res.should == [200, 'ok']
217
+ end
218
+
219
+ it "sets etag and returns 412 if If-None-Match is *" do
220
+ res('HTTP_IF_NONE_MATCH' => '*').should == [412, '']
221
+ end
222
+
223
+ it "sets etag and if If-None-Match is * and it is a new resource" do
224
+ res('HTTP_IF_NONE_MATCH' => '*', 'new_resource'=>true).should == [200, 'ok']
225
+ end
226
+
227
+ it "sets etag and returns 412 if If-None-Match is etag" do
228
+ res('HTTP_IF_NONE_MATCH' => '"foo"').should == [412, '']
229
+ end
230
+
231
+ it "sets etag and returns 412 if If-None-Match includes etag" do
232
+ res('HTTP_IF_NONE_MATCH' => '"bar", "foo"').should == [412, '']
233
+ end
234
+
235
+ it "sets etag if If-None-Match does not include etag" do
236
+ res('HTTP_IF_NONE_MATCH' => '"bar", "baz"').should == [200, 'ok']
237
+ end
238
+
239
+ it "sets etag and does not change status code if status code set and not 2xx or 304 if If-None-Match is etag" do
240
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>499).should == [499, 'ok']
241
+ end
242
+
243
+ it "sets etag and returns 304 if status code set to 2xx if If-None-Match is etag" do
244
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>201).should == [412, '']
245
+ end
246
+
247
+ it "sets etag and returns 304 if status code is already 304 if If-None-Match is etag" do
248
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>304).should == [412, '']
249
+ end
250
+
251
+ it "sets etag if If-Match is *" do
252
+ res('HTTP_IF_MATCH' => '*').should == [200, 'ok']
253
+ end
254
+
255
+ it "sets etag if If-Match is etag" do
256
+ res('HTTP_IF_MATCH' => '"foo"').should == [200, 'ok']
257
+ end
258
+
259
+ it "sets etag if If-Match includes etag" do
260
+ res('HTTP_IF_MATCH' => '"bar", "foo"').should == [200, 'ok']
261
+ end
262
+
263
+ it "sets etag and returns 412 if If-Match is * for new resources" do
264
+ res('HTTP_IF_MATCH' => '*', 'new_resource'=>true).should == [412, '']
265
+ end
266
+
267
+ it "sets etag if If-Match does not include etag" do
268
+ res('HTTP_IF_MATCH' => '"bar", "baz"', 'new_resource'=>true).should == [412, '']
269
+ end
270
+ end
271
+
272
+ describe 'for POST requests' do
273
+ def res(a={})
274
+ s, h, b = req(a.merge('REQUEST_METHOD'=>'POST'))
275
+ h['ETag'].should == '"foo"'
276
+ [s, b.join]
277
+ end
278
+
279
+ it "sets etag if no If-None-Match" do
280
+ res.should == [200, 'ok']
281
+ end
282
+
283
+ it "sets etag and returns 412 if If-None-Match is * and it is not a new resource" do
284
+ res('HTTP_IF_NONE_MATCH' => '*', 'new_resource'=>false).should == [412, '']
285
+ end
286
+
287
+ it "sets etag and if If-None-Match is *" do
288
+ res('HTTP_IF_NONE_MATCH' => '*').should == [200, 'ok']
289
+ end
290
+
291
+ it "sets etag and returns 412 if If-None-Match is etag" do
292
+ res('HTTP_IF_NONE_MATCH' => '"foo"').should == [412, '']
293
+ end
294
+
295
+ it "sets etag and returns 412 if If-None-Match includes etag" do
296
+ res('HTTP_IF_NONE_MATCH' => '"bar", "foo"').should == [412, '']
297
+ end
298
+
299
+ it "sets etag if If-None-Match does not include etag" do
300
+ res('HTTP_IF_NONE_MATCH' => '"bar", "baz"').should == [200, 'ok']
301
+ end
302
+
303
+ it "sets etag and does not change status code if status code set and not 2xx or 304 if If-None-Match is etag" do
304
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>499).should == [499, 'ok']
305
+ end
306
+
307
+ it "sets etag and returns 304 if status code set to 2xx if If-None-Match is etag" do
308
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>201).should == [412, '']
309
+ end
310
+
311
+ it "sets etag and returns 304 if status code is already 304 if If-None-Match is etag" do
312
+ res('HTTP_IF_NONE_MATCH' => '"foo"', 'status'=>304).should == [412, '']
313
+ end
314
+
315
+ it "sets etag if If-Match is * and this is not a new resource" do
316
+ res('HTTP_IF_MATCH' => '*', 'new_resource'=>false).should == [200, 'ok']
317
+ end
318
+
319
+ it "sets etag if If-Match is etag" do
320
+ res('HTTP_IF_MATCH' => '"foo"').should == [200, 'ok']
321
+ end
322
+
323
+ it "sets etag if If-Match includes etag" do
324
+ res('HTTP_IF_MATCH' => '"bar", "foo"').should == [200, 'ok']
325
+ end
326
+
327
+ it "sets etag and returns 412 if If-Match is * for new resources" do
328
+ res('HTTP_IF_MATCH' => '*').should == [412, '']
329
+ end
330
+
331
+ it "sets etag if If-Match does not include etag" do
332
+ res('HTTP_IF_MATCH' => '"bar", "baz"', 'new_resource'=>true).should == [412, '']
333
+ end
334
+ end
335
+ end
@@ -0,0 +1,182 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ begin
4
+ require 'tilt/erb'
5
+ rescue LoadError
6
+ warn "tilt not installed, skipping chunked plugin test"
7
+ else
8
+ describe "chunked plugin" do
9
+ def cbody(env={})
10
+ b = ''
11
+ req({'HTTP_VERSION'=>'HTTP/1.1'}.merge(env))[2].each{|s| b << s}
12
+ b
13
+ end
14
+
15
+ it "streams templates in chunked encoding only if HTTP 1.1 is used" do
16
+ app(:chunked) do |r|
17
+ chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
18
+ end
19
+
20
+ cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
21
+ body.should == "hmt"
22
+ end
23
+
24
+ it "hex encodes chunk sizes" do
25
+ m = 'm' * 31
26
+ app(:chunked) do |r|
27
+ chunked(:inline=>m, :layout=>{:inline=>'h<%= yield %>t'})
28
+ end
29
+
30
+ cbody.should == "1\r\nh\r\n1f\r\n#{m}\r\n1\r\nt\r\n0\r\n\r\n"
31
+ body.should == "h#{m}t"
32
+ end
33
+
34
+ it "accepts a block that is called after layout yielding but before content when streaming" do
35
+ app(:chunked) do |r|
36
+ chunked(:inline=>'m<%= @h %>', :layout=>{:inline=>'<%= @h %><%= yield %>t'}) do
37
+ @h = 'h'
38
+ end
39
+ end
40
+
41
+ cbody.should == "2\r\nmh\r\n1\r\nt\r\n0\r\n\r\n"
42
+ body.should == "hmht"
43
+ end
44
+
45
+ it "works when a layout is not used" do
46
+ app(:chunked) do |r|
47
+ chunked(:inline=>'m', :layout=>nil)
48
+ end
49
+
50
+ cbody.should == "1\r\nm\r\n0\r\n\r\n"
51
+ body.should == "m"
52
+ end
53
+
54
+ it "streams partial template responses if flush is used in content template" do
55
+ app(:chunked) do |r|
56
+ chunked(:inline=>'m<%= flush %>n', :layout=>{:inline=>'h<%= yield %>t'})
57
+ end
58
+
59
+ cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nn\r\n1\r\nt\r\n0\r\n\r\n"
60
+ body.should == "hmnt"
61
+ end
62
+
63
+ it "streams partial template responses if flush is used in layout template" do
64
+ app(:chunked) do |r|
65
+ chunked(:inline=>'m', :layout=>{:inline=>'h<%= flush %>i<%= yield %>t'})
66
+ end
67
+
68
+ cbody.should == "1\r\nh\r\n1\r\ni\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
69
+ body.should == "himt"
70
+ end
71
+
72
+ it "does not stream if no_chunk! is used" do
73
+ app(:chunked) do |r|
74
+ no_chunk!
75
+ chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
76
+ end
77
+
78
+ cbody.should == "hmt"
79
+ body.should == "hmt"
80
+ end
81
+
82
+ it "streams existing response body before call" do
83
+ app(:chunked) do |r|
84
+ response.write('a')
85
+ response.write chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
86
+ end
87
+
88
+ cbody.should == "1\r\na\r\n1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
89
+ body.should == "ahmt"
90
+ end
91
+
92
+ it "should not include Content-Length header even if body is already written to" do
93
+ app(:chunked) do |r|
94
+ response.write('a')
95
+ response.write chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
96
+ end
97
+
98
+ header('Content-Length', 'HTTP_VERSION'=>'HTTP/1.1').should == nil
99
+ header('Content-Length', 'HTTP_VERSION'=>'HTTP/1.0').should == '4'
100
+ end
101
+
102
+ it "stream template responses for view if :chunk_by_default is used" do
103
+ app(:bare) do
104
+ plugin :chunked, :chunk_by_default=>true
105
+ route do |r|
106
+ view(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
107
+ end
108
+ end
109
+
110
+ cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
111
+ body.should == "hmt"
112
+ end
113
+
114
+ it "uses Transfer-Encoding header when chunking" do
115
+ app(:chunked) do |r|
116
+ chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
117
+ end
118
+
119
+ header('Transfer-Encoding', 'HTTP_VERSION'=>'HTTP/1.1').should == 'chunked'
120
+ header('Transfer-Encoding', 'HTTP_VERSION'=>'HTTP/1.0').should == nil
121
+ end
122
+
123
+ it "uses given :headers when chunking" do
124
+ app(:bare) do
125
+ plugin :chunked, :headers=>{'Foo'=>'bar'}
126
+ route do |r|
127
+ chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %>t'})
128
+ end
129
+ end
130
+
131
+ header('Foo', 'HTTP_VERSION'=>'HTTP/1.1').should == 'bar'
132
+ header('Foo', 'HTTP_VERSION'=>'HTTP/1.0').should == nil
133
+ end
134
+
135
+ it "handles multiple arguments to chunked" do
136
+ app(:bare) do
137
+ plugin :chunked, :chunk_by_default=>true
138
+ plugin :render, :views => "./spec/views"
139
+ route do |r|
140
+ chunked('about', :locals=>{:title=>'m'}, :layout=>{:inline=>'h<%= yield %>t'})
141
+ end
142
+ end
143
+
144
+ cbody.should == "1\r\nh\r\nb\r\n<h1>m</h1>\n\r\n1\r\nt\r\n0\r\n\r\n"
145
+ body.should == "h<h1>m</h1>\nt"
146
+ end
147
+
148
+ it "handles multiple hash arguments to chunked" do
149
+ app(:chunked) do |r|
150
+ chunked({:inline=>'m'}, :layout=>{:inline=>'h<%= yield %>t'})
151
+ end
152
+
153
+ cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
154
+ body.should == "hmt"
155
+ end
156
+
157
+ it "handles :layout_opts option" do
158
+ app(:chunked) do |r|
159
+ chunked(:inline=>'m', :layout=>{:inline=>'<%= h %><%= yield %>t'}, :layout_opts=>{:locals=>{:h=>'h'}})
160
+ end
161
+
162
+ cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\nt\r\n0\r\n\r\n"
163
+ body.should == "hmt"
164
+ end
165
+
166
+ it "uses handle_chunk_error for handling errors when chunking" do
167
+ app(:chunked) do |r|
168
+ chunked(:inline=>'m', :layout=>{:inline=>'h<%= yield %><% raise %>'})
169
+ end
170
+ proc{cbody}.should raise_error
171
+ proc{body}.should raise_error
172
+
173
+ app.send(:define_method, :handle_chunk_error) do |v|
174
+ @_out_buf = 'e'
175
+ flush
176
+ end
177
+
178
+ cbody.should == "1\r\nh\r\n1\r\nm\r\n1\r\ne\r\n0\r\n\r\n"
179
+ proc{body}.should raise_error
180
+ end
181
+ end
182
+ end