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
@@ -24,8 +24,6 @@ class Roda
24
24
  # not set a local variable inside the template.
25
25
  module RenderEach
26
26
  module InstanceMethods
27
- EMPTY_STRING = ''.freeze
28
-
29
27
  # For each value in enum, render the given template using the
30
28
  # given opts. The template and options hash are passed to +render+.
31
29
  # Additional options supported:
@@ -154,8 +154,7 @@ class Roda
154
154
  end
155
155
  end
156
156
 
157
- res = response
158
- request.halt [res.status || 200, res.headers, Stream.new(opts, &block)]
157
+ throw :halt, response.finish_with_body(Stream.new(opts, &block))
159
158
  end
160
159
  end
161
160
  end
@@ -6,7 +6,7 @@ class Roda
6
6
  # use, and template names that do not contain a slash will
7
7
  # automatically use that view subdirectory. Example:
8
8
  #
9
- # plugin :render
9
+ # plugin :render, :layout=>'./layout'
10
10
  # plugin :view_subdirs
11
11
  #
12
12
  # route do |r|
@@ -25,6 +25,12 @@ class Roda
25
25
  #
26
26
  # This plugin should be loaded after the render plugin, since
27
27
  # it works by overriding parts of the render plugin.
28
+ #
29
+ # Note that when a view subdirectory is set, the layout will
30
+ # also be looked up in the subdirectory unless it contains
31
+ # a slash. So if you want to use a view subdirectory for
32
+ # templates but have a shared layout, you should make sure your
33
+ # layout contains a slash, similar to the example above.
28
34
  module ViewSubdirs
29
35
  module InstanceMethods
30
36
  # Set the view subdirectory to use. This can be set to nil
data/lib/roda/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Roda
2
- RodaVersion = '1.0.0'.freeze
2
+ RodaVersion = '1.1.0'.freeze
3
3
  end
@@ -0,0 +1 @@
1
+ body { color: red; }
@@ -0,0 +1 @@
1
+ no access
@@ -0,0 +1 @@
1
+ body { color: blue; }
@@ -0,0 +1 @@
1
+ console.log('test')
@@ -18,7 +18,7 @@ describe "integration" do
18
18
 
19
19
  end
20
20
 
21
- it "should setup middleware using use " do
21
+ it "should setup middleware using use" do
22
22
  c = @c
23
23
  app(:bare) do
24
24
  use c, "First", "Second" do
@@ -35,7 +35,24 @@ describe "integration" do
35
35
  body('/hello').should == 'D First Second Block'
36
36
  end
37
37
 
38
- it "should inherit middleware in subclass " do
38
+ it "should support adding middleware using use after route block setup" do
39
+ c = @c
40
+ app(:bare) do
41
+ route do |r|
42
+ r.get "hello" do
43
+ "D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
44
+ end
45
+ end
46
+
47
+ use c, "First", "Second" do
48
+ "Block"
49
+ end
50
+ end
51
+
52
+ body('/hello').should == 'D First Second Block'
53
+ end
54
+
55
+ it "should inherit middleware in subclass" do
39
56
  c = @c
40
57
  @app = Class.new(app(:bare){use(c, '1', '2'){"3"}})
41
58
  @app.route do |r|
@@ -47,7 +64,55 @@ describe "integration" do
47
64
  body('/hello').should == 'D 1 2 3'
48
65
  end
49
66
 
50
- it "should not have future middleware additions to parent class affect subclass " do
67
+ it "should inherit route in subclass" do
68
+ c = @c
69
+ app(:bare) do
70
+ use(c, '1', '2'){"3"}
71
+ route do |r|
72
+ r.get "hello" do
73
+ "D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
74
+ end
75
+ end
76
+ end
77
+ @app = Class.new(app)
78
+
79
+ body('/hello').should == 'D 1 2 3'
80
+ end
81
+
82
+ it "should use instance of subclass when inheriting routes" do
83
+ c = @c
84
+ obj = nil
85
+ app(:bare) do
86
+ use(c, '1', '2'){"3"}
87
+ route do |r|
88
+ r.get "hello" do
89
+ obj = self
90
+ "D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
91
+ end
92
+ end
93
+ end
94
+ @app = Class.new(app)
95
+
96
+ body('/hello').should == 'D 1 2 3'
97
+ obj.should be_a_kind_of(@app)
98
+ end
99
+
100
+ it "should handle middleware added to subclass using superclass route" do
101
+ c = @c
102
+ app(:bare) do
103
+ route do |r|
104
+ r.get "hello" do
105
+ "D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
106
+ end
107
+ end
108
+ end
109
+ @app = Class.new(app)
110
+ @app.use(c, '1', '2'){"3"}
111
+
112
+ body('/hello').should == 'D 1 2 3'
113
+ end
114
+
115
+ it "should not have future middleware additions to superclass affect subclass" do
51
116
  c = @c
52
117
  a = app
53
118
  @app = Class.new(a)
@@ -60,4 +125,31 @@ describe "integration" do
60
125
 
61
126
  body('/hello').should == 'D '
62
127
  end
128
+
129
+ it "should not have future middleware additions to subclass affect superclass" do
130
+ c = @c
131
+ a = app do |r|
132
+ r.get "hello" do
133
+ "D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
134
+ end
135
+ end
136
+ @app = Class.new(a)
137
+ @app.use(c, '1', '2'){"3"}
138
+ @app = a
139
+
140
+ body('/hello').should == 'D '
141
+ end
142
+
143
+ it "should have app return the rack application to call" do
144
+ app(:bare){}.app.should == nil
145
+ app.route{|r|}
146
+ app.app.should be_a_kind_of(Proc)
147
+ c = Class.new{def initialize(app) @app = app end; def call(env) @app.call(env) end}
148
+ app.use c
149
+ app.app.should be_a_kind_of(c)
150
+ end
151
+
152
+ it "should have route_block return the route block" do
153
+ app{|r| 1}.route_block.call(nil).should == 1
154
+ end
63
155
  end
@@ -104,7 +104,7 @@ describe "r.is" do
104
104
 
105
105
  it "matches regexps" do
106
106
  app do |r|
107
- r.is /(\w+)/ do |id|
107
+ r.is(/(\w+)/) do |id|
108
108
  id
109
109
  end
110
110
  end
@@ -458,7 +458,7 @@ describe "path matchers" do
458
458
 
459
459
  it "a path with some regex captures" do
460
460
  app do |r|
461
- r.on /user(\d+)/ do |uid|
461
+ r.on(/user(\d+)/) do |uid|
462
462
  uid
463
463
  end
464
464
  end
@@ -0,0 +1,413 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+ require 'fileutils'
3
+
4
+ begin
5
+ for lib in %w'tilt sass'
6
+ require lib
7
+ end
8
+ run_tests = true
9
+ rescue LoadError
10
+ warn "#{lib} not installed, skipping assets plugin test"
11
+ end
12
+
13
+ if run_tests
14
+ metadata_file = 'spec/assets/tmp/precompiled.json'
15
+ js_file = 'spec/assets/js/head/app.js'
16
+ css_file = 'spec/assets/css/no_access.css'
17
+ js_mtime = File.mtime(js_file)
18
+ js_atime = File.atime(js_file)
19
+ css_mtime = File.mtime(css_file)
20
+ css_atime = File.atime(css_file)
21
+ describe 'assets plugin' do
22
+ before do
23
+ app(:bare) do
24
+ plugin :assets,
25
+ :css => ['app.scss', 'raw.css'],
26
+ :js => { :head => ['app.js'] },
27
+ :path => 'spec/assets',
28
+ :public => 'spec',
29
+ :css_opts => {:cache=>false}
30
+
31
+ route do |r|
32
+ r.assets
33
+
34
+ r.is 'test' do
35
+ "#{assets(:css)}\n#{assets([:js, :head])}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ after do
41
+ File.utime(js_atime, js_mtime, js_file)
42
+ File.utime(css_atime, css_mtime, css_file)
43
+ File.delete(metadata_file) if File.file?(metadata_file)
44
+ end
45
+ after(:all) do
46
+ FileUtils.rm_r('spec/assets/tmp') if File.directory?('spec/assets/tmp')
47
+ FileUtils.rm_r('spec/public') if File.directory?('spec/public')
48
+ end
49
+
50
+ it 'assets_opts should use correct paths given options' do
51
+ keys = [:js_path, :css_path, :compiled_js_path, :compiled_css_path, :js_prefix, :css_prefix, :compiled_js_prefix, :compiled_css_prefix]
52
+ app.assets_opts.values_at(*keys).should == %w"spec/assets/js/ spec/assets/css/ spec/assets/app spec/assets/app assets/js/ assets/css/ assets/app assets/app"
53
+
54
+ app.plugin :assets, :path=>'bar/', :public=>'foo/', :prefix=>'as/', :js_dir=>'j/', :css_dir=>'c/', :compiled_name=>'a'
55
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/as/a foo/as/a as/j/ as/c/ as/a as/a"
56
+
57
+ app.plugin :assets, :path=>'bar', :public=>'foo', :prefix=>'as', :js_dir=>'j', :css_dir=>'c', :compiled_name=>'a'
58
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/as/a foo/as/a as/j/ as/c/ as/a as/a"
59
+
60
+ app.plugin :assets, :compiled_js_dir=>'cj', :compiled_css_dir=>'cs', :compiled_path=>'cp'
61
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a as/j/ as/c/ as/cj/a as/cs/a"
62
+
63
+ app.plugin :assets, :compiled_js_route=>'cjr', :compiled_css_route=>'ccr', :js_route=>'jr', :css_route=>'cr'
64
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a as/jr/ as/cr/ as/cjr/a as/ccr/a"
65
+
66
+ app.plugin :assets, :compiled_js_route=>'cj', :compiled_css_route=>'cs', :js_route=>'j', :css_route=>'c'
67
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a as/j/ as/c/ as/cj/a as/cs/a"
68
+
69
+ app.plugin :assets
70
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a as/j/ as/c/ as/cj/a as/cs/a"
71
+
72
+ app.plugin :assets, :compiled_js_dir=>'', :compiled_css_dir=>nil, :compiled_js_route=>nil, :compiled_css_route=>nil
73
+ app.assets_opts.values_at(*keys).should == %w"bar/j/ bar/c/ foo/cp/a foo/cp/a as/j/ as/c/ as/a as/a"
74
+
75
+ app.plugin :assets, :js_dir=>'', :css_dir=>nil, :js_route=>nil, :css_route=>nil
76
+ app.assets_opts.values_at(*keys).should == %w"bar/ bar/ foo/cp/a foo/cp/a as/ as/ as/a as/a"
77
+
78
+ app.plugin :assets, :public=>''
79
+ app.assets_opts.values_at(*keys).should == %w"bar/ bar/ cp/a cp/a as/ as/ as/a as/a"
80
+
81
+ app.plugin :assets, :path=>'', :compiled_path=>nil
82
+ app.assets_opts.values_at(*keys).should == ['', '', 'a', 'a', 'as/', 'as/', 'as/a', 'as/a']
83
+
84
+ app.plugin :assets, :prefix=>''
85
+ app.assets_opts.values_at(*keys).should == ['', '', 'a', 'a', '', '', 'a', 'a']
86
+
87
+ app.plugin :assets, :compiled_name=>nil
88
+ app.assets_opts.values_at(*keys).should == ['', '', '', '', '', '', '', '']
89
+ end
90
+
91
+ it 'assets_opts should use headers and dependencies given options' do
92
+ keys = [:css_headers, :js_headers, :dependencies]
93
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"text/css; charset=UTF-8"}, {'Content-Type'=>"application/javascript; charset=UTF-8"}, {}]
94
+
95
+ app.plugin :assets, :headers=>{'A'=>'B'}, :dependencies=>{'a'=>'b'}
96
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B'}, {'a'=>'b'}]
97
+
98
+ app.plugin :assets, :css_headers=>{'C'=>'D'}, :js_headers=>{'E'=>'F'}, :dependencies=>{'c'=>'d'}
99
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B', 'C'=>'D'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
100
+
101
+ app.plugin :assets
102
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B', 'C'=>'D'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
103
+
104
+ app.plugin :assets
105
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B', 'C'=>'D'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
106
+
107
+ app.plugin :assets, :headers=>{'Content-Type'=>'C', 'E'=>'G'}
108
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"C", 'A'=>'B', 'C'=>'D', 'E'=>'G'}, {'Content-Type'=>"C", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
109
+
110
+ app.plugin :assets, :css_headers=>{'A'=>'B1'}, :js_headers=>{'E'=>'F1'}, :dependencies=>{'c'=>'d1'}
111
+ app.assets_opts.values_at(*keys).should == [{'Content-Type'=>"C", 'A'=>'B1', 'C'=>'D', 'E'=>'G'}, {'Content-Type'=>"C", 'A'=>'B', 'E'=>'F1'}, {'a'=>'b', 'c'=>'d1'}]
112
+ end
113
+
114
+ it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling' do
115
+ html = body('/test')
116
+ html.scan(/<link/).length.should == 2
117
+ html =~ %r{href="(/assets/css/app\.scss)"}
118
+ css = body($1)
119
+ html =~ %r{href="(/assets/css/raw\.css)"}
120
+ css2 = body($1)
121
+ html.scan(/<script/).length.should == 1
122
+ html =~ %r{src="(/assets/js/head/app\.js)"}
123
+ js = body($1)
124
+ css.should =~ /color: red;/
125
+ css2.should =~ /color: blue;/
126
+ js.should include('console.log')
127
+ end
128
+
129
+ it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling, with different options' do
130
+ app.plugin :assets, :path=>'spec/', :js_dir=>'assets/js', :css_dir=>'assets/css', :prefix=>'a',
131
+ :js_route=>'foo', :css_route=>'bar', :add_suffix=>true, :css_opts=>{:style=>:compressed}
132
+ html = body('/test')
133
+ html.scan(/<link/).length.should == 2
134
+ html =~ %r{href="(/a/bar/app\.scss.css)"}
135
+ css = body($1)
136
+ html =~ %r{href="(/a/bar/raw\.css.css)"}
137
+ css2 = body($1)
138
+ html.scan(/<script/).length.should == 1
139
+ html =~ %r{src="(/a/foo/head/app\.js.js)"}
140
+ js = body($1)
141
+ css.should =~ /color:red\}/
142
+ css2.should =~ /color: blue;/
143
+ js.should include('console.log')
144
+ end
145
+
146
+ it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling with a multi-level hash' do
147
+ app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :prefix=>nil,
148
+ :css=>{:assets=>{:css=>%w'app.scss raw.css'}}, :js=>{:assets=>{:js=>{:head=>'app.js'}}}
149
+ app.route do |r|
150
+ r.assets
151
+ r.is 'test' do
152
+ "#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
153
+ end
154
+ end
155
+ html = body('/test')
156
+ html.scan(/<link/).length.should == 2
157
+ html =~ %r{href="(/assets/css/app\.scss)"}
158
+ css = body($1)
159
+ html =~ %r{href="(/assets/css/raw\.css)"}
160
+ css2 = body($1)
161
+ html.scan(/<script/).length.should == 1
162
+ html =~ %r{src="(/assets/js/head/app\.js)"}
163
+ js = body($1)
164
+ css.should =~ /color: red;/
165
+ css2.should =~ /color: blue;/
166
+ js.should include('console.log')
167
+ end
168
+
169
+ it 'should handle :group_subdirs => false' do
170
+ app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :prefix=>nil, :group_subdirs=>false,
171
+ :css=>{:assets=>{:css=>%w'assets/css/app.scss assets/css/raw.css'}}, :js=>{:assets=>{:js=>{:head=>'assets/js/head/app.js'}}}
172
+ app.route do |r|
173
+ r.assets
174
+ r.is 'test' do
175
+ "#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
176
+ end
177
+ end
178
+ html = body('/test')
179
+ html.scan(/<link/).length.should == 2
180
+ html =~ %r{href="(/assets/css/app\.scss)"}
181
+ css = body($1)
182
+ html =~ %r{href="(/assets/css/raw\.css)"}
183
+ css2 = body($1)
184
+ html.scan(/<script/).length.should == 1
185
+ html =~ %r{src="(/assets/js/head/app\.js)"}
186
+ js = body($1)
187
+ css.should =~ /color: red;/
188
+ css2.should =~ /color: blue;/
189
+ js.should include('console.log')
190
+ end
191
+
192
+ it 'should handle compiling assets, linking to them, and accepting requests for them' do
193
+ app.compile_assets
194
+ html = body('/test')
195
+ html.scan(/<link/).length.should == 1
196
+ html =~ %r{href="(/assets/app\.[a-f0-9]{40}\.css)"}
197
+ css = body($1)
198
+ html.scan(/<script/).length.should == 1
199
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
200
+ js = body($1)
201
+ css.should =~ /color: ?red/
202
+ css.should =~ /color: ?blue/
203
+ js.should include('console.log')
204
+ end
205
+
206
+ it 'should handle compiling assets, linking to them, and accepting requests for them, with different options' do
207
+ app.plugin :assets, :compiled_path=>nil, :js_dir=>'assets/js', :css_dir=>'assets/css', :prefix=>'a',
208
+ :public=>'spec/assets', :path=>'spec', :compiled_js_route=>'foo', :compiled_css_route=>'bar'
209
+ app.compile_assets
210
+ html = body('/test')
211
+ html.scan(/<link/).length.should == 1
212
+ html =~ %r{href="(/a/bar/app\.[a-f0-9]{40}\.css)"}
213
+ css = body($1)
214
+ html.scan(/<script/).length.should == 1
215
+ html =~ %r{src="(/a/foo/app\.head\.[a-f0-9]{40}\.js)"}
216
+ js = body($1)
217
+ css.should =~ /color: ?red/
218
+ css.should =~ /color: ?blue/
219
+ js.should include('console.log')
220
+ end
221
+
222
+ it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling with a multi-level hash' do
223
+ app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :compiled_js_dir=>nil, :compiled_css_dir=>nil,
224
+ :css=>{:assets=>{:css=>%w'app.scss raw.css'}}, :js=>{:assets=>{:js=>{:head=>'app.js'}}}
225
+ app.compile_assets
226
+ app.route do |r|
227
+ r.assets
228
+ r.is 'test' do
229
+ "#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
230
+ end
231
+ end
232
+ html = body('/test')
233
+ html.scan(/<link/).length.should == 1
234
+ html =~ %r{href="(/assets/app\.assets\.css\.[a-f0-9]{40}\.css)"}
235
+ css = body($1)
236
+ html.scan(/<script/).length.should == 1
237
+ html =~ %r{src="(/assets/app\.assets\.js\.head\.[a-f0-9]{40}\.js)"}
238
+ js = body($1)
239
+ css.should =~ /color: ?red/
240
+ css.should =~ /color: ?blue/
241
+ js.should include('console.log')
242
+ end
243
+
244
+ it 'should handle :group_subdirs => false when compiling' do
245
+ app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :compiled_js_dir=>nil, :compiled_css_dir=>nil, :group_subdirs=>false,
246
+ :css=>{:assets=>{:css=>%w'assets/css/app.scss assets/css/raw.css'}}, :js=>{:assets=>{:js=>{:head=>'assets/js/head/app.js'}}}
247
+ app.compile_assets
248
+ app.route do |r|
249
+ r.assets
250
+ r.is 'test' do
251
+ "#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
252
+ end
253
+ end
254
+ html = body('/test')
255
+ html.scan(/<link/).length.should == 1
256
+ html =~ %r{href="(/assets/app\.assets\.css\.[a-f0-9]{40}\.css)"}
257
+ css = body($1)
258
+ html.scan(/<script/).length.should == 1
259
+ html =~ %r{src="(/assets/app\.assets\.js\.head\.[a-f0-9]{40}\.js)"}
260
+ js = body($1)
261
+ css.should =~ /color: ?red/
262
+ css.should =~ /color: ?blue/
263
+ js.should include('console.log')
264
+ end
265
+
266
+ it 'should handle automatically creating directories when compiling assets' do
267
+ app.plugin :assets, :public=>'spec/public'
268
+ app.compile_assets
269
+ html = body('/test')
270
+ html.scan(/<link/).length.should == 1
271
+ html =~ %r{href="(/assets/app\.[a-f0-9]{40}\.css)"}
272
+ css = body($1)
273
+ html.scan(/<script/).length.should == 1
274
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
275
+ js = body($1)
276
+ css.should =~ /color: ?red/
277
+ css.should =~ /color: ?blue/
278
+ js.should include('console.log')
279
+ end
280
+
281
+ it 'should handle compiling only css assets' do
282
+ app.compile_assets(:css)
283
+ html = body('/test')
284
+ html.scan(/<link/).length.should == 1
285
+ html =~ %r{href="(/assets/app\.[a-f0-9]{40}\.css)"}
286
+ css = body($1)
287
+ html.scan(/<script/).length.should == 0
288
+ css.should =~ /color: ?red/
289
+ css.should =~ /color: ?blue/
290
+ end
291
+
292
+ it 'should handle compiling only js assets' do
293
+ app.compile_assets(:js)
294
+ html = body('/test')
295
+ html.scan(/<link/).length.should == 0
296
+ html.scan(/<script/).length.should == 1
297
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
298
+ js = body($1)
299
+ js.should include('console.log')
300
+ end
301
+
302
+ it 'should handle compiling asset subfolders' do
303
+ app.compile_assets([:js, :head])
304
+ html = body('/test')
305
+ html.scan(/<link/).length.should == 0
306
+ html.scan(/<script/).length.should == 1
307
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
308
+ js = body($1)
309
+ js.should include('console.log')
310
+ end
311
+
312
+ it 'should handle compiling assets when only a single asset type is present' do
313
+ app.plugin :assets, :css=>nil
314
+ app.compile_assets
315
+ html = body('/test')
316
+ html.scan(/<link/).length.should == 0
317
+ html.scan(/<script/).length.should == 1
318
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
319
+ js = body($1)
320
+ js.should include('console.log')
321
+ end
322
+
323
+ it 'should handle compiling assets when an empty array is used' do
324
+ app.plugin :assets, :css=>[]
325
+ app.compile_assets
326
+ html = body('/test')
327
+ html.scan(/<link/).length.should == 0
328
+ html.scan(/<script/).length.should == 1
329
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
330
+ js = body($1)
331
+ js.should include('console.log')
332
+ end
333
+
334
+ it '#assets should include attributes given' do
335
+ app.new.assets([:js, :head], 'a'=>'b').should == '<script type="text/javascript" a="b" src="/assets/js/head/app.js"></script>'
336
+ end
337
+
338
+ it '#assets should escape attribute values given' do
339
+ app.new.assets([:js, :head], 'a'=>'b"e').should == '<script type="text/javascript" a="b&quot;e" src="/assets/js/head/app.js"></script>'
340
+ end
341
+
342
+ it 'requests for assets should return 304 if the asset has not been modified' do
343
+ loc = '/assets/js/head/app.js'
344
+ lm = header('Last-Modified', loc)
345
+ status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should == 304
346
+ body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should == ''
347
+ end
348
+
349
+ it 'requests for assets should not return 304 if the asset has been modified' do
350
+ loc = '/assets/js/head/app.js'
351
+ lm = header('Last-Modified', loc)
352
+ File.utime(js_atime, js_mtime+1, js_file)
353
+ status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should == 200
354
+ body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should include('console.log')
355
+ end
356
+
357
+ it 'requests for assets should return 304 if the dependency of an asset has not been modified' do
358
+ app.plugin :assets, :dependencies=>{js_file=>css_file}
359
+ loc = '/assets/js/head/app.js'
360
+ lm = header('Last-Modified', loc)
361
+ status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should == 304
362
+ body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should == ''
363
+ end
364
+
365
+ it 'requests for assets should return 200 if the dependency of an asset has been modified' do
366
+ app.plugin :assets, :dependencies=>{js_file=>css_file}
367
+ loc = '/assets/js/head/app.js'
368
+ lm = header('Last-Modified', loc)
369
+ File.utime(css_atime, [css_mtime+1, js_mtime+1].max, css_file)
370
+ status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should == 200
371
+ body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).should include('console.log')
372
+ end
373
+
374
+ it 'should do a terminal match for assets' do
375
+ status('/assets/css/app.scss/foo').should == 404
376
+ end
377
+
378
+ it 'should only allow files that you specify' do
379
+ status('/assets/css/no_access.css').should == 404
380
+ end
381
+
382
+ it 'should not add routes for empty asset types' do
383
+ app.plugin :assets, :css=>nil
384
+ a = app::RodaRequest.assets_matchers
385
+ a.length.should == 1
386
+ a.first.length.should == 2
387
+ a.first.first.should == 'js'
388
+ 'assets/js/head/app.js'.should =~ a.first.last
389
+ 'assets/js/head/app2.js'.should_not =~ a.first.last
390
+ end
391
+
392
+ it 'should not add routes if no asset types' do
393
+ app.plugin :assets, :js=>nil, :css=>nil
394
+ app::RodaRequest.assets_matchers.should == []
395
+ end
396
+
397
+ it 'should support :precompiled option' do
398
+ app.plugin :assets, :precompiled=>metadata_file
399
+ File.exist?(metadata_file).should == false
400
+ app.new.assets([:js, :head]).should == '<script type="text/javascript" src="/assets/js/head/app.js"></script>'
401
+
402
+ app.compile_assets
403
+ File.exist?(metadata_file).should == true
404
+ app.new.assets([:js, :head]).should =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
405
+
406
+ app.plugin :assets, :compiled=>false, :precompiled=>false
407
+ app.new.assets([:js, :head]).should == '<script type="text/javascript" src="/assets/js/head/app.js"></script>'
408
+
409
+ app.plugin :assets, :precompiled=>metadata_file
410
+ app.new.assets([:js, :head]).should =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
411
+ end
412
+ end
413
+ end