shoulda-matchers 2.0.0 → 2.1.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 (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