merb 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/README +21 -14
  2. data/Rakefile +157 -108
  3. data/SVN_REVISION +1 -0
  4. data/app_generators/merb/templates/Rakefile +20 -4
  5. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
  6. data/app_generators/merb/templates/config/boot.rb +1 -1
  7. data/app_generators/merb/templates/config/dependencies.rb +3 -3
  8. data/app_generators/merb/templates/config/merb.yml +5 -0
  9. data/app_generators/merb/templates/config/merb_init.rb +3 -3
  10. data/app_generators/merb/templates/script/destroy +3 -0
  11. data/app_generators/merb/templates/script/generate +1 -1
  12. data/app_generators/merb/templates/spec/spec_helper.rb +2 -2
  13. data/app_generators/merb/templates/test/test_helper.rb +1 -1
  14. data/app_generators/merb_plugin/merb_plugin_generator.rb +4 -0
  15. data/bin/merb +1 -3
  16. data/lib/merb.rb +144 -76
  17. data/lib/merb/abstract_controller.rb +6 -5
  18. data/lib/merb/assets.rb +119 -0
  19. data/lib/merb/boot_loader.rb +217 -0
  20. data/lib/merb/caching.rb +1 -1
  21. data/lib/merb/caching/action_cache.rb +1 -1
  22. data/lib/merb/caching/fragment_cache.rb +1 -1
  23. data/lib/merb/caching/store/file_cache.rb +1 -1
  24. data/lib/merb/config.rb +290 -0
  25. data/lib/merb/controller.rb +5 -5
  26. data/lib/merb/core_ext/get_args.rb +1 -0
  27. data/lib/merb/core_ext/hash.rb +182 -169
  28. data/lib/merb/core_ext/kernel.rb +57 -26
  29. data/lib/merb/dispatcher.rb +6 -6
  30. data/lib/merb/drb_server.rb +1 -1
  31. data/lib/merb/generators/merb_generator_helpers.rb +7 -6
  32. data/lib/merb/logger.rb +1 -1
  33. data/lib/merb/mail_controller.rb +3 -4
  34. data/lib/merb/mailer.rb +2 -2
  35. data/lib/merb/mixins/basic_authentication.rb +2 -2
  36. data/lib/merb/mixins/controller.rb +1 -1
  37. data/lib/merb/mixins/general_controller.rb +13 -20
  38. data/lib/merb/mixins/inline_partial.rb +32 -0
  39. data/lib/merb/mixins/render.rb +3 -3
  40. data/lib/merb/mixins/responder.rb +1 -1
  41. data/lib/merb/mixins/view_context.rb +159 -33
  42. data/lib/merb/mongrel_handler.rb +9 -9
  43. data/lib/merb/plugins.rb +1 -1
  44. data/lib/merb/request.rb +25 -1
  45. data/lib/merb/router.rb +264 -226
  46. data/lib/merb/server.rb +66 -560
  47. data/lib/merb/session/cookie_store.rb +14 -13
  48. data/lib/merb/session/mem_cache_session.rb +20 -10
  49. data/lib/merb/session/memory_session.rb +21 -11
  50. data/lib/merb/template.rb +2 -2
  51. data/lib/merb/template/erubis.rb +3 -33
  52. data/lib/merb/template/haml.rb +8 -3
  53. data/lib/merb/test/fake_request.rb +8 -3
  54. data/lib/merb/test/helper.rb +66 -22
  55. data/lib/merb/test/rspec.rb +9 -155
  56. data/lib/merb/test/rspec_matchers/controller_matchers.rb +117 -0
  57. data/lib/merb/test/rspec_matchers/markup_matchers.rb +98 -0
  58. data/lib/merb/upload_handler.rb +2 -1
  59. data/lib/merb/version.rb +38 -3
  60. data/lib/merb/view_context.rb +1 -2
  61. data/lib/tasks/merb.rake +11 -11
  62. data/merb_generators/part_controller/USAGE +5 -0
  63. data/merb_generators/part_controller/part_controller_generator.rb +27 -0
  64. data/merb_generators/part_controller/templates/controller.rb +8 -0
  65. data/merb_generators/part_controller/templates/helper.rb +5 -0
  66. data/merb_generators/part_controller/templates/index.html.erb +3 -0
  67. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +1 -1
  68. data/script/destroy +14 -0
  69. data/script/generate +14 -0
  70. data/spec/fixtures/controllers/dispatch_spec_controllers.rb +9 -1
  71. data/spec/fixtures/controllers/render_spec_controllers.rb +5 -5
  72. data/spec/fixtures/models/router_spec_models.rb +10 -0
  73. data/spec/merb/abstract_controller_spec.rb +2 -2
  74. data/spec/merb/assets_spec.rb +207 -0
  75. data/spec/merb/caching_spec.rb +2 -2
  76. data/spec/merb/controller_spec.rb +7 -2
  77. data/spec/merb/cookie_store_spec.rb +1 -1
  78. data/spec/merb/core_ext/class_spec.rb +97 -0
  79. data/spec/merb/core_ext/enumerable_spec.rb +27 -0
  80. data/spec/merb/core_ext/hash_spec.rb +251 -0
  81. data/spec/merb/core_ext/inflector_spec.rb +34 -0
  82. data/spec/merb/core_ext/kernel_spec.rb +25 -0
  83. data/spec/merb/core_ext/numeric_spec.rb +26 -0
  84. data/spec/merb/core_ext/object_spec.rb +47 -0
  85. data/spec/merb/core_ext/string_spec.rb +22 -0
  86. data/spec/merb/core_ext/symbol_spec.rb +7 -0
  87. data/spec/merb/dependency_spec.rb +22 -0
  88. data/spec/merb/dispatch_spec.rb +23 -12
  89. data/spec/merb/fake_request_spec.rb +8 -0
  90. data/spec/merb/generator_spec.rb +140 -21
  91. data/spec/merb/handler_spec.rb +5 -5
  92. data/spec/merb/mail_controller_spec.rb +3 -3
  93. data/spec/merb/render_spec.rb +1 -1
  94. data/spec/merb/responder_spec.rb +3 -3
  95. data/spec/merb/router_spec.rb +260 -191
  96. data/spec/merb/server_spec.rb +5 -5
  97. data/spec/merb/upload_handler_spec.rb +7 -0
  98. data/spec/merb/version_spec.rb +33 -0
  99. data/spec/merb/view_context_spec.rb +217 -59
  100. data/spec/spec_generator_helper.rb +15 -0
  101. data/spec/spec_helper.rb +5 -3
  102. data/spec/spec_helpers/url_shared_behaviour.rb +5 -7
  103. metadata +32 -7
  104. data/lib/merb/caching/store/memcache.rb +0 -20
  105. data/lib/merb/mixins/form_control.rb +0 -332
  106. data/lib/patch +0 -69
  107. data/spec/merb/core_ext_spec.rb +0 -464
  108. data/spec/merb/form_control_mixin_spec.rb +0 -431
@@ -38,8 +38,8 @@ class FakeModelWithArguments
38
38
  end
39
39
 
40
40
  def to_xml(*args)
41
- options = args.last.is_a?(Hash) ? args.pop.stringify_keys : {}
42
- options.keys.sort.inject('') do |str, tag|
41
+ options = args.last.is_a?(Hash) ? args.pop : {}
42
+ options.keys.sort_by{|s| s.to_s}.inject('') do |str, tag|
43
43
  str << "<#{tag}>#{options[tag]}</#{tag}>"
44
44
  end
45
45
  end
@@ -169,6 +169,6 @@ class ExtensionTemplateController < Merb::Controller
169
169
 
170
170
  end
171
171
 
172
- Merb::Server.load_action_arguments
173
- Merb::Server.load_controller_template_path_cache
174
- Merb::Server.load_erubis_inline_helpers
172
+ Merb::BootLoader.load_action_arguments
173
+ Merb::BootLoader.load_controller_template_path_cache
174
+ Merb::BootLoader.load_inline_helpers
@@ -18,3 +18,13 @@ class Comment
18
18
  42
19
19
  end
20
20
  end
21
+
22
+
23
+ class Blog
24
+ def id
25
+ 42
26
+ end
27
+ def to_param
28
+ "hello#{id}"
29
+ end
30
+ end
@@ -13,8 +13,8 @@ describe Merb::AbstractController do
13
13
  end
14
14
 
15
15
  after(:all) do
16
- Merb::Server.load_controller_template_path_cache
17
- Merb::Server.load_erubis_inline_helpers
16
+ Merb::BootLoader.load_controller_template_path_cache
17
+ Merb::BootLoader.load_inline_helpers
18
18
  end
19
19
 
20
20
  it "should add a template path" do
@@ -0,0 +1,207 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require "fileutils"
4
+
5
+ describe Merb::Assets, "in the production environment" do
6
+
7
+ before(:each) do
8
+ @old_env = Merb::Config[:environment]
9
+ Merb::Config[:environment] = :production
10
+ Merb::Config.delete(:bundle_assets)
11
+ end
12
+
13
+ after(:each) do
14
+ Merb::Config[:environment] = @old_env
15
+ end
16
+
17
+ it "should bundle assets" do
18
+ Merb::Assets.bundle?.should == true
19
+ end
20
+
21
+ end
22
+
23
+ describe Merb::Assets, "in the development environment" do
24
+
25
+ before(:each) do
26
+ @old_env = Merb::Config[:environment]
27
+ Merb::Config[:environment] = :development
28
+ Merb::Config.delete(:bundle_assets)
29
+ end
30
+
31
+ after(:each) do
32
+ Merb::Config[:environment] = @old_env
33
+ end
34
+
35
+ it "should not bundle assets" do
36
+ Merb::Assets.bundle?.should == false
37
+ end
38
+
39
+ end
40
+
41
+ describe Merb::Assets, "in the development environment with asset bundling enabled" do
42
+
43
+ before(:each) do
44
+ @old_env = Merb::Config[:environment]
45
+ Merb::Config[:environment] = :development
46
+ Merb::Config[:bundle_assets] = true
47
+ end
48
+
49
+ after(:each) do
50
+ Merb::Config[:environment] = @old_env
51
+ end
52
+
53
+ it "should bundle assets" do
54
+ Merb::Assets.bundle?.should == true
55
+ end
56
+
57
+ end
58
+
59
+ describe Merb::Assets::AssetHelpers, "asset_path" do
60
+
61
+ include Merb::Assets::AssetHelpers
62
+
63
+ it "should turn a symbol shorthand for a javascript asset into a URI path" do
64
+ asset_path(:javascript, :dingo).should == "/javascripts/dingo.js"
65
+ end
66
+
67
+ it "should turn a string shorthand for a javascript asset into a URI path" do
68
+ asset_path(:javascript, "dingo").should == "/javascripts/dingo.js"
69
+ asset_path(:javascript, "dingo.r7337").should == "/javascripts/dingo.r7337.js"
70
+ asset_path(:javascript, "dingo/wakka").should == "/javascripts/dingo/wakka.js"
71
+ end
72
+
73
+ it "should turn the filename of a javascript asset into a URI path" do
74
+ asset_path(:javascript, "dingo.js").should == "/javascripts/dingo.js"
75
+ end
76
+
77
+ it "should turn a symbol shorthand for a stylesheet asset into a URI path" do
78
+ asset_path(:stylesheet, :dingo).should == "/stylesheets/dingo.css"
79
+ end
80
+
81
+ it "should turn a string shorthand for a stylesheet asset into a URI path" do
82
+ asset_path(:stylesheet, "dingo").should == "/stylesheets/dingo.css"
83
+ asset_path(:stylesheet, "dingo.r7337").should == "/stylesheets/dingo.r7337.css"
84
+ asset_path(:stylesheet, "dingo/wakka").should == "/stylesheets/dingo/wakka.css"
85
+ end
86
+
87
+ it "should turn the filename of a stylesheet asset into a URI path" do
88
+ asset_path(:stylesheet, "dingo.css").should == "/stylesheets/dingo.css"
89
+ end
90
+
91
+ it "should turn an asset name into a file path" do
92
+ asset_path(:stylesheet, :dingo, true).should == "public/stylesheets/dingo.css"
93
+ end
94
+
95
+ it "should prepend any path prefix to a URI path" do
96
+ begin
97
+ Merb::Config[:path_prefix] = '/inky'
98
+ asset_path(:stylesheet, "dingo.css").should == "/inky/stylesheets/dingo.css"
99
+ ensure
100
+ Merb::Config.delete(:path_prefix)
101
+ end
102
+ end
103
+
104
+ it "should prepend any path prefix to a local path" do
105
+ begin
106
+ Merb::Config[:path_prefix] = '/inky'
107
+ asset_path(:stylesheet, "dingo.css", true).should == "public/stylesheets/dingo.css"
108
+ ensure
109
+ Merb::Config.delete(:path_prefix)
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "an asset bundler with callbacks", :shared => true do
115
+ it "should collect callbacks for execution after bundling" do
116
+ @bundler_klass.add_callback { |filename| "yay #{filename}" }
117
+ @bundler_klass.callbacks.should have(1).procs
118
+ @bundler_klass.callbacks.first.call("me").should == "yay me"
119
+ @bundler_klass.callbacks.clear
120
+ end
121
+ end
122
+
123
+ describe "an asset bundler", :shared => true do
124
+
125
+ include Merb::Assets::AssetHelpers
126
+
127
+ before(:each) do
128
+ FileUtils.mkdir_p("./public/#{@bundler_klass.asset_type}s")
129
+ create_fixture_files
130
+ end
131
+
132
+ after(:each) do
133
+ FileUtils.rm_rf("./public")
134
+ end
135
+
136
+ def create_fixture_files
137
+ @asset1 = :asset1
138
+ @asset1filename = asset_path(@bundler_klass.asset_type, @asset1, true)
139
+ File.open(@asset1filename, "w") { |f| f << "one" }
140
+
141
+ @asset2 = :asset2
142
+ @asset2filename = asset_path(@bundler_klass.asset_type, @asset2, true)
143
+ File.open(@asset2filename, "w") { |f| f << "two" }
144
+
145
+ @asset3 = :asset3
146
+ @asset3filename = asset_path(@bundler_klass.asset_type, @asset3, true)
147
+ File.open(@asset3filename, "w") { |f| f << "three" }
148
+ end
149
+
150
+ it "should combine files into a single file" do
151
+ bundler = @bundler_klass.new(true, @asset1, @asset2, @asset3)
152
+ bundle_name = bundler.bundle!
153
+ File.read(asset_path(@bundler_klass.asset_type, bundle_name, true)).should == "one\ntwo\nthree\n"
154
+ end
155
+
156
+ it "should not overwrite bundles" do
157
+ File.open(asset_path(@bundler_klass.asset_type, :all, true), "w") { |f| f << "exists" }
158
+ bundler = @bundler_klass.new(true, @asset1, @asset2, @asset3)
159
+ bundle_name = bundler.bundle!
160
+ File.read(asset_path(@bundler_klass.asset_type, bundle_name, true)).should == "exists"
161
+ end
162
+
163
+ it "should execute all callbacks, passing them the bundled file's path" do
164
+ begin
165
+ @bundler_klass.add_callback do |filename|
166
+ File.open(filename, "a") do |f|
167
+ f.puts "yay"
168
+ end
169
+ end
170
+
171
+ bundler = @bundler_klass.new(true, @asset1, @asset2, @asset3)
172
+ bundle_name = bundler.bundle!
173
+ File.read(asset_path(@bundler_klass.asset_type, bundle_name, true)).should == "one\ntwo\nthree\nyay\n"
174
+ ensure
175
+ @bundler_klass.callbacks.clear
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ describe Merb::Assets::JavascriptAssetBundler do
182
+
183
+ before(:each) do
184
+ @bundler_klass = Merb::Assets::JavascriptAssetBundler
185
+ end
186
+
187
+ it "should handle javascript assets" do
188
+ @bundler_klass.asset_type.should == :javascript
189
+ end
190
+
191
+ it_should_behave_like "an asset bundler"
192
+ it_should_behave_like "an asset bundler with callbacks"
193
+ end
194
+
195
+ describe Merb::Assets::StylesheetAssetBundler do
196
+
197
+ before(:each) do
198
+ @bundler_klass = Merb::Assets::StylesheetAssetBundler
199
+ end
200
+
201
+ it "should handle stylesheet assets" do
202
+ @bundler_klass.asset_type.should == :stylesheet
203
+ end
204
+
205
+ it_should_behave_like "an asset bundler"
206
+ it_should_behave_like "an asset bundler with callbacks"
207
+ end
@@ -83,7 +83,7 @@ end
83
83
 
84
84
  describe "fragment caching in memory" do
85
85
  before do
86
- Merb::Server.config['cache_store'] = 'memory'
86
+ Merb::Config['cache_store'] = 'memory'
87
87
  @c = Merb::Caching::Fragment
88
88
  @c.clear
89
89
  end
@@ -93,7 +93,7 @@ end
93
93
 
94
94
  describe "fragment caching to a file" do
95
95
  before do
96
- Merb::Server.config[:cache_store] = 'file'
96
+ Merb::Config[:cache_store] = 'file'
97
97
  @c = Merb::Caching::Fragment
98
98
  @c.clear
99
99
  end
@@ -13,11 +13,16 @@ describe "Merb::Controller" do
13
13
  c = new_controller
14
14
  c._layout.should == :application
15
15
  end
16
-
16
+
17
17
  it "should have a spec helper to dispatch that skips the router" do
18
18
  Merb::Router.should_not_receive(:match)
19
19
  dispatch_to(Bar, :foo, :id => "1") do |controller|
20
- controller.should_receive(:foo).with("1")
20
+ if defined? ParseTreeArray # We have parameterized actions
21
+ controller.should_receive(:foo).with("1")
22
+ else
23
+ controller.should_receive(:foo) # No parameterized actions
24
+ controller.params[:id].should == "1"
25
+ end
21
26
  end
22
27
  end
23
28
 
@@ -18,7 +18,7 @@ class TestCookieSessionController < Merb::Controller
18
18
 
19
19
  end
20
20
 
21
- Merb::Server.config[:session_secret_key] = 'Secret!'
21
+ Merb::Config[:session_secret_key] = 'Secret!'
22
22
 
23
23
  describe Merb::SessionMixin do
24
24
  it "should set the cookie if the cookie is changed" do
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ # Class cattr_reader
4
+
5
+ class ClassWithCAttrReader
6
+ cattr_reader :bacon
7
+ def initialize; @@bacon = "chunky"; end
8
+ end
9
+
10
+ describe "Core Class with cattr_reader", :shared => true do
11
+
12
+ it "should read value from attribute" do
13
+ @klass.bacon.should == "chunky"
14
+ end
15
+
16
+ it "should not write to attribute" do
17
+ lambda {
18
+ @klass.bacon = "soggy"
19
+ }.should raise_error(NoMethodError)
20
+ end
21
+
22
+ end
23
+
24
+ describe Class, "with cattr_reader" do
25
+
26
+ before do
27
+ @klass = ClassWithCAttrReader.new.class
28
+ end
29
+ it_should_behave_like "Core Class with cattr_reader"
30
+
31
+ end
32
+
33
+ describe Class, "with cattr_reader (instantiated)" do
34
+
35
+ before do
36
+ @klass = ClassWithCAttrReader.new
37
+ end
38
+ it_should_behave_like "Core Class with cattr_reader"
39
+
40
+ end
41
+
42
+ # Class cattr_writer
43
+
44
+ class ClassWithCAttrWriter
45
+ cattr_writer :bacon
46
+ def self.chunky?; @@bacon == "chunky"; end
47
+ def chunky?; self.class.chunky?; end
48
+ end
49
+
50
+ describe "Core Class with cattr_writer", :shared => true do
51
+
52
+ it "should write value to attribute" do
53
+ @klass.should be_chunky
54
+ end
55
+
56
+ it "should not read attribute" do
57
+ lambda {
58
+ @klass.bacon
59
+ }.should raise_error(NoMethodError)
60
+ end
61
+
62
+ end
63
+
64
+ describe Class, "with cattr_writer" do
65
+
66
+ before do
67
+ @klass = ClassWithCAttrWriter.new.class
68
+ @klass.bacon = "chunky"
69
+ end
70
+ it_should_behave_like "Core Class with cattr_writer"
71
+
72
+ end
73
+
74
+ describe Class, "with cattr_writer (instantiated)" do
75
+
76
+ before do
77
+ @klass = ClassWithCAttrWriter.new
78
+ @klass.bacon = "chunky"
79
+ end
80
+ it_should_behave_like "Core Class with cattr_writer"
81
+
82
+ end
83
+
84
+ class ClassWithAttrInitialize
85
+ attr_initialize :dog, :muppet
86
+ attr_accessor :dog, :muppet
87
+ end
88
+
89
+ describe ClassWithAttrInitialize do
90
+
91
+ it "should initialize with values" do
92
+ c = ClassWithAttrInitialize.new("louie", "bert")
93
+ c.dog.should == "louie"
94
+ c.muppet.should == "bert"
95
+ end
96
+
97
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Enumerable do
4
+ before do
5
+ @mascots = ['louie', 'bert', 'ernie']
6
+ end
7
+
8
+ it "should perform injecting" do
9
+ @mascots.injecting({}) { |m,i| m[i] = i.size }.should ==
10
+ { 'louie' => 5, 'bert' => 4, 'ernie' => 5 }
11
+ end
12
+
13
+ it "should find arrays of things inside other arrays" do
14
+ @mascots.include_any?('louie', 'sasquatch').should be_true
15
+ end
16
+
17
+ it "should recognize absence of arrays of things inside other arrays" do
18
+ @mascots.include_any?('chicken', 'sasquatch').should be_false
19
+ end
20
+
21
+ it "should group by" do
22
+ groups = (1..6).group_by{ |i| i % 3 }
23
+ groups[0].should == [3,6]
24
+ groups[1].should == [1,4]
25
+ groups[2].should == [2,5]
26
+ end
27
+ end
@@ -0,0 +1,251 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Hash, "environmentize_keys!" do
4
+ it "should transform keys to uppercase text" do
5
+ { :test_1 => 'test', 'test_2' => 'test', 1 => 'test' }.environmentize_keys!.should ==
6
+ { 'TEST_1' => 'test', 'TEST_2' => 'test', '1' => 'test' }
7
+ end
8
+
9
+ it "should only transform one level of keys" do
10
+ { :test_1 => { :test2 => 'test'} }.environmentize_keys!.should ==
11
+ { 'TEST_1' => { :test2 => 'test'} }
12
+ end
13
+ end
14
+
15
+ describe Hash, "only" do
16
+ before do
17
+ @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE' }
18
+ end
19
+
20
+ it "should return a hash with only the given key(s)" do
21
+ @hash.only(:one).should == { :one => 'ONE' }
22
+ @hash.only(:one, 3).should == { :one => 'ONE', 3 => 'THREE' }
23
+ end
24
+ end
25
+
26
+ describe Hash, "except" do
27
+ before do
28
+ @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE' }
29
+ end
30
+
31
+ it "should return a hash without only the given key(s)" do
32
+ @hash.except(:one).should == { 'two' => 'TWO', 3 => 'THREE' }
33
+ @hash.except(:one, 3).should == { 'two' => 'TWO' }
34
+ end
35
+ end
36
+
37
+ describe Hash, "symbolize_keys!" do
38
+ before do
39
+ @hash = { 'one' => 1, 'two' => 2 }
40
+ @hash_with_another_hash = { 'a' => 'A', 'prefs' => { 'private' => true, 'sex' => 'yes please' } }
41
+ @hash_with_non_string_keys = { 'one' => 1, 2 => 'TWO', '' => 'blank' }
42
+ end
43
+
44
+ it "should convert all keys to symbols" do
45
+ @hash.symbolize_keys!
46
+ @hash.should == { :one => 1, :two => 2 }
47
+ end
48
+
49
+ it "should recursively convert all keys to symbols" do
50
+ @hash_with_another_hash.symbolize_keys!
51
+ @hash_with_another_hash.should == { :a => 'A', :prefs => { :private => true, :sex => 'yes please' } }
52
+ end
53
+ end
54
+
55
+ describe Hash, "to_xml_attributes" do
56
+ before do
57
+ @hash = { :one => "ONE", "two" => "TWO" }
58
+ end
59
+
60
+ it "should turn the hash into xml attributes" do
61
+ attrs = @hash.to_xml_attributes
62
+ attrs.should match(/one="ONE"/m)
63
+ attrs.should match(/two="TWO"/m)
64
+ end
65
+ end
66
+
67
+ describe Hash, "from_xml" do
68
+ it "should transform a simple tag with content" do
69
+ xml = "<tag>This is the contents</tag>"
70
+ Hash.from_xml(xml).should == { 'tag' => 'This is the contents' }
71
+ end
72
+
73
+ it "should work with cdata tags" do
74
+ xml = <<-END
75
+ <tag>
76
+ <![CDATA[
77
+ text inside cdata
78
+ ]]>
79
+ </tag>
80
+ END
81
+ Hash.from_xml(xml)["tag"].strip.should == "text inside cdata"
82
+ end
83
+
84
+ it "should transform a simple tag with attributes" do
85
+ xml = "<tag attr1='1' attr2='2'></tag>"
86
+ hash = { 'tag' => { 'attr1' => '1', 'attr2' => '2' } }
87
+ Hash.from_xml(xml).should == hash
88
+ end
89
+
90
+ it "should transform repeating siblings into an array" do
91
+ xml =<<-XML
92
+ <opt>
93
+ <user login="grep" fullname="Gary R Epstein" />
94
+ <user login="stty" fullname="Simon T Tyson" />
95
+ </opt>
96
+ XML
97
+
98
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Array)
99
+
100
+ hash = {
101
+ 'opt' => {
102
+ 'user' => [{
103
+ 'login' => 'grep',
104
+ 'fullname' => 'Gary R Epstein'
105
+ },{
106
+ 'login' => 'stty',
107
+ 'fullname' => 'Simon T Tyson'
108
+ }]
109
+ }
110
+ }
111
+
112
+ Hash.from_xml(xml).should == hash
113
+ end
114
+
115
+ it "should not transform non-repeating siblings into an array" do
116
+ xml =<<-XML
117
+ <opt>
118
+ <user login="grep" fullname="Gary R Epstein" />
119
+ </opt>
120
+ XML
121
+
122
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Hash)
123
+
124
+ hash = {
125
+ 'opt' => {
126
+ 'user' => {
127
+ 'login' => 'grep',
128
+ 'fullname' => 'Gary R Epstein'
129
+ }
130
+ }
131
+ }
132
+
133
+ Hash.from_xml(xml).should == hash
134
+ end
135
+
136
+ it "should typecast an integer" do
137
+ xml = "<tag type='integer'>10</tag>"
138
+ Hash.from_xml(xml)['tag'].should == 10
139
+ end
140
+
141
+ it "should typecast a true boolean" do
142
+ xml = "<tag type='boolean'>true</tag>"
143
+ Hash.from_xml(xml)['tag'].should be_true
144
+ end
145
+
146
+ it "should typecast a false boolean" do
147
+ ["false", "1", "0", "some word" ].each do |w|
148
+ Hash.from_xml("<tag type='boolean'>#{w}</tag>")['tag'].should be_false
149
+ end
150
+ end
151
+
152
+ it "should typecast a datetime" do
153
+ xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
154
+ Hash.from_xml(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
155
+ end
156
+
157
+ it "should typecast a date" do
158
+ xml = "<tag type='date'>2007-12-31</tag>"
159
+ Hash.from_xml(xml)['tag'].should == Date.parse('2007-12-31')
160
+ end
161
+
162
+ it "should unescape html entities" do
163
+ values = {
164
+ "<" => "&lt;",
165
+ ">" => "&gt;",
166
+ '"' => "&quot;",
167
+ "'" => "&apos;",
168
+ "&" => "&amp;"
169
+ }
170
+ values.each do |k,v|
171
+ xml = "<tag>Some content #{v}</tag>"
172
+ Hash.from_xml(xml)['tag'].should match(Regexp.new(k))
173
+ end
174
+ end
175
+
176
+ it "should undasherize keys as tags" do
177
+ xml = "<tag-1>Stuff</tag-1>"
178
+ Hash.from_xml(xml).keys.should include( 'tag_1' )
179
+ end
180
+
181
+ it "should undasherize keys as attributes" do
182
+ xml = "<tag1 attr-1='1'></tag1>"
183
+ Hash.from_xml(xml)['tag1'].keys.should include( 'attr_1')
184
+ end
185
+
186
+ it "should undasherize keys as tags and attributes" do
187
+ xml = "<tag-1 attr-1='1'></tag-1>"
188
+ Hash.from_xml(xml).keys.should include( 'tag_1' )
189
+ Hash.from_xml(xml)['tag_1'].keys.should include( 'attr_1')
190
+ end
191
+
192
+ it "should render nested content correctly" do
193
+ xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
194
+ Hash.from_xml(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
195
+ end
196
+
197
+ it "should render nested content with split text nodes correctly" do
198
+ xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
199
+ Hash.from_xml(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
200
+ end
201
+
202
+ it "should ignore attributes when a child is a text node" do
203
+ xml = "<root attr1='1'>Stuff</root>"
204
+ Hash.from_xml(xml).should == { "root" => "Stuff" }
205
+ end
206
+
207
+ it "should ignore attributes when any child is a text node" do
208
+ xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
209
+ Hash.from_xml(xml).should == { "root" => "Stuff <em>in italics</em>" }
210
+ end
211
+
212
+ it "should correctly transform multiple children" do
213
+ xml = <<-XML
214
+ <user gender='m'>
215
+ <age type='integer'>35</age>
216
+ <name>Home Simpson</name>
217
+ <dob type='date'>1988-01-01</dob>
218
+ <joined-at type='datetime'>2000-04-28 23:01</joined-at>
219
+ <is-cool type='boolean'>true</is-cool>
220
+ </user>
221
+ XML
222
+
223
+ hash = {
224
+ "user" => {
225
+ "gender" => "m",
226
+ "age" => 35,
227
+ "name" => "Home Simpson",
228
+ "dob" => Date.parse('1988-01-01'),
229
+ "joined_at" => Time.parse("2000-04-28 23:01"),
230
+ "is_cool" => true
231
+ }
232
+ }
233
+
234
+ Hash.from_xml(xml).should == hash
235
+ end
236
+ end
237
+
238
+ describe Hash, 'to_params' do
239
+ before do
240
+ @hash = { :name => 'Bob', :address => { :street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222'] } }
241
+ end
242
+
243
+ it 'should convert correctly into query parameters' do
244
+ @hash.to_params.split('&').sort.should ==
245
+ 'name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave.'.split('&').sort
246
+ end
247
+
248
+ it 'should not leave a trailing &' do
249
+ @hash.to_params.should_not match(/&$/)
250
+ end
251
+ end