forme 1.8.0 → 1.12.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.
@@ -3,31 +3,48 @@ require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_helper.rb')
3
3
 
4
4
  require 'rubygems'
5
5
  begin
6
- require 'action_controller/railtie'
7
- rescue LoadError
8
- warn "unable to load rails, skipping rails spec"
9
- else
10
- begin
11
- require 'active_pack/gem_version'
12
- rescue LoadError
13
- end
14
- require 'forme/rails'
6
+ require 'action_controller/railtie'
15
7
 
16
- class FormeRails < Rails::Application
17
- routes.append do
18
- %w'index inputs_block inputs_block_wrapper nest nest_sep nest_inputs nest_seq hash legend combined noblock noblock_post safe_buffer'.each do |action|
19
- get action, :controller=>'forme', :action=>action
20
- end
8
+ begin
9
+ require 'active_pack/gem_version'
10
+ rescue LoadError
21
11
  end
22
- config.active_support.deprecation = :stderr
23
- config.middleware.delete(ActionDispatch::ShowExceptions)
24
- config.middleware.delete(Rack::Lock)
25
- config.secret_key_base = 'foo'*15
26
- config.secret_token = 'secret token'*15 if Rails.respond_to?(:version) && Rails.version < '5.2'
27
- config.eager_load = true
28
- initialize!
29
- end
12
+ require 'forme/rails'
30
13
 
14
+ if Rails.respond_to?(:version) && Rails.version.start_with?('4')
15
+ # Work around issue in backported openssl environments where
16
+ # secret is 64 bytes intead of 32 bytes
17
+ require 'active_support/message_encryptor'
18
+ ActiveSupport::MessageEncryptor.send :prepend, Module.new {
19
+ def initialize(secret, *signature_key_or_options)
20
+ secret = secret[0, 32]
21
+ super
22
+ end
23
+ }
24
+ end
25
+
26
+ class FormeRails < Rails::Application
27
+ routes.append do
28
+ %w'index inputs_block inputs_block_wrapper nest nest_sep nest_inputs nest_seq hash legend combined noblock noblock_post safe_buffer'.each do |action|
29
+ get action, :controller=>'forme', :action=>action
30
+ end
31
+ end
32
+ config.active_support.deprecation = :stderr
33
+ config.middleware.delete(ActionDispatch::HostAuthorization) if defined?(ActionDispatch::HostAuthorization)
34
+ config.middleware.delete(ActionDispatch::ShowExceptions)
35
+ config.middleware.delete(Rack::Lock)
36
+ config.secret_key_base = 'foo'*15
37
+ config.secret_token = 'secret token'*15 if Rails.respond_to?(:version) && Rails.version < '5.2'
38
+ config.eager_load = true
39
+ begin
40
+ initialize!
41
+ rescue NoMethodError
42
+ raise LoadError
43
+ end
44
+ end
45
+ rescue LoadError
46
+ warn "unable to load or setup rails, skipping rails spec"
47
+ else
31
48
  class FormeController < ActionController::Base
32
49
  helper Forme::Rails::ERB
33
50
 
@@ -219,20 +236,20 @@ describe "Forme Rails integration" do
219
236
  end
220
237
 
221
238
  it "#form should correctly handle situation where multiple templates are used with same form object" do
222
- sin_get('/nest_sep').must_equal "0 <form action=\"/baz\"> 1 <p>FBB</p> 2 n1 <div> n2 <input id=\"first\" name=\"first\" type=\"text\" value=\"foo\"/> <input id=\"last\" name=\"last\" type=\"text\" value=\"bar\"/> n3 </div> n4 <fieldset class=\"inputs\"><legend>Foo</legend><input id=\"first\" name=\"first\" type=\"text\" value=\"foo\"/><input id=\"last\" name=\"last\" type=\"text\" value=\"bar\"/></fieldset> n5 3 </form>4"
239
+ sin_get('/nest_sep').must_equal '0 <form action="/baz"> 1 <p>FBB</p> 2 n1 <div> n2 <input id="first" name="first" type="text" value="foo"/> <input id="last" name="last" type="text" value="bar"/> n3 </div> n4 <fieldset class="inputs"><legend>Foo</legend><input id="first" name="first" type="text" value="foo"/><input id="last" name="last" type="text" value="bar"/></fieldset> n5 3 </form>4'
223
240
  end
224
241
 
225
242
  it "#form should correctly handle situation where multiple templates are used with same form object" do
226
- sin_get('/nest_inputs').must_equal "0 <form action=\"/baz\"> 1 <p>FBB</p> 2 n1 <fieldset class=\"inputs\"> n2 <input id=\"first\" name=\"first\" type=\"text\" value=\"foo\"/> <input id=\"last\" name=\"last\" type=\"text\" value=\"bar\"/> n3 </fieldset> n4 <fieldset class=\"inputs\"><legend>Foo</legend><input id=\"first\" name=\"first\" type=\"text\" value=\"foo\"/><input id=\"last\" name=\"last\" type=\"text\" value=\"bar\"/></fieldset> n5 3 </form>4"
243
+ sin_get('/nest_inputs').must_equal '0 <form action="/baz"> 1 <p>FBB</p> 2 n1 <fieldset class="inputs"> n2 <input id="first" name="first" type="text" value="foo"/> <input id="last" name="last" type="text" value="bar"/> n3 </fieldset> n4 <fieldset class="inputs"><legend>Foo</legend><input id="first" name="first" type="text" value="foo"/><input id="last" name="last" type="text" value="bar"/></fieldset> n5 3 </form>4'
227
244
  end
228
245
 
229
246
  unless defined?(JRUBY_VERSION) && JRUBY_VERSION =~ /\A9\.0/ && defined?(ActionPack::VERSION::STRING) && ActionPack::VERSION::STRING >= '5.1'
230
247
  it "#form should correctly handle situation Sequel integration with subforms where multiple templates are used with same form object" do
231
- sin_get('/nest_seq').sub(%r{<input name=\"authenticity_token\" type=\"hidden\" value=\"([^\"]+)\"/>}, "<input name=\"authenticity_token\" type=\"hidden\" value=\"csrf\"/>").must_equal "0 <form action=\"/baz\" class=\"forme album\" method=\"post\"><input name=\"authenticity_token\" type=\"hidden\" value=\"csrf\"/> 1 <input id=\"album_artist_attributes_id\" name=\"album[artist_attributes][id]\" type=\"hidden\" value=\"2\"/><fieldset class=\"inputs\"><legend>Foo</legend><label>Name: <input id=\"album_artist_attributes_name\" name=\"album[artist_attributes][name]\" type=\"text\" value=\"A\"/></label></fieldset> 2 n1 <input id=\"album_artist_attributes_id\" name=\"album[artist_attributes][id]\" type=\"hidden\" value=\"2\"/><fieldset class=\"inputs\"><legend>Artist</legend> n2 <label>Name2: <input id=\"album_artist_attributes_name2\" name=\"album[artist_attributes][name2]\" type=\"text\" value=\"A2\"/></label> n3 </fieldset> n4 <input id=\"album_artist_attributes_id\" name=\"album[artist_attributes][id]\" type=\"hidden\" value=\"2\"/><fieldset class=\"inputs\"><legend>Bar</legend><label>Name3: <input id=\"album_artist_attributes_name3\" name=\"album[artist_attributes][name3]\" type=\"text\" value=\"A3\"/></label></fieldset> n5 3 </form>4"
248
+ sin_get('/nest_seq').sub(%r{<input name="authenticity_token" type="hidden" value="([^"]+)"/>}, '<input name="authenticity_token" type="hidden" value="csrf"/>').must_equal '0 <form action="/baz" class="forme album" method="post"><input name="authenticity_token" type="hidden" value="csrf"/> 1 <input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="2"/><fieldset class="inputs"><legend>Foo</legend><label>Name: <input id="album_artist_attributes_name" maxlength="255" name="album[artist_attributes][name]" type="text" value="A"/></label></fieldset> 2 n1 <input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="2"/><fieldset class="inputs"><legend>Artist</legend> n2 <label>Name2: <input id="album_artist_attributes_name2" name="album[artist_attributes][name2]" type="text" value="A2"/></label> n3 </fieldset> n4 <input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="2"/><fieldset class="inputs"><legend>Bar</legend><label>Name3: <input id="album_artist_attributes_name3" name="album[artist_attributes][name3]" type="text" value="A3"/></label></fieldset> n5 3 </form>4'
232
249
  end
233
250
 
234
251
  it "#form should work without a block with hidden tags" do
235
- sin_get('/noblock_post').sub(%r{<input name=\"authenticity_token\" type=\"hidden\" value=\"([^\"]+)\"/>}, "<input name=\"authenticity_token\" type=\"hidden\" value=\"csrf\"/>").must_equal '<form method="post"><input name="authenticity_token" type="hidden" value="csrf"/><input type="submit" value="xyz"/></form>'
252
+ sin_get('/noblock_post').sub(%r{<input name="authenticity_token" type="hidden" value="([^"]+)"/>}, '<input name="authenticity_token" type="hidden" value="csrf"/>').must_equal '<form method="post"><input name="authenticity_token" type="hidden" value="csrf"/><input type="submit" value="xyz"/></form>'
236
253
  end
237
254
  end
238
255
 
@@ -20,7 +20,15 @@ rescue LoadError
20
20
  end
21
21
 
22
22
  class FormeRodaTest < Roda
23
- use Rack::Session::Cookie, :secret => "__a_very_long_string__"
23
+ opts[:check_dynamic_arity] = opts[:check_arity] = :warn
24
+
25
+ if defined?(Roda::RodaVersionNumber) && Roda::RodaVersionNumber >= 30100
26
+ require 'roda/session_middleware'
27
+ opts[:sessions_convert_symbols] = true
28
+ use RodaSessionMiddleware, :secret=>SecureRandom.random_bytes(64), :key=>'rack.session'
29
+ else
30
+ use Rack::Session::Cookie, :secret => "__a_very_long_string__"
31
+ end
24
32
 
25
33
  def erb(s, opts={})
26
34
  render(opts.merge(:inline=>s))
@@ -114,6 +122,354 @@ else
114
122
  sin_get('/csrf/0').wont_include '<input name="_csrf" type="hidden" value="'
115
123
  end
116
124
  end
125
+
126
+ describe "Forme Roda ERB Sequel integration with roda forme_set plugin and route_csrf plugin with #{plugin_opts}" do
127
+ before do
128
+ @app = Class.new(FormeRodaTest)
129
+ @app.plugin :route_csrf, plugin_opts
130
+ @app.plugin(:forme_set, :secret=>'1'*64)
131
+
132
+ @ab = Album.new
133
+ end
134
+
135
+ def forme_parse(*args, &block)
136
+ _forme_set(:forme_parse, *args, &block)
137
+ end
138
+
139
+ def forme_set(*args, &block)
140
+ _forme_set(:forme_set, *args, &block)
141
+ end
142
+
143
+ def forme_call(params)
144
+ @app.call('REQUEST_METHOD'=>'POST', 'rack.input'=>StringIO.new, :params=>params)
145
+ end
146
+
147
+ def _forme_set(meth, obj, orig_hash, *form_args, &block)
148
+ hash = {}
149
+ forme_set_block = orig_hash.delete(:forme_set_block)
150
+ orig_hash.each{|k,v| hash[k.to_s] = v}
151
+ album = @ab
152
+ ret, _, data, hmac = nil
153
+
154
+ @app.route do |r|
155
+ r.get do
156
+ form(*env[:args], &env[:block]).to_s
157
+ end
158
+ r.post do
159
+ r.params.replace(env[:params])
160
+ ret = send(meth, album, &forme_set_block)
161
+ nil
162
+ end
163
+ end
164
+ body = @app.call('REQUEST_METHOD'=>'GET', :args=>[album, *form_args], :block=>block)[2].join
165
+ 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
166
+ csrf = $1
167
+ data = $2
168
+ hmac = $3
169
+ data.gsub!("&quot;", '"') if data
170
+ h = {"album"=>hash, "_forme_set_data"=>data, "_forme_set_data_hmac"=>hmac, "_csrf"=>csrf}
171
+ if data && hmac
172
+ forme_call(h)
173
+ end
174
+ meth == :forme_parse ? ret : h
175
+ end
176
+
177
+ it "#forme_set should include HMAC values if form includes inputs for obj" do
178
+ h = forme_set(@ab, :name=>'Foo')
179
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
180
+ @ab.name.must_be_nil
181
+ @ab.copies_sold.must_be_nil
182
+
183
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
184
+ hmac = h.delete("_forme_set_data_hmac")
185
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
186
+ proc{forme_call(h.merge("_forme_set_data_hmac"=>hmac+'1'))}.must_raise Roda::RodaPlugins::FormeSet::Error
187
+ data = h["_forme_set_data"]
188
+ data.sub!(/"csrf":\["_csrf","./, "\"csrf\":[\"_csrf\",\"|")
189
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, '1'*64, data)
190
+ proc{forme_call(h.merge("_forme_set_data_hmac"=>hmac))}.must_raise Roda::RodaPlugins::FormeSet::Error
191
+ @ab.name.must_equal 'Foo'
192
+ @ab.copies_sold.must_be_nil
193
+
194
+ forme_set(@ab, :copies_sold=>100){|f| f.input(:name)}
195
+ @ab.name.must_be_nil
196
+ @ab.copies_sold.must_be_nil
197
+ end
198
+
199
+ it "#forme_set should handle custom form namespaces" do
200
+ forme_set(@ab, {"album"=>{"name"=>'Foo', 'copies_sold'=>'100'}}, {}, :namespace=>'album'){|f| f.input(:name); f.input(:copies_sold)}
201
+ @ab.name.must_equal 'Foo'
202
+ @ab.copies_sold.must_equal 100
203
+
204
+ proc{forme_set(@ab, {"a"=>{"name"=>'Foo'}}, {}, :namespace=>'album'){|f| f.input(:name); f.input(:copies_sold)}}.must_raise Roda::RodaPlugins::FormeSet::Error
205
+ end
206
+
207
+ it "#forme_set should call plugin block if there is an error with the form submission hmac not matching data" do
208
+ @app.plugin :forme_set do |error_type, _|
209
+ request.on{error_type.to_s}
210
+ end
211
+
212
+ h = forme_set(@ab, :name=>'Foo')
213
+ forme_call(h)[2].must_equal ['missing_data']
214
+
215
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
216
+ hmac = h.delete("_forme_set_data_hmac")
217
+ forme_call(h)[2].must_equal ['missing_hmac']
218
+
219
+ forme_call(h.merge("_forme_set_data_hmac"=>hmac+'1'))[2].must_equal ['hmac_mismatch']
220
+
221
+ data = h["_forme_set_data"]
222
+ data.sub!(/"csrf":\["_csrf","./, "\"csrf\":[\"_csrf\",\"|")
223
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, '1'*64, data)
224
+ forme_call(h.merge("_forme_set_data_hmac"=>hmac))[2].must_equal ['csrf_mismatch']
225
+
226
+ h = forme_set(@ab, :name=>'Foo')
227
+ h.delete('album')
228
+ forme_call(h)[2].must_equal ['missing_namespace']
229
+ end
230
+
231
+ it "#forme_set should raise if plugin block does not raise or throw" do
232
+ @app.plugin :forme_set do |_, obj|
233
+ obj
234
+ end
235
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
236
+ h.delete("_forme_set_data_hmac")
237
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
238
+ end
239
+
240
+ it "#forme_set should only set values in the form" do
241
+ forme_set(@ab, :name=>'Foo')
242
+ @ab.name.must_be_nil
243
+
244
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
245
+ @ab.name.must_equal 'Foo'
246
+
247
+ forme_set(@ab, 'copies_sold'=>'1'){|f| f.input(:name)}
248
+ @ab.name.must_be_nil
249
+ @ab.copies_sold.must_be_nil
250
+
251
+ forme_set(@ab, 'name'=>'Bar', 'copies_sold'=>'1'){|f| f.input(:name); f.input(:copies_sold)}
252
+ @ab.name.must_equal 'Bar'
253
+ @ab.copies_sold.must_equal 1
254
+ end
255
+
256
+ it "#forme_set should handle form_versions" do
257
+ h = forme_set(@ab, {:name=>'Foo'}){|f| f.input(:name)}
258
+ @ab.name.must_equal 'Foo'
259
+
260
+ obj = nil
261
+ version = nil
262
+ name = nil
263
+ forme_set_block = proc do |v, o|
264
+ obj = o
265
+ name = o.name
266
+ version = v
267
+ end
268
+ h2 = forme_set(@ab, {:name=>'Foo', :forme_set_block=>forme_set_block}, {}, :form_version=>1){|f| f.input(:name)}
269
+ obj.must_be_same_as @ab
270
+ name.must_equal 'Foo'
271
+ version.must_equal 1
272
+
273
+ forme_call(h)
274
+ obj.must_be_same_as @ab
275
+ version.must_be_nil
276
+
277
+ forme_set(@ab, {:name=>'Bar', :forme_set_block=>forme_set_block}, {}, :form_version=>2){|f| f.input(:name)}
278
+ obj.must_be_same_as @ab
279
+ name.must_equal 'Bar'
280
+ version.must_equal 2
281
+
282
+ h['album']['name'] = 'Baz'
283
+ forme_call(h)
284
+ obj.must_be_same_as @ab
285
+ name.must_equal 'Baz'
286
+ version.must_be_nil
287
+
288
+ forme_call(h2)
289
+ obj.must_be_same_as @ab
290
+ version.must_equal 1
291
+ end
292
+
293
+ it "#forme_set should work for forms without blocks" do
294
+ forme_set(@ab, {:name=>'Foo'}, {}, :inputs=>[:name])
295
+ @ab.name.must_equal 'Foo'
296
+ end
297
+
298
+ it "#forme_set should handle different ways to specify parameter names" do
299
+ [{:attr=>{:name=>'foo'}}, {:attr=>{'name'=>:foo}}, {:name=>'foo'}, {:name=>'bar[foo]'}, {:key=>:foo}].each do |opts|
300
+ forme_set(@ab, name=>'Foo'){|f| f.input(:name, opts)}
301
+ @ab.name.must_be_nil
302
+
303
+ forme_set(@ab, 'foo'=>'Foo'){|f| f.input(:name, opts)}
304
+ @ab.name.must_equal 'Foo'
305
+ end
306
+ end
307
+
308
+ it "#forme_set should ignore values where key is explicitly set to nil" do
309
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :key=>nil)}
310
+ @ab.forme_set(:name=>'Foo')
311
+ @ab.name.must_be_nil
312
+ @ab.forme_set(nil=>'Foo')
313
+ @ab.name.must_be_nil
314
+ end
315
+
316
+ it "#forme_set should skip inputs with disabled/readonly formatter set on input" do
317
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
318
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :formatter=>formatter)}
319
+ @ab.name.must_be_nil
320
+ end
321
+
322
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :formatter=>:default)}
323
+ @ab.name.must_equal 'Foo'
324
+ end
325
+
326
+ it "#forme_set should skip inputs with disabled/readonly formatter set on Form" do
327
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
328
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:disabled){|f| f.input(:name)}
329
+ @ab.name.must_be_nil
330
+ end
331
+
332
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:default){|f| f.input(:name)}
333
+ @ab.name.must_equal 'Foo'
334
+ end
335
+
336
+ it "#forme_set should skip inputs with disabled/readonly formatter set using with_opts" do
337
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
338
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>formatter){f.input(:name)}}
339
+ @ab.name.must_be_nil
340
+ end
341
+
342
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:default){f.input(:name)}}
343
+ @ab.name.must_equal 'Foo'
344
+ end
345
+
346
+ it "#forme_set should prefer input formatter to with_opts formatter" do
347
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:default){f.input(:name, :formatter=>:readonly)}}
348
+ @ab.name.must_be_nil
349
+
350
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:readonly){f.input(:name, :formatter=>:default)}}
351
+ @ab.name.must_equal 'Foo'
352
+ end
353
+
354
+ it "#forme_set should prefer with_opts formatter to form formatter" do
355
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:default){|f| f.with_opts(:formatter=>:readonly){f.input(:name)}}
356
+ @ab.name.must_be_nil
357
+
358
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:readonly){|f| f.with_opts(:formatter=>:default){f.input(:name)}}
359
+ @ab.name.must_equal 'Foo'
360
+ end
361
+
362
+ it "#forme_set should handle setting values for associated objects" do
363
+ forme_set(@ab, :artist_id=>'1')
364
+ @ab.artist_id.must_be_nil
365
+
366
+ forme_set(@ab, :artist_id=>'1'){|f| f.input(:artist)}
367
+ @ab.artist_id.must_equal 1
368
+
369
+ forme_set(@ab, 'tag_pks'=>%w'1 2'){|f| f.input(:artist)}
370
+ @ab.artist_id.must_be_nil
371
+ @ab.tag_pks.must_equal []
372
+
373
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'1 2'){|f| f.input(:artist); f.input(:tags)}
374
+ @ab.artist_id.must_equal 1
375
+ @ab.tag_pks.must_equal [1, 2]
376
+ end
377
+
378
+ it "#forme_set should handle validations for filtered associations" do
379
+ [
380
+ [{:dataset=>proc{|ds| ds.exclude(:id=>1)}},
381
+ {:dataset=>proc{|ds| ds.exclude(:id=>1)}}],
382
+ [{:options=>Artist.exclude(:id=>1).select_order_map([:name, :id])},
383
+ {:options=>Tag.exclude(:id=>1).select_order_map(:id), :name=>'tag_pks[]'}],
384
+ [{:options=>Artist.exclude(:id=>1).all, :text_method=>:name, :value_method=>:id},
385
+ {:options=>Tag.exclude(:id=>1).all, :text_method=>:name, :value_method=>:id}],
386
+ ].each do |artist_opts, tag_opts|
387
+ @ab.forme_validations.clear
388
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'1 2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
389
+ @ab.artist_id.must_equal 1
390
+ @ab.tag_pks.must_equal [1, 2]
391
+ @ab.valid?.must_equal false
392
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
393
+ @ab.errors[:tag_pks].must_equal ['invalid value submitted']
394
+
395
+ @ab.forme_validations.clear
396
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
397
+ @ab.forme_set('artist_id'=>'1', 'tag_pks'=>['2'])
398
+ @ab.artist_id.must_equal 1
399
+ @ab.tag_pks.must_equal [2]
400
+ @ab.valid?.must_equal false
401
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
402
+ @ab.errors[:tag_pks].must_be_nil
403
+
404
+ @ab.forme_validations.clear
405
+ forme_set(@ab, 'artist_id'=>'2', 'tag_pks'=>%w'2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
406
+ @ab.valid?.must_equal true
407
+ end
408
+ end
409
+
410
+ it "#forme_set should not require associated values for many_to_one association with select boxes" do
411
+ forme_set(@ab, {}){|f| f.input(:artist)}
412
+ @ab.valid?.must_equal true
413
+
414
+ forme_set(@ab, {'artist_id'=>nil}){|f| f.input(:artist)}
415
+ @ab.valid?.must_equal true
416
+
417
+ forme_set(@ab, {'artist_id'=>''}){|f| f.input(:artist)}
418
+ @ab.valid?.must_equal true
419
+ end
420
+
421
+ it "#forme_set should not require associated values for many_to_one association with radio buttons" do
422
+ forme_set(@ab, {}){|f| f.input(:artist, :as=>:radio)}
423
+ @ab.valid?.must_equal true
424
+ end
425
+
426
+ it "#forme_set should require associated values for many_to_one association with select boxes when :required is used" do
427
+ forme_set(@ab, {}){|f| f.input(:artist, :required=>true)}
428
+ @ab.valid?.must_equal false
429
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
430
+ end
431
+
432
+ it "#forme_set should require associated values for many_to_one association with radio buttons when :required is used" do
433
+ forme_set(@ab, {}){|f| f.input(:artist, :as=>:radio, :required=>true)}
434
+ @ab.valid?.must_equal false
435
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
436
+ end
437
+
438
+ it "#forme_set should handle cases where currently associated values is nil" do
439
+ def @ab.tag_pks; nil; end
440
+ forme_set(@ab, :tag_pks=>['1']){|f| f.input(:tags)}
441
+ @ab.valid?.must_equal true
442
+ end
443
+
444
+ it "#forme_parse should return hash with values and validations" do
445
+ forme_parse(@ab, :name=>'Foo'){|f| f.input(:name)}.must_equal(:values=>{:name=>'Foo'}, :validations=>{}, :form_version=>nil)
446
+
447
+ hash = forme_parse(@ab, :name=>'Foo', 'artist_id'=>'1') do |f|
448
+ f.input(:name)
449
+ f.input(:artist, :dataset=>proc{|ds| ds.exclude(:id=>1)})
450
+ end
451
+ hash.must_equal(:values=>{:name=>'Foo', :artist_id=>'1'}, :validations=>{:artist_id=>[:valid, false]}, :form_version=>nil)
452
+
453
+ @ab.set(hash[:values])
454
+ @ab.valid?.must_equal true
455
+
456
+ @ab.forme_validations.merge!(hash[:validations])
457
+ @ab.valid?.must_equal false
458
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
459
+
460
+ @ab = Album.new
461
+ hash = forme_parse(@ab, {:name=>'Foo', 'artist_id'=>'1'}, {}, :form_version=>1) do |f|
462
+ f.input(:name)
463
+ f.input(:artist, :dataset=>proc{|ds| ds.exclude(:id=>2)})
464
+ end
465
+ hash.must_equal(:values=>{:name=>'Foo', :artist_id=>'1'}, :validations=>{:artist_id=>[:valid, true]}, :form_version=>1)
466
+ @ab.set(hash[:values])
467
+ @ab.valid?.must_equal true
468
+
469
+ @ab.forme_validations.merge!(hash[:validations])
470
+ @ab.valid?.must_equal true
471
+ end
472
+ end
117
473
  end
118
474
  end
119
475
  end