WIP
This commit is contained in:
parent
3d007a2b0e
commit
ec240c3b52
|
@ -2,7 +2,6 @@ require 'shoulda/matchers/active_model/helpers'
|
|||
require 'shoulda/matchers/active_model/qualifiers'
|
||||
require 'shoulda/matchers/active_model/validation_matcher'
|
||||
require 'shoulda/matchers/active_model/validation_matcher/build_description'
|
||||
require 'shoulda/matchers/active_model/validation_matcher/build_expectation'
|
||||
require 'shoulda/matchers/active_model/validator'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_changed_value_error'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_does_not_exist_error'
|
||||
|
@ -10,6 +9,7 @@ require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute
|
|||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setter_and_validator'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setters'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setters_and_validators'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/build_expectation_description'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/successful_check'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/successful_setting'
|
||||
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher'
|
||||
|
|
|
@ -111,65 +111,6 @@ module Shoulda
|
|||
ValidationMatcher::BuildDescription.call(self, simple_description)
|
||||
end
|
||||
|
||||
def expectation_description
|
||||
"Expected #{model} #{expectation}."
|
||||
end
|
||||
|
||||
def positive_aberration_description
|
||||
validator = result.validator
|
||||
message = ''
|
||||
|
||||
if validator.validation_message_type_matches?
|
||||
if validator.has_validation_messages?
|
||||
message << 'The record was invalid, but'
|
||||
|
||||
if validator.captured_validation_exception?
|
||||
message << ' the exception message was '
|
||||
message << validator.validation_exception_message.inspect
|
||||
message << ' instead.'
|
||||
else
|
||||
message << " it produced these validation errors instead:\n\n"
|
||||
message << validator.all_formatted_validation_error_messages
|
||||
end
|
||||
elsif expects_strict?
|
||||
message << 'However, no such exception was raised.'
|
||||
else
|
||||
message << 'However, no such error was found on '
|
||||
message << ":#{attribute_to_check_message_against}."
|
||||
end
|
||||
elsif validator.captured_validation_exception?
|
||||
message << 'The record was invalid, but it'
|
||||
message << ' raised a validation exception '
|
||||
message << validator.validation_exception_message.inspect
|
||||
message << ' instead.'
|
||||
else
|
||||
message << 'The record was invalid, but '
|
||||
message << " it produced these validation errors instead:\n\n"
|
||||
message << validator.all_formatted_validation_error_messages
|
||||
end
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def negative_aberration_description
|
||||
validator = result.validator
|
||||
|
||||
# description = 'However, '
|
||||
|
||||
# if validator.captured_validation_exception?
|
||||
# description << ' it raised a validation exception with the message '
|
||||
# description << validator.validation_exception_message.inspect
|
||||
# description << '.'
|
||||
# else
|
||||
# description << " it produced these validation errors:\n\n"
|
||||
# description << validator.all_formatted_validation_error_messages
|
||||
# end
|
||||
|
||||
# description
|
||||
|
||||
"However, it did."
|
||||
end
|
||||
|
||||
def expectation_clauses_for_values_to_preset
|
||||
attribute_setters_for_values_to_preset.
|
||||
select(&:attribute_set?).
|
||||
|
@ -182,10 +123,6 @@ module Shoulda
|
|||
map(&:attribute_setter_expectation_clause)
|
||||
end
|
||||
|
||||
def model
|
||||
subject.class
|
||||
end
|
||||
|
||||
def last_attribute_setter_used
|
||||
result.attribute_setter
|
||||
end
|
||||
|
@ -194,6 +131,10 @@ module Shoulda
|
|||
last_attribute_setter_used.value_written
|
||||
end
|
||||
|
||||
def model
|
||||
subject.class
|
||||
end
|
||||
|
||||
def pretty_print(pp)
|
||||
Shoulda::Matchers::Util.pretty_print(self, pp, {
|
||||
was_negated: was_negated?,
|
||||
|
@ -342,6 +283,72 @@ module Shoulda
|
|||
Shoulda::Matchers.word_wrap(message)
|
||||
end
|
||||
|
||||
def positive_aberration_description
|
||||
validator = result.validator
|
||||
|
||||
# description = 'However, '
|
||||
|
||||
# if validator.captured_validation_exception?
|
||||
# description << ' it raised a validation exception with the message '
|
||||
# description << validator.validation_exception_message.inspect
|
||||
# description << '.'
|
||||
# else
|
||||
# description << " it produced these validation errors:\n\n"
|
||||
# description << validator.all_formatted_validation_error_messages
|
||||
# end
|
||||
|
||||
# description
|
||||
|
||||
'However, it did fail with that error.'
|
||||
end
|
||||
|
||||
def negative_aberration_description
|
||||
validator = result.validator
|
||||
message = ''
|
||||
|
||||
if validator.validation_message_type_matches?
|
||||
if validator.has_validation_messages?
|
||||
message << 'The record did indeed fail validation, but'
|
||||
|
||||
if validator.captured_validation_exception?
|
||||
message << ' the exception message was '
|
||||
message << validator.validation_exception_message.inspect
|
||||
message << ' instead.'
|
||||
else
|
||||
message << ' it produced these validation errors on '
|
||||
message << ":#{attribute_to_check_message_against} instead:\n\n"
|
||||
message << validator.formatted_validation_messages
|
||||
end
|
||||
elsif expects_strict?
|
||||
message << 'However, no such exception was raised.'
|
||||
else
|
||||
message << 'However, no such error was found on '
|
||||
message << ":#{attribute_to_check_message_against}"
|
||||
|
||||
if context.present?
|
||||
message << ' there. (Perhaps the validation was run under a '
|
||||
message << 'different context?)'
|
||||
else
|
||||
message << '.'
|
||||
end
|
||||
end
|
||||
elsif validator.captured_validation_exception?
|
||||
message << 'The record did indeed fail validation, but it '
|
||||
message << 'raised a validation exception '
|
||||
message << validator.validation_exception_message.inspect
|
||||
message << ' instead.'
|
||||
elsif validator.has_any_validation_errors?
|
||||
message << 'The record did indeed fail validation, but instead of '
|
||||
message << 'raising an exception, it produced errors on '
|
||||
message << "these attributes:\n\n"
|
||||
message << validator.all_formatted_validation_errors
|
||||
else
|
||||
message << 'However, it did not fail validation.'
|
||||
end
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run(strategy)
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
class AllowOrDisallowValueMatcher
|
||||
# @private
|
||||
class BuildExpectationDescription
|
||||
def self.call(matcher, negated:)
|
||||
new(matcher, negated: negated).call
|
||||
end
|
||||
|
||||
def initialize(matcher, negated:)
|
||||
@matcher = matcher
|
||||
@negated = negated
|
||||
end
|
||||
|
||||
def call
|
||||
parts = [
|
||||
'With',
|
||||
attribute_setter_clauses + ',',
|
||||
'the',
|
||||
matcher.model,
|
||||
'was expected',
|
||||
expectation,
|
||||
]
|
||||
|
||||
parts.join(' ') + '.'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :matcher
|
||||
|
||||
def negated?
|
||||
@negated
|
||||
end
|
||||
|
||||
def attribute_setter_clauses
|
||||
clauses = [
|
||||
clauses_for_values_to_preset,
|
||||
clauses_for_values_to_set,
|
||||
]
|
||||
|
||||
clauses.select(&:present?).join(' and ')
|
||||
end
|
||||
|
||||
def clauses_for_values_to_preset
|
||||
matcher.expectation_clauses_for_values_to_preset
|
||||
end
|
||||
|
||||
def clauses_for_values_to_set
|
||||
matcher.expectation_clauses_for_values_to_set
|
||||
end
|
||||
|
||||
def expectation
|
||||
if matcher.expects_custom_validation_message?
|
||||
expectation_with_expected_message
|
||||
else
|
||||
expectation_without_expected_message
|
||||
end
|
||||
end
|
||||
|
||||
def expectation_with_expected_message
|
||||
parts = [
|
||||
to_or_not_to,
|
||||
'fail validation',
|
||||
context_clause,
|
||||
'by',
|
||||
error_message_clause,
|
||||
]
|
||||
parts.select(&:present?).join(' ')
|
||||
end
|
||||
|
||||
def to_or_not_to
|
||||
if negated?
|
||||
'to'
|
||||
else
|
||||
'not to'
|
||||
end
|
||||
end
|
||||
|
||||
def context_clause
|
||||
if matcher.context.present?
|
||||
"(context: #{matcher.context.inspect})"
|
||||
end
|
||||
end
|
||||
|
||||
def error_message_clause
|
||||
if matcher.expects_strict?
|
||||
'raising an exception'
|
||||
else
|
||||
clause = ''
|
||||
|
||||
if matcher.expected_message.is_a?(Regexp)
|
||||
clause << 'placing an error matching '
|
||||
else
|
||||
clause << 'placing the error '
|
||||
end
|
||||
|
||||
clause << "#{matcher.expected_message.inspect} "
|
||||
clause << "on :#{matcher.attribute_to_check_message_against}"
|
||||
end
|
||||
end
|
||||
|
||||
def expectation_without_expected_message
|
||||
if negated?
|
||||
'be invalid'
|
||||
else
|
||||
'be valid'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -309,42 +309,6 @@ module Shoulda
|
|||
# "something other than #{inspected_values_to_set}"
|
||||
# end
|
||||
|
||||
def expectation
|
||||
error_type_clause =
|
||||
if expects_strict?
|
||||
'raising an exception'
|
||||
else
|
||||
'placing the error'
|
||||
end
|
||||
|
||||
if expected_message
|
||||
preface =
|
||||
"not to fail validation by #{error_type_clause} " +
|
||||
"#{expected_message.inspect} on " +
|
||||
":#{attribute_to_check_message_against}"
|
||||
|
||||
ValidationMatcher::BuildExpectation.call(
|
||||
self,
|
||||
preface,
|
||||
state: :valid,
|
||||
)
|
||||
else
|
||||
ValidationMatcher::BuildExpectation.call(
|
||||
self,
|
||||
'to be valid',
|
||||
state: :valid,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def aberration_description
|
||||
if was_negated?
|
||||
positive_aberration_description
|
||||
else
|
||||
negative_aberration_description
|
||||
end
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
|
||||
|
@ -366,6 +330,21 @@ module Shoulda
|
|||
def failure_message_when_negated
|
||||
negative_failure_message
|
||||
end
|
||||
|
||||
def expectation_description
|
||||
AllowOrDisallowValueMatcher::BuildExpectationDescription.call(
|
||||
self,
|
||||
negated: was_negated?,
|
||||
)
|
||||
end
|
||||
|
||||
def aberration_description
|
||||
if was_negated?
|
||||
negative_aberration_description
|
||||
else
|
||||
positive_aberration_description
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,39 +7,18 @@ module Shoulda
|
|||
# inspected_values_to_set
|
||||
# end
|
||||
|
||||
def expectation
|
||||
error_type_clause =
|
||||
if expects_strict?
|
||||
'raising an exception'
|
||||
else
|
||||
'placing the error'
|
||||
end
|
||||
|
||||
if expected_message
|
||||
preface =
|
||||
"to fail validation by #{error_type_clause} " +
|
||||
"#{expected_message.inspect} on " +
|
||||
":#{attribute_to_check_message_against}"
|
||||
|
||||
ValidationMatcher::BuildExpectation.call(
|
||||
self,
|
||||
preface,
|
||||
state: :invalid,
|
||||
)
|
||||
else
|
||||
ValidationMatcher::BuildExpectation.call(
|
||||
self,
|
||||
'to be invalid',
|
||||
state: :invalid,
|
||||
)
|
||||
end
|
||||
def expectation_description
|
||||
AllowOrDisallowValueMatcher::BuildExpectationDescription.call(
|
||||
self,
|
||||
negated: !was_negated?,
|
||||
)
|
||||
end
|
||||
|
||||
def aberration_description
|
||||
if was_negated?
|
||||
negative_aberration_description
|
||||
else
|
||||
positive_aberration_description
|
||||
else
|
||||
negative_aberration_description
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,10 @@ module Shoulda
|
|||
list_items.join("\n")
|
||||
end
|
||||
|
||||
def format_attribute_specific_validation_errors(errors)
|
||||
errors.map { |error| "* #{error}" }.join("\n")
|
||||
end
|
||||
|
||||
def default_error_message(type, attribute, options = {})
|
||||
model_name = options.delete(:model_name)
|
||||
instance = options.delete(:instance)
|
||||
|
|
|
@ -9,143 +9,92 @@ module Shoulda
|
|||
:>= => :greater_than_or_equal_to,
|
||||
:< => :less_than,
|
||||
:<= => :less_than_or_equal_to,
|
||||
:== => :equal_to
|
||||
}
|
||||
:== => :equal_to,
|
||||
}.freeze
|
||||
|
||||
def initialize(numericality_matcher, value, operator)
|
||||
super(nil)
|
||||
unless numericality_matcher.respond_to? :diff_to_compare
|
||||
raise ArgumentError, 'numericality_matcher is invalid'
|
||||
def initialize(numericality_matcher, attribute, value, operator)
|
||||
super(attribute)
|
||||
|
||||
if !numericality_matcher.respond_to?(:diff_to_compare)
|
||||
raise ArgumentError.new(
|
||||
'The given numericality matcher does not respond to ' +
|
||||
':diff_to_compare',
|
||||
)
|
||||
end
|
||||
|
||||
@numericality_matcher = numericality_matcher
|
||||
@value = value
|
||||
@operator = operator
|
||||
@message = ERROR_MESSAGES[operator]
|
||||
@message = ERROR_MESSAGES.fetch(operator)
|
||||
# @comparison_expectation = COMPARISON_EXPECTATIONS.fetch(operator)
|
||||
end
|
||||
|
||||
def simple_description
|
||||
description = ''
|
||||
# def comparison_description
|
||||
# "#{comparison_expectation} #{value}"
|
||||
# end
|
||||
|
||||
if expects_strict?
|
||||
description << ' strictly'
|
||||
protected
|
||||
|
||||
def add_submatchers
|
||||
comparison_tuples.each do |diff, add_submatcher_method_name|
|
||||
__send__(add_submatcher_method_name, diff, nil) do |matcher|
|
||||
matcher.with_message(message, values: { count: value })
|
||||
end
|
||||
end
|
||||
|
||||
description +
|
||||
"disallow :#{attribute} from being a number that is not " +
|
||||
"#{comparison_expectation} #{@value}"
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
@attribute = attribute
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expects_custom_validation_message = true
|
||||
@message = message
|
||||
self
|
||||
end
|
||||
|
||||
def expects_custom_validation_message?
|
||||
@expects_custom_validation_message
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
all_bounds_correct?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
last_failing_submatcher.failure_message
|
||||
end
|
||||
|
||||
def failure_message_when_negated
|
||||
last_failing_submatcher.failure_message_when_negated
|
||||
end
|
||||
|
||||
def comparison_description
|
||||
"#{comparison_expectation} #{@value}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def all_bounds_correct?
|
||||
failing_submatchers.empty?
|
||||
end
|
||||
attr_reader :numericality_matcher, :value, :operator, :message,
|
||||
:comparison_expectation
|
||||
|
||||
def failing_submatchers
|
||||
submatchers_and_results.
|
||||
select { |x| !x[:matched] }.
|
||||
map { |x| x[:matcher] }
|
||||
end
|
||||
|
||||
def last_failing_submatcher
|
||||
failing_submatchers.last
|
||||
end
|
||||
|
||||
def submatchers
|
||||
@_submatchers ||=
|
||||
comparison_combos.map do |diff, submatcher_method_name|
|
||||
matcher = __send__(submatcher_method_name, diff, nil)
|
||||
matcher.with_message(@message, values: { count: @value })
|
||||
matcher
|
||||
end
|
||||
end
|
||||
|
||||
def submatchers_and_results
|
||||
@_submatchers_and_results ||=
|
||||
submatchers.map do |matcher|
|
||||
{ matcher: matcher, matched: matcher.matches?(@subject) }
|
||||
end
|
||||
end
|
||||
|
||||
def comparison_combos
|
||||
diffs_to_compare.zip(submatcher_method_names)
|
||||
end
|
||||
|
||||
def submatcher_method_names
|
||||
assertions.map do |value|
|
||||
if value
|
||||
:allow_value_matcher
|
||||
else
|
||||
:disallow_value_matcher
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def assertions
|
||||
case @operator
|
||||
when :>
|
||||
[false, false, true]
|
||||
when :>=
|
||||
[false, true, true]
|
||||
when :==
|
||||
[false, true, false]
|
||||
when :<
|
||||
[true, false, false]
|
||||
when :<=
|
||||
[true, true, false]
|
||||
end
|
||||
def comparison_tuples
|
||||
diffs_to_compare.zip(add_submatcher_method_names)
|
||||
end
|
||||
|
||||
def diffs_to_compare
|
||||
diff_to_compare = @numericality_matcher.diff_to_compare
|
||||
values = [-1, 0, 1].map { |sign| @value + (diff_to_compare * sign) }
|
||||
diff_to_compare = numericality_matcher.diff_to_compare
|
||||
values = [-1, 0, 1].map { |sign| value + (diff_to_compare * sign) }
|
||||
|
||||
if @numericality_matcher.given_numeric_column?
|
||||
if numericality_matcher.given_numeric_column?
|
||||
values
|
||||
else
|
||||
values.map(&:to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def comparison_expectation
|
||||
case @operator
|
||||
when :> then "greater than"
|
||||
when :>= then "greater than or equal to"
|
||||
when :== then "equal to"
|
||||
when :< then "less than"
|
||||
when :<= then "less than or equal to"
|
||||
def add_submatcher_method_names
|
||||
case operator
|
||||
when :>
|
||||
[
|
||||
:add_submatcher_disallowing,
|
||||
:add_submatcher_disallowing,
|
||||
:add_submatcher_allowing,
|
||||
]
|
||||
when :>=
|
||||
[
|
||||
:add_submatcher_disallowing,
|
||||
:add_submatcher_allowing,
|
||||
:add_submatcher_allowing,
|
||||
]
|
||||
when :==
|
||||
[
|
||||
:add_submatcher_disallowing,
|
||||
:add_submatcher_allowing,
|
||||
:add_submatcher_disallowing,
|
||||
]
|
||||
when :<
|
||||
[
|
||||
:add_submatcher_allowing,
|
||||
:add_submatcher_disallowing,
|
||||
:add_submatcher_disallowing,
|
||||
]
|
||||
when :<=
|
||||
[
|
||||
:add_submatcher_allowing,
|
||||
:add_submatcher_allowing,
|
||||
:add_submatcher_disallowing,
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,19 +6,10 @@ module Shoulda
|
|||
class EvenNumberMatcher < NumericTypeMatcher
|
||||
NON_EVEN_NUMBER_VALUE = 1
|
||||
|
||||
def simple_description
|
||||
description = ''
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
if expects_strict?
|
||||
description << 'strictly '
|
||||
end
|
||||
|
||||
description +
|
||||
"disallow :#{attribute} from being an odd number"
|
||||
end
|
||||
|
||||
def allowed_type_adjective
|
||||
'even'
|
||||
with_message(:even)
|
||||
end
|
||||
|
||||
def diff_to_compare
|
||||
|
@ -27,12 +18,8 @@ module Shoulda
|
|||
|
||||
protected
|
||||
|
||||
def wrap_disallow_value_matcher(matcher)
|
||||
matcher.with_message(:even)
|
||||
end
|
||||
|
||||
def disallowed_value
|
||||
if @numeric_type_matcher.given_numeric_column?
|
||||
if numericality_matcher.given_numeric_column?
|
||||
NON_EVEN_NUMBER_VALUE
|
||||
else
|
||||
NON_EVEN_NUMBER_VALUE.to_s
|
||||
|
|
|
@ -5,34 +5,11 @@ module Shoulda
|
|||
module ActiveModel
|
||||
module NumericalityMatchers
|
||||
# @private
|
||||
class NumericTypeMatcher
|
||||
extend Forwardable
|
||||
class NumericTypeMatcher < ValidationMatcher
|
||||
def initialize(numericality_matcher, attribute)
|
||||
super(attribute)
|
||||
|
||||
def_delegators(
|
||||
:disallow_value_matcher,
|
||||
:expects_custom_validation_message?,
|
||||
:expects_strict?,
|
||||
:failure_message,
|
||||
:failure_message_when_negated,
|
||||
:ignore_interference_by_writer,
|
||||
:ignoring_interference_by_writer,
|
||||
:matches?,
|
||||
:on,
|
||||
:strict,
|
||||
:with_message,
|
||||
)
|
||||
|
||||
def initialize(numeric_type_matcher, attribute)
|
||||
@numeric_type_matcher = numeric_type_matcher
|
||||
@attribute = attribute
|
||||
end
|
||||
|
||||
def allowed_type_name
|
||||
'number'
|
||||
end
|
||||
|
||||
def allowed_type_adjective
|
||||
''
|
||||
@numericality_matcher = numericality_matcher
|
||||
end
|
||||
|
||||
def diff_to_compare
|
||||
|
@ -41,26 +18,15 @@ module Shoulda
|
|||
|
||||
protected
|
||||
|
||||
attr_reader :attribute
|
||||
attr_reader :numericality_matcher
|
||||
|
||||
def wrap_disallow_value_matcher(matcher)
|
||||
raise NotImplementedError
|
||||
def add_submatchers
|
||||
add_submatcher_disallowing(disallowed_value)
|
||||
end
|
||||
|
||||
def disallowed_value
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disallow_value_matcher
|
||||
@_disallow_value_matcher ||= begin
|
||||
DisallowValueMatcher.new(disallowed_value).tap do |matcher|
|
||||
matcher.for(attribute)
|
||||
wrap_disallow_value_matcher(matcher)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,19 +6,10 @@ module Shoulda
|
|||
class OddNumberMatcher < NumericTypeMatcher
|
||||
NON_ODD_NUMBER_VALUE = 2
|
||||
|
||||
def simple_description
|
||||
description = ''
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
if expects_strict?
|
||||
description << 'strictly '
|
||||
end
|
||||
|
||||
description +
|
||||
"disallow :#{attribute} from being an even number"
|
||||
end
|
||||
|
||||
def allowed_type_adjective
|
||||
'odd'
|
||||
with_message(:odd)
|
||||
end
|
||||
|
||||
def diff_to_compare
|
||||
|
@ -27,12 +18,8 @@ module Shoulda
|
|||
|
||||
protected
|
||||
|
||||
def wrap_disallow_value_matcher(matcher)
|
||||
matcher.with_message(:odd)
|
||||
end
|
||||
|
||||
def disallowed_value
|
||||
if @numeric_type_matcher.given_numeric_column?
|
||||
if numericality_matcher.given_numeric_column?
|
||||
NON_ODD_NUMBER_VALUE
|
||||
else
|
||||
NON_ODD_NUMBER_VALUE.to_s
|
||||
|
|
|
@ -6,18 +6,10 @@ module Shoulda
|
|||
class OnlyIntegerMatcher < NumericTypeMatcher
|
||||
NON_INTEGER_VALUE = 0.1
|
||||
|
||||
def simple_description
|
||||
description = ''
|
||||
def initialize(*args)
|
||||
super
|
||||
|
||||
if expects_strict?
|
||||
description << ' strictly'
|
||||
end
|
||||
|
||||
description + "disallow :#{attribute} from being a decimal number"
|
||||
end
|
||||
|
||||
def allowed_type_name
|
||||
'integer'
|
||||
with_message(:not_an_integer)
|
||||
end
|
||||
|
||||
def diff_to_compare
|
||||
|
@ -26,12 +18,8 @@ module Shoulda
|
|||
|
||||
protected
|
||||
|
||||
def wrap_disallow_value_matcher(matcher)
|
||||
matcher.with_message(:not_an_integer)
|
||||
end
|
||||
|
||||
def disallowed_value
|
||||
if @numeric_type_matcher.given_numeric_column?
|
||||
if numericality_matcher.given_numeric_column?
|
||||
NON_INTEGER_VALUE
|
||||
else
|
||||
NON_INTEGER_VALUE.to_s
|
||||
|
|
|
@ -303,317 +303,228 @@ module Shoulda
|
|||
end
|
||||
|
||||
# @private
|
||||
class ValidateNumericalityOfMatcher
|
||||
NUMERIC_NAME = 'number'
|
||||
NON_NUMERIC_VALUE = 'abcd'
|
||||
class ValidateNumericalityOfMatcher < ValidationMatcher
|
||||
NON_NUMERIC_VALUE = 'not-a-number'.freeze
|
||||
DEFAULT_DIFF_TO_COMPARE = 1
|
||||
|
||||
include Qualifiers::IgnoringInterferenceByWriter
|
||||
|
||||
attr_reader :diff_to_compare
|
||||
|
||||
def initialize(attribute)
|
||||
super
|
||||
@attribute = attribute
|
||||
@submatchers = []
|
||||
super(attribute)
|
||||
@options = {
|
||||
only_integer: false,
|
||||
allow_nil: false,
|
||||
comparisons: {},
|
||||
cardinality: nil,
|
||||
}
|
||||
@diff_to_compare = DEFAULT_DIFF_TO_COMPARE
|
||||
@expects_custom_validation_message = false
|
||||
@expects_to_allow_nil = false
|
||||
@expects_strict = false
|
||||
@allowed_type_adjective = nil
|
||||
@allowed_type_name = 'number'
|
||||
@context = nil
|
||||
@expected_message = nil
|
||||
end
|
||||
|
||||
def strict
|
||||
@expects_strict = true
|
||||
self
|
||||
end
|
||||
|
||||
def expects_strict?
|
||||
@expects_strict
|
||||
end
|
||||
|
||||
def only_integer
|
||||
prepare_submatcher(
|
||||
NumericalityMatchers::OnlyIntegerMatcher.new(self, @attribute)
|
||||
)
|
||||
options[:only_integer] = true
|
||||
self
|
||||
end
|
||||
|
||||
def allow_nil
|
||||
@expects_to_allow_nil = true
|
||||
prepare_submatcher(
|
||||
AllowValueMatcher.new(nil)
|
||||
.for(@attribute)
|
||||
.with_message(:not_a_number)
|
||||
)
|
||||
options[:allow_nil] = true
|
||||
self
|
||||
end
|
||||
|
||||
def expects_to_allow_nil?
|
||||
@expects_to_allow_nil
|
||||
options[:allow_nil]
|
||||
end
|
||||
|
||||
def odd
|
||||
prepare_submatcher(
|
||||
NumericalityMatchers::OddNumberMatcher.new(self, @attribute)
|
||||
)
|
||||
options[:cardinality] = :odd
|
||||
self
|
||||
end
|
||||
|
||||
def even
|
||||
prepare_submatcher(
|
||||
NumericalityMatchers::EvenNumberMatcher.new(self, @attribute)
|
||||
)
|
||||
options[:cardinality] = :even
|
||||
self
|
||||
end
|
||||
|
||||
def is_greater_than(value)
|
||||
prepare_submatcher(comparison_matcher_for(value, :>).for(@attribute))
|
||||
options[:comparisons][:>] = value
|
||||
self
|
||||
end
|
||||
|
||||
def is_greater_than_or_equal_to(value)
|
||||
prepare_submatcher(comparison_matcher_for(value, :>=).for(@attribute))
|
||||
options[:comparisons][:>=] = value
|
||||
self
|
||||
end
|
||||
|
||||
def is_equal_to(value)
|
||||
prepare_submatcher(comparison_matcher_for(value, :==).for(@attribute))
|
||||
options[:comparisons][:==] = value
|
||||
self
|
||||
end
|
||||
|
||||
def is_less_than(value)
|
||||
prepare_submatcher(comparison_matcher_for(value, :<).for(@attribute))
|
||||
options[:comparisons][:<] = value
|
||||
self
|
||||
end
|
||||
|
||||
def is_less_than_or_equal_to(value)
|
||||
prepare_submatcher(comparison_matcher_for(value, :<=).for(@attribute))
|
||||
options[:comparisons][:<=] = value
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expects_custom_validation_message = true
|
||||
@expected_message = message
|
||||
self
|
||||
end
|
||||
|
||||
def expects_custom_validation_message?
|
||||
@expects_custom_validation_message
|
||||
end
|
||||
|
||||
def on(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
@number_of_submatchers = @submatchers.size
|
||||
|
||||
add_disallow_value_matcher
|
||||
qualify_submatchers
|
||||
|
||||
first_failing_submatcher.nil?
|
||||
end
|
||||
|
||||
def simple_description
|
||||
description = ''
|
||||
|
||||
description << "validate that :#{@attribute} looks like "
|
||||
description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
|
||||
|
||||
if comparison_descriptions.present?
|
||||
description << ' ' + comparison_descriptions
|
||||
end
|
||||
|
||||
description
|
||||
end
|
||||
|
||||
def description
|
||||
ValidationMatcher::BuildDescription.call(self, simple_description)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
overall_failure_message.dup.tap do |message|
|
||||
message << "\n"
|
||||
message << failure_message_for_first_failing_submatcher
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message_when_negated
|
||||
overall_failure_message_when_negated.dup.tap do |message|
|
||||
if submatcher_failure_message_when_negated.present?
|
||||
raise "hmm, this needs to be implemented."
|
||||
message << "\n"
|
||||
message << Shoulda::Matchers.word_wrap(
|
||||
submatcher_failure_message_when_negated,
|
||||
indent: 2
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def given_numeric_column?
|
||||
attribute_is_active_record_column? &&
|
||||
[:integer, :float, :decimal].include?(column_type)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def simple_description
|
||||
parts = [
|
||||
"validate that :#{attribute} looks like",
|
||||
Shoulda::Matchers::Util.a_or_an(expected_value_description),
|
||||
]
|
||||
|
||||
parts.join(' ')
|
||||
end
|
||||
|
||||
def add_submatchers
|
||||
add_default_submatcher
|
||||
add_submatcher_for_only_integer
|
||||
add_submatcher_for_allow_nil
|
||||
add_submatcher_for_cardinality
|
||||
add_submatchers_for_comparisons
|
||||
end
|
||||
|
||||
def add_submatcher(submatcher)
|
||||
if submatcher.respond_to?(:diff_to_compare)
|
||||
@diff_to_compare = [diff_to_compare, submatcher.diff_to_compare].max
|
||||
end
|
||||
|
||||
super(submatcher)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def model
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def overall_failure_message
|
||||
Shoulda::Matchers.word_wrap(
|
||||
"#{model.name} did not properly #{description}."
|
||||
)
|
||||
end
|
||||
|
||||
def overall_failure_message_when_negated
|
||||
Shoulda::Matchers.word_wrap(
|
||||
"Expected #{model.name} not to #{description}, but it did."
|
||||
)
|
||||
end
|
||||
attr_reader :options, :allowed_type_adjective, :allowed_type_name
|
||||
|
||||
def attribute_is_active_record_column?
|
||||
columns_hash.key?(@attribute.to_s)
|
||||
columns_hash.key?(attribute.to_s)
|
||||
end
|
||||
|
||||
def column_type
|
||||
columns_hash[@attribute.to_s].type
|
||||
columns_hash[attribute.to_s].type
|
||||
end
|
||||
|
||||
def columns_hash
|
||||
if @subject.class.respond_to?(:columns_hash)
|
||||
@subject.class.columns_hash
|
||||
if model.respond_to?(:columns_hash)
|
||||
model.columns_hash
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def add_disallow_value_matcher
|
||||
disallow_value_matcher = DisallowValueMatcher.
|
||||
new(NON_NUMERIC_VALUE).
|
||||
for(@attribute).
|
||||
with_message(:not_a_number)
|
||||
|
||||
add_submatcher(disallow_value_matcher)
|
||||
def add_default_submatcher
|
||||
add_submatcher_disallowing(
|
||||
NON_NUMERIC_VALUE,
|
||||
expected_message || :not_a_number,
|
||||
)
|
||||
end
|
||||
|
||||
def prepare_submatcher(submatcher)
|
||||
add_submatcher(submatcher)
|
||||
submatcher
|
||||
end
|
||||
|
||||
def comparison_matcher_for(value, operator)
|
||||
NumericalityMatchers::ComparisonMatcher.
|
||||
new(self, value, operator).
|
||||
for(@attribute)
|
||||
end
|
||||
|
||||
def add_submatcher(submatcher)
|
||||
if submatcher.respond_to?(:allowed_type_name)
|
||||
@allowed_type_name = submatcher.allowed_type_name
|
||||
end
|
||||
|
||||
if submatcher.respond_to?(:allowed_type_adjective)
|
||||
@allowed_type_adjective = submatcher.allowed_type_adjective
|
||||
end
|
||||
|
||||
if submatcher.respond_to?(:diff_to_compare)
|
||||
@diff_to_compare = [@diff_to_compare, submatcher.diff_to_compare].max
|
||||
end
|
||||
|
||||
@submatchers << submatcher
|
||||
end
|
||||
|
||||
def qualify_submatchers
|
||||
@submatchers.each do |submatcher|
|
||||
if @expects_strict
|
||||
submatcher.strict(@expects_strict)
|
||||
end
|
||||
|
||||
if @expected_message.present?
|
||||
submatcher.with_message(@expected_message)
|
||||
end
|
||||
|
||||
if @context
|
||||
submatcher.on(@context)
|
||||
end
|
||||
|
||||
submatcher.ignoring_interference_by_writer(
|
||||
ignore_interference_by_writer
|
||||
def add_submatcher_for_only_integer
|
||||
if options[:only_integer]
|
||||
submatcher = build_submatcher(
|
||||
NumericalityMatchers::OnlyIntegerMatcher,
|
||||
self,
|
||||
attribute,
|
||||
)
|
||||
add_submatchers_within(submatcher)
|
||||
end
|
||||
end
|
||||
|
||||
def number_of_submatchers_for_failure_message
|
||||
if has_been_qualified?
|
||||
@submatchers.size - 1
|
||||
def add_submatcher_for_allow_nil
|
||||
if options[:allow_nil]
|
||||
add_submatcher_allowing(nil, :not_a_number)
|
||||
end
|
||||
end
|
||||
|
||||
def add_submatcher_for_cardinality
|
||||
case options[:cardinality]
|
||||
when :odd
|
||||
submatcher = build_submatcher(
|
||||
NumericalityMatchers::OddNumberMatcher,
|
||||
self,
|
||||
attribute,
|
||||
)
|
||||
add_submatchers_within(submatcher)
|
||||
when :even
|
||||
submatcher = build_submatcher(
|
||||
NumericalityMatchers::EvenNumberMatcher,
|
||||
self,
|
||||
attribute,
|
||||
)
|
||||
add_submatchers_within(submatcher)
|
||||
end
|
||||
end
|
||||
|
||||
def add_submatchers_for_comparisons
|
||||
options[:comparisons].each do |operator, value|
|
||||
submatcher = build_submatcher(
|
||||
NumericalityMatchers::ComparisonMatcher,
|
||||
self,
|
||||
attribute,
|
||||
value,
|
||||
operator,
|
||||
)
|
||||
add_submatchers_within(submatcher)
|
||||
end
|
||||
end
|
||||
|
||||
def expected_value_description
|
||||
parts = [
|
||||
expected_value_cardinality,
|
||||
expected_value_type,
|
||||
expected_value_comparisons,
|
||||
]
|
||||
|
||||
parts.select(&:present?).join(' ')
|
||||
end
|
||||
|
||||
def expected_value_cardinality
|
||||
if options[:cardinality]
|
||||
options[:cardinality].to_s
|
||||
end
|
||||
end
|
||||
|
||||
def expected_value_type
|
||||
if options[:only_integer]
|
||||
'integer'
|
||||
else
|
||||
@submatchers.size
|
||||
'number'
|
||||
end
|
||||
end
|
||||
|
||||
def has_been_qualified?
|
||||
@submatchers.any? do |submatcher|
|
||||
submatcher.class.parent == NumericalityMatchers
|
||||
end
|
||||
end
|
||||
def expected_value_comparisons
|
||||
parts = options[:comparisons].map do |operator, value|
|
||||
subparts = []
|
||||
|
||||
def first_failing_submatcher
|
||||
@_failing_submatchers ||= @submatchers.detect do |submatcher|
|
||||
!submatcher.matches?(@subject)
|
||||
end
|
||||
end
|
||||
subparts <<
|
||||
case operator
|
||||
when :> then 'greater than'
|
||||
when :>= then 'greater than or equal to'
|
||||
when :< then 'less than'
|
||||
when :<= then 'less than or equal to'
|
||||
when :== then 'equal to'
|
||||
end
|
||||
|
||||
def submatcher_failure_message
|
||||
first_failing_submatcher.failure_message
|
||||
end
|
||||
subparts << value
|
||||
|
||||
def submatcher_failure_message_when_negated
|
||||
first_failing_submatcher.failure_message_when_negated
|
||||
end
|
||||
|
||||
def failure_message_for_first_failing_submatcher
|
||||
submatcher = first_failing_submatcher
|
||||
|
||||
if number_of_submatchers_for_failure_message > 1
|
||||
submatcher_description = submatcher.simple_description.
|
||||
sub(/\bvalidate that\b/, 'validates').
|
||||
sub(/\bdisallow\b/, 'disallows').
|
||||
sub(/\ballow\b/, 'allows')
|
||||
submatcher_message =
|
||||
"In checking that #{model.name} #{submatcher_description}, " +
|
||||
submatcher.failure_message[0].downcase +
|
||||
submatcher.failure_message[1..-1]
|
||||
else
|
||||
submatcher_message = submatcher.failure_message
|
||||
subparts.join(' ')
|
||||
end
|
||||
|
||||
Shoulda::Matchers.word_wrap(submatcher_message, indent: 2)
|
||||
parts.to_sentence
|
||||
end
|
||||
|
||||
def full_allowed_type
|
||||
"#{@allowed_type_adjective} #{@allowed_type_name}".strip
|
||||
end
|
||||
|
||||
def comparison_descriptions
|
||||
description_array = submatcher_comparison_descriptions
|
||||
description_array.empty? ? '' : submatcher_comparison_descriptions.join(' and ')
|
||||
end
|
||||
|
||||
def submatcher_comparison_descriptions
|
||||
@submatchers.inject([]) do |arr, submatcher|
|
||||
if submatcher.respond_to? :comparison_description
|
||||
arr << submatcher.comparison_description
|
||||
end
|
||||
arr
|
||||
def add_submatchers_within(matcher)
|
||||
matcher.populated_submatchers.each do |submatcher|
|
||||
add_submatcher(submatcher)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,16 +5,21 @@ module Shoulda
|
|||
class ValidationMatcher
|
||||
include Qualifiers::IgnoringInterferenceByWriter
|
||||
|
||||
attr_reader :expected_message, :validation_context
|
||||
|
||||
def initialize(attribute)
|
||||
super
|
||||
|
||||
@attribute = attribute
|
||||
@expects_strict = false
|
||||
@record = nil
|
||||
@submatchers = []
|
||||
@expected_message = nil
|
||||
@expects_custom_validation_message = false
|
||||
@_submatchers_added = false
|
||||
@validation_context = nil
|
||||
@submatchers = []
|
||||
|
||||
@record = nil
|
||||
@was_negated = nil
|
||||
@_submatchers_populated = false
|
||||
end
|
||||
|
||||
def description
|
||||
|
@ -29,13 +34,13 @@ module Shoulda
|
|||
end
|
||||
end
|
||||
|
||||
def on(context)
|
||||
@context = context
|
||||
def on(validation_context)
|
||||
@validation_context = validation_context
|
||||
self
|
||||
end
|
||||
|
||||
def strict
|
||||
@expects_strict = true
|
||||
def strict(expects_strict = true)
|
||||
@expects_strict = expects_strict
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -59,10 +64,7 @@ module Shoulda
|
|||
def matches?(record)
|
||||
@record = record
|
||||
|
||||
if !@_submatchers_added
|
||||
add_submatchers
|
||||
@_submatchers_added = true
|
||||
end
|
||||
populate_submatchers
|
||||
|
||||
all_submatchers_match?.tap do
|
||||
@was_negated = false
|
||||
|
@ -142,13 +144,28 @@ module Shoulda
|
|||
@was_negated
|
||||
end
|
||||
|
||||
def populated_submatchers
|
||||
populate_submatchers
|
||||
submatchers
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :attribute, :context, :record, :submatchers,
|
||||
:first_failing_submatcher
|
||||
attr_reader :attribute, :submatchers, :record
|
||||
|
||||
def model
|
||||
record.class
|
||||
def simple_description
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def expectation
|
||||
description
|
||||
end
|
||||
|
||||
def populate_submatchers
|
||||
if !@_submatchers_populated
|
||||
add_submatchers
|
||||
@_submatchers_populated = true
|
||||
end
|
||||
end
|
||||
|
||||
def add_submatchers
|
||||
|
@ -200,20 +217,47 @@ module Shoulda
|
|||
)
|
||||
end
|
||||
|
||||
def build_submatcher(matcher_class, *args)
|
||||
matcher = matcher_class.new(*args).
|
||||
with_message(expected_message).
|
||||
on(validation_context).
|
||||
strict(expects_strict?).
|
||||
ignoring_interference_by_writer(ignore_interference_by_writer)
|
||||
yield matcher if block_given?
|
||||
|
||||
matcher
|
||||
end
|
||||
|
||||
def model
|
||||
record.class
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def overall_failure_message
|
||||
Shoulda::Matchers.word_wrap(<<~MESSAGE)
|
||||
Your test expecting #{model.name} to #{simple_description} didn't
|
||||
pass.
|
||||
MESSAGE
|
||||
message = "Your test expecting #{model.name} to #{description}"
|
||||
|
||||
message <<
|
||||
if message.include?(',')
|
||||
", didn't pass."
|
||||
else
|
||||
" didn't pass."
|
||||
end
|
||||
|
||||
Shoulda::Matchers.word_wrap(message)
|
||||
end
|
||||
|
||||
def overall_failure_message_when_negated
|
||||
Shoulda::Matchers.word_wrap(<<~MESSAGE)
|
||||
Your test expecting #{model.name} not to #{simple_description}
|
||||
didn't pass.
|
||||
MESSAGE
|
||||
message = "Your test expecting #{model.name} not to #{description}"
|
||||
|
||||
message <<
|
||||
if message.include?(',')
|
||||
", didn't pass."
|
||||
else
|
||||
"didn't pass."
|
||||
end
|
||||
|
||||
Shoulda::Matchers.word_wrap(message)
|
||||
end
|
||||
|
||||
def indented_failure_message_for_first_failing_submatcher
|
||||
|
@ -262,16 +306,14 @@ module Shoulda
|
|||
end
|
||||
|
||||
def build_allow_or_disallow_value_matcher(matcher_class:, values:, message:)
|
||||
matcher = matcher_class.new(*values, part_of_larger_matcher: true).
|
||||
for(attribute).
|
||||
with_message(message).
|
||||
on(context).
|
||||
strict(expects_strict?).
|
||||
ignoring_interference_by_writer(ignore_interference_by_writer)
|
||||
|
||||
yield matcher if block_given?
|
||||
|
||||
matcher
|
||||
build_submatcher(
|
||||
matcher_class,
|
||||
*values,
|
||||
part_of_larger_matcher: true,
|
||||
) do |matcher|
|
||||
matcher.for(attribute).with_message(message || expected_message)
|
||||
yield matcher if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
class SubmatcherResult
|
||||
|
|
|
@ -4,35 +4,31 @@ module Shoulda
|
|||
class ValidationMatcher
|
||||
# @private
|
||||
class BuildDescription
|
||||
def self.call(matcher, main_description, **options)
|
||||
new(matcher, **options).call(main_description)
|
||||
def self.call(matcher, main_description)
|
||||
new(matcher).call(main_description)
|
||||
end
|
||||
|
||||
def initialize(matcher, expectation_state: :agnostic)
|
||||
def initialize(matcher)
|
||||
@matcher = matcher
|
||||
@expectation_state = expectation_state
|
||||
end
|
||||
|
||||
def call(main_description)
|
||||
if description_clauses_for_qualifiers.present?
|
||||
[main_description, description_clauses_for_qualifiers].join(', ')
|
||||
else
|
||||
main_description
|
||||
description = decorate_with_validation_context(main_description)
|
||||
|
||||
if description_clause_for_allow_blank_or_nil.present?
|
||||
description << " #{description_clause_for_allow_blank_or_nil}"
|
||||
end
|
||||
end
|
||||
|
||||
def description_clauses_for_qualifiers
|
||||
parts = [
|
||||
description_clause_for_allow_blank_or_nil,
|
||||
description_clause_for_strict_or_custom_validation_message,
|
||||
]
|
||||
if description_clause_for_strict_or_custom_validation_message.present?
|
||||
description << ", #{description_clause_for_strict_or_custom_validation_message}"
|
||||
end
|
||||
|
||||
parts.select(&:present?).join(', and')
|
||||
description
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :matcher, :expectation_state
|
||||
attr_reader :matcher
|
||||
|
||||
private
|
||||
|
||||
|
@ -45,50 +41,52 @@ module Shoulda
|
|||
end
|
||||
|
||||
def description_clause_for_strict_or_custom_validation_message
|
||||
if expectation_state == :agnostic
|
||||
if expects_strict?
|
||||
description_clause_for_strict
|
||||
elsif expects_custom_validation_message?
|
||||
description_clause_for_custom_validation_message
|
||||
end
|
||||
if expects_strict?
|
||||
description_clause_for_strict
|
||||
elsif expects_custom_validation_message?
|
||||
description_clause_for_custom_validation_message
|
||||
end
|
||||
end
|
||||
|
||||
def description_clause_for_allow_blank
|
||||
'only if it is not blank'
|
||||
'(only when not blank)'
|
||||
end
|
||||
|
||||
def description_clause_for_allow_nil
|
||||
'only if it is not nil'
|
||||
'(only when not nil)'
|
||||
end
|
||||
|
||||
def description_clause_for_strict
|
||||
parts = []
|
||||
'raising a validation exception on failure'
|
||||
end
|
||||
|
||||
parts << 'raising a validation exception'
|
||||
def description_clause_for_custom_validation_message
|
||||
parts = ['producing a validation error']
|
||||
|
||||
if matcher.try(:expects_custom_validation_message?)
|
||||
parts << matcher.expected_message.inspect
|
||||
end
|
||||
parts <<
|
||||
if matcher.expected_message.is_a?(Regexp)
|
||||
"matching #{matcher.expected_message.inspect}"
|
||||
else
|
||||
matcher.expected_message.inspect
|
||||
end
|
||||
|
||||
if expectation_state == :agnostic
|
||||
parts << 'on failure'
|
||||
end
|
||||
parts << 'on failure'
|
||||
|
||||
parts.join(' ')
|
||||
end
|
||||
|
||||
def description_clause_for_custom_validation_message
|
||||
parts = [
|
||||
'producing a validation error',
|
||||
matcher.expected_message.inspect,
|
||||
]
|
||||
|
||||
if expectation_state == :agnostic
|
||||
parts << 'on failure'
|
||||
def decorate_with_validation_context(description)
|
||||
if validation_context.present?
|
||||
description.gsub(/\bvalidat(?:e|ion)\b/) do |str|
|
||||
"#{str} (context: #{validation_context.inspect})"
|
||||
end
|
||||
else
|
||||
description
|
||||
end
|
||||
end
|
||||
|
||||
parts.join(' ')
|
||||
def validation_context
|
||||
matcher.try(:validation_context)
|
||||
end
|
||||
|
||||
def expects_to_allow_blank?
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
class ValidationMatcher
|
||||
# @private
|
||||
class BuildExpectation
|
||||
def self.call(matcher, preface, **options)
|
||||
new(matcher, preface, **options).call
|
||||
end
|
||||
|
||||
def initialize(matcher, preface, state:)
|
||||
@matcher = matcher
|
||||
@preface = preface
|
||||
|
||||
@build_description = BuildDescription.new(
|
||||
matcher,
|
||||
expectation_state: state,
|
||||
)
|
||||
end
|
||||
|
||||
def call
|
||||
parts = [
|
||||
[preface, 'with', clauses].join(' '),
|
||||
build_description.description_clauses_for_qualifiers,
|
||||
]
|
||||
|
||||
parts.select(&:present?).join(', ')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :preface, :matcher, :build_description
|
||||
|
||||
def clauses
|
||||
clauses = [
|
||||
expectation_clauses_for_values_to_preset,
|
||||
expectation_clauses_for_values_to_set,
|
||||
]
|
||||
|
||||
clauses.select(&:present?).join(' and ')
|
||||
end
|
||||
|
||||
def expectation_clauses_for_values_to_preset
|
||||
matcher.expectation_clauses_for_values_to_preset
|
||||
end
|
||||
|
||||
def expectation_clauses_for_values_to_set
|
||||
matcher.expectation_clauses_for_values_to_set
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -43,10 +43,18 @@ module Shoulda
|
|||
validation_messages.any?
|
||||
end
|
||||
|
||||
def all_formatted_validation_error_messages
|
||||
def has_any_validation_errors?
|
||||
all_validation_errors.any?
|
||||
end
|
||||
|
||||
def all_formatted_validation_errors
|
||||
format_validation_errors(all_validation_errors)
|
||||
end
|
||||
|
||||
def formatted_validation_messages
|
||||
format_attribute_specific_validation_errors(validation_messages)
|
||||
end
|
||||
|
||||
def validation_exception_message
|
||||
validation_result[:validation_exception_message]
|
||||
end
|
||||
|
|
|
@ -37,6 +37,8 @@ module UnitTests
|
|||
def next_value
|
||||
if value.is_a?(Array)
|
||||
value + [value.first.class.new]
|
||||
elsif value.is_a?(Float)
|
||||
value + 0.1
|
||||
elsif value.respond_to?(:next)
|
||||
value.next
|
||||
else
|
||||
|
@ -100,6 +102,12 @@ module UnitTests
|
|||
'a'
|
||||
end
|
||||
|
||||
def round_up
|
||||
Float(value).ceil
|
||||
rescue ArgumentError
|
||||
value
|
||||
end
|
||||
|
||||
def change_value(value, value_changer)
|
||||
self.class.call(column_type, value, value_changer)
|
||||
end
|
||||
|
|
|
@ -62,9 +62,9 @@ Your test expecting Example to validate absence of :attr didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "must be
|
||||
blank" on :attr with :attr set to ‹"an arbitrary value"›. However, no
|
||||
such error was found on :attr.
|
||||
✘ With :attr set to ‹"an arbitrary value"›, the Example was expected to
|
||||
fail validation by placing the error "must be blank" on :attr.
|
||||
However, no such error was found on :attr.
|
||||
MESSAGE
|
||||
|
||||
assertion = lambda do
|
||||
|
@ -106,9 +106,9 @@ Your test expecting Example to validate absence of :attr didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "must be
|
||||
blank" on :attr with :attr set to ‹"an arbitrary value"›. However, no
|
||||
such error was found on :attr.
|
||||
✘ With :attr set to ‹"an arbitrary value"›, the Example was expected to
|
||||
fail validation by placing the error "must be blank" on :attr.
|
||||
However, no such error was found on :attr.
|
||||
MESSAGE
|
||||
|
||||
assertion = lambda do
|
||||
|
@ -175,8 +175,8 @@ Your test expecting Parent to validate absence of :children didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Parent to fail validation by placing the error "must be
|
||||
blank" on :children with :children set to ‹[#<Child id: nil>]›.
|
||||
✘ With :children set to ‹[#<Child id: nil>]›, the Parent was expected to
|
||||
fail validation by placing the error "must be blank" on :children.
|
||||
However, no such error was found on :children.
|
||||
MESSAGE
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ Your test expecting Example to validate acceptance of :attr didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "must be
|
||||
accepted" on :attr with :attr set to ‹false› (which was read back as
|
||||
‹nil›). However, no such error was found on :attr.
|
||||
✘ With :attr set to ‹false› (which was read back as ‹nil›), the Example
|
||||
was expected to fail validation by placing the error "must be
|
||||
accepted" on :attr. However, no such error was found on :attr.
|
||||
|
||||
As indicated above, :attr seems to be changing certain values as they
|
||||
are set, and this could have something to do with why this matcher is
|
||||
|
|
|
@ -42,20 +42,21 @@ pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✔︎ Expected Example to fail validation by placing the error "doesn't
|
||||
match Password" on :password_confirmation with :password_confirmation
|
||||
set to ‹"some value"› and :password set to ‹"different value"› (which
|
||||
was read back as ‹"different valuf"›).
|
||||
✔︎ With :password_confirmation set to ‹"some value"› and :password set to
|
||||
‹"different value"› (which was read back as ‹"different valuf"›), the
|
||||
Example was expected to fail validation by placing the error "doesn't
|
||||
match Password" on :password_confirmation.
|
||||
|
||||
✘ Expected Example not to fail validation by placing the error "doesn't
|
||||
match Password" on :password_confirmation with :password_confirmation
|
||||
set to ‹"same value"› and :password set to ‹"same value"› (which was
|
||||
read back as ‹"same valuf"›). However, it did.
|
||||
✘ With :password_confirmation set to ‹"same value"› and :password set to
|
||||
‹"same value"› (which was read back as ‹"same valuf"›), the Example
|
||||
was expected not to fail validation by placing the error "doesn't
|
||||
match Password" on :password_confirmation. However, it did fail with
|
||||
that error.
|
||||
|
||||
✔︎ Expected Example not to fail validation by placing the error "doesn't
|
||||
match Password" on :password_confirmation with :password_confirmation
|
||||
set to ‹nil› and :password set to ‹"any value"› (which was read back
|
||||
as ‹"any valuf"›).
|
||||
✔︎ With :password_confirmation set to ‹nil› and :password set to ‹"any
|
||||
value"› (which was read back as ‹"any valuf"›), the Example was
|
||||
expected not to fail validation by placing the error "doesn't match
|
||||
Password" on :password_confirmation.
|
||||
|
||||
As indicated above, :password seems to be changing certain values as
|
||||
they are set, and this could have something to do with why this matcher
|
||||
|
@ -126,21 +127,21 @@ Your test expecting Example to validate confirmation of
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "doesn't
|
||||
match Attribute to confirm" on :attribute_to_confirm_confirmation with
|
||||
:attribute_to_confirm_confirmation set to ‹"some value"› and
|
||||
:attribute_to_confirm set to ‹"different value"›. However, no such
|
||||
error was found on :attribute_to_confirm_confirmation.
|
||||
✘ With :attribute_to_confirm_confirmation set to ‹"some value"› and
|
||||
:attribute_to_confirm set to ‹"different value"›, the Example was
|
||||
expected to fail validation by placing the error "doesn't match
|
||||
Attribute to confirm" on :attribute_to_confirm_confirmation. However,
|
||||
no such error was found on :attribute_to_confirm_confirmation.
|
||||
|
||||
✔︎ Expected Example not to fail validation by placing the error "doesn't
|
||||
match Attribute to confirm" on :attribute_to_confirm_confirmation with
|
||||
:attribute_to_confirm_confirmation set to ‹"same value"› and
|
||||
:attribute_to_confirm set to ‹"same value"›.
|
||||
✔︎ With :attribute_to_confirm_confirmation set to ‹"same value"› and
|
||||
:attribute_to_confirm set to ‹"same value"›, the Example was expected
|
||||
not to fail validation by placing the error "doesn't match Attribute
|
||||
to confirm" on :attribute_to_confirm_confirmation.
|
||||
|
||||
✔︎ Expected Example not to fail validation by placing the error "doesn't
|
||||
match Attribute to confirm" on :attribute_to_confirm_confirmation with
|
||||
:attribute_to_confirm_confirmation set to ‹nil› and
|
||||
:attribute_to_confirm set to ‹"any value"›.
|
||||
✔︎ With :attribute_to_confirm_confirmation set to ‹nil› and
|
||||
:attribute_to_confirm set to ‹"any value"›, the Example was expected
|
||||
not to fail validation by placing the error "doesn't match Attribute
|
||||
to confirm" on :attribute_to_confirm_confirmation.
|
||||
MESSAGE
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
|
|
|
@ -31,19 +31,21 @@ range ‹2› to ‹5› didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example not to fail validation by placing the error "is
|
||||
reserved" on :attr with :attr set to ‹1› (which was read back as ‹2›).
|
||||
However, it did.
|
||||
✘ With :attr set to ‹1› (which was read back as ‹2›), the Example was
|
||||
expected not to fail validation by placing the error "is reserved" on
|
||||
:attr. However, it did fail with that error.
|
||||
|
||||
✔︎ Expected Example to fail validation by placing the error "is reserved"
|
||||
on :attr with :attr set to ‹2› (which was read back as ‹3›).
|
||||
✔︎ With :attr set to ‹2› (which was read back as ‹3›), the Example was
|
||||
expected to fail validation by placing the error "is reserved" on
|
||||
:attr.
|
||||
|
||||
✔︎ Expected Example not to fail validation by placing the error "is
|
||||
reserved" on :attr with :attr set to ‹6› (which was read back as ‹7›).
|
||||
✔︎ With :attr set to ‹6› (which was read back as ‹7›), the Example was
|
||||
expected not to fail validation by placing the error "is reserved" on
|
||||
:attr.
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "is reserved"
|
||||
on :attr with :attr set to ‹5› (which was read back as ‹6›). However,
|
||||
no such error was found on :attr.
|
||||
✘ With :attr set to ‹5› (which was read back as ‹6›), the Example was
|
||||
expected to fail validation by placing the error "is reserved" on
|
||||
:attr. However, no such error was found on :attr.
|
||||
|
||||
As indicated above, :attr seems to be changing certain values as they
|
||||
are set, and this could have something to do with why this matcher is
|
||||
|
@ -179,13 +181,13 @@ nor ‹"two"› didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "is reserved"
|
||||
on :attr with :attr set to ‹"one"› (which was read back as ‹"onf"›).
|
||||
However, no such error was found on :attr.
|
||||
✘ With :attr set to ‹"one"› (which was read back as ‹"onf"›), the
|
||||
Example was expected to fail validation by placing the error "is
|
||||
reserved" on :attr. However, no such error was found on :attr.
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "is reserved"
|
||||
on :attr with :attr set to ‹"two"› (which was read back as ‹"twp"›).
|
||||
However, no such error was found on :attr.
|
||||
✘ With :attr set to ‹"two"› (which was read back as ‹"twp"›), the
|
||||
Example was expected to fail validation by placing the error "is
|
||||
reserved" on :attr. However, no such error was found on :attr.
|
||||
|
||||
As indicated above, :attr seems to be changing certain values as they
|
||||
are set, and this could have something to do with why this matcher is
|
||||
|
|
|
@ -39,13 +39,14 @@ least 4 didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "is too short
|
||||
(minimum is 4 characters)" on :attr with :attr set to ‹"xxx"› (which
|
||||
was read back as ‹"xxxa"›). However, no such error was found on :attr.
|
||||
✘ With :attr set to ‹"xxx"› (which was read back as ‹"xxxa"›), the
|
||||
Example was expected to fail validation by placing the error "is too
|
||||
short (minimum is 4 characters)" on :attr. However, no such error was
|
||||
found on :attr.
|
||||
|
||||
✔︎ Expected Example not to fail validation by placing the error "is too
|
||||
short (minimum is 4 characters)" on :attr with :attr set to ‹"xxxx"›
|
||||
(which was read back as ‹"xxxxa"›).
|
||||
✔︎ With :attr set to ‹"xxxx"› (which was read back as ‹"xxxxa"›), the
|
||||
Example was expected not to fail validation by placing the error "is
|
||||
too short (minimum is 4 characters)" on :attr.
|
||||
|
||||
As indicated above, :attr seems to be changing certain values as they
|
||||
are set, and this could have something to do with why this matcher is
|
||||
|
@ -111,13 +112,14 @@ most 4 didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "is too long
|
||||
(maximum is 4 characters)" on :attr with :attr set to ‹"xxxxx"› (which
|
||||
was read back as ‹"xxxx"›). However, no such error was found on :attr.
|
||||
✘ With :attr set to ‹"xxxxx"› (which was read back as ‹"xxxx"›), the
|
||||
Example was expected to fail validation by placing the error "is too
|
||||
long (maximum is 4 characters)" on :attr. However, no such error was
|
||||
found on :attr.
|
||||
|
||||
✔︎ Expected Example not to fail validation by placing the error "is too
|
||||
long (maximum is 4 characters)" on :attr with :attr set to ‹"xxxx"›
|
||||
(which was read back as ‹"xxx"›).
|
||||
✔︎ With :attr set to ‹"xxxx"› (which was read back as ‹"xxx"›), the
|
||||
Example was expected not to fail validation by placing the error "is
|
||||
too long (maximum is 4 characters)" on :attr.
|
||||
|
||||
As indicated above, :attr seems to be changing certain values as they
|
||||
are set, and this could have something to do with why this matcher is
|
||||
|
@ -176,22 +178,24 @@ didn't pass.
|
|||
The matcher ran the following subtests. Those indicated with ✘ failed
|
||||
when they should have passed:
|
||||
|
||||
✘ Expected Example to fail validation by placing the error "is the wrong
|
||||
length (should be 4 characters)" on :attr with :attr set to ‹"xxx"›
|
||||
(which was read back as ‹"xxxa"›). However, no such error was found on
|
||||
:attr.
|
||||
✘ With :attr set to ‹"xxx"› (which was read back as ‹"xxxa"›), the
|
||||
Example was expected to fail validation by placing the error "is the
|
||||
wrong length (should be 4 characters)" on :attr. However, no such
|
||||
error was found on :attr.
|
||||
|
||||
✘ Expected Example not to fail validation by placing the error "is the
|
||||
wrong length (should be 4 characters)" on :attr with :attr set to
|
||||
‹"xxxx"› (which was read back as ‹"xxxxa"›). However, it did.
|
||||
✘ With :attr set to ‹"xxxx"› (which was read back as ‹"xxxxa"›), the
|
||||
Example was expected not to fail validation by placing the error "is
|
||||
the wrong length (should be 4 characters)" on :attr. However, it did
|
||||
fail with that error.
|
||||
|
||||
✔︎ Expected Example to fail validation by placing the error "is the wrong
|
||||
length (should be 4 characters)" on :attr with :attr set to ‹"xxxxx"›
|
||||
(which was read back as ‹"xxxxxa"›).
|
||||
✔︎ With :attr set to ‹"xxxxx"› (which was read back as ‹"xxxxxa"›), the
|
||||
Example was expected to fail validation by placing the error "is the
|
||||
wrong length (should be 4 characters)" on :attr.
|
||||
|
||||
✘ Expected Example not to fail validation by placing the error "is the
|
||||
wrong length (should be 4 characters)" on :attr with :attr set to
|
||||
‹"xxxx"› (which was read back as ‹"xxxxa"›). However, it did.
|
||||
✘ With :attr set to ‹"xxxx"› (which was read back as ‹"xxxxa"›), the
|
||||
Example was expected not to fail validation by placing the error "is
|
||||
the wrong length (should be 4 characters)" on :attr. However, it did
|
||||
fail with that error.
|
||||
|
||||
As indicated above, :attr seems to be changing certain values as they
|
||||
are set, and this could have something to do with why this matcher is
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue