shoulda-matchers 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/Appraisals +3 -3
- data/Gemfile.lock +2 -2
- data/NEWS.md +20 -0
- data/README.md +1 -1
- data/gemfiles/3.0.gemfile +1 -1
- data/gemfiles/3.0.gemfile.lock +27 -27
- data/gemfiles/3.1.gemfile +1 -1
- data/gemfiles/3.1.gemfile.lock +36 -36
- data/gemfiles/3.2.gemfile +1 -1
- data/gemfiles/3.2.gemfile.lock +35 -35
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +4 -1
- data/lib/shoulda/matchers/active_model.rb +14 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +45 -21
- data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +28 -6
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +22 -4
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +1 -0
- data/lib/shoulda/matchers/active_model/errors.rb +7 -0
- data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +9 -1
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +27 -8
- data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +14 -5
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +8 -5
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +1 -1
- data/spec/shoulda/action_controller/set_the_flash_matcher_spec.rb +6 -0
- data/spec/shoulda/active_model/allow_value_matcher_spec.rb +32 -0
- data/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb +23 -1
- data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +49 -0
- data/spec/shoulda/active_model/ensure_length_of_matcher_spec.rb +13 -0
- data/spec/shoulda/active_model/exception_message_finder_spec.rb +112 -0
- data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +15 -0
- data/spec/shoulda/active_model/validation_message_finder_spec.rb +107 -0
- data/spec/shoulda/active_record/association_matcher_spec.rb +8 -0
- data/spec/shoulda/active_record/have_db_index_matcher_spec.rb +17 -0
- metadata +18 -6
@@ -25,6 +25,9 @@ module Shoulda # :nodoc:
|
|
25
25
|
attr_reader :failure_message, :negative_failure_message
|
26
26
|
|
27
27
|
def to(value)
|
28
|
+
if !value.is_a?(String) && !value.is_a?(Regexp)
|
29
|
+
raise "cannot match against #{value.inspect}"
|
30
|
+
end
|
28
31
|
@value = value
|
29
32
|
self
|
30
33
|
end
|
@@ -134,7 +137,7 @@ module Shoulda # :nodoc:
|
|
134
137
|
|
135
138
|
def pretty_key
|
136
139
|
if @options[:key]
|
137
|
-
"[:#{@key}]"
|
140
|
+
"[:#{@options[:key]}]"
|
138
141
|
else
|
139
142
|
""
|
140
143
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'shoulda/matchers/active_model/helpers'
|
2
2
|
require 'shoulda/matchers/active_model/validation_matcher'
|
3
|
+
require 'shoulda/matchers/active_model/validation_message_finder'
|
4
|
+
require 'shoulda/matchers/active_model/exception_message_finder'
|
3
5
|
require 'shoulda/matchers/active_model/allow_value_matcher'
|
4
6
|
require 'shoulda/matchers/active_model/ensure_length_of_matcher'
|
5
7
|
require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
|
@@ -11,6 +13,7 @@ require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
|
|
11
13
|
require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
|
12
14
|
require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
|
13
15
|
require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
|
16
|
+
require 'shoulda/matchers/active_model/errors'
|
14
17
|
|
15
18
|
|
16
19
|
module Shoulda
|
@@ -27,8 +30,19 @@ module Shoulda
|
|
27
30
|
# end
|
28
31
|
# it { should allow_value("(123) 456-7890").for(:phone_number) }
|
29
32
|
# it { should_not allow_mass_assignment_of(:password) }
|
33
|
+
# it { should allow_value('Activated', 'Pending').for(:status).strict }
|
34
|
+
# it { should_not allow_value('Amazing').for(:status).strict }
|
30
35
|
# end
|
31
36
|
#
|
37
|
+
# These tests work with the following model:
|
38
|
+
#
|
39
|
+
# class User < ActiveRecord::Base
|
40
|
+
# validates_presence_of :name
|
41
|
+
# validates_presence_of :phone_number
|
42
|
+
# validates_format_of :phone_number, :with => /\\(\\d{3}\\) \\d{3}\\-\\d{4}/
|
43
|
+
# validates_inclusion_of :status, :in => %w(Activated Pending), :strict => true
|
44
|
+
# attr_accessible :name, :phone_number
|
45
|
+
# end
|
32
46
|
module ActiveModel
|
33
47
|
end
|
34
48
|
end
|
@@ -11,6 +11,9 @@ module Shoulda # :nodoc:
|
|
11
11
|
# * <tt>with_message</tt> - value the test expects to find in
|
12
12
|
# <tt>errors.on(:attribute)</tt>. Regexp or string. If omitted,
|
13
13
|
# the test looks for any errors in <tt>errors.on(:attribute)</tt>.
|
14
|
+
# * <tt>strict</tt> - expects the model to raise an exception when the
|
15
|
+
# validation fails rather than adding to the errors collection. Used for
|
16
|
+
# testing `validates!` and the `:strict => true` validation options.
|
14
17
|
#
|
15
18
|
# Example:
|
16
19
|
# it { should_not allow_value('bad').for(:isbn) }
|
@@ -29,6 +32,7 @@ module Shoulda # :nodoc:
|
|
29
32
|
|
30
33
|
def initialize(*values)
|
31
34
|
@values_to_match = values
|
35
|
+
@message_finder_factory = ValidationMessageFinder
|
32
36
|
@options = {}
|
33
37
|
end
|
34
38
|
|
@@ -42,6 +46,11 @@ module Shoulda # :nodoc:
|
|
42
46
|
self
|
43
47
|
end
|
44
48
|
|
49
|
+
def strict
|
50
|
+
@message_finder_factory = ExceptionMessageFinder
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
45
54
|
def matches?(instance)
|
46
55
|
@instance = instance
|
47
56
|
@values_to_match.none? do |value|
|
@@ -60,30 +69,29 @@ module Shoulda # :nodoc:
|
|
60
69
|
end
|
61
70
|
|
62
71
|
def description
|
63
|
-
|
72
|
+
message_finder.allow_description(allowed_values)
|
64
73
|
end
|
65
74
|
|
66
75
|
private
|
67
76
|
|
68
77
|
def errors_match?
|
69
|
-
|
70
|
-
|
78
|
+
has_messages? && errors_for_attribute_match?
|
79
|
+
end
|
80
|
+
|
81
|
+
def has_messages?
|
82
|
+
message_finder.has_messages?
|
83
|
+
end
|
84
|
+
|
85
|
+
def errors_for_attribute_match?
|
86
|
+
if expected_message
|
87
|
+
@matched_error = errors_match_regexp? || errors_match_string?
|
71
88
|
else
|
72
|
-
|
73
|
-
@matched_error = errors_match_regexp? || errors_match_string?
|
74
|
-
else
|
75
|
-
errors_for_attribute.compact.any?
|
76
|
-
end
|
89
|
+
errors_for_attribute.compact.any?
|
77
90
|
end
|
78
91
|
end
|
79
92
|
|
80
93
|
def errors_for_attribute
|
81
|
-
|
82
|
-
errors = @instance.errors[@attribute]
|
83
|
-
else
|
84
|
-
errors = @instance.errors.on(@attribute)
|
85
|
-
end
|
86
|
-
Array.wrap(errors)
|
94
|
+
message_finder.messages
|
87
95
|
end
|
88
96
|
|
89
97
|
def errors_match_regexp?
|
@@ -100,15 +108,15 @@ module Shoulda # :nodoc:
|
|
100
108
|
|
101
109
|
def expectation
|
102
110
|
includes_expected_message = expected_message ? "to include #{expected_message.inspect}" : ''
|
103
|
-
[
|
111
|
+
[error_source, includes_expected_message, "when #{@attribute} is set to #{@value.inspect}"].join(' ')
|
112
|
+
end
|
113
|
+
|
114
|
+
def error_source
|
115
|
+
message_finder.source_description
|
104
116
|
end
|
105
117
|
|
106
118
|
def error_description
|
107
|
-
|
108
|
-
"no errors"
|
109
|
-
else
|
110
|
-
"errors: #{pretty_error_messages(@instance)}"
|
111
|
-
end
|
119
|
+
message_finder.messages_description
|
112
120
|
end
|
113
121
|
|
114
122
|
def allowed_values
|
@@ -122,16 +130,32 @@ module Shoulda # :nodoc:
|
|
122
130
|
def expected_message
|
123
131
|
if @options.key?(:expected_message)
|
124
132
|
if Symbol === @options[:expected_message]
|
125
|
-
|
133
|
+
default_expected_message
|
126
134
|
else
|
127
135
|
@options[:expected_message]
|
128
136
|
end
|
129
137
|
end
|
130
138
|
end
|
131
139
|
|
140
|
+
def default_expected_message
|
141
|
+
message_finder.expected_message_from(default_attribute_message)
|
142
|
+
end
|
143
|
+
|
144
|
+
def default_attribute_message
|
145
|
+
default_error_message(
|
146
|
+
@options[:expected_message],
|
147
|
+
:model_name => model_name,
|
148
|
+
:attribute => @attribute
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
132
152
|
def model_name
|
133
153
|
@instance.class.to_s.underscore
|
134
154
|
end
|
155
|
+
|
156
|
+
def message_finder
|
157
|
+
@message_finder ||= @message_finder_factory.new(@instance, @attribute)
|
158
|
+
end
|
135
159
|
end
|
136
160
|
end
|
137
161
|
end
|
@@ -5,6 +5,7 @@ module Shoulda # :nodoc:
|
|
5
5
|
# Ensure that the attribute's value is not in the range specified
|
6
6
|
#
|
7
7
|
# Options:
|
8
|
+
# * <tt>in_array</tt> - the array of not allowed values for this attribute
|
8
9
|
# * <tt>in_range</tt> - the range of not allowed values for this attribute
|
9
10
|
# * <tt>with_message</tt> - value the test expects to find in
|
10
11
|
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
@@ -18,6 +19,10 @@ module Shoulda # :nodoc:
|
|
18
19
|
end
|
19
20
|
|
20
21
|
class EnsureExclusionOfMatcher < ValidationMatcher # :nodoc:
|
22
|
+
def in_array(array)
|
23
|
+
@array = array
|
24
|
+
self
|
25
|
+
end
|
21
26
|
|
22
27
|
def in_range(range)
|
23
28
|
@range = range
|
@@ -32,20 +37,30 @@ module Shoulda # :nodoc:
|
|
32
37
|
end
|
33
38
|
|
34
39
|
def description
|
35
|
-
"ensure exclusion of #{@attribute} in #{
|
40
|
+
"ensure exclusion of #{@attribute} in #{inspect_message}"
|
36
41
|
end
|
37
42
|
|
38
43
|
def matches?(subject)
|
39
44
|
super(subject)
|
40
45
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
if @range
|
47
|
+
allows_lower_value &&
|
48
|
+
disallows_minimum_value &&
|
49
|
+
allows_higher_value &&
|
50
|
+
disallows_maximum_value
|
51
|
+
elsif @array
|
52
|
+
disallows_all_values_in_array?
|
53
|
+
end
|
45
54
|
end
|
46
55
|
|
47
56
|
private
|
48
57
|
|
58
|
+
def disallows_all_values_in_array?
|
59
|
+
@array.all? do |value|
|
60
|
+
disallows_value_of(value, expected_message)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
49
64
|
def allows_lower_value
|
50
65
|
@minimum == 0 || allows_value_of(@minimum - 1, expected_message)
|
51
66
|
end
|
@@ -65,8 +80,15 @@ module Shoulda # :nodoc:
|
|
65
80
|
def expected_message
|
66
81
|
@expected_message || :exclusion
|
67
82
|
end
|
68
|
-
end
|
69
83
|
|
84
|
+
def inspect_message
|
85
|
+
if @range
|
86
|
+
@range.inspect
|
87
|
+
else
|
88
|
+
@array.inspect
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
70
92
|
end
|
71
93
|
end
|
72
94
|
end
|
@@ -5,7 +5,7 @@ module Shoulda # :nodoc:
|
|
5
5
|
# Ensure that the attribute's value is in the range specified
|
6
6
|
#
|
7
7
|
# Options:
|
8
|
-
# * <tt>in_array</tt> - the
|
8
|
+
# * <tt>in_array</tt> - the array of allowed values for this attribute
|
9
9
|
# * <tt>in_range</tt> - the range of allowed values for this attribute
|
10
10
|
# * <tt>with_low_message</tt> - value the test expects to find in
|
11
11
|
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
|
@@ -83,7 +83,7 @@ module Shoulda # :nodoc:
|
|
83
83
|
disallows_higher_value &&
|
84
84
|
allows_maximum_value
|
85
85
|
elsif @array
|
86
|
-
if allows_all_values_in_array? && allows_blank_value? && allows_nil_value?
|
86
|
+
if allows_all_values_in_array? && allows_blank_value? && allows_nil_value? && disallows_value_outside_of_array?
|
87
87
|
true
|
88
88
|
else
|
89
89
|
@failure_message = "#{@array} doesn't match array in validation"
|
@@ -96,7 +96,8 @@ module Shoulda # :nodoc:
|
|
96
96
|
|
97
97
|
def allows_blank_value?
|
98
98
|
if @options.key?(:allow_blank)
|
99
|
-
|
99
|
+
blank_values = ['', ' ', "\n", "\r", "\t", "\f"]
|
100
|
+
@options[:allow_blank] == blank_values.all? { |value| allows_value_of(value) }
|
100
101
|
else
|
101
102
|
true
|
102
103
|
end
|
@@ -135,8 +136,25 @@ module Shoulda # :nodoc:
|
|
135
136
|
def allows_maximum_value
|
136
137
|
allows_value_of(@maximum, @high_message)
|
137
138
|
end
|
138
|
-
end
|
139
139
|
|
140
|
+
def disallows_value_outside_of_array?
|
141
|
+
if value_outside_of_array
|
142
|
+
disallows_value_of(value_outside_of_array)
|
143
|
+
else
|
144
|
+
raise CouldNotDetermineValueOutsideOfArray
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def value_outside_of_array
|
149
|
+
found = @array.detect do |item|
|
150
|
+
!@array.include?(item.next)
|
151
|
+
end
|
152
|
+
|
153
|
+
if found
|
154
|
+
found.next
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
140
158
|
end
|
141
159
|
end
|
142
160
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel
|
4
|
+
|
5
|
+
# Finds message information from exceptions thrown by #valid?
|
6
|
+
class ExceptionMessageFinder
|
7
|
+
def initialize(instance, attribute)
|
8
|
+
@instance = instance
|
9
|
+
@attribute = attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
def allow_description(allowed_values)
|
13
|
+
"doesn't raise when #{@attribute} is set to #{allowed_values}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def messages_description
|
17
|
+
if has_messages?
|
18
|
+
messages.join
|
19
|
+
else
|
20
|
+
'no exception'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_messages?
|
25
|
+
messages.any?
|
26
|
+
end
|
27
|
+
|
28
|
+
def messages
|
29
|
+
@messages ||= validate_and_rescue
|
30
|
+
end
|
31
|
+
|
32
|
+
def source_description
|
33
|
+
'exception'
|
34
|
+
end
|
35
|
+
|
36
|
+
def expected_message_from(attribute_message)
|
37
|
+
"#{human_attribute_name} #{attribute_message}"
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def validate_and_rescue
|
43
|
+
@instance.valid?
|
44
|
+
[]
|
45
|
+
rescue ::ActiveModel::StrictValidationFailed => exception
|
46
|
+
[exception.message]
|
47
|
+
end
|
48
|
+
|
49
|
+
def human_attribute_name
|
50
|
+
@instance.class.human_attribute_name(@attribute)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
@@ -122,7 +122,7 @@ module Shoulda # :nodoc:
|
|
122
122
|
previous_value = existing.send(scope)
|
123
123
|
|
124
124
|
# Assume the scope is a foreign key if the field is nil
|
125
|
-
previous_value ||=
|
125
|
+
previous_value ||= correct_type_for_column(@subject.class.columns_hash[scope.to_s])
|
126
126
|
|
127
127
|
next_value = if previous_value.respond_to?(:next)
|
128
128
|
previous_value.next
|
@@ -146,6 +146,14 @@ module Shoulda # :nodoc:
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
|
149
|
+
def correct_type_for_column(column)
|
150
|
+
if column.type == :string
|
151
|
+
'0'
|
152
|
+
else
|
153
|
+
0
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
149
157
|
def class_name
|
150
158
|
@subject.class.name
|
151
159
|
end
|
@@ -6,6 +6,12 @@ module Shoulda # :nodoc:
|
|
6
6
|
|
7
7
|
def initialize(attribute)
|
8
8
|
@attribute = attribute
|
9
|
+
@strict = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def strict
|
13
|
+
@strict = true
|
14
|
+
self
|
9
15
|
end
|
10
16
|
|
11
17
|
def negative_failure_message
|
@@ -20,10 +26,8 @@ module Shoulda # :nodoc:
|
|
20
26
|
private
|
21
27
|
|
22
28
|
def allows_value_of(value, message = nil)
|
23
|
-
allow =
|
24
|
-
|
25
|
-
for(@attribute).
|
26
|
-
with_message(message)
|
29
|
+
allow = allow_value_matcher(value, message)
|
30
|
+
|
27
31
|
if allow.matches?(@subject)
|
28
32
|
@negative_failure_message = allow.failure_message
|
29
33
|
true
|
@@ -34,10 +38,8 @@ module Shoulda # :nodoc:
|
|
34
38
|
end
|
35
39
|
|
36
40
|
def disallows_value_of(value, message = nil)
|
37
|
-
disallow =
|
38
|
-
|
39
|
-
for(@attribute).
|
40
|
-
with_message(message)
|
41
|
+
disallow = allow_value_matcher(value, message)
|
42
|
+
|
41
43
|
if disallow.matches?(@subject)
|
42
44
|
@failure_message = disallow.negative_failure_message
|
43
45
|
false
|
@@ -46,6 +48,23 @@ module Shoulda # :nodoc:
|
|
46
48
|
true
|
47
49
|
end
|
48
50
|
end
|
51
|
+
|
52
|
+
def allow_value_matcher(value, message)
|
53
|
+
matcher = AllowValueMatcher.
|
54
|
+
new(value).
|
55
|
+
for(@attribute).
|
56
|
+
with_message(message)
|
57
|
+
|
58
|
+
if strict?
|
59
|
+
matcher.strict
|
60
|
+
else
|
61
|
+
matcher
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def strict?
|
66
|
+
@strict
|
67
|
+
end
|
49
68
|
end
|
50
69
|
end
|
51
70
|
end
|