shoulda-matchers 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +9 -15
  3. data/Appraisals +10 -8
  4. data/Gemfile.lock +1 -1
  5. data/NEWS.md +62 -32
  6. data/gemfiles/3.0.gemfile.lock +2 -2
  7. data/gemfiles/3.1.gemfile.lock +2 -2
  8. data/gemfiles/3.2.gemfile +1 -1
  9. data/gemfiles/3.2.gemfile.lock +3 -3
  10. data/lib/shoulda/matchers/active_model.rb +1 -0
  11. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +7 -2
  12. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +1 -1
  13. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +1 -1
  14. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +2 -0
  15. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +3 -2
  16. data/lib/shoulda/matchers/active_model/helpers.rb +5 -2
  17. data/lib/shoulda/matchers/active_model/odd_even_number_matcher.rb +47 -0
  18. data/lib/shoulda/matchers/active_model/only_integer_matcher.rb +4 -0
  19. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +26 -3
  20. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +68 -16
  21. data/lib/shoulda/matchers/active_model/validation_matcher.rb +5 -0
  22. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +3 -2
  23. data/lib/shoulda/matchers/active_record/association_matcher.rb +29 -4
  24. data/lib/shoulda/matchers/version.rb +1 -1
  25. data/shoulda-matchers.gemspec +1 -0
  26. data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +195 -0
  27. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +18 -0
  28. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +21 -0
  29. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +23 -0
  30. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +46 -0
  31. data/spec/shoulda/matchers/active_model/odd_even_number_matcher_spec.rb +93 -0
  32. data/spec/shoulda/matchers/active_model/only_integer_matcher_spec.rb +4 -4
  33. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +60 -0
  34. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +16 -0
  35. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +45 -8
  36. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +13 -0
  37. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +50 -2
  38. data/spec/support/i18n_faker.rb +10 -0
  39. metadata +10 -3
@@ -56,6 +56,24 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher do
56
56
  end
57
57
  end
58
58
 
59
+ context "an attribute with a context-dependent validation" do
60
+ context "without the validation context" do
61
+ it "allows a bad value" do
62
+ validating_format(:with => /abc/, :on => :customisable).should allow_value("xyz").for(:attr)
63
+ end
64
+ end
65
+
66
+ context "with the validation context" do
67
+ it "allows a good value" do
68
+ validating_format(:with => /abc/, :on => :customisable).should allow_value("abcde").for(:attr).on(:customisable)
69
+ end
70
+
71
+ it "rejects a bad value" do
72
+ validating_format(:with => /abc/, :on => :customisable).should_not allow_value("xyz").for(:attr).on(:customisable)
73
+ end
74
+ end
75
+ end
76
+
59
77
  context 'an attribute with several validations' do
60
78
  let(:model) do
61
79
  define_model :example, :attr => :string do
@@ -18,6 +18,18 @@ describe Shoulda::Matchers::ActiveModel::EnsureExclusionOfMatcher do
18
18
  end
19
19
  end
20
20
 
21
+ context 'an attribute which must be excluded from a range with excluded end' do
22
+ it 'accepts ensuring the correct range' do
23
+ validating_exclusion(:in => 2...5).
24
+ should ensure_exclusion_of(:attr).in_range(2...5)
25
+ end
26
+
27
+ it 'rejects ensuring excluded value' do
28
+ validating_exclusion(:in => 2...5).
29
+ should_not ensure_exclusion_of(:attr).in_range(2...4)
30
+ end
31
+ end
32
+
21
33
  context 'an attribute with a custom validation message' do
22
34
  it 'accepts ensuring the correct range' do
23
35
  validating_exclusion(:in => 2..4, :message => 'not good').
@@ -35,6 +47,15 @@ describe Shoulda::Matchers::ActiveModel::EnsureExclusionOfMatcher do
35
47
 
36
48
  model.should ensure_exclusion_of(:attr).in_range(2..5).
37
49
  with_message(/should be out of this range/)
50
+
51
+ model = custom_validation do
52
+ if attr >= 2 && attr <= 4
53
+ errors.add(:attr, 'should be out of this range')
54
+ end
55
+ end
56
+
57
+ model.should ensure_exclusion_of(:attr).in_range(2...5).
58
+ with_message(/should be out of this range/)
38
59
  end
39
60
  end
40
61
 
@@ -56,6 +56,18 @@ describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
56
56
  end
57
57
  end
58
58
 
59
+ context 'an attribute which must be included in a range with excluded end' do
60
+ it 'accepts ensuring the correct range' do
61
+ validating_inclusion(:in => 2...5).
62
+ should ensure_inclusion_of(:attr).in_range(2...5)
63
+ end
64
+
65
+ it 'rejects ensuring a lower maximum value' do
66
+ validating_inclusion(:in => 2...5).
67
+ should_not ensure_inclusion_of(:attr).in_range(2...4)
68
+ end
69
+ end
70
+
59
71
  context 'an attribute with a custom ranged value validation' do
60
72
  it 'accepts ensuring the correct range' do
61
73
  validating_inclusion(:in => 2..4, :message => 'not good').
@@ -75,6 +87,17 @@ describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
75
87
 
76
88
  model.should ensure_inclusion_of(:attr).in_range(2..5).
77
89
  with_low_message(/low/).with_high_message(/high/)
90
+
91
+ model = custom_validation do
92
+ if attr < 2
93
+ errors.add(:attr, 'too low')
94
+ elsif attr > 4
95
+ errors.add(:attr, 'too high')
96
+ end
97
+ end
98
+
99
+ model.should ensure_inclusion_of(:attr).in_range(2...5).
100
+ with_low_message(/low/).with_high_message(/high/)
78
101
  end
79
102
  end
80
103
 
@@ -105,6 +105,52 @@ describe Shoulda::Matchers::ActiveModel::EnsureLengthOfMatcher do
105
105
  end
106
106
  end
107
107
 
108
+ context 'using translations' do
109
+ after { I18n.backend.reload! }
110
+
111
+ context "a too_long translation containing %{attribute}, %{model}" do
112
+ before do
113
+ stub_translation(
114
+ "activerecord.errors.messages.too_long",
115
+ "The %{attribute} of your %{model} is too long (maximum is %{count} characters)")
116
+ end
117
+
118
+ it "does not raise an exception" do
119
+ expect {
120
+ validating_length(:maximum => 4).should ensure_length_of(:attr).is_at_most(4)
121
+ }.to_not raise_exception(I18n::MissingInterpolationArgument)
122
+ end
123
+ end
124
+
125
+ context "a too_short translation containing %{attribute}, %{model}" do
126
+ before do
127
+ stub_translation(
128
+ "activerecord.errors.messages.too_short",
129
+ "The %{attribute} of your %{model} is too short (minimum is %{count} characters)")
130
+ end
131
+
132
+ it "does not raise an exception" do
133
+ expect {
134
+ validating_length(:minimum => 4).should ensure_length_of(:attr).is_at_least(4)
135
+ }.to_not raise_exception(I18n::MissingInterpolationArgument)
136
+ end
137
+ end
138
+
139
+ context "a wrong_length translation containing %{attribute}, %{model}" do
140
+ before do
141
+ stub_translation(
142
+ "activerecord.errors.messages.wrong_length",
143
+ "The %{attribute} of your %{model} is the wrong length (should be %{count} characters)")
144
+ end
145
+
146
+ it "does not raise an exception" do
147
+ expect {
148
+ validating_length(:is => 4).should ensure_length_of(:attr).is_equal_to(4)
149
+ }.to_not raise_exception(I18n::MissingInterpolationArgument)
150
+ end
151
+ end
152
+ end
153
+
108
154
  def validating_length(options = {})
109
155
  define_model(:example, :attr => :string) do
110
156
  validates_length_of :attr, options
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoulda::Matchers::ActiveModel::OddEvenNumberMatcher do
4
+ context 'given an attribute that only allows odd number values' do
5
+ it 'matches' do
6
+ validating_odd_number.should new_odd_matcher
7
+ end
8
+
9
+ it 'returns itself when given a message' do
10
+ matcher = new_odd_matcher
11
+ matcher.with_message('some message').should == matcher
12
+ end
13
+ end
14
+
15
+ context 'given an attribute that only allows even number values' do
16
+ it 'matches' do
17
+ validating_even_number.should new_even_matcher
18
+ end
19
+
20
+ it 'returns itself when given a message' do
21
+ matcher = new_even_matcher
22
+ matcher.with_message('some message').should == matcher
23
+ end
24
+ end
25
+
26
+ context 'given an attribute that only allows odd number values with a custom validation message' do
27
+ it 'only accepts odd number values for that attribute with that message' do
28
+ validating_odd_number(:message => 'custom').should new_odd_matcher.with_message(/custom/)
29
+ end
30
+
31
+ it 'rejects odd number values for that attribute with another message' do
32
+ validating_odd_number(:message => 'custom').should_not new_odd_matcher.with_message(/wrong/)
33
+ end
34
+ end
35
+
36
+ context 'given an attribute that only allows even number values with a custom validation message' do
37
+ it 'only accepts even number values for that attribute with that message' do
38
+ validating_even_number(:message => 'custom').should new_even_matcher.with_message(/custom/)
39
+ end
40
+
41
+ it 'rejects even number values for that attribute with another message' do
42
+ validating_even_number(:message => 'custom').should_not new_even_matcher.with_message(/wrong/)
43
+ end
44
+ end
45
+
46
+ context 'when the model does not have an odd validation' do
47
+ it 'does not match' do
48
+ define_model(:example, :attr => :string).new.should_not new_odd_matcher
49
+ end
50
+
51
+ it 'fails with the ActiveRecord :odd message' do
52
+ matcher = new_odd_matcher
53
+
54
+ matcher.matches?(define_model(:example, :attr => :string).new)
55
+
56
+ matcher.failure_message_for_should.should include 'Expected errors to include "must be odd"'
57
+ end
58
+ end
59
+
60
+ context 'when the model does not have an even validation' do
61
+ it 'does not match' do
62
+ define_model(:example, :attr => :string).new.should_not new_even_matcher
63
+ end
64
+
65
+ it 'fails with the ActiveRecord :even message' do
66
+ matcher = new_even_matcher
67
+
68
+ matcher.matches?(define_model(:example, :attr => :string).new)
69
+
70
+ matcher.failure_message_for_should.should include 'Expected errors to include "must be even"'
71
+ end
72
+ end
73
+
74
+ def new_odd_matcher
75
+ described_class.new(:attr, :odd => true)
76
+ end
77
+
78
+ def new_even_matcher
79
+ described_class.new(:attr, :even => true)
80
+ end
81
+
82
+ def validating_odd_number(options = {})
83
+ define_model :example, :attr => :string do
84
+ validates_numericality_of :attr, { :odd => true }.merge(options)
85
+ end.new
86
+ end
87
+
88
+ def validating_even_number(options = {})
89
+ define_model :example, :attr => :string do
90
+ validates_numericality_of :attr, { :even => true }.merge(options)
91
+ end.new
92
+ end
93
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do
4
4
  context 'given an attribute that only allows integer values' do
5
5
  it 'matches' do
6
- only_integer.should new_matcher
6
+ validating_only_integer.should new_matcher
7
7
  end
8
8
 
9
9
  it 'allows integer types' do
@@ -18,11 +18,11 @@ describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do
18
18
 
19
19
  context 'given an attribute that only allows integer values with a custom validation message' do
20
20
  it 'only accepts integer values for that attribute with that message' do
21
- only_integer(:message => 'custom').should new_matcher.with_message(/custom/)
21
+ validating_only_integer(:message => 'custom').should new_matcher.with_message(/custom/)
22
22
  end
23
23
 
24
24
  it 'rejects integer values for that attribute with another message' do
25
- only_integer(:message => 'custom').should_not new_matcher.with_message(/wrong/)
25
+ validating_only_integer(:message => 'custom').should_not new_matcher.with_message(/wrong/)
26
26
  end
27
27
  end
28
28
 
@@ -44,7 +44,7 @@ describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do
44
44
  described_class.new(:attr)
45
45
  end
46
46
 
47
- def only_integer(options = {})
47
+ def validating_only_integer(options = {})
48
48
  define_model :example, :attr => :string do
49
49
  validates_numericality_of :attr, { :only_integer => true }.merge(options)
50
50
  end.new
@@ -22,6 +22,14 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
22
22
  define_model(:example, :attr => :string).new.should_not matcher
23
23
  end
24
24
 
25
+ it 'rejects with the ActiveRecord :not_a_number message' do
26
+ the_matcher = matcher
27
+
28
+ the_matcher.matches?(define_model(:example, :attr => :string).new)
29
+
30
+ the_matcher.failure_message_for_should_not.should include 'Did not expect errors to include "is not a number"'
31
+ end
32
+
25
33
  it 'rejects with the ActiveRecord :not_an_integer message' do
26
34
  the_matcher = matcher.only_integer
27
35
 
@@ -29,6 +37,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
29
37
 
30
38
  the_matcher.failure_message_for_should.should include 'Expected errors to include "must be an integer"'
31
39
  end
40
+
41
+ it 'rejects with the ActiveRecord :odd message' do
42
+ the_matcher = matcher.odd
43
+
44
+ the_matcher.matches?(define_model(:example, :attr => :string).new)
45
+
46
+ the_matcher.failure_message_for_should.should include 'Expected errors to include "must be odd"'
47
+ end
48
+
49
+ it 'rejects with the ActiveRecord :even message' do
50
+ the_matcher = matcher.even
51
+
52
+ the_matcher.matches?(define_model(:example, :attr => :string).new)
53
+
54
+ the_matcher.failure_message_for_should.should include 'Expected errors to include "must be even"'
55
+ end
32
56
  end
33
57
 
34
58
  context 'with the only_integer option' do
@@ -49,6 +73,42 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
49
73
  end
50
74
  end
51
75
 
76
+ context 'with the odd option' do
77
+ it 'allows odd number values for that attribute' do
78
+ validating_numericality(:odd => true).should matcher.odd
79
+ end
80
+
81
+ it 'rejects when the model does not enforce odd number values' do
82
+ validating_numericality.should_not matcher.odd
83
+ end
84
+
85
+ it 'rejects with the ActiveRecord :odd message' do
86
+ the_matcher = matcher.odd
87
+
88
+ the_matcher.matches?(validating_numericality)
89
+
90
+ the_matcher.failure_message_for_should.should include 'Expected errors to include "must be odd"'
91
+ end
92
+ end
93
+
94
+ context 'with the even option' do
95
+ it 'allows even number values for that attribute' do
96
+ validating_numericality(:even => true).should matcher.even
97
+ end
98
+
99
+ it 'rejects when the model does not enforce even number values' do
100
+ validating_numericality.should_not matcher.even
101
+ end
102
+
103
+ it 'rejects with the ActiveRecord :even message' do
104
+ the_matcher = matcher.even
105
+
106
+ the_matcher.matches?(validating_numericality)
107
+
108
+ the_matcher.failure_message_for_should.should include 'Expected errors to include "must be even"'
109
+ end
110
+ end
111
+
52
112
  context 'with a custom validation message' do
53
113
  it 'accepts when the messages match' do
54
114
  validating_numericality(:message => 'custom').
@@ -82,6 +82,22 @@ describe Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher do
82
82
  end
83
83
  end
84
84
 
85
+ context "an i18n translation containing %{attribute} and %{model}" do
86
+ before do
87
+ stub_translation(
88
+ "activerecord.errors.messages.blank",
89
+ "Please enter a %{attribute} for your %{model}")
90
+ end
91
+
92
+ after { I18n.backend.reload! }
93
+
94
+ it "does not raise an exception" do
95
+ expect {
96
+ validating_presence.should validate_presence_of(:attr)
97
+ }.to_not raise_exception
98
+ end
99
+ end
100
+
85
101
  if active_model_3_2?
86
102
  context 'a strictly required attribute' do
87
103
  it 'accepts when the :strict options match' do
@@ -112,21 +112,58 @@ describe Shoulda::Matchers::ActiveModel::ValidateUniquenessOfMatcher do
112
112
  should_not matcher.scoped_to(:fake)
113
113
  end
114
114
 
115
- def create_existing_record
116
- @existing ||= Example.create!(:attr => 'value', :scope1 => 1, :scope2 => 2, :other => 3)
115
+ context 'when the scoped attribute is a date' do
116
+ it "accepts" do
117
+ validating_scoped_uniqueness([:scope1], :date, :scope1 => Date.today).
118
+ should matcher.scoped_to(:scope1)
119
+ end
120
+
121
+ context 'when too narrow of a scope is specified' do
122
+ it 'rejects' do
123
+ validating_scoped_uniqueness([:scope1, :scope2], :date, :scope1 => Date.today, :scope2 => Date.today).
124
+ should_not matcher.scoped_to(:scope1, :scope2, :other)
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'when the scoped attribute is a datetime' do
130
+ it 'accepts' do
131
+ validating_scoped_uniqueness([:scope1], :datetime, :scope1 => DateTime.now).
132
+ should matcher.scoped_to(:scope1)
133
+ end
134
+
135
+ context 'with a nil value' do
136
+ it 'accepts' do
137
+ validating_scoped_uniqueness([:scope1], :datetime, :scope1 => nil).
138
+ should matcher.scoped_to(:scope1)
139
+ end
140
+ end
141
+
142
+ context 'when too narrow of a scope is specified' do
143
+ it 'rejects' do
144
+ validating_scoped_uniqueness([:scope1, :scope2], :datetime, :scope1 => DateTime.now, :scope2 => DateTime.now).
145
+ should_not matcher.scoped_to(:scope1, :scope2, :other)
146
+ end
147
+ end
148
+ end
149
+
150
+ def create_existing_record(attributes = {})
151
+ default_attributes = {:attr => 'value', :scope1 => 1, :scope2 => 2, :other => 3}
152
+ @existing ||= Example.create!(default_attributes.merge(attributes))
117
153
  end
118
154
 
119
- def define_scoped_model(scope)
120
- define_model(:example, :attr => :string, :scope1 => :integer,
121
- :scope2 => :integer, :other => :integer) do
155
+ def define_scoped_model(scope, scope_attr_type = :integer)
156
+ define_model(:example, :attr => :string, :scope1 => scope_attr_type,
157
+ :scope2 => scope_attr_type, :other => :integer) do
122
158
  attr_accessible :attr, :scope1, :scope2, :other
123
159
  validates_uniqueness_of :attr, :scope => scope
124
160
  end
125
161
  end
126
162
 
127
- def validating_scoped_uniqueness(scope)
128
- model = define_scoped_model(scope).new
129
- create_existing_record
163
+ def validating_scoped_uniqueness(*args)
164
+ attributes = args.extract_options!
165
+ model = define_scoped_model(*args).new
166
+ create_existing_record(attributes)
130
167
  model
131
168
  end
132
169
  end
@@ -70,6 +70,19 @@ describe Shoulda::Matchers::ActiveModel::ValidationMessageFinder do
70
70
 
71
71
  description.should == 'no errors'
72
72
  end
73
+
74
+ it 'should not fetch attribute values for errors that were copied from an autosaved belongs_to association' do
75
+ instance = define_model(:example) do
76
+ validate do |record|
77
+ record.errors.add('association.association_attribute', 'is invalid')
78
+ end
79
+ end.new
80
+ finder = Shoulda::Matchers::ActiveModel::ValidationMessageFinder.new(instance, :attribute)
81
+
82
+ expected_messages = ['association.association_attribute is invalid']
83
+ finder.messages_description.should == "errors: #{expected_messages}"
84
+ end
85
+
73
86
  end
74
87
 
75
88
  context '#source_description' do