mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Refactor allow_value & disallow_value
This is part of a collection of commits that aim to improve failure messages across the board, in order to make matchers easier to debug when something goes wrong. * Have the failure message describe more clearly what the `allow_value` matcher was trying to do when it failed. * Make the description of the matcher more readable. * For each value that `allow_value` sets, use a different Validator instance. The matcher still changes state as it runs, but a future commit will refactor this further. * Merge StrictValidator back into Validator, and remove it. The way that StrictValidator worked (as a module that was mixed into an instance of Validator at runtime) was confusing, and there's really no need to split out the logic anymore. * Fix or fill in tests involving failure messages and descriptions.
This commit is contained in:
parent
8361f39579
commit
583be384c3
7 changed files with 517 additions and 290 deletions
|
@ -2,7 +2,6 @@ require 'shoulda/matchers/active_model/helpers'
|
|||
require 'shoulda/matchers/active_model/validation_matcher'
|
||||
require 'shoulda/matchers/active_model/validation_matcher/build_description'
|
||||
require 'shoulda/matchers/active_model/validator'
|
||||
require 'shoulda/matchers/active_model/strict_validator'
|
||||
require 'shoulda/matchers/active_model/allow_value_matcher'
|
||||
require 'shoulda/matchers/active_model/disallow_value_matcher'
|
||||
require 'shoulda/matchers/active_model/validate_length_of_matcher'
|
||||
|
|
|
@ -354,99 +354,194 @@ https://github.com/thoughtbot/shoulda-matchers/issues
|
|||
|
||||
include Helpers
|
||||
|
||||
attr_accessor :attribute_with_message
|
||||
attr_accessor :options
|
||||
attr_accessor :failure_message_preface
|
||||
attr_reader :last_value_set
|
||||
|
||||
def initialize(*values)
|
||||
self.values_to_match = values
|
||||
self.options = {}
|
||||
self.after_setting_value_callback = -> {}
|
||||
self.validator = Validator.new
|
||||
@values_to_set = values
|
||||
@options = {}
|
||||
@after_setting_value_callback = -> {}
|
||||
@ignoring_interference_by_writer = false
|
||||
@expects_strict = false
|
||||
@expects_custom_validation_message = false
|
||||
@context = nil
|
||||
|
||||
@failure_message_preface = proc do
|
||||
<<-PREFIX.strip_heredoc.strip
|
||||
After setting :#{attribute_to_set} to #{last_value_set.inspect},
|
||||
the matcher expected the #{model.name} to be
|
||||
PREFIX
|
||||
end
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
self.attribute_to_set = attribute
|
||||
self.attribute_to_check_message_against = attribute
|
||||
@attribute_to_set = attribute
|
||||
@attribute_to_check_message_against = attribute
|
||||
self
|
||||
end
|
||||
|
||||
def on(context)
|
||||
validator.context = context
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message, options={})
|
||||
self.options[:expected_message] = message
|
||||
self.options[:expected_message_values] = options.fetch(:values, {})
|
||||
|
||||
if options.key?(:against)
|
||||
self.attribute_to_check_message_against = options[:against]
|
||||
if context.present?
|
||||
@context = context
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def strict
|
||||
validator.strict = true
|
||||
def with_message(message, given_options = {})
|
||||
if message.present?
|
||||
@expects_custom_validation_message = true
|
||||
options[:expected_message] = message
|
||||
options[:expected_message_values] = given_options.fetch(:values, {})
|
||||
|
||||
if given_options.key?(:against)
|
||||
@attribute_to_check_message_against = given_options[:against]
|
||||
end
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def expects_custom_validation_message?
|
||||
@expects_custom_validation_message
|
||||
end
|
||||
|
||||
def strict(expects_strict = true)
|
||||
@expects_strict = expects_strict
|
||||
self
|
||||
end
|
||||
|
||||
def expects_strict?
|
||||
@expects_strict
|
||||
end
|
||||
|
||||
def ignoring_interference_by_writer
|
||||
@ignoring_interference_by_writer = true
|
||||
self
|
||||
end
|
||||
|
||||
def _after_setting_value(&callback)
|
||||
self.after_setting_value_callback = callback
|
||||
@after_setting_value_callback = callback
|
||||
end
|
||||
|
||||
def matches?(instance)
|
||||
self.instance = instance
|
||||
values_to_match.all? { |value| value_matches?(value) }
|
||||
@instance = instance
|
||||
first_failing_value_and_validator.nil?
|
||||
end
|
||||
|
||||
def does_not_match?(instance)
|
||||
self.instance = instance
|
||||
values_to_match.all? { |value| !value_matches?(value) }
|
||||
@instance = instance
|
||||
first_passing_value_and_validator.nil?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Did not expect #{expectation},\ngot#{error_description}"
|
||||
validator = first_failing_validator
|
||||
message =
|
||||
failure_message_preface.call +
|
||||
' valid, but it was invalid instead,'
|
||||
|
||||
if validator.captured_validation_exception?
|
||||
message << ' raising a validation exception with the message '
|
||||
message << validator.validation_exception_message.inspect
|
||||
message << '.'
|
||||
else
|
||||
message << " producing these validation errors:\n\n"
|
||||
message << validator.all_formatted_validation_error_messages
|
||||
end
|
||||
|
||||
Shoulda::Matchers.word_wrap(message)
|
||||
end
|
||||
|
||||
def failure_message_when_negated
|
||||
"Expected #{expectation},\ngot#{error_description}"
|
||||
validator = first_passing_validator
|
||||
message = failure_message_preface.call + ' invalid'
|
||||
|
||||
if validator.type_of_message_matched?
|
||||
if validator.has_messages?
|
||||
message << ' and to'
|
||||
|
||||
if validator.captured_validation_exception?
|
||||
message << ' raise a validation exception with message'
|
||||
else
|
||||
message << ' produce'
|
||||
|
||||
if expected_message.is_a?(Regexp)
|
||||
message << ' a'
|
||||
else
|
||||
message << ' the'
|
||||
end
|
||||
|
||||
message << ' validation error'
|
||||
end
|
||||
|
||||
if expected_message.is_a?(Regexp)
|
||||
message << ' matching'
|
||||
end
|
||||
|
||||
message << " #{expected_message.inspect}"
|
||||
|
||||
unless validator.captured_validation_exception?
|
||||
message << " on :#{attribute_to_check_message_against}"
|
||||
end
|
||||
|
||||
message << '. The record was indeed 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
|
||||
else
|
||||
message << ', but it was valid instead.'
|
||||
end
|
||||
elsif validator.captured_validation_exception?
|
||||
message << ' and to produce validation errors, but the record'
|
||||
message << ' raised a validation exception instead.'
|
||||
else
|
||||
message << ' and to raise a validation exception, but the record'
|
||||
message << ' produced validation errors instead.'
|
||||
end
|
||||
|
||||
Shoulda::Matchers.word_wrap(message)
|
||||
end
|
||||
|
||||
def simple_description
|
||||
"allow :#{attribute_to_set} to be #{inspected_values_to_set}"
|
||||
end
|
||||
|
||||
def description
|
||||
validator.allow_description(allowed_values)
|
||||
ValidationMatcher::BuildDescription.call(self, simple_description)
|
||||
end
|
||||
|
||||
def model
|
||||
instance.class
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :instance, :attribute_to_check_message_against
|
||||
attr_accessor :values_to_match, :attribute_to_set, :value,
|
||||
:matched_error, :after_setting_value_callback, :validator
|
||||
attr_reader(
|
||||
:after_setting_value_callback,
|
||||
:attribute_to_check_message_against,
|
||||
:attribute_to_set,
|
||||
:context,
|
||||
:instance,
|
||||
:options,
|
||||
:values_to_set,
|
||||
)
|
||||
|
||||
def instance=(instance)
|
||||
@instance = instance
|
||||
validator.record = instance
|
||||
end
|
||||
|
||||
def attribute_to_check_message_against=(attribute)
|
||||
@attribute_to_check_message_against = attribute
|
||||
validator.attribute = attribute
|
||||
end
|
||||
private
|
||||
|
||||
def ignoring_interference_by_writer?
|
||||
@ignoring_interference_by_writer
|
||||
end
|
||||
|
||||
def value_matches?(value)
|
||||
self.value = value
|
||||
def value_matches?(value, validator)
|
||||
@last_value_set = value
|
||||
set_attribute(value)
|
||||
!(errors_match? || any_range_error_occurred?)
|
||||
!errors_match?(validator) && !any_range_error_occurred?(validator)
|
||||
end
|
||||
|
||||
def set_attribute(value)
|
||||
|
@ -468,64 +563,36 @@ https://github.com/thoughtbot/shoulda-matchers/issues
|
|||
end
|
||||
end
|
||||
|
||||
def errors_match?
|
||||
has_messages? && errors_for_attribute_match?
|
||||
def errors_match?(validator)
|
||||
validator.has_messages? &&
|
||||
validator.type_of_message_matched? &&
|
||||
errors_for_attribute_match?(validator)
|
||||
end
|
||||
|
||||
def has_messages?
|
||||
validator.has_messages?
|
||||
def errors_for_attribute_match?(validator)
|
||||
matched_errors(validator).compact.any?
|
||||
end
|
||||
|
||||
def errors_for_attribute_match?
|
||||
def matched_errors(validator)
|
||||
if expected_message
|
||||
self.matched_error = errors_match_regexp? || errors_match_string?
|
||||
validator.messages.grep(expected_message)
|
||||
else
|
||||
errors_for_attribute.compact.any?
|
||||
validator.messages
|
||||
end
|
||||
end
|
||||
|
||||
def errors_for_attribute
|
||||
validator.formatted_messages
|
||||
end
|
||||
|
||||
def errors_match_regexp?
|
||||
if Regexp === expected_message
|
||||
errors_for_attribute.detect { |e| e =~ expected_message }
|
||||
end
|
||||
end
|
||||
|
||||
def errors_match_string?
|
||||
if errors_for_attribute.include?(expected_message)
|
||||
expected_message
|
||||
end
|
||||
end
|
||||
|
||||
def any_range_error_occurred?
|
||||
def any_range_error_occurred?(validator)
|
||||
validator.captured_range_error?
|
||||
end
|
||||
|
||||
def expectation
|
||||
parts = [
|
||||
expected_messages_description,
|
||||
"when #{attribute_to_set} is set to #{value.inspect}"
|
||||
]
|
||||
|
||||
parts.join(' ').squeeze(' ')
|
||||
end
|
||||
|
||||
def expected_messages_description
|
||||
validator.expected_messages_description(expected_message)
|
||||
end
|
||||
|
||||
def error_description
|
||||
validator.messages_description
|
||||
end
|
||||
|
||||
def allowed_values
|
||||
if values_to_match.length > 1
|
||||
"any of [#{values_to_match.map(&:inspect).join(', ')}]"
|
||||
def inspected_values_to_set
|
||||
if values_to_set.size > 1
|
||||
values_to_set.map(&:inspect).to_sentence(
|
||||
two_words_connector: " or ",
|
||||
last_word_connector: ", or"
|
||||
)
|
||||
else
|
||||
values_to_match.first.inspect
|
||||
values_to_set.first.inspect
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -540,7 +607,11 @@ https://github.com/thoughtbot/shoulda-matchers/issues
|
|||
end
|
||||
|
||||
def default_expected_message
|
||||
validator.expected_message_from(default_attribute_message)
|
||||
if expects_strict?
|
||||
"#{human_attribute_name} #{default_attribute_message}"
|
||||
else
|
||||
default_attribute_message
|
||||
end
|
||||
end
|
||||
|
||||
def default_attribute_message
|
||||
|
@ -560,9 +631,51 @@ https://github.com/thoughtbot/shoulda-matchers/issues
|
|||
defaults.merge(options[:expected_message_values])
|
||||
end
|
||||
|
||||
def values_and_validators
|
||||
@_values_and_validators ||= values_to_set.map do |value|
|
||||
[value, build_validator]
|
||||
end
|
||||
end
|
||||
|
||||
def build_validator
|
||||
validator = Validator.new(
|
||||
instance,
|
||||
attribute_to_check_message_against
|
||||
)
|
||||
validator.context = context
|
||||
validator.expects_strict = expects_strict?
|
||||
validator
|
||||
end
|
||||
|
||||
def first_failing_value_and_validator
|
||||
@_first_failing_value_and_validator ||=
|
||||
values_and_validators.detect do |value, validator|
|
||||
!value_matches?(value, validator)
|
||||
end
|
||||
end
|
||||
|
||||
def first_failing_validator
|
||||
first_failing_value_and_validator[1]
|
||||
end
|
||||
|
||||
def first_passing_value_and_validator
|
||||
@_first_passing_value_and_validator ||=
|
||||
values_and_validators.detect do |value, validator|
|
||||
value_matches?(value, validator)
|
||||
end
|
||||
end
|
||||
|
||||
def first_passing_validator
|
||||
first_passing_value_and_validator[1]
|
||||
end
|
||||
|
||||
def model_name
|
||||
instance.class.to_s.underscore
|
||||
end
|
||||
|
||||
def human_attribute_name
|
||||
instance.class.human_attribute_name(attribute_to_check_message_against)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,46 +7,55 @@ module Shoulda
|
|||
class DisallowValueMatcher
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :allow_matcher, :_after_setting_value
|
||||
def_delegators :allow_matcher,
|
||||
:_after_setting_value,
|
||||
:attribute_to_set,
|
||||
:description,
|
||||
:model,
|
||||
:last_value_set,
|
||||
:simple_description,
|
||||
:failure_message_preface,
|
||||
:failure_message_preface=
|
||||
|
||||
def initialize(value)
|
||||
@allow_matcher = AllowValueMatcher.new(value)
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
!@allow_matcher.matches?(subject)
|
||||
!allow_matcher.matches?(subject)
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
@allow_matcher.for(attribute)
|
||||
allow_matcher.for(attribute)
|
||||
self
|
||||
end
|
||||
|
||||
def on(context)
|
||||
@allow_matcher.on(context)
|
||||
allow_matcher.on(context)
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message, options={})
|
||||
@allow_matcher.with_message(message, options)
|
||||
allow_matcher.with_message(message, options)
|
||||
self
|
||||
end
|
||||
|
||||
def strict(strict = true)
|
||||
allow_matcher.strict(strict)
|
||||
self
|
||||
end
|
||||
|
||||
def ignoring_interference_by_writer
|
||||
@allow_matcher.ignoring_interference_by_writer
|
||||
allow_matcher.ignoring_interference_by_writer
|
||||
self
|
||||
end
|
||||
|
||||
def failure_message
|
||||
@allow_matcher.failure_message_when_negated
|
||||
allow_matcher.failure_message_when_negated
|
||||
end
|
||||
|
||||
def failure_message_when_negated
|
||||
@allow_matcher.failure_message
|
||||
end
|
||||
|
||||
def strict
|
||||
@allow_matcher.strict
|
||||
self
|
||||
allow_matcher.failure_message
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -3,25 +3,17 @@ module Shoulda
|
|||
module ActiveModel
|
||||
# @private
|
||||
module Helpers
|
||||
def pretty_error_messages(obj)
|
||||
obj.errors.map do |attribute, message|
|
||||
full_message = message.dup.inspect
|
||||
parenthetical_parts = []
|
||||
def pretty_error_messages(object)
|
||||
format_validation_errors(object.errors)
|
||||
end
|
||||
|
||||
unless attribute.to_sym == :base
|
||||
parenthetical_parts << "attribute: #{attribute}"
|
||||
def format_validation_errors(errors)
|
||||
list_items = errors.keys.map do |attribute|
|
||||
messages = errors[attribute]
|
||||
"* #{attribute}: #{messages}"
|
||||
end
|
||||
|
||||
if obj.respond_to?(attribute)
|
||||
parenthetical_parts << "value: #{obj.__send__(attribute).inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
if parenthetical_parts.any?
|
||||
full_message << " (#{parenthetical_parts.join(', ')})"
|
||||
end
|
||||
|
||||
"* " + full_message
|
||||
end.join("\n")
|
||||
list_items.join("\n")
|
||||
end
|
||||
|
||||
def default_error_message(type, options = {})
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
# @private
|
||||
module StrictValidator
|
||||
def allow_description(allowed_values)
|
||||
"doesn't raise when #{attribute} is set to #{allowed_values}"
|
||||
end
|
||||
|
||||
def expected_message_from(attribute_message)
|
||||
"#{human_attribute_name} #{attribute_message}"
|
||||
end
|
||||
|
||||
def formatted_messages
|
||||
[messages.first.message]
|
||||
end
|
||||
|
||||
def messages_description
|
||||
if has_messages?
|
||||
': ' + messages.first.message.inspect
|
||||
else
|
||||
' no exception'
|
||||
end
|
||||
end
|
||||
|
||||
def expected_messages_description(expected_message)
|
||||
if expected_message
|
||||
"exception to include #{expected_message.inspect}"
|
||||
else
|
||||
'an exception to have been raised'
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def collect_messages
|
||||
validation_exceptions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validation_exceptions
|
||||
record.valid?(context)
|
||||
[]
|
||||
rescue ::ActiveModel::StrictValidationFailed => exception
|
||||
[exception]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,99 +5,104 @@ module Shoulda
|
|||
class Validator
|
||||
include Helpers
|
||||
|
||||
attr_writer :attribute, :context, :record
|
||||
attr_writer :context, :expects_strict
|
||||
|
||||
def initialize
|
||||
def initialize(record, attribute)
|
||||
@record = record
|
||||
@attribute = attribute
|
||||
@context = context
|
||||
@expects_strict = false
|
||||
reset
|
||||
end
|
||||
|
||||
def reset
|
||||
@messages = nil
|
||||
end
|
||||
|
||||
def strict=(strict)
|
||||
@strict = strict
|
||||
|
||||
if strict
|
||||
extend StrictValidator
|
||||
end
|
||||
end
|
||||
|
||||
def allow_description(allowed_values)
|
||||
"allow #{attribute} to be set to #{allowed_values}"
|
||||
end
|
||||
|
||||
def expected_message_from(attribute_message)
|
||||
attribute_message
|
||||
@_validation_result = nil
|
||||
@captured_validation_exception = false
|
||||
@captured_range_error = false
|
||||
end
|
||||
|
||||
def messages
|
||||
@messages ||= collect_messages
|
||||
end
|
||||
|
||||
def formatted_messages
|
||||
messages
|
||||
if expects_strict?
|
||||
[validation_exception_message]
|
||||
else
|
||||
validation_error_messages
|
||||
end
|
||||
end
|
||||
|
||||
def has_messages?
|
||||
messages.any?
|
||||
end
|
||||
|
||||
def messages_description
|
||||
if has_messages?
|
||||
" errors:\n#{pretty_error_messages(record)}"
|
||||
else
|
||||
' no errors'
|
||||
end
|
||||
def type_of_message_matched?
|
||||
expects_strict? == captured_validation_exception?
|
||||
end
|
||||
|
||||
def expected_messages_description(expected_message)
|
||||
if expected_message
|
||||
"errors to include #{expected_message.inspect}"
|
||||
else
|
||||
'errors'
|
||||
end
|
||||
def captured_validation_exception?
|
||||
@captured_validation_exception
|
||||
end
|
||||
|
||||
def captured_range_error?
|
||||
!!captured_range_error
|
||||
!!@captured_range_error
|
||||
end
|
||||
|
||||
def all_validation_errors
|
||||
validation_result[:all_validation_errors]
|
||||
end
|
||||
|
||||
def all_formatted_validation_error_messages
|
||||
format_validation_errors(all_validation_errors)
|
||||
end
|
||||
|
||||
def validation_error_messages
|
||||
validation_result[:validation_error_messages]
|
||||
end
|
||||
|
||||
def validation_exception_message
|
||||
validation_result[:validation_exception_message]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :attribute, :context, :strict, :record,
|
||||
:captured_range_error
|
||||
|
||||
def collect_messages
|
||||
validation_errors
|
||||
end
|
||||
attr_reader :attribute, :context, :record
|
||||
|
||||
private
|
||||
|
||||
def strict?
|
||||
!!@strict
|
||||
def expects_strict?
|
||||
@expects_strict
|
||||
end
|
||||
|
||||
def collect_errors_or_exceptions
|
||||
collect_messages
|
||||
def validation_result
|
||||
@_validation_result ||= perform_validation
|
||||
end
|
||||
|
||||
def validation_errors
|
||||
def perform_validation
|
||||
if context
|
||||
record.valid?(context)
|
||||
else
|
||||
record.valid?
|
||||
end
|
||||
|
||||
if record.errors.respond_to?(:[])
|
||||
record.errors[attribute]
|
||||
else
|
||||
record.errors.on(attribute)
|
||||
end
|
||||
end
|
||||
all_validation_errors = record.errors.dup
|
||||
|
||||
def human_attribute_name
|
||||
record.class.human_attribute_name(attribute)
|
||||
validation_error_messages =
|
||||
if record.errors.respond_to?(:[])
|
||||
record.errors[attribute]
|
||||
else
|
||||
record.errors.on(attribute)
|
||||
end
|
||||
|
||||
{
|
||||
all_validation_errors: all_validation_errors,
|
||||
validation_error_messages: validation_error_messages,
|
||||
validation_exception_message: nil
|
||||
}
|
||||
rescue ::ActiveModel::StrictValidationFailed => exception
|
||||
@captured_validation_exception = true
|
||||
{
|
||||
all_validation_errors: nil,
|
||||
validation_error_messages: [],
|
||||
validation_exception_message: exception.message
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,21 +13,24 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
it 'describes itself with multiple values' do
|
||||
matcher = allow_value('foo', 'bar').for(:baz)
|
||||
|
||||
expect(matcher.description).to eq 'allow baz to be set to any of ["foo", "bar"]'
|
||||
expect(matcher.description).to eq(
|
||||
'allow :baz to be "foo" or "bar"'
|
||||
)
|
||||
end
|
||||
|
||||
it 'describes itself with a single value' do
|
||||
matcher = allow_value('foo').for(:baz)
|
||||
|
||||
expect(matcher.description).to eq 'allow baz to be set to "foo"'
|
||||
expect(matcher.description).to eq 'allow :baz to be "foo"'
|
||||
end
|
||||
|
||||
if active_model_3_2?
|
||||
it 'describes itself with a strict validation' do
|
||||
strict_matcher = allow_value('xyz').for(:attr).strict
|
||||
|
||||
expect(strict_matcher.description).
|
||||
to eq %q(doesn't raise when attr is set to "xyz")
|
||||
expect(strict_matcher.description).to eq(
|
||||
'allow :attr to be "xyz", raising a validation exception on failure'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -51,8 +54,19 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
expect(validating_format(with: /abc/)).to allow_value('abcde').for(:attr)
|
||||
end
|
||||
|
||||
it 'rejects a bad value' do
|
||||
expect(validating_format(with: /abc/)).not_to allow_value('xyz').for(:attr)
|
||||
it 'rejects a bad value with an appropriate failure message' do
|
||||
message = <<-MESSAGE
|
||||
After setting :attr to "xyz", the matcher expected the Example to be
|
||||
valid, but it was invalid instead, producing these validation errors:
|
||||
|
||||
* attr: ["is invalid"]
|
||||
MESSAGE
|
||||
|
||||
assertion = lambda do
|
||||
expect(validating_format(with: /abc/)).to allow_value('xyz').for(:attr)
|
||||
end
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
it 'allows several good values' do
|
||||
|
@ -60,11 +74,31 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
to allow_value('abcde', 'deabc').for(:attr)
|
||||
end
|
||||
|
||||
it 'rejects several bad values' do
|
||||
expect(validating_format(with: /abc/)).
|
||||
not_to allow_value('xyz', 'zyx', nil, []).
|
||||
for(:attr).
|
||||
ignoring_interference_by_writer
|
||||
context 'given several bad values' do
|
||||
it 'rejects' do
|
||||
expect(validating_format(with: /abc/)).
|
||||
not_to allow_value('xyz', 'zyx', nil, []).
|
||||
for(:attr).
|
||||
ignoring_interference_by_writer
|
||||
end
|
||||
|
||||
it 'produces an appropriate failure message' do
|
||||
message = <<-MESSAGE
|
||||
After setting :attr to "zyx", the matcher expected the Example to be
|
||||
valid, but it was invalid instead, producing these validation errors:
|
||||
|
||||
* attr: ["is invalid"]
|
||||
MESSAGE
|
||||
|
||||
assertion = lambda do
|
||||
expect(validating_format(with: /abc/)).
|
||||
to allow_value('abc', 'abcde', 'zyx', nil, []).
|
||||
for(:attr).
|
||||
ignoring_interference_by_writer
|
||||
end
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -74,27 +108,95 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
to allow_value('abcde').for(:attr).with_message(/bad/)
|
||||
end
|
||||
|
||||
it 'rejects a bad value' do
|
||||
expect(validating_format(with: /abc/, message: 'bad value')).
|
||||
not_to allow_value('xyz').for(:attr).with_message(/bad/)
|
||||
it 'rejects a bad value with an appropriate failure message' do
|
||||
message = <<-MESSAGE
|
||||
After setting :attr to "xyz", the matcher expected the Example to be
|
||||
valid, but it was invalid instead, producing these validation errors:
|
||||
|
||||
* attr: ["bad value"]
|
||||
MESSAGE
|
||||
|
||||
assertion = lambda do
|
||||
expect(validating_format(with: /abc/, message: 'bad value')).
|
||||
to allow_value('xyz').for(:attr).with_message(/bad/)
|
||||
end
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
it 'allows interpolation values for the message to be provided' do
|
||||
options = {
|
||||
attribute_name: :attr,
|
||||
attribute_type: :string
|
||||
}
|
||||
context 'when the custom messages do not match' do
|
||||
it 'rejects with an appropriate failure message' do
|
||||
message = <<-MESSAGE
|
||||
After setting :attr to "xyz", the matcher expected the Example to be
|
||||
invalid and to produce a validation error matching /different/ on :attr.
|
||||
The record was indeed invalid, but it produced these validation errors
|
||||
instead:
|
||||
|
||||
record = record_with_custom_validation(options) do
|
||||
if self.attr == 'xyz'
|
||||
self.errors.add :attr, :greater_than, count: 2
|
||||
* attr: ["bad value"]
|
||||
MESSAGE
|
||||
|
||||
assertion = lambda do
|
||||
expect(validating_format(with: /abc/, message: 'bad value')).
|
||||
not_to allow_value('xyz').for(:attr).with_message(/different/)
|
||||
end
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when interpolation values are provided along with a custom message' do
|
||||
context 'when the messages match' do
|
||||
it 'accepts' do
|
||||
options = {
|
||||
attribute_name: :attr,
|
||||
attribute_type: :string
|
||||
}
|
||||
|
||||
record = record_with_custom_validation(options) do
|
||||
if self.attr == 'xyz'
|
||||
self.errors.add :attr, :greater_than, count: 2
|
||||
end
|
||||
end
|
||||
|
||||
expect(record).
|
||||
not_to allow_value('xyz').
|
||||
for(:attr).
|
||||
with_message(:greater_than, values: { count: 2 })
|
||||
end
|
||||
end
|
||||
|
||||
expect(record).
|
||||
not_to allow_value('xyz').
|
||||
for(:attr).
|
||||
with_message(:greater_than, values: { count: 2 })
|
||||
context 'when the messages do not match' do
|
||||
it 'rejects with an appropriate failure message' do
|
||||
options = {
|
||||
attribute_name: :attr,
|
||||
attribute_type: :string
|
||||
}
|
||||
|
||||
record = record_with_custom_validation(options) do
|
||||
if self.attr == 'xyz'
|
||||
self.errors.add :attr, "some other error"
|
||||
end
|
||||
end
|
||||
|
||||
assertion = lambda do
|
||||
expect(record).
|
||||
not_to allow_value('xyz').
|
||||
for(:attr).
|
||||
with_message(:greater_than, values: { count: 2 })
|
||||
end
|
||||
|
||||
message = <<-MESSAGE
|
||||
After setting :attr to "xyz", the matcher expected the Example to be
|
||||
invalid and to produce the validation error "must be greater than 2" on
|
||||
:attr. The record was indeed invalid, but it produced these validation
|
||||
errors instead:
|
||||
|
||||
* attr: ["some other error"]
|
||||
MESSAGE
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -102,25 +204,62 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
include UnitTests::AllowValueMatcherHelpers
|
||||
|
||||
context 'when the validation error message was provided directly' do
|
||||
it 'passes given a valid value' do
|
||||
builder = builder_for_record_with_different_error_attribute
|
||||
expect(builder.record).
|
||||
to allow_value(builder.valid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(builder.message,
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
context 'given a valid value' do
|
||||
it 'accepts' do
|
||||
builder = builder_for_record_with_different_error_attribute
|
||||
expect(builder.record).
|
||||
to allow_value(builder.valid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(
|
||||
builder.message,
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'fails given an invalid value' do
|
||||
builder = builder_for_record_with_different_error_attribute
|
||||
invalid_value = "#{builder.valid_value} (invalid)"
|
||||
expect(builder.record).
|
||||
not_to allow_value(invalid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(builder.message,
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
context 'given an invalid value' do
|
||||
it 'rejects' do
|
||||
builder = builder_for_record_with_different_error_attribute
|
||||
invalid_value = "#{builder.valid_value} (invalid)"
|
||||
|
||||
expect(builder.record).
|
||||
not_to allow_value(invalid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(
|
||||
builder.message,
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
end
|
||||
|
||||
context 'if the messages do not match' do
|
||||
it 'technically accepts' do
|
||||
builder = builder_for_record_with_different_error_attribute(
|
||||
message: "a different error"
|
||||
)
|
||||
invalid_value = "#{builder.valid_value} (invalid)"
|
||||
|
||||
assertion = lambda do
|
||||
expect(builder.record).
|
||||
not_to allow_value(invalid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(
|
||||
"some error",
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
end
|
||||
|
||||
message = <<-MESSAGE
|
||||
After setting :#{builder.attribute_to_validate} to "#{invalid_value}", the
|
||||
matcher expected the #{builder.model.name} to be invalid and to produce the validation
|
||||
error "some error" on :#{builder.attribute_that_receives_error}. The record was
|
||||
indeed invalid, but it produced these validation errors instead:
|
||||
|
||||
* #{builder.attribute_that_receives_error}: ["a different error"]
|
||||
MESSAGE
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -130,7 +269,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
expect(builder.record).
|
||||
to allow_value(builder.valid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(builder.validation_message_key,
|
||||
with_message(
|
||||
builder.validation_message_key,
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
end
|
||||
|
@ -141,7 +281,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
expect(builder.record).
|
||||
not_to allow_value(invalid_value).
|
||||
for(builder.attribute_to_validate).
|
||||
with_message(builder.validation_message_key,
|
||||
with_message(
|
||||
builder.validation_message_key,
|
||||
against: builder.attribute_that_receives_error
|
||||
)
|
||||
end
|
||||
|
@ -199,11 +340,16 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
end
|
||||
|
||||
it "does not match given good values along with bad values" do
|
||||
message = %{Expected errors when attr is set to "12345",\ngot no errors}
|
||||
message = <<-MESSAGE.strip_heredoc
|
||||
After setting :attr to "12345", the matcher expected the Example to be
|
||||
invalid, but it was valid instead.
|
||||
MESSAGE
|
||||
|
||||
expect {
|
||||
assertion = lambda do
|
||||
expect(model).not_to allow_value('12345', *bad_values).for(:attr)
|
||||
}.to fail_with_message(message)
|
||||
end
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -226,25 +372,39 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
|
||||
if active_model_3_2?
|
||||
context 'an attribute with a strict format validation' do
|
||||
it 'strictly rejects a bad value' do
|
||||
expect(validating_format(with: /abc/, strict: true)).
|
||||
not_to allow_value('xyz').for(:attr).strict
|
||||
end
|
||||
context 'when qualified with strict' do
|
||||
it 'rejects a bad value, providing the correct failure message' do
|
||||
message = <<-MESSAGE.strip_heredoc
|
||||
After setting :attr to "xyz", the matcher expected the Example to be
|
||||
valid, but it was invalid instead, raising a validation exception with
|
||||
the message "Attr is invalid".
|
||||
MESSAGE
|
||||
|
||||
it 'strictly allows a bad value with a different message' do
|
||||
expect(validating_format(with: /abc/, strict: true)).
|
||||
to allow_value('xyz').for(:attr).with_message(/abc/).strict
|
||||
end
|
||||
assertion = lambda do
|
||||
expect(validating_format(with: /abc/, strict: true)).
|
||||
to allow_value('xyz').for(:attr).strict
|
||||
end
|
||||
|
||||
it 'provides a useful negative failure message' do
|
||||
matcher = allow_value('xyz').for(:attr).strict.with_message(/abc/)
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
matcher.matches?(validating_format(with: /abc/, strict: true))
|
||||
context 'qualified with a custom message' do
|
||||
it 'rejects a bad value when the failure messages do not match' do
|
||||
message = <<-MESSAGE.strip_heredoc
|
||||
After setting :attr to "xyz", the matcher expected the Example to be
|
||||
invalid and to raise a validation exception with message matching /abc/.
|
||||
The record was indeed invalid, but the exception message was "Attr is
|
||||
invalid" instead.
|
||||
MESSAGE
|
||||
|
||||
expect(matcher.failure_message_when_negated).to eq(
|
||||
%{Expected exception to include /abc/ when attr is set to "xyz",\n} +
|
||||
%{got: "Attr is invalid"}
|
||||
)
|
||||
assertion = lambda do
|
||||
expect(validating_format(with: /abc/, strict: true)).
|
||||
not_to allow_value('xyz').for(:attr).with_message(/abc/).strict
|
||||
end
|
||||
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue