reform 2.3.0.rc1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +7 -11
  4. data/CHANGES.md +43 -3
  5. data/Gemfile +2 -5
  6. data/ISSUE_TEMPLATE.md +1 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +7 -9
  9. data/Rakefile +6 -10
  10. data/lib/reform/contract.rb +7 -7
  11. data/lib/reform/contract/custom_error.rb +41 -0
  12. data/lib/reform/contract/validate.rb +10 -6
  13. data/lib/reform/errors.rb +27 -15
  14. data/lib/reform/form.rb +22 -11
  15. data/lib/reform/form/call.rb +1 -1
  16. data/lib/reform/form/composition.rb +2 -2
  17. data/lib/reform/form/dry.rb +22 -60
  18. data/lib/reform/form/dry/input_hash.rb +37 -0
  19. data/lib/reform/form/populator.rb +9 -11
  20. data/lib/reform/form/prepopulate.rb +3 -2
  21. data/lib/reform/form/validate.rb +19 -12
  22. data/lib/reform/result.rb +36 -9
  23. data/lib/reform/validation.rb +10 -8
  24. data/lib/reform/validation/groups.rb +2 -4
  25. data/lib/reform/version.rb +1 -1
  26. data/reform.gemspec +9 -9
  27. data/test/benchmarking.rb +10 -11
  28. data/test/call_test.rb +8 -8
  29. data/test/changed_test.rb +13 -13
  30. data/test/coercion_test.rb +56 -24
  31. data/test/composition_test.rb +49 -51
  32. data/test/contract/custom_error_test.rb +55 -0
  33. data/test/contract_test.rb +18 -18
  34. data/test/default_test.rb +3 -3
  35. data/test/deserialize_test.rb +14 -17
  36. data/test/docs/validation_test.rb +134 -0
  37. data/test/errors_test.rb +131 -86
  38. data/test/feature_test.rb +9 -11
  39. data/test/fixtures/dry_error_messages.yml +65 -52
  40. data/test/form_option_test.rb +3 -3
  41. data/test/form_test.rb +6 -6
  42. data/test/from_test.rb +17 -21
  43. data/test/inherit_test.rb +28 -35
  44. data/test/module_test.rb +23 -28
  45. data/test/parse_option_test.rb +12 -12
  46. data/test/parse_pipeline_test.rb +3 -3
  47. data/test/populate_test.rb +146 -93
  48. data/test/populator_skip_test.rb +3 -4
  49. data/test/prepopulator_test.rb +20 -21
  50. data/test/read_only_test.rb +12 -1
  51. data/test/readable_test.rb +7 -7
  52. data/test/reform_test.rb +38 -42
  53. data/test/save_test.rb +16 -19
  54. data/test/setup_test.rb +15 -15
  55. data/test/skip_if_test.rb +30 -19
  56. data/test/skip_setter_and_getter_test.rb +8 -9
  57. data/test/test_helper.rb +12 -5
  58. data/test/validate_test.rb +160 -140
  59. data/test/validation/dry_validation_test.rb +407 -236
  60. data/test/validation/result_test.rb +29 -31
  61. data/test/validation_library_provided_test.rb +3 -3
  62. data/test/virtual_test.rb +46 -6
  63. data/test/writeable_test.rb +13 -13
  64. metadata +32 -29
  65. data/test/readonly_test.rb +0 -14
@@ -0,0 +1,55 @@
1
+ require "test_helper"
2
+
3
+ class CustomerErrorTest < MiniTest::Spec
4
+ let(:key) { :name }
5
+ let(:error_text) { "text2" }
6
+ let(:starting_error) { [OpenStruct.new(errors: {title: ["text1"]})] }
7
+
8
+ let(:custom_error) { Reform::Contract::CustomError.new(key, error_text, @results) }
9
+
10
+ before { @results = starting_error }
11
+
12
+ it "base class structure" do
13
+ assert_equal custom_error.success?, false
14
+ assert_equal custom_error.failure?, true
15
+ assert_equal custom_error.errors, key => [error_text]
16
+ assert_equal custom_error.messages, key => [error_text]
17
+ assert_equal custom_error.hint, {}
18
+ end
19
+
20
+ describe "updates @results accordingly" do
21
+ it "add new key" do
22
+ custom_error
23
+
24
+ assert_equal @results.size, 2
25
+ errors = @results.map(&:errors)
26
+
27
+ assert_equal errors[0], starting_error.first.errors
28
+ assert_equal errors[1], custom_error.errors
29
+ end
30
+
31
+ describe "when key error already exists in @results" do
32
+ let(:key) { :title }
33
+
34
+ it "merge errors text" do
35
+ custom_error
36
+
37
+ assert_equal @results.size, 1
38
+
39
+ assert_equal @results.first.errors.values, [%w[text1 text2]]
40
+ end
41
+
42
+ describe "add error text is already" do
43
+ let(:error_text) { "text1" }
44
+
45
+ it 'does not create duplicates' do
46
+ custom_error
47
+
48
+ assert_equal @results.size, 1
49
+
50
+ assert_equal @results.first.errors.values, [%w[text1]]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class ContractTest < MiniTest::Spec
4
4
  Song = Struct.new(:title, :album, :composer)
@@ -16,19 +16,19 @@ class ContractTest < MiniTest::Spec
16
16
  properties :year, :style, readable: false
17
17
 
18
18
  validation do
19
- required(:name).filled
19
+ params { required(:name).filled }
20
20
  end
21
21
 
22
22
  collection :songs do
23
23
  property :title
24
24
  validation do
25
- required(:title).filled
25
+ params { required(:title).filled }
26
26
  end
27
27
 
28
28
  property :composer do
29
29
  property :name
30
30
  validation do
31
- required(:name).filled
31
+ params { required(:name).filled }
32
32
  end
33
33
  end
34
34
  end
@@ -36,42 +36,42 @@ class ContractTest < MiniTest::Spec
36
36
  property :artist, form: ArtistForm
37
37
  end
38
38
 
39
- let (:song) { Song.new("Broken") }
40
- let (:song_with_composer) { Song.new("Resist Stance", nil, composer) }
41
- let (:composer) { Artist.new("Greg Graffin") }
42
- let (:artist) { Artist.new("Bad Religion") }
43
- let (:album) { Album.new("The Dissent Of Man", 123, [song, song_with_composer], artist) }
39
+ let(:song) { Song.new("Broken") }
40
+ let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
41
+ let(:composer) { Artist.new("Greg Graffin") }
42
+ let(:artist) { Artist.new("Bad Religion") }
43
+ let(:album) { Album.new("The Dissent Of Man", 123, [song, song_with_composer], artist) }
44
44
 
45
- let (:form) { AlbumForm.new(album) }
45
+ let(:form) { AlbumForm.new(album) }
46
46
 
47
47
  # accept `property form: SongForm`.
48
48
  it do
49
- form.artist.must_be_instance_of ArtistForm
49
+ assert form.artist.is_a? ArtistForm
50
50
  end
51
51
 
52
52
  describe ".properties" do
53
53
  it "defines a property when called with one argument" do
54
- form.must_respond_to :duration
54
+ assert_respond_to form, :duration
55
55
  end
56
56
 
57
57
  it "defines several properties when called with multiple arguments" do
58
- form.must_respond_to :year
59
- form.must_respond_to :style
58
+ assert_respond_to form, :year
59
+ assert_respond_to form, :style
60
60
  end
61
61
 
62
62
  it "passes options to each property when options are provided" do
63
63
  readable = AlbumForm.new(album).options_for(:style)[:readable]
64
- readable.must_equal false
64
+ assert_equal readable, false
65
65
  end
66
66
 
67
67
  it "returns the list of defined properties" do
68
68
  returned_value = AlbumForm.properties(:hello, :world, virtual: true)
69
- returned_value.must_equal [:hello, :world]
69
+ assert_equal returned_value, %i[hello world]
70
70
  end
71
71
  end
72
72
 
73
73
  describe "#options_for" do
74
- it { AlbumForm.options_for(:name).extend(Declarative::Inspect).inspect.must_equal "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
75
- it { AlbumForm.new(album).options_for(:name).extend(Declarative::Inspect).inspect.must_equal "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
74
+ it { assert_equal AlbumForm.options_for(:name).extend(Declarative::Inspect).inspect, "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
75
+ it { assert_equal AlbumForm.new(album).options_for(:name).extend(Declarative::Inspect).inspect, "#<Disposable::Twin::Definition: @options={:private_name=>:name, :name=>\"name\"}>" }
76
76
  end
77
77
  end
data/test/default_test.rb CHANGED
@@ -16,7 +16,7 @@ class DefaultTest < Minitest::Spec
16
16
  it do
17
17
  form = AlbumForm.new(Album.new(nil, [Song.new]))
18
18
 
19
- form.name.must_equal "Wrong"
20
- form.songs[0].title.must_equal "It's Catching Up"
19
+ assert_equal form.name, "Wrong"
20
+ assert_equal form.songs[0].title, "It's Catching Up"
21
21
  end
22
- end
22
+ end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
  require "representable/json"
3
3
 
4
4
  class DeserializeTest < MiniTest::Spec
@@ -10,7 +10,7 @@ class DeserializeTest < MiniTest::Spec
10
10
  module Json
11
11
  def deserialize(params)
12
12
  deserializer.new(self).
13
- # extend(Representable::Debug).
13
+ # extend(Representable::Debug).
14
14
  from_json(params)
15
15
  end
16
16
 
@@ -18,7 +18,7 @@ class DeserializeTest < MiniTest::Spec
18
18
  Disposable::Rescheme.from(self.class,
19
19
  include: [Representable::JSON],
20
20
  superclass: Representable::Decorator,
21
- definitions_from: lambda { |inline| inline.definitions },
21
+ definitions_from: ->(inline) { inline.definitions },
22
22
  options_from: :deserializer,
23
23
  exclude_options: [:populator]
24
24
  )
@@ -26,15 +26,13 @@ class DeserializeTest < MiniTest::Spec
26
26
  end
27
27
  include Json
28
28
 
29
-
30
29
  property :title
31
30
  property :artist, populate_if_empty: Artist do
32
31
  property :name
33
32
  end
34
33
  end
35
34
 
36
-
37
- let (:artist) { Artist.new("A-ha") }
35
+ let(:artist) { Artist.new("A-ha") }
38
36
  it do
39
37
  artist_id = artist.object_id
40
38
 
@@ -43,9 +41,9 @@ class DeserializeTest < MiniTest::Spec
43
41
 
44
42
  form.validate(json)
45
43
 
46
- form.title.must_equal "Apocalypse Soon"
47
- form.artist.name.must_equal "Mute"
48
- form.artist.model.object_id.must_equal artist_id
44
+ assert_equal form.title, "Apocalypse Soon"
45
+ assert_equal form.artist.name, "Mute"
46
+ assert_equal form.artist.model.object_id, artist_id
49
47
  end
50
48
 
51
49
  describe "infering the deserializer from another form should NOT copy its populators" do
@@ -62,14 +60,13 @@ class DeserializeTest < MiniTest::Spec
62
60
  # also tests the Form#deserializer API. # FIXME.
63
61
  it "uses deserializer inferred from JsonAlbumForm but deserializes/populates to CompilationForm" do
64
62
  form = CompilationForm.new(Album.new)
65
- form.validate("artist"=> {"name" => "Horowitz"}) # the deserializer doesn't know symbols.
63
+ form.validate("artist" => {"name" => "Horowitz"}) # the deserializer doesn't know symbols.
66
64
  form.sync
67
- form.artist.model.must_equal Artist.new("Horowitz", %{{"name"=>"Horowitz"}})
65
+ assert_equal form.artist.model, Artist.new("Horowitz", %{{"name"=>"Horowitz"}})
68
66
  end
69
67
  end
70
68
  end
71
69
 
72
-
73
70
  class ValidateWithBlockTest < MiniTest::Spec
74
71
  Song = Struct.new(:title, :album, :composer)
75
72
  Album = Struct.new(:title, :artist)
@@ -90,15 +87,15 @@ class ValidateWithBlockTest < MiniTest::Spec
90
87
  deserializer = Disposable::Rescheme.from(AlbumForm,
91
88
  include: [Representable::JSON],
92
89
  superclass: Representable::Decorator,
93
- definitions_from: lambda { |inline| inline.definitions },
90
+ definitions_from: ->(inline) { inline.definitions },
94
91
  options_from: :deserializer
95
92
  )
96
93
 
97
- form.validate(json) do |params|
94
+ assert form.validate(json) { |params|
98
95
  deserializer.new(form).from_json(params)
99
- end.must_equal true # with block must return result, too.
96
+ } # with block must return result, too.
100
97
 
101
- form.title.must_equal "Apocalypse Soon"
102
- form.artist.name.must_equal "Mute"
98
+ assert_equal form.title, "Apocalypse Soon"
99
+ assert_equal form.artist.name, "Mute"
103
100
  end
104
101
  end
@@ -0,0 +1,134 @@
1
+ require 'test_helper'
2
+ require 'reform/form/dry'
3
+
4
+ class DocsDryVTest < Minitest::Spec
5
+ #:basic
6
+ class AlbumForm < Reform::Form
7
+ feature Reform::Form::Dry
8
+
9
+ property :name
10
+
11
+ validation do
12
+ params do
13
+ required(:name).filled
14
+ end
15
+ end
16
+ end
17
+ #:basic end
18
+
19
+ it 'validates correctly' do
20
+ form = DocsDryVTest::AlbumForm.new(Album.new(nil, nil, nil))
21
+ result = form.call(name: nil)
22
+
23
+ refute result.success?
24
+ assert_equal({ name: ['must be filled'] }, form.errors.messages)
25
+ end
26
+ end
27
+
28
+ class DocsDryVWithRulesTest < Minitest::Spec
29
+ #:basic_with_rules
30
+ class AlbumForm < Reform::Form
31
+ feature Reform::Form::Dry
32
+
33
+ property :name
34
+
35
+ validation name: :default do
36
+ option :form
37
+
38
+ params do
39
+ required(:name).filled
40
+ end
41
+
42
+ rule(:name) do
43
+ key.failure('must be unique') if Album.where.not(id: form.model.id).where(name: value).exists?
44
+ end
45
+ end
46
+ end
47
+ #:basic_with_rules end
48
+
49
+ it 'validates correctly' do
50
+ Album = Struct.new(:name, :songs, :artist, :user)
51
+ form = DocsDryVWithRulesTest::AlbumForm.new(Album.new(nil, nil, nil, nil))
52
+ result = form.call(name: nil)
53
+
54
+ refute result.success?
55
+ assert_equal({ name: ['must be filled'] }, form.errors.messages)
56
+ end
57
+ end
58
+
59
+ class DryVWithNestedTest < Minitest::Spec
60
+ #:nested
61
+ class AlbumForm < Reform::Form
62
+ feature Reform::Form::Dry
63
+
64
+ property :name
65
+
66
+ validation do
67
+ params { required(:name).filled }
68
+ end
69
+
70
+ property :artist do
71
+ property :name
72
+
73
+ validation do
74
+ params { required(:name).filled }
75
+ end
76
+ end
77
+ end
78
+ #:nested end
79
+
80
+ it 'validates correctly' do
81
+ form = DryVWithNestedTest::AlbumForm.new(Album.new(nil, nil, Artist.new(nil)))
82
+ result = form.call(name: nil, artist: { name: '' })
83
+
84
+ refute result.success?
85
+ assert_equal({ name: ['must be filled'], 'artist.name': ['must be filled'] }, form.errors.messages)
86
+ end
87
+ end
88
+
89
+ class DryVValGroupTest < Minitest::Spec
90
+ class AlbumForm < Reform::Form
91
+ feature Reform::Form::Dry
92
+
93
+ property :name
94
+ property :artist
95
+ #:validation_groups
96
+ validation name: :default do
97
+ params { required(:name).filled }
98
+ end
99
+
100
+ validation name: :artist, if: :default do
101
+ params { required(:artist).filled }
102
+ end
103
+
104
+ validation name: :famous, after: :default do
105
+ params { optional(:artist) }
106
+
107
+ rule(:artist) do
108
+ if value
109
+ key.failure('only famous artist') unless value =~ /famous/
110
+ end
111
+ end
112
+ end
113
+ #:validation_groups end
114
+ end
115
+
116
+ it 'validates correctly' do
117
+ form = DryVValGroupTest::AlbumForm.new(Album.new(nil, nil, nil))
118
+ result = form.call(name: nil)
119
+
120
+ refute result.success?
121
+ assert_equal({ name: ['must be filled'] }, result.errors.messages)
122
+
123
+ result = form.call(name: 'Title')
124
+ refute result.success?
125
+ assert_equal({ artist: ['must be filled'] }, result.errors.messages)
126
+
127
+ result = form.call(name: 'Title', artist: 'Artist')
128
+ refute result.success?
129
+ assert_equal({ artist: ['only famous artist'] }, result.errors.messages)
130
+
131
+ result = form.call(name: 'Title', artist: 'Artist famous')
132
+ assert result.success?
133
+ end
134
+ end
data/test/errors_test.rb CHANGED
@@ -1,24 +1,28 @@
1
1
  require "test_helper"
2
2
 
3
- # TODO:
4
- # This test should, at some point soon, only test the `Errors` object and its
5
- # Rails-ish API. No validation specifics, etc. to be tested here.
6
-
7
3
  class ErrorsTest < MiniTest::Spec
8
4
  class AlbumForm < TestForm
9
5
  property :title
6
+ validation do
7
+ params { required(:title).filled }
8
+ end
9
+
10
+ property :artists, default: []
11
+ property :producer do
12
+ property :name
13
+ end
10
14
 
11
15
  property :hit do
12
16
  property :title
13
17
  validation do
14
- required(:title).filled
18
+ params { required(:title).filled }
15
19
  end
16
20
  end
17
21
 
18
22
  collection :songs do
19
23
  property :title
20
24
  validation do
21
- required(:title).filled
25
+ params { required(:title).filled }
22
26
  end
23
27
  end
24
28
 
@@ -27,154 +31,195 @@ class ErrorsTest < MiniTest::Spec
27
31
  property :label do
28
32
  property :name
29
33
  validation do
30
- required(:name).filled
34
+ params { required(:name).filled }
31
35
  end
32
36
  end
33
37
  # TODO: make band a required object.
34
38
 
35
39
  validation do
36
- configure do
37
- config.messages_file = "test/fixtures/dry_error_messages.yml"
40
+ config.messages.load_paths << "test/fixtures/dry_error_messages.yml"
38
41
 
39
- def good_musical_taste?(value)
40
- value != "Nickelback"
41
- end
42
- end
42
+ params { required(:name).filled }
43
43
 
44
- required(:name).filled(:good_musical_taste?)
44
+ rule(:name) { key.failure(:good_musical_taste?) if value == "Nickelback" }
45
45
  end
46
46
  end
47
47
 
48
48
  validation do
49
- required(:title).filled
49
+ params do
50
+ required(:title).filled
51
+ required(:artists).each(:str?)
52
+ required(:producer).hash do
53
+ required(:name).filled
54
+ end
55
+ end
50
56
  end
51
57
  end
52
58
 
53
- let (:album) do
59
+ let(:album_title) { "Blackhawks Over Los Angeles" }
60
+ let(:album) do
54
61
  OpenStruct.new(
55
- :title => "Blackhawks Over Los Angeles",
56
- :hit => song,
57
- :songs => songs, # TODO: document this requirement,
58
- :band => Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
62
+ title: album_title,
63
+ hit: song,
64
+ songs: songs, # TODO: document this requirement,
65
+ band: Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
66
+ producer: Struct.new(:name).new("Sun Records")
59
67
  )
60
68
  end
61
- let (:song) { OpenStruct.new(:title => "Downtown") }
62
- let (:songs) { [song=OpenStruct.new(:title => "Calling"), song] }
63
- let (:form) { AlbumForm.new(album) }
69
+ let(:song) { OpenStruct.new(title: "Downtown") }
70
+ let(:songs) { [song = OpenStruct.new(title: "Calling"), song] }
71
+ let(:form) { AlbumForm.new(album) }
64
72
 
73
+ describe "#validate with invalid array property" do
74
+ it do
75
+ refute form.validate(
76
+ title: "Swimming Pool - EP",
77
+ band: {
78
+ name: "Marie Madeleine",
79
+ label: {name: "Ekler'o'shocK"}
80
+ },
81
+ artists: [42, "Good Charlotte", 43]
82
+ )
83
+ assert_equal form.errors.messages, artists: {0 => ["must be a string"], 2 => ["must be a string"]}
84
+ assert_equal form.errors.size, 1
85
+ end
86
+ end
65
87
 
66
88
  describe "#errors without #validate" do
67
89
  it do
68
- form.errors.size.must_equal 0
90
+ assert_equal form.errors.size, 0
69
91
  end
70
92
  end
71
93
 
72
94
  describe "blank everywhere" do
73
- before { form.validate(
74
- "hit" =>{"title" => ""},
75
- "title" => "",
76
- "songs" => [{"title" => ""}, {"title" => ""}]) } # FIXME: what happens if item must be filled?
77
-
78
- it do
79
- form.errors.messages.must_equal({
80
- :title => ["must be filled"],
81
- :"hit.title"=>["must be filled"],
82
- :"songs.title"=>["must be filled"],
83
- :"band.label.name"=>["must be filled"]
84
- })
95
+ before do
96
+ form.validate(
97
+ "hit" => {"title" => ""},
98
+ "title" => "",
99
+ "songs" => [{"title" => ""}, {"title" => ""}],
100
+ "producer" => {"name" => ""}
101
+ )
85
102
  end
86
103
 
87
104
  it do
88
- #form.errors.must_equal({:title => ["must be filled"]})
89
- # TODO: this should only contain local errors?
105
+ assert_equal form.errors.messages,{
106
+ title: ["must be filled"],
107
+ "hit.title": ["must be filled"],
108
+ "songs.title": ["must be filled"],
109
+ "band.label.name": ["must be filled"],
110
+ "producer.name": ["must be filled"]
111
+ }
90
112
  end
91
113
 
114
+ # it do
115
+ # form.errors.must_equal({:title => ["must be filled"]})
116
+ # TODO: this should only contain local errors?
117
+ # end
118
+
92
119
  # nested forms keep their own Errors:
93
- it { form.hit.errors.messages.must_equal({:title=>["must be filled"]}) }
94
- it { form.songs[0].errors.messages.must_equal({:title=>["must be filled"]}) }
120
+ it { assert_equal form.producer.errors.messages, name: ["must be filled"] }
121
+ it { assert_equal form.hit.errors.messages, title: ["must be filled"] }
122
+ it { assert_equal form.songs[0].errors.messages, title: ["must be filled"] }
95
123
 
96
124
  it do
97
- form.errors.messages.must_equal({
98
- :title => ["must be filled"],
99
- :"hit.title" => ["must be filled"],
100
- :"songs.title"=> ["must be filled"],
101
- :"band.label.name"=>["must be filled"]
102
- })
103
- form.errors.size.must_equal(4)
125
+ assert_equal form.errors.messages, {
126
+ title: ["must be filled"],
127
+ "hit.title": ["must be filled"],
128
+ "songs.title": ["must be filled"],
129
+ "band.label.name": ["must be filled"],
130
+ "producer.name": ["must be filled"]
131
+ }
132
+ assert_equal form.errors.size, 5
104
133
  end
105
134
  end
106
135
 
107
-
108
136
  describe "#validate with main form invalid" do
109
137
  it do
110
- form.validate("title"=>"", "band"=>{"label"=>{:name => "Fat Wreck"}}).must_equal false
111
- form.errors.messages.must_equal({:title=>["must be filled"]})
112
- form.errors.size.must_equal(1)
138
+ refute form.validate("title" => "", "band" => {"label" => {name: "Fat Wreck"}}, "producer" => nil)
139
+ assert_equal form.errors.messages, title: ["must be filled"], producer: ["must be a hash"]
140
+ assert_equal form.errors.size, 2
113
141
  end
114
142
  end
115
143
 
116
-
117
144
  describe "#validate with middle nested form invalid" do
118
- before { @result = form.validate("hit"=>{"title" => ""}, "band"=>{"label"=>{:name => "Fat Wreck"}}) }
145
+ before { @result = form.validate("hit" => {"title" => ""}, "band" => {"label" => {name: "Fat Wreck"}}) }
119
146
 
120
- it { @result.must_equal false }
121
- it { form.errors.messages.must_equal({:"hit.title"=>["must be filled"]}) }
122
- it { form.errors.size.must_equal(1) }
147
+ it { refute @result }
148
+ it { assert_equal form.errors.messages, "hit.title": ["must be filled"] }
149
+ it { assert_equal form.errors.size, 1 }
123
150
  end
124
151
 
125
-
126
152
  describe "#validate with collection form invalid" do
127
- before { @result = form.validate("songs"=>[{"title" => ""}], "band"=>{"label"=>{:name => "Fat Wreck"}}) }
153
+ before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {name: "Fat Wreck"}}) }
128
154
 
129
- it { @result.must_equal false }
130
- it { form.errors.messages.must_equal({:"songs.title"=>["must be filled"]}) }
131
- it { form.errors.size.must_equal(1) }
155
+ it { refute @result }
156
+ it { assert_equal form.errors.messages, "songs.title": ["must be filled"] }
157
+ it { assert_equal form.errors.size, 1 }
132
158
  end
133
159
 
134
-
135
160
  describe "#validate with collection and 2-level-nested invalid" do
136
- before { @result = form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
161
+ before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
137
162
 
138
- it { @result.must_equal false }
139
- it { form.errors.messages.must_equal({:"songs.title"=>["must be filled"], :"band.label.name"=>["must be filled"]}) }
140
- it { form.errors.size.must_equal(2) }
163
+ it { refute @result }
164
+ it { assert_equal form.errors.messages, "songs.title": ["must be filled"], "band.label.name": ["must be filled"] }
165
+ it { assert_equal form.errors.size, 2 }
141
166
  end
142
167
 
143
168
  describe "#validate with nested form using :base invalid" do
144
169
  it do
145
- result = form.validate("songs"=>[{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
146
- result.must_equal false
147
- form.errors.messages.must_equal({:"band.name"=>["you're a bad person"]})
148
- form.errors.size.must_equal(1)
170
+ result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
171
+ refute result
172
+ assert_equal form.errors.messages, "band.name": ["you're a bad person"]
173
+ assert_equal form.errors.size, 1
174
+ end
175
+ end
176
+
177
+ describe "#add" do
178
+ let(:album_title) { nil }
179
+ it do
180
+ result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
181
+ refute result
182
+ assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"]
183
+ # add a new custom error
184
+ form.errors.add(:policy, "error_text")
185
+ assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"]
186
+ # does not duplicate errors
187
+ form.errors.add(:title, "must be filled")
188
+ assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text"]
189
+ # merge existing errors
190
+ form.errors.add(:policy, "another error")
191
+ assert_equal form.errors.messages, title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text", "another error"]
149
192
  end
150
193
  end
151
194
 
152
195
  describe "correct #validate" do
153
- before { @result = form.validate(
154
- "hit" => {"title" => "Sacrifice"},
155
- "title" => "Second Heat",
156
- "songs" => [{"title"=>"Heart Of A Lion"}],
157
- "band" => {"label"=>{:name => "Fat Wreck"}}
158
- ) }
159
-
160
- it { @result.must_equal true }
161
- it { form.hit.title.must_equal "Sacrifice" }
162
- it { form.title.must_equal "Second Heat" }
163
- it { form.songs.first.title.must_equal "Heart Of A Lion" }
196
+ before do
197
+ @result = form.validate(
198
+ "hit" => {"title" => "Sacrifice"},
199
+ "title" => "Second Heat",
200
+ "songs" => [{"title" => "Heart Of A Lion"}],
201
+ "band" => {"label" => {name: "Fat Wreck"}}
202
+ )
203
+ end
204
+
205
+ it { assert @result }
206
+ it { assert_equal form.hit.title, "Sacrifice" }
207
+ it { assert_equal form.title, "Second Heat" }
208
+ it { assert_equal form.songs.first.title, "Heart Of A Lion" }
164
209
  it do
165
210
  skip "WE DON'T NEED COUNT AND EMPTY? ON THE CORE ERRORS OBJECT"
166
- form.errors.size.must_equal(0)
167
- form.errors.empty?.must_equal(true)
211
+ assert_equal form.errors.size, 0
212
+ assert form.errors.empty
168
213
  end
169
214
  end
170
215
 
171
-
172
216
  describe "Errors#to_s" do
173
- before { form.validate("songs"=>[{"title" => ""}], "band" => {"label" => {}}) }
217
+ before { form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
174
218
 
175
219
  # to_s is aliased to messages
176
220
  it {
177
221
  skip "why do we need Errors#to_s ?"
178
- form.errors.to_s.must_equal "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}" }
222
+ assert_equal form.errors.to_s, "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}"
223
+ }
179
224
  end
180
225
  end