2018-09-15 17:58:43 +00:00
|
|
|
require "../matchers"
|
2019-08-09 17:29:53 +00:00
|
|
|
require "../test_block"
|
|
|
|
require "../test_value"
|
2018-09-15 17:58:43 +00:00
|
|
|
|
2018-09-27 22:20:55 +00:00
|
|
|
module Spectator::DSL
|
2018-11-03 02:48:36 +00:00
|
|
|
# Methods for defining matchers for expectations.
|
2018-09-27 22:20:55 +00:00
|
|
|
module MatcherDSL
|
2018-11-03 02:48:36 +00:00
|
|
|
# Indicates that some value should equal another.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The == operator is used for this check.
|
2018-11-03 02:48:36 +00:00
|
|
|
# The value passed to this method is the expected value.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(1 + 2).to eq(3)
|
|
|
|
# ```
|
2018-09-27 22:20:55 +00:00
|
|
|
macro eq(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::EqualityMatcher.new(%test_value)
|
2018-09-15 17:58:43 +00:00
|
|
|
end
|
2019-01-19 20:29:10 +00:00
|
|
|
|
2019-01-24 04:45:16 +00:00
|
|
|
# Indicates that some value should not equal another.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The != operator is used for this check.
|
2019-01-24 04:45:16 +00:00
|
|
|
# The value passed to this method is the unexpected value.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(1 + 2).to ne(5)
|
|
|
|
# ```
|
|
|
|
macro ne(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::InequalityMatcher.new(%test_value)
|
2019-01-24 04:45:16 +00:00
|
|
|
end
|
|
|
|
|
2019-01-24 00:17:45 +00:00
|
|
|
# Indicates that some value when compared to another satisfies an operator.
|
2019-02-18 06:01:43 +00:00
|
|
|
# An operator can follow, such as: <, <=, >, or >=.
|
|
|
|
# See `Spectator::Matchers::TruthyMatcher` for a full list of operators.
|
2019-01-24 00:17:45 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(1 + 1).to be > 1
|
|
|
|
# expect(5).to be >= 3
|
|
|
|
# ```
|
|
|
|
#
|
2019-01-24 05:10:03 +00:00
|
|
|
# Additionally, a value can just "be" truthy by omitting an operator.
|
|
|
|
# ```
|
|
|
|
# expect("foo").to be
|
|
|
|
# # is the same as:
|
|
|
|
# expect("foo").to be_truthy
|
|
|
|
# ```
|
2019-01-24 00:17:45 +00:00
|
|
|
macro be
|
2019-08-08 23:15:13 +00:00
|
|
|
::Spectator::Matchers::TruthyMatcher.new
|
2019-01-24 00:17:45 +00:00
|
|
|
end
|
|
|
|
|
2019-05-08 22:59:59 +00:00
|
|
|
# Indicates that some object should be the same as another.
|
|
|
|
# This checks if two references are the same.
|
|
|
|
# The `Reference#same?` method is used for this check.
|
2019-01-19 20:29:10 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
2019-05-08 22:59:59 +00:00
|
|
|
# obj = "foobar"
|
|
|
|
# expect(obj).to be(obj)
|
|
|
|
# expect(obj.dup).to_not be(obj)
|
2019-01-19 20:29:10 +00:00
|
|
|
# ```
|
|
|
|
macro be(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::ReferenceMatcher.new(%test_value)
|
2019-01-19 20:29:10 +00:00
|
|
|
end
|
2019-01-19 21:08:50 +00:00
|
|
|
|
|
|
|
# Indicates that some value should be of a specified type.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The `Object#is_a?` method is used for this check.
|
|
|
|
# A type name or type union should be used for *expected*.
|
2019-01-19 21:08:50 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foo").to be_a(String)
|
|
|
|
#
|
|
|
|
# x = Random.rand(2) == 0 ? "foobar" : 5
|
|
|
|
# expect(x).to be_a(Int32 | String)
|
|
|
|
# ```
|
|
|
|
macro be_a(expected)
|
2019-01-19 21:28:52 +00:00
|
|
|
::Spectator::Matchers::TypeMatcher({{expected}}).new
|
2019-01-19 21:08:50 +00:00
|
|
|
end
|
2019-01-19 21:40:14 +00:00
|
|
|
|
2019-01-25 18:21:41 +00:00
|
|
|
# Indicates that some value should be of a specified type.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The `Object#is_a?` method is used for this check.
|
|
|
|
# A type name or type union should be used for *expected*.
|
2019-01-25 18:21:41 +00:00
|
|
|
# This method is identical to `#be_a`,
|
|
|
|
# and exists just to improve grammar.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(123).to be_an(Int32)
|
|
|
|
# ```
|
|
|
|
macro be_an(expected)
|
|
|
|
be_a({{expected}})
|
|
|
|
end
|
|
|
|
|
2019-03-29 05:20:32 +00:00
|
|
|
# Indicates that some value should respond to a method call.
|
|
|
|
# One or more method names can be provided.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to respond_to(:downcase)
|
|
|
|
# expect(%i[a b c]).to respond_to(:size, :first)
|
|
|
|
# ```
|
|
|
|
macro respond_to(*expected)
|
|
|
|
::Spectator::Matchers::RespondMatcher({% begin %}NamedTuple(
|
|
|
|
{% for method in expected %}
|
|
|
|
{{method.id.stringify}}: Nil,
|
|
|
|
{% end %}
|
|
|
|
){% end %}).new
|
|
|
|
end
|
|
|
|
|
2019-01-24 04:45:16 +00:00
|
|
|
# Indicates that some value should be less than another.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The < operator is used for this check.
|
2019-01-24 04:45:16 +00:00
|
|
|
# The value passed to this method is the value expected to be larger.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(3 - 1).to be_lt(3)
|
|
|
|
# ```
|
|
|
|
macro be_lt(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::LessThanMatcher.new(%test_value)
|
2019-01-24 04:45:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some value should be less than or equal to another.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The <= operator is used for this check.
|
2019-01-24 04:45:16 +00:00
|
|
|
# The value passed to this method is the value expected to be larger or equal.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(3 - 1).to be_le(3)
|
|
|
|
# ```
|
|
|
|
macro be_le(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::LessThanEqualMatcher.new(%test_value)
|
2019-01-24 04:45:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some value should be greater than another.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The > operator is used for this check.
|
2019-01-24 04:45:16 +00:00
|
|
|
# The value passed to this method is the value expected to be smaller.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(3 + 1).to be_gt(3)
|
|
|
|
# ```
|
|
|
|
macro be_gt(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::GreaterThanMatcher.new(%test_value)
|
2019-01-24 04:45:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some value should be greater than or equal to another.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The >= operator is used for this check.
|
2019-01-24 04:45:16 +00:00
|
|
|
# The value passed to this method is the value expected to be smaller or equal.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(3 + 1).to be_ge(3)
|
|
|
|
# ```
|
|
|
|
macro be_ge(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::GreaterThanEqualMatcher.new(%test_value)
|
2019-01-24 04:45:16 +00:00
|
|
|
end
|
|
|
|
|
2019-01-19 21:40:14 +00:00
|
|
|
# Indicates that some value should match another.
|
2019-05-08 22:39:00 +00:00
|
|
|
# The === (case equality) operator is used for this check.
|
|
|
|
# Typically a regular expression is used.
|
|
|
|
# This has identical behavior as a "when" condition in a case block.
|
2019-01-19 21:40:14 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foo").to match(/foo|bar/)
|
|
|
|
# expect("BAR").to match(/foo|bar/i)
|
2019-05-08 22:39:00 +00:00
|
|
|
# expect(1 + 2).to match(3)
|
|
|
|
# expect(5).to match(Int32) # Using `#be_a` instead is recommened here.
|
|
|
|
# expect({:foo, 5}).to match({Symbol, Int32})
|
2019-01-19 21:40:14 +00:00
|
|
|
# ```
|
|
|
|
macro match(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::CaseMatcher.new(%test_value)
|
2019-01-19 21:40:14 +00:00
|
|
|
end
|
2019-01-23 22:35:32 +00:00
|
|
|
|
|
|
|
# Indicates that some value should be true.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(nil.nil?).to be_true
|
|
|
|
# expect(%i[a b c].any?).to be_true
|
|
|
|
# ```
|
|
|
|
macro be_true
|
|
|
|
eq(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some value should be false.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foo".nil?).to be_false
|
|
|
|
# expect(%i[a b c].empty?).to be_false
|
|
|
|
# ```
|
|
|
|
macro be_false
|
|
|
|
eq(false)
|
|
|
|
end
|
2019-01-23 22:51:52 +00:00
|
|
|
|
|
|
|
# Indicates that some value should be truthy.
|
2019-02-18 06:01:43 +00:00
|
|
|
# This means that the value is not false and not nil.
|
2019-01-23 22:51:52 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(123).to be_truthy
|
|
|
|
# expect(true).to be_truthy
|
|
|
|
# ```
|
|
|
|
macro be_truthy
|
2019-08-08 23:15:13 +00:00
|
|
|
::Spectator::Matchers::TruthyMatcher.new
|
2019-01-23 22:51:52 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some value should be falsey.
|
2019-02-18 06:01:43 +00:00
|
|
|
# This means that the value is either false or nil.
|
2019-01-23 22:51:52 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(false).to be_falsey
|
|
|
|
# expect(nil).to be_falsey
|
|
|
|
# ```
|
|
|
|
macro be_falsey
|
|
|
|
::Spectator::Matchers::TruthyMatcher.new(false)
|
|
|
|
end
|
2019-01-25 20:20:38 +00:00
|
|
|
|
|
|
|
# Indicates that some value should be contained within another.
|
|
|
|
# This checker can be used in one of two ways.
|
|
|
|
#
|
2019-02-18 06:01:43 +00:00
|
|
|
# The first: the *expected* argument can be anything
|
|
|
|
# that implements the `includes?` method.
|
2019-01-25 20:20:38 +00:00
|
|
|
# This is typically a `Range`, but can also be `Enumerable`.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(:foo).to be_within(%i[foo bar baz])
|
|
|
|
# expect(7).to be_within(1..10)
|
|
|
|
# ```
|
|
|
|
#
|
2019-02-18 06:01:43 +00:00
|
|
|
# The other way is to use this is with the "of" keyword.
|
2019-01-25 20:20:38 +00:00
|
|
|
# This creates a lower and upper bound
|
2019-02-18 06:01:43 +00:00
|
|
|
# centered around the value of the *expected* argument.
|
2019-01-25 20:20:38 +00:00
|
|
|
# This usage is helpful for comparisons on floating-point numbers.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(50.0).to be_within(0.01).of(50.0)
|
|
|
|
# expect(speed).to be_within(5).of(speed_limit)
|
|
|
|
# ```
|
|
|
|
#
|
2019-02-18 06:01:43 +00:00
|
|
|
# NOTE: The of suffix must be used
|
2019-05-17 00:20:08 +00:00
|
|
|
# if the *expected* argument does not implement an `includes?` method.
|
2019-01-25 20:20:38 +00:00
|
|
|
#
|
|
|
|
# Additionally, for this second usage,
|
2019-02-18 06:01:43 +00:00
|
|
|
# an "inclusive" or "exclusive" suffix can be added.
|
2019-01-25 20:20:38 +00:00
|
|
|
# These modify the upper-bound on the range being checked against.
|
2019-02-18 06:01:43 +00:00
|
|
|
# By default, the range is inclusive.
|
2019-01-25 20:20:38 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(days).to be_within(1).of(30).inclusive # 29, 30, or 31
|
|
|
|
# expect(100).to be_within(2).of(99).exclusive # 97, 98, 99, or 100 (not 101)
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# NOTE: Do not attempt to mix the two use cases.
|
|
|
|
# It likely won't work and will result in a compilation error.
|
|
|
|
macro be_within(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::CollectionMatcher.new(%test_value)
|
2019-01-25 20:20:38 +00:00
|
|
|
end
|
|
|
|
|
2019-05-15 03:50:09 +00:00
|
|
|
# Indicates that some value should be between a lower and upper-bound.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(7).to be_between(1, 10)
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# Additionally, an "inclusive" or "exclusive" suffix can be added.
|
|
|
|
# These modify the upper-bound on the range being checked against.
|
|
|
|
# By default, the range is inclusive.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(days).to be_between(28, 31).inclusive # 28, 29, 30, or 31
|
|
|
|
# expect(100).to be_between(97, 101).exclusive # 97, 98, 99, or 100 (not 101)
|
|
|
|
# ```
|
|
|
|
macro be_between(min, max)
|
2019-08-10 18:49:54 +00:00
|
|
|
%range = Range.new({{min}}, {{max}}))
|
|
|
|
%label = [{{min.stringify}}, {{max.stringify}}].join(" to ")
|
|
|
|
%test_value = ::Spectator::TestValue.new(%range, %label)
|
|
|
|
:Spectator::Matchers::RangeMatcher.new(%test_value)
|
2019-05-15 03:50:09 +00:00
|
|
|
end
|
2019-01-26 23:42:58 +00:00
|
|
|
|
2019-02-02 02:10:28 +00:00
|
|
|
# Indicates that some value should be within a delta of an expected value.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect(pi).to be_close(3.14159265359, 0.0000001)
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# This is functionly equivalent to:
|
|
|
|
# ```
|
|
|
|
# be_within(expected).of(delta)
|
|
|
|
# ```
|
|
|
|
macro be_close(expected, delta)
|
|
|
|
be_within({{delta}}).of({{expected}})
|
|
|
|
end
|
|
|
|
|
2019-01-26 23:42:58 +00:00
|
|
|
# Indicates that some value should or should not be nil.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect(error).to be_nil
|
|
|
|
# expect(input).to_not be_nil
|
|
|
|
# ```
|
|
|
|
macro be_nil
|
|
|
|
::Spectator::Matchers::NilMatcher.new
|
|
|
|
end
|
2019-01-27 00:04:46 +00:00
|
|
|
|
|
|
|
# Indicates that some collection should be empty.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect([]).to be_empty
|
|
|
|
# ```
|
|
|
|
macro be_empty
|
|
|
|
::Spectator::Matchers::EmptyMatcher.new
|
|
|
|
end
|
2019-01-31 23:43:43 +00:00
|
|
|
|
|
|
|
# Indicates that some value or set should start with another value.
|
|
|
|
# This is typically used on a `String` or `Array` (any `Enumerable` works).
|
2019-02-18 06:01:43 +00:00
|
|
|
# The *expected* argument can be a `String`, `Char`, or `Regex`
|
2019-01-31 23:43:43 +00:00
|
|
|
# when the actual type (being comapred against) is a `String`.
|
|
|
|
# For `Enumerable` types, only the first item is inspected.
|
2019-02-18 06:01:43 +00:00
|
|
|
# It is compared with the === operator,
|
2019-01-31 23:43:43 +00:00
|
|
|
# so that values, types, regular expressions, and others can be tested.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to start_with("foo")
|
|
|
|
# expect("foobar").to start_with('f')
|
|
|
|
# expect("FOOBAR").to start_with(/foo/i)
|
|
|
|
#
|
|
|
|
# expect(%i[a b c]).to start_with(:a)
|
|
|
|
# expect(%i[a b c]).to start_with(Symbol)
|
|
|
|
# expect(%w[foo bar]).to start_with(/foo/)
|
|
|
|
# ```
|
|
|
|
macro start_with(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::StartWithMatcher.new(%test_value)
|
2019-01-31 23:43:43 +00:00
|
|
|
end
|
2019-02-01 05:05:27 +00:00
|
|
|
|
|
|
|
# Indicates that some value or set should end with another value.
|
|
|
|
# This is typically used on a `String` or `Array` (any `Indexable` works).
|
2019-02-18 06:01:43 +00:00
|
|
|
# The *expected* argument can be a `String`, `Char`, or `Regex`
|
2019-02-01 05:05:27 +00:00
|
|
|
# when the actual type (being comapred against) is a `String`.
|
|
|
|
# For `Indexable` types, only the last item is inspected.
|
2019-02-18 06:01:43 +00:00
|
|
|
# It is compared with the === operator,
|
2019-02-01 05:05:27 +00:00
|
|
|
# so that values, types, regular expressions, and others can be tested.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to end_with("bar")
|
|
|
|
# expect("foobar").to end_with('r')
|
|
|
|
# expect("FOOBAR").to end_with(/bar/i)
|
|
|
|
#
|
|
|
|
# expect(%i[a b c]).to end_with(:c)
|
|
|
|
# expect(%i[a b c]).to end_with(Symbol)
|
|
|
|
# expect(%w[foo bar]).to end_with(/bar/)
|
|
|
|
# ```
|
|
|
|
macro end_with(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::EndWithMatcher.new(%test_value)
|
2019-02-01 05:05:27 +00:00
|
|
|
end
|
2019-02-01 06:06:00 +00:00
|
|
|
|
|
|
|
# Indicates that some value or set should contain another value.
|
|
|
|
# This is typically used on a `String` or `Array` (any `Enumerable` works).
|
2019-02-18 06:01:43 +00:00
|
|
|
# The *expected* argument can be a `String` or `Char`
|
2019-02-01 06:06:00 +00:00
|
|
|
# when the actual type (being comapred against) is a `String`.
|
2019-02-01 23:25:45 +00:00
|
|
|
# For `Enumerable` types, items are compared using the underying implementation.
|
2019-02-01 06:06:00 +00:00
|
|
|
# In both cases, the `includes?` method is used.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to contain("foo")
|
|
|
|
# expect("foobar").to contain('o')
|
|
|
|
# expect(%i[a b c]).to contain(:b)
|
|
|
|
# ```
|
2019-02-02 02:43:42 +00:00
|
|
|
#
|
|
|
|
# Additionally, multiple arguments can be specified.
|
|
|
|
# ```
|
|
|
|
# expect("foobarbaz").to contain("foo", "bar")
|
|
|
|
# expect(%i[a b c]).to contain(:a, :b)
|
|
|
|
# ```
|
|
|
|
macro contain(*expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}})
|
|
|
|
::Spectator::Matchers::ContainMatcher.new(%test_value)
|
2019-02-01 06:06:00 +00:00
|
|
|
end
|
2019-02-01 23:25:45 +00:00
|
|
|
|
|
|
|
# Indicates that some value or set should contain another value.
|
|
|
|
# This is similar to `#contain`, but uses a different method for matching.
|
|
|
|
# Typically a `String` or `Array` (any `Enumerable` works) is checked against.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The *expected* argument can be a `String` or `Char`
|
2019-02-01 23:25:45 +00:00
|
|
|
# when the actual type (being comapred against) is a `String`.
|
|
|
|
# The `includes?` method is used for this case.
|
|
|
|
# For `Enumerable` types, each item is inspected until one matches.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The === operator is used for this case, which allows for equality, type, regex, and other matches.
|
2019-02-01 23:25:45 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to have("foo")
|
|
|
|
# expect("foobar").to have('o')
|
|
|
|
#
|
|
|
|
# expect(%i[a b c]).to have(:b)
|
|
|
|
# expect(%w[FOO BAR BAZ]).to have(/bar/i)
|
|
|
|
# expect([1, 2, 3, :a, :b, :c]).to have(Int32)
|
|
|
|
# ```
|
2019-02-02 03:27:51 +00:00
|
|
|
#
|
|
|
|
# Additionally, multiple arguments can be specified.
|
|
|
|
# ```
|
|
|
|
# expect("foobarbaz").to have("foo", "bar")
|
|
|
|
# expect(%i[a b c]).to have(:a, :b)
|
|
|
|
# expect(%w[FOO BAR BAZ]).to have(/foo/i, String)
|
|
|
|
# ```
|
|
|
|
macro have(*expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}})
|
|
|
|
::Spectator::Matchers::HaveMatcher.new(%test_value)
|
2019-02-01 23:25:45 +00:00
|
|
|
end
|
2019-02-02 05:24:06 +00:00
|
|
|
|
2019-02-05 19:47:50 +00:00
|
|
|
# Indicates that some set, such as a `Hash`, has a given key.
|
|
|
|
# The `has_key?` method is used for this check.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect({foo: "bar"}).to have_key(:foo)
|
|
|
|
# expect({"lucky" => 7}).to have_key("lucky")
|
|
|
|
# ```
|
|
|
|
macro have_key(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::HaveKeyMatcher.new(%test_value)
|
2019-02-05 19:47:50 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# ditto
|
|
|
|
macro has_key(expected)
|
|
|
|
have_key({{expected}})
|
|
|
|
end
|
|
|
|
|
2019-02-06 02:37:25 +00:00
|
|
|
# Indicates that some set, such as a `Hash`, has a given value.
|
|
|
|
# The `has_value?` method is used for this check.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect({foo: "bar"}).to have_value("bar")
|
|
|
|
# expect({"lucky" => 7}).to have_value(7)
|
|
|
|
# ```
|
|
|
|
macro have_value(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::HaveValueMatcher.new(%test_value)
|
2019-02-06 02:37:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# ditto
|
|
|
|
macro has_value(expected)
|
|
|
|
have_value({{expected}})
|
|
|
|
end
|
|
|
|
|
2019-03-30 21:07:04 +00:00
|
|
|
# Indicates that some set should contain some values in exact order.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
|
|
|
|
# ```
|
|
|
|
macro contain_exactly(*expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::ArrayMatcher.new(%test_value)
|
2019-03-30 21:07:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some set should contain the same values in exact order as another set.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect([1, 2, 3]).to match_array([1, 2, 3])
|
|
|
|
# ```
|
|
|
|
macro match_array(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::ArrayMatcher.new(%test_value)
|
2019-03-30 21:07:04 +00:00
|
|
|
end
|
|
|
|
|
2019-06-09 18:44:46 +00:00
|
|
|
# Indicates that some set should have a specified size.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect([1, 2, 3]).to have_size(3)
|
|
|
|
# ```
|
|
|
|
macro have_size(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::SizeMatcher.new(%test_value)
|
2019-06-09 18:44:46 +00:00
|
|
|
end
|
|
|
|
|
2019-06-09 18:45:49 +00:00
|
|
|
# Indicates that some set should have the same size (number of elements) as another set.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# ```
|
|
|
|
# expect([1, 2, 3]).to have_size_of(%i[x y z])
|
|
|
|
# ```
|
|
|
|
macro have_size_of(expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new(({{expected}}), {{expected.stringify}})
|
|
|
|
::Spectator::Matchers::SizeOfMatcher.new(%test_value)
|
2019-06-09 18:45:49 +00:00
|
|
|
end
|
|
|
|
|
2019-02-02 05:24:06 +00:00
|
|
|
# Indicates that some value should have a set of attributes matching some conditions.
|
|
|
|
# A list of named arguments are expected.
|
|
|
|
# The names correspond to the attributes in the instance to check.
|
2019-02-18 06:01:43 +00:00
|
|
|
# The values are conditions to check with the === operator against the attribute's value.
|
2019-02-02 05:24:06 +00:00
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to have_attributes(size: 6, upcase: "FOOBAR")
|
|
|
|
# expect(%i[a b c]).to have_attributes(size: 1..5, first: Symbol)
|
|
|
|
# ```
|
|
|
|
macro have_attributes(**expected)
|
2019-08-10 18:49:54 +00:00
|
|
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.double_splat.stringify}})
|
|
|
|
::Spectator::Matchers::AttributesMatcher.new(%test_value)
|
2019-02-02 05:24:06 +00:00
|
|
|
end
|
2019-02-05 05:52:09 +00:00
|
|
|
|
2019-07-14 15:39:27 +00:00
|
|
|
# Indicates that some expression's value should change after taking an action.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# i = 0
|
|
|
|
# expect { i += 1 }.to change { i }
|
|
|
|
# expect { i += 0 }.to_not change { i }
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# i = 0
|
|
|
|
# expect { i += 5 }.to change { i }.from(0).to(5)
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# i = 0
|
|
|
|
# expect { i += 5 }.to change { i }.to(5)
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# i = 0
|
|
|
|
# expect { i += 5 }.to change { i }.from(0)
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# i = 0
|
|
|
|
# expect { i += 42 }.to change { i }.by(42)
|
|
|
|
# ```
|
2019-08-11 19:16:40 +00:00
|
|
|
#
|
|
|
|
# The block short-hand syntax can be used here.
|
|
|
|
# It will reference the current subject.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# expect { subject << :foo }.to change(&.size).by(1)
|
|
|
|
# ```
|
2019-07-14 15:39:27 +00:00
|
|
|
macro change(&expression)
|
2019-08-11 19:16:40 +00:00
|
|
|
{% if expression.is_a?(Nop) %}
|
|
|
|
{% raise "Block must be provided to change matcher" %}
|
|
|
|
{% end %}
|
|
|
|
|
|
|
|
# Check if the short-hand method syntax is used.
|
|
|
|
# This is a hack, since macros don't get this as a "literal" or something similar.
|
|
|
|
# The Crystal compiler will translate:
|
|
|
|
# ```
|
|
|
|
# &.foo
|
|
|
|
# ```
|
|
|
|
# to:
|
|
|
|
# ```
|
|
|
|
# { |__arg0| __arg0.foo }
|
|
|
|
# ```
|
|
|
|
# The hack used here is to check if it looks like a compiler-generated block.
|
|
|
|
{% if expression.args == ["__arg0".id] && expression.body.is_a?(Call) && expression.body.id =~ /^__arg0\./ %}
|
|
|
|
# Extract the method name to make it clear to the user what is tested.
|
|
|
|
# The raw block can't be used because it's not clear to the user.
|
|
|
|
{% method_name = expression.body.id.split('.')[1..-1].join('.') %}
|
|
|
|
%proc = ->{ subject.{{method_name.id}} }
|
|
|
|
%test_block = ::Spectator::TestBlock.create(%proc, {{"#" + method_name}})
|
|
|
|
{% elsif expression.args.empty? %}
|
|
|
|
# In this case, it looks like the short-hand method syntax wasn't used.
|
|
|
|
# Capture the block as a proc and pass along.
|
|
|
|
%proc = ->{{expression}}
|
|
|
|
%test_block = ::Spectator::TestBlock.create(%proc, {{"`" + expression.body.stringify + "`"}})
|
|
|
|
{% else %}
|
|
|
|
{% raise "Unexpected block arguments in change matcher" %}
|
|
|
|
{% end %}
|
|
|
|
|
2019-08-10 18:49:54 +00:00
|
|
|
::Spectator::Matchers::ChangeMatcher.new(%test_block)
|
2019-07-14 15:39:27 +00:00
|
|
|
end
|
|
|
|
|
2019-03-22 03:10:16 +00:00
|
|
|
# Indicates that some block should raise an error.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect { raise "foobar" }.to raise_error
|
|
|
|
# ```
|
|
|
|
macro raise_error
|
|
|
|
::Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
|
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some block should raise an error with a given message or type.
|
|
|
|
# The *type_or_message* parameter should be an exception type
|
|
|
|
# or a string or regex to match the exception's message against.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# hash = {"foo" => "bar"}
|
|
|
|
# expect { hash["baz"] }.to raise_error(KeyError)
|
|
|
|
# expect { hash["baz"] }.to raise_error(/baz/)
|
|
|
|
# expect { raise "foobar" }.to raise_error("foobar")
|
|
|
|
# ```
|
|
|
|
macro raise_error(type_or_message)
|
|
|
|
::Spectator::Matchers::ExceptionMatcher.create({{type_or_message}}, {{type_or_message.stringify}})
|
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some block should raise an error with a given message and type.
|
|
|
|
# The *type* is the exception type expected to be raised.
|
|
|
|
# The *message* is a string or regex to match to exception message against.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# hash = {"foo" => "bar"}
|
|
|
|
# expect { hash["baz"] }.to raise_error(KeyError, /baz/)
|
|
|
|
# expect { raise ArgumentError.new("foobar") }.to raise_error(ArgumentError, "foobar")
|
|
|
|
# ```
|
|
|
|
macro raise_error(type, message)
|
2019-05-08 21:18:56 +00:00
|
|
|
::Spectator::Matchers::ExceptionMatcher.create({{type}}, {{message}}, {{message.stringify}})
|
2019-03-22 03:10:16 +00:00
|
|
|
end
|
|
|
|
|
2019-03-22 03:18:36 +00:00
|
|
|
# Indicates that some block should raise an error.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# expect_raises { raise "foobar" }
|
|
|
|
# ```
|
|
|
|
macro expect_raises
|
2019-04-01 00:53:40 +00:00
|
|
|
expect {{block}}.to raise_error
|
2019-03-22 03:18:36 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some block should raise an error with a given type.
|
|
|
|
# The *type* parameter should be an exception type.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# hash = {"foo" => "bar"}
|
|
|
|
# expect_raises(KeyError) { hash["baz"] }.to raise_error(KeyError)
|
|
|
|
# ```
|
2019-03-27 03:45:31 +00:00
|
|
|
macro expect_raises(type, &block)
|
|
|
|
expect {{block}}.to raise_error({{type}})
|
2019-03-22 03:18:36 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Indicates that some block should raise an error with a given message and type.
|
|
|
|
# The *type* is the exception type expected to be raised.
|
|
|
|
# The *message* is a string or regex to match to exception message against.
|
|
|
|
# This method is included for compatibility with Crystal's default spec.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# ```
|
|
|
|
# hash = {"foo" => "bar"}
|
|
|
|
# expect_raises(KeyError, /baz/) { hash["baz"] }
|
|
|
|
# expect_raises(ArgumentError, "foobar") { raise ArgumentError.new("foobar") }
|
|
|
|
# ```
|
2019-03-27 03:45:31 +00:00
|
|
|
macro expect_raises(type, message, &block)
|
|
|
|
expect {{block}}.to raise_error({{type}}, {{message}})
|
2019-03-22 03:18:36 +00:00
|
|
|
end
|
|
|
|
|
2019-02-05 05:52:09 +00:00
|
|
|
# Used to create predicate matchers.
|
2019-06-02 05:14:58 +00:00
|
|
|
# Any missing method that starts with 'be_' or 'have_' will be handled.
|
2019-02-05 05:52:09 +00:00
|
|
|
# All other method names will be ignored and raise a compile-time error.
|
|
|
|
#
|
2019-02-18 06:01:43 +00:00
|
|
|
# This can be used to simply check a predicate method that ends in '?'.
|
2019-02-05 05:52:09 +00:00
|
|
|
# For instance:
|
|
|
|
# ```
|
|
|
|
# expect("foobar").to be_ascii_only
|
|
|
|
# # Is equivalent to:
|
|
|
|
# expect("foobar".ascii_only?).to be_true
|
2019-06-02 05:14:58 +00:00
|
|
|
#
|
|
|
|
# expect("foobar").to_not have_back_references
|
|
|
|
# # Is equivalent to:
|
|
|
|
# expect("foobar".has_back_references?).to_not be_true
|
2019-02-05 05:52:09 +00:00
|
|
|
# ```
|
|
|
|
macro method_missing(call)
|
|
|
|
{% if call.name.starts_with?("be_") %}
|
2019-06-02 05:14:58 +00:00
|
|
|
# Remove `be_` prefix.
|
|
|
|
{% method_name = call.name[3..-1] %}
|
2019-06-02 05:27:16 +00:00
|
|
|
{% matcher = "PredicateMatcher" %}
|
2019-06-02 05:14:58 +00:00
|
|
|
{% elsif call.name.starts_with?("have_") %}
|
2019-06-02 05:27:16 +00:00
|
|
|
# Remove `have_` prefix.
|
|
|
|
{% method_name = call.name[5..-1] %}
|
|
|
|
{% matcher = "HavePredicateMatcher" %}
|
2019-02-05 05:52:09 +00:00
|
|
|
{% else %}
|
2019-06-02 05:06:39 +00:00
|
|
|
{% raise "Undefined local variable or method '#{call}'" %}
|
2019-02-05 05:52:09 +00:00
|
|
|
{% end %}
|
2019-06-02 05:14:58 +00:00
|
|
|
|
|
|
|
descriptor = { {{method_name}}: Tuple.new({{call.args.splat}}) }
|
|
|
|
label = String::Builder.new({{method_name.stringify}})
|
|
|
|
{% unless call.args.empty? %}
|
|
|
|
label << '('
|
|
|
|
{% for arg, index in call.args %}
|
|
|
|
label << {{arg}}
|
|
|
|
{% if index < call.args.size - 1 %}
|
|
|
|
label << ", "
|
|
|
|
{% end %}
|
|
|
|
{% end %}
|
|
|
|
label << ')'
|
|
|
|
{% end %}
|
2019-08-10 18:49:54 +00:00
|
|
|
test_value = ::Spectator::TestValue.new(descriptor, label.to_s)
|
|
|
|
::Spectator::Matchers::{{matcher.id}}.new(test_value)
|
2019-02-05 05:52:09 +00:00
|
|
|
end
|
2018-09-15 17:58:43 +00:00
|
|
|
end
|
|
|
|
end
|