reform 2.2.4 → 2.3.1

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