forme 1.8.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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