roda 1.1.0 → 1.2.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +70 -0
  3. data/README.rdoc +261 -302
  4. data/Rakefile +1 -1
  5. data/doc/release_notes/1.2.0.txt +406 -0
  6. data/lib/roda.rb +206 -124
  7. data/lib/roda/plugins/all_verbs.rb +11 -10
  8. data/lib/roda/plugins/assets.rb +5 -5
  9. data/lib/roda/plugins/backtracking_array.rb +12 -5
  10. data/lib/roda/plugins/caching.rb +10 -8
  11. data/lib/roda/plugins/class_level_routing.rb +94 -0
  12. data/lib/roda/plugins/content_for.rb +6 -0
  13. data/lib/roda/plugins/default_headers.rb +4 -11
  14. data/lib/roda/plugins/delay_build.rb +42 -0
  15. data/lib/roda/plugins/delegate.rb +64 -0
  16. data/lib/roda/plugins/drop_body.rb +33 -0
  17. data/lib/roda/plugins/empty_root.rb +48 -0
  18. data/lib/roda/plugins/environments.rb +68 -0
  19. data/lib/roda/plugins/error_email.rb +1 -2
  20. data/lib/roda/plugins/error_handler.rb +1 -1
  21. data/lib/roda/plugins/halt.rb +7 -5
  22. data/lib/roda/plugins/head.rb +4 -2
  23. data/lib/roda/plugins/header_matchers.rb +17 -9
  24. data/lib/roda/plugins/hooks.rb +16 -32
  25. data/lib/roda/plugins/json.rb +4 -10
  26. data/lib/roda/plugins/mailer.rb +233 -0
  27. data/lib/roda/plugins/match_affix.rb +48 -0
  28. data/lib/roda/plugins/multi_route.rb +9 -11
  29. data/lib/roda/plugins/multi_run.rb +81 -0
  30. data/lib/roda/plugins/named_templates.rb +93 -0
  31. data/lib/roda/plugins/not_allowed.rb +43 -48
  32. data/lib/roda/plugins/path.rb +63 -2
  33. data/lib/roda/plugins/render.rb +79 -48
  34. data/lib/roda/plugins/render_each.rb +6 -0
  35. data/lib/roda/plugins/sinatra_helpers.rb +523 -0
  36. data/lib/roda/plugins/slash_path_empty.rb +25 -0
  37. data/lib/roda/plugins/static_path_info.rb +64 -0
  38. data/lib/roda/plugins/streaming.rb +1 -1
  39. data/lib/roda/plugins/view_subdirs.rb +12 -8
  40. data/lib/roda/version.rb +1 -1
  41. data/spec/integration_spec.rb +33 -0
  42. data/spec/plugin/backtracking_array_spec.rb +24 -18
  43. data/spec/plugin/class_level_routing_spec.rb +138 -0
  44. data/spec/plugin/delay_build_spec.rb +23 -0
  45. data/spec/plugin/delegate_spec.rb +20 -0
  46. data/spec/plugin/drop_body_spec.rb +20 -0
  47. data/spec/plugin/empty_root_spec.rb +14 -0
  48. data/spec/plugin/environments_spec.rb +31 -0
  49. data/spec/plugin/h_spec.rb +1 -3
  50. data/spec/plugin/header_matchers_spec.rb +14 -0
  51. data/spec/plugin/hooks_spec.rb +3 -5
  52. data/spec/plugin/mailer_spec.rb +191 -0
  53. data/spec/plugin/match_affix_spec.rb +22 -0
  54. data/spec/plugin/multi_run_spec.rb +31 -0
  55. data/spec/plugin/named_templates_spec.rb +65 -0
  56. data/spec/plugin/path_spec.rb +66 -2
  57. data/spec/plugin/render_spec.rb +46 -1
  58. data/spec/plugin/sinatra_helpers_spec.rb +534 -0
  59. data/spec/plugin/slash_path_empty_spec.rb +22 -0
  60. data/spec/plugin/static_path_info_spec.rb +50 -0
  61. data/spec/request_spec.rb +23 -0
  62. data/spec/response_spec.rb +12 -1
  63. metadata +48 -6
@@ -59,9 +59,14 @@ describe "render plugin" do
59
59
  end
60
60
 
61
61
  it "custom default layout support" do
62
- app.render_opts[:layout] = "layout-alternative"
62
+ app.plugin :render, :layout => "layout-alternative"
63
63
  body("/home").strip.should == "<title>Alternative Layout: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"
64
64
  end
65
+
66
+ it "using hash for :layout" do
67
+ app.plugin :render, :layout => {:inline=> 'a<%= yield %>b'}
68
+ body("/home").strip.should == "a<h1>Home</h1>\n<p>Hello Agent Smith</p>\nb"
69
+ end
65
70
  end
66
71
 
67
72
  describe "render plugin" do
@@ -119,6 +124,46 @@ describe "render plugin" do
119
124
  body.strip.should == '<%= bar %>'
120
125
  end
121
126
 
127
+ it "template renders with :template opts" do
128
+ app(:render) do
129
+ render_opts[:views] = "./spec/views"
130
+ render(:template=>"about", :locals=>{:title => "About Roda"})
131
+ end
132
+ body.strip.should == "<h1>About Roda</h1>"
133
+ end
134
+
135
+ it "template renders with :template_class opts" do
136
+ app(:render) do
137
+ @a = 1
138
+ render(:inline=>'i#{@a}', :template_class=>::Tilt[:str])
139
+ end
140
+ body.should == "i1"
141
+ end
142
+
143
+ it "template cache respects :opts" do
144
+ c = Class.new do
145
+ def initialize(path, _, opts)
146
+ @path = path
147
+ @opts = opts
148
+ end
149
+ def render(*)
150
+ "#{@path}-#{@opts[:foo]}"
151
+ end
152
+ end
153
+
154
+ app(:render) do |r|
155
+ r.is "a" do
156
+ render(:inline=>"i", :template_class=>c, :opts=>{:foo=>'a'})
157
+ end
158
+ r.is "b" do
159
+ render(:inline=>"i", :template_class=>c, :opts=>{:foo=>'b'})
160
+ end
161
+ end
162
+
163
+ body('/a').should == "i-a"
164
+ body('/b').should == "i-b"
165
+ end
166
+
122
167
  it "render_opts inheritance" do
123
168
  c = Class.new(Roda)
124
169
  c.plugin :render
@@ -0,0 +1,534 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ require 'uri'
4
+
5
+ describe "sinatra_helpers plugin" do
6
+ def sin_app(&block)
7
+ app(:sinatra_helpers, &block)
8
+ end
9
+
10
+ def status_app(code, &block)
11
+ #code += 2 if [204, 205, 304].include? code
12
+ block ||= proc{}
13
+ sin_app do |r|
14
+ status code
15
+ instance_eval(&block).inspect
16
+ end
17
+ end
18
+
19
+ it 'status returns the response status code if not given an argument' do
20
+ status_app(207){status}
21
+ body.should == "207"
22
+ end
23
+
24
+ it 'status sets the response status code if given an argument' do
25
+ status_app 207
26
+ status.should == 207
27
+ end
28
+
29
+ it 'not_found? is true only if status == 404' do
30
+ status_app(404){not_found?}
31
+ body.should == 'true'
32
+ status_app(405){not_found?}
33
+ body.should == 'false'
34
+ status_app(403){not_found?}
35
+ body.should == 'false'
36
+ end
37
+
38
+ it 'informational? is true only for 1xx status' do
39
+ status_app(100 + rand(100)){informational?}
40
+ body.should == 'true'
41
+ status_app(200 + rand(400)){informational?}
42
+ body.should == 'false'
43
+ end
44
+
45
+ it 'success? is true only for 2xx status' do
46
+ status_app(200 + rand(100)){success?}
47
+ body.should == 'true'
48
+ status_app(100 + rand(100)){success?}
49
+ body.should == 'false'
50
+ status_app(300 + rand(300)){success?}
51
+ body.should == 'false'
52
+ end
53
+
54
+ it 'redirect? is true only for 3xx status' do
55
+ status_app(300 + rand(100)){redirect?}
56
+ body.should == 'true'
57
+ status_app(200 + rand(100)){redirect?}
58
+ body.should == 'false'
59
+ status_app(400 + rand(200)){redirect?}
60
+ body.should == 'false'
61
+ end
62
+
63
+ it 'client_error? is true only for 4xx status' do
64
+ status_app(400 + rand(100)){client_error?}
65
+ body.should == 'true'
66
+ status_app(200 + rand(200)){client_error?}
67
+ body.should == 'false'
68
+ status_app(500 + rand(100)){client_error?}
69
+ body.should == 'false'
70
+ end
71
+
72
+ it 'server_error? is true only for 5xx status' do
73
+ status_app(500 + rand(100)){server_error?}
74
+ body.should == 'true'
75
+ status_app(200 + rand(300)){server_error?}
76
+ body.should == 'false'
77
+ end
78
+
79
+ describe 'body' do
80
+ it 'takes a block for deferred body generation' do
81
+ sin_app{body{'Hello World'}; nil}
82
+ body.should == 'Hello World'
83
+ header('Content-Length').should == '11'
84
+ end
85
+
86
+ it 'takes a String, Array, or other object responding to #each' do
87
+ sin_app{body 'Hello World'; nil}
88
+ body.should == 'Hello World'
89
+ header('Content-Length').should == '11'
90
+
91
+ sin_app{body ['Hello ', 'World']; nil}
92
+ body.should == 'Hello World'
93
+ header('Content-Length').should == '11'
94
+
95
+ o = Object.new
96
+ def o.each; yield 'Hello World' end
97
+ sin_app{body o; nil}
98
+ body.should == 'Hello World'
99
+ header('Content-Length').should == '11'
100
+ end
101
+ end
102
+
103
+ describe 'redirect' do
104
+ it 'uses a 302 when only a path is given' do
105
+ sin_app do
106
+ redirect '/foo'
107
+ fail 'redirect should halt'
108
+ end
109
+
110
+ status.should == 302
111
+ body.should == ''
112
+ header('Location').should == '/foo'
113
+ end
114
+
115
+ it 'adds script_name if given a path' do
116
+ sin_app{redirect "/foo"}
117
+ header('Location', '/bar', 'SCRIPT_NAME'=>'/foo').should == '/foo'
118
+ end
119
+
120
+ it 'does not adds script_name if not given a path' do
121
+ sin_app{redirect}
122
+ header('Location', '/bar', 'SCRIPT_NAME'=>'/foo', 'REQUEST_METHOD'=>'POST').should == '/foo/bar'
123
+ end
124
+
125
+ it 'respects :absolute_redirects option' do
126
+ sin_app{redirect}
127
+ app.opts[:absolute_redirects] = true
128
+ header('Location', '/bar', 'HTTP_HOST'=>'example.org', 'SCRIPT_NAME'=>'/foo', 'REQUEST_METHOD'=>'POST').should == 'http://example.org/foo/bar'
129
+ end
130
+
131
+ it 'respects :prefixed_redirects option' do
132
+ sin_app{redirect "/bar"}
133
+ app.opts[:prefixed_redirects] = true
134
+ header('Location', 'SCRIPT_NAME'=>'/foo').should == '/foo/bar'
135
+ end
136
+
137
+ it 'ignores :prefix_redirects option if not given a path' do
138
+ sin_app{redirect}
139
+ app.opts[:prefix_redirects] = true
140
+ header('Location', "/bar", 'SCRIPT_NAME'=>'/foo', 'REQUEST_METHOD'=>'POST').should == '/foo/bar'
141
+ end
142
+
143
+ it 'uses the code given when specified' do
144
+ sin_app{redirect '/foo', 301}
145
+ status.should == 301
146
+ end
147
+
148
+ it 'redirects back to request.referer when passed back' do
149
+ sin_app{redirect back}
150
+ header('Location', 'HTTP_REFERER' => '/foo').should == '/foo'
151
+ end
152
+
153
+ it 'uses 303 for post requests if request is HTTP 1.1, 302 for 1.0' do
154
+ sin_app{redirect '/foo'}
155
+ status('HTTP_VERSION' => 'HTTP/1.1', 'REQUEST_METHOD'=>'POST').should == 303
156
+ status('HTTP_VERSION' => 'HTTP/1.0', 'REQUEST_METHOD'=>'POST').should == 302
157
+ end
158
+ end
159
+
160
+ describe 'error' do
161
+ it 'sets a status code and halts' do
162
+ sin_app do
163
+ error
164
+ fail 'error should halt'
165
+ end
166
+
167
+ status.should == 500
168
+ body.should == ''
169
+ end
170
+
171
+ it 'accepts status code' do
172
+ sin_app{error 501}
173
+ status.should == 501
174
+ body.should == ''
175
+ end
176
+
177
+ it 'accepts body' do
178
+ sin_app{error '501'}
179
+ status.should == 500
180
+ body.should == '501'
181
+ end
182
+
183
+ it 'accepts status code and body' do
184
+ sin_app{error 502, '501'}
185
+ status.should == 502
186
+ body.should == '501'
187
+ end
188
+ end
189
+
190
+ describe 'not_found' do
191
+ it 'halts with a 404 status' do
192
+ sin_app do
193
+ not_found
194
+ fail 'not_found should halt'
195
+ end
196
+
197
+ status.should == 404
198
+ body.should == ''
199
+ end
200
+
201
+ it 'accepts optional body' do
202
+ sin_app{not_found 'nf'}
203
+ status.should == 404
204
+ body.should == 'nf'
205
+ end
206
+ end
207
+
208
+ describe 'headers' do
209
+ it 'sets headers on the response object when given a Hash' do
210
+ sin_app do
211
+ headers 'X-Foo' => 'bar'
212
+ 'kthx'
213
+ end
214
+
215
+ header('X-Foo').should == 'bar'
216
+ body.should == 'kthx'
217
+ end
218
+
219
+ it 'returns the response headers hash when no hash provided' do
220
+ sin_app{headers['X-Foo'] = 'bar'}
221
+ header('X-Foo').should == 'bar'
222
+ end
223
+ end
224
+
225
+ describe 'mime_type' do
226
+ before do
227
+ sin_app{|r| mime_type(r.path).to_s}
228
+ end
229
+
230
+ it "looks up mime types in Rack's MIME registry" do
231
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
232
+ body('foo').should == 'application/foo'
233
+ body(:foo).should == 'application/foo'
234
+ body('.foo').should == 'application/foo'
235
+ end
236
+
237
+ it 'returns nil when given nil' do
238
+ body('PATH_INFO'=>nil).should == ''
239
+ end
240
+
241
+ it 'returns nil when media type not registered' do
242
+ body('bizzle').should == ''
243
+ end
244
+
245
+ it 'returns the argument when given a media type string' do
246
+ body('text/plain').should == 'text/plain'
247
+ end
248
+
249
+ it 'supports mime types registered at the class level' do
250
+ app.mime_type :foo, 'application/foo'
251
+ body(:foo).should == 'application/foo'
252
+ end
253
+ end
254
+
255
+ describe 'content_type' do
256
+ it 'sets the Content-Type header' do
257
+ sin_app do
258
+ content_type 'text/plain'
259
+ 'Hello World'
260
+ end
261
+
262
+ header('Content-Type').should == 'text/plain'
263
+ body.should == 'Hello World'
264
+ end
265
+
266
+ it 'takes media type parameters (like charset=)' do
267
+ sin_app{content_type 'text/html', :charset => 'latin1'}
268
+ header('Content-Type').should == 'text/html;charset=latin1'
269
+ end
270
+
271
+ it "looks up symbols in Rack's mime types dictionary" do
272
+ sin_app{content_type :foo}
273
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
274
+ header('Content-Type').should == 'application/foo'
275
+ end
276
+
277
+ it 'fails when no mime type is registered for the argument provided' do
278
+ sin_app{content_type :bizzle}
279
+ proc{body}.should raise_error(Roda::RodaError)
280
+ end
281
+
282
+ it 'handles already present params' do
283
+ sin_app{content_type 'foo/bar;level=1', :charset => 'utf-8'}
284
+ header('Content-Type').should == 'foo/bar;level=1, charset=utf-8'
285
+ end
286
+
287
+ it 'does not add charset if present' do
288
+ sin_app{content_type 'text/plain;charset=utf-16', :charset => 'utf-8'}
289
+ header('Content-Type').should == 'text/plain;charset=utf-16'
290
+ end
291
+
292
+ it 'properly encodes parameters with delimiter characters' do
293
+ sin_app{|r| content_type 'image/png', :comment => r.path }
294
+ header('Content-Type', 'Hello, world!').should == 'image/png;comment="Hello, world!"'
295
+ header('Content-Type', 'semi;colon').should == 'image/png;comment="semi;colon"'
296
+ header('Content-Type', '"Whatever."').should == 'image/png;comment="\"Whatever.\""'
297
+ end
298
+ end
299
+
300
+ describe 'attachment' do
301
+ before do
302
+ sin_app{|r| attachment r.path; 'b'}
303
+ end
304
+
305
+ it 'sets the Content-Disposition header' do
306
+ header('Content-Disposition', '/foo/test.xml').should == 'attachment; filename="test.xml"'
307
+ body.should == 'b'
308
+ end
309
+
310
+ it 'sets the Content-Disposition header even when a filename is not given' do
311
+ sin_app{attachment}
312
+ header('Content-Disposition', '/foo/test.xml').should == 'attachment'
313
+ end
314
+
315
+ it 'sets the Content-Type header' do
316
+ header('Content-Type', 'test.xml').should == 'application/xml'
317
+ end
318
+
319
+ it 'does not modify the default Content-Type without a file extension' do
320
+ header('Content-Type', 'README').should == 'text/html'
321
+ end
322
+
323
+ it 'should not modify the Content-Type if it is already set' do
324
+ sin_app do
325
+ content_type :atom
326
+ attachment 'test.xml'
327
+ end
328
+
329
+ header('Content-Type', 'README').should == 'application/atom+xml'
330
+ end
331
+ end
332
+
333
+ describe 'send_file' do
334
+ before(:all) do
335
+ file = @file = 'spec/assets/css/raw.css'
336
+ @content = File.read(@file)
337
+ sin_app{send_file file, env['OPTS'] || {}}
338
+ end
339
+
340
+ it "sends the contents of the file" do
341
+ status.should == 200
342
+ body.should == @content
343
+ end
344
+
345
+ it 'sets the Content-Type response header if a mime-type can be located' do
346
+ header('Content-Type').should == 'text/css'
347
+ end
348
+
349
+ it 'sets the Content-Type response header if type option is set to a file extension' do
350
+ header('Content-Type', 'OPTS'=>{:type => 'html'}).should == 'text/html'
351
+ end
352
+
353
+ it 'sets the Content-Type response header if type option is set to a mime type' do
354
+ header('Content-Type', 'OPTS'=>{:type => 'application/octet-stream'}).should == 'application/octet-stream'
355
+ end
356
+
357
+ it 'sets the Content-Length response header' do
358
+ header('Content-Length').should == @content.length.to_s
359
+ end
360
+
361
+ it 'sets the Last-Modified response header' do
362
+ header('Last-Modified').should == File.mtime(@file).httpdate
363
+ end
364
+
365
+ it 'allows passing in a different Last-Modified response header with :last_modified' do
366
+ time = Time.now
367
+ @app.plugin :caching
368
+ header('Last-Modified', 'OPTS'=>{:last_modified => time}).should == time.httpdate
369
+ end
370
+
371
+ it "returns a 404 when not found" do
372
+ sin_app{send_file 'this-file-does-not-exist.txt'}
373
+ status.should == 404
374
+ end
375
+
376
+ it "does not set the Content-Disposition header by default" do
377
+ header('Content-Disposition').should == nil
378
+ end
379
+
380
+ it "sets the Content-Disposition header when :disposition set to 'attachment'" do
381
+ header('Content-Disposition', 'OPTS'=>{:disposition => 'attachment'}).should == 'attachment; filename="raw.css"'
382
+ end
383
+
384
+ it "does not set add a file name if filename is false" do
385
+ header('Content-Disposition', 'OPTS'=>{:disposition => 'inline', :filename=>false}).should == 'inline'
386
+ end
387
+
388
+ it "sets the Content-Disposition header when :disposition set to 'inline'" do
389
+ header('Content-Disposition', 'OPTS'=>{:disposition => 'inline'}).should == 'inline; filename="raw.css"'
390
+ end
391
+
392
+ it "sets the Content-Disposition header when :filename provided" do
393
+ header('Content-Disposition', 'OPTS'=>{:filename => 'foo.txt'}).should == 'attachment; filename="foo.txt"'
394
+ end
395
+
396
+ it 'allows setting a custom status code' do
397
+ status('OPTS'=>{:status=>201}).should == 201
398
+ end
399
+
400
+ it "is able to send files with unknown mime type" do
401
+ header('Content-Type', 'OPTS'=>{:type => '.foobar'}).should == 'application/octet-stream'
402
+ end
403
+
404
+ it "does not override Content-Type if already set and no explicit type is given" do
405
+ file = @file
406
+ sin_app do
407
+ content_type :png
408
+ send_file file
409
+ end
410
+ header('Content-Type').should == 'image/png'
411
+ end
412
+
413
+ it "does override Content-Type even if already set, if explicit type is given" do
414
+ file = @file
415
+ sin_app do
416
+ content_type :png
417
+ send_file file, :type => :gif
418
+ end
419
+ header('Content-Type').should == 'image/gif'
420
+ end
421
+ end
422
+
423
+ describe 'uri' do
424
+ describe "without arguments" do
425
+ before do
426
+ sin_app{uri}
427
+ end
428
+
429
+ it 'generates absolute urls' do
430
+ body('HTTP_HOST'=>'example.org').should == 'http://example.org/'
431
+ end
432
+
433
+ it 'includes path_info' do
434
+ body('/foo', 'HTTP_HOST'=>'example.org').should == 'http://example.org/foo'
435
+ end
436
+
437
+ it 'includes script_name' do
438
+ body('/bar', 'HTTP_HOST'=>'example.org', "SCRIPT_NAME" => '/foo').should == 'http://example.org/foo/bar'
439
+ end
440
+
441
+ it 'handles standard HTTP and HTTPS ports' do
442
+ body('SERVER_NAME'=>'example.org', 'SERVER_PORT' => '80').should == 'http://example.org/'
443
+ body('SERVER_NAME'=>'example.org', 'SERVER_PORT' => '443', 'HTTPS'=>'on').should == 'https://example.org/'
444
+ end
445
+
446
+ it 'handles non-standard HTTP port' do
447
+ body('SERVER_NAME'=>'example.org', 'SERVER_PORT' => '81').should == 'http://example.org:81/'
448
+ body('SERVER_NAME'=>'example.org', 'SERVER_PORT' => '443').should == 'http://example.org:443/'
449
+ end
450
+
451
+ it 'handles non-standard HTTPS port' do
452
+ body('SERVER_NAME'=>'example.org', 'SERVER_PORT' => '444', 'HTTPS'=>'on').should == 'https://example.org:444/'
453
+ body('SERVER_NAME'=>'example.org', 'SERVER_PORT' => '80', 'HTTPS'=>'on').should == 'https://example.org:80/'
454
+ end
455
+
456
+ it 'handles reverse proxy' do
457
+ body('SERVER_NAME'=>'example.org', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080').should == 'http://example.com/'
458
+ end
459
+ end
460
+
461
+ it 'allows passing an alternative to path_info' do
462
+ sin_app{uri '/bar'}
463
+ body('HTTP_HOST'=>'example.org').should == 'http://example.org/bar'
464
+ body('HTTP_HOST'=>'example.org', "SCRIPT_NAME" => '/foo').should == 'http://example.org/foo/bar'
465
+ end
466
+
467
+ it 'handles absolute URIs' do
468
+ sin_app{uri 'http://google.com'}
469
+ body('HTTP_HOST'=>'example.org').should == 'http://google.com'
470
+ end
471
+
472
+ it 'handles different protocols' do
473
+ sin_app{uri 'mailto:jsmith@example.com'}
474
+ body('HTTP_HOST'=>'example.org').should == 'mailto:jsmith@example.com'
475
+ end
476
+
477
+ it 'allows turning off host' do
478
+ sin_app{uri '/foo', false}
479
+ body('HTTP_HOST'=>'example.org').should == '/foo'
480
+ body('HTTP_HOST'=>'example.org', "SCRIPT_NAME" => '/bar').should == '/bar/foo'
481
+ end
482
+
483
+ it 'allows turning off script_name' do
484
+ sin_app{uri '/foo', true, false}
485
+ body('HTTP_HOST'=>'example.org').should == 'http://example.org/foo'
486
+ body('HTTP_HOST'=>'example.org', "SCRIPT_NAME" => '/bar').should == 'http://example.org/foo'
487
+ end
488
+
489
+ it 'is aliased to #url' do
490
+ sin_app{url}
491
+ body('HTTP_HOST'=>'example.org').should == 'http://example.org/'
492
+ end
493
+
494
+ it 'is aliased to #to' do
495
+ sin_app{to}
496
+ body('HTTP_HOST'=>'example.org').should == 'http://example.org/'
497
+ end
498
+
499
+ it 'accepts a URI object instead of a String' do
500
+ sin_app{uri URI.parse('http://roda.jeremyevans.net')}
501
+ body.should == 'http://roda.jeremyevans.net'
502
+ end
503
+ end
504
+
505
+ it 'logger logs to rack.logger' do
506
+ sin_app{logger.info "foo"}
507
+ o = Object.new
508
+ def o.method_missing(*a)
509
+ (@a ||= []) << a
510
+ end
511
+ def o.logs
512
+ @a
513
+ end
514
+
515
+ status('rack.logger'=>o).should == 404
516
+ o.logs.should == [[:info, 'foo']]
517
+ end
518
+
519
+ it 'supports disabling delegation if :delegate=>false option is provided' do
520
+ app(:bare) do
521
+ plugin :sinatra_helpers, :delegate=>false
522
+ route do |r|
523
+ r.root{content_type}
524
+ r.is("req"){r.ssl?.to_s}
525
+ r.is("res"){response.not_found?.inspect}
526
+ end
527
+ end
528
+
529
+ proc{body}.should raise_error(NameError)
530
+ body('/req').should == 'false'
531
+ body('/res').should == 'nil'
532
+ end
533
+ end
534
+