1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
module RSpec
module CollectionMatchers
class Have
include RSpec::Matchers::Composable unless RSpec::Expectations::Version::STRING.to_f < 3.0
QUERY_METHODS = [:size, :length, :count].freeze
IGNORED_CLASSES = [Integer].freeze
def initialize(expected, relativity=:exactly)
@expected = case expected
when :no then 0
when String then expected.to_i
else expected
end
@relativity = relativity
@actual = @collection_name = @plural_collection_name = nil
end
def relativities
@relativities ||= {
:exactly => "",
:at_least => "at least ",
:at_most => "at most "
}
end
if RUBY_VERSION == '1.9.2'
# On Ruby 1.9.2 items that don't return an array for `to_ary`
# can't be flattened in arrays, we need to be able to do this
# to produce diffs for compound matchers, so this corrects the
# default implementation. Note that rspec-support has code that
# directly checks for pattern and prevents infinite recursion.
def to_ary
[self]
end
end
def matches?(collection_or_owner)
collection = determine_collection(collection_or_owner)
case collection
when enumerator_class
for query_method in QUERY_METHODS
next unless collection.respond_to?(query_method)
@actual = collection.__send__(query_method)
break unless @actual.nil?
end
raise not_a_collection if @actual.nil?
else
query_method = determine_query_method(collection)
raise not_a_collection if !query_method || is_ignored_class?(collection)
@actual = collection.__send__(query_method)
end
case @relativity
when :at_least then @actual >= @expected
when :at_most then @actual <= @expected
else @actual == @expected
end
end
alias == matches?
def determine_collection(collection_or_owner)
if collection_or_owner.respond_to?(@collection_name)
collection_or_owner.__send__(@collection_name, *@args, &@block)
elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
elsif determine_query_method(collection_or_owner)
collection_or_owner
else
collection_or_owner.__send__(@collection_name, *@args, &@block)
end
end
def determine_query_method(collection)
QUERY_METHODS.detect {|m| collection.respond_to?(m)}
end
def is_ignored_class?(collection)
IGNORED_CLASSES.any? {|klass| klass === collection}
end
def not_a_collection
"expected #{@collection_name} to be a collection but it does not respond to #length, #size or #count"
end
def failure_message
return errors_on_message(:expected, ", got #{@actual}") if is_errors_on?
"expected #{relative_expectation} #{@collection_name}, got #{@actual}"
end
alias failure_message_for_should failure_message
def failure_message_when_negated
if @relativity == :exactly
return "expected target not to have #{@expected} #{@collection_name}, got #{@actual}"
elsif @relativity == :at_most
return <<-EOF
Isn't life confusing enough?
Instead of having to figure out the meaning of this:
#{Syntax.negative_expression("actual", "have_at_most(#{@expected}).#{@collection_name}")}
We recommend that you use this instead:
#{Syntax.positive_expression("actual", "have_at_least(#{@expected + 1}).#{@collection_name}")}
EOF
elsif @relativity == :at_least
return <<-EOF
Isn't life confusing enough?
Instead of having to figure out the meaning of this:
#{Syntax.negative_expression("actual", "have_at_least(#{@expected}).#{@collection_name}")}
We recommend that you use this instead:
#{Syntax.positive_expression("actual", "have_at_most(#{@expected - 1}).#{@collection_name}")}
EOF
end
end
alias failure_message_for_should_not failure_message_when_negated
def description
return errors_on_message(:have) if is_errors_on?
"have #{relative_expectation} #{@collection_name}"
end
def respond_to?(m, include_all = false)
@expected.respond_to?(m, include_all) || super
end
private
def method_missing(method, *args, &block)
@collection_name = method
if inflector = (defined?(ActiveSupport::Inflector) && ActiveSupport::Inflector.respond_to?(:pluralize) ? ActiveSupport::Inflector : (defined?(Inflector) ? Inflector : nil))
@plural_collection_name = inflector.pluralize(method.to_s)
end
@args = args
@block = block
self
end
def relative_expectation
"#{relativities[@relativity]}#{@expected}"
end
def enumerator_class
RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
end
def is_errors_on?
[:errors_on, :error_on].include? @collection_name
end
def errors_on_message(prefix, suffix = nil)
"#{prefix} #{relative_expectation} #{@collection_name.to_s.gsub('_', ' ')} :#{@args[0]}#{suffix}"
end
end
module Syntax
# @api private
# Generates a positive expectation expression.
def self.positive_expression(target_expression, matcher_expression)
expression_generator.positive_expression(target_expression, matcher_expression)
end
# @api private
# Generates a negative expectation expression.
def self.negative_expression(target_expression, matcher_expression)
expression_generator.negative_expression(target_expression, matcher_expression)
end
# @api private
# Selects which expression generator to use based on the configured syntax.
def self.expression_generator
if RSpec::Expectations::Syntax.expect_enabled?
ExpectExpressionGenerator
else
ShouldExpressionGenerator
end
end
# @api private
# Generates expectation expressions for the `should` syntax.
module ShouldExpressionGenerator
def self.positive_expression(target_expression, matcher_expression)
"#{target_expression}.should #{matcher_expression}"
end
def self.negative_expression(target_expression, matcher_expression)
"#{target_expression}.should_not #{matcher_expression}"
end
end
# @api private
# Generates expectation expressions for the `expect` syntax.
module ExpectExpressionGenerator
def self.positive_expression(target_expression, matcher_expression)
"expect(#{target_expression}).to #{matcher_expression}"
end
def self.negative_expression(target_expression, matcher_expression)
"expect(#{target_expression}).not_to #{matcher_expression}"
end
end
end
end
end
|