reform 2.2.4 → 2.3.1

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 (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +11 -6
  6. data/Appraisals +8 -0
  7. data/CHANGES.md +54 -4
  8. data/CONTRIBUTING.md +31 -0
  9. data/Gemfile +2 -16
  10. data/ISSUE_TEMPLATE.md +25 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +5 -7
  13. data/Rakefile +18 -9
  14. data/gemfiles/0.13.0.gemfile +8 -0
  15. data/gemfiles/1.5.0.gemfile +9 -0
  16. data/lib/reform.rb +1 -0
  17. data/lib/reform/contract.rb +7 -17
  18. data/lib/reform/contract/custom_error.rb +41 -0
  19. data/lib/reform/contract/validate.rb +53 -23
  20. data/lib/reform/errors.rb +61 -0
  21. data/lib/reform/form.rb +36 -10
  22. data/lib/reform/form/call.rb +1 -1
  23. data/lib/reform/form/composition.rb +2 -2
  24. data/lib/reform/form/dry.rb +10 -58
  25. data/lib/reform/form/dry/input_hash.rb +37 -0
  26. data/lib/reform/form/dry/new_api.rb +46 -0
  27. data/lib/reform/form/dry/old_api.rb +61 -0
  28. data/lib/reform/form/populator.rb +11 -27
  29. data/lib/reform/form/prepopulate.rb +4 -3
  30. data/lib/reform/form/validate.rb +28 -13
  31. data/lib/reform/result.rb +90 -0
  32. data/lib/reform/validation.rb +19 -11
  33. data/lib/reform/validation/groups.rb +12 -27
  34. data/lib/reform/version.rb +1 -1
  35. data/reform.gemspec +15 -13
  36. data/test/benchmarking.rb +39 -6
  37. data/test/call_new_api.rb +23 -0
  38. data/test/{call_test.rb → call_old_api.rb} +4 -4
  39. data/test/changed_test.rb +8 -8
  40. data/test/coercion_test.rb +51 -19
  41. data/test/composition_new_api.rb +186 -0
  42. data/test/{composition_test.rb → composition_old_api.rb} +66 -31
  43. data/test/contract/custom_error_test.rb +55 -0
  44. data/test/contract_new_api.rb +77 -0
  45. data/test/{contract_test.rb → contract_old_api.rb} +13 -13
  46. data/test/default_test.rb +2 -2
  47. data/test/deserialize_test.rb +11 -14
  48. data/test/errors_new_api.rb +225 -0
  49. data/test/errors_old_api.rb +230 -0
  50. data/test/feature_test.rb +8 -10
  51. data/test/fixtures/dry_error_messages.yml +73 -23
  52. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  53. data/test/form_new_api.rb +57 -0
  54. data/test/{form_test.rb → form_old_api.rb} +5 -5
  55. data/test/form_option_new_api.rb +24 -0
  56. data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
  57. data/test/from_test.rb +9 -13
  58. data/test/inherit_new_api.rb +105 -0
  59. data/test/inherit_old_api.rb +105 -0
  60. data/test/{module_test.rb → module_new_api.rb} +20 -25
  61. data/test/module_old_api.rb +146 -0
  62. data/test/parse_option_test.rb +40 -0
  63. data/test/parse_pipeline_test.rb +3 -3
  64. data/test/populate_new_api.rb +304 -0
  65. data/test/{populate_test.rb → populate_old_api.rb} +83 -49
  66. data/test/populator_skip_test.rb +9 -9
  67. data/test/prepopulator_test.rb +8 -9
  68. data/test/read_only_test.rb +12 -1
  69. data/test/readable_test.rb +7 -7
  70. data/test/reform_new_api.rb +204 -0
  71. data/test/{reform_test.rb → reform_old_api.rb} +30 -51
  72. data/test/save_new_api.rb +101 -0
  73. data/test/{save_test.rb → save_old_api.rb} +32 -20
  74. data/test/setup_test.rb +8 -8
  75. data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
  76. data/test/skip_if_old_api.rb +92 -0
  77. data/test/skip_setter_and_getter_test.rb +3 -4
  78. data/test/test_helper.rb +25 -14
  79. data/test/validate_new_api.rb +408 -0
  80. data/test/{validate_test.rb → validate_old_api.rb} +59 -69
  81. data/test/validation/dry_validation_new_api.rb +836 -0
  82. data/test/validation/dry_validation_old_api.rb +772 -0
  83. data/test/validation/result_test.rb +77 -0
  84. data/test/validation_library_provided_test.rb +16 -0
  85. data/test/virtual_test.rb +47 -7
  86. data/test/writeable_test.rb +35 -6
  87. metadata +127 -56
  88. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  89. data/lib/reform/contract/errors.rb +0 -43
  90. data/lib/reform/form/mongoid.rb +0 -37
  91. data/lib/reform/form/orm.rb +0 -26
  92. data/lib/reform/mongoid.rb +0 -4
  93. data/test/deprecation_test.rb +0 -27
  94. data/test/errors_test.rb +0 -165
  95. data/test/inherit_test.rb +0 -119
  96. data/test/readonly_test.rb +0 -14
  97. data/test/validation/dry_test.rb +0 -60
  98. data/test/validation/dry_validation_test.rb +0 -352
  99. data/test/validation/errors.yml +0 -4
@@ -1,25 +1,25 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class ContractValidateTest < MiniTest::Spec
4
4
  Song = Struct.new(:title, :album, :composer)
5
5
  Album = Struct.new(:name, :songs, :artist)
6
6
  Artist = Struct.new(:name)
7
7
 
8
- class AlbumForm < Reform::Contract
8
+ class AlbumForm < TestContract
9
9
  property :name
10
10
  validation do
11
- key(:name).required
11
+ required(:name).filled
12
12
  end
13
13
 
14
14
  collection :songs do
15
15
  property :title
16
16
  validation do
17
- key(:title).required
17
+ required(:title).filled
18
18
  end
19
19
 
20
20
  property :composer do
21
21
  validation do
22
- key(:name).required
22
+ required(:name).filled
23
23
  end
24
24
  property :name
25
25
  end
@@ -30,13 +30,13 @@ class ContractValidateTest < MiniTest::Spec
30
30
  end
31
31
  end
32
32
 
33
- let (:song) { Song.new("Broken") }
34
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
35
- let (:composer) { Artist.new("Greg Graffin") }
36
- let (:artist) { Artist.new("Bad Religion") }
37
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
33
+ let(:song) { Song.new("Broken") }
34
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
35
+ let(:composer) { Artist.new("Greg Graffin") }
36
+ let(:artist) { Artist.new("Bad Religion") }
37
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
38
38
 
39
- let (:form) { AlbumForm.new(album) }
39
+ let(:form) { AlbumForm.new(album) }
40
40
 
41
41
  # valid
42
42
  it do
@@ -50,34 +50,32 @@ class ContractValidateTest < MiniTest::Spec
50
50
  album.name = nil
51
51
 
52
52
  form.validate.must_equal false
53
- form.errors.messages.inspect.must_equal "{:name=>[\"is missing\"], :\"songs.composer.name\"=>[\"is missing\"]}"
53
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"]}"
54
54
  end
55
55
  end
56
56
 
57
-
58
57
  # no configuration results in "sync" (formerly known as parse_strategy: :sync).
59
58
  class ValidateWithoutConfigurationTest < MiniTest::Spec
60
59
  Song = Struct.new(:title, :album, :composer)
61
60
  Album = Struct.new(:name, :songs, :artist)
62
61
  Artist = Struct.new(:name)
63
62
 
64
- class AlbumForm < Reform::Form
63
+ class AlbumForm < TestForm
65
64
  property :name
66
65
  validation do
67
- key(:name).required
66
+ required(:name).filled
68
67
  end
69
68
 
70
69
  collection :songs do
71
-
72
70
  property :title
73
71
  validation do
74
- key(:title).required
72
+ required(:title).filled
75
73
  end
76
74
 
77
75
  property :composer do
78
76
  property :name
79
77
  validation do
80
- key(:name).required
78
+ required(:name).filled
81
79
  end
82
80
  end
83
81
  end
@@ -87,23 +85,25 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
87
85
  end
88
86
  end
89
87
 
90
- let (:song) { Song.new("Broken") }
91
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
92
- let (:composer) { Artist.new("Greg Graffin") }
93
- let (:artist) { Artist.new("Bad Religion") }
94
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
88
+ let(:song) { Song.new("Broken") }
89
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
90
+ let(:composer) { Artist.new("Greg Graffin") }
91
+ let(:artist) { Artist.new("Bad Religion") }
92
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
95
93
 
96
- let (:form) { AlbumForm.new(album) }
94
+ let(:form) { AlbumForm.new(album) }
97
95
 
98
96
  # valid.
99
97
  it do
100
- object_ids = {song: form.songs[0].object_id, song_with_composer: form.songs[1].object_id,
101
- artist: form.artist.object_id, composer: form.songs[1].composer.object_id}
98
+ object_ids = {
99
+ song: form.songs[0].object_id, song_with_composer: form.songs[1].object_id,
100
+ artist: form.artist.object_id, composer: form.songs[1].composer.object_id
101
+ }
102
102
 
103
103
  form.validate(
104
104
  "name" => "Best Of",
105
105
  "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => "Sting"}}],
106
- "artist" => {"name" => "The Police"},
106
+ "artist" => {"name" => "The Police"}
107
107
  ).must_equal true
108
108
 
109
109
  form.errors.messages.inspect.must_equal "{}"
@@ -121,7 +121,6 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
121
121
  form.songs[1].composer.object_id.must_equal object_ids[:composer]
122
122
  form.artist.object_id.must_equal object_ids[:artist]
123
123
 
124
-
125
124
  # model has not changed, yet.
126
125
  album.name.must_equal "The Dissent Of Man"
127
126
  album.songs[0].title.must_equal "Broken"
@@ -135,7 +134,7 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
135
134
  form.validate(
136
135
  name: "Best Of",
137
136
  songs: [{title: "The X-Creep"}, {title: "Trudging", composer: {name: "SNFU"}}],
138
- artist: {name: "The Police"},
137
+ artist: {name: "The Police"}
139
138
  ).must_equal true
140
139
 
141
140
  form.name.must_equal "Best Of"
@@ -160,45 +159,45 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
160
159
  Album = Struct.new(:name, :songs, :artist)
161
160
  Artist = Struct.new(:name)
162
161
 
163
- class AlbumForm < Reform::Form
162
+ class AlbumForm < TestForm
164
163
  property :name
165
164
  validation do
166
- key(:name).required
165
+ required(:name).filled
167
166
  end
168
167
 
169
168
  collection :songs,
170
- internal_populator: lambda { |input, options|
171
- collection = options[:represented].songs
172
- (item = collection[options[:index]]) ? item : collection.insert(options[:index], Song.new) } do
173
-
169
+ internal_populator: ->(input, options) {
170
+ collection = options[:represented].songs
171
+ (item = collection[options[:index]]) ? item : collection.insert(options[:index], Song.new)
172
+ } do
174
173
  property :title
175
174
  validation do
176
- key(:title).required
175
+ required(:title).filled
177
176
  end
178
177
 
179
- property :composer, internal_populator: lambda { |input, options| (item = options[:represented].composer) ? item : Artist.new } do
178
+ property :composer, internal_populator: ->(input, options) { (item = options[:represented].composer) ? item : Artist.new } do
180
179
  property :name
181
180
  validation do
182
- key(:name).required
181
+ required(:name).filled
183
182
  end
184
183
  end
185
184
  end
186
185
 
187
- property :artist, internal_populator: lambda { |input, options| (item = options[:represented].artist) ? item : Artist.new } do
186
+ property :artist, internal_populator: ->(input, options) { (item = options[:represented].artist) ? item : Artist.new } do
188
187
  property :name
189
188
  validation do
190
- key(:name).required
189
+ required(:name).filled
191
190
  end
192
191
  end
193
192
  end
194
193
 
195
- let (:song) { Song.new("Broken") }
196
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
197
- let (:composer) { Artist.new("Greg Graffin") }
198
- let (:artist) { Artist.new("Bad Religion") }
199
- let (:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
194
+ let(:song) { Song.new("Broken") }
195
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
196
+ let(:composer) { Artist.new("Greg Graffin") }
197
+ let(:artist) { Artist.new("Bad Religion") }
198
+ let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
200
199
 
201
- let (:form) { AlbumForm.new(album) }
200
+ let(:form) { AlbumForm.new(album) }
202
201
 
203
202
  # valid.
204
203
  it("xxx") do
@@ -207,6 +206,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
207
206
  "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => "Sting"}}],
208
207
  "artist" => {"name" => "The Police"},
209
208
  ).must_equal true
209
+ form.valid?.must_equal true
210
210
 
211
211
  form.errors.messages.inspect.must_equal "{}"
212
212
 
@@ -217,7 +217,6 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
217
217
  form.songs[1].composer.name.must_equal "Sting"
218
218
  form.artist.name.must_equal "The Police"
219
219
 
220
-
221
220
  # model has not changed, yet.
222
221
  album.name.must_equal "The Dissent Of Man"
223
222
  album.songs[0].title.must_equal "Broken"
@@ -233,6 +232,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
233
232
  "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => ""}}],
234
233
  "artist" => {"name" => ""},
235
234
  ).must_equal false
235
+ form.valid?.must_equal false
236
236
 
237
237
  form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"], :\"artist.name\"=>[\"must be filled\"]}"
238
238
  end
@@ -256,7 +256,6 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
256
256
  form.songs.size.must_equal 3
257
257
  form.artist.name.must_equal "Bad Religion"
258
258
 
259
-
260
259
  # model has not changed, yet.
261
260
  album.name.must_equal "The Dissent Of Man"
262
261
  album.songs[0].title.must_equal "Broken"
@@ -266,25 +265,24 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
266
265
  album.artist.name.must_equal "Bad Religion"
267
266
  end
268
267
 
269
-
270
268
  # allow writeable: false even in the deserializer.
271
- class SongForm < Reform::Form
269
+ class SongForm < TestForm
272
270
  property :title, deserializer: {writeable: false}
273
271
  end
274
272
 
275
273
  it do
276
274
  form = SongForm.new(song = Song.new)
277
275
  form.validate("title" => "Ignore me!")
278
- form.title.must_equal nil
276
+ assert_nil form.title
279
277
  form.title = "Unopened"
280
278
  form.sync # only the deserializer is marked as not-writeable.
281
279
  song.title.must_equal "Unopened"
282
280
  end
283
281
  end
284
282
 
285
- # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/apotonick/reform/pull/104
283
+ # # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/trailblazer/reform/pull/104
286
284
  # # describe ":populator with :empty" do
287
- # # let (:form) {
285
+ # # let(:form) {
288
286
  # # Class.new(Reform::Form) do
289
287
  # # collection :songs, :empty => true, :populator => lambda { |fragment, index, args|
290
288
  # # songs[index] = args.binding[:form].new(Song.new)
@@ -294,7 +292,7 @@ end
294
292
  # # end
295
293
  # # }
296
294
 
297
- # # let (:params) {
295
+ # # let(:params) {
298
296
  # # {
299
297
  # # "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
300
298
  # # }
@@ -308,10 +306,9 @@ end
308
306
  # # it { subject.songs[1].title.must_equal "Roxanne" }
309
307
  # # end
310
308
 
311
-
312
309
  # # test cardinalities.
313
310
  # describe "with empty collection and cardinality" do
314
- # let (:album) { Album.new }
311
+ # let(:album) { Album.new }
315
312
 
316
313
  # subject { Class.new(Reform::Form) do
317
314
  # include Reform::Form::ActiveModel
@@ -329,7 +326,6 @@ end
329
326
  # validates :hit, :presence => true
330
327
  # end.new(album) }
331
328
 
332
-
333
329
  # describe "invalid" do
334
330
  # before { subject.validate({}).must_equal false }
335
331
 
@@ -342,9 +338,8 @@ end
342
338
  # end
343
339
  # end
344
340
 
345
-
346
341
  # describe "valid" do
347
- # let (:album) { Album.new(nil, Song.new, [Song.new("Urban Myth")]) }
342
+ # let(:album) { Album.new(nil, Song.new, [Song.new("Urban Myth")]) }
348
343
 
349
344
  # before {
350
345
  # subject.validate({"songs" => [{"title"=>"Daddy, Brother, Lover, Little Boy"}], "hit" => {"title"=>"The Horse"}}).
@@ -355,13 +350,9 @@ end
355
350
  # end
356
351
  # end
357
352
 
358
-
359
-
360
-
361
-
362
353
  # # providing manual validator method allows accessing form's API.
363
354
  # describe "with ::validate" do
364
- # let (:form) {
355
+ # let(:form) {
365
356
  # Class.new(Reform::Form) do
366
357
  # property :title
367
358
 
@@ -373,8 +364,8 @@ end
373
364
  # end
374
365
  # }
375
366
 
376
- # let (:params) { {"title" => "Fallout"} }
377
- # let (:song) { Song.new("Englishman") }
367
+ # let(:params) { {"title" => "Fallout"} }
368
+ # let(:song) { Song.new("Englishman") }
378
369
 
379
370
  # subject { form.new(song) }
380
371
 
@@ -384,10 +375,9 @@ end
384
375
  # it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
385
376
  # end
386
377
 
387
-
388
378
  # # overriding the reader for a nested form should only be considered when rendering.
389
379
  # describe "with overridden reader for nested form" do
390
- # let (:form) {
380
+ # let(:form) {
391
381
  # Class.new(Reform::Form) do
392
382
  # property :band, :populate_if_empty => lambda { |*| Band.new } do
393
383
  # property :label
@@ -407,7 +397,7 @@ end
407
397
  # end.new(album)
408
398
  # }
409
399
 
410
- # let (:album) { Album.new }
400
+ # let(:album) { Album.new }
411
401
 
412
402
  # # don't use #artist when validating!
413
403
  # it do
@@ -0,0 +1,836 @@
1
+ require "test_helper"
2
+ require "reform/form/dry"
3
+ require "reform/form/coercion"
4
+ #---
5
+ # one "nested" Schema per form.
6
+ class DryValidationErrorsAPITest < Minitest::Spec
7
+ Album = Struct.new(:title, :artist, :songs)
8
+ Song = Struct.new(:title)
9
+ Artist = Struct.new(:email, :label)
10
+ Label = Struct.new(:location)
11
+
12
+ class AlbumForm < TestForm
13
+ property :title
14
+
15
+ validation do
16
+ params do
17
+ required(:title).filled(min_size?: 2)
18
+ end
19
+ end
20
+
21
+ property :artist do
22
+ property :email
23
+
24
+ validation do
25
+ params { required(:email).filled }
26
+ end
27
+
28
+ property :label do
29
+ property :location
30
+
31
+ validation do
32
+ params { required(:location).filled }
33
+ end
34
+ end
35
+ end
36
+
37
+ # note the validation block is *in* the collection block, per item, so to speak.
38
+ collection :songs do
39
+ property :title
40
+
41
+ validation do
42
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
43
+
44
+ params { required(:title).filled }
45
+ end
46
+ end
47
+ end
48
+
49
+ let(:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
50
+
51
+ it "everything wrong" do
52
+ result = form.(title: nil, artist: {email: ""}, songs: [{title: "Clams have feelings too"}, {title: ""}])
53
+
54
+ result.success?.must_equal false
55
+
56
+ form.errors.messages.must_equal(title: ["must be filled", "size cannot be less than 2"], "artist.email": ["must be filled"], "artist.label.location": ["must be filled"], "songs.title": ["must be filled"])
57
+ form.artist.errors.messages.must_equal(email: ["must be filled"], "label.location": ["must be filled"])
58
+ form.artist.label.errors.messages.must_equal(location: ["must be filled"])
59
+ form.songs[0].errors.messages.must_equal({})
60
+ form.songs[1].errors.messages.must_equal(title: ["must be filled"])
61
+
62
+ # #errors[]
63
+ form.errors[:nonsense].must_equal []
64
+ form.errors[:title].must_equal ["must be filled", "size cannot be less than 2"]
65
+ form.artist.errors[:email].must_equal ["must be filled"]
66
+ form.artist.label.errors[:location].must_equal ["must be filled"]
67
+ form.songs[0].errors[:title].must_equal []
68
+ form.songs[1].errors[:title].must_equal ["must be filled"]
69
+
70
+ # #to_result
71
+ form.to_result.errors.must_equal(title: ["must be filled"])
72
+ form.to_result.messages.must_equal(title: ["must be filled", "size cannot be less than 2"])
73
+ form.to_result.hints.must_equal(title: ["size cannot be less than 2"])
74
+ form.artist.to_result.errors.must_equal(email: ["must be filled"])
75
+ form.artist.to_result.messages.must_equal(email: ["must be filled"])
76
+ form.artist.to_result.hints.must_equal({})
77
+ form.artist.label.to_result.errors.must_equal(location: ["must be filled"])
78
+ form.artist.label.to_result.messages.must_equal(location: ["must be filled"])
79
+ form.artist.label.to_result.hints.must_equal({})
80
+ form.songs[0].to_result.errors.must_equal({})
81
+ form.songs[0].to_result.messages.must_equal({})
82
+ form.songs[0].to_result.hints.must_equal({})
83
+ form.songs[1].to_result.errors.must_equal(title: ["must be filled"])
84
+ form.songs[1].to_result.messages.must_equal(title: ["must be filled"])
85
+ form.songs[1].to_result.hints.must_equal({})
86
+ form.songs[1].to_result.errors(locale: :de).must_equal(title: ["muss abgefüllt sein"])
87
+ # seems like dry-v when calling Dry::Schema::Result#messages locale option is ignored
88
+ # started a topic in their forum https://discourse.dry-rb.org/t/dry-result-messages-ignore-locale-option/910
89
+ # form.songs[1].to_result.messages(locale: :de).must_equal(title: ["muss abgefüllt sein"])
90
+ form.songs[1].to_result.hints(locale: :de).must_equal({})
91
+ end
92
+
93
+ it "only nested property is invalid." do
94
+ result = form.(title: "Black Star", artist: {email: ""})
95
+
96
+ result.success?.must_equal false
97
+
98
+ # errors.messages
99
+ form.errors.messages.must_equal("artist.email": ["must be filled"], "artist.label.location": ["must be filled"], "songs.title": ["must be filled"])
100
+ form.artist.errors.messages.must_equal(email: ["must be filled"], "label.location": ["must be filled"])
101
+ form.artist.label.errors.messages.must_equal(location: ["must be filled"])
102
+ end
103
+
104
+ it "nested collection invalid" do
105
+ result = form.(title: "Black Star", artist: {email: "uhm", label: {location: "Hannover"}}, songs: [{title: ""}])
106
+
107
+ result.success?.must_equal false
108
+ form.errors.messages.must_equal("songs.title": ["must be filled"])
109
+ end
110
+
111
+ #---
112
+ #- validation .each
113
+ class CollectionExternalValidationsForm < TestForm
114
+ collection :songs do
115
+ property :title
116
+ end
117
+
118
+ validation do
119
+ params do
120
+ required(:songs).each do
121
+ schema do
122
+ required(:title).filled
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ it do
130
+ form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
131
+ form.validate(songs: [{title: "Liar"}, {title: ""}])
132
+
133
+ form.errors.messages.must_equal("songs.title": ["must be filled"])
134
+ form.songs[0].errors.messages.must_equal({})
135
+ form.songs[1].errors.messages.must_equal(title: ["must be filled"])
136
+ end
137
+ end
138
+
139
+ class DryValidationExplicitSchemaTest < Minitest::Spec
140
+ Session = Struct.new(:name, :email)
141
+ SessionSchema = Dry::Validation.Contract do
142
+ params do
143
+ required(:name).filled
144
+ required(:email).filled
145
+ end
146
+ end
147
+
148
+ class SessionForm < TestForm
149
+ include Coercion
150
+
151
+ property :name
152
+ property :email
153
+
154
+ validation schema: SessionSchema
155
+ end
156
+
157
+ let(:form) { SessionForm.new(Session.new) }
158
+
159
+ # valid.
160
+ it do
161
+ form.validate(name: "Helloween", email: "yep").must_equal true
162
+ form.errors.messages.inspect.must_equal "{}"
163
+ end
164
+
165
+ it "invalid" do
166
+ form.validate(name: "", email: "yep").must_equal false
167
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"]}"
168
+ end
169
+ end
170
+
171
+ class DryValidationDefaultGroupTest < Minitest::Spec
172
+ Session = Struct.new(:username, :email, :password, :confirm_password, :starts_at, :active, :color)
173
+
174
+ class SessionForm < TestForm
175
+ include Coercion
176
+
177
+ property :username
178
+ property :email
179
+ property :password
180
+ property :confirm_password
181
+ property :starts_at, type: DRY_TYPES_CONSTANT::DateTime
182
+ property :active, type: DRY_TYPES_CONSTANT::Bool
183
+ property :color
184
+
185
+ validation do
186
+ params do
187
+ required(:username).filled
188
+ required(:email).filled
189
+ required(:starts_at).filled(:date_time?)
190
+ required(:active).filled(:bool?)
191
+ end
192
+ end
193
+
194
+ validation name: :another_block do
195
+ params { required(:confirm_password).filled }
196
+ end
197
+
198
+ validation name: :dynamic_args do
199
+ option :form
200
+ params { optional(:color) }
201
+ rule(:color) do
202
+ if value
203
+ key.failure("must be one of: #{form.colors}") unless form.colors.include? value
204
+ end
205
+ end
206
+ end
207
+
208
+ def colors
209
+ %(red orange green)
210
+ end
211
+ end
212
+
213
+ let(:form) { SessionForm.new(Session.new) }
214
+
215
+ # valid.
216
+ it do
217
+ assert form.validate(
218
+ username: "Helloween",
219
+ email: "yep",
220
+ starts_at: "01/01/2000 - 11:00",
221
+ active: "true",
222
+ confirm_password: "pA55w0rd"
223
+ )
224
+ assert form.active
225
+ assert_equal "{}", form.errors.messages.inspect
226
+ end
227
+
228
+ it "invalid" do
229
+ form.validate(
230
+ username: "Helloween",
231
+ email: "yep",
232
+ active: "1",
233
+ starts_at: "01/01/2000 - 11:00",
234
+ color: "purple"
235
+ ).must_equal false
236
+ form.active.must_equal true
237
+ form.errors.messages.inspect.must_equal "{:confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
238
+ end
239
+ end
240
+
241
+ class ValidationGroupsTest < MiniTest::Spec
242
+ describe "basic validations" do
243
+ Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
244
+ SomeClass = Struct.new(:id)
245
+
246
+ class SessionForm < TestForm
247
+ property :username
248
+ property :email
249
+ property :password
250
+ property :confirm_password
251
+ property :special_class
252
+
253
+ validation do
254
+ params do
255
+ required(:username).filled
256
+ required(:email).filled
257
+ required(:special_class).filled(type?: SomeClass)
258
+ end
259
+ end
260
+
261
+ validation name: :email, if: :default do
262
+ params { required(:email).filled(min_size?: 3) }
263
+ end
264
+
265
+ validation name: :password, if: :email do
266
+ params { required(:password).filled(min_size?: 2) }
267
+ end
268
+
269
+ validation name: :confirm, if: :default, after: :email do
270
+ params { required(:confirm_password).filled(min_size?: 2) }
271
+ end
272
+ end
273
+
274
+ let(:form) { SessionForm.new(Session.new) }
275
+
276
+ # valid.
277
+ it do
278
+ form.validate(username: "Helloween",
279
+ special_class: SomeClass.new(id: 15),
280
+ email: "yep",
281
+ password: "99",
282
+ confirm_password: "99").must_equal true
283
+ form.errors.messages.inspect.must_equal "{}"
284
+ end
285
+
286
+ # invalid.
287
+ it do
288
+ form.validate({}).must_equal false
289
+ form.errors.messages.must_equal({username: ["must be filled"], email: ["must be filled"], special_class: ["must be filled", "must be ValidationGroupsTest::SomeClass"]})
290
+ end
291
+
292
+ # partially invalid.
293
+ # 2nd group fails.
294
+ it do
295
+ form.validate(username: "Helloween", email: "yo", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
296
+ form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"]}"
297
+ end
298
+ # 3rd group fails.
299
+ it do
300
+ form.validate(username: "Helloween", email: "yo!", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
301
+ form.errors.messages.inspect
302
+ .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
303
+ end
304
+ # 4th group with after: fails.
305
+ it do
306
+ form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
307
+ form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"size cannot be less than 2\"]}"
308
+ end
309
+ end
310
+
311
+ class ValidationWithOptionsTest < MiniTest::Spec
312
+ describe "basic validations" do
313
+ Session = Struct.new(:username)
314
+ class SessionForm < TestForm
315
+ property :username
316
+
317
+ validation name: :default, with: {user: OpenStruct.new(name: "Nick")} do
318
+ option :user
319
+ params do
320
+ required(:username).filled
321
+ end
322
+ rule(:username) do
323
+ key.failure("must be equal to #{user.name}") unless user.name == value
324
+ end
325
+ end
326
+ end
327
+
328
+ let(:form) { SessionForm.new(Session.new) }
329
+
330
+ # valid.
331
+ it do
332
+ form.validate(username: "Nick").must_equal true
333
+ form.errors.messages.inspect.must_equal "{}"
334
+ end
335
+
336
+ # invalid.
337
+ it do
338
+ form.validate(username: "Fred").must_equal false
339
+ form.errors.messages.inspect.must_equal "{:username=>[\"must be equal to Nick\"]}"
340
+ end
341
+ end
342
+ end
343
+
344
+ #---
345
+ #- validation( schema: MySchema )
346
+ describe "with custom schema" do
347
+ Session2 = Struct.new(:username, :email, :password)
348
+
349
+ MySchema = Dry::Schema.Params do
350
+ config.messages.load_paths << "test/fixtures/dry_error_messages.yml"
351
+
352
+ required(:password).filled(min_size?: 6)
353
+ end
354
+
355
+ class Session2Form < TestForm
356
+ property :username
357
+ property :email
358
+ property :password
359
+
360
+ validation schema: MySchema do
361
+ params do
362
+ required(:username).filled
363
+ required(:email).filled
364
+ end
365
+
366
+ rule(:email) do
367
+ key.failure(:good_musical_taste?) unless value.is_a? String
368
+ end
369
+ end
370
+ end
371
+
372
+ let(:form) { Session2Form.new(Session2.new) }
373
+
374
+ # valid.
375
+ it do
376
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
377
+ form.validate(username: "Helloween", email: "yep", password: "extrasafe").must_equal true
378
+ form.errors.messages.inspect.must_equal "{}"
379
+ end
380
+
381
+ # invalid.
382
+ it do
383
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
384
+ form.validate({}).must_equal false
385
+ form.errors.messages.must_equal(password: ["must be filled", "size cannot be less than 6"], username: ["must be filled"], email: ["must be filled", "you're a bad person"])
386
+ end
387
+
388
+ it do
389
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
390
+ form.validate(email: 1).must_equal false
391
+ form.errors.messages.inspect.must_equal "{:password=>[\"must be filled\", \"size cannot be less than 6\"], :username=>[\"must be filled\"], :email=>[\"you're a bad person\"]}"
392
+ end
393
+ end
394
+
395
+ describe "MIXED nested validations" do
396
+ class AlbumForm < TestForm
397
+ property :title
398
+
399
+ property :hit do
400
+ property :title
401
+
402
+ validation do
403
+ params { required(:title).filled }
404
+ end
405
+ end
406
+
407
+ collection :songs do
408
+ property :title
409
+
410
+ validation do
411
+ params { required(:title).filled }
412
+ end
413
+ end
414
+
415
+ # we test this one by running an each / schema dry-v check on the main block
416
+ collection :producers do
417
+ property :name
418
+ end
419
+
420
+ property :band do
421
+ property :name
422
+ property :label do
423
+ property :location
424
+ end
425
+ end
426
+
427
+ validation do
428
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
429
+ params do
430
+ required(:title).filled
431
+ required(:band).hash do
432
+ required(:name).filled
433
+ required(:label).hash do
434
+ required(:location).filled
435
+ end
436
+ end
437
+
438
+ required(:producers).each do
439
+ hash { required(:name).filled }
440
+ end
441
+ end
442
+
443
+ rule(:title) do
444
+ key.failure(:good_musical_taste?) unless value != "Nickelback"
445
+ end
446
+ end
447
+ end
448
+
449
+ let(:album) do
450
+ OpenStruct.new(
451
+ hit: OpenStruct.new,
452
+ songs: [OpenStruct.new, OpenStruct.new],
453
+ band: Struct.new(:name, :label).new("", OpenStruct.new),
454
+ producers: [OpenStruct.new, OpenStruct.new, OpenStruct.new],
455
+ )
456
+ end
457
+
458
+ let(:form) { AlbumForm.new(album) }
459
+
460
+ it "maps errors to form objects correctly" do
461
+ result = form.validate(
462
+ "title" => "Nickelback",
463
+ "songs" => [{"title" => ""}, {"title" => ""}],
464
+ "band" => {"size" => "", "label" => {"location" => ""}},
465
+ "producers" => [{"name" => ""}, {"name" => "something lovely"}]
466
+ )
467
+
468
+ result.must_equal false
469
+ # from nested validation
470
+ form.errors.messages.must_equal(title: ["you're a bad person"], "hit.title": ["must be filled"], "songs.title": ["must be filled"], "producers.name": ["must be filled"], "band.name": ["must be filled"], "band.label.location": ["must be filled"])
471
+
472
+ # songs have their own validation.
473
+ form.songs[0].errors.messages.must_equal(title: ["must be filled"])
474
+ # hit got its own validation group.
475
+ form.hit.errors.messages.must_equal(title: ["must be filled"])
476
+
477
+ form.band.label.errors.messages.must_equal(location: ["must be filled"])
478
+ form.band.errors.messages.must_equal(name: ["must be filled"], "label.location": ["must be filled"])
479
+ form.producers[0].errors.messages.must_equal(name: ["must be filled"])
480
+
481
+ # TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
482
+ form.producers[0].to_result.errors.must_equal(name: ["must be filled"])
483
+ form.producers[0].to_result.messages.must_equal(name: ["must be filled"])
484
+ form.producers[0].to_result.hints.must_equal({})
485
+ end
486
+
487
+ # FIXME: fix the "must be filled error"
488
+
489
+ it "renders full messages correctly" do
490
+ result = form.validate(
491
+ "title" => "",
492
+ "songs" => [{"title" => ""}, {"title" => ""}],
493
+ "band" => {"size" => "", "label" => {"name" => ""}},
494
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
495
+ )
496
+
497
+ result.must_equal false
498
+ form.band.errors.full_messages.must_equal ["Name must be filled", "Label Location must be filled"]
499
+ form.band.label.errors.full_messages.must_equal ["Location must be filled"]
500
+ form.producers.first.errors.full_messages.must_equal ["Name must be filled"]
501
+ form.errors.full_messages.must_equal ["Title must be filled", "Hit Title must be filled", "Songs Title must be filled", "Producers Name must be filled", "Band Name must be filled", "Band Label Location must be filled"]
502
+ end
503
+
504
+ describe "only 1 nested validation" do
505
+ class AlbumFormWith1NestedVal < TestForm
506
+ property :title
507
+ property :band do
508
+ property :name
509
+ property :label do
510
+ property :location
511
+ end
512
+ end
513
+
514
+ validation do
515
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
516
+
517
+ params do
518
+ required(:title).filled
519
+
520
+ required(:band).schema do
521
+ required(:name).filled
522
+ required(:label).schema do
523
+ required(:location).filled
524
+ end
525
+ end
526
+ end
527
+ end
528
+ end
529
+
530
+ let(:form) { AlbumFormWith1NestedVal.new(album) }
531
+
532
+ it "allows to access dry's result semantics per nested form" do
533
+ form.validate(
534
+ "title" => "",
535
+ "songs" => [{"title" => ""}, {"title" => ""}],
536
+ "band" => {"size" => "", "label" => {"name" => ""}},
537
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
538
+ )
539
+
540
+ form.to_result.errors.must_equal(title: ["must be filled"])
541
+ form.band.to_result.errors.must_equal(name: ["must be filled"])
542
+ form.band.label.to_result.errors.must_equal(location: ["must be filled"])
543
+
544
+ # with locale: "de"
545
+ form.to_result.errors(locale: :de).must_equal(title: ["muss abgefüllt sein"])
546
+ form.band.to_result.errors(locale: :de).must_equal(name: ["muss abgefüllt sein"])
547
+ form.band.label.to_result.errors(locale: :de).must_equal(location: ["muss abgefüllt sein"])
548
+ end
549
+ end
550
+ end
551
+
552
+ # describe "same-named group" do
553
+ # class OverwritingForm < TestForm
554
+ # include Reform::Form::Dry::Validations
555
+
556
+ # property :username
557
+ # property :email
558
+
559
+ # validation :email do # FIX ME: is this working for other validator or just bugging here?
560
+ # key(:email, &:filled?) # it's not considered, overitten
561
+ # end
562
+
563
+ # validation :email do # just another group.
564
+ # key(:username, &:filled?)
565
+ # end
566
+ # end
567
+
568
+ # let(:form) { OverwritingForm.new(Session.new) }
569
+
570
+ # # valid.
571
+ # it do
572
+ # form.validate({username: "Helloween"}).must_equal true
573
+ # end
574
+
575
+ # # invalid.
576
+ # it "whoo" do
577
+ # form.validate({}).must_equal false
578
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username can't be blank\"]}"
579
+ # end
580
+ # end
581
+
582
+ describe "inherit: true in same group" do
583
+ class InheritSameGroupForm < TestForm
584
+ property :username
585
+ property :email
586
+ property :full_name, virtual: true
587
+
588
+ validation name: :username do
589
+ params do
590
+ required(:username).filled
591
+ required(:full_name).filled
592
+ end
593
+ end
594
+
595
+ validation name: :username, inherit: true do # extends the above.
596
+ params do
597
+ optional(:username).maybe(:string)
598
+ required(:email).filled
599
+ end
600
+ end
601
+ end
602
+
603
+ let(:form) { InheritSameGroupForm.new(Session.new) }
604
+
605
+ # valid.
606
+ it do
607
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
608
+ form.validate(email: 9).must_equal true
609
+ end
610
+
611
+ # invalid.
612
+ it do
613
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
614
+ form.validate({}).must_equal false
615
+ form.errors.messages.must_equal email: ["must be filled"], full_name: ["must be filled"]
616
+ end
617
+ end
618
+
619
+ describe "if: with lambda" do
620
+ class IfWithLambdaForm < TestForm
621
+ property :username
622
+ property :email
623
+ property :password
624
+
625
+ validation name: :email do
626
+ params { required(:email).filled }
627
+ end
628
+
629
+ # run this is :email group is true.
630
+ validation name: :after_email, if: ->(results) { results[:email].success? } do # extends the above.
631
+ params { required(:username).filled }
632
+ end
633
+
634
+ # block gets evaled in form instance context.
635
+ validation name: :password, if: ->(results) { email == "john@trb.org" } do
636
+ params { required(:password).filled }
637
+ end
638
+ end
639
+
640
+ let(:form) { IfWithLambdaForm.new(Session.new) }
641
+
642
+ # valid.
643
+ it do
644
+ form.validate(username: "Strung Out", email: 9).must_equal true
645
+ end
646
+
647
+ # invalid.
648
+ it do
649
+ form.validate(email: 9).must_equal false
650
+ form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
651
+ end
652
+ end
653
+
654
+ class NestedSchemaValidationTest < MiniTest::Spec
655
+ AddressSchema = Dry::Schema.Params do
656
+ required(:company).filled(:int?)
657
+ end
658
+
659
+ class OrderForm < TestForm
660
+ property :delivery_address do
661
+ property :company
662
+ end
663
+
664
+ validation do
665
+ params { required(:delivery_address).schema(AddressSchema) }
666
+ end
667
+ end
668
+
669
+ let(:company) { Struct.new(:company) }
670
+ let(:order) { Struct.new(:delivery_address) }
671
+ let(:form) { OrderForm.new(order.new(company.new)) }
672
+
673
+ it "has company error" do
674
+ form.validate(delivery_address: {company: "not int"}).must_equal false
675
+ form.errors.messages.must_equal(:"delivery_address.company" => ["must be an integer"])
676
+ end
677
+ end
678
+
679
+ class NestedSchemaValidationWithFormTest < MiniTest::Spec
680
+ class CompanyForm < TestForm
681
+ property :company
682
+
683
+ validation do
684
+ params { required(:company).filled(:int?) }
685
+ end
686
+ end
687
+
688
+ class OrderFormWithForm < TestForm
689
+ property :delivery_address, form: CompanyForm
690
+ end
691
+
692
+ let(:company) { Struct.new(:company) }
693
+ let(:order) { Struct.new(:delivery_address) }
694
+ let(:form) { OrderFormWithForm.new(order.new(company.new)) }
695
+
696
+ it "has company error" do
697
+ form.validate(delivery_address: {company: "not int"}).must_equal false
698
+ form.errors.messages.must_equal(:"delivery_address.company" => ["must be an integer"])
699
+ end
700
+ end
701
+
702
+ class CollectionPropertyWithCustomRuleTest < MiniTest::Spec
703
+ Artist = Struct.new(:first_name, :last_name)
704
+ Song = Struct.new(:title, :enabled)
705
+ Album = Struct.new(:title, :songs, :artist)
706
+
707
+ class AlbumForm < TestForm
708
+ property :title
709
+
710
+ collection :songs, virtual: true, populate_if_empty: Song do
711
+ property :title
712
+ property :enabled
713
+
714
+ validation do
715
+ params { required(:title).filled }
716
+ end
717
+ end
718
+
719
+ property :artist, populate_if_empty: Artist do
720
+ property :first_name
721
+ property :last_name
722
+ end
723
+
724
+ validation do
725
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
726
+
727
+ params do
728
+ required(:songs).filled
729
+ required(:artist).filled
730
+ end
731
+
732
+ rule(:songs) do
733
+ key.failure(:a_song?) unless value.any? { |el| el && el[:enabled] }
734
+ end
735
+
736
+ rule(:artist) do
737
+ key.failure(:with_last_name?) unless value[:last_name]
738
+ end
739
+ end
740
+ end
741
+
742
+ it "validates fails and shows the correct errors" do
743
+ form = AlbumForm.new(Album.new(nil, [], nil))
744
+ form.validate(
745
+ "songs" => [
746
+ {"title" => "One", "enabled" => false},
747
+ {"title" => nil, "enabled" => false},
748
+ {"title" => "Three", "enabled" => false}
749
+ ],
750
+ "artist" => {"last_name" => nil}
751
+ ).must_equal false
752
+ form.songs.size.must_equal 3
753
+
754
+ form.errors.messages.must_equal(
755
+ :songs => ["must have at least one enabled song"],
756
+ :artist => ["must have last name"],
757
+ :"songs.title" => ["must be filled"]
758
+ )
759
+ end
760
+ end
761
+
762
+ class DryVWithSchemaAndParams < MiniTest::Spec
763
+ Foo = Struct.new(:age)
764
+
765
+ class ParamsForm < TestForm
766
+ property :age
767
+
768
+ validation do
769
+ params { required(:age).value(:integer) }
770
+
771
+ rule(:age) { key.failure("value exceeded") if value > 999 }
772
+ end
773
+ end
774
+
775
+ class SchemaForm < TestForm
776
+ property :age
777
+
778
+ validation do
779
+ schema { required(:age).value(:integer) }
780
+
781
+ rule(:age) { key.failure("value exceeded") if value > 999 }
782
+ end
783
+ end
784
+
785
+ it "using params" do
786
+ model = Foo.new
787
+ form = ParamsForm.new(model)
788
+ form.validate(age: "99").must_equal true
789
+ form.sync
790
+ model.age.must_equal "99"
791
+
792
+ form = ParamsForm.new(Foo.new)
793
+ form.validate(age: "1000").must_equal false
794
+ form.errors.messages.must_equal age: ["value exceeded"]
795
+ end
796
+
797
+ it "using schema" do
798
+ model = Foo.new
799
+ form = SchemaForm.new(model)
800
+ form.validate(age: "99").must_equal false
801
+ form.validate(age: 99).must_equal true
802
+ form.sync
803
+ model.age.must_equal 99
804
+
805
+ form = SchemaForm.new(Foo.new)
806
+ form.validate(age: 1000).must_equal false
807
+ form.errors.messages.must_equal age: ["value exceeded"]
808
+ end
809
+ end
810
+
811
+ # Currenty dry-v don't support that option, it doesn't make sense
812
+ # I've talked to @solnic and he plans to add a "hint" feature to show
813
+ # more errors messages than only those that have failed.
814
+ #
815
+ # describe "multiple errors for property" do
816
+ # class MultipleErrorsForPropertyForm < TestForm
817
+ # include Reform::Form::Dry::Validations
818
+
819
+ # property :username
820
+
821
+ # validation :default do
822
+ # key(:username) do |username|
823
+ # username.filled? | (username.min_size?(2) & username.max_size?(3))
824
+ # end
825
+ # end
826
+ # end
827
+
828
+ # let(:form) { MultipleErrorsForPropertyForm.new(Session.new) }
829
+
830
+ # # valid.
831
+ # it do
832
+ # form.validate({username: ""}).must_equal false
833
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\", \"username is not proper size\"]}"
834
+ # end
835
+ # end
836
+ end