shoulda-matchers 1.3.0 → 1.4.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 (37) hide show
  1. data/.travis.yml +1 -0
  2. data/Appraisals +3 -3
  3. data/Gemfile.lock +2 -2
  4. data/NEWS.md +20 -0
  5. data/README.md +1 -1
  6. data/gemfiles/3.0.gemfile +1 -1
  7. data/gemfiles/3.0.gemfile.lock +27 -27
  8. data/gemfiles/3.1.gemfile +1 -1
  9. data/gemfiles/3.1.gemfile.lock +36 -36
  10. data/gemfiles/3.2.gemfile +1 -1
  11. data/gemfiles/3.2.gemfile.lock +35 -35
  12. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +4 -1
  13. data/lib/shoulda/matchers/active_model.rb +14 -0
  14. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +45 -21
  15. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +28 -6
  16. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +22 -4
  17. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +1 -0
  18. data/lib/shoulda/matchers/active_model/errors.rb +7 -0
  19. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
  20. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +9 -1
  21. data/lib/shoulda/matchers/active_model/validation_matcher.rb +27 -8
  22. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
  23. data/lib/shoulda/matchers/active_record/association_matcher.rb +14 -5
  24. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +8 -5
  25. data/lib/shoulda/matchers/version.rb +1 -1
  26. data/shoulda-matchers.gemspec +1 -1
  27. data/spec/shoulda/action_controller/set_the_flash_matcher_spec.rb +6 -0
  28. data/spec/shoulda/active_model/allow_value_matcher_spec.rb +32 -0
  29. data/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb +23 -1
  30. data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +49 -0
  31. data/spec/shoulda/active_model/ensure_length_of_matcher_spec.rb +13 -0
  32. data/spec/shoulda/active_model/exception_message_finder_spec.rb +112 -0
  33. data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +15 -0
  34. data/spec/shoulda/active_model/validation_message_finder_spec.rb +107 -0
  35. data/spec/shoulda/active_record/association_matcher_spec.rb +8 -0
  36. data/spec/shoulda/active_record/have_db_index_matcher_spec.rb +17 -0
  37. 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
- "allow #{@attribute} to be set to #{allowed_values}"
72
+ message_finder.allow_description(allowed_values)
64
73
  end
65
74
 
66
75
  private
67
76
 
68
77
  def errors_match?
69
- if @instance.valid?
70
- false
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
- if expected_message
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
- if @instance.errors.respond_to?(:[])
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
- ["errors", includes_expected_message, "when #{@attribute} is set to #{@value.inspect}"].join(' ')
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
- if @instance.errors.empty?
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
- default_error_message(@options[:expected_message], :model_name => model_name, :attribute => @attribute)
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 #{@range.inspect}"
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
- allows_lower_value &&
42
- disallows_minimum_value &&
43
- allows_higher_value &&
44
- disallows_maximum_value
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 range of allowed values for this attribute
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
- @options[:allow_blank] == allows_value_of('')
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
@@ -58,6 +58,7 @@ module Shoulda # :nodoc:
58
58
  @options[:minimum] = length
59
59
  @options[:maximum] = length
60
60
  @short_message ||= :wrong_length
61
+ @long_message ||= :wrong_length
61
62
  self
62
63
  end
63
64
 
@@ -0,0 +1,7 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveModel # :nodoc:
4
+ class CouldNotDetermineValueOutsideOfArray < RuntimeError; end
5
+ end
6
+ end
7
+ 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 ||= 0
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 = AllowValueMatcher.
24
- new(value).
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 = AllowValueMatcher.
38
- new(value).
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