rubycut-sinatra-contrib 1.4.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 (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