forme 1.9.0 → 2.0.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +70 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +396 -202
  5. data/lib/forme/bs3.rb +19 -5
  6. data/lib/forme/erb.rb +18 -16
  7. data/lib/forme/form.rb +151 -118
  8. data/lib/forme/input.rb +1 -1
  9. data/lib/forme/rails.rb +41 -72
  10. data/lib/forme/raw.rb +2 -2
  11. data/lib/forme/sinatra.rb +6 -2
  12. data/lib/forme/tag.rb +3 -12
  13. data/lib/forme/template.rb +118 -0
  14. data/lib/forme/transformers/error_handler.rb +46 -1
  15. data/lib/forme/transformers/formatter.rb +36 -35
  16. data/lib/forme/transformers/helper.rb +0 -1
  17. data/lib/forme/transformers/inputs_wrapper.rb +6 -6
  18. data/lib/forme/transformers/labeler.rb +19 -0
  19. data/lib/forme/transformers/wrapper.rb +1 -1
  20. data/lib/forme/version.rb +2 -2
  21. data/lib/forme.rb +15 -2
  22. data/lib/roda/plugins/forme.rb +1 -1
  23. data/lib/roda/plugins/forme_erubi_capture.rb +62 -0
  24. data/lib/roda/plugins/forme_route_csrf.rb +16 -20
  25. data/lib/roda/plugins/forme_set.rb +177 -0
  26. data/lib/sequel/plugins/forme.rb +42 -55
  27. data/lib/sequel/plugins/forme_i18n.rb +3 -1
  28. data/lib/sequel/plugins/forme_set.rb +50 -28
  29. data/spec/all.rb +1 -1
  30. data/spec/bs3_reference_spec.rb +18 -18
  31. data/spec/bs3_sequel_plugin_spec.rb +7 -7
  32. data/spec/bs3_spec.rb +23 -11
  33. data/spec/erb_helper.rb +73 -58
  34. data/spec/erubi_capture_helper.rb +202 -0
  35. data/spec/forme_spec.rb +80 -29
  36. data/spec/rails_integration_spec.rb +47 -24
  37. data/spec/roda_integration_spec.rb +459 -48
  38. data/spec/sequel_helper.rb +0 -1
  39. data/spec/sequel_i18n_helper.rb +1 -1
  40. data/spec/sequel_i18n_plugin_spec.rb +3 -2
  41. data/spec/sequel_plugin_spec.rb +25 -8
  42. data/spec/sequel_set_plugin_spec.rb +10 -3
  43. data/spec/shared_erb_specs.rb +75 -0
  44. data/spec/sinatra_integration_spec.rb +5 -6
  45. data/spec/spec_helper.rb +23 -5
  46. metadata +30 -8
  47. data/lib/forme/erb_form.rb +0 -74
@@ -1,8 +1,7 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_helper.rb')
3
- require File.join(File.dirname(File.expand_path(__FILE__)), 'erb_helper.rb')
1
+ require_relative 'spec_helper'
2
+ require_relative 'sequel_helper'
3
+ require_relative 'erb_helper'
4
4
 
5
- require 'rubygems'
6
5
  begin
7
6
  require 'roda'
8
7
  require 'tilt'
@@ -17,26 +16,42 @@ rescue LoadError
17
16
  rescue LoadError
18
17
  require 'tilt/erb'
19
18
  end
20
- end
21
-
22
- class FormeRodaTest < Roda
23
- use Rack::Session::Cookie, :secret => "__a_very_long_string__"
24
-
25
- def erb(s, opts={})
26
- render(opts.merge(:inline=>s))
19
+ else
20
+ begin
21
+ require 'erubi/capture_end'
22
+ require_relative 'erubi_capture_helper'
23
+ rescue LoadError
27
24
  end
25
+ end
28
26
 
29
- route do |r|
30
- r.get 'use_request_specific_token', :use do |use|
31
- render :inline=>"[#{Base64.strict_encode64(send(:csrf_secret))}]<%= form({:method=>:post}, {:use_request_specific_token=>#{use == '1'}}) %>"
27
+ def FormeRodaTest(block=ERB_BLOCK)
28
+ Class.new(Roda) do
29
+ opts[:check_dynamic_arity] = opts[:check_arity] = :warn
30
+
31
+ if defined?(Roda::RodaVersionNumber) && Roda::RodaVersionNumber >= 30100
32
+ require 'roda/session_middleware'
33
+ opts[:sessions_convert_symbols] = true
34
+ use RodaSessionMiddleware, :secret=>SecureRandom.random_bytes(64), :key=>'rack.session'
35
+ else
36
+ use Rack::Session::Cookie, :secret => "__a_very_long_string__"
32
37
  end
33
- r.get 'hidden_tags' do |use|
34
- render :inline=>"<%= form({:method=>:post}, {:hidden_tags=>[{:foo=>'bar'}]}) %>"
38
+
39
+ def erb(s, opts={})
40
+ render(opts.merge(:inline=>s))
35
41
  end
36
- r.get 'csrf', :use do |use|
37
- render :inline=>"<%= form({:method=>:post}, {:csrf=>#{use == '1'}}) %>"
42
+
43
+ route do |r|
44
+ r.get 'use_request_specific_token', :use do |use|
45
+ render :inline=>"[#{Base64.strict_encode64(send(:csrf_secret))}]<%= form({:method=>:post}, {:use_request_specific_token=>#{use == '1'}}) %>"
46
+ end
47
+ r.get 'hidden_tags2' do |use|
48
+ render :inline=>"<%= form({:method=>:post}, {:hidden_tags=>[{:foo=>'bar'}]}) %>"
49
+ end
50
+ r.get 'csrf', :use do |use|
51
+ render :inline=>"<%= form({:method=>:post}, {:csrf=>#{use == '1'}}) %>"
52
+ end
53
+ instance_exec(r, &block)
38
54
  end
39
- instance_exec(r, &ERB_BLOCK)
40
55
  end
41
56
  end
42
57
 
@@ -46,7 +61,7 @@ rescue LoadError
46
61
  warn "unable to load rack/csrf, skipping roda csrf plugin spec"
47
62
  else
48
63
  describe "Forme Roda ERB integration with roda forme and csrf plugins" do
49
- app = FormeRodaCSRFTest = Class.new(FormeRodaTest)
64
+ app = FormeRodaCSRFTest = FormeRodaTest()
50
65
  app.plugin :csrf
51
66
  app.plugin :forme
52
67
 
@@ -60,58 +75,454 @@ describe "Forme Roda ERB integration with roda forme and csrf plugins" do
60
75
  end
61
76
  end
62
77
 
78
+ module FormeRouteCsrfSpecs
79
+ extend Minitest::Spec::DSL
80
+ include FormeErbSpecs
81
+
82
+ it "should have a valid CSRF tag" do
83
+ output = sin_get('/use_request_specific_token/1')
84
+ output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
85
+ secret = $1
86
+ token = $2
87
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
88
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
89
+ end
90
+
91
+ it "should handle the :use_request_specific_token => true option" do
92
+ output = sin_get('/use_request_specific_token/1')
93
+ output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
94
+ secret = $1
95
+ token = $2
96
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
97
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
98
+ end
99
+
100
+ it "should handle the :use_request_specific_token => false option" do
101
+ output = sin_get('/use_request_specific_token/0')
102
+ output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
103
+ secret = $1
104
+ token = $2
105
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/0', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal(plugin_opts.empty? ? false : true)
106
+ end
107
+
108
+ silence_warnings "should include :hidden_tags option" do
109
+ sin_get('/hidden_tags2').must_include 'name="foo" type="hidden" value="bar"'
110
+ end
111
+
112
+ it "should handle the :csrf option" do
113
+ sin_get('/csrf/1').must_include '<input name="_csrf" type="hidden" value="'
114
+ sin_get('/csrf/0').wont_include '<input name="_csrf" type="hidden" value="'
115
+ end
116
+ end
117
+
63
118
  begin
64
119
  require 'roda/plugins/route_csrf'
120
+ require 'roda/plugins/capture_erb'
121
+ require 'roda/plugins/inject_erb'
65
122
  rescue LoadError
66
- warn "unable to load roda/plugins/route_csrf, skipping forme_route_csrf plugin spec"
123
+ warn "unable to load necessary Roda plugins, skipping forme_erubi_capture plugin spec"
67
124
  else
125
+ describe "Forme Roda Erubi::CaptureEnd integration with roda forme_route_csrf" do
126
+ app = FormeRodaTest(ERUBI_CAPTURE_BLOCK)
127
+ app.plugin :forme_erubi_capture
128
+ app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureEndEngine}}
129
+
130
+ define_method(:app){app}
131
+ define_method(:plugin_opts){{}}
132
+ define_method(:sin_get) do |path|
133
+ s = String.new
134
+ app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
135
+ s.gsub(/\s+/, ' ').strip
136
+ end
137
+
138
+ include FormeRouteCsrfSpecs
139
+ end if defined?(ERUBI_CAPTURE_BLOCK)
140
+
68
141
  [{}, {:require_request_specific_tokens=>false}].each do |plugin_opts|
69
142
  describe "Forme Roda ERB integration with roda forme_route_csrf and route_csrf plugin with #{plugin_opts}" do
70
- app = Class.new(FormeRodaTest)
143
+ app = FormeRodaTest()
71
144
  app.plugin :forme_route_csrf
72
145
  app.plugin :route_csrf, plugin_opts
73
146
 
147
+ define_method(:app){app}
148
+ define_method(:plugin_opts){plugin_opts}
74
149
  define_method(:sin_get) do |path|
75
150
  s = String.new
76
151
  app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
77
152
  s.gsub(/\s+/, ' ').strip
78
153
  end
79
154
 
80
- include FormeErbSpecs
155
+ include FormeRouteCsrfSpecs
156
+ end
157
+
158
+ describe "Forme Roda ERB Sequel integration with roda forme_set plugin and route_csrf plugin with #{plugin_opts}" do
159
+ before do
160
+ @app = FormeRodaTest()
161
+ @app.plugin :route_csrf, plugin_opts
162
+ @app.plugin(:forme_set, :secret=>'1'*64)
163
+
164
+ @ab = Album.new
165
+ end
166
+
167
+ def forme_parse(*args, &block)
168
+ _forme_set(:forme_parse, *args, &block)
169
+ end
170
+
171
+ def forme_set(*args, &block)
172
+ _forme_set(:forme_set, *args, &block)
173
+ end
174
+
175
+ def forme_call(params)
176
+ @app.call('REQUEST_METHOD'=>'POST', 'rack.input'=>StringIO.new, :params=>params)
177
+ end
178
+
179
+ def _forme_set(meth, obj, orig_hash, *form_args, &block)
180
+ hash = {}
181
+ forme_set_block = orig_hash.delete(:forme_set_block)
182
+ orig_hash.each{|k,v| hash[k.to_s] = v}
183
+ album = @ab
184
+ ret, _, data, hmac = nil
185
+
186
+ @app.route do |r|
187
+ r.get do
188
+ if @block = env[:block]
189
+ render(:inline=>'<% form(*env[:args]) do |f| %><%= @block.call(f) %><% end %>')
190
+ else
191
+ form(*env[:args])
192
+ end
193
+ end
194
+ r.post do
195
+ r.params.replace(env[:params])
196
+ ret = send(meth, album, &forme_set_block)
197
+ nil
198
+ end
199
+ end
200
+ body = @app.call('REQUEST_METHOD'=>'GET', :args=>[album, *form_args], :block=>block)[2].join
201
+ body =~ %r|<input name="_csrf" type="hidden" value="([^"]+)"/>.*<input name="_forme_set_data" type="hidden" value="([^"]+)"/><input name="_forme_set_data_hmac" type="hidden" value="([^"]+)"/>|n
202
+ csrf = $1
203
+ data = $2
204
+ hmac = $3
205
+ data.gsub!("&quot;", '"') if data
206
+ h = {"album"=>hash, "_forme_set_data"=>data, "_forme_set_data_hmac"=>hmac, "_csrf"=>csrf}
207
+ if data && hmac
208
+ forme_call(h)
209
+ end
210
+ meth == :forme_parse ? ret : h
211
+ end
212
+
213
+ it "should have subform work correctly" do
214
+ @app.route do |r|
215
+ @album = Album.load(:name=>'N', :copies_sold=>2, :id=>1)
216
+ @album.associations[:artist] = Artist.load(:name=>'A', :id=>2)
217
+ erb <<END
218
+ 0
219
+ <% form(@album, {:action=>'/baz'}, :button=>'Sub') do |f| %>
220
+ 1
221
+ <%= f.subform(:artist, :inputs=>[:name], :legend=>'Foo', :grid=>true, :labels=>%w'Name') %>
222
+ 2
223
+ <% end %>
224
+ 3
225
+ END
226
+ end
227
+
228
+ body = @app.call('REQUEST_METHOD'=>'GET')[2].join.gsub("\n", ' ').gsub(/ +/, ' ').chomp(' ')
229
+ body.sub(%r{<input name="_csrf" type="hidden" value="([^"]+)"/>}, '<input name="_csrf" type="hidden" value="csrf"/>').must_equal '0 <form action="/baz" class="forme album" method="post"><input name="_csrf" type="hidden" value="csrf"/> 1 <table><caption>Foo</caption><thead><tr><th>Name</th></tr></thead><tbody><input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="2"/><tr><td class="string"><input id="album_artist_attributes_name" maxlength="255" name="album[artist_attributes][name]" type="text" value="A"/></td></tr></tbody></table> 2 <input type="submit" value="Sub"/></form>3'
230
+ end
231
+
232
+ it "#forme_set should include HMAC values if form includes inputs for obj" do
233
+ h = forme_set(@ab, :name=>'Foo')
234
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
235
+ @ab.name.must_be_nil
236
+ @ab.copies_sold.must_be_nil
237
+
238
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
239
+ hmac = h.delete("_forme_set_data_hmac")
240
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
241
+ proc{forme_call(h.merge("_forme_set_data_hmac"=>hmac+'1'))}.must_raise Roda::RodaPlugins::FormeSet::Error
242
+ data = h["_forme_set_data"]
243
+ data.sub!(/"csrf":\["_csrf","./, "\"csrf\":[\"_csrf\",\"|")
244
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, '1'*64, data)
245
+ proc{forme_call(h.merge("_forme_set_data_hmac"=>hmac))}.must_raise Roda::RodaPlugins::FormeSet::Error
246
+ @ab.name.must_equal 'Foo'
247
+ @ab.copies_sold.must_be_nil
81
248
 
82
- it "should handle the :hidden_tags option" do
83
- output = sin_get('/use_request_specific_token/1')
84
- output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
85
- secret = $1
86
- token = $2
87
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
88
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
249
+ forme_set(@ab, :copies_sold=>100){|f| f.input(:name)}
250
+ @ab.name.must_be_nil
251
+ @ab.copies_sold.must_be_nil
89
252
  end
90
253
 
91
- it "should handle the :use_request_specific_token => true option" do
92
- output = sin_get('/use_request_specific_token/1')
93
- output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
94
- secret = $1
95
- token = $2
96
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
97
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
254
+ it "#forme_set should handle custom form namespaces" do
255
+ forme_set(@ab, {"album"=>{"name"=>'Foo', 'copies_sold'=>'100'}}, {}, :namespace=>'album'){|f| f.input(:name); f.input(:copies_sold)}
256
+ @ab.name.must_equal 'Foo'
257
+ @ab.copies_sold.must_equal 100
258
+
259
+ proc{forme_set(@ab, {"a"=>{"name"=>'Foo'}}, {}, :namespace=>'album'){|f| f.input(:name); f.input(:copies_sold)}}.must_raise Roda::RodaPlugins::FormeSet::Error
98
260
  end
99
261
 
100
- it "should handle the :use_request_specific_token => false option" do
101
- output = sin_get('/use_request_specific_token/0')
102
- output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
103
- secret = $1
104
- token = $2
105
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/0', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal(plugin_opts.empty? ? false : true)
262
+ it "#forme_set should call plugin block if there is an error with the form submission hmac not matching data" do
263
+ @app.plugin :forme_set do |error_type, _|
264
+ request.on{error_type.to_s}
265
+ end
266
+
267
+ h = forme_set(@ab, :name=>'Foo')
268
+ forme_call(h)[2].must_equal ['missing_data']
269
+
270
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
271
+ hmac = h.delete("_forme_set_data_hmac")
272
+ forme_call(h)[2].must_equal ['missing_hmac']
273
+
274
+ forme_call(h.merge("_forme_set_data_hmac"=>hmac+'1'))[2].must_equal ['hmac_mismatch']
275
+
276
+ data = h["_forme_set_data"]
277
+ data.sub!(/"csrf":\["_csrf","./, "\"csrf\":[\"_csrf\",\"|")
278
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, '1'*64, data)
279
+ forme_call(h.merge("_forme_set_data_hmac"=>hmac))[2].must_equal ['csrf_mismatch']
280
+
281
+ h = forme_set(@ab, :name=>'Foo')
282
+ h.delete('album')
283
+ forme_call(h)[2].must_equal ['missing_namespace']
106
284
  end
107
285
 
108
- it "should handle the :hidden_tags option" do
109
- sin_get('/hidden_tags').must_include 'name="foo" type="hidden" value="bar"'
286
+ it "#forme_set should raise if plugin block does not raise or throw" do
287
+ @app.plugin :forme_set do |_, obj|
288
+ obj
289
+ end
290
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
291
+ h.delete("_forme_set_data_hmac")
292
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
110
293
  end
111
294
 
112
- it "should handle the :csrf option" do
113
- sin_get('/csrf/1').must_include '<input name="_csrf" type="hidden" value="'
114
- sin_get('/csrf/0').wont_include '<input name="_csrf" type="hidden" value="'
295
+ it "#forme_set should only set values in the form" do
296
+ forme_set(@ab, :name=>'Foo')
297
+ @ab.name.must_be_nil
298
+
299
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
300
+ @ab.name.must_equal 'Foo'
301
+
302
+ forme_set(@ab, 'copies_sold'=>'1'){|f| f.input(:name)}
303
+ @ab.name.must_be_nil
304
+ @ab.copies_sold.must_be_nil
305
+
306
+ forme_set(@ab, 'name'=>'Bar', 'copies_sold'=>'1'){|f| f.input(:name); f.input(:copies_sold)}
307
+ @ab.name.must_equal 'Bar'
308
+ @ab.copies_sold.must_equal 1
309
+ end
310
+
311
+ it "#forme_set should handle form_versions" do
312
+ h = forme_set(@ab, {:name=>'Foo'}){|f| f.input(:name)}
313
+ @ab.name.must_equal 'Foo'
314
+
315
+ obj = nil
316
+ version = nil
317
+ name = nil
318
+ forme_set_block = proc do |v, o|
319
+ obj = o
320
+ name = o.name
321
+ version = v
322
+ end
323
+ h2 = forme_set(@ab, {:name=>'Foo', :forme_set_block=>forme_set_block}, {}, :form_version=>1){|f| f.input(:name)}
324
+ obj.must_be_same_as @ab
325
+ name.must_equal 'Foo'
326
+ version.must_equal 1
327
+
328
+ forme_call(h)
329
+ obj.must_be_same_as @ab
330
+ version.must_be_nil
331
+
332
+ forme_set(@ab, {:name=>'Bar', :forme_set_block=>forme_set_block}, {}, :form_version=>2){|f| f.input(:name)}
333
+ obj.must_be_same_as @ab
334
+ name.must_equal 'Bar'
335
+ version.must_equal 2
336
+
337
+ h['album']['name'] = 'Baz'
338
+ forme_call(h)
339
+ obj.must_be_same_as @ab
340
+ name.must_equal 'Baz'
341
+ version.must_be_nil
342
+
343
+ forme_call(h2)
344
+ obj.must_be_same_as @ab
345
+ version.must_equal 1
346
+ end
347
+
348
+ it "#forme_set should work for forms without blocks" do
349
+ forme_set(@ab, {:name=>'Foo'}, {}, :inputs=>[:name])
350
+ @ab.name.must_equal 'Foo'
351
+ end
352
+
353
+ it "#forme_set should handle different ways to specify parameter names" do
354
+ [{:attr=>{:name=>'foo'}}, {:attr=>{'name'=>:foo}}, {:name=>'foo'}, {:name=>'bar[foo]'}, {:key=>:foo}].each do |opts|
355
+ forme_set(@ab, name=>'Foo'){|f| f.input(:name, opts)}
356
+ @ab.name.must_be_nil
357
+
358
+ forme_set(@ab, 'foo'=>'Foo'){|f| f.input(:name, opts)}
359
+ @ab.name.must_equal 'Foo'
360
+ end
361
+ end
362
+
363
+ it "#forme_set should ignore values where key is explicitly set to nil" do
364
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :key=>nil)}
365
+ @ab.forme_set(:name=>'Foo')
366
+ @ab.name.must_be_nil
367
+ @ab.forme_set(nil=>'Foo')
368
+ @ab.name.must_be_nil
369
+ end
370
+
371
+ it "#forme_set should skip inputs with disabled/readonly formatter set on input" do
372
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
373
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :formatter=>formatter)}
374
+ @ab.name.must_be_nil
375
+ end
376
+
377
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :formatter=>:default)}
378
+ @ab.name.must_equal 'Foo'
379
+ end
380
+
381
+ it "#forme_set should skip inputs with disabled/readonly formatter set on Form" do
382
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
383
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:disabled){|f| f.input(:name)}
384
+ @ab.name.must_be_nil
385
+ end
386
+
387
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:default){|f| f.input(:name)}
388
+ @ab.name.must_equal 'Foo'
389
+ end
390
+
391
+ it "#forme_set should skip inputs with disabled/readonly formatter set using with_opts" do
392
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
393
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>formatter){f.input(:name)}}
394
+ @ab.name.must_be_nil
395
+ end
396
+
397
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:default){f.input(:name)}}
398
+ @ab.name.must_equal 'Foo'
399
+ end
400
+
401
+ it "#forme_set should prefer input formatter to with_opts formatter" do
402
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:default){f.input(:name, :formatter=>:readonly)}}
403
+ @ab.name.must_be_nil
404
+
405
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:readonly){f.input(:name, :formatter=>:default)}}
406
+ @ab.name.must_equal 'Foo'
407
+ end
408
+
409
+ it "#forme_set should prefer with_opts formatter to form formatter" do
410
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:default){|f| f.with_opts(:formatter=>:readonly){f.input(:name)}}
411
+ @ab.name.must_be_nil
412
+
413
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:readonly){|f| f.with_opts(:formatter=>:default){f.input(:name)}}
414
+ @ab.name.must_equal 'Foo'
415
+ end
416
+
417
+ it "#forme_set should handle setting values for associated objects" do
418
+ forme_set(@ab, :artist_id=>'1')
419
+ @ab.artist_id.must_be_nil
420
+
421
+ forme_set(@ab, :artist_id=>'1'){|f| f.input(:artist)}
422
+ @ab.artist_id.must_equal 1
423
+
424
+ forme_set(@ab, 'tag_pks'=>%w'1 2'){|f| f.input(:artist)}
425
+ @ab.artist_id.must_be_nil
426
+ @ab.tag_pks.must_equal []
427
+
428
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'1 2'){|f| f.input(:artist); f.input(:tags)}
429
+ @ab.artist_id.must_equal 1
430
+ @ab.tag_pks.must_equal [1, 2]
431
+ end
432
+
433
+ it "#forme_set should handle validations for filtered associations" do
434
+ [
435
+ [{:dataset=>proc{|ds| ds.exclude(:id=>1)}},
436
+ {:dataset=>proc{|ds| ds.exclude(:id=>1)}}],
437
+ [{:options=>Artist.exclude(:id=>1).select_order_map([:name, :id])},
438
+ {:options=>Tag.exclude(:id=>1).select_order_map(:id), :name=>'tag_pks[]'}],
439
+ [{:options=>Artist.exclude(:id=>1).all, :text_method=>:name, :value_method=>:id},
440
+ {:options=>Tag.exclude(:id=>1).all, :text_method=>:name, :value_method=>:id}],
441
+ ].each do |artist_opts, tag_opts|
442
+ @ab.forme_validations.clear
443
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'1 2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
444
+ @ab.artist_id.must_equal 1
445
+ @ab.tag_pks.must_equal [1, 2]
446
+ @ab.valid?.must_equal false
447
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
448
+ @ab.errors[:tag_pks].must_equal ['invalid value submitted']
449
+
450
+ @ab.forme_validations.clear
451
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
452
+ @ab.forme_set('artist_id'=>'1', 'tag_pks'=>['2'])
453
+ @ab.artist_id.must_equal 1
454
+ @ab.tag_pks.must_equal [2]
455
+ @ab.valid?.must_equal false
456
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
457
+ @ab.errors[:tag_pks].must_be_nil
458
+
459
+ @ab.forme_validations.clear
460
+ forme_set(@ab, 'artist_id'=>'2', 'tag_pks'=>%w'2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
461
+ @ab.valid?.must_equal true
462
+ end
463
+ end
464
+
465
+ it "#forme_set should not require associated values for many_to_one association with select boxes" do
466
+ forme_set(@ab, {}){|f| f.input(:artist)}
467
+ @ab.valid?.must_equal true
468
+
469
+ forme_set(@ab, {'artist_id'=>nil}){|f| f.input(:artist)}
470
+ @ab.valid?.must_equal true
471
+
472
+ forme_set(@ab, {'artist_id'=>''}){|f| f.input(:artist)}
473
+ @ab.valid?.must_equal true
474
+ end
475
+
476
+ it "#forme_set should not require associated values for many_to_one association with radio buttons" do
477
+ forme_set(@ab, {}){|f| f.input(:artist, :as=>:radio)}
478
+ @ab.valid?.must_equal true
479
+ end
480
+
481
+ it "#forme_set should require associated values for many_to_one association with select boxes when :required is used" do
482
+ forme_set(@ab, {}){|f| f.input(:artist, :required=>true)}
483
+ @ab.valid?.must_equal false
484
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
485
+ end
486
+
487
+ it "#forme_set should require associated values for many_to_one association with radio buttons when :required is used" do
488
+ forme_set(@ab, {}){|f| f.input(:artist, :as=>:radio, :required=>true)}
489
+ @ab.valid?.must_equal false
490
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
491
+ end
492
+
493
+ it "#forme_set should handle cases where currently associated values is nil" do
494
+ def @ab.tag_pks; nil; end
495
+ forme_set(@ab, :tag_pks=>['1']){|f| f.input(:tags)}
496
+ @ab.valid?.must_equal true
497
+ end
498
+
499
+ it "#forme_parse should return hash with values and validations" do
500
+ forme_parse(@ab, :name=>'Foo'){|f| f.input(:name)}.must_equal(:values=>{:name=>'Foo'}, :validations=>{}, :form_version=>nil)
501
+
502
+ hash = forme_parse(@ab, :name=>'Foo', 'artist_id'=>'1') do |f|
503
+ f.input(:name)
504
+ f.input(:artist, :dataset=>proc{|ds| ds.exclude(:id=>1)})
505
+ end
506
+ hash.must_equal(:values=>{:name=>'Foo', :artist_id=>'1'}, :validations=>{:artist_id=>[:valid, false]}, :form_version=>nil)
507
+
508
+ @ab.set(hash[:values])
509
+ @ab.valid?.must_equal true
510
+
511
+ @ab.forme_validations.merge!(hash[:validations])
512
+ @ab.valid?.must_equal false
513
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
514
+
515
+ @ab = Album.new
516
+ hash = forme_parse(@ab, {:name=>'Foo', 'artist_id'=>'1'}, {}, :form_version=>1) do |f|
517
+ f.input(:name)
518
+ f.input(:artist, :dataset=>proc{|ds| ds.exclude(:id=>2)})
519
+ end
520
+ hash.must_equal(:values=>{:name=>'Foo', :artist_id=>'1'}, :validations=>{:artist_id=>[:valid, true]}, :form_version=>1)
521
+ @ab.set(hash[:values])
522
+ @ab.valid?.must_equal true
523
+
524
+ @ab.forme_validations.merge!(hash[:validations])
525
+ @ab.valid?.must_equal true
115
526
  end
116
527
  end
117
528
  end
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'sequel'
3
2
 
4
3
  db_url = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' ? 'jdbc:sqlite::memory:' : 'sqlite:/'
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_helper.rb')
1
+ require_relative 'sequel_helper'
2
2
 
3
3
  gem 'i18n', '>= 0.7.0'
4
4
  require 'i18n'
@@ -1,7 +1,8 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
1
+ require_relative 'spec_helper'
2
2
 
3
3
  begin
4
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_i18n_helper.rb')
4
+ raise LoadError if defined?(JRUBY_VERSION) && /\A9\.2\./.match(JRUBY_VERSION)
5
+ require_relative 'sequel_i18n_helper'
5
6
  rescue LoadError
6
7
  warn "unable to load i18n, skipping i18n Sequel plugin spec"
7
8
  else