From a71d6f79458abb62f5c028068d7ca4a0ceb3897f Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Thu, 1 Jan 2009 20:13:44 +1100 Subject: [PATCH] added between option testing to matcher and refactored --- .../rails/matchers/validate_timeliness.rb | 138 +++++++++++------- .../matchers/validate_timeliness_spec.rb | 33 ++++- 2 files changed, 117 insertions(+), 54 deletions(-) diff --git a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb index 782aecb..73c97e5 100644 --- a/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +++ b/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb @@ -2,120 +2,156 @@ module Spec module Rails module Matchers class ValidateTimeliness - cattr_accessor :test_values - @@test_values = { + VALIDITY_TEST_VALUES = { :date => {:pass => '2000-01-01', :fail => '2000-01-32'}, :time => {:pass => '12:00', :fail => '25:00'}, :datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'} } + OPTION_TEST_SETTINGS = { + :before => { :method => :-, :modify_on => :valid }, + :after => { :method => :+, :modify_on => :valid }, + :on_or_before => { :method => :+, :modify_on => :invalid }, + :on_or_after => { :method => :-, :modify_on => :invalid } + } + def initialize(attribute, options) @expected, @options = attribute, options @validator = ValidatesTimeliness::Validator.new(options) - compile_error_messages - end - - def compile_error_messages - messages = validator.send(:error_messages) - @messages = messages.inject({}) {|h, (k, v)| h[k] = v.gsub(/ (\%s|\{\{\w*\}\})/, ''); h } end def matches?(record) @record = record - type = options[:type] + @type = options[:type] - invalid_value = @@test_values[type][:fail] - valid_value = parse_and_cast(@@test_values[type][:pass]) - valid = error_matching(invalid_value, /#{messages["invalid_#{type}".to_sym]}/) && - no_error_matching(valid_value, /#{messages["invalid_#{type}".to_sym]}/) + valid = test_validity - valid = test_option(:before, :-) if options[:before] && valid - valid = test_option(:after, :+) if options[:after] && valid + valid = test_option(:before) if @options[:before] && valid + valid = test_option(:after) if @options[:after] && valid - valid = test_option(:on_or_before, :+, :modify_on => :invalid) if options[:on_or_before] && valid - valid = test_option(:on_or_after, :-, :modify_on => :invalid) if options[:on_or_after] && valid + valid = test_option(:on_or_before) if @options[:on_or_before] && valid + valid = test_option(:on_or_after) if @options[:on_or_after] && valid + + valid = test_between if @options[:between] && valid return valid end def failure_message - "expected model to validate #{options[:type]} attribute #{expected.inspect} with #{last_failure}" + "expected model to validate #{@type} attribute #{@expected.inspect} with #{@last_failure}" end def negative_failure_message - "expected not to validate #{options[:type]} attribute #{expected.inspect}" + "expected not to validate #{@type} attribute #{@expected.inspect}" end def description - "have validated #{options[:type]} attribute #{expected.inspect}" + "have validated #{@type} attribute #{@expected.inspect}" end private - attr_reader :actual, :expected, :record, :options, :messages, :last_failure, :validator - - def test_option(option, modifier, settings={}) - settings.reverse_merge!(:modify_on => :valid) - boundary = parse_and_cast(options[option]) + + def test_validity + invalid_value = VALIDITY_TEST_VALUES[@type][:fail] + valid_value = parse_and_cast(VALIDITY_TEST_VALUES[@type][:pass]) + error_matching(invalid_value, "invalid_#{@type}".to_sym) && + no_error_matching(valid_value, "invalid_#{@type}".to_sym) + end + + def test_option(option) + settings = OPTION_TEST_SETTINGS[option] + boundary = parse_and_cast(@options[option]) + method = settings[:method] + valid_value, invalid_value = if settings[:modify_on] == :valid - [ boundary.send(modifier, 1), boundary ] + [ boundary.send(method, 1), boundary ] else - [ boundary, boundary.send(modifier, 1) ] + [ boundary, boundary.send(method, 1) ] end - message = messages[option] - error_matching(invalid_value, /#{message}/) && - no_error_matching(valid_value, /#{message}/) + error_matching(invalid_value, option) && + no_error_matching(valid_value, option) + end + + def test_before + before = parse_and_cast(@options[:before]) + + error_matching(before - 1, :before) && + no_error_matching(before, :before) + end + + def test_between + between = parse_and_cast(@options[:between]) + + error_matching(between.first - 1, :between) && + error_matching(between.last + 1, :between) && + no_error_matching(between.first, :between) && + no_error_matching(between.last, :between) end def parse_and_cast(value) - value = validator.send(:restriction_value, value, record) - validator.send(:type_cast_value, value) + value = @validator.send(:restriction_value, value, @record) + @validator.send(:type_cast_value, value) end - def error_matching(value, match) - record.send("#{expected}=", value) - record.valid? - errors = record.errors.on(expected) - pass = [ errors ].flatten.any? {|error| match === error } - @last_failure = "error matching #{match.inspect} when value is #{format_value(value)}" unless pass + def error_matching(value, option) + match = error_message_for(option) + @record.send("#{@expected}=", value) + @record.valid? + errors = @record.errors.on(@expected) + pass = [ errors ].flatten.any? {|error| /#{match}/ === error } + @last_failure = "error matching '#{match}' when value is #{format_value(value)}" unless pass pass end - def no_error_matching(value, match) - pass = !error_matching(value, match) - @last_failure = "no error matching #{match.inspect} when value is #{format_value(value)}" unless pass + def no_error_matching(value, option) + pass = !error_matching(value, option) + unless pass + error = error_message_for(option) + @last_failure = "no error matching '#{error}' when value is #{format_value(value)}" + end pass end + + def error_message_for(option) + msg = @validator.send(:error_messages)[option] + restriction = @validator.send(:restriction_value, @validator.configuration[option], @record) + + if restriction + restriction = [restriction] unless restriction.is_a?(Array) + restriction.map! {|r| @validator.send(:type_cast_value, r) } + interpolate = @validator.send(:interpolation_values, option, restriction ) + if defined?(I18n) + msg = @record.errors.generate_message(@expected, option, interpolate) + else + msg = msg % interpolate + end + end + msg + end def format_value(value) return value if value.is_a?(String) - value.strftime(ValidatesTimeliness::Validator.error_value_formats[options[:type]]) + value.strftime(ValidatesTimeliness::Validator.error_value_formats[@type]) end end def validate_date(attribute, options={}) options[:type] = :date - validate_timeliness_of(attribute, options) + ValidateTimeliness.new(attribute, options) end def validate_time(attribute, options={}) options[:type] = :time - validate_timeliness_of(attribute, options) + ValidateTimeliness.new(attribute, options) end def validate_datetime(attribute, options={}) options[:type] = :datetime - validate_timeliness_of(attribute, options) - end - - private - - def validate_timeliness_of(attribute, options={}) ValidateTimeliness.new(attribute, options) end - end end end diff --git a/spec/spec/rails/matchers/validate_timeliness_spec.rb b/spec/spec/rails/matchers/validate_timeliness_spec.rb index d5ba5f4..7cdf7b1 100644 --- a/spec/spec/rails/matchers/validate_timeliness_spec.rb +++ b/spec/spec/rails/matchers/validate_timeliness_spec.rb @@ -6,13 +6,17 @@ end class WithValidation < Person validates_date :birth_date, :before => '2000-01-10', :after => '2000-01-01', - :on_or_before => '2000-01-09', :on_or_after => '2000-01-02' + :on_or_before => '2000-01-09', :on_or_after => '2000-01-02', + :between => ['2000-01-01', '2000-01-03'] + validates_time :birth_time, :before => '23:00', :after => '09:00', - :on_or_before => '22:00', :on_or_after => '10:00' + :on_or_before => '22:00', :on_or_after => '10:00', + :between => ['09:00', '17:00'] validates_datetime :birth_date_and_time, :before => '2000-01-10 23:00', :after => '2000-01-01 09:00', - :on_or_before => '2000-01-09 23:00', :on_or_after => '2000-01-02 09:00' + :on_or_before => '2000-01-09 23:00', :on_or_after => '2000-01-02 09:00', + :between => ['2000-01-01 09:00', '2000-01-01 17:00'] end @@ -137,6 +141,29 @@ describe "ValidateTimeliness matcher" do end end + describe "between option" do + test_values = { + :date => [ ['2000-01-01', '2000-01-03'], ['2000-01-01', '2000-01-04'] ], + :time => [ ['09:00', '17:00'], ['09:00', '17:01'] ], + :datetime => [ ['2000-01-01 09:00', '2000-01-01 17:00'], ['2000-01-01 09:00', '2000-01-01 17:01'] ] + } + + [:date, :time, :datetime].each do |type| + + it "should report that #{type} is validated" do + with_validation.should self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0]) + end + + it "should report that #{type} is not validated when option value is incorrect" do + with_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][1]) + end + + it "should report that #{type} is not validated with option" do + no_validation.should_not self.send("validate_#{type}", attribute_for_type(type), :between => test_values[type][0]) + end + end + end + describe "custom messages" do before do