rubycut-sinatra-contrib 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +136 -0
  3. data/Rakefile +75 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +124 -0
  6. data/lib/sinatra/config_file.rb +167 -0
  7. data/lib/sinatra/content_for.rb +125 -0
  8. data/lib/sinatra/contrib.rb +39 -0
  9. data/lib/sinatra/contrib/all.rb +2 -0
  10. data/lib/sinatra/contrib/setup.rb +53 -0
  11. data/lib/sinatra/contrib/version.rb +17 -0
  12. data/lib/sinatra/cookies.rb +331 -0
  13. data/lib/sinatra/decompile.rb +120 -0
  14. data/lib/sinatra/engine_tracking.rb +96 -0
  15. data/lib/sinatra/extension.rb +95 -0
  16. data/lib/sinatra/json.rb +130 -0
  17. data/lib/sinatra/link_header.rb +132 -0
  18. data/lib/sinatra/multi_route.rb +87 -0
  19. data/lib/sinatra/namespace.rb +284 -0
  20. data/lib/sinatra/reloader.rb +394 -0
  21. data/lib/sinatra/respond_with.rb +249 -0
  22. data/lib/sinatra/streaming.rb +267 -0
  23. data/lib/sinatra/test_helpers.rb +87 -0
  24. data/sinatra-contrib.gemspec +127 -0
  25. data/spec/capture_spec.rb +93 -0
  26. data/spec/config_file/key_value.yml +6 -0
  27. data/spec/config_file/key_value.yml.erb +6 -0
  28. data/spec/config_file/key_value_override.yml +2 -0
  29. data/spec/config_file/missing_env.yml +4 -0
  30. data/spec/config_file/with_envs.yml +7 -0
  31. data/spec/config_file/with_nested_envs.yml +11 -0
  32. data/spec/config_file_spec.rb +63 -0
  33. data/spec/content_for/different_key.erb +1 -0
  34. data/spec/content_for/different_key.erubis +1 -0
  35. data/spec/content_for/different_key.haml +2 -0
  36. data/spec/content_for/different_key.slim +2 -0
  37. data/spec/content_for/layout.erb +1 -0
  38. data/spec/content_for/layout.erubis +1 -0
  39. data/spec/content_for/layout.haml +1 -0
  40. data/spec/content_for/layout.slim +1 -0
  41. data/spec/content_for/multiple_blocks.erb +4 -0
  42. data/spec/content_for/multiple_blocks.erubis +4 -0
  43. data/spec/content_for/multiple_blocks.haml +8 -0
  44. data/spec/content_for/multiple_blocks.slim +8 -0
  45. data/spec/content_for/multiple_yields.erb +3 -0
  46. data/spec/content_for/multiple_yields.erubis +3 -0
  47. data/spec/content_for/multiple_yields.haml +3 -0
  48. data/spec/content_for/multiple_yields.slim +3 -0
  49. data/spec/content_for/passes_values.erb +1 -0
  50. data/spec/content_for/passes_values.erubis +1 -0
  51. data/spec/content_for/passes_values.haml +1 -0
  52. data/spec/content_for/passes_values.slim +1 -0
  53. data/spec/content_for/same_key.erb +1 -0
  54. data/spec/content_for/same_key.erubis +1 -0
  55. data/spec/content_for/same_key.haml +2 -0
  56. data/spec/content_for/same_key.slim +2 -0
  57. data/spec/content_for/takes_values.erb +1 -0
  58. data/spec/content_for/takes_values.erubis +1 -0
  59. data/spec/content_for/takes_values.haml +3 -0
  60. data/spec/content_for/takes_values.slim +3 -0
  61. data/spec/content_for_spec.rb +213 -0
  62. data/spec/cookies_spec.rb +802 -0
  63. data/spec/decompile_spec.rb +44 -0
  64. data/spec/extension_spec.rb +33 -0
  65. data/spec/json_spec.rb +117 -0
  66. data/spec/link_header_spec.rb +100 -0
  67. data/spec/multi_route_spec.rb +60 -0
  68. data/spec/namespace/foo.erb +1 -0
  69. data/spec/namespace/nested/foo.erb +1 -0
  70. data/spec/namespace_spec.rb +676 -0
  71. data/spec/okjson.rb +581 -0
  72. data/spec/reloader/app.rb.erb +40 -0
  73. data/spec/reloader_spec.rb +441 -0
  74. data/spec/respond_with/bar.erb +1 -0
  75. data/spec/respond_with/bar.json.erb +1 -0
  76. data/spec/respond_with/foo.html.erb +1 -0
  77. data/spec/respond_with/not_html.sass +2 -0
  78. data/spec/respond_with_spec.rb +297 -0
  79. data/spec/spec_helper.rb +7 -0
  80. data/spec/streaming_spec.rb +436 -0
  81. metadata +313 -0
@@ -0,0 +1,44 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+
4
+ RSpec::Matchers.define :decompile do |path|
5
+ match do |app|
6
+ @compiled, @keys = app.send :compile, path
7
+ @decompiled = app.decompile(@compiled, @keys)
8
+ @decompiled.should == path
9
+ end
10
+
11
+ failure_message_for_should do |app|
12
+ values = [app, @compiled, @keys, path, @decompiled].map(&:inspect)
13
+ "expected %s to decompile %s with %s to %s, but was %s" % values
14
+ end
15
+ end
16
+
17
+ describe Sinatra::Decompile do
18
+ subject { Sinatra::Application }
19
+ it { should decompile("") }
20
+ it { should decompile("/") }
21
+ it { should decompile("/?") }
22
+ it { should decompile("/foo") }
23
+ it { should decompile("/:name") }
24
+ it { should decompile("/:name?") }
25
+ it { should decompile("/:foo/:bar") }
26
+ it { should decompile("/page/:id/edit") }
27
+ it { should decompile("/hello/*") }
28
+ it { should decompile("/*/foo/*") }
29
+ it { should decompile("*") }
30
+ it { should decompile(":name.:format") }
31
+ it { should decompile("a b") }
32
+ it { should decompile("a+b") }
33
+ it { should decompile(/./) }
34
+ it { should decompile(/f(oo)/) }
35
+ it { should decompile(/ba+r/) }
36
+
37
+ it 'just returns strings' do
38
+ subject.decompile('/foo').should == '/foo'
39
+ end
40
+
41
+ it 'just decompile simple regexps without keys' do
42
+ subject.decompile(%r{/foo}).should == '/foo'
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+
4
+ describe Sinatra::Extension do
5
+ module ExampleExtension
6
+ extend Sinatra::Extension
7
+
8
+ set :foo, :bar
9
+ settings.set :bar, :blah
10
+
11
+ configure :test, :production do
12
+ set :reload_stuff, false
13
+ end
14
+
15
+ configure :development do
16
+ set :reload_stuff, true
17
+ end
18
+
19
+ get '/' do
20
+ "from extension, yay"
21
+ end
22
+ end
23
+
24
+ before { mock_app { register ExampleExtension }}
25
+
26
+ it('allows using set') { settings.foo.should == :bar }
27
+ it('implements configure') { settings.reload_stuff.should be_false }
28
+
29
+ it 'allows defing routes' do
30
+ get('/').should be_ok
31
+ body.should == "from extension, yay"
32
+ end
33
+ end
@@ -0,0 +1,117 @@
1
+ require 'backports'
2
+ require 'multi_json'
3
+
4
+ require_relative 'spec_helper'
5
+ require_relative 'okjson'
6
+
7
+ shared_examples_for "a json encoder" do |lib, const|
8
+ before do
9
+ begin
10
+ require lib if lib
11
+ @encoder = eval(const)
12
+ rescue LoadError
13
+ pending "unable to load #{lib}"
14
+ end
15
+ end
16
+
17
+ it "allows setting :encoder to #{const}" do
18
+ enc = @encoder
19
+ mock_app { get('/') { json({'foo' => 'bar'}, :encoder => enc) }}
20
+ results_in 'foo' => 'bar'
21
+ end
22
+
23
+ it "allows setting settings.json_encoder to #{const}" do
24
+ enc = @encoder
25
+ mock_app do
26
+ set :json_encoder, enc
27
+ get('/') { json 'foo' => 'bar' }
28
+ end
29
+ results_in 'foo' => 'bar'
30
+ end
31
+ end
32
+
33
+ describe Sinatra::JSON do
34
+ def mock_app(&block)
35
+ super do
36
+ helpers Sinatra::JSON
37
+ class_eval(&block)
38
+ end
39
+ end
40
+
41
+ def results_in(obj)
42
+ OkJson.decode(get('/').body).should == obj
43
+ end
44
+
45
+ it "encodes objects to json out of the box" do
46
+ mock_app { get('/') { json :foo => [1, 'bar', nil] } }
47
+ results_in 'foo' => [1, 'bar', nil]
48
+ end
49
+
50
+ it "sets the content type to 'application/json'" do
51
+ mock_app { get('/') { json({}) } }
52
+ get('/')["Content-Type"].should include("application/json")
53
+ end
54
+
55
+ it "allows overriding content type with :content_type" do
56
+ mock_app { get('/') { json({}, :content_type => "foo/bar") } }
57
+ get('/')["Content-Type"].should == "foo/bar"
58
+ end
59
+
60
+ it "accepts shorthands for :content_type" do
61
+ mock_app { get('/') { json({}, :content_type => :js) } }
62
+ get('/')["Content-Type"].should == "application/javascript;charset=utf-8"
63
+ end
64
+
65
+ it 'calls generate on :encoder if available' do
66
+ enc = Object.new
67
+ def enc.generate(obj) obj.inspect end
68
+ mock_app { get('/') { json(42, :encoder => enc) }}
69
+ get('/').body.should == '42'
70
+ end
71
+
72
+ it 'calls encode on :encoder if available' do
73
+ enc = Object.new
74
+ def enc.encode(obj) obj.inspect end
75
+ mock_app { get('/') { json(42, :encoder => enc) }}
76
+ get('/').body.should == '42'
77
+ end
78
+
79
+ it 'sends :encoder as method call if it is a Symbol' do
80
+ mock_app { get('/') { json(42, :encoder => :inspect) }}
81
+ get('/').body.should == '42'
82
+ end
83
+
84
+ it 'calls generate on settings.json_encoder if available' do
85
+ enc = Object.new
86
+ def enc.generate(obj) obj.inspect end
87
+ mock_app do
88
+ set :json_encoder, enc
89
+ get('/') { json 42 }
90
+ end
91
+ get('/').body.should == '42'
92
+ end
93
+
94
+ it 'calls encode on settings.json_encode if available' do
95
+ enc = Object.new
96
+ def enc.encode(obj) obj.inspect end
97
+ mock_app do
98
+ set :json_encoder, enc
99
+ get('/') { json 42 }
100
+ end
101
+ get('/').body.should == '42'
102
+ end
103
+
104
+ it 'sends settings.json_encode as method call if it is a Symbol' do
105
+ mock_app do
106
+ set :json_encoder, :inspect
107
+ get('/') { json 42 }
108
+ end
109
+ get('/').body.should == '42'
110
+ end
111
+
112
+ describe('Yajl') { it_should_behave_like "a json encoder", "yajl", "Yajl::Encoder" } unless defined? JRUBY_VERSION
113
+ describe('JSON') { it_should_behave_like "a json encoder", "json", "::JSON" }
114
+ describe('OkJson') { it_should_behave_like "a json encoder", nil, "OkJson" }
115
+ describe('to_json') { it_should_behave_like "a json encoder", "json", ":to_json" }
116
+ describe('without') { it_should_behave_like "a json encoder", nil, "Sinatra::JSON" }
117
+ end
@@ -0,0 +1,100 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+
4
+ describe Sinatra::LinkHeader do
5
+ before do
6
+ mock_app do
7
+ helpers Sinatra::LinkHeader
8
+ before('/') { link 'something', :rel => 'from-filter', :foo => :bar }
9
+
10
+ get '/' do
11
+ link :something, 'booyah'
12
+ end
13
+
14
+ get '/style' do
15
+ stylesheet '/style.css'
16
+ end
17
+
18
+ get '/prefetch' do
19
+ prefetch '/foo'
20
+ end
21
+
22
+ get '/link_headers' do
23
+ response['Link'] = "<foo> ;bar=\"baz\""
24
+ stylesheet '/style.css'
25
+ prefetch '/foo'
26
+ link_headers
27
+ end
28
+ end
29
+ end
30
+
31
+ describe :link do
32
+ it "sets link headers" do
33
+ get '/'
34
+ headers['Link'].lines.should include('<booyah>; rel="something"')
35
+ end
36
+
37
+ it "returns link html tags" do
38
+ get '/'
39
+ body.should == '<link href="booyah" rel="something" />'
40
+ end
41
+
42
+ it "takes an options hash" do
43
+ get '/'
44
+ elements = ["<something>", "foo=\"bar\"", "rel=\"from-filter\""]
45
+ headers['Link'].lines.first.strip.split('; ').sort.should == elements
46
+ end
47
+ end
48
+
49
+ describe :stylesheet do
50
+ it 'sets link headers' do
51
+ get '/style'
52
+ headers['Link'].should match(%r{^</style\.css>;})
53
+ end
54
+
55
+ it 'sets type to text/css' do
56
+ get '/style'
57
+ headers['Link'].should include('type="text/css"')
58
+ end
59
+
60
+ it 'sets rel to stylesheet' do
61
+ get '/style'
62
+ headers['Link'].should include('rel="stylesheet"')
63
+ end
64
+
65
+ it 'returns html tag' do
66
+ get '/style'
67
+ body.should match(%r{^<link href="/style\.css"})
68
+ end
69
+ end
70
+
71
+ describe :prefetch do
72
+ it 'sets link headers' do
73
+ get '/prefetch'
74
+ headers['Link'].should match(%r{^</foo>;})
75
+ end
76
+
77
+ it 'sets rel to prefetch' do
78
+ get '/prefetch'
79
+ headers['Link'].should include('rel="prefetch"')
80
+ end
81
+
82
+ it 'returns html tag' do
83
+ get '/prefetch'
84
+ body.should == '<link href="/foo" rel="prefetch" />'
85
+ end
86
+ end
87
+
88
+ describe :link_headers do
89
+ it 'generates html for all link headers' do
90
+ get '/link_headers'
91
+ body.should include('<link href="/foo" rel="prefetch" />')
92
+ body.should include('<link href="/style.css" ')
93
+ end
94
+
95
+ it "respects Link headers not generated on its own" do
96
+ get '/link_headers'
97
+ body.should include('<link href="foo" bar="baz" />')
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,60 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+
4
+ describe Sinatra::MultiRoute do
5
+
6
+ it 'does not break normal routing' do
7
+ mock_app do
8
+ register Sinatra::MultiRoute
9
+ get('/') { 'normal' }
10
+ end
11
+
12
+ get('/').should be_ok
13
+ body.should be == 'normal'
14
+ end
15
+
16
+ it 'supports multiple routes' do
17
+ mock_app do
18
+ register Sinatra::MultiRoute
19
+ get('/foo', '/bar') { 'paths' }
20
+ end
21
+
22
+ get('/foo').should be_ok
23
+ body.should be == 'paths'
24
+ get('/bar').should be_ok
25
+ body.should be == 'paths'
26
+ end
27
+
28
+ it 'triggers conditions' do
29
+ count = 0
30
+ mock_app do
31
+ register Sinatra::MultiRoute
32
+ set(:some_condition) { |_| count += 1 }
33
+ get('/foo', '/bar', :some_condition => true) { 'paths' }
34
+ end
35
+
36
+ count.should be == 4
37
+ end
38
+
39
+ it 'supports multiple verbs' do
40
+ mock_app do
41
+ register Sinatra::MultiRoute
42
+ route('PUT', 'POST', '/') { 'verb' }
43
+ end
44
+
45
+ post('/').should be_ok
46
+ body.should be == 'verb'
47
+ put('/').should be_ok
48
+ body.should be == 'verb'
49
+ end
50
+
51
+ it 'takes symbols as verbs' do
52
+ mock_app do
53
+ register Sinatra::MultiRoute
54
+ route(:get, '/baz') { 'symbol as verb' }
55
+ end
56
+
57
+ get('/baz').should be_ok
58
+ body.should be == 'symbol as verb'
59
+ end
60
+ end
@@ -0,0 +1 @@
1
+ hi
@@ -0,0 +1 @@
1
+ ho
@@ -0,0 +1,676 @@
1
+ require 'backports'
2
+ require_relative 'spec_helper'
3
+
4
+ describe Sinatra::Namespace do
5
+ verbs = [:get, :head, :post, :put, :delete, :options]
6
+ verbs << :patch if Sinatra::VERSION >= '1.3'
7
+
8
+ def mock_app(&block)
9
+ super do
10
+ register Sinatra::Namespace
11
+ class_eval(&block)
12
+ end
13
+ end
14
+
15
+ def namespace(*args, &block)
16
+ mock_app { namespace(*args, &block) }
17
+ end
18
+
19
+ verbs.each do |verb|
20
+ describe "HTTP #{verb.to_s.upcase}" do
21
+
22
+ it 'prefixes the path with the namespace' do
23
+ namespace('/foo') { send(verb, '/bar') { 'baz' }}
24
+ send(verb, '/foo/bar').should be_ok
25
+ body.should == 'baz' unless verb == :head
26
+ send(verb, '/foo/baz').should_not be_ok
27
+ end
28
+
29
+ context 'when namespace is a string' do
30
+ it 'accepts routes with no path' do
31
+ namespace('/foo') { send(verb) { 'bar' } }
32
+ send(verb, '/foo').should be_ok
33
+ body.should == 'bar' unless verb == :head
34
+ end
35
+
36
+ it 'accepts the path as a named parameter' do
37
+ namespace('/foo') { send(verb, '/:bar') { params[:bar] }}
38
+ send(verb, '/foo/bar').should be_ok
39
+ body.should == 'bar' unless verb == :head
40
+ send(verb, '/foo/baz').should be_ok
41
+ body.should == 'baz' unless verb == :head
42
+ end
43
+
44
+ it 'accepts the path as a regular expression' do
45
+ namespace('/foo') { send(verb, /\/\d\d/) { 'bar' }}
46
+ send(verb, '/foo/12').should be_ok
47
+ body.should == 'bar' unless verb == :head
48
+ send(verb, '/foo/123').should_not be_ok
49
+ end
50
+ end
51
+
52
+ context 'when namespace is a named parameter' do
53
+ it 'accepts routes with no path' do
54
+ namespace('/:foo') { send(verb) { 'bar' } }
55
+ send(verb, '/foo').should be_ok
56
+ body.should == 'bar' unless verb == :head
57
+ end
58
+
59
+ it 'sets the parameter correctly' do
60
+ namespace('/:foo') { send(verb, '/bar') { params[:foo] }}
61
+ send(verb, '/foo/bar').should be_ok
62
+ body.should == 'foo' unless verb == :head
63
+ send(verb, '/fox/bar').should be_ok
64
+ body.should == 'fox' unless verb == :head
65
+ send(verb, '/foo/baz').should_not be_ok
66
+ end
67
+
68
+ it 'accepts the path as a named parameter' do
69
+ namespace('/:foo') { send(verb, '/:bar') { params[:bar] }}
70
+ send(verb, '/foo/bar').should be_ok
71
+ body.should == 'bar' unless verb == :head
72
+ send(verb, '/foo/baz').should be_ok
73
+ body.should == 'baz' unless verb == :head
74
+ end
75
+
76
+ it 'accepts the path as regular expression' do
77
+ namespace('/:foo') { send(verb, %r{/bar}) { params[:foo] }}
78
+ send(verb, '/foo/bar').should be_ok
79
+ body.should == 'foo' unless verb == :head
80
+ send(verb, '/fox/bar').should be_ok
81
+ body.should == 'fox' unless verb == :head
82
+ send(verb, '/foo/baz').should_not be_ok
83
+ end
84
+ end
85
+
86
+ context 'when namespace is a regular expression' do
87
+ it 'accepts routes with no path' do
88
+ namespace(%r{/foo}) { send(verb) { 'bar' } }
89
+ send(verb, '/foo').should be_ok
90
+ body.should == 'bar' unless verb == :head
91
+ end
92
+
93
+ it 'accepts the path as a named parameter' do
94
+ namespace(%r{/foo}) { send(verb, '/:bar') { params[:bar] }}
95
+ send(verb, '/foo/bar').should be_ok
96
+ body.should == 'bar' unless verb == :head
97
+ send(verb, '/foo/baz').should be_ok
98
+ body.should == 'baz' unless verb == :head
99
+ end
100
+
101
+ it 'accepts the path as a regular expression' do
102
+ namespace(/\/\d\d/) { send(verb, /\/\d\d/) { 'foo' }}
103
+ send(verb, '/23/12').should be_ok
104
+ body.should == 'foo' unless verb == :head
105
+ send(verb, '/123/12').should_not be_ok
106
+ end
107
+ end
108
+
109
+ context 'when namespace is a splat' do
110
+ it 'accepts the path as a splat' do
111
+ namespace('/*') { send(verb, '/*') { params[:splat].join ' - ' }}
112
+ send(verb, '/foo/bar').should be_ok
113
+ body.should == 'foo - bar' unless verb == :head
114
+ end
115
+ end
116
+
117
+ describe 'before-filters' do
118
+ specify 'are triggered' do
119
+ ran = false
120
+ namespace('/foo') { before { ran = true }}
121
+ send(verb, '/foo')
122
+ ran.should be_true
123
+ end
124
+
125
+ specify 'are not triggered for a different namespace' do
126
+ ran = false
127
+ namespace('/foo') { before { ran = true }}
128
+ send(verb, '/fox')
129
+ ran.should be_false
130
+ end
131
+ end
132
+
133
+ describe 'after-filters' do
134
+ specify 'are triggered' do
135
+ ran = false
136
+ namespace('/foo') { after { ran = true }}
137
+ send(verb, '/foo')
138
+ ran.should be_true
139
+ end
140
+
141
+ specify 'are not triggered for a different namespace' do
142
+ ran = false
143
+ namespace('/foo') { after { ran = true }}
144
+ send(verb, '/fox')
145
+ ran.should be_false
146
+ end
147
+ end
148
+
149
+ describe 'conditions' do
150
+ context 'when the namespace has no prefix' do
151
+ specify 'are accepted in the namespace' do
152
+ mock_app do
153
+ namespace(:host_name => 'example.com') { send(verb) { 'yes' }}
154
+ send(verb, '/') { 'no' }
155
+ end
156
+ send(verb, '/', {}, 'HTTP_HOST' => 'example.com')
157
+ last_response.should be_ok
158
+ body.should == 'yes' unless verb == :head
159
+ send(verb, '/', {}, 'HTTP_HOST' => 'example.org')
160
+ last_response.should be_ok
161
+ body.should == 'no' unless verb == :head
162
+ end
163
+
164
+ specify 'are accepted in the route definition' do
165
+ namespace :host_name => 'example.com' do
166
+ send(verb, '/foo', :provides => :txt) { 'ok' }
167
+ end
168
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain').should be_ok
169
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html').should_not be_ok
170
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain').should_not be_ok
171
+ end
172
+
173
+ specify 'are accepted in the before-filter' do
174
+ ran = false
175
+ namespace :provides => :txt do
176
+ before('/foo', :host_name => 'example.com') { ran = true }
177
+ send(verb, '/*') { 'ok' }
178
+ end
179
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
180
+ ran.should be_false
181
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
182
+ ran.should be_false
183
+ send(verb, '/bar', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
184
+ ran.should be_false
185
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
186
+ ran.should be_true
187
+ end
188
+
189
+ specify 'are accepted in the after-filter' do
190
+ ran = false
191
+ namespace :provides => :txt do
192
+ after('/foo', :host_name => 'example.com') { ran = true }
193
+ send(verb, '/*') { 'ok' }
194
+ end
195
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
196
+ ran.should be_false
197
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
198
+ ran.should be_false
199
+ send(verb, '/bar', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
200
+ ran.should be_false
201
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
202
+ ran.should be_true
203
+ end
204
+ end
205
+
206
+ context 'when the namespace is a string' do
207
+ specify 'are accepted in the namespace' do
208
+ namespace '/foo', :host_name => 'example.com' do
209
+ send(verb) { 'ok' }
210
+ end
211
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com').should be_ok
212
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org').should_not be_ok
213
+ end
214
+
215
+ specify 'are accepted in the before-filter' do
216
+ namespace '/foo' do
217
+ before(:host_name => 'example.com') { @yes = 'yes' }
218
+ send(verb) { @yes || 'no' }
219
+ end
220
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com')
221
+ last_response.should be_ok
222
+ body.should == 'yes' unless verb == :head
223
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org')
224
+ last_response.should be_ok
225
+ body.should == 'no' unless verb == :head
226
+ end
227
+
228
+ specify 'are accepted in the after-filter' do
229
+ ran = false
230
+ namespace '/foo' do
231
+ before(:host_name => 'example.com') { ran = true }
232
+ send(verb) { 'ok' }
233
+ end
234
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org')
235
+ ran.should be_false
236
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com')
237
+ ran.should be_true
238
+ end
239
+
240
+ specify 'are accepted in the route definition' do
241
+ namespace '/foo' do
242
+ send(verb, :host_name => 'example.com') { 'ok' }
243
+ end
244
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com').should be_ok
245
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org').should_not be_ok
246
+ end
247
+
248
+ context 'when the namespace has a condition' do
249
+ specify 'are accepted in the before-filter' do
250
+ ran = false
251
+ namespace '/', :provides => :txt do
252
+ before(:host_name => 'example.com') { ran = true }
253
+ send(verb) { 'ok' }
254
+ end
255
+ send(verb, '/', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
256
+ ran.should be_false
257
+ send(verb, '/', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
258
+ ran.should be_false
259
+ send(verb, '/', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
260
+ ran.should be_true
261
+ end
262
+
263
+ specify 'are accepted in the filters' do
264
+ ran = false
265
+ namespace '/f', :provides => :txt do
266
+ before('oo', :host_name => 'example.com') { ran = true }
267
+ send(verb, '/*') { 'ok' }
268
+ end
269
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
270
+ ran.should be_false
271
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
272
+ ran.should be_false
273
+ send(verb, '/far', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
274
+ ran.should be_false
275
+ send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
276
+ ran.should be_true
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+ describe 'helpers' do
283
+ it 'are defined using the helpers method' do
284
+ namespace '/foo' do
285
+ helpers do
286
+ def magic
287
+ 42
288
+ end
289
+ end
290
+
291
+ send verb, '/bar' do
292
+ magic.to_s
293
+ end
294
+ end
295
+
296
+ send(verb, '/foo/bar').should be_ok
297
+ body.should == '42' unless verb == :head
298
+ end
299
+
300
+ it 'can be defined as normal methods' do
301
+ namespace '/foo' do
302
+ def magic
303
+ 42
304
+ end
305
+
306
+ send verb, '/bar' do
307
+ magic.to_s
308
+ end
309
+ end
310
+
311
+ send(verb, '/foo/bar').should be_ok
312
+ body.should == '42' unless verb == :head
313
+ end
314
+
315
+ it 'can be defined using module mixins' do
316
+ mixin = Module.new do
317
+ def magic
318
+ 42
319
+ end
320
+ end
321
+
322
+ namespace '/foo' do
323
+ helpers mixin
324
+ send verb, '/bar' do
325
+ magic.to_s
326
+ end
327
+ end
328
+
329
+ send(verb, '/foo/bar').should be_ok
330
+ body.should == '42' unless verb == :head
331
+ end
332
+
333
+ specify 'are unavailable outside the namespace where they are defined' do
334
+ mock_app do
335
+ namespace '/foo' do
336
+ def magic
337
+ 42
338
+ end
339
+
340
+ send verb, '/bar' do
341
+ magic.to_s
342
+ end
343
+ end
344
+
345
+ send verb, '/' do
346
+ magic.to_s
347
+ end
348
+ end
349
+
350
+ proc { send verb, '/' }.should raise_error(NameError)
351
+ end
352
+
353
+ specify 'are unavailable outside the namespace that they are mixed into' do
354
+ mixin = Module.new do
355
+ def magic
356
+ 42
357
+ end
358
+ end
359
+
360
+ mock_app do
361
+ namespace '/foo' do
362
+ helpers mixin
363
+ send verb, '/bar' do
364
+ magic.to_s
365
+ end
366
+ end
367
+
368
+ send verb, '/' do
369
+ magic.to_s
370
+ end
371
+ end
372
+
373
+ proc { send verb, '/' }.should raise_error(NameError)
374
+ end
375
+
376
+ specify 'are available to nested namespaces' do
377
+ mock_app do
378
+ helpers do
379
+ def magic
380
+ 42
381
+ end
382
+ end
383
+
384
+ namespace '/foo' do
385
+ send verb, '/bar' do
386
+ magic.to_s
387
+ end
388
+ end
389
+ end
390
+
391
+ send(verb, '/foo/bar').should be_ok
392
+ body.should == '42' unless verb == :head
393
+ end
394
+
395
+ specify 'can call super from nested definitions' do
396
+ mock_app do
397
+ helpers do
398
+ def magic
399
+ 42
400
+ end
401
+ end
402
+
403
+ namespace '/foo' do
404
+ def magic
405
+ super - 19
406
+ end
407
+
408
+ send verb, '/bar' do
409
+ magic.to_s
410
+ end
411
+ end
412
+ end
413
+
414
+ send(verb, '/foo/bar').should be_ok
415
+ body.should == '23' unless verb == :head
416
+ end
417
+ end
418
+
419
+ describe 'nesting' do
420
+ it 'routes to nested namespaces' do
421
+ namespace '/foo' do
422
+ namespace '/bar' do
423
+ send(verb, '/baz') { 'OKAY!!11!'}
424
+ end
425
+ end
426
+
427
+ send(verb, '/foo/bar/baz').should be_ok
428
+ body.should == 'OKAY!!11!' unless verb == :head
429
+ end
430
+
431
+ it 'exposes helpers to nested namespaces' do
432
+ namespace '/foo' do
433
+ helpers do
434
+ def magic
435
+ 42
436
+ end
437
+ end
438
+
439
+ namespace '/bar' do
440
+ send verb, '/baz' do
441
+ magic.to_s
442
+ end
443
+ end
444
+ end
445
+
446
+ send(verb, '/foo/bar/baz').should be_ok
447
+ body.should == '42' unless verb == :head
448
+ end
449
+
450
+ specify 'does not provide access to nested helper methods' do
451
+ namespace '/foo' do
452
+ namespace '/bar' do
453
+ def magic
454
+ 42
455
+ end
456
+
457
+ send verb, '/baz' do
458
+ magic.to_s
459
+ end
460
+ end
461
+
462
+ send verb do
463
+ magic.to_s
464
+ end
465
+ end
466
+
467
+ proc { send verb, '/foo' }.should raise_error(NameError)
468
+ end
469
+
470
+ it 'accepts a nested namespace as a named parameter' do
471
+ namespace('/:a') { namespace('/:b') { send(verb) { params[:a] }}}
472
+ send(verb, '/foo/bar').should be_ok
473
+ body.should == 'foo' unless verb == :head
474
+ end
475
+ end
476
+
477
+ describe 'error handling' do
478
+ it 'can be customized using the not_found block' do
479
+ namespace('/de') do
480
+ not_found { 'nicht gefunden' }
481
+ end
482
+ send(verb, '/foo').status.should == 404
483
+ last_response.body.should_not == 'nicht gefunden' unless verb == :head
484
+ get('/en/foo').status.should == 404
485
+ last_response.body.should_not == 'nicht gefunden' unless verb == :head
486
+ get('/de/foo').status.should == 404
487
+ last_response.body.should == 'nicht gefunden' unless verb == :head
488
+ end
489
+
490
+ it 'can be customized for specific error codes' do
491
+ namespace('/de') do
492
+ error(404) { 'nicht gefunden' }
493
+ end
494
+ send(verb, '/foo').status.should == 404
495
+ last_response.body.should_not == 'nicht gefunden' unless verb == :head
496
+ get('/en/foo').status.should == 404
497
+ last_response.body.should_not == 'nicht gefunden' unless verb == :head
498
+ get('/de/foo').status.should == 404
499
+ last_response.body.should == 'nicht gefunden' unless verb == :head
500
+ end
501
+
502
+ it 'falls back to the handler defined in the base app' do
503
+ mock_app do
504
+ error(404) { 'not found...' }
505
+ namespace('/en') do
506
+ end
507
+ namespace('/de') do
508
+ error(404) { 'nicht gefunden' }
509
+ end
510
+ end
511
+ send(verb, '/foo').status.should == 404
512
+ last_response.body.should == 'not found...' unless verb == :head
513
+ get('/en/foo').status.should == 404
514
+ last_response.body.should == 'not found...' unless verb == :head
515
+ get('/de/foo').status.should == 404
516
+ last_response.body.should == 'nicht gefunden' unless verb == :head
517
+ end
518
+
519
+ it 'can be customized for specific Exception classes' do
520
+ mock_app do
521
+ class AError < StandardError; end
522
+ class BError < AError; end
523
+
524
+ error(AError) do
525
+ body('auth failed')
526
+ 401
527
+ end
528
+
529
+ namespace('/en') do
530
+ get '/foo' do
531
+ raise BError
532
+ end
533
+ end
534
+
535
+ namespace('/de') do
536
+ error(AError) do
537
+ body('methode nicht erlaubt')
538
+ 406
539
+ end
540
+
541
+ get '/foo' do
542
+ raise BError
543
+ end
544
+ end
545
+ end
546
+ get('/en/foo').status.should == 401
547
+ last_response.body.should == 'auth failed' unless verb == :head
548
+ get('/de/foo').status.should == 406
549
+ last_response.body.should == 'methode nicht erlaubt' unless verb == :head
550
+ end
551
+ end
552
+
553
+ unless verb == :head
554
+ describe 'templates' do
555
+ specify 'default to the base app\'s template' do
556
+ mock_app do
557
+ template(:foo) { 'hi' }
558
+ send(verb, '/') { erb :foo }
559
+ namespace '/foo' do
560
+ send(verb) { erb :foo }
561
+ end
562
+ end
563
+
564
+ send(verb, '/').body.should == 'hi'
565
+ send(verb, '/foo').body.should == 'hi'
566
+ end
567
+
568
+ specify 'can be nested' do
569
+ mock_app do
570
+ template(:foo) { 'hi' }
571
+ send(verb, '/') { erb :foo }
572
+ namespace '/foo' do
573
+ template(:foo) { 'ho' }
574
+ send(verb) { erb :foo }
575
+ end
576
+ end
577
+
578
+ send(verb, '/').body.should == 'hi'
579
+ send(verb, '/foo').body.should == 'ho'
580
+ end
581
+
582
+ specify 'can use a custom views directory' do
583
+ mock_app do
584
+ set :views, File.expand_path('../namespace', __FILE__)
585
+ send(verb, '/') { erb :foo }
586
+ namespace('/foo') do
587
+ set :views, File.expand_path('../namespace/nested', __FILE__)
588
+ send(verb) { erb :foo }
589
+ end
590
+ end
591
+
592
+ send(verb, '/').body.should == "hi\n"
593
+ send(verb, '/foo').body.should == "ho\n"
594
+ end
595
+
596
+ specify 'default to the base app\'s layout' do
597
+ mock_app do
598
+ layout { 'he said: <%= yield %>' }
599
+ template(:foo) { 'hi' }
600
+ send(verb, '/') { erb :foo }
601
+ namespace '/foo' do
602
+ template(:foo) { 'ho' }
603
+ send(verb) { erb :foo }
604
+ end
605
+ end
606
+
607
+ send(verb, '/').body.should == 'he said: hi'
608
+ send(verb, '/foo').body.should == 'he said: ho'
609
+ end
610
+
611
+ specify 'can define nested layouts' do
612
+ mock_app do
613
+ layout { 'Hello <%= yield %>!' }
614
+ template(:foo) { 'World' }
615
+ send(verb, '/') { erb :foo }
616
+ namespace '/foo' do
617
+ layout { 'Hi <%= yield %>!' }
618
+ send(verb) { erb :foo }
619
+ end
620
+ end
621
+
622
+ send(verb, '/').body.should == 'Hello World!'
623
+ send(verb, '/foo').body.should == 'Hi World!'
624
+ end
625
+ end
626
+ end
627
+
628
+ describe 'extensions' do
629
+ specify 'provide read access to settings' do
630
+ value = nil
631
+ mock_app do
632
+ set :foo, 42
633
+ namespace '/foo' do
634
+ value = foo
635
+ end
636
+ end
637
+ value.should == 42
638
+ end
639
+
640
+ specify 'can be registered within a namespace' do
641
+ a = b = nil
642
+ extension = Module.new { define_method(:views) { 'CUSTOM!!!' } }
643
+ mock_app do
644
+ namespace '/' do
645
+ register extension
646
+ a = views
647
+ end
648
+ b = views
649
+ end
650
+ a.should == 'CUSTOM!!!'
651
+ b.should_not == 'CUSTOM!!!'
652
+ end
653
+
654
+ specify 'trigger the route_added hook' do
655
+ route = nil
656
+ extension = Module.new
657
+ extension.singleton_class.class_eval do
658
+ define_method(:route_added) { |*r| route = r }
659
+ end
660
+ mock_app do
661
+ namespace '/f' do
662
+ register extension
663
+ get('oo') { }
664
+ end
665
+ get('/bar') { }
666
+ end
667
+ route[1].should == '/foo'
668
+ end
669
+
670
+ specify 'prevent app-global settings from being changed' do
671
+ proc { namespace('/') { set :foo, :bar }}.should raise_error
672
+ end
673
+ end
674
+ end
675
+ end
676
+ end