shoulda-matchers 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +5 -0
- data/Appraisals +6 -0
- data/Gemfile.lock +15 -15
- data/NEWS.md +20 -1
- data/README.md +6 -1
- data/features/step_definitions/rails_steps.rb +3 -3
- data/gemfiles/3.0.gemfile.lock +23 -27
- data/gemfiles/3.1.gemfile.lock +26 -30
- data/gemfiles/3.2.gemfile +16 -0
- data/gemfiles/3.2.gemfile.lock +157 -0
- data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +46 -29
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +6 -2
- data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +12 -7
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +28 -15
- data/lib/shoulda/matchers/action_mailer/have_sent_email_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +13 -9
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -8
- data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +8 -6
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +63 -8
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +67 -34
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +3 -3
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +9 -5
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -3
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +27 -23
- data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +38 -25
- data/lib/shoulda/matchers/active_record/association_matcher.rb +49 -33
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +42 -35
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +15 -13
- data/lib/shoulda/matchers/active_record/query_the_database_matcher.rb +24 -23
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +13 -12
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +1 -1
- data/spec/shoulda/action_controller/assign_to_matcher_spec.rb +5 -3
- data/spec/shoulda/action_mailer/have_sent_email_spec.rb +40 -0
- data/spec/shoulda/active_model/allow_mass_assignment_of_matcher_spec.rb +12 -10
- data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +52 -0
- data/spec/shoulda/active_model/helpers_spec.rb +35 -6
- data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +0 -1
- data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +8 -1
- data/spec/shoulda/active_record/serialize_matcher_spec.rb +1 -1
- data/spec/support/active_model_versions.rb +9 -0
- data/spec/support/model_builder.rb +15 -7
- metadata +123 -128
@@ -2,7 +2,8 @@ module Shoulda # :nodoc:
|
|
2
2
|
module Matchers
|
3
3
|
module ActiveModel # :nodoc:
|
4
4
|
|
5
|
-
# Ensures that the length of the attribute is validated.
|
5
|
+
# Ensures that the length of the attribute is validated. Only works with
|
6
|
+
# string/text columns because it uses a string to check length.
|
6
7
|
#
|
7
8
|
# Options:
|
8
9
|
# * <tt>is_at_least</tt> - minimum length of this attribute
|
@@ -36,47 +37,56 @@ module Shoulda # :nodoc:
|
|
36
37
|
class EnsureLengthOfMatcher < ValidationMatcher # :nodoc:
|
37
38
|
include Helpers
|
38
39
|
|
40
|
+
def initialize(attribute)
|
41
|
+
super(attribute)
|
42
|
+
@options = {}
|
43
|
+
end
|
44
|
+
|
39
45
|
def is_at_least(length)
|
40
|
-
@minimum = length
|
46
|
+
@options[:minimum] = length
|
41
47
|
@short_message ||= :too_short
|
42
48
|
self
|
43
49
|
end
|
44
50
|
|
45
51
|
def is_at_most(length)
|
46
|
-
@maximum = length
|
52
|
+
@options[:maximum] = length
|
47
53
|
@long_message ||= :too_long
|
48
54
|
self
|
49
55
|
end
|
50
56
|
|
51
57
|
def is_equal_to(length)
|
52
|
-
@minimum = length
|
53
|
-
@maximum = length
|
58
|
+
@options[:minimum] = length
|
59
|
+
@options[:maximum] = length
|
54
60
|
@short_message ||= :wrong_length
|
55
61
|
self
|
56
62
|
end
|
57
63
|
|
58
64
|
def with_short_message(message)
|
59
|
-
|
65
|
+
if message
|
66
|
+
@short_message = message
|
67
|
+
end
|
60
68
|
self
|
61
69
|
end
|
62
70
|
alias_method :with_message, :with_short_message
|
63
71
|
|
64
72
|
def with_long_message(message)
|
65
|
-
|
73
|
+
if message
|
74
|
+
@long_message = message
|
75
|
+
end
|
66
76
|
self
|
67
77
|
end
|
68
78
|
|
69
79
|
def description
|
70
80
|
description = "ensure #{@attribute} has a length "
|
71
|
-
if @minimum && @maximum
|
72
|
-
if @minimum == @maximum
|
73
|
-
description << "of exactly #{@minimum}"
|
81
|
+
if @options.key?(:minimum) && @options.key?(:maximum)
|
82
|
+
if @options[:minimum] == @options[:maximum]
|
83
|
+
description << "of exactly #{@options[:minimum]}"
|
74
84
|
else
|
75
|
-
description << "between #{@minimum} and #{@maximum}"
|
85
|
+
description << "between #{@options[:minimum]} and #{@options[:maximum]}"
|
76
86
|
end
|
77
87
|
else
|
78
|
-
description << "of at least #{@minimum}" if @minimum
|
79
|
-
description << "of at most #{@maximum}" if @maximum
|
88
|
+
description << "of at least #{@options[:minimum]}" if @options[:minimum]
|
89
|
+
description << "of at most #{@options[:maximum]}" if @options[:maximum]
|
80
90
|
end
|
81
91
|
description
|
82
92
|
end
|
@@ -84,11 +94,7 @@ module Shoulda # :nodoc:
|
|
84
94
|
def matches?(subject)
|
85
95
|
super(subject)
|
86
96
|
translate_messages!
|
87
|
-
|
88
|
-
allows_minimum_length &&
|
89
|
-
((@minimum == @maximum) ||
|
90
|
-
(disallows_higher_length &&
|
91
|
-
allows_maximum_length))
|
97
|
+
lower_bound_matches? && upper_bound_matches?
|
92
98
|
end
|
93
99
|
|
94
100
|
private
|
@@ -96,39 +102,66 @@ module Shoulda # :nodoc:
|
|
96
102
|
def translate_messages!
|
97
103
|
if Symbol === @short_message
|
98
104
|
@short_message = default_error_message(@short_message,
|
99
|
-
:
|
105
|
+
:model_name => @subject.class.to_s.underscore,
|
106
|
+
:attribute => @attribute,
|
107
|
+
:count => @options[:minimum])
|
100
108
|
end
|
101
109
|
|
102
110
|
if Symbol === @long_message
|
103
111
|
@long_message = default_error_message(@long_message,
|
104
|
-
:
|
112
|
+
:model_name => @subject.class.to_s.underscore,
|
113
|
+
:attribute => @attribute,
|
114
|
+
:count => @options[:maximum])
|
105
115
|
end
|
106
116
|
end
|
107
117
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
118
|
+
def lower_bound_matches?
|
119
|
+
disallows_lower_length? && allows_minimum_length?
|
120
|
+
end
|
121
|
+
|
122
|
+
def upper_bound_matches?
|
123
|
+
disallows_higher_length? && allows_maximum_length?
|
124
|
+
end
|
125
|
+
|
126
|
+
def disallows_lower_length?
|
127
|
+
if @options.key?(:minimum)
|
128
|
+
@options[:minimum] == 0 ||
|
129
|
+
disallows_length_of?(@options[:minimum] - 1, @short_message)
|
130
|
+
else
|
131
|
+
true
|
132
|
+
end
|
112
133
|
end
|
113
134
|
|
114
|
-
def disallows_higher_length
|
115
|
-
@
|
135
|
+
def disallows_higher_length?
|
136
|
+
if @options.key?(:maximum)
|
137
|
+
disallows_length_of?(@options[:maximum] + 1, @long_message)
|
138
|
+
else
|
139
|
+
true
|
140
|
+
end
|
116
141
|
end
|
117
142
|
|
118
|
-
def allows_minimum_length
|
119
|
-
|
143
|
+
def allows_minimum_length?
|
144
|
+
if @options.key?(:minimum)
|
145
|
+
allows_length_of?(@options[:minimum], @short_message)
|
146
|
+
else
|
147
|
+
true
|
148
|
+
end
|
120
149
|
end
|
121
150
|
|
122
|
-
def allows_maximum_length
|
123
|
-
|
151
|
+
def allows_maximum_length?
|
152
|
+
if @options.key?(:maximum)
|
153
|
+
allows_length_of?(@options[:maximum], @long_message)
|
154
|
+
else
|
155
|
+
true
|
156
|
+
end
|
124
157
|
end
|
125
158
|
|
126
|
-
def allows_length_of(length, message)
|
127
|
-
|
159
|
+
def allows_length_of?(length, message)
|
160
|
+
allows_value_of(string_of_length(length), message)
|
128
161
|
end
|
129
162
|
|
130
|
-
def disallows_length_of(length, message)
|
131
|
-
|
163
|
+
def disallows_length_of?(length, message)
|
164
|
+
disallows_value_of(string_of_length(length), message)
|
132
165
|
end
|
133
166
|
|
134
167
|
def string_of_length(length)
|
@@ -20,7 +20,9 @@ module Shoulda # :nodoc:
|
|
20
20
|
class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
|
21
21
|
|
22
22
|
def with_message(message)
|
23
|
-
|
23
|
+
if message
|
24
|
+
@expected_message = message
|
25
|
+
end
|
24
26
|
self
|
25
27
|
end
|
26
28
|
|
@@ -33,9 +35,7 @@ module Shoulda # :nodoc:
|
|
33
35
|
def description
|
34
36
|
"require #{@attribute} to be accepted"
|
35
37
|
end
|
36
|
-
|
37
38
|
end
|
38
|
-
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Shoulda # :nodoc:
|
2
2
|
module Matchers
|
3
3
|
module ActiveModel # :nodoc:
|
4
|
-
# Ensure that the attribute is numeric
|
4
|
+
# Ensure that the attribute is numeric.
|
5
5
|
#
|
6
6
|
# Options:
|
7
7
|
# * <tt>with_message</tt> - value the test expects to find in
|
@@ -18,9 +18,13 @@ module Shoulda # :nodoc:
|
|
18
18
|
end
|
19
19
|
|
20
20
|
class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
|
21
|
+
def initialize(attribute)
|
22
|
+
super(attribute)
|
23
|
+
@options = {}
|
24
|
+
end
|
21
25
|
|
22
26
|
def only_integer
|
23
|
-
@only_integer = true
|
27
|
+
@options[:only_integer] = true
|
24
28
|
self
|
25
29
|
end
|
26
30
|
|
@@ -43,7 +47,7 @@ module Shoulda # :nodoc:
|
|
43
47
|
private
|
44
48
|
|
45
49
|
def allowed_type
|
46
|
-
if @only_integer
|
50
|
+
if @options[:only_integer]
|
47
51
|
"integer"
|
48
52
|
else
|
49
53
|
"numeric"
|
@@ -51,9 +55,9 @@ module Shoulda # :nodoc:
|
|
51
55
|
end
|
52
56
|
|
53
57
|
def disallows_non_integers?
|
54
|
-
if @only_integer
|
58
|
+
if @options[:only_integer]
|
55
59
|
message = @expected_message || :not_an_integer
|
56
|
-
disallows_value_of(0.1, message)
|
60
|
+
disallows_value_of(0.1, message)
|
57
61
|
else
|
58
62
|
true
|
59
63
|
end
|
@@ -47,15 +47,18 @@ module Shoulda # :nodoc:
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def collection?
|
50
|
-
if
|
51
|
-
reflection = @subject.class.reflect_on_association(@attribute)
|
50
|
+
if reflection
|
52
51
|
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
53
52
|
else
|
54
53
|
false
|
55
54
|
end
|
56
55
|
end
|
57
|
-
end
|
58
56
|
|
57
|
+
def reflection
|
58
|
+
@subject.class.respond_to?(:reflect_on_association) &&
|
59
|
+
@subject.class.reflect_on_association(@attribute)
|
60
|
+
end
|
61
|
+
end
|
59
62
|
end
|
60
63
|
end
|
61
64
|
end
|
@@ -38,11 +38,12 @@ module Shoulda # :nodoc:
|
|
38
38
|
include Helpers
|
39
39
|
|
40
40
|
def initialize(attribute)
|
41
|
-
|
41
|
+
super(attribute)
|
42
|
+
@options = {}
|
42
43
|
end
|
43
44
|
|
44
45
|
def scoped_to(*scopes)
|
45
|
-
@scopes = [*scopes].flatten
|
46
|
+
@options[:scopes] = [*scopes].flatten
|
46
47
|
self
|
47
48
|
end
|
48
49
|
|
@@ -52,30 +53,30 @@ module Shoulda # :nodoc:
|
|
52
53
|
end
|
53
54
|
|
54
55
|
def case_insensitive
|
55
|
-
@case_insensitive = true
|
56
|
+
@options[:case_insensitive] = true
|
56
57
|
self
|
57
58
|
end
|
58
59
|
|
59
60
|
def description
|
60
61
|
result = "require "
|
61
|
-
result << "case sensitive " unless @case_insensitive
|
62
|
+
result << "case sensitive " unless @options[:case_insensitive]
|
62
63
|
result << "unique value for #{@attribute}"
|
63
|
-
result << " scoped to #{@scopes.join(', ')}"
|
64
|
+
result << " scoped to #{@options[:scopes].join(', ')}" if @options[:scopes].present?
|
64
65
|
result
|
65
66
|
end
|
66
67
|
|
67
68
|
def matches?(subject)
|
68
69
|
@subject = subject.class.new
|
69
70
|
@expected_message ||= :taken
|
70
|
-
|
71
|
+
has_existing? &&
|
71
72
|
set_scoped_attributes &&
|
72
|
-
validate_attribute &&
|
73
|
-
validate_after_scope_change
|
73
|
+
validate_attribute? &&
|
74
|
+
validate_after_scope_change?
|
74
75
|
end
|
75
76
|
|
76
77
|
private
|
77
78
|
|
78
|
-
def
|
79
|
+
def has_existing?
|
79
80
|
if @existing = @subject.class.find(:first)
|
80
81
|
true
|
81
82
|
else
|
@@ -85,32 +86,34 @@ module Shoulda # :nodoc:
|
|
85
86
|
end
|
86
87
|
|
87
88
|
def set_scoped_attributes
|
88
|
-
|
89
|
-
@scopes.
|
89
|
+
if @options[:scopes].present?
|
90
|
+
@options[:scopes].all? do |scope|
|
90
91
|
setter = :"#{scope}="
|
91
|
-
|
92
|
-
@
|
93
|
-
|
94
|
-
|
92
|
+
if @subject.respond_to?(setter)
|
93
|
+
@subject.send("#{scope}=", @existing.send(scope))
|
94
|
+
true
|
95
|
+
else
|
96
|
+
@failure_message = "#{class_name} doesn't seem to have a #{scope} attribute."
|
97
|
+
false
|
95
98
|
end
|
96
|
-
@subject.send("#{scope}=", @existing.send(scope))
|
97
99
|
end
|
100
|
+
else
|
101
|
+
true
|
98
102
|
end
|
99
|
-
true
|
100
103
|
end
|
101
104
|
|
102
|
-
def validate_attribute
|
105
|
+
def validate_attribute?
|
103
106
|
disallows_value_of(existing_value, @expected_message)
|
104
107
|
end
|
105
108
|
|
106
109
|
# TODO: There is a chance that we could change the scoped field
|
107
110
|
# to a value that's already taken. An alternative implementation
|
108
111
|
# could actually find all values for scope and create a unique
|
109
|
-
def validate_after_scope_change
|
110
|
-
if @scopes.blank?
|
112
|
+
def validate_after_scope_change?
|
113
|
+
if @options[:scopes].blank?
|
111
114
|
true
|
112
115
|
else
|
113
|
-
@scopes.all? do |scope|
|
116
|
+
@options[:scopes].all? do |scope|
|
114
117
|
previous_value = @existing.send(scope)
|
115
118
|
|
116
119
|
# Assume the scope is a foreign key if the field is nil
|
@@ -142,11 +145,12 @@ module Shoulda # :nodoc:
|
|
142
145
|
|
143
146
|
def existing_value
|
144
147
|
value = @existing.send(@attribute)
|
145
|
-
|
148
|
+
if @options[:case_insensitive] && value.respond_to?(:swapcase!)
|
149
|
+
value.swapcase!
|
150
|
+
end
|
146
151
|
value
|
147
152
|
end
|
148
153
|
end
|
149
|
-
|
150
154
|
end
|
151
155
|
end
|
152
156
|
end
|
@@ -24,21 +24,21 @@ module Shoulda
|
|
24
24
|
class AcceptNestedAttributesForMatcher
|
25
25
|
def initialize(name)
|
26
26
|
@name = name
|
27
|
-
|
27
|
+
@options = {}
|
28
28
|
end
|
29
29
|
|
30
30
|
def allow_destroy(allow_destroy)
|
31
|
-
@allow_destroy = allow_destroy
|
31
|
+
@options[:allow_destroy] = allow_destroy
|
32
32
|
self
|
33
33
|
end
|
34
34
|
|
35
35
|
def limit(limit)
|
36
|
-
@limit = limit
|
36
|
+
@options[:limit] = limit
|
37
37
|
self
|
38
38
|
end
|
39
39
|
|
40
40
|
def update_only(update_only)
|
41
|
-
@update_only = update_only
|
41
|
+
@options[:update_only] = update_only
|
42
42
|
self
|
43
43
|
end
|
44
44
|
|
@@ -60,9 +60,15 @@ module Shoulda
|
|
60
60
|
|
61
61
|
def description
|
62
62
|
description = "accepts_nested_attributes_for :#{@name}"
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
if @options.key?(:allow_destroy)
|
64
|
+
description += " allow_destroy => #{@options[:allow_destroy]}"
|
65
|
+
end
|
66
|
+
if @options.key?(:limit)
|
67
|
+
description += " limit => #{@options[:limit]}"
|
68
|
+
end
|
69
|
+
if @options.key?(:update_only)
|
70
|
+
description += " update_only => #{@options[:update_only]}"
|
71
|
+
end
|
66
72
|
description
|
67
73
|
end
|
68
74
|
|
@@ -78,31 +84,30 @@ module Shoulda
|
|
78
84
|
end
|
79
85
|
|
80
86
|
def allow_destroy_correct?
|
81
|
-
|
82
|
-
|
83
|
-
else
|
84
|
-
@problem = (@allow_destroy ? "should" : "should not") +
|
85
|
-
" allow destroy"
|
86
|
-
false
|
87
|
-
end
|
87
|
+
failure_message = "#{should_or_should_not(@options[:allow_destroy])} allow destroy"
|
88
|
+
verify_option_is_correct(:allow_destroy, failure_message)
|
88
89
|
end
|
89
90
|
|
90
91
|
def limit_correct?
|
91
|
-
|
92
|
-
|
93
|
-
else
|
94
|
-
@problem = "limit should be #@limit, got #{config[:limit]}"
|
95
|
-
false
|
96
|
-
end
|
92
|
+
failure_message = "limit should be #{@options[:limit]}, got #{config[:limit]}"
|
93
|
+
verify_option_is_correct(:limit, failure_message)
|
97
94
|
end
|
98
95
|
|
99
96
|
def update_only_correct?
|
100
|
-
|
101
|
-
|
97
|
+
failure_message = "#{should_or_should_not(@options[:update_only])} be update only"
|
98
|
+
verify_option_is_correct(:update_only, failure_message)
|
99
|
+
end
|
100
|
+
|
101
|
+
def verify_option_is_correct(option, failure_message)
|
102
|
+
if @options.key?(option)
|
103
|
+
if @options[option] == config[option]
|
104
|
+
true
|
105
|
+
else
|
106
|
+
@problem = failure_message
|
107
|
+
false
|
108
|
+
end
|
102
109
|
else
|
103
|
-
|
104
|
-
" be update only"
|
105
|
-
false
|
110
|
+
true
|
106
111
|
end
|
107
112
|
end
|
108
113
|
|
@@ -121,6 +126,14 @@ module Shoulda
|
|
121
126
|
def expectation
|
122
127
|
"#{model_class.name} to accept nested attributes for #{@name}"
|
123
128
|
end
|
129
|
+
|
130
|
+
def should_or_should_not(value)
|
131
|
+
if value
|
132
|
+
"should"
|
133
|
+
else
|
134
|
+
"should not"
|
135
|
+
end
|
136
|
+
end
|
124
137
|
end
|
125
138
|
end
|
126
139
|
end
|