From a4289e9651f140f3d0d40e3480c0ce26f4341822 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Sat, 11 Jul 2020 11:33:54 -0600 Subject: [PATCH] Add the qualifier allow_blank to validate_length_of (#1318) Co-authored-by: Elliot Winkler --- .../validate_inclusion_of_matcher.rb | 23 +------- .../validate_length_of_matcher.rb | 30 +++++++++- .../active_model/validation_matcher.rb | 25 +++++++++ .../validate_length_of_matcher_spec.rb | 56 +++++++++++++++++++ 4 files changed, 110 insertions(+), 24 deletions(-) diff --git a/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb index 6ddb740f7..8ae79a67a 100644 --- a/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb @@ -311,15 +311,6 @@ def in_range(range) self end - def allow_blank - @options[:allow_blank] = true - self - end - - def expects_to_allow_blank? - @options[:allow_blank] - end - def allow_nil @options[:allow_nil] = true self @@ -423,14 +414,14 @@ def matches_for_array? allows_all_values_in_array? && disallows_all_values_outside_of_array? && allows_nil_value? && - allows_blank_value? + allow_blank_matches? end def does_not_match_for_array? disallows_any_values_in_array? || allows_any_value_outside_of_array? || disallows_nil_value? || - disallows_blank_value? + allow_blank_does_not_match? end def allows_lower_value @@ -616,16 +607,6 @@ def disallows_nil_value? @options[:allow_nil] && disallows_value_of(nil) end - def allows_blank_value? - @options[:allow_blank] != true || - BLANK_VALUES.all? { |value| allows_value_of(value) } - end - - def disallows_blank_value? - @options[:allow_blank] && - BLANK_VALUES.any? { |value| disallows_value_of(value) } - end - def inspected_array Shoulda::Matchers::Util.inspect_values(@array).to_sentence( two_words_connector: " or ", diff --git a/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb index a4032495a..4c5dff6b1 100644 --- a/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb @@ -232,13 +232,34 @@ module ActiveModel # it { should validate_length_of(:bio).is_at_least(15).allow_nil } # end # - # # Test::Unit + # # Minitest (Shoulda) # class UserTest < ActiveSupport::TestCase # should validate_length_of(:bio).is_at_least(15).allow_nil # end # # @return [ValidateLengthOfMatcher] # + # # ##### allow_blank + # + # Use `allow_blank` to assert that the attribute allows blank. + # + # class User + # include ActiveModel::Model + # attr_accessor :bio + # + # validates_length_of :bio, minimum: 15, allow_blank: true + # end + # + # # RSpec + # describe User do + # it { should validate_length_of(:bio).is_at_least(15).allow_blank } + # end + # + # # Minitest (Shoulda) + # class UserTest < ActiveSupport::TestCase + # should validate_length_of(:bio).is_at_least(15).allow_blank + # end + # def validate_length_of(attr) ValidateLengthOfMatcher.new(attr) end @@ -331,7 +352,8 @@ def matches?(subject) lower_bound_matches? && upper_bound_matches? && - allow_nil_matches? + allow_nil_matches? && + allow_blank_matches? end def does_not_match?(subject) @@ -339,7 +361,8 @@ def does_not_match?(subject) lower_bound_does_not_match? || upper_bound_does_not_match? || - allow_nil_does_not_match? + allow_nil_does_not_match? || + allow_blank_does_not_match? end private @@ -376,6 +399,7 @@ def allows_lower_length? def disallows_lower_length? !@options.key?(:minimum) || @options[:minimum] == 0 || + (@options[:minimum] == 1 && expects_to_allow_blank?) || disallows_length_of?( @options[:minimum] - 1, translated_short_message diff --git a/lib/shoulda/matchers/active_model/validation_matcher.rb b/lib/shoulda/matchers/active_model/validation_matcher.rb index 3575999c9..e2a153c40 100644 --- a/lib/shoulda/matchers/active_model/validation_matcher.rb +++ b/lib/shoulda/matchers/active_model/validation_matcher.rb @@ -24,6 +24,11 @@ def on(context) self end + def allow_blank + options[:allow_blank] = true + self + end + def strict @expects_strict = true self @@ -116,8 +121,20 @@ def disallow_value_matcher(value, message = nil, &block) ) end + def allow_blank_matches? + !expects_to_allow_blank? || + blank_values.all? { |value| allows_value_of(value) } + end + + def allow_blank_does_not_match? + expects_to_allow_blank? && + blank_values.all? { |value| disallows_value_of(value) } + end + private + attr_reader :options + def overall_failure_message Shoulda::Matchers.word_wrap( "Expected #{model.name} to #{description}, but this could not be " + @@ -161,6 +178,14 @@ def run_allow_or_disallow_matcher(matcher) @last_submatcher_run = matcher matcher.matches?(subject) end + + def expects_to_allow_blank? + options[:allow_blank] + end + + def blank_values + ['', ' ', "\n", "\r", "\t", "\f"] + end end end end diff --git a/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb index 3cdf2c0f3..bcd568049 100644 --- a/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb @@ -311,6 +311,62 @@ def configure_validation_matcher(matcher) end end + context 'qualified with allow_blank' do + context 'and validating with allow_blank' do + context 'with minimum' do + context 'and minimum is 1' do + it 'accepts' do + expect(validating_length(minimum: 1, allow_blank: true)). + to validate_length_of(:attr).is_at_least(1).allow_blank + end + end + + context 'and minimum is greater than 1' do + it 'accepts' do + expect(validating_length(minimum: 2, allow_blank: true)). + to validate_length_of(:attr).is_at_least(2).allow_blank + end + end + end + + context 'with maximum' do + context 'and maximum is 0' do + it 'accepts' do + expect(validating_length(maximum: 0, allow_blank: true)). + to validate_length_of(:attr).is_at_most(0).allow_blank + end + end + + context 'and maximum is greater than 0' do + it 'accepts' do + expect(validating_length(maximum: 1, allow_blank: true)). + to validate_length_of(:attr).is_at_most(1).allow_blank + end + end + end + end + + context 'and not validating with allow_blank' do + it 'rejects' do + assertion = lambda do + expect(validating_length(minimum: 1)). + to validate_length_of(:attr).is_at_least(1).allow_blank + end + + message = <<-MESSAGE +Expected Example to validate that the length of :attr is at least 1, but +this could not be proved. + After setting :attr to ‹""›, the matcher expected the Example to be + valid, but it was invalid instead, producing these validation errors: + + * attr: ["is too short (minimum is 1 character)"] + MESSAGE + + expect(&assertion).to fail_with_message(message) + end + end + end + def define_model_validating_length(options = {}) options = options.dup attribute_name = options.delete(:attribute_name) { :attr }