lib/rspec/matchers/built_in/change.rb in rspec-expectations-3.0.0.beta2 vs lib/rspec/matchers/built_in/change.rb in rspec-expectations-3.0.0.rc1

- old
+ new

@@ -1,75 +1,107 @@ module RSpec module Matchers module BuiltIn - # Describes an expected mutation. + # @api private + # Provides the implementation for `change`. + # Not intended to be instantiated directly. class Change include Composable + # @api public # Specifies the delta of the expected change. def by(expected_delta) ChangeRelatively.new(@change_details, expected_delta, :by) do |actual_delta| values_match?(expected_delta, actual_delta) end end + # @api public # Specifies a minimum delta of the expected change. def by_at_least(minimum) ChangeRelatively.new(@change_details, minimum, :by_at_least) do |actual_delta| actual_delta >= minimum end end + # @api public # Specifies a maximum delta of the expected change. def by_at_most(maximum) ChangeRelatively.new(@change_details, maximum, :by_at_most) do |actual_delta| actual_delta <= maximum end end + # @api public # Specifies the new value you expect. def to(value) ChangeToValue.new(@change_details, value) end + # @api public # Specifies the original value. def from(value) ChangeFromValue.new(@change_details, value) end - # @api private + # @private def matches?(event_proc) + @event_proc = event_proc + return false unless Proc === event_proc raise_block_syntax_error if block_given? @change_details.perform_change(event_proc) @change_details.changed? end + def does_not_match?(event_proc) + raise_block_syntax_error if block_given? + !matches?(event_proc) && Proc === event_proc + end + # @api private + # @return [String] def failure_message - "expected #{@change_details.message} to have changed, but is still #{description_of @change_details.actual_before}" + "expected #{@change_details.message} to have changed, but #{positive_failure_reason}" end # @api private + # @return [String] def failure_message_when_negated - "expected #{@change_details.message} not to have changed, but did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}" + "expected #{@change_details.message} not to have changed, but #{negative_failure_reason}" end # @api private + # @return [String] def description "change #{@change_details.message}" end + # @private + def supports_block_expectations? + true + end + private def initialize(receiver=nil, message=nil, &block) @change_details = ChangeDetails.new(receiver, message, &block) end def raise_block_syntax_error raise SyntaxError, "The block passed to the `change` matcher must use `{ ... }` instead of do/end" end + + def positive_failure_reason + return "was not given a block" unless Proc === @event_proc + "is still #{description_of @change_details.actual_before}" + end + + def negative_failure_reason + return "was not given a block" unless Proc === @event_proc + "did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}" + end end # Used to specify a relative change. # @api private class ChangeRelatively @@ -80,57 +112,85 @@ @expected_delta = expected_delta @relativity = relativity @comparer = comparer end + # @private def failure_message - "expected #{@change_details.message} to have changed #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}, " + - "but was changed by #{description_of @change_details.actual_delta}" + "expected #{@change_details.message} to have changed #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}, but #{failure_reason}" end + # @private def matches?(event_proc) + @event_proc = event_proc + return false unless Proc === event_proc @change_details.perform_change(event_proc) @comparer.call(@change_details.actual_delta) end + # @private def does_not_match?(event_proc) raise NotImplementedError, "`expect { }.not_to change { }.#{@relativity}()` is not supported" end - # @api private + # @private def description "change #{@change_details.message} #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}" end + + # @private + def supports_block_expectations? + true + end + + private + + def failure_reason + return "was not given a block" unless Proc === @event_proc + "was changed by #{description_of @change_details.actual_delta}" + end end - # Base class for specifying a change from and/or to specific values. # @api private + # Base class for specifying a change from and/or to specific values. class SpecificValuesChange include Composable + # @private MATCH_ANYTHING = ::Object.ancestors.last def initialize(change_details, from, to) @change_details = change_details @expected_before = from @expected_after = to end + # @private def matches?(event_proc) + @event_proc = event_proc + return false unless Proc === event_proc @change_details.perform_change(event_proc) @change_details.changed? && matches_before? && matches_after? end + # @private def description "change #{@change_details.message} #{change_description}" end + # @private def failure_message - return before_value_failure unless matches_before? - return did_not_change_failure unless @change_details.changed? + return not_given_a_block_failure unless Proc === @event_proc + return before_value_failure unless matches_before? + return did_not_change_failure unless @change_details.changed? after_value_failure end + # @private + def supports_block_expectations? + true + end + private def matches_before? values_match?(@expected_before, @change_details.actual_before) end @@ -152,37 +212,48 @@ end def did_change_failure "expected #{@change_details.message} not to have changed, but did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}" end + + def not_given_a_block_failure + "expected #{@change_details.message} to have changed #{change_description}, but was not given a block" + end end + # @api private # Used to specify a change from a specific value # (and, optionally, to a specific value). - # @api private class ChangeFromValue < SpecificValuesChange def initialize(change_details, expected_before) @description_suffix = nil super(change_details, expected_before, MATCH_ANYTHING) end + # @api public + # Specifies the new value you expect. def to(value) @expected_after = value @description_suffix = " to #{description_of value}" self end + # @private def does_not_match?(event_proc) if @description_suffix raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported" end + @event_proc = event_proc + return false unless Proc === event_proc @change_details.perform_change(event_proc) !@change_details.changed? && matches_before? end + # @private def failure_message_when_negated + return not_given_a_block_failure unless Proc === @event_proc return before_value_failure unless matches_before? did_change_failure end private @@ -190,25 +261,28 @@ def change_description "from #{description_of @expected_before}#{@description_suffix}" end end + # @api private # Used to specify a change to a specific value # (and, optionally, from a specific value). - # @api private class ChangeToValue < SpecificValuesChange def initialize(change_details, expected_after) @description_suffix = nil super(change_details, MATCH_ANYTHING, expected_after) end + # @api public + # Specifies the original value. def from(value) @expected_before = value @description_suffix = " from #{description_of value}" self end + # @private def does_not_match?(event_proc) raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported" end private @@ -216,12 +290,12 @@ def change_description "to #{description_of @expected_after}#{@description_suffix}" end end + # @private # Encapsulates the details of the before/after values. - # @api private class ChangeDetails attr_reader :message, :actual_before, :actual_after def initialize(receiver=nil, message=nil, &block) @message = message ? "##{message}" : "result" @@ -244,9 +318,11 @@ private def evaluate_value_proc case val = @value_proc.call + when IO # enumerable, but we don't want to dup it. + val when Enumerable, String val.dup else val end