This commit is contained in:
Elliot Winkler 2017-11-29 17:28:26 -06:00
parent e45a70dffa
commit d09e8f0a16
8 changed files with 172 additions and 93 deletions

View File

@ -8,6 +8,7 @@ AllCops:
- "db/**/*" - "db/**/*"
DisplayCopNames: false DisplayCopNames: false
StyleGuideCopsOnly: false StyleGuideCopsOnly: false
TargetRubyVersion: 2.4
Naming/AccessorMethodName: Naming/AccessorMethodName:
Description: Check the naming of accessor methods for get_/set_. Description: Check the naming of accessor methods for get_/set_.
Enabled: false Enabled: false
@ -22,7 +23,6 @@ Naming/BinaryOperatorParameterName:
Naming/ClassAndModuleCamelCase: Naming/ClassAndModuleCamelCase:
Description: Use CamelCase for classes and modules. Description: Use CamelCase for classes and modules.
StyleGuide: https://github.com/bbatsov/ruby-style-guide#camelcase-classes StyleGuide: https://github.com/bbatsov/ruby-style-guide#camelcase-classes
Enabled: true
Naming/ConstantName: Naming/ConstantName:
Description: Constants should use SCREAMING_SNAKE_CASE. Description: Constants should use SCREAMING_SNAKE_CASE.
StyleGuide: https://github.com/bbatsov/ruby-style-guide#screaming-snake-case StyleGuide: https://github.com/bbatsov/ruby-style-guide#screaming-snake-case

View File

@ -20,7 +20,7 @@ module Shoulda
:values_to_preset, :values_to_preset,
) )
def initialize(*values) def initialize(*values, part_of_larger_matcher: false)
super super
@values_to_set = values @values_to_set = values
@options = {} @options = {}
@ -32,6 +32,7 @@ module Shoulda
@failure_message_preface = nil @failure_message_preface = nil
@attribute_changed_value_message = nil @attribute_changed_value_message = nil
@was_negated = nil @was_negated = nil
@part_of_larger_matcher = part_of_larger_matcher
end end
def for(attribute_name) def for(attribute_name)
@ -93,6 +94,19 @@ module Shoulda
@was_negated @was_negated
end end
def part_of_larger_matcher?
@part_of_larger_matcher
end
def include_attribute_changed_value_message?
!ignore_interference_by_writer.never? &&
result.attribute_setter.attribute_changed_value?
end
def attribute_changed_value_message
stored_attribute_changed_value_message.call
end
def description def description
ValidationMatcher::BuildDescription.call(self, simple_description) ValidationMatcher::BuildDescription.call(self, simple_description)
end end
@ -163,8 +177,8 @@ module Shoulda
message = attribute_setter.failure_message message = attribute_setter.failure_message
end end
if include_attribute_changed_value_message? if !part_of_larger_matcher? && include_attribute_changed_value_message?
message << "\n\n" + attribute_changed_value_message.call message << "\n\n" + attribute_changed_value_message
end end
Shoulda::Matchers.word_wrap(message) Shoulda::Matchers.word_wrap(message)
@ -232,8 +246,8 @@ module Shoulda
message = attribute_setter.failure_message message = attribute_setter.failure_message
end end
if include_attribute_changed_value_message? if !part_of_larger_matcher? && include_attribute_changed_value_message?
message << "\n\n" + attribute_changed_value_message.call message << "\n\n" + attribute_changed_value_message
end end
Shoulda::Matchers.word_wrap(message) Shoulda::Matchers.word_wrap(message)
@ -271,23 +285,18 @@ module Shoulda
end end
end end
def include_attribute_changed_value_message? def stored_attribute_changed_value_message
!ignore_interference_by_writer.never? &&
result.attribute_setter.attribute_changed_value?
end
def attribute_changed_value_message
@attribute_changed_value_message || @attribute_changed_value_message ||
method(:default_attribute_changed_value_message) method(:default_attribute_changed_value_message)
end end
def default_attribute_changed_value_message def default_attribute_changed_value_message
<<-MESSAGE.strip <<-MESSAGE.strip
As indicated in the message above, :#{result.attribute_setter.attribute_name} As indicated above, :#{result.attribute_setter.attribute_name} seems to be
seems to be changing certain values as they are set, and this could have changing certain values as they are set, and this could have something to do
something to do with why this test is failing. If you've overridden the writer with why this test is failing. If you've overridden the writer method for this
method for this attribute, then you may need to change it to make this test attribute, then you may need to change it to make this test pass. Otherwise, you
pass, or do something else entirely. may need to do something else entirely.
MESSAGE MESSAGE
end end

View File

@ -84,7 +84,8 @@ module Shoulda
end end
def simple_description def simple_description
"fail validation when :#{attribute} is empty/falsy" # "fail validation when :#{attribute} is empty/falsy"
"validate absence of :#{attribute}"
end end
protected protected
@ -118,9 +119,9 @@ module Shoulda
end end
def column_type def column_type
subject.class.respond_to?(:columns_hash) && record.class.respond_to?(:columns_hash) &&
subject.class.columns_hash[attribute.to_s].respond_to?(:type) && record.class.columns_hash[attribute.to_s].respond_to?(:type) &&
subject.class.columns_hash[attribute.to_s].type record.class.columns_hash[attribute.to_s].type
end end
def collection? def collection?
@ -132,8 +133,8 @@ module Shoulda
end end
def reflection def reflection
subject.class.respond_to?(:reflect_on_association) && record.class.respond_to?(:reflect_on_association) &&
subject.class.reflect_on_association(attribute) record.class.reflect_on_association(attribute)
end end
end end
end end

View File

@ -86,14 +86,19 @@ module Shoulda
@expected_message = :accepted @expected_message = :accepted
end end
def matches?(subject) def matches?(record)
super(subject) add_matcher_disallowing(false, expected_message)
disallows_value_of(false, @expected_message) super(record)
end end
def simple_description def simple_description
%(validate that :#{@attribute} has been set to "1") # "fail validation when :#{attribute} is not set to a true-like value"
"validate acceptance of :#{attribute}"
end end
private
attr_reader :expected_message
end end
end end
end end

View File

@ -81,36 +81,34 @@ module Shoulda
message = "\n\n" message = "\n\n"
if was_negated? if was_negated?
message << 'At least one of these submatchers should have failed:' message << 'At least one of these subtests should have failed:'
else else
message << 'All of these submatchers should have passed:' message << 'All of these subtests should have passed:'
end end
message << "\n\n" message << "\n\n"
list = submatcher_results.map do |submatcher, matched| list = submatcher_results.map do |result|
icon = if matched then '✔︎' else '✘' end item = ''
submessage = "#{icon} should #{submatcher.description}"
if !matched if result.expected?
sub_failure_message = item << "* #{result.submatcher_expectation} (✔︎)"
if submatcher.was_negated? elsif result.matched?
submatcher.failure_message_when_negated item << "* #{result.submatcher_expectation} (PASSED ✘)"
else else
submatcher.failure_message item << "* #{result.submatcher_failure_message} (✘)"
end
submessage << "\n\n"
submessage << Shoulda::Matchers.word_wrap(
"#{sub_failure_message}\n",
indent: 2,
)
end end
submessage item
end end
message << Shoulda::Matchers.word_wrap(list.join("\n")) message << list.join("\n")
if submatcher_result_with_attribute_changed_value_message.present?
message << "\n\n#{submatcher_result_with_attribute_changed_value_message.submatcher_attribute_changed_value_message}"
end
Shoulda::Matchers.word_wrap(message)
end end
def was_negated? def was_negated?
@ -178,24 +176,24 @@ module Shoulda
private private
def overall_failure_message def overall_failure_message
message = Shoulda::Matchers.word_wrap(<<~MESSAGE)
"Expected #{model.name} to #{description}, " + Your test expecting #{model.name} to #{simple_description} didn't
"but there were some issues." pass.
MESSAGE
Shoulda::Matchers.word_wrap(message)
end end
def overall_failure_message_when_negated def overall_failure_message_when_negated
Shoulda::Matchers.word_wrap( Shoulda::Matchers.word_wrap(<<~MESSAGE)
"Expected #{model.name} not to #{description}, but it did." Your test expecting #{model.name} not to #{simple_description}
) didn't pass.
MESSAGE
end end
def indented_failure_message_for_first_failing_submatcher def indented_failure_message_for_first_failing_submatcher
if failure_message_for_first_failing_submatcher.present? if failure_message_for_first_failing_submatcher.present?
"\n" + Shoulda::Matchers.word_wrap( "\n" + Shoulda::Matchers.word_wrap(
failure_message_for_first_failing_submatcher, failure_message_for_first_failing_submatcher,
indent: 2 indent: 2,
) )
end end
end end
@ -205,25 +203,39 @@ module Shoulda
end end
def all_submatchers_match? def all_submatchers_match?
submatcher_results.all? { |submatcher, matched| matched } submatcher_results.all?(&:expected?)
end end
def first_failing_submatcher def first_failing_submatcher
tuple = submatcher_results.detect { |submatcher, matched | !matched } tuple = submatcher_results.detect(&:unexpected?)
if tuple if tuple
tuple.first tuple.first
end end
end end
def submatcher_result_with_attribute_changed_value_message
unexpected_submatcher_results.detect do |result|
result.submatcher_includes_attribute_changed_value_message?
end
end
def unexpected_submatcher_results
submatcher_results.select(&:unexpected?)
end
def submatcher_results def submatcher_results
@_submatcher_results ||= submatchers.map do |submatcher| @_submatcher_results ||= submatchers.map do |submatcher|
[submatcher, submatcher.matches?(subject)] SubmatcherResult.new(
submatcher,
submatcher.matches?(subject),
was_negated?,
)
end end
end end
def build_allow_or_disallow_value_matcher(matcher_class:, values:, message:) def build_allow_or_disallow_value_matcher(matcher_class:, values:, message:)
matcher = matcher_class.new(*values). matcher = matcher_class.new(*values, part_of_larger_matcher: true).
for(attribute). for(attribute).
with_message(message). with_message(message).
on(context). on(context).
@ -234,6 +246,63 @@ module Shoulda
matcher matcher
end end
class SubmatcherResult
def initialize(submatcher, matched, parent_was_negated)
@submatcher = submatcher
@matched = matched
@parent_was_negated = parent_was_negated
end
def matched?
@matched
end
def expected?
(!parent_was_negated? && matched?) ||
(parent_was_negated? && !matched?)
end
def unexpected?
!expected?
end
def submatcher_model
submatcher.model
end
def submatcher_description
submatcher.description
end
def submatcher_failure_message
submatcher.failure_message
end
def submatcher_expectation
if submatcher.was_negated?
"Expected #{submatcher.model} not to #{submatcher.description}"
else
"Expected #{submatcher.model} to #{submatcher.description}"
end
end
def submatcher_includes_attribute_changed_value_message?
submatcher.include_attribute_changed_value_message?
end
def submatcher_attribute_changed_value_message
submatcher.attribute_changed_value_message
end
private
attr_reader :submatcher
def parent_was_negated?
@parent_was_negated
end
end
end end
end end
end end

View File

@ -30,7 +30,7 @@ module Shoulda
def fails? def fails?
perform_validation perform_validation
validation_messages.any? || validation_messages_match? validation_messages_match?
end end
def captured_validation_exception? def captured_validation_exception?

View File

@ -57,16 +57,13 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
record = define_model(:example, attr: :string).new record = define_model(:example, attr: :string).new
message = <<-MESSAGE message = <<-MESSAGE
Expected Example to fail validation when :attr is empty/falsy, but there Expected Example to validate absence of :attr, but there were some
were some issues. issues.
All of these submatchers should have passed: All of these subtests should have passed:
should fail validation when :attr is set to "an arbitrary value", * After setting :attr to "an arbitrary value", the matcher expected
producing a custom validation error the Example to be invalid, but it was valid instead. ()
After setting :attr to "an arbitrary value", the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE MESSAGE
assertion = lambda do assertion = lambda do
@ -103,16 +100,13 @@ All of these submatchers should have passed:
context 'an ActiveModel class without an absence validation' do context 'an ActiveModel class without an absence validation' do
it 'rejects with the correct failure message' do it 'rejects with the correct failure message' do
message = <<-MESSAGE message = <<-MESSAGE
Expected Example to fail validation when :attr is empty/falsy, but there Expected Example to validate absence of :attr, but there were some
were some issues. issues.
All of these submatchers should have passed: All of these subtests should have passed:
should fail validation when :attr is set to "an arbitrary value", * After setting :attr to "an arbitrary value", the matcher expected
producing a custom validation error the Example to be invalid, but it was valid instead. ()
After setting :attr to "an arbitrary value", the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE MESSAGE
assertion = lambda do assertion = lambda do
@ -174,16 +168,13 @@ All of these submatchers should have passed:
model = having_and_belonging_to_many(:children, absence: false) model = having_and_belonging_to_many(:children, absence: false)
message = <<-MESSAGE message = <<-MESSAGE
Expected Parent to fail validation when :children is empty/falsy, but Expected Parent to validate absence of :children, but there were some
there were some issues. issues.
All of these submatchers should have passed: All of these subtests should have passed:
should fail validation when :children is set to [#<Child id: nil>], * After setting :children to [#<Child id: nil>], the matcher expected
producing a custom validation error the Parent to be invalid, but it was valid instead. ()
After setting :children to [#<Child id: nil>], the matcher expected
the Parent to be invalid, but it was valid instead.
MESSAGE MESSAGE
assertion = lambda do assertion = lambda do

View File

@ -21,15 +21,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateAcceptanceOfMatcher, type: :mod
attribute_name: :attr, attribute_name: :attr,
changing_values_with: :always_nil, changing_values_with: :always_nil,
expected_message: <<-MESSAGE.strip expected_message: <<-MESSAGE.strip
Example did not properly validate that :attr has been set to "1". Your test expecting Example to validate acceptance of :attr didn't pass.
After setting :attr to false -- which was read back as nil -- the
matcher expected the Example to be invalid, but it was valid instead.
As indicated in the message above, :attr seems to be changing certain All of these subtests should have passed:
values as they are set, and this could have something to do with why
this test is failing. If you've overridden the writer method for this * After setting :attr to false -- which was read back as nil -- the
attribute, then you may need to change it to make this test pass, or matcher expected the Example to be invalid, but it was valid instead.
do something else entirely. ()
As indicated above, :attr seems to be changing certain values as they
are set, and this could have something to do with why this test is
failing. If you've overridden the writer method for this attribute, then
you may need to change it to make this test pass. Otherwise, you may
need to do something else entirely.
MESSAGE MESSAGE
}, },
}, },