reform 2.3.0.rc1 → 2.3.0.rc2

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +26 -11
  6. data/CHANGES.md +25 -2
  7. data/Gemfile +6 -3
  8. data/ISSUE_TEMPLATE.md +1 -1
  9. data/README.md +2 -4
  10. data/Rakefile +18 -9
  11. data/lib/reform/contract.rb +7 -7
  12. data/lib/reform/contract/custom_error.rb +41 -0
  13. data/lib/reform/contract/validate.rb +9 -5
  14. data/lib/reform/errors.rb +27 -15
  15. data/lib/reform/form.rb +22 -11
  16. data/lib/reform/form/call.rb +1 -1
  17. data/lib/reform/form/composition.rb +2 -2
  18. data/lib/reform/form/dry.rb +10 -86
  19. data/lib/reform/form/dry/input_hash.rb +37 -0
  20. data/lib/reform/form/dry/new_api.rb +58 -0
  21. data/lib/reform/form/dry/old_api.rb +61 -0
  22. data/lib/reform/form/populator.rb +9 -11
  23. data/lib/reform/form/prepopulate.rb +3 -2
  24. data/lib/reform/form/validate.rb +19 -12
  25. data/lib/reform/result.rb +36 -9
  26. data/lib/reform/validation.rb +10 -8
  27. data/lib/reform/validation/groups.rb +2 -3
  28. data/lib/reform/version.rb +1 -1
  29. data/reform.gemspec +10 -9
  30. data/test/benchmarking.rb +10 -11
  31. data/test/call_new_api.rb +23 -0
  32. data/test/{call_test.rb → call_old_api.rb} +3 -3
  33. data/test/changed_test.rb +7 -7
  34. data/test/coercion_test.rb +50 -18
  35. data/test/composition_new_api.rb +186 -0
  36. data/test/{composition_test.rb → composition_old_api.rb} +23 -26
  37. data/test/contract/custom_error_test.rb +55 -0
  38. data/test/contract_new_api.rb +77 -0
  39. data/test/{contract_test.rb → contract_old_api.rb} +8 -8
  40. data/test/default_test.rb +1 -1
  41. data/test/deserialize_test.rb +8 -11
  42. data/test/errors_new_api.rb +225 -0
  43. data/test/errors_old_api.rb +230 -0
  44. data/test/feature_test.rb +7 -9
  45. data/test/fixtures/dry_error_messages.yml +5 -2
  46. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  47. data/test/form_new_api.rb +57 -0
  48. data/test/{form_test.rb → form_old_api.rb} +2 -2
  49. data/test/form_option_new_api.rb +24 -0
  50. data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
  51. data/test/from_test.rb +8 -12
  52. data/test/inherit_new_api.rb +105 -0
  53. data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
  54. data/test/module_new_api.rb +137 -0
  55. data/test/{module_test.rb → module_old_api.rb} +19 -15
  56. data/test/parse_option_test.rb +5 -5
  57. data/test/parse_pipeline_test.rb +2 -2
  58. data/test/populate_new_api.rb +304 -0
  59. data/test/{populate_test.rb → populate_old_api.rb} +28 -34
  60. data/test/populator_skip_test.rb +1 -2
  61. data/test/prepopulator_test.rb +5 -6
  62. data/test/read_only_test.rb +12 -1
  63. data/test/readable_test.rb +5 -5
  64. data/test/reform_new_api.rb +204 -0
  65. data/test/{reform_test.rb → reform_old_api.rb} +17 -23
  66. data/test/save_new_api.rb +101 -0
  67. data/test/{save_test.rb → save_old_api.rb} +10 -13
  68. data/test/setup_test.rb +6 -6
  69. data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
  70. data/test/skip_if_old_api.rb +92 -0
  71. data/test/skip_setter_and_getter_test.rb +2 -3
  72. data/test/test_helper.rb +13 -5
  73. data/test/validate_new_api.rb +408 -0
  74. data/test/{validate_test.rb → validate_old_api.rb} +43 -53
  75. data/test/validation/dry_validation_new_api.rb +826 -0
  76. data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
  77. data/test/validation/result_test.rb +20 -22
  78. data/test/validation_library_provided_test.rb +3 -3
  79. data/test/virtual_test.rb +46 -6
  80. data/test/writeable_test.rb +7 -7
  81. metadata +101 -51
  82. data/test/errors_test.rb +0 -180
  83. data/test/readonly_test.rb +0 -14
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require "test_helper"
2
2
 
3
3
  class FormCompositionInheritanceTest < MiniTest::Spec
4
4
  module SizePrice
@@ -26,10 +26,9 @@ class FormCompositionInheritanceTest < MiniTest::Spec
26
26
  property :size, inherit: true, on: :measurement
27
27
  end
28
28
 
29
-
30
- let (:measurement) { Measurement.new(:l) }
31
- let (:tshirt) { Tshirt.new(2, :m) }
32
- let (:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
29
+ let(:measurement) { Measurement.new(:l) }
30
+ let(:tshirt) { Tshirt.new(2, :m) }
31
+ let(:form) { OutfitForm.new(tshirt: tshirt, measurement: measurement) }
33
32
 
34
33
  Tshirt = Struct.new(:price, :size)
35
34
  Measurement = Struct.new(:size)
@@ -46,28 +45,28 @@ class FormCompositionTest < MiniTest::Spec
46
45
  class RequestForm < TestForm
47
46
  include Composition
48
47
 
49
- property :name, :on => :requester
50
- property :requester_id, :on => :requester, :from => :id
51
- properties :title, :id, :on => :song
48
+ property :name, on: :requester
49
+ property :requester_id, on: :requester, from: :id
50
+ properties :title, :id, on: :song
52
51
  # property :channel # FIXME: what about the "main model"?
53
- property :channel, :virtual => true, :on => :song
54
- property :requester, :on => :requester
55
- property :captcha, :on => :song, :virtual => true
52
+ property :channel, virtual: true, on: :song
53
+ property :requester, on: :requester
54
+ property :captcha, on: :song, virtual: true
56
55
 
57
56
  validation do
58
57
  required(:name).filled
59
58
  required(:title).filled
60
59
  end
61
60
 
62
- property :band, :on => :song do
61
+ property :band, on: :song do
63
62
  property :title
64
63
  end
65
64
  end
66
65
 
67
- let (:form) { RequestForm.new(:song => song, :requester => requester) }
68
- let (:song) { Song.new(1, "Rio", band) }
69
- let (:requester) { Requester.new(2, "Duran Duran", "MCP") }
70
- let (:band) { Band.new("Duran^2") }
66
+ let(:form) { RequestForm.new(song: song, requester: requester) }
67
+ let(:song) { Song.new(1, "Rio", band) }
68
+ let(:requester) { Requester.new(2, "Duran Duran", "MCP") }
69
+ let(:band) { Band.new("Duran^2") }
71
70
 
72
71
  # delegation form -> composition works
73
72
  it { form.id.must_equal 1 }
@@ -85,7 +84,6 @@ class FormCompositionTest < MiniTest::Spec
85
84
  it { form.model[:requester].must_equal requester }
86
85
  it { form.model[:song].must_equal song }
87
86
 
88
-
89
87
  it "creates Composition for you" do
90
88
  form.validate("title" => "Greyhound", "name" => "Frenzal Rhomb").must_equal true
91
89
  form.validate("title" => "", "name" => "Frenzal Rhomb").must_equal false
@@ -101,7 +99,7 @@ class FormCompositionTest < MiniTest::Spec
101
99
  hash[:title] = form.title
102
100
  end
103
101
 
104
- hash.must_equal({:name=>"Duran Duran", :title=>"Rio"})
102
+ hash.must_equal({name: "Duran Duran", title: "Rio"})
105
103
  end
106
104
 
107
105
  it "provides nested symbolized hash as second block argument" do
@@ -114,9 +112,9 @@ class FormCompositionTest < MiniTest::Spec
114
112
  end
115
113
 
116
114
  hash.must_equal({
117
- :song=>{"title"=>"Greyhound", "id"=>1, "channel" => "JJJ", "captcha"=>"wonderful", "band"=>{"title"=>"Duran^2"}},
118
- :requester=>{"name"=>"Frenzal Rhomb", "id"=>2, "requester" => "MCP"}
119
- }
115
+ song: {"title" => "Greyhound", "id" => 1, "channel" => "JJJ", "captcha" => "wonderful", "band" => {"title" => "Duran^2"}},
116
+ requester: {"name" => "Frenzal Rhomb", "id" => 2, "requester" => "MCP"}
117
+ }
120
118
  )
121
119
  end
122
120
 
@@ -162,12 +160,11 @@ class FormCompositionTest < MiniTest::Spec
162
160
  end
163
161
  end
164
162
 
165
-
166
163
  class FormCompositionCollectionTest < MiniTest::Spec
167
164
  Book = Struct.new(:id, :name)
168
165
  Library = Struct.new(:id) do
169
166
  def books
170
- [Book.new(1,"My book")]
167
+ [Book.new(1, "My book")]
171
168
  end
172
169
  end
173
170
 
@@ -180,8 +177,8 @@ class FormCompositionCollectionTest < MiniTest::Spec
180
177
  end
181
178
  end
182
179
 
183
- let (:form) { LibraryForm.new(library: library) }
184
- let (:library) { Library.new(2) }
180
+ let(:form) { LibraryForm.new(library: library) }
181
+ let(:library) { Library.new(2) }
185
182
 
186
- it { form.save do |hash| hash.must_equal({:library=>{"books"=>[{"id"=>1, "name"=>"My book"}]}}) end }
183
+ it { form.save { |hash| hash.must_equal({library: {"books" => [{"id" => 1, "name" => "My book"}]}}) } }
187
184
  end
@@ -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
@@ -0,0 +1,77 @@
1
+ require "test_helper"
2
+
3
+ class ContractTest < MiniTest::Spec
4
+ Song = Struct.new(:title, :album, :composer)
5
+ Album = Struct.new(:name, :duration, :songs, :artist)
6
+ Artist = Struct.new(:name)
7
+
8
+ class ArtistForm < TestForm
9
+ property :name
10
+ end
11
+
12
+ class AlbumForm < TestContract
13
+ property :name
14
+
15
+ properties :duration
16
+ properties :year, :style, readable: false
17
+
18
+ validation do
19
+ params { required(:name).filled }
20
+ end
21
+
22
+ collection :songs do
23
+ property :title
24
+ validation do
25
+ params { required(:title).filled }
26
+ end
27
+
28
+ property :composer do
29
+ property :name
30
+ validation do
31
+ params { required(:name).filled }
32
+ end
33
+ end
34
+ end
35
+
36
+ property :artist, form: ArtistForm
37
+ end
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) }
44
+
45
+ let(:form) { AlbumForm.new(album) }
46
+
47
+ # accept `property form: SongForm`.
48
+ it do
49
+ form.artist.must_be_instance_of ArtistForm
50
+ end
51
+
52
+ describe ".properties" do
53
+ it "defines a property when called with one argument" do
54
+ form.must_respond_to :duration
55
+ end
56
+
57
+ it "defines several properties when called with multiple arguments" do
58
+ form.must_respond_to :year
59
+ form.must_respond_to :style
60
+ end
61
+
62
+ it "passes options to each property when options are provided" do
63
+ readable = AlbumForm.new(album).options_for(:style)[:readable]
64
+ readable.must_equal false
65
+ end
66
+
67
+ it "returns the list of defined properties" do
68
+ returned_value = AlbumForm.properties(:hello, :world, virtual: true)
69
+ returned_value.must_equal %i[hello world]
70
+ end
71
+ end
72
+
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\"}>" }
76
+ end
77
+ 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)
@@ -36,13 +36,13 @@ 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
@@ -66,7 +66,7 @@ class ContractTest < MiniTest::Spec
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
+ returned_value.must_equal %i[hello world]
70
70
  end
71
71
  end
72
72
 
@@ -19,4 +19,4 @@ class DefaultTest < Minitest::Spec
19
19
  form.name.must_equal "Wrong"
20
20
  form.songs[0].title.must_equal "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
 
@@ -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
65
  form.artist.model.must_equal 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,13 +87,13 @@ 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
+ form.validate(json) { |params|
98
95
  deserializer.new(form).from_json(params)
99
- end.must_equal true # with block must return result, too.
96
+ }.must_equal true # with block must return result, too.
100
97
 
101
98
  form.title.must_equal "Apocalypse Soon"
102
99
  form.artist.name.must_equal "Mute"
@@ -0,0 +1,225 @@
1
+ require "test_helper"
2
+
3
+ class ErrorsTest < MiniTest::Spec
4
+ class AlbumForm < TestForm
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
14
+
15
+ property :hit do
16
+ property :title
17
+ validation do
18
+ params { required(:title).filled }
19
+ end
20
+ end
21
+
22
+ collection :songs do
23
+ property :title
24
+ validation do
25
+ params { required(:title).filled }
26
+ end
27
+ end
28
+
29
+ property :band do # yepp, people do crazy stuff like that.
30
+ property :name
31
+ property :label do
32
+ property :name
33
+ validation do
34
+ params { required(:name).filled }
35
+ end
36
+ end
37
+ # TODO: make band a required object.
38
+
39
+ validation do
40
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
41
+
42
+ params { required(:name).filled }
43
+
44
+ rule(:name) { key.failure(:good_musical_taste?) if value == "Nickelback" }
45
+ end
46
+ end
47
+
48
+ validation do
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
56
+ end
57
+ end
58
+
59
+ let(:album_title) { "Blackhawks Over Los Angeles" }
60
+ let(:album) do
61
+ 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")
67
+ )
68
+ end
69
+ let(:song) { OpenStruct.new(title: "Downtown") }
70
+ let(:songs) { [song = OpenStruct.new(title: "Calling"), song] }
71
+ let(:form) { AlbumForm.new(album) }
72
+
73
+ describe "#validate with invalid array property" do
74
+ it do
75
+ 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
+ ).must_equal false
83
+ form.errors.messages.must_equal(artists: {0 => ["must be a string"], 2 => ["must be a string"]})
84
+ form.errors.size.must_equal(1)
85
+ end
86
+ end
87
+
88
+ describe "#errors without #validate" do
89
+ it do
90
+ form.errors.size.must_equal 0
91
+ end
92
+ end
93
+
94
+ describe "blank everywhere" do
95
+ before do
96
+ form.validate(
97
+ "hit" => {"title" => ""},
98
+ "title" => "",
99
+ "songs" => [{"title" => ""}, {"title" => ""}],
100
+ "producer" => {"name" => ""}
101
+ )
102
+ end
103
+
104
+ it do
105
+ form.errors.messages.must_equal(
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
+ )
112
+ end
113
+
114
+ # it do
115
+ # form.errors.must_equal({:title => ["must be filled"]})
116
+ # TODO: this should only contain local errors?
117
+ # end
118
+
119
+ # nested forms keep their own Errors:
120
+ it { form.producer.errors.messages.must_equal(name: ["must be filled"]) }
121
+ it { form.hit.errors.messages.must_equal(title: ["must be filled"]) }
122
+ it { form.songs[0].errors.messages.must_equal(title: ["must be filled"]) }
123
+
124
+ it do
125
+ form.errors.messages.must_equal(
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
+ form.errors.size.must_equal(5)
133
+ end
134
+ end
135
+
136
+ describe "#validate with main form invalid" do
137
+ it do
138
+ form.validate("title" => "", "band" => {"label" => {name: "Fat Wreck"}}, "producer" => nil).must_equal false
139
+ form.errors.messages.must_equal(title: ["must be filled"], producer: ["must be a hash"])
140
+ form.errors.size.must_equal(2)
141
+ end
142
+ end
143
+
144
+ describe "#validate with middle nested form invalid" do
145
+ before { @result = form.validate("hit" => {"title" => ""}, "band" => {"label" => {name: "Fat Wreck"}}) }
146
+
147
+ it { @result.must_equal false }
148
+ it { form.errors.messages.must_equal("hit.title": ["must be filled"]) }
149
+ it { form.errors.size.must_equal(1) }
150
+ end
151
+
152
+ describe "#validate with collection form invalid" do
153
+ before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {name: "Fat Wreck"}}) }
154
+
155
+ it { @result.must_equal false }
156
+ it { form.errors.messages.must_equal("songs.title": ["must be filled"]) }
157
+ it { form.errors.size.must_equal(1) }
158
+ end
159
+
160
+ describe "#validate with collection and 2-level-nested invalid" do
161
+ before { @result = form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
162
+
163
+ it { @result.must_equal false }
164
+ it { form.errors.messages.must_equal("songs.title": ["must be filled"], "band.label.name": ["must be filled"]) }
165
+ it { form.errors.size.must_equal(2) }
166
+ end
167
+
168
+ describe "#validate with nested form using :base invalid" do
169
+ it do
170
+ result = form.validate("songs" => [{"title" => "Someday"}], "band" => {"name" => "Nickelback", "label" => {"name" => "Roadrunner Records"}})
171
+ result.must_equal false
172
+ form.errors.messages.must_equal("band.name": ["you're a bad person"])
173
+ form.errors.size.must_equal(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
+ result.must_equal false
182
+ form.errors.messages.must_equal(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
+ form.errors.messages.must_equal(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
+ form.errors.messages.must_equal(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
+ form.errors.messages.must_equal(title: ["must be filled"], "band.name": ["you're a bad person"], policy: ["error_text", "another error"])
192
+ end
193
+ end
194
+
195
+ describe "correct #validate" do
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 { @result.must_equal true }
206
+ it { form.hit.title.must_equal "Sacrifice" }
207
+ it { form.title.must_equal "Second Heat" }
208
+ it { form.songs.first.title.must_equal "Heart Of A Lion" }
209
+ it do
210
+ skip "WE DON'T NEED COUNT AND EMPTY? ON THE CORE ERRORS OBJECT"
211
+ form.errors.size.must_equal(0)
212
+ form.errors.empty?.must_equal(true)
213
+ end
214
+ end
215
+
216
+ describe "Errors#to_s" do
217
+ before { form.validate("songs" => [{"title" => ""}], "band" => {"label" => {}}) }
218
+
219
+ # to_s is aliased to messages
220
+ it {
221
+ skip "why do we need Errors#to_s ?"
222
+ form.errors.to_s.must_equal "{:\"songs.title\"=>[\"must be filled\"], :\"band.label.name\"=>[\"must be filled\"]}"
223
+ }
224
+ end
225
+ end