roda 1.0.0 → 1.1.0

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