reform 2.2.4 → 2.3.0.rc1
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.
- checksums.yaml +4 -4
- data/.travis.yml +13 -7
- data/CHANGES.md +26 -4
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +1 -12
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +3 -3
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +1 -11
- data/lib/reform/contract/validate.rb +49 -23
- data/lib/reform/errors.rb +49 -0
- data/lib/reform/form.rb +20 -5
- data/lib/reform/form/dry.rb +57 -29
- data/lib/reform/form/populator.rb +2 -16
- data/lib/reform/form/prepopulate.rb +1 -1
- data/lib/reform/form/validate.rb +10 -2
- data/lib/reform/result.rb +63 -0
- data/lib/reform/validation.rb +19 -13
- data/lib/reform/validation/groups.rb +11 -25
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +7 -6
- data/test/benchmarking.rb +39 -5
- data/test/call_test.rb +1 -1
- data/test/changed_test.rb +1 -1
- data/test/coercion_test.rb +2 -2
- data/test/composition_test.rb +47 -9
- data/test/contract_test.rb +5 -5
- data/test/default_test.rb +1 -1
- data/test/deserialize_test.rb +3 -3
- data/test/errors_test.rb +36 -21
- data/test/feature_test.rb +1 -1
- data/test/fixtures/dry_error_messages.yml +70 -23
- data/test/form_option_test.rb +3 -3
- data/test/form_test.rb +3 -3
- data/test/from_test.rb +2 -2
- data/test/inherit_test.rb +44 -51
- data/test/module_test.rb +12 -12
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +2 -2
- data/test/populate_test.rb +59 -19
- data/test/populator_skip_test.rb +9 -8
- data/test/prepopulator_test.rb +3 -3
- data/test/readable_test.rb +2 -2
- data/test/readonly_test.rb +1 -1
- data/test/reform_test.rb +16 -31
- data/test/save_test.rb +23 -8
- data/test/setup_test.rb +2 -2
- data/test/skip_if_test.rb +4 -4
- data/test/skip_setter_and_getter_test.rb +1 -1
- data/test/test_helper.rb +13 -10
- data/test/validate_test.rb +18 -18
- data/test/validation/dry_validation_test.rb +430 -117
- data/test/validation/result_test.rb +79 -0
- data/test/validation_library_provided_test.rb +16 -0
- data/test/virtual_test.rb +1 -1
- data/test/writeable_test.rb +31 -2
- metadata +42 -23
- data/gemfiles/Gemfile.disposable-0.3 +0 -6
- data/lib/reform/contract/errors.rb +0 -43
- data/lib/reform/form/mongoid.rb +0 -37
- data/lib/reform/form/orm.rb +0 -26
- data/lib/reform/mongoid.rb +0 -4
- data/test/deprecation_test.rb +0 -27
- data/test/validation/dry_test.rb +0 -60
- data/test/validation/errors.yml +0 -4
data/test/save_test.rb
CHANGED
@@ -5,22 +5,22 @@ class SaveTest < BaseTest
|
|
5
5
|
Album = Struct.new(:name, :songs, :artist)
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
class AlbumForm <
|
8
|
+
class AlbumForm < TestForm
|
9
9
|
property :name
|
10
10
|
validation do
|
11
|
-
|
11
|
+
required(:name).filled
|
12
12
|
end
|
13
13
|
|
14
14
|
collection :songs do
|
15
15
|
property :title
|
16
16
|
validation do
|
17
|
-
|
17
|
+
required(:title).filled
|
18
18
|
end
|
19
19
|
|
20
20
|
property :composer do
|
21
21
|
property :name
|
22
22
|
validation do
|
23
|
-
|
23
|
+
required(:name).filled
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -36,7 +36,7 @@ class SaveTest < BaseTest
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def saved?
|
39
|
-
@saved
|
39
|
+
defined?(@saved) && @saved
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -58,7 +58,22 @@ class SaveTest < BaseTest
|
|
58
58
|
album.saved?.must_equal true
|
59
59
|
album.songs[0].title.must_equal "Fixed"
|
60
60
|
album.songs[0].saved?.must_equal true
|
61
|
-
album.artist.saved
|
61
|
+
assert_nil album.artist.saved?
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#sync with block" do
|
65
|
+
it do
|
66
|
+
form = AlbumForm.new(Album.new("Greatest Hits"))
|
67
|
+
|
68
|
+
form.validate(name: nil) # nil-out the title.
|
69
|
+
|
70
|
+
nested_hash = nil
|
71
|
+
form.sync do |hash|
|
72
|
+
nested_hash = hash
|
73
|
+
end
|
74
|
+
|
75
|
+
nested_hash.must_equal({"name"=>nil, "artist"=>nil})
|
76
|
+
end
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
@@ -68,7 +83,7 @@ end
|
|
68
83
|
# include Saveable
|
69
84
|
# end
|
70
85
|
|
71
|
-
# class SongForm <
|
86
|
+
# class SongForm < TestForm
|
72
87
|
# property :title#, save: false
|
73
88
|
# property :length, virtual: true
|
74
89
|
# end
|
@@ -83,7 +98,7 @@ end
|
|
83
98
|
# form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
|
84
99
|
|
85
100
|
# song.title.must_equal "A Poor Man's Memory"
|
86
|
-
# song.length
|
101
|
+
# assert_nil song.length
|
87
102
|
# song.id.must_equal "10: 120"
|
88
103
|
# end
|
89
104
|
# end
|
data/test/setup_test.rb
CHANGED
@@ -5,7 +5,7 @@ class SetupTest < MiniTest::Spec
|
|
5
5
|
Album = Struct.new(:name, :songs, :artist)
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
class AlbumForm <
|
8
|
+
class AlbumForm < TestForm
|
9
9
|
property :name
|
10
10
|
collection :songs do
|
11
11
|
property :title
|
@@ -33,7 +33,7 @@ class SetupTest < MiniTest::Spec
|
|
33
33
|
|
34
34
|
form.name.must_equal "The Dissent Of Man"
|
35
35
|
form.songs[0].title.must_equal "Broken"
|
36
|
-
form.songs[0].composer
|
36
|
+
assert_nil form.songs[0].composer
|
37
37
|
form.songs[1].title.must_equal "Resist Stance"
|
38
38
|
form.songs[1].composer.name.must_equal "Greg Graffin"
|
39
39
|
form.artist.name.must_equal "Bad Religion"
|
data/test/skip_if_test.rb
CHANGED
@@ -2,13 +2,13 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class SkipIfTest < BaseTest
|
4
4
|
|
5
|
-
class AlbumForm <
|
5
|
+
class AlbumForm < TestForm
|
6
6
|
property :title
|
7
7
|
|
8
8
|
property :hit, skip_if: lambda { |options| options[:fragment]["title"]=="" } do
|
9
9
|
property :title
|
10
10
|
validation do
|
11
|
-
|
11
|
+
required(:title).filled
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -36,7 +36,7 @@ class SkipIfTest < BaseTest
|
|
36
36
|
it do
|
37
37
|
form = AlbumForm.new(Album.new)
|
38
38
|
form.validate("hit" => {"title" => ""}).must_equal true
|
39
|
-
form.hit
|
39
|
+
assert_nil form.hit # hit hasn't been deserialised.
|
40
40
|
end
|
41
41
|
|
42
42
|
# skips deserialization when not present.
|
@@ -50,7 +50,7 @@ end
|
|
50
50
|
|
51
51
|
class SkipIfAllBlankTest < BaseTest
|
52
52
|
# skip_if: :all_blank"
|
53
|
-
class AlbumForm <
|
53
|
+
class AlbumForm < TestForm
|
54
54
|
collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
|
55
55
|
property :title
|
56
56
|
property :length
|
data/test/test_helper.rb
CHANGED
@@ -3,9 +3,20 @@ require 'minitest/autorun'
|
|
3
3
|
require "representable/debug"
|
4
4
|
require "declarative/testing"
|
5
5
|
require "pp"
|
6
|
+
require 'byebug'
|
7
|
+
|
8
|
+
require "reform/form/dry"
|
9
|
+
# setup test classes so we can test without dry being included
|
10
|
+
class TestForm < Reform::Form
|
11
|
+
feature Reform::Form::Dry
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestContract < Reform::Contract
|
15
|
+
feature Reform::Form::Dry
|
16
|
+
end
|
6
17
|
|
7
18
|
class BaseTest < MiniTest::Spec
|
8
|
-
class AlbumForm <
|
19
|
+
class AlbumForm < TestForm
|
9
20
|
property :title
|
10
21
|
|
11
22
|
property :hit do
|
@@ -17,7 +28,7 @@ class BaseTest < MiniTest::Spec
|
|
17
28
|
end
|
18
29
|
end
|
19
30
|
|
20
|
-
Song = Struct.new(:title, :length)
|
31
|
+
Song = Struct.new(:title, :length, :rating)
|
21
32
|
Album = Struct.new(:title, :hit, :songs, :band)
|
22
33
|
Band = Struct.new(:label)
|
23
34
|
Label = Struct.new(:name)
|
@@ -39,11 +50,3 @@ MiniTest::Spec.class_eval do
|
|
39
50
|
end
|
40
51
|
end
|
41
52
|
|
42
|
-
require "reform/form/dry"
|
43
|
-
Reform::Contract.class_eval do
|
44
|
-
feature Reform::Form::Dry
|
45
|
-
end
|
46
|
-
# FIXME!
|
47
|
-
Reform::Form.class_eval do
|
48
|
-
feature Reform::Form::Dry
|
49
|
-
end
|
data/test/validate_test.rb
CHANGED
@@ -5,21 +5,21 @@ class ContractValidateTest < MiniTest::Spec
|
|
5
5
|
Album = Struct.new(:name, :songs, :artist)
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
class AlbumForm <
|
8
|
+
class AlbumForm < TestContract
|
9
9
|
property :name
|
10
10
|
validation do
|
11
|
-
|
11
|
+
required(:name).filled
|
12
12
|
end
|
13
13
|
|
14
14
|
collection :songs do
|
15
15
|
property :title
|
16
16
|
validation do
|
17
|
-
|
17
|
+
required(:title).filled
|
18
18
|
end
|
19
19
|
|
20
20
|
property :composer do
|
21
21
|
validation do
|
22
|
-
|
22
|
+
required(:name).filled
|
23
23
|
end
|
24
24
|
property :name
|
25
25
|
end
|
@@ -50,7 +50,7 @@ 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=>[\"
|
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
|
|
@@ -61,23 +61,23 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
|
|
61
61
|
Album = Struct.new(:name, :songs, :artist)
|
62
62
|
Artist = Struct.new(:name)
|
63
63
|
|
64
|
-
class AlbumForm <
|
64
|
+
class AlbumForm < TestForm
|
65
65
|
property :name
|
66
66
|
validation do
|
67
|
-
|
67
|
+
required(:name).filled
|
68
68
|
end
|
69
69
|
|
70
70
|
collection :songs do
|
71
71
|
|
72
72
|
property :title
|
73
73
|
validation do
|
74
|
-
|
74
|
+
required(:title).filled
|
75
75
|
end
|
76
76
|
|
77
77
|
property :composer do
|
78
78
|
property :name
|
79
79
|
validation do
|
80
|
-
|
80
|
+
required(:name).filled
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
@@ -160,10 +160,10 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
160
160
|
Album = Struct.new(:name, :songs, :artist)
|
161
161
|
Artist = Struct.new(:name)
|
162
162
|
|
163
|
-
class AlbumForm <
|
163
|
+
class AlbumForm < TestForm
|
164
164
|
property :name
|
165
165
|
validation do
|
166
|
-
|
166
|
+
required(:name).filled
|
167
167
|
end
|
168
168
|
|
169
169
|
collection :songs,
|
@@ -173,13 +173,13 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
173
173
|
|
174
174
|
property :title
|
175
175
|
validation do
|
176
|
-
|
176
|
+
required(:title).filled
|
177
177
|
end
|
178
178
|
|
179
179
|
property :composer, internal_populator: lambda { |input, options| (item = options[:represented].composer) ? item : Artist.new } do
|
180
180
|
property :name
|
181
181
|
validation do
|
182
|
-
|
182
|
+
required(:name).filled
|
183
183
|
end
|
184
184
|
end
|
185
185
|
end
|
@@ -187,7 +187,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
187
187
|
property :artist, internal_populator: lambda { |input, options| (item = options[:represented].artist) ? item : Artist.new } do
|
188
188
|
property :name
|
189
189
|
validation do
|
190
|
-
|
190
|
+
required(:name).filled
|
191
191
|
end
|
192
192
|
end
|
193
193
|
end
|
@@ -268,21 +268,21 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
268
268
|
|
269
269
|
|
270
270
|
# allow writeable: false even in the deserializer.
|
271
|
-
class SongForm <
|
272
|
-
property :title, deserializer: {writeable: false}
|
271
|
+
class SongForm < TestForm
|
272
|
+
property :title, deserializer: { writeable: false }
|
273
273
|
end
|
274
274
|
|
275
275
|
it do
|
276
276
|
form = SongForm.new(song = Song.new)
|
277
277
|
form.validate("title" => "Ignore me!")
|
278
|
-
form.title
|
278
|
+
assert_nil form.title
|
279
279
|
form.title = "Unopened"
|
280
280
|
form.sync # only the deserializer is marked as not-writeable.
|
281
281
|
song.title.must_equal "Unopened"
|
282
282
|
end
|
283
283
|
end
|
284
284
|
|
285
|
-
# # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/
|
285
|
+
# # 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
286
|
# # describe ":populator with :empty" do
|
287
287
|
# # let (:form) {
|
288
288
|
# # Class.new(Reform::Form) do
|
@@ -1,20 +1,208 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require "test_helper"
|
2
3
|
require "reform/form/dry"
|
4
|
+
require "reform/form/coercion"
|
5
|
+
|
6
|
+
#---
|
7
|
+
# one "nested" Schema per form.
|
8
|
+
class DryValidationErrorsAPITest < Minitest::Spec
|
9
|
+
Album = Struct.new(:title, :artist, :songs)
|
10
|
+
Song = Struct.new(:title)
|
11
|
+
Artist = Struct.new(:email, :label)
|
12
|
+
Label = Struct.new(:location)
|
13
|
+
|
14
|
+
class AlbumForm < TestForm
|
15
|
+
property :title
|
16
|
+
|
17
|
+
validation do
|
18
|
+
# required(:title).filled
|
19
|
+
required(:title).filled(min_size?: 2)
|
20
|
+
end
|
21
|
+
|
22
|
+
property :artist do
|
23
|
+
property :email
|
24
|
+
|
25
|
+
validation do
|
26
|
+
required(:email).filled
|
27
|
+
end
|
28
|
+
|
29
|
+
property :label do
|
30
|
+
property :location
|
31
|
+
|
32
|
+
validation do
|
33
|
+
required(:location).filled
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# note the validation block is *in* the collection block, per item, so to speak.
|
39
|
+
collection :songs do
|
40
|
+
property :title
|
41
|
+
|
42
|
+
validation do
|
43
|
+
configure do
|
44
|
+
config.messages_file = 'test/fixtures/dry_error_messages.yml'
|
45
|
+
end
|
46
|
+
|
47
|
+
required(:title).filled
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
let (:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
|
54
|
+
|
55
|
+
it "everything wrong" do
|
56
|
+
result = form.({ title: nil, artist: { email: "" }, songs: [{ title: "Clams have feelings too" }, { title: "" }] })
|
57
|
+
|
58
|
+
result.success?.must_equal false
|
59
|
+
|
60
|
+
# errors.messages
|
61
|
+
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"]})
|
62
|
+
form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
|
63
|
+
form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
|
64
|
+
form.songs[0].errors.messages.must_equal({})
|
65
|
+
form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
|
66
|
+
|
67
|
+
# #errors[]
|
68
|
+
form.errors[:nonsense].must_be_nil
|
69
|
+
form.errors[:title].must_equal ["must be filled", "size cannot be less than 2"]
|
70
|
+
form.artist.errors[:email].must_equal ["must be filled"]
|
71
|
+
form.artist.label.errors[:location].must_equal ["must be filled"]
|
72
|
+
form.songs[0].errors[:title].must_be_nil
|
73
|
+
form.songs[1].errors[:title].must_equal ["must be filled"]
|
74
|
+
|
75
|
+
# #to_result
|
76
|
+
form.to_result.errors.must_equal({:title=>["must be filled"]})
|
77
|
+
form.to_result.messages.must_equal({:title=>["must be filled", "size cannot be less than 2"]})
|
78
|
+
form.to_result.hints.must_equal({:title=>["size cannot be less than 2"]})
|
79
|
+
form.artist.to_result.errors.must_equal({:email=>["must be filled"]})
|
80
|
+
form.artist.to_result.messages.must_equal({:email=>["must be filled"]})
|
81
|
+
form.artist.to_result.hints.must_equal({:email=>[]})
|
82
|
+
form.artist.label.to_result.errors.must_equal({:location=>["must be filled"]})
|
83
|
+
form.artist.label.to_result.messages.must_equal({:location=>["must be filled"]})
|
84
|
+
form.artist.label.to_result.hints.must_equal({:location=>[]})
|
85
|
+
form.songs[0].to_result.errors.must_equal({})
|
86
|
+
form.songs[0].to_result.messages.must_equal({})
|
87
|
+
form.songs[0].to_result.hints.must_equal({})
|
88
|
+
form.songs[1].to_result.errors.must_equal({:title=>["must be filled"]})
|
89
|
+
form.songs[1].to_result.messages.must_equal({:title=>["must be filled"]})
|
90
|
+
form.songs[1].to_result.hints.must_equal({:title=>[]})
|
91
|
+
form.songs[1].to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
|
92
|
+
form.songs[1].to_result.messages(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
|
93
|
+
form.songs[1].to_result.hints(locale: :de).must_equal({:title=>[]})
|
94
|
+
end
|
95
|
+
|
96
|
+
it "only nested property is invalid." do
|
97
|
+
result = form.({ title: "Black Star", artist: { email: "" } })
|
98
|
+
|
99
|
+
result.success?.must_equal false
|
100
|
+
|
101
|
+
# errors.messages
|
102
|
+
form.errors.messages.must_equal({:"artist.email"=>["must be filled"], :"artist.label.location"=>["must be filled"], :"songs.title"=>["must be filled"]})
|
103
|
+
form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
|
104
|
+
form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
|
105
|
+
end
|
106
|
+
|
107
|
+
it "nested collection invalid" do
|
108
|
+
result = form.({ title: "Black Star", artist: { email: "uhm", label: { location: "Hannover" } }, songs: [ { title: "" } ] })
|
109
|
+
|
110
|
+
result.success?.must_equal false
|
111
|
+
form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
|
112
|
+
end
|
113
|
+
|
114
|
+
#---
|
115
|
+
#- validation .each
|
116
|
+
class CollectionExternalValidationsForm < TestForm
|
117
|
+
collection :songs do
|
118
|
+
property :title
|
119
|
+
end
|
120
|
+
|
121
|
+
validation do
|
122
|
+
required(:songs).each do
|
123
|
+
schema do
|
124
|
+
required(:title).filled
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it do
|
131
|
+
form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
|
132
|
+
form.validate(songs: [ { title: "Liar"}, { title: ""} ])
|
133
|
+
|
134
|
+
form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
|
135
|
+
form.songs[0].errors.messages.must_equal({})
|
136
|
+
form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class DryValidationExplicitSchemaTest < Minitest::Spec
|
141
|
+
Session = Struct.new(:name, :email)
|
142
|
+
SessionSchema = Dry::Validation.Schema do
|
143
|
+
required(:name).filled
|
144
|
+
required(:email).filled
|
145
|
+
end
|
146
|
+
|
147
|
+
class SessionForm < TestForm
|
148
|
+
include Coercion
|
149
|
+
|
150
|
+
property :name
|
151
|
+
property :email
|
152
|
+
|
153
|
+
validation schema: SessionSchema
|
154
|
+
end
|
155
|
+
|
156
|
+
let (:form) { SessionForm.new(Session.new) }
|
157
|
+
|
158
|
+
# valid.
|
159
|
+
it do
|
160
|
+
form.validate(name: "Helloween", email: "yep").must_equal true
|
161
|
+
form.errors.messages.inspect.must_equal "{}"
|
162
|
+
end
|
163
|
+
|
164
|
+
it "invalid" do
|
165
|
+
form.validate(name: "", email: "yep").must_equal false
|
166
|
+
form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"]}"
|
167
|
+
end
|
168
|
+
end
|
3
169
|
|
4
170
|
class DryValidationDefaultGroupTest < Minitest::Spec
|
5
|
-
Session = Struct.new(:username, :email, :password, :confirm_password)
|
171
|
+
Session = Struct.new(:username, :email, :password, :confirm_password, :starts_at, :active, :color)
|
6
172
|
|
7
|
-
class SessionForm <
|
8
|
-
include
|
173
|
+
class SessionForm < TestForm
|
174
|
+
include Coercion
|
9
175
|
|
10
176
|
property :username
|
11
177
|
property :email
|
12
178
|
property :password
|
13
179
|
property :confirm_password
|
180
|
+
property :starts_at, type: Types::Form::DateTime
|
181
|
+
property :active, type: Types::Form::Bool
|
182
|
+
property :color
|
14
183
|
|
15
184
|
validation do
|
16
|
-
|
17
|
-
|
185
|
+
required(:username).filled
|
186
|
+
required(:email).filled
|
187
|
+
required(:starts_at).filled(:date_time?)
|
188
|
+
required(:active).filled(:bool?)
|
189
|
+
end
|
190
|
+
|
191
|
+
validation name: :another_block do
|
192
|
+
required(:confirm_password).filled
|
193
|
+
end
|
194
|
+
|
195
|
+
validation name: :dynamic_args, with: { form: true } do
|
196
|
+
configure do
|
197
|
+
def colors
|
198
|
+
form.colors
|
199
|
+
end
|
200
|
+
end
|
201
|
+
required(:color).maybe(included_in?: colors)
|
202
|
+
end
|
203
|
+
|
204
|
+
def colors
|
205
|
+
%(red orange green)
|
18
206
|
end
|
19
207
|
end
|
20
208
|
|
@@ -23,38 +211,52 @@ class DryValidationDefaultGroupTest < Minitest::Spec
|
|
23
211
|
# valid.
|
24
212
|
it do
|
25
213
|
form.validate(username: "Helloween",
|
26
|
-
email: "yep"
|
214
|
+
email: "yep",
|
215
|
+
starts_at: "01/01/2000 - 11:00",
|
216
|
+
active: "true",
|
217
|
+
confirm_password: 'pA55w0rd').must_equal true
|
27
218
|
form.errors.messages.inspect.must_equal "{}"
|
28
219
|
end
|
220
|
+
|
221
|
+
it "invalid" do
|
222
|
+
form.validate(username: "Helloween",
|
223
|
+
email: "yep",
|
224
|
+
active: 'hello',
|
225
|
+
starts_at: "01/01/2000 - 11:00",
|
226
|
+
color: 'purple').must_equal false
|
227
|
+
form.errors.messages.inspect.must_equal "{:active=>[\"must be boolean\"], :confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
|
228
|
+
end
|
29
229
|
end
|
30
230
|
|
31
231
|
class ValidationGroupsTest < MiniTest::Spec
|
32
232
|
describe "basic validations" do
|
33
|
-
Session = Struct.new(:username, :email, :password, :confirm_password)
|
233
|
+
Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
|
234
|
+
SomeClass= Struct.new(:id)
|
34
235
|
|
35
|
-
class SessionForm <
|
36
|
-
include Reform::Form::Dry::Validations
|
236
|
+
class SessionForm < TestForm
|
37
237
|
|
38
238
|
property :username
|
39
239
|
property :email
|
40
240
|
property :password
|
41
241
|
property :confirm_password
|
242
|
+
property :special_class
|
42
243
|
|
43
|
-
validation
|
44
|
-
|
45
|
-
|
244
|
+
validation do
|
245
|
+
required(:username).filled
|
246
|
+
required(:email).filled
|
247
|
+
required(:special_class).filled(type?: SomeClass)
|
46
248
|
end
|
47
249
|
|
48
|
-
validation :email, if: :default do
|
49
|
-
|
250
|
+
validation name: :email, if: :default do
|
251
|
+
required(:email).filled(min_size?: 3)
|
50
252
|
end
|
51
253
|
|
52
|
-
validation :nested, if: :default do
|
53
|
-
|
254
|
+
validation name: :nested, if: :default do
|
255
|
+
required(:password).filled(min_size?: 2)
|
54
256
|
end
|
55
257
|
|
56
|
-
validation :confirm, if: :default, after: :email do
|
57
|
-
|
258
|
+
validation name: :confirm, if: :default, after: :email do
|
259
|
+
required(:confirm_password).filled(min_size?: 2)
|
58
260
|
end
|
59
261
|
end
|
60
262
|
|
@@ -63,6 +265,7 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
63
265
|
# valid.
|
64
266
|
it do
|
65
267
|
form.validate({ username: "Helloween",
|
268
|
+
special_class: SomeClass.new(id: 15),
|
66
269
|
email: "yep",
|
67
270
|
password: "99",
|
68
271
|
confirm_password: "99" }).must_equal true
|
@@ -72,160 +275,274 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
72
275
|
# invalid.
|
73
276
|
it do
|
74
277
|
form.validate({}).must_equal false
|
75
|
-
form.errors.messages.
|
278
|
+
form.errors.messages.must_equal({:username=>["must be filled"], :email=>["must be filled"], :special_class=>["must be filled", "must be ValidationGroupsTest::SomeClass"]})
|
76
279
|
end
|
77
280
|
|
78
281
|
# partially invalid.
|
79
282
|
# 2nd group fails.
|
80
283
|
it do
|
81
|
-
form.validate(username: "Helloween", email: "yo", confirm_password:"9").must_equal false
|
82
|
-
form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"], :password=>[\"
|
284
|
+
form.validate(username: "Helloween", email: "yo", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
|
285
|
+
form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
|
83
286
|
end
|
84
287
|
# 3rd group fails.
|
85
288
|
it do
|
86
|
-
form.validate(username: "Helloween", email: "yo!", confirm_password:"9").must_equal false
|
289
|
+
form.validate(username: "Helloween", email: "yo!", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
|
87
290
|
form.errors.messages.inspect
|
88
|
-
.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"
|
291
|
+
.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
|
89
292
|
end
|
90
293
|
# 4th group with after: fails.
|
91
294
|
it do
|
92
|
-
form.validate(username: "Helloween", email: "yo!", password: "", confirm_password: "9").must_equal false
|
93
|
-
form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"
|
295
|
+
form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
|
296
|
+
form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"size cannot be less than 2\"]}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
class ValidationWithOptionsTest < MiniTest::Spec
|
301
|
+
describe "basic validations" do
|
302
|
+
Session = Struct.new(:username)
|
303
|
+
class SessionForm < TestForm
|
304
|
+
property :username
|
305
|
+
|
306
|
+
validation name: :default, with: { user: OpenStruct.new(name: "Nick") } do
|
307
|
+
configure do
|
308
|
+
def users_name
|
309
|
+
user.name
|
310
|
+
end
|
311
|
+
end
|
312
|
+
required(:username).filled(eql?: users_name)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
let (:form) { SessionForm.new(Session.new) }
|
317
|
+
|
318
|
+
# valid.
|
319
|
+
it do
|
320
|
+
form.validate({ username: "Nick" }).must_equal true
|
321
|
+
form.errors.messages.inspect.must_equal "{}"
|
322
|
+
end
|
323
|
+
|
324
|
+
# invalid.
|
325
|
+
it do
|
326
|
+
form.validate({ username: 'Fred'}).must_equal false
|
327
|
+
form.errors.messages.inspect.must_equal "{:username=>[\"must be equal to Nick\"]}"
|
328
|
+
end
|
94
329
|
end
|
95
330
|
end
|
96
331
|
|
97
|
-
|
98
|
-
|
99
|
-
|
332
|
+
#---
|
333
|
+
#- validation( schema: MySchema )
|
334
|
+
describe "with custom schema" do
|
335
|
+
Session2 = Struct.new(:username, :email, :password)
|
336
|
+
|
337
|
+
MySchema = Dry::Validation.Schema do
|
338
|
+
configure do
|
339
|
+
config.messages_file = 'test/fixtures/dry_error_messages.yml'
|
340
|
+
|
341
|
+
def good_musical_taste?(val)
|
342
|
+
val.is_a? String
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
required(:password).filled(:min_size? => 6)
|
348
|
+
end
|
349
|
+
|
350
|
+
class Session2Form < TestForm
|
351
|
+
property :username
|
352
|
+
property :email
|
353
|
+
property :password
|
100
354
|
|
355
|
+
validation schema: MySchema do
|
356
|
+
required(:username).filled
|
357
|
+
required(:email).filled(:good_musical_taste?)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
let (:form) { Session2Form.new(Session2.new) }
|
362
|
+
|
363
|
+
# valid.
|
364
|
+
it do
|
365
|
+
form.validate({ username: "Helloween", email: "yep", password: "extrasafe" }).must_equal true
|
366
|
+
form.errors.messages.inspect.must_equal "{}"
|
367
|
+
end
|
368
|
+
|
369
|
+
# invalid.
|
370
|
+
it do
|
371
|
+
form.validate({}).must_equal false
|
372
|
+
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"]})
|
373
|
+
end
|
374
|
+
|
375
|
+
it do
|
376
|
+
form.validate({email: 1}).must_equal false
|
377
|
+
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\"]}"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
describe "MIXED nested validations" do
|
382
|
+
class AlbumForm < TestForm
|
101
383
|
property :title
|
102
384
|
|
103
385
|
property :hit do
|
104
386
|
property :title
|
105
387
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
# key(:title, &:filled?)
|
110
|
-
# end
|
388
|
+
validation do
|
389
|
+
required(:title).filled
|
390
|
+
end
|
111
391
|
end
|
112
392
|
|
113
393
|
collection :songs do
|
114
394
|
property :title
|
395
|
+
|
396
|
+
validation do
|
397
|
+
required(:title).filled
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# we test this one by running an each / schema dry-v check on the main block
|
402
|
+
collection :producers do
|
403
|
+
property :name
|
115
404
|
end
|
116
405
|
|
117
406
|
property :band do
|
118
407
|
property :name
|
119
408
|
property :label do
|
120
|
-
property :
|
409
|
+
property :location
|
121
410
|
end
|
122
411
|
end
|
123
412
|
|
124
|
-
validation
|
125
|
-
|
126
|
-
key(:title).required
|
127
|
-
|
128
|
-
key(:band).schema do
|
129
|
-
key(:name).required
|
130
|
-
key(:label).schema do
|
131
|
-
key(:name).required
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
413
|
+
validation do
|
135
414
|
configure do
|
136
|
-
|
415
|
+
config.messages_file = "test/fixtures/dry_error_messages.yml"
|
137
416
|
# message need to be defined on fixtures/dry_error_messages
|
138
417
|
# d-v expects you to define your custome messages on the .yml file
|
139
418
|
def good_musical_taste?(value)
|
140
419
|
value != 'Nickelback'
|
141
420
|
end
|
421
|
+
end
|
422
|
+
|
423
|
+
required(:title).filled(:good_musical_taste?)
|
142
424
|
|
143
|
-
|
144
|
-
|
425
|
+
required(:band).schema do
|
426
|
+
required(:name).filled
|
427
|
+
required(:label).schema do
|
428
|
+
required(:location).filled
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
required(:producers).each do
|
433
|
+
schema do
|
434
|
+
required(:name).filled
|
145
435
|
end
|
146
436
|
end
|
147
437
|
|
148
|
-
key(:title).required(:good_musical_taste?)
|
149
|
-
key(:title).required(:form_access_validation?)
|
150
438
|
end
|
151
439
|
end
|
152
440
|
|
153
441
|
let (:album) do
|
154
442
|
OpenStruct.new(
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
443
|
+
:hit => OpenStruct.new,
|
444
|
+
:songs => [OpenStruct.new, OpenStruct.new],
|
445
|
+
:band => Struct.new(:name, :label).new("", OpenStruct.new),
|
446
|
+
:producers => [OpenStruct.new, OpenStruct.new, OpenStruct.new],
|
159
447
|
)
|
160
448
|
end
|
161
|
-
|
162
|
-
let (:songs) { [ song = OpenStruct.new(:title => "Calling"), song ] }
|
449
|
+
|
163
450
|
let (:form) { AlbumForm.new(album) }
|
164
451
|
|
165
|
-
|
166
|
-
it do
|
452
|
+
it "maps errors to form objects correctly" do
|
167
453
|
result = form.validate(
|
168
|
-
"title" => "
|
169
|
-
"songs" => [
|
170
|
-
|
171
|
-
|
172
|
-
],
|
173
|
-
"band" => {"label" => {"name" => "Epitaph"}},
|
454
|
+
"title" => "Nickelback",
|
455
|
+
"songs" => [ {"title" => ""}, {"title" => ""} ],
|
456
|
+
"band" => {"size" => "", "label" => {"location" => ""}},
|
457
|
+
"producers" => [{"name" => ''}, {"name" => 'something lovely'}]
|
174
458
|
)
|
175
459
|
|
176
|
-
result.must_equal
|
177
|
-
|
178
|
-
|
179
|
-
|
460
|
+
result.must_equal false
|
461
|
+
# from nested validation
|
462
|
+
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"]})
|
463
|
+
|
464
|
+
# songs have their own validation.
|
465
|
+
form.songs[0].errors.messages.must_equal({:title=>["must be filled"]})
|
466
|
+
# hit got its own validation group.
|
467
|
+
form.hit.errors.messages.must_equal({:title=>["must be filled"]})
|
180
468
|
|
469
|
+
form.band.label.errors.messages.must_equal({:location=>["must be filled"]})
|
470
|
+
form.band.errors.messages.must_equal({:name=>["must be filled"], :"label.location"=>["must be filled"]})
|
471
|
+
form.producers[0].errors.messages.must_equal({:name=>["must be filled"]})
|
472
|
+
|
473
|
+
# TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
|
474
|
+
form.producers[0].to_result.errors.must_equal({:name=>["must be filled"]})
|
475
|
+
form.producers[0].to_result.messages.must_equal({:name=>["must be filled"]})
|
476
|
+
form.producers[0].to_result.hints.must_equal({:name=>[]})
|
477
|
+
end
|
181
478
|
|
182
|
-
|
479
|
+
# FIXME: fix the "must be filled error"
|
183
480
|
|
184
|
-
it "
|
185
|
-
|
186
|
-
|
187
|
-
|
481
|
+
it "renders full messages correctly" do
|
482
|
+
result = form.validate(
|
483
|
+
"title" => "",
|
484
|
+
"songs" => [ {"title" => ""}, {"title" => ""} ],
|
485
|
+
"band" => {"size" => "", "label" => {"name" => ""}},
|
486
|
+
"producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
|
487
|
+
)
|
188
488
|
|
189
|
-
|
489
|
+
result.must_equal false
|
490
|
+
form.band.errors.full_messages.must_equal ["Name must be filled", "Label Location must be filled"]
|
491
|
+
form.band.label.errors.full_messages.must_equal ["Location must be filled"]
|
492
|
+
form.producers.first.errors.full_messages.must_equal ["Name must be filled"]
|
493
|
+
form.errors.full_messages.must_equal ["Title must be filled", "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"]
|
494
|
+
end
|
190
495
|
|
191
|
-
|
192
|
-
|
496
|
+
describe "only 1 nested validation" do
|
497
|
+
class AlbumFormWith1NestedVal < TestForm
|
498
|
+
property :title
|
499
|
+
property :band do
|
500
|
+
property :name
|
501
|
+
property :label do
|
502
|
+
property :location
|
193
503
|
end
|
194
504
|
end
|
195
|
-
end.must_raise(NoMethodError)
|
196
|
-
# e.message.must_equal 'validates() is not supported by Dry Validation backend.'
|
197
505
|
|
198
|
-
|
199
|
-
|
200
|
-
|
506
|
+
validation do
|
507
|
+
configure do
|
508
|
+
config.messages_file = 'test/fixtures/dry_error_messages.yml'
|
509
|
+
end
|
201
510
|
|
202
|
-
|
511
|
+
required(:title).filled
|
203
512
|
|
204
|
-
|
205
|
-
|
513
|
+
required(:band).schema do
|
514
|
+
required(:name).filled
|
515
|
+
required(:label).schema do
|
516
|
+
required(:location).filled
|
517
|
+
end
|
206
518
|
end
|
207
519
|
end
|
208
|
-
end
|
209
|
-
# e.message.must_equal 'validate() is not supported by Dry Validation backend.'
|
520
|
+
end
|
210
521
|
|
211
|
-
|
212
|
-
class FailingForm < Reform::Form
|
213
|
-
include Reform::Form::Dry::Validations
|
522
|
+
let (:form) { AlbumFormWith1NestedVal.new(album) }
|
214
523
|
|
215
|
-
|
524
|
+
it "allows to access dry's result semantics per nested form" do
|
525
|
+
result = form.validate(
|
526
|
+
"title" => "",
|
527
|
+
"songs" => [ {"title" => ""}, {"title" => ""} ],
|
528
|
+
"band" => {"size" => "", "label" => {"name" => ""}},
|
529
|
+
"producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
|
530
|
+
)
|
216
531
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
532
|
+
form.to_result.errors.must_equal({:title=>["must be filled"]})
|
533
|
+
form.band.to_result.errors.must_equal({:name=>["must be filled"]})
|
534
|
+
form.band.label.to_result.errors.must_equal({:location=>["must be filled"]})
|
535
|
+
|
536
|
+
# with locale: "de"
|
537
|
+
form.to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
|
538
|
+
form.band.to_result.errors(locale: :de).must_equal({:name=>["muss abgefüllt sein"]})
|
539
|
+
form.band.label.to_result.errors(locale: :de).must_equal({:location=>["muss abgefüllt sein"]})
|
540
|
+
end
|
223
541
|
end
|
224
542
|
end
|
225
543
|
|
226
|
-
|
227
544
|
# describe "same-named group" do
|
228
|
-
# class OverwritingForm <
|
545
|
+
# class OverwritingForm < TestForm
|
229
546
|
# include Reform::Form::Dry::Validations
|
230
547
|
|
231
548
|
# property :username
|
@@ -256,18 +573,16 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
256
573
|
|
257
574
|
|
258
575
|
describe "inherit: true in same group" do
|
259
|
-
class InheritSameGroupForm <
|
260
|
-
include Reform::Form::Dry::Validations
|
261
|
-
|
576
|
+
class InheritSameGroupForm < TestForm
|
262
577
|
property :username
|
263
578
|
property :email
|
264
579
|
|
265
|
-
validation :email do
|
266
|
-
|
580
|
+
validation name: :email do
|
581
|
+
required(:email).filled
|
267
582
|
end
|
268
583
|
|
269
|
-
validation :email, inherit: true do # extends the above.
|
270
|
-
|
584
|
+
validation name: :email, inherit: true do # extends the above.
|
585
|
+
required(:username).filled
|
271
586
|
end
|
272
587
|
end
|
273
588
|
|
@@ -281,31 +596,29 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
281
596
|
# invalid.
|
282
597
|
it do
|
283
598
|
form.validate({}).must_equal false
|
284
|
-
form.errors.messages.inspect.must_equal "{:email=>[\"
|
599
|
+
form.errors.messages.inspect.must_equal "{:email=>[\"must be filled\"], :username=>[\"must be filled\"]}"
|
285
600
|
end
|
286
601
|
end
|
287
602
|
|
288
603
|
|
289
604
|
describe "if: with lambda" do
|
290
|
-
class IfWithLambdaForm <
|
291
|
-
include Reform::Form::Dry::Validations # ::build_errors.
|
292
|
-
|
605
|
+
class IfWithLambdaForm < TestForm
|
293
606
|
property :username
|
294
607
|
property :email
|
295
608
|
property :password
|
296
609
|
|
297
|
-
validation :email do
|
298
|
-
|
610
|
+
validation name: :email do
|
611
|
+
required(:email).filled
|
299
612
|
end
|
300
613
|
|
301
614
|
# run this is :email group is true.
|
302
|
-
validation :after_email, if: lambda { |results| results[:email]
|
303
|
-
|
615
|
+
validation name: :after_email, if: lambda { |results| results[:email].success? } do # extends the above.
|
616
|
+
required(:username).filled
|
304
617
|
end
|
305
618
|
|
306
619
|
# block gets evaled in form instance context.
|
307
|
-
validation :password, if: lambda { |results| email == "john@trb.org" } do
|
308
|
-
|
620
|
+
validation name: :password, if: lambda { |results| email == "john@trb.org" } do
|
621
|
+
required(:password).filled
|
309
622
|
end
|
310
623
|
end
|
311
624
|
|
@@ -319,7 +632,7 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
319
632
|
# invalid.
|
320
633
|
it do
|
321
634
|
form.validate({email: 9}).must_equal false
|
322
|
-
form.errors.messages.inspect.must_equal "{:username=>[\"
|
635
|
+
form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
|
323
636
|
end
|
324
637
|
end
|
325
638
|
|
@@ -329,7 +642,7 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
329
642
|
# more errors messages than only those that have failed.
|
330
643
|
#
|
331
644
|
# describe "multiple errors for property" do
|
332
|
-
# class MultipleErrorsForPropertyForm <
|
645
|
+
# class MultipleErrorsForPropertyForm < TestForm
|
333
646
|
# include Reform::Form::Dry::Validations
|
334
647
|
|
335
648
|
# property :username
|