roda 1.0.0 → 1.1.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 (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