roda 2.0.0 → 2.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +26 -0
  3. data/README.rdoc +83 -22
  4. data/Rakefile +1 -1
  5. data/doc/release_notes/2.1.0.txt +124 -0
  6. data/lib/roda/plugins/assets.rb +17 -9
  7. data/lib/roda/plugins/class_level_routing.rb +5 -2
  8. data/lib/roda/plugins/delegate.rb +6 -3
  9. data/lib/roda/plugins/indifferent_params.rb +7 -0
  10. data/lib/roda/plugins/mailer.rb +18 -1
  11. data/lib/roda/plugins/multi_route.rb +2 -1
  12. data/lib/roda/plugins/path.rb +75 -6
  13. data/lib/roda/plugins/render.rb +33 -14
  14. data/lib/roda/plugins/static.rb +35 -0
  15. data/lib/roda/plugins/view_options.rb +161 -0
  16. data/lib/roda/plugins/view_subdirs.rb +6 -63
  17. data/lib/roda/version.rb +1 -1
  18. data/spec/composition_spec.rb +12 -0
  19. data/spec/matchers_spec.rb +34 -0
  20. data/spec/plugin/assets_spec.rb +112 -17
  21. data/spec/plugin/delete_empty_headers_spec.rb +12 -0
  22. data/spec/plugin/mailer_spec.rb +46 -3
  23. data/spec/plugin/module_include_spec.rb +17 -0
  24. data/spec/plugin/multi_route_spec.rb +10 -0
  25. data/spec/plugin/named_templates_spec.rb +6 -0
  26. data/spec/plugin/not_found_spec.rb +1 -1
  27. data/spec/plugin/path_spec.rb +76 -0
  28. data/spec/plugin/render_each_spec.rb +6 -0
  29. data/spec/plugin/render_spec.rb +40 -1
  30. data/spec/plugin/sinatra_helpers_spec.rb +5 -0
  31. data/spec/plugin/static_spec.rb +30 -0
  32. data/spec/plugin/view_options_spec.rb +117 -0
  33. data/spec/spec_helper.rb +5 -1
  34. data/spec/views/multiple-layout.erb +1 -0
  35. data/spec/views/multiple.erb +1 -0
  36. metadata +10 -4
  37. data/spec/plugin/static_path_info_spec.rb +0 -56
  38. data/spec/plugin/view_subdirs_spec.rb +0 -44
@@ -1,63 +1,6 @@
1
- class Roda
2
- module RodaPlugins
3
- # The view_subdirs plugin is designed for sites that have
4
- # outgrown a flat view directory and use subdirectories
5
- # for views. It allows you to set the view directory to
6
- # use, and template names that do not contain a slash will
7
- # automatically use that view subdirectory. Example:
8
- #
9
- # plugin :render, :layout=>'./layout'
10
- # plugin :view_subdirs
11
- #
12
- # route do |r|
13
- # r.on "users" do
14
- # set_view_subdir 'users'
15
- #
16
- # r.get :id do
17
- # view 'profile' # uses ./views/users/profile.erb
18
- # end
19
- #
20
- # r.get 'list' do
21
- # view 'lists/users' # uses ./views/lists/users.erb
22
- # end
23
- # end
24
- # end
25
- #
26
- # Note that when a view subdirectory is set, the layout will
27
- # also be looked up in the subdirectory unless it contains
28
- # a slash. So if you want to use a view subdirectory for
29
- # templates but have a shared layout, you should make sure your
30
- # layout contains a slash, similar to the example above.
31
- module ViewSubdirs
32
- # Load the render plugin before this plugin, since this plugin
33
- # works by overriding a method in the render plugin.
34
- def self.load_dependencies(app)
35
- app.plugin :render
36
- end
37
-
38
- module InstanceMethods
39
- # Set the view subdirectory to use. This can be set to nil
40
- # to not use a view subdirectory.
41
- def set_view_subdir(v)
42
- @_view_subdir = v
43
- end
44
-
45
- private
46
-
47
- # Override the template name to use the view subdirectory if the
48
- # there is a view subdirectory and the template name does not
49
- # contain a slash.
50
- def template_name(opts)
51
- name = super
52
- if (v = @_view_subdir) && name !~ /\//
53
- "#{v}/#{name}"
54
- else
55
- name
56
- end
57
- end
58
- end
59
- end
60
-
61
- register_plugin(:view_subdirs, ViewSubdirs)
62
- end
63
- end
1
+ # View options is a superset of the view_subdirs plugin,
2
+ # which no longer exists. For backwards compatibility,
3
+ # make attempts to load the view_subdirs plugin load the
4
+ # view_options plugin instead.
5
+ require 'roda/plugins/view_options'
6
+ Roda::RodaPlugins.register_plugin(:view_subdirs, Roda::RodaPlugins::ViewOptions)
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 0
7
+ RodaMinorVersion = 1
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -16,4 +16,16 @@ describe "r.run" do
16
16
 
17
17
  body("/provider/services/101").should == 'View 101'
18
18
  end
19
+
20
+ it "modifies SCRIPT_NAME/PATH_INFO when calling run" do
21
+ a = app{|r| "#{r.script_name}|#{r.path_info}"}
22
+ app(:static_path_info){|r| r.on("a"){r.run a}}
23
+ body("/a/b").should == "/a|/b"
24
+ end
25
+
26
+ it "restores SCRIPT_NAME/PATH_INFO before returning from run" do
27
+ a = app{|r| "#{r.script_name}|#{r.path_info}"}
28
+ app(:static_path_info){|r| s = catch(:halt){r.on("a"){r.run a}}; "#{s[2].join}%#{r.script_name}|#{r.path_info}"}
29
+ body("/a/b").should == "/a|/b%|/a/b"
30
+ end
19
31
  end
@@ -291,6 +291,40 @@ describe "r.on" do
291
291
  body("/123").should == '+1'
292
292
  end
293
293
 
294
+ it "does not modify SCRIPT_NAME/PATH_INFO during routing" do
295
+ app(:pass) do |r|
296
+ r.on "foo" do
297
+ r.is "bar" do
298
+ "bar|#{env['SCRIPT_NAME']}|#{env['PATH_INFO']}"
299
+ end
300
+ r.is "baz" do
301
+ r.pass
302
+ end
303
+ "foo|#{env['SCRIPT_NAME']}|#{env['PATH_INFO']}"
304
+ end
305
+ "#{env['SCRIPT_NAME']}|#{env['PATH_INFO']}"
306
+ end
307
+
308
+ body.should == '|/'
309
+ body('SCRIPT_NAME'=>'/a').should == '/a|/'
310
+ body('/foo').should == 'foo||/foo'
311
+ body('/foo', 'SCRIPT_NAME'=>'/a').should == 'foo|/a|/foo'
312
+ body('/foo/bar').should == 'bar||/foo/bar'
313
+ body('/foo/bar', 'SCRIPT_NAME'=>'/a').should == 'bar|/a|/foo/bar'
314
+ body('/foo/baz').should == 'foo||/foo/baz'
315
+ body('/foo/baz', 'SCRIPT_NAME'=>'/a').should == 'foo|/a|/foo/baz'
316
+ end
317
+
318
+ it "should have path/matched_path/remaining_path work correctly" do
319
+ app do |r|
320
+ r.on "foo" do
321
+ "#{r.path}:#{r.matched_path}:#{r.remaining_path}"
322
+ end
323
+ end
324
+
325
+ body("/foo/bar").should == "/foo/bar:/foo:/bar"
326
+ end
327
+
294
328
  it "ensures remaining_path is reverted if modified in failing matcher" do
295
329
  app do |r|
296
330
  r.on lambda { @remaining_path = "/blah"; false } do
@@ -11,9 +11,9 @@ rescue LoadError
11
11
  end
12
12
 
13
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'
14
+ metadata_file = File.expand_path('spec/assets/tmp/precompiled.json')
15
+ js_file = File.expand_path('spec/assets/js/head/app.js')
16
+ css_file = File.expand_path('spec/assets/css/no_access.css')
17
17
  js_mtime = File.mtime(js_file)
18
18
  js_atime = File.atime(js_file)
19
19
  css_mtime = File.mtime(css_file)
@@ -48,44 +48,58 @@ if run_tests
48
48
  end
49
49
 
50
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"
51
+ fpaths = [:js_path, :css_path, :compiled_js_path, :compiled_css_path]
52
+ rpaths = [:js_prefix, :css_prefix, :compiled_js_prefix, :compiled_css_prefix]
53
+ app.assets_opts.values_at(*fpaths).should == %w"spec/assets/js/ spec/assets/css/ spec/assets/app spec/assets/app".map{|s| File.join(Dir.pwd, s)}
54
+ app.assets_opts.values_at(*rpaths).should == %w"assets/js/ assets/css/ assets/app assets/app"
53
55
 
54
56
  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"
57
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/as/a foo/as/a".map{|s| File.join(Dir.pwd, s)}
58
+ app.assets_opts.values_at(*rpaths).should == %w"as/j/ as/c/ as/a as/a"
56
59
 
57
60
  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"
61
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/as/a foo/as/a".map{|s| File.join(Dir.pwd, s)}
62
+ app.assets_opts.values_at(*rpaths).should == %w"as/j/ as/c/ as/a as/a"
59
63
 
60
64
  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"
65
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
66
+ app.assets_opts.values_at(*rpaths).should == %w"as/j/ as/c/ as/cj/a as/cs/a"
62
67
 
63
68
  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"
69
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
70
+ app.assets_opts.values_at(*rpaths).should == %w"as/jr/ as/cr/ as/cjr/a as/ccr/a"
65
71
 
66
72
  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"
73
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
74
+ app.assets_opts.values_at(*rpaths).should == %w"as/j/ as/c/ as/cj/a as/cs/a"
68
75
 
69
76
  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"
77
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
78
+ app.assets_opts.values_at(*rpaths).should == %w"as/j/ as/c/ as/cj/a as/cs/a"
71
79
 
72
80
  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"
81
+ app.assets_opts.values_at(*fpaths).should == %w"bar/j/ bar/c/ foo/cp/a foo/cp/a".map{|s| File.join(Dir.pwd, s)}
82
+ app.assets_opts.values_at(*rpaths).should == %w"as/j/ as/c/ as/a as/a"
74
83
 
75
84
  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"
85
+ app.assets_opts.values_at(*fpaths).should == %w"bar/ bar/ foo/cp/a foo/cp/a".map{|s| File.join(Dir.pwd, s)}
86
+ app.assets_opts.values_at(*rpaths).should == %w"as/ as/ as/a as/a"
77
87
 
78
88
  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"
89
+ app.assets_opts.values_at(*fpaths).should == %w"bar/ bar/ cp/a cp/a".map{|s| File.join(Dir.pwd, s)}
90
+ app.assets_opts.values_at(*rpaths).should == %w"as/ as/ as/a as/a"
80
91
 
81
92
  app.plugin :assets, :path=>'', :compiled_path=>nil
82
- app.assets_opts.values_at(*keys).should == ['', '', 'a', 'a', 'as/', 'as/', 'as/a', 'as/a']
93
+ app.assets_opts.values_at(*fpaths).should == ['', '', 'a', 'a'].map{|s| File.join(Dir.pwd, s)}
94
+ app.assets_opts.values_at(*rpaths).should == ['as/', 'as/', 'as/a', 'as/a']
83
95
 
84
96
  app.plugin :assets, :prefix=>''
85
- app.assets_opts.values_at(*keys).should == ['', '', 'a', 'a', '', '', 'a', 'a']
97
+ app.assets_opts.values_at(*fpaths).should == ['', '', 'a', 'a'].map{|s| File.join(Dir.pwd, s)}
98
+ app.assets_opts.values_at(*rpaths).should == ['', '', 'a', 'a']
86
99
 
87
100
  app.plugin :assets, :compiled_name=>nil
88
- app.assets_opts.values_at(*keys).should == ['', '', '', '', '', '', '', '']
101
+ app.assets_opts.values_at(*fpaths).should == ['', ''].map{|s| File.join(Dir.pwd, s)} + ['', ''].map{|s| File.join(Dir.pwd, s).chop}
102
+ app.assets_opts.values_at(*rpaths).should == ['', '', '', '']
89
103
  end
90
104
 
91
105
  it 'assets_opts should use headers and dependencies given options' do
@@ -126,6 +140,22 @@ if run_tests
126
140
  js.should include('console.log')
127
141
  end
128
142
 
143
+ it 'should handle rendering assets, linking to them, and accepting requests for them when :add_script_name app option is used' do
144
+ app.opts[:add_script_name] = true
145
+ app.plugin :assets
146
+ html = body('/test', 'SCRIPT_NAME'=>'/foo')
147
+ html.scan(/<link/).length.should == 2
148
+ html =~ %r{href="/foo(/assets/css/app\.scss)"}
149
+ css = body($1)
150
+ html =~ %r{href="/foo(/assets/css/raw\.css)"}
151
+ css2 = body($1)
152
+ html.scan(/<script/).length.should == 1
153
+ html =~ %r{src="/foo(/assets/js/head/app\.js)"}
154
+ js = body($1)
155
+ css.should =~ /color: red;/
156
+ css2.should =~ /color: blue;/
157
+ end
158
+
129
159
  it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling, with different options' do
130
160
  app.plugin :assets, :path=>'spec/', :js_dir=>'assets/js', :css_dir=>'assets/css', :prefix=>'a',
131
161
  :js_route=>'foo', :css_route=>'bar', :add_suffix=>true, :css_opts=>{:style=>:compressed}
@@ -203,6 +233,21 @@ if run_tests
203
233
  js.should include('console.log')
204
234
  end
205
235
 
236
+ it 'should handle compiling assets, linking to them, and accepting requests for them when :add_script_name app option is used' do
237
+ app.opts[:add_script_name] = true
238
+ app.plugin :assets
239
+ app.compile_assets
240
+ html = body('/test', 'SCRIPT_NAME'=>'/foo')
241
+ html =~ %r{href="/foo(/assets/app\.[a-f0-9]{40}\.css)"}
242
+ css = body($1)
243
+ html.scan(/<script/).length.should == 1
244
+ html =~ %r{src="/foo(/assets/app\.head\.[a-f0-9]{40}\.js)"}
245
+ js = body($1)
246
+ css.should =~ /color: ?red/
247
+ css.should =~ /color: ?blue/
248
+ js.should include('console.log')
249
+ end
250
+
206
251
  it 'should handle compiling assets, linking to them, and accepting requests for them, with different options' do
207
252
  app.plugin :assets, :compiled_path=>nil, :js_dir=>'assets/js', :css_dir=>'assets/css', :prefix=>'a',
208
253
  :public=>'spec/assets', :path=>'spec', :compiled_js_route=>'foo', :compiled_css_route=>'bar'
@@ -241,6 +286,29 @@ if run_tests
241
286
  js.should include('console.log')
242
287
  end
243
288
 
289
+ it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling with a multi-level hash when :add_script_name app option is used' do
290
+ app.opts[:add_script_name] = true
291
+ app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :compiled_js_dir=>nil, :compiled_css_dir=>nil,
292
+ :css=>{:assets=>{:css=>%w'app.scss raw.css'}}, :js=>{:assets=>{:js=>{:head=>'app.js'}}}
293
+ app.compile_assets
294
+ app.route do |r|
295
+ r.assets
296
+ r.is 'test' do
297
+ "#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
298
+ end
299
+ end
300
+ html = body('/test', 'SCRIPT_NAME'=>'/foo')
301
+ html.scan(/<link/).length.should == 1
302
+ html =~ %r{href="/foo(/assets/app\.assets\.css\.[a-f0-9]{40}\.css)"}
303
+ css = body($1)
304
+ html.scan(/<script/).length.should == 1
305
+ html =~ %r{src="/foo(/assets/app\.assets\.js\.head\.[a-f0-9]{40}\.js)"}
306
+ js = body($1)
307
+ css.should =~ /color: ?red/
308
+ css.should =~ /color: ?blue/
309
+ js.should include('console.log')
310
+ end
311
+
244
312
  it 'should handle :group_subdirs => false when compiling' do
245
313
  app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :compiled_js_dir=>nil, :compiled_css_dir=>nil, :group_subdirs=>false,
246
314
  :css=>{:assets=>{:css=>%w'assets/css/app.scss assets/css/raw.css'}}, :js=>{:assets=>{:js=>{:head=>'assets/js/head/app.js'}}}
@@ -410,4 +478,31 @@ if run_tests
410
478
  app.allocate.assets([:js, :head]).should =~ %r{src="(/assets/app\.head\.[a-f0-9]{40}\.js)"}
411
479
  end
412
480
  end
481
+
482
+ describe 'assets plugin' do
483
+ it "app :root option affects :views default" do
484
+ app.plugin :assets
485
+ app.assets_opts[:path].should == File.join(Dir.pwd, 'assets')
486
+ app.assets_opts[:js_path].should == File.join(Dir.pwd, 'assets/js/')
487
+ app.assets_opts[:css_path].should == File.join(Dir.pwd, 'assets/css/')
488
+
489
+ app.opts[:root] = '/foo'
490
+ app.plugin :assets
491
+ app.assets_opts[:path].should == '/foo/assets'
492
+ app.assets_opts[:js_path].should == '/foo/assets/js/'
493
+ app.assets_opts[:css_path].should == '/foo/assets/css/'
494
+
495
+ app.opts[:root] = '/foo/bar'
496
+ app.plugin :assets
497
+ app.assets_opts[:path].should == '/foo/bar/assets'
498
+ app.assets_opts[:js_path].should == '/foo/bar/assets/js/'
499
+ app.assets_opts[:css_path].should == '/foo/bar/assets/css/'
500
+
501
+ app.opts[:root] = nil
502
+ app.plugin :assets
503
+ app.assets_opts[:path].should == File.join(Dir.pwd, 'assets')
504
+ app.assets_opts[:js_path].should == File.join(Dir.pwd, 'assets/js/')
505
+ app.assets_opts[:css_path].should == File.join(Dir.pwd, 'assets/css/')
506
+ end
507
+ end
413
508
  end
@@ -12,4 +12,16 @@ describe "delete_empty_headers plugin" do
12
12
 
13
13
  req[1].should == {'Bar'=>'1'}
14
14
  end
15
+
16
+ it "is called when finishing with a body" do
17
+ app(:delete_empty_headers) do |r|
18
+ response['Foo'] = ''
19
+ response['Content-Type'] = ''
20
+ response['Content-Length'] = ''
21
+ response['Bar'] = '1'
22
+ r.halt response.finish_with_body(['a'])
23
+ end
24
+
25
+ req[1].should == {'Bar'=>'1'}
26
+ end
15
27
  end
@@ -87,6 +87,46 @@ describe "mailer plugin" do
87
87
  m.attachments.length.should == 1
88
88
  m.attachments.first.content_type.should =~ /mailer_spec\.rb/
89
89
  m.content_type.should =~ /\Amultipart\/mixed/
90
+ m.parts.length.should == 1
91
+ m.parts.first.body.should == File.read(__FILE__)
92
+ end
93
+
94
+ it "supports attachments with blocks" do
95
+ app(:mailer) do |r|
96
+ r.mail do
97
+ instance_exec(&setup_email)
98
+ add_file __FILE__ do
99
+ response.mail.attachments.last.content_type = 'text/foo'
100
+ end
101
+ end
102
+ end
103
+
104
+ m = app.mail('foo')
105
+ m.attachments.length.should == 1
106
+ m.attachments.first.content_type.should == 'text/foo'
107
+ m.content_type.should =~ /\Amultipart\/mixed/
108
+ m.parts.length.should == 1
109
+ m.parts.first.body.should == File.read(__FILE__)
110
+ end
111
+
112
+ it "supports plain-text attachments with an email body" do
113
+ app(:mailer) do |r|
114
+ r.mail do
115
+ instance_exec(&setup_email)
116
+ add_file :filename=>'a.txt', :content=>'b'
117
+ 'c'
118
+ end
119
+ end
120
+
121
+ m = app.mail('foo')
122
+ m.parts.length.should == 2
123
+ m.parts.first.content_type.should =~ /text\/plain/
124
+ m.parts.first.body.should == 'c'
125
+ m.parts.last.content_type.should =~ /text\/plain/
126
+ m.parts.last.body.should == 'b'
127
+ m.attachments.length.should == 1
128
+ m.attachments.first.content_type.should =~ /a\.txt/
129
+ m.content_type.should =~ /\Amultipart\/mixed/
90
130
  end
91
131
 
92
132
  it "supports regular web requests in same application" do
@@ -174,7 +214,7 @@ describe "mailer plugin" do
174
214
  app.mail('/').content_type.should =~ /\Atext\/foo/
175
215
  end
176
216
 
177
- it "supports handle setting the default content type when attachments are used" do
217
+ it "supports setting the default content type when attachments are used" do
178
218
  app(:bare) do
179
219
  plugin :mailer, :content_type=>'text/html'
180
220
  route do
@@ -184,8 +224,11 @@ describe "mailer plugin" do
184
224
  end
185
225
  m = app.mail('/')
186
226
  m.content_type.should =~ /\Amultipart\/mixed/
187
- m.parts.first.content_type.should =~ /\Atext\/css/
188
- m.parts.last.content_type.should =~ /\Atext\/html/
227
+ m.parts.length.should == 2
228
+ m.parts.first.content_type.should =~ /\Atext\/html/
229
+ m.parts.first.body.should == "a"
230
+ m.parts.last.content_type.should =~ /\Atext\/css/
231
+ m.parts.last.body.should == File.read('spec/assets/css/raw.css')
189
232
  end
190
233
  end
191
234
  end
@@ -28,4 +28,21 @@ describe "module_include plugin" do
28
28
 
29
29
  req.should == [1, {}, []]
30
30
  end
31
+
32
+ it "should work if called multiple times with a block" do
33
+ app(:bare) do
34
+ plugin :module_include
35
+ request_module{def h; halt response.f end}
36
+ request_module{def i; h end}
37
+ response_module{def f; finish end}
38
+ response_module{def finish; [1, {}, []] end}
39
+
40
+ route do |r|
41
+ r.i
42
+ end
43
+ end
44
+
45
+ req.should == [1, {}, []]
46
+ end
47
+
31
48
  end
@@ -170,6 +170,16 @@ describe "multi_route plugin" do
170
170
  end
171
171
  end
172
172
 
173
+ describe "multi_route plugin" do
174
+ it "r.multi_route works even without routes defined" do
175
+ app(:multi_route) do |r|
176
+ r.multi_route
177
+ 'a'
178
+ end
179
+ body.should == 'a'
180
+ end
181
+ end
182
+
173
183
  describe "multi_route plugin" do
174
184
  before do
175
185
  app(:bare) do