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.
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