mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Merge branch 'specs' into 'master'
Initial RSpec specs and various fixes See merge request arctic-fox/spectator!24
This commit is contained in:
commit
d2f0f52729
54 changed files with 1200 additions and 188 deletions
5
.guardian.yml
Normal file
5
.guardian.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
files: ./**/*.cr
|
||||
run: time crystal spec --error-trace
|
||||
---
|
||||
files: ./shard.yml
|
||||
run: shards
|
|
@ -1,12 +1,12 @@
|
|||
name: spectator
|
||||
version: 0.9.1
|
||||
version: 0.9.2
|
||||
description: |
|
||||
A feature-rich spec testing framework for Crystal with similarities to RSpec.
|
||||
|
||||
authors:
|
||||
- Michael Miller <icy.arctic.fox@gmail.com>
|
||||
|
||||
crystal: 0.31.1
|
||||
crystal: 0.32.1
|
||||
|
||||
license: MIT
|
||||
|
||||
|
|
32
spec/matchers/equality_matcher_spec.cr
Normal file
32
spec/matchers/equality_matcher_spec.cr
Normal file
|
@ -0,0 +1,32 @@
|
|||
require "../spec_helper"
|
||||
|
||||
Spectator.describe "eq matcher" do
|
||||
it "is true for equal values" do
|
||||
expect(42).to eq(42)
|
||||
end
|
||||
|
||||
it "is false for inequal values" do
|
||||
expect(42).to_not eq(24)
|
||||
end
|
||||
|
||||
it "is true for identical references" do
|
||||
string = "foobar"
|
||||
expect(string).to eq(string)
|
||||
end
|
||||
|
||||
it "is false for different references" do
|
||||
string1 = "foo"
|
||||
string2 = "bar"
|
||||
expect(string1).to_not eq(string2)
|
||||
end
|
||||
|
||||
double(:fake) do
|
||||
stub instance.==(other) { true }
|
||||
end
|
||||
|
||||
it "uses the == operator" do
|
||||
dbl = double(:fake)
|
||||
expect(42).to eq(dbl)
|
||||
expect(dbl).to have_received(:==).with(42).once
|
||||
end
|
||||
end
|
39
spec/rspec/expectations/all_matcher_spec.cr
Normal file
39
spec/rspec/expectations/all_matcher_spec.cr
Normal file
|
@ -0,0 +1,39 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/all-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`all` matcher" do
|
||||
context "array usage" do
|
||||
describe [1, 3, 5] do
|
||||
it { is_expected.to all(be_odd) }
|
||||
it { is_expected.to all(be_an(Int32)) } # Changed to Int32 to satisfy compiler.
|
||||
it { is_expected.to all(be < 10) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to all(be_even) }
|
||||
xit { is_expected.to all(be_a(String)) }
|
||||
xit { is_expected.to all(be > 2) }
|
||||
end
|
||||
end
|
||||
|
||||
context "compound matcher usage" do
|
||||
# Changed `include` to `contain` to match our own.
|
||||
# `include` is a keyword and can't be used as a method name in Crystal.
|
||||
|
||||
# TODO: Add support for compound matchers.
|
||||
describe ["anything", "everything", "something"] do
|
||||
xit { is_expected.to all(be_a(String)) } # .and contain("thing") ) }
|
||||
xit { is_expected.to all(be_a(String)) } # .and end_with("g") ) }
|
||||
xit { is_expected.to all(start_with("s")) } # .or contain("y") ) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
# TODO: Add support for compound matchers.
|
||||
xit { is_expected.to all(contain("foo")) } # .and contain("bar") ) }
|
||||
xit { is_expected.to all(be_a(String)) } # .and start_with("a") ) }
|
||||
xit { is_expected.to all(start_with("a")) } # .or contain("z") ) }
|
||||
end
|
||||
end
|
||||
end
|
70
spec/rspec/expectations/be_matchers_spec.cr
Normal file
70
spec/rspec/expectations/be_matchers_spec.cr
Normal file
|
@ -0,0 +1,70 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/be-matchers
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`be` matchers" do
|
||||
context "be_truthy matcher" do
|
||||
specify { expect(true).to be_truthy }
|
||||
specify { expect(7).to be_truthy }
|
||||
specify { expect("foo").to be_truthy }
|
||||
specify { expect(nil).not_to be_truthy }
|
||||
specify { expect(false).not_to be_truthy }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
pending { expect(true).not_to be_truthy }
|
||||
pending { expect(7).not_to be_truthy }
|
||||
pending { expect("foo").not_to be_truthy }
|
||||
pending { expect(nil).to be_truthy }
|
||||
pending { expect(false).to be_truthy }
|
||||
end
|
||||
|
||||
context "be_falsey matcher" do
|
||||
specify { expect(nil).to be_falsey }
|
||||
specify { expect(false).to be_falsey }
|
||||
specify { expect(true).not_to be_falsey }
|
||||
specify { expect(7).not_to be_falsey }
|
||||
specify { expect("foo").not_to be_falsey }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
pending { expect(nil).not_to be_falsey }
|
||||
pending { expect(false).not_to be_falsey }
|
||||
pending { expect(true).to be_falsey }
|
||||
pending { expect(7).to be_falsey }
|
||||
pending { expect("foo").to be_falsey }
|
||||
end
|
||||
|
||||
context "be_nil matcher" do
|
||||
specify { expect(nil).to be_nil }
|
||||
specify { expect(false).not_to be_nil }
|
||||
specify { expect(true).not_to be_nil }
|
||||
specify { expect(7).not_to be_nil }
|
||||
specify { expect("foo").not_to be_nil }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
pending { expect(nil).not_to be_nil }
|
||||
pending { expect(false).to be_nil }
|
||||
pending { expect(true).to be_nil }
|
||||
pending { expect(7).to be_nil }
|
||||
pending { expect("foo").to be_nil }
|
||||
end
|
||||
|
||||
context "be matcher" do
|
||||
specify { expect(true).to be }
|
||||
specify { expect(7).to be }
|
||||
specify { expect("foo").to be }
|
||||
specify { expect(nil).not_to be }
|
||||
specify { expect(false).not_to be }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
pending { expect(true).not_to be }
|
||||
pending { expect(7).not_to be }
|
||||
pending { expect("foo").not_to be }
|
||||
pending { expect(nil).to be }
|
||||
pending { expect(false).to be }
|
||||
end
|
||||
end
|
25
spec/rspec/expectations/be_within_matcher_spec.cr
Normal file
25
spec/rspec/expectations/be_within_matcher_spec.cr
Normal file
|
@ -0,0 +1,25 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/be-within-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`be_within` matcher" do
|
||||
context "basic usage" do
|
||||
describe 27.5 do
|
||||
it { is_expected.to be_within(0.5).of(27.9) }
|
||||
it { is_expected.to be_within(0.5).of(28.0) }
|
||||
it { is_expected.to be_within(0.5).of(27.1) }
|
||||
it { is_expected.to be_within(0.5).of(27.0) }
|
||||
|
||||
it { is_expected.not_to be_within(0.5).of(28.1) }
|
||||
it { is_expected.not_to be_within(0.5).of(26.9) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to be_within(0.5).of(28) }
|
||||
xit { is_expected.not_to be_within(0.5).of(27) }
|
||||
xit { is_expected.to be_within(0.5).of(28.1) }
|
||||
xit { is_expected.to be_within(0.5).of(26.9) }
|
||||
end
|
||||
end
|
||||
end
|
49
spec/rspec/expectations/change_matcher_spec.cr
Normal file
49
spec/rspec/expectations/change_matcher_spec.cr
Normal file
|
@ -0,0 +1,49 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/change-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`change` matcher" do
|
||||
# Modified this example type to work in Crystal.
|
||||
module Counter
|
||||
extend self
|
||||
|
||||
@@count = 0
|
||||
|
||||
def increment
|
||||
@@count += 1
|
||||
end
|
||||
|
||||
def count
|
||||
@@count
|
||||
end
|
||||
end
|
||||
|
||||
context "expect change" do
|
||||
describe "Counter#increment" do # TODO: Allow multiple arguments to context/describe.
|
||||
it "should increment the count" do
|
||||
expect { Counter.increment }.to change { Counter.count }.from(0).to(1)
|
||||
end
|
||||
|
||||
# deliberate failure
|
||||
# TODO: Add support for expected failures.
|
||||
xit "should increment the count by 2" do
|
||||
expect { Counter.increment }.to change { Counter.count }.by(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "expect no change" do
|
||||
describe "Counter#increment" do # TODO: Allow multiple arguments to context/describe.
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit "should not increment the count by 1 (using not_to)" do
|
||||
expect { Counter.increment }.not_to change { Counter.count }
|
||||
end
|
||||
|
||||
xit "should not increment the count by 1 (using to_not)" do
|
||||
expect { Counter.increment }.to_not change { Counter.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
spec/rspec/expectations/comparison_matchers_spec.cr
Normal file
47
spec/rspec/expectations/comparison_matchers_spec.cr
Normal file
|
@ -0,0 +1,47 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/comparison-matchers
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "Comparison matchers" do
|
||||
context "numeric operator matchers" do
|
||||
describe 18 do
|
||||
it { is_expected.to be < 20 }
|
||||
it { is_expected.to be > 15 }
|
||||
it { is_expected.to be <= 19 }
|
||||
it { is_expected.to be >= 17 }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to be < 15 }
|
||||
xit { is_expected.to be > 20 }
|
||||
xit { is_expected.to be <= 17 }
|
||||
xit { is_expected.to be >= 19 }
|
||||
# it { is_expected.to be < 'a' } # Removed because Crystal doesn't support Int32#<(Char)
|
||||
end
|
||||
|
||||
describe 'a' do
|
||||
it { is_expected.to be < 'b' }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
# it { is_expected.to be < 18 } # Removed because Crystal doesn't support Char#<(Int32)
|
||||
end
|
||||
end
|
||||
|
||||
context "string operator matchers" do
|
||||
describe "Strawberry" do
|
||||
it { is_expected.to be < "Tomato" }
|
||||
it { is_expected.to be > "Apple" }
|
||||
it { is_expected.to be <= "Turnip" }
|
||||
it { is_expected.to be >= "Banana" }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to be < "Cranberry" }
|
||||
xit { is_expected.to be > "Zuchini" }
|
||||
xit { is_expected.to be <= "Potato" }
|
||||
xit { is_expected.to be >= "Tomato" }
|
||||
end
|
||||
end
|
||||
end
|
32
spec/rspec/expectations/contain_exactly_matcher_spec.cr
Normal file
32
spec/rspec/expectations/contain_exactly_matcher_spec.cr
Normal file
|
@ -0,0 +1,32 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/contain-exactly-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`contain_exactly` matcher" do
|
||||
context "Array is expected to contain every value" do
|
||||
describe [1, 2, 3] do
|
||||
it { is_expected.to contain_exactly(1, 2, 3) }
|
||||
it { is_expected.to contain_exactly(1, 3, 2) }
|
||||
it { is_expected.to contain_exactly(2, 1, 3) }
|
||||
it { is_expected.to contain_exactly(2, 3, 1) }
|
||||
it { is_expected.to contain_exactly(3, 1, 2) }
|
||||
it { is_expected.to contain_exactly(3, 2, 1) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to contain_exactly(1, 2, 1) }
|
||||
end
|
||||
end
|
||||
|
||||
context "Array is not expected to contain every value" do
|
||||
describe [1, 2, 3] do
|
||||
it { is_expected.to_not contain_exactly(1, 2, 3, 4) }
|
||||
it { is_expected.to_not contain_exactly(1, 2) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to_not contain_exactly(1, 3, 2) }
|
||||
end
|
||||
end
|
||||
end
|
99
spec/rspec/expectations/contain_matcher_spec.cr
Normal file
99
spec/rspec/expectations/contain_matcher_spec.cr
Normal file
|
@ -0,0 +1,99 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# In Ruby, this is the `include` matcher.
|
||||
# However, `include` is a reserved keyword in Crystal.
|
||||
# So instead, it is `contain` in Spectator.
|
||||
Spectator.describe "`contain` matcher" do
|
||||
context "array usage" do
|
||||
describe [1, 3, 7] do
|
||||
it { is_expected.to contain(1) }
|
||||
it { is_expected.to contain(3) }
|
||||
it { is_expected.to contain(7) }
|
||||
it { is_expected.to contain(1, 7) }
|
||||
it { is_expected.to contain(1, 3, 7) }
|
||||
|
||||
# Utility matcher method `a_kind_of` is not supported.
|
||||
# it { is_expected.to contain(a_kind_of(Int)) }
|
||||
|
||||
# TODO: Compound matchers aren't supported.
|
||||
# it { is_expected.to contain(be_odd.and be < 10) }
|
||||
|
||||
# TODO: Fix behavior and cleanup output.
|
||||
# This syntax is allowed, but produces a wrong result and bad output.
|
||||
xit { is_expected.to contain(be_odd) }
|
||||
xit { is_expected.not_to contain(be_even) }
|
||||
|
||||
it { is_expected.not_to contain(17) }
|
||||
it { is_expected.not_to contain(43, 100) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to contain(4) }
|
||||
xit { is_expected.to contain(be_even) }
|
||||
xit { is_expected.not_to contain(1) }
|
||||
xit { is_expected.not_to contain(3) }
|
||||
xit { is_expected.not_to contain(7) }
|
||||
xit { is_expected.not_to contain(1, 3, 7) }
|
||||
|
||||
# both of these should fail since it contains 1 but not 9
|
||||
xit { is_expected.to contain(1, 9) }
|
||||
xit { is_expected.not_to contain(1, 9) }
|
||||
end
|
||||
end
|
||||
|
||||
context "string usage" do
|
||||
describe "a string" do
|
||||
it { is_expected.to contain("str") }
|
||||
it { is_expected.to contain("a", "str", "ng") }
|
||||
it { is_expected.not_to contain("foo") }
|
||||
it { is_expected.not_to contain("foo", "bar") }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to contain("foo") }
|
||||
xit { is_expected.not_to contain("str") }
|
||||
xit { is_expected.to contain("str", "foo") }
|
||||
xit { is_expected.not_to contain("str", "foo") }
|
||||
end
|
||||
end
|
||||
|
||||
context "hash usage" do
|
||||
# A hash can't be described inline here for some reason.
|
||||
# So it is placed in the subject instead.
|
||||
describe ":a => 7, :b => 5" do
|
||||
subject { {:a => 7, :b => 5} }
|
||||
|
||||
# Hash syntax is changed here from `:a => 7` to `a: 7`.
|
||||
xit { is_expected.to contain(:a) }
|
||||
xit { is_expected.to contain(:b, :a) }
|
||||
|
||||
# TODO: This hash-like syntax isn't supported.
|
||||
# it { is_expected.to contain(a: 7) }
|
||||
# it { is_expected.to contain(b: 5, a: 7) }
|
||||
xit { is_expected.not_to contain(:c) }
|
||||
xit { is_expected.not_to contain(:c, :d) }
|
||||
# it { is_expected.not_to contain(d: 2) }
|
||||
# it { is_expected.not_to contain(a: 5) }
|
||||
# it { is_expected.not_to contain(b: 7, a: 5) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to contain(:a) }
|
||||
xit { is_expected.not_to contain(:b, :a) }
|
||||
# it { is_expected.not_to contain(a: 7) }
|
||||
# it { is_expected.not_to contain(a: 7, b: 5) }
|
||||
xit { is_expected.to contain(:c) }
|
||||
xit { is_expected.to contain(:c, :d) }
|
||||
# it { is_expected.to contain(d: 2) }
|
||||
# it { is_expected.to contain(a: 5) }
|
||||
# it { is_expected.to contain(a: 5, b: 7) }
|
||||
|
||||
# Mixed cases--the hash contains one but not the other.
|
||||
# All 4 of these cases should fail.
|
||||
xit { is_expected.to contain(:a, :d) }
|
||||
xit { is_expected.not_to contain(:a, :d) }
|
||||
# it { is_expected.to contain(a: 7, d: 3) }
|
||||
# it { is_expected.not_to contain(a: 7, d: 3) }
|
||||
end
|
||||
end
|
||||
end
|
30
spec/rspec/expectations/cover_matcher_spec.cr
Normal file
30
spec/rspec/expectations/cover_matcher_spec.cr
Normal file
|
@ -0,0 +1,30 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/cover-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`cover` matcher" do
|
||||
context "range usage" do
|
||||
describe (1..10) do
|
||||
it { is_expected.to cover(4) }
|
||||
it { is_expected.to cover(6) }
|
||||
it { is_expected.to cover(8) }
|
||||
it { is_expected.to cover(4, 6) }
|
||||
it { is_expected.to cover(4, 6, 8) }
|
||||
it { is_expected.not_to cover(11) }
|
||||
it { is_expected.not_to cover(11, 12) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to cover(11) }
|
||||
xit { is_expected.not_to cover(4) }
|
||||
xit { is_expected.not_to cover(6) }
|
||||
xit { is_expected.not_to cover(8) }
|
||||
xit { is_expected.not_to cover(4, 6, 8) }
|
||||
|
||||
# both of these should fail since it covers 5 but not 11
|
||||
xit { is_expected.to cover(5, 11) }
|
||||
xit { is_expected.not_to cover(5, 11) }
|
||||
end
|
||||
end
|
||||
end
|
33
spec/rspec/expectations/end_with_matcher_spec.cr
Normal file
33
spec/rspec/expectations/end_with_matcher_spec.cr
Normal file
|
@ -0,0 +1,33 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/end-with-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`end_with` matcher" do
|
||||
context "string usage" do
|
||||
describe "this string" do
|
||||
it { is_expected.to end_with "string" }
|
||||
it { is_expected.not_to end_with "stringy" }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to end_with "string" }
|
||||
xit { is_expected.to end_with "stringy" }
|
||||
end
|
||||
end
|
||||
|
||||
context "array usage" do
|
||||
describe [0, 1, 2, 3, 4] do
|
||||
it { is_expected.to end_with 4 }
|
||||
# TODO: Add support for multiple items at the end of an array.
|
||||
# it { is_expected.to end_with 3, 4 }
|
||||
it { is_expected.not_to end_with 3 }
|
||||
# it { is_expected.not_to end_with 0, 1, 2, 3, 4, 5 }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to end_with 4 }
|
||||
xit { is_expected.to end_with 3 }
|
||||
end
|
||||
end
|
||||
end
|
64
spec/rspec/expectations/equality_matchers_spec.cr
Normal file
64
spec/rspec/expectations/equality_matchers_spec.cr
Normal file
|
@ -0,0 +1,64 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/equality-matchers
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "Equality matchers" do
|
||||
context "compare using eq (==)" do
|
||||
describe "a string" do
|
||||
it "is equal to another string of the same value" do
|
||||
expect("this string").to eq("this string")
|
||||
end
|
||||
|
||||
it "is not equal to another string of a different value" do
|
||||
expect("this string").not_to eq("a different string")
|
||||
end
|
||||
end
|
||||
|
||||
describe "an integer" do
|
||||
it "is equal to a float for the same value" do
|
||||
expect(5).to eq(5.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "compare using ==" do
|
||||
describe "a string" do
|
||||
it "is equal to another string of the same value" do
|
||||
expect("this string").to be == "this string"
|
||||
end
|
||||
|
||||
it "is not equal to another string of a different value" do
|
||||
expect("this string").not_to be == "a different string"
|
||||
end
|
||||
end
|
||||
|
||||
describe "an integer" do
|
||||
it "is equal to a float of the same value" do
|
||||
expect(5).to be == 5.0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# There are no #eql? and #equal? methods in Crystal, so these tests are skipped.
|
||||
|
||||
context "compare using be (same?)" do
|
||||
it "is equal to itself" do
|
||||
string = "this string"
|
||||
expect(string).to be(string)
|
||||
end
|
||||
|
||||
it "is not equal to another reference of the same value" do
|
||||
# Strings with identical contents are the same reference in Crystal.
|
||||
# This test is modified to reflect that.
|
||||
# expect("this string").not_to be("this string")
|
||||
box1 = Box.new("this string")
|
||||
box2 = Box.new("this string")
|
||||
expect(box1).not_to be(box2)
|
||||
end
|
||||
|
||||
it "is not equal to another string of a different value" do
|
||||
expect("this string").not_to be("a different string")
|
||||
end
|
||||
end
|
||||
end
|
37
spec/rspec/expectations/have_attributes_matcher_spec.cr
Normal file
37
spec/rspec/expectations/have_attributes_matcher_spec.cr
Normal file
|
@ -0,0 +1,37 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/have-attributes-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`have_attributes` matcher" do
|
||||
context "basic usage" do
|
||||
# Use `record` instead of `Struct.new`.
|
||||
record Person, name : String, age : Int32
|
||||
|
||||
describe Person.new("Jim", 32) do
|
||||
# Changed some syntax for Ruby hashes to Crystal named tuples.
|
||||
|
||||
# Spectator doesn't support helper matchers like `a_string_starting_with` and `a_value <`.
|
||||
# But maybe in the future it will.
|
||||
it { is_expected.to have_attributes(name: "Jim") }
|
||||
# it { is_expected.to have_attributes(name: a_string_starting_with("J") ) }
|
||||
it { is_expected.to have_attributes(age: 32) }
|
||||
# it { is_expected.to have_attributes(age: (a_value > 30) ) }
|
||||
it { is_expected.to have_attributes(name: "Jim", age: 32) }
|
||||
# it { is_expected.to have_attributes(name: a_string_starting_with("J"), age: (a_value > 30) ) }
|
||||
it { is_expected.not_to have_attributes(name: "Bob") }
|
||||
it { is_expected.not_to have_attributes(age: 10) }
|
||||
# it { is_expected.not_to have_attributes(age: (a_value < 30) ) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to have_attributes(name: "Bob") }
|
||||
xit { is_expected.to have_attributes(name: 10) }
|
||||
|
||||
# fails if any of the attributes don't match
|
||||
xit { is_expected.to have_attributes(name: "Bob", age: 32) }
|
||||
xit { is_expected.to have_attributes(name: "Jim", age: 10) }
|
||||
xit { is_expected.to have_attributes(name: "Bob", age: 10) }
|
||||
end
|
||||
end
|
||||
end
|
30
spec/rspec/expectations/match_matcher_spec.cr
Normal file
30
spec/rspec/expectations/match_matcher_spec.cr
Normal file
|
@ -0,0 +1,30 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/match-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`match` matcher" do
|
||||
context "string usage" do
|
||||
describe "a string" do
|
||||
it { is_expected.to match(/str/) }
|
||||
it { is_expected.not_to match(/foo/) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to match(/str/) }
|
||||
xit { is_expected.to match(/foo/) }
|
||||
end
|
||||
end
|
||||
|
||||
context "regular expression usage" do
|
||||
describe /foo/ do
|
||||
it { is_expected.to match("food") }
|
||||
it { is_expected.not_to match("drinks") }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to match("food") }
|
||||
xit { is_expected.to match("drinks") }
|
||||
end
|
||||
end
|
||||
end
|
86
spec/rspec/expectations/predicate_matchers_spec.cr
Normal file
86
spec/rspec/expectations/predicate_matchers_spec.cr
Normal file
|
@ -0,0 +1,86 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/predicate-matchers
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "Predicate matchers" do
|
||||
context "should be_zero (based on Int#zero?)" do
|
||||
describe 0 do
|
||||
it { is_expected.to be_zero }
|
||||
end
|
||||
|
||||
describe 7 do
|
||||
# deliberate failure
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to be_zero }
|
||||
end
|
||||
end
|
||||
|
||||
context "should_not be_empty (based on Array#empty?)" do
|
||||
describe [1, 2, 3] do
|
||||
it { is_expected.not_to be_empty }
|
||||
end
|
||||
|
||||
describe [] of Int32 do
|
||||
# deliberate failure
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
context "should have_key (based on Hash#has_key?)" do
|
||||
describe Hash do
|
||||
subject { {:foo => 7} }
|
||||
|
||||
it { is_expected.to have_key(:foo) }
|
||||
|
||||
# deliberate failure
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.to have_key(:bar) }
|
||||
end
|
||||
end
|
||||
|
||||
context "should_not have_all_string_keys (based on custom #has_all_string_keys? method)" do
|
||||
class ::Hash(K, V)
|
||||
def has_all_string_keys?
|
||||
keys.all? { |k| String === k }
|
||||
end
|
||||
end
|
||||
|
||||
describe Hash do
|
||||
context "with symbol keys" do
|
||||
subject { {:foo => 7, :bar => 5} }
|
||||
|
||||
it { is_expected.not_to have_all_string_keys }
|
||||
end
|
||||
|
||||
context "with string keys" do
|
||||
subject { {"foo" => 7, "bar" => 5} }
|
||||
|
||||
# deliberate failure
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to have_all_string_keys }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "matcher arguments are passed on to the predicate method" do
|
||||
struct ::Int
|
||||
def multiple_of?(x)
|
||||
(self % x).zero?
|
||||
end
|
||||
end
|
||||
|
||||
describe 12 do
|
||||
it { is_expected.to be_multiple_of(3) }
|
||||
it { is_expected.not_to be_multiple_of(7) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to be_multiple_of(4) }
|
||||
xit { is_expected.to be_multiple_of(5) }
|
||||
end
|
||||
end
|
||||
|
||||
# The examples using private methods cause a compilation error in Crystal, and can't be used here.
|
||||
end
|
33
spec/rspec/expectations/start_with_matcher_spec.cr
Normal file
33
spec/rspec/expectations/start_with_matcher_spec.cr
Normal file
|
@ -0,0 +1,33 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/start-with-matcher
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "`start_with` matcher" do
|
||||
context "with a string" do
|
||||
describe "this string" do
|
||||
it { is_expected.to start_with "this" }
|
||||
it { is_expected.not_to start_with "that" }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to start_with "this" }
|
||||
xit { is_expected.to start_with "that" }
|
||||
end
|
||||
end
|
||||
|
||||
context "with an array" do
|
||||
describe [0, 1, 2, 3, 4] do
|
||||
it { is_expected.to start_with 0 }
|
||||
# TODO: Add support for multiple items at the beginning of an array.
|
||||
# it { is_expected.to start_with(0, 1) }
|
||||
it { is_expected.not_to start_with(2) }
|
||||
# it { is_expected.not_to start_with(0, 1, 2, 3, 4, 5) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to start_with 0 }
|
||||
xit { is_expected.to start_with 3 }
|
||||
end
|
||||
end
|
||||
end
|
101
spec/rspec/expectations/type_matchers_spec.cr
Normal file
101
spec/rspec/expectations/type_matchers_spec.cr
Normal file
|
@ -0,0 +1,101 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
# Examples taken from:
|
||||
# https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/type-matchers
|
||||
# and modified to fit Spectator and Crystal.
|
||||
Spectator.describe "Type matchers" do
|
||||
context "be_(a_)kind_of matcher" do
|
||||
# The docs use Float as an example.
|
||||
# This doesn't work with the Crystal compiler,
|
||||
# so a custom hierarchy is used instead.
|
||||
# "Error: can't use Number as generic type argument yet, use a more specific type"
|
||||
|
||||
module MyModule; end
|
||||
|
||||
class Base; end
|
||||
|
||||
class Derived < Base
|
||||
include MyModule
|
||||
end
|
||||
|
||||
describe Derived do
|
||||
# the actual class
|
||||
it { is_expected.to be_kind_of(Derived) }
|
||||
it { is_expected.to be_a_kind_of(Derived) }
|
||||
it { is_expected.to be_a(Derived) }
|
||||
|
||||
# the superclass
|
||||
it { is_expected.to be_kind_of(Base) }
|
||||
it { is_expected.to be_a_kind_of(Base) }
|
||||
it { is_expected.to be_an(Base) }
|
||||
|
||||
# an included module
|
||||
it { is_expected.to be_kind_of(MyModule) }
|
||||
it { is_expected.to be_a_kind_of(MyModule) }
|
||||
it { is_expected.to be_a(MyModule) }
|
||||
|
||||
# negative passing case
|
||||
it { is_expected.not_to be_kind_of(String) }
|
||||
it { is_expected.not_to be_a_kind_of(String) }
|
||||
it { is_expected.not_to be_a(String) }
|
||||
|
||||
# deliberate failures
|
||||
# TODO: Add support for expected failures.
|
||||
xit { is_expected.not_to be_kind_of(Derived) }
|
||||
xit { is_expected.not_to be_a_kind_of(Derived) }
|
||||
xit { is_expected.not_to be_a(Derived) }
|
||||
xit { is_expected.not_to be_kind_of(Base) }
|
||||
xit { is_expected.not_to be_a_kind_of(Base) }
|
||||
xit { is_expected.not_to be_an(Base) }
|
||||
xit { is_expected.not_to be_kind_of(MyModule) }
|
||||
xit { is_expected.not_to be_a_kind_of(MyModule) }
|
||||
xit { is_expected.not_to be_a(MyModule) }
|
||||
xit { is_expected.to be_kind_of(String) }
|
||||
xit { is_expected.to be_a_kind_of(String) }
|
||||
xit { is_expected.to be_a(String) }
|
||||
end
|
||||
|
||||
context "be_(an_)instance_of matcher" do
|
||||
# The docs use Float as an example.
|
||||
# This doesn't work with the Crystal compiler,
|
||||
# so a custom hierarchy is used instead.
|
||||
# "Error: can't use Number as generic type argument yet, use a more specific type"
|
||||
|
||||
module MyModule; end
|
||||
|
||||
class Base; end
|
||||
|
||||
class Derived < Base
|
||||
include MyModule
|
||||
end
|
||||
|
||||
describe Derived do
|
||||
# the actual class
|
||||
it { is_expected.to be_instance_of(Derived) }
|
||||
it { is_expected.to be_an_instance_of(Derived) }
|
||||
|
||||
# the superclass
|
||||
it { is_expected.not_to be_instance_of(Base) }
|
||||
it { is_expected.not_to be_an_instance_of(Base) }
|
||||
|
||||
# an included module
|
||||
it { is_expected.not_to be_instance_of(MyModule) }
|
||||
it { is_expected.not_to be_an_instance_of(MyModule) }
|
||||
|
||||
# another class with no relation to the subject's hierarchy
|
||||
it { is_expected.not_to be_instance_of(String) }
|
||||
it { is_expected.not_to be_an_instance_of(String) }
|
||||
|
||||
# deliberate failures
|
||||
xit { is_expected.not_to be_instance_of(Derived) }
|
||||
xit { is_expected.not_to be_an_instance_of(Derived) }
|
||||
xit { is_expected.to be_instance_of(Base) }
|
||||
xit { is_expected.to be_an_instance_of(Base) }
|
||||
xit { is_expected.to be_instance_of(MyModule) }
|
||||
xit { is_expected.to be_an_instance_of(MyModule) }
|
||||
xit { is_expected.to be_instance_of(String) }
|
||||
xit { is_expected.to be_an_instance_of(String) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1 @@
|
|||
require "../src/spectator"
|
||||
|
||||
# Prevent Spectator from trying to run tests on its own.
|
||||
Spectator.autorun = false
|
||||
|
|
|
@ -6,7 +6,7 @@ module Spectator
|
|||
extend self
|
||||
|
||||
# Current version of the Spectator library.
|
||||
VERSION = "0.9.1"
|
||||
VERSION = "0.9.2"
|
||||
|
||||
# Top-level describe method.
|
||||
# All specs in a file must be wrapped in this call.
|
||||
|
|
|
@ -3,7 +3,7 @@ require "../spec_builder"
|
|||
|
||||
module Spectator
|
||||
module DSL
|
||||
macro it(description, _source_file = __FILE__, _source_line = __LINE__, &block)
|
||||
macro it(description = nil, _source_file = __FILE__, _source_line = __LINE__, &block)
|
||||
{% if block.is_a?(Nop) %}
|
||||
{% if description.is_a?(Call) %}
|
||||
def %run
|
||||
|
@ -20,17 +20,17 @@ module Spectator
|
|||
|
||||
%source = ::Spectator::Source.new({{_source_file}}, {{_source_line}})
|
||||
::Spectator::SpecBuilder.add_example(
|
||||
{{description.is_a?(StringLiteral) ? description : description.stringify}},
|
||||
{{description.is_a?(StringLiteral) || description.is_a?(NilLiteral) ? description : description.stringify}},
|
||||
%source,
|
||||
{{@type.name}}
|
||||
) { |test| test.as({{@type.name}}).%run }
|
||||
end
|
||||
|
||||
macro specify(description, &block)
|
||||
macro specify(description = nil, &block)
|
||||
it({{description}}) {{block}}
|
||||
end
|
||||
|
||||
macro pending(description, _source_file = __FILE__, _source_line = __LINE__, &block)
|
||||
macro pending(description = nil, _source_file = __FILE__, _source_line = __LINE__, &block)
|
||||
{% if block.is_a?(Nop) %}
|
||||
{% if description.is_a?(Call) %}
|
||||
def %run
|
||||
|
@ -47,17 +47,17 @@ module Spectator
|
|||
|
||||
%source = ::Spectator::Source.new({{_source_file}}, {{_source_line}})
|
||||
::Spectator::SpecBuilder.add_pending_example(
|
||||
{{description.is_a?(StringLiteral) ? description : description.stringify}},
|
||||
{{description.is_a?(StringLiteral) || description.is_a?(NilLiteral) ? description : description.stringify}},
|
||||
%source,
|
||||
{{@type.name}}
|
||||
) { |test| test.as({{@type.name}}).%run }
|
||||
end
|
||||
|
||||
macro skip(description, &block)
|
||||
macro skip(description = nil, &block)
|
||||
pending({{description}}) {{block}}
|
||||
end
|
||||
|
||||
macro xit(description, &block)
|
||||
macro xit(description = nil, &block)
|
||||
pending({{description}}) {{block}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ module Spectator
|
|||
%source = ::Spectator::Source.new({{_source_file}}, {{_source_line}})
|
||||
::Spectator::SpecBuilder.start_group({{description}}, %source)
|
||||
|
||||
{% if what.is_a?(Path) || what.is_a?(Generic) %}
|
||||
{% if (what.is_a?(Path) || what.is_a?(Generic)) && what.resolve? %}
|
||||
macro described_class
|
||||
{{what}}
|
||||
end
|
||||
|
@ -27,6 +27,10 @@ module Spectator
|
|||
def subject(*args)
|
||||
described_class.new(*args)
|
||||
end
|
||||
{% else %}
|
||||
def subject
|
||||
{{what}}
|
||||
end
|
||||
{% end %}
|
||||
|
||||
{{block.body}}
|
||||
|
|
|
@ -94,6 +94,60 @@ module Spectator
|
|||
be_a({{expected}})
|
||||
end
|
||||
|
||||
# Indicates that some value should be of a specified type.
|
||||
# The `Object#is_a?` method is used for this check.
|
||||
# A type name or type union should be used for *expected*.
|
||||
# This method is identical to `#be_a`,
|
||||
# and exists just to improve grammar.
|
||||
#
|
||||
# Examples:
|
||||
# ```
|
||||
# expect(123).to be_kind_of(Int)
|
||||
# ```
|
||||
macro be_kind_of(expected)
|
||||
be_a({{expected}})
|
||||
end
|
||||
|
||||
# Indicates that some value should be of a specified type.
|
||||
# The `Object#is_a?` method is used for this check.
|
||||
# A type name or type union should be used for *expected*.
|
||||
# This method is identical to `#be_a`,
|
||||
# and exists just to improve grammar.
|
||||
#
|
||||
# Examples:
|
||||
# ```
|
||||
# expect(123).to be_a_kind_of(Int)
|
||||
# ```
|
||||
macro be_a_kind_of(expected)
|
||||
be_a({{expected}})
|
||||
end
|
||||
|
||||
# Indicates that some value should be of a specified type.
|
||||
# The value's runtime class is checked.
|
||||
# A type name or type union should be used for *expected*.
|
||||
#
|
||||
# Examples:
|
||||
# ```
|
||||
# expect(123).to be_instance_of(Int32)
|
||||
# ```
|
||||
macro be_instance_of(expected)
|
||||
::Spectator::Matchers::InstanceMatcher({{expected}}).new
|
||||
end
|
||||
|
||||
# Indicates that some value should be of a specified type.
|
||||
# The value's runtime class is checked.
|
||||
# A type name or type union should be used for *expected*.
|
||||
# This method is identical to `#be_an_instance_of`,
|
||||
# and exists just to improve grammar.
|
||||
#
|
||||
# Examples:
|
||||
# ```
|
||||
# expect(123).to be_an_instance_of(Int32)
|
||||
# ```
|
||||
macro be_an_instance_of(expected)
|
||||
be_instance_of({{expected}})
|
||||
end
|
||||
|
||||
# Indicates that some value should respond to a method call.
|
||||
# One or more method names can be provided.
|
||||
#
|
||||
|
@ -400,6 +454,27 @@ module Spectator
|
|||
::Spectator::Matchers::ContainMatcher.new(%test_value)
|
||||
end
|
||||
|
||||
# Indicates that some range (or collection) should contain another value.
|
||||
# This is typically used on a `Range` (although any `Enumerable` works).
|
||||
# The `includes?` method is used.
|
||||
#
|
||||
# Examples:
|
||||
# ```
|
||||
# expect(1..10).to contain(5)
|
||||
# expect((1..)).to contain(100)
|
||||
# expect(..100).to contain(50)
|
||||
# ```
|
||||
#
|
||||
# Additionally, multiple arguments can be specified.
|
||||
# ```
|
||||
# expect(1..10).to contain(2, 3)
|
||||
# expect(..100).to contain(0, 50)
|
||||
# ```
|
||||
macro cover(*expected)
|
||||
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}})
|
||||
::Spectator::Matchers::ContainMatcher.new(%test_value)
|
||||
end
|
||||
|
||||
# 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.
|
||||
|
@ -466,22 +541,23 @@ module Spectator
|
|||
have_value({{expected}})
|
||||
end
|
||||
|
||||
# Indicates that some set should contain some values in exact order.
|
||||
# Indicates that some set should contain some values in any order.
|
||||
#
|
||||
# Example:
|
||||
# ```
|
||||
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
|
||||
# expect([1, 2, 3]).to contain_exactly(3, 2, 1)
|
||||
# ```
|
||||
macro contain_exactly(*expected)
|
||||
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
||||
::Spectator::Matchers::ArrayMatcher.new(%test_value)
|
||||
end
|
||||
|
||||
# Indicates that some set should contain the same values in exact order as another set.
|
||||
# Indicates that some set should contain the same values in any order as another set.
|
||||
# This is the same as `#contain_exactly`, but takes an array as an argument.
|
||||
#
|
||||
# Example:
|
||||
# ```
|
||||
# expect([1, 2, 3]).to match_array([1, 2, 3])
|
||||
# expect([1, 2, 3]).to match_array([3, 2, 1])
|
||||
# ```
|
||||
macro match_array(expected)
|
||||
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
||||
|
@ -716,8 +792,8 @@ module Spectator
|
|||
{% raise "Undefined local variable or method '#{call}'" %}
|
||||
{% end %}
|
||||
|
||||
descriptor = { {{method_name}}: Tuple.new({{call.args.splat}}) }
|
||||
label = String::Builder.new({{method_name.stringify}})
|
||||
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 %}
|
||||
|
|
|
@ -19,11 +19,18 @@ module Spectator
|
|||
end
|
||||
|
||||
macro let!(name, &block)
|
||||
# TODO: Doesn't work with late-defined values (let).
|
||||
@%value = {{yield}}
|
||||
@%wrapper : ::Spectator::ValueWrapper?
|
||||
|
||||
def %wrapper
|
||||
{{block.body}}
|
||||
end
|
||||
|
||||
before_each do
|
||||
@%wrapper = ::Spectator::TypedValueWrapper.new(%wrapper)
|
||||
end
|
||||
|
||||
def {{name.id}}
|
||||
@%value
|
||||
@%wrapper.as(::Spectator::TypedValueWrapper(typeof(%wrapper))).value
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ module Spectator
|
|||
# Concrete types must implement the `#run_impl` method.
|
||||
abstract class Example < ExampleComponent
|
||||
@finished = false
|
||||
@description : String? = nil
|
||||
|
||||
protected setter description
|
||||
|
||||
# Indicates whether the example has already been run.
|
||||
def finished? : Bool
|
||||
|
@ -24,10 +27,12 @@ module Spectator
|
|||
end
|
||||
|
||||
def description : String | Symbol
|
||||
@test_wrapper.description
|
||||
@description || @test_wrapper.description
|
||||
end
|
||||
|
||||
def symbolic? : Bool
|
||||
return false unless @test_wrapper.description?
|
||||
|
||||
description = @test_wrapper.description
|
||||
description.starts_with?('#') || description.starts_with?('.')
|
||||
end
|
||||
|
|
|
@ -44,6 +44,10 @@ module Spectator::Expectations
|
|||
values?.not_nil!
|
||||
end
|
||||
|
||||
def description
|
||||
@match_data.description
|
||||
end
|
||||
|
||||
# Creates the JSON representation of the expectation.
|
||||
def to_json(json : ::JSON::Builder)
|
||||
json.object do
|
||||
|
|
|
@ -46,6 +46,7 @@ module Spectator
|
|||
# Reports the outcome of an expectation.
|
||||
# An exception will be raised when a failing result is given.
|
||||
def report_expectation(expectation : Expectations::Expectation) : Nil
|
||||
@example.description = expectation.description unless @example.test_wrapper.description?
|
||||
@reporter.report(expectation)
|
||||
end
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module Spectator::Matchers
|
|||
match_data = matcher.match(element)
|
||||
break match_data unless match_data.matched?
|
||||
end
|
||||
found || SuccessfulMatchData.new
|
||||
found || SuccessfulMatchData.new(description)
|
||||
end
|
||||
|
||||
# Negated matching for this matcher is not supported.
|
||||
|
|
|
@ -5,7 +5,7 @@ require "./unordered_array_matcher"
|
|||
|
||||
module Spectator::Matchers
|
||||
# Matcher for checking that the contents of one array (or similar type)
|
||||
# has the exact same contents as another and in the same order.
|
||||
# has the exact same contents as another but may be in any order.
|
||||
struct ArrayMatcher(ExpectedType) < Matcher
|
||||
# Expected value and label.
|
||||
private getter expected
|
||||
|
@ -25,15 +25,19 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
actual_elements = actual.value.to_a
|
||||
expected_elements = expected.value.to_a
|
||||
index = compare_arrays(expected_elements, actual_elements)
|
||||
missing, extra = compare_arrays(expected_elements, actual_elements)
|
||||
|
||||
case index
|
||||
when Int # Content differs.
|
||||
failed_content_mismatch(expected_elements, actual_elements, index, actual.label)
|
||||
when true # Contents are identical.
|
||||
SuccessfulMatchData.new
|
||||
else # Size differs.
|
||||
failed_size_mismatch(expected_elements, actual_elements, actual.label)
|
||||
if missing.empty? && extra.empty?
|
||||
# Contents are identical.
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
# Content differs.
|
||||
FailedMatchData.new(description, "#{actual.label} does not contain exactly #{expected.label}",
|
||||
expected: expected_elements.inspect,
|
||||
actual: actual_elements.inspect,
|
||||
missing: missing.empty? ? "None" : missing.inspect,
|
||||
extra: extra.empty? ? "None" : extra.inspect
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -42,14 +46,17 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
actual_elements = actual.value.to_a
|
||||
expected_elements = expected.value.to_a
|
||||
missing, extra = compare_arrays(expected_elements, actual_elements)
|
||||
|
||||
case compare_arrays(expected_elements, actual_elements)
|
||||
when Int # Contents differ.
|
||||
SuccessfulMatchData.new
|
||||
when true # Contents are identical.
|
||||
failed_content_identical(expected_elements, actual_elements, actual.label)
|
||||
else # Size differs.
|
||||
SuccessfulMatchData.new
|
||||
if missing.empty? && extra.empty?
|
||||
# Contents are identical.
|
||||
FailedMatchData.new(description, "#{actual.label} contains exactly #{expected.label}",
|
||||
expected: "Not #{expected_elements.inspect}",
|
||||
actual: actual_elements.inspect
|
||||
)
|
||||
else
|
||||
# Content differs.
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,49 +72,41 @@ module Spectator::Matchers
|
|||
UnorderedArrayMatcher.new(expected)
|
||||
end
|
||||
|
||||
# Compares two arrays to determine whether they contain the same elements, and in the same order.
|
||||
# If the arrays are the same, then `true` is returned.
|
||||
# If they are different, `false` or an integer is returned.
|
||||
# `false` is returned when the sizes of the arrays don't match.
|
||||
# An integer is returned, that is the index of the mismatched elements in the arrays.
|
||||
# Compares two arrays to determine whether they contain the same elements, but in any order.
|
||||
# A tuple of two arrays is returned.
|
||||
# The first array is the missing elements (present in expected, missing in actual).
|
||||
# The second array array is the extra elements (not present in expected, present in actual).
|
||||
private def compare_arrays(expected_elements, actual_elements)
|
||||
if expected_elements.size == actual_elements.size
|
||||
index = 0
|
||||
expected_elements.zip(actual_elements) do |expected_element, actual_element|
|
||||
return index unless expected_element == actual_element
|
||||
index += 1
|
||||
# Produce hashes where the array elements are the keys, and the values are the number of occurances.
|
||||
expected_hash = expected_elements.group_by(&.itself).map { |k, v| {k, v.size} }.to_h
|
||||
actual_hash = actual_elements.group_by(&.itself).map { |k, v| {k, v.size} }.to_h
|
||||
|
||||
{
|
||||
hash_count_difference(expected_hash, actual_hash),
|
||||
hash_count_difference(actual_hash, expected_hash),
|
||||
}
|
||||
end
|
||||
|
||||
# Expects two hashes, with values as counts for keys.
|
||||
# Produces an array of differences with elements repeated if needed.
|
||||
private def hash_count_difference(first, second)
|
||||
# Subtract the number of occurances from the other array.
|
||||
# A duplicate hash is used here because the original can't be modified,
|
||||
# since it there's a two-way comparison.
|
||||
#
|
||||
# Then reject elements that have zero (or less) occurances.
|
||||
# Lastly, expand to the correct number of elements.
|
||||
first.map do |element, count|
|
||||
if second_count = second[element]?
|
||||
{element, count - second_count}
|
||||
else
|
||||
{element, count}
|
||||
end
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Produces match data for a failure when the array sizes differ.
|
||||
private def failed_size_mismatch(expected_elements, actual_elements, actual_label)
|
||||
FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (size mismatch)",
|
||||
expected: expected_elements.inspect,
|
||||
actual: actual_elements.inspect,
|
||||
"expected size": expected_elements.size.to_s,
|
||||
"actual size": actual_elements.size.to_s
|
||||
)
|
||||
end
|
||||
|
||||
# Produces match data for a failure when the array content is mismatched.
|
||||
private def failed_content_mismatch(expected_elements, actual_elements, index, actual_label)
|
||||
FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (element mismatch)",
|
||||
expected: expected_elements[index].inspect,
|
||||
actual: actual_elements[index].inspect,
|
||||
index: index.to_s
|
||||
)
|
||||
end
|
||||
|
||||
# Produces match data for a failure when the arrays are identical, but they shouldn't be (negation).
|
||||
private def failed_content_identical(expected_elements, actual_elements, actual_label)
|
||||
FailedMatchData.new("#{actual_label} contains exactly #{expected.label}",
|
||||
expected: "Not #{expected_elements.inspect}",
|
||||
actual: actual_elements.inspect
|
||||
)
|
||||
end.reject do |(_, count)|
|
||||
count <= 0
|
||||
end.map do |(element, count)|
|
||||
Array.new(count, element)
|
||||
end.flatten
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,9 +28,9 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} does not have attributes #{expected.label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} does not have attributes #{expected.label}", **values(snapshot))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -39,9 +39,9 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
FailedMatchData.new("#{actual.label} has attributes #{expected.label}", **negated_values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} has attributes #{expected.label}", **negated_values(snapshot))
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@ module Spectator::Matchers
|
|||
expected.value === actual.value
|
||||
end
|
||||
|
||||
# Overload that takes a regex so that the operands are flipped.
|
||||
# This mimics RSpec's behavior.
|
||||
private def match?(actual : TestExpression(Regex)) : Bool forall T
|
||||
actual.value === expected.value
|
||||
end
|
||||
|
||||
# Message displayed when the matcher isn't satisifed.
|
||||
#
|
||||
# This is only called when `#match?` returns false.
|
||||
|
|
|
@ -30,21 +30,21 @@ module Spectator::Matchers
|
|||
before, after = change(actual)
|
||||
if expected_before == before
|
||||
if before == after
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label}",
|
||||
before: before.inspect,
|
||||
after: after.inspect
|
||||
)
|
||||
elsif expected_after == after
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label} to #{expected_after.inspect}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label} to #{expected_after.inspect}",
|
||||
before: before.inspect,
|
||||
after: after.inspect,
|
||||
expected: expected_after.inspect
|
||||
)
|
||||
end
|
||||
else
|
||||
FailedMatchData.new("#{expression.label} was not initially #{expected_before.inspect}",
|
||||
FailedMatchData.new(description, "#{expression.label} was not initially #{expected_before.inspect}",
|
||||
expected: expected_before.inspect,
|
||||
actual: before.inspect,
|
||||
)
|
||||
|
@ -57,15 +57,15 @@ module Spectator::Matchers
|
|||
before, after = change(actual)
|
||||
if expected_before == before
|
||||
if expected_after == after
|
||||
FailedMatchData.new("#{actual.label} changed #{expression.label} from #{expected_before.inspect} to #{expected_after.inspect}",
|
||||
FailedMatchData.new(description, "#{actual.label} changed #{expression.label} from #{expected_before.inspect} to #{expected_after.inspect}",
|
||||
before: before.inspect,
|
||||
after: after.inspect
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
else
|
||||
FailedMatchData.new("#{expression.label} was not initially #{expected_before.inspect}",
|
||||
FailedMatchData.new(description, "#{expression.label} was not initially #{expected_before.inspect}",
|
||||
expected: expected_before.inspect,
|
||||
actual: before.inspect,
|
||||
)
|
||||
|
|
|
@ -27,18 +27,18 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
before, after = change(actual)
|
||||
if expected != before
|
||||
FailedMatchData.new("#{expression.label} was not initially #{expected}",
|
||||
FailedMatchData.new(description, "#{expression.label} was not initially #{expected}",
|
||||
expected: expected.inspect,
|
||||
actual: before.inspect,
|
||||
)
|
||||
elsif before == after
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label} from #{expected}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label} from #{expected}",
|
||||
before: before.inspect,
|
||||
after: after.inspect,
|
||||
expected: "Not #{expected.inspect}"
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,14 +47,14 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
before, after = change(actual)
|
||||
if expected != before
|
||||
FailedMatchData.new("#{expression.label} was not initially #{expected}",
|
||||
FailedMatchData.new(description, "#{expression.label} was not initially #{expected}",
|
||||
expected: expected.inspect,
|
||||
actual: before.inspect
|
||||
)
|
||||
elsif before == after
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} changed #{expression.label} from #{expected}",
|
||||
FailedMatchData.new(description, "#{actual.label} changed #{expression.label} from #{expected}",
|
||||
before: before.inspect,
|
||||
after: after.inspect,
|
||||
expected: expected.inspect
|
||||
|
|
|
@ -25,12 +25,12 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
before, after = change(actual)
|
||||
if before == after
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label}",
|
||||
before: before.inspect,
|
||||
after: after.inspect
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -39,9 +39,9 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
before, after = change(actual)
|
||||
if before == after
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} changed #{expression.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} changed #{expression.label}",
|
||||
before: before.inspect,
|
||||
after: after.inspect
|
||||
)
|
||||
|
|
|
@ -25,14 +25,14 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
before, after = change(actual)
|
||||
if before == after
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label}",
|
||||
before: before.inspect,
|
||||
after: after.inspect
|
||||
)
|
||||
elsif @evaluator.call(before, after)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label} #{@relativity}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label} #{@relativity}",
|
||||
before: before.inspect,
|
||||
after: after.inspect
|
||||
)
|
||||
|
|
|
@ -27,15 +27,15 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
before, after = change(actual)
|
||||
if before == after
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label}",
|
||||
before: before.inspect,
|
||||
after: after.inspect,
|
||||
expected: expected.inspect
|
||||
)
|
||||
elsif expected == after
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} did not change #{expression.label} to #{expected}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not change #{expression.label} to #{expected}",
|
||||
before: before.inspect,
|
||||
after: after.inspect,
|
||||
expected: expected.inspect
|
||||
|
|
|
@ -18,6 +18,13 @@ module Spectator::Matchers
|
|||
end
|
||||
end
|
||||
|
||||
# If the expectation is negated, then this method is called instead of `#match?`.
|
||||
private def does_not_match?(actual : TestExpression(T)) : Bool forall T
|
||||
!expected.value.any? do |item|
|
||||
actual.value.includes?(item)
|
||||
end
|
||||
end
|
||||
|
||||
# Message displayed when the matcher isn't satisifed.
|
||||
#
|
||||
# This is only called when `#match?` returns false.
|
||||
|
@ -25,7 +32,7 @@ module Spectator::Matchers
|
|||
# The message should typically only contain the test expression labels.
|
||||
# Actual values should be returned by `#values`.
|
||||
private def failure_message(actual) : String
|
||||
"#{actual.label} does not match #{expected.label}"
|
||||
"#{actual.label} does not contain #{expected.label}"
|
||||
end
|
||||
|
||||
# Message displayed when the matcher isn't satisifed and is negated.
|
||||
|
|
|
@ -23,7 +23,8 @@ module Spectator::Matchers
|
|||
|
||||
# Actually performs the test against the expression.
|
||||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
if (value = actual.value).responds_to?(:ends_with?)
|
||||
value = actual.value
|
||||
if value.is_a?(String) || value.responds_to?(:ends_with?)
|
||||
match_ends_with(value, actual.label)
|
||||
else
|
||||
match_last(value, actual.label)
|
||||
|
@ -33,10 +34,11 @@ module Spectator::Matchers
|
|||
# Performs the test against the expression, but inverted.
|
||||
# A successful match with `#match` should normally fail for this method, and vice-versa.
|
||||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
if actual.value.responds_to?(:ends_with?)
|
||||
negated_match_ends_with(actual)
|
||||
value = actual.value
|
||||
if value.is_a?(String) || value.responds_to?(:ends_with?)
|
||||
negated_match_ends_with(value, actual.label)
|
||||
else
|
||||
negated_match_last(actual)
|
||||
negated_match_last(value, actual.label)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -44,9 +46,9 @@ module Spectator::Matchers
|
|||
# This method expects (and uses) the `#ends_with?` method on the value.
|
||||
private def match_ends_with(actual_value, actual_label)
|
||||
if actual_value.ends_with?(expected.value)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual_label} does not end with #{expected.label} (using #ends_with?)",
|
||||
FailedMatchData.new(description, "#{actual_label} does not end with #{expected.label} (using #ends_with?)",
|
||||
expected: expected.value.inspect,
|
||||
actual: actual_value.inspect
|
||||
)
|
||||
|
@ -60,9 +62,9 @@ module Spectator::Matchers
|
|||
last = list.last
|
||||
|
||||
if expected.value === last
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual_label} does not end with #{expected.label} (using expected === last)",
|
||||
FailedMatchData.new(description, "#{actual_label} does not end with #{expected.label} (using expected === last)",
|
||||
expected: expected.value.inspect,
|
||||
actual: last.inspect,
|
||||
list: list.inspect
|
||||
|
@ -72,31 +74,31 @@ module Spectator::Matchers
|
|||
|
||||
# Checks whether the actual value does not end with the expected value.
|
||||
# This method expects (and uses) the `#ends_with?` method on the value.
|
||||
private def negated_match_ends_with(actual)
|
||||
if actual.value.ends_with?(expected.value)
|
||||
FailedMatchData.new("#{actual.label} ends with #{expected.label} (using #ends_with?)",
|
||||
expected: expected.value.inspect,
|
||||
actual: actual.value.inspect
|
||||
private def negated_match_ends_with(actual_value, actual_label)
|
||||
if actual_value.ends_with?(expected.value)
|
||||
FailedMatchData.new(description, "#{actual_label} ends with #{expected.label} (using #ends_with?)",
|
||||
expected: "Not #{expected.value.inspect}",
|
||||
actual: actual_value.inspect
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether the last element of the value is not the expected value.
|
||||
# This method expects that the actual value is a set (enumerable).
|
||||
private def negated_match_last(actual)
|
||||
list = actual.value.to_a
|
||||
private def negated_match_last(actual_value, actual_label)
|
||||
list = actual_value.to_a
|
||||
last = list.last
|
||||
|
||||
if expected.value === last
|
||||
FailedMatchData.new("#{actual.label} ends with #{expected.label} (using expected === last)",
|
||||
expected: expected.value.inspect,
|
||||
FailedMatchData.new(description, "#{actual_label} ends with #{expected.label} (using expected === last)",
|
||||
expected: "Not #{expected.value.inspect}",
|
||||
actual: last.inspect,
|
||||
list: list.inspect
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,16 +33,16 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
exception = capture_exception { actual.value }
|
||||
if exception.nil?
|
||||
FailedMatchData.new("#{actual.label} did not raise", expected: ExceptionType.inspect)
|
||||
FailedMatchData.new(description, "#{actual.label} did not raise", expected: ExceptionType.inspect)
|
||||
else
|
||||
if exception.is_a?(ExceptionType)
|
||||
if (value = expected.value).nil?
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
if value === exception.message
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} raised #{exception.class}, but the message is not #{expected.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} raised #{exception.class}, but the message is not #{expected.label}",
|
||||
"expected type": ExceptionType.inspect,
|
||||
"actual type": exception.class.inspect,
|
||||
"expected message": value.inspect,
|
||||
|
@ -51,7 +51,7 @@ module Spectator::Matchers
|
|||
end
|
||||
end
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} did not raise #{ExceptionType}",
|
||||
FailedMatchData.new(description, "#{actual.label} did not raise #{ExceptionType}",
|
||||
expected: ExceptionType.inspect,
|
||||
actual: exception.class.inspect
|
||||
)
|
||||
|
@ -64,32 +64,37 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
exception = capture_exception { actual.value }
|
||||
if exception.nil?
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
if exception.is_a?(ExceptionType)
|
||||
if (value = expected.value).nil?
|
||||
FailedMatchData.new("#{actual.label} raised #{exception.class}",
|
||||
FailedMatchData.new(description, "#{actual.label} raised #{exception.class}",
|
||||
expected: "Not #{ExceptionType}",
|
||||
actual: exception.class.inspect
|
||||
)
|
||||
else
|
||||
if value === exception.message
|
||||
FailedMatchData.new("#{actual.label} raised #{exception.class} with message matching #{expected.label}",
|
||||
FailedMatchData.new(description, "#{actual.label} raised #{exception.class} with message matching #{expected.label}",
|
||||
"expected type": ExceptionType.inspect,
|
||||
"actual type": exception.class.inspect,
|
||||
"expected message": value.inspect,
|
||||
"actual message": exception.message.to_s
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_message(message : T) forall T
|
||||
value = TestValue.new(message)
|
||||
ExceptionMatcher(ExceptionType, T).new(value)
|
||||
end
|
||||
|
||||
# Runs a block of code and returns the exception it threw.
|
||||
# If no exception was thrown, *nil* is returned.
|
||||
private def capture_exception
|
||||
|
|
|
@ -15,11 +15,13 @@ module Spectator::Matchers
|
|||
getter values : Array(Tuple(Symbol, String))
|
||||
|
||||
# Creates the match data.
|
||||
def initialize(@failure_message, @values)
|
||||
def initialize(description, @failure_message, @values)
|
||||
super(description)
|
||||
end
|
||||
|
||||
# Creates the match data.
|
||||
def initialize(@failure_message, **values)
|
||||
def initialize(description, @failure_message, **values)
|
||||
super(description)
|
||||
@values = values.to_a
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,13 +7,6 @@ module Spectator::Matchers
|
|||
# Each key in the tuple is a predicate (without the '?' and 'has_' prefix) to test.
|
||||
# Each value is a a `Tuple` of arguments to pass to the predicate method.
|
||||
struct HavePredicateMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
||||
# Expected value and label.
|
||||
private getter expected
|
||||
|
||||
# Creates the matcher with a expected values.
|
||||
def initialize(@expected : TestValue(ExpectedType))
|
||||
end
|
||||
|
||||
# Short text about the matcher's purpose.
|
||||
# This explains what condition satisfies the matcher.
|
||||
# The description is used when the one-liner syntax is used.
|
||||
|
@ -25,9 +18,9 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} does not have #{expected.label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} does not have #{expected.label}", **values(snapshot))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -36,9 +29,9 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
FailedMatchData.new("#{actual.label} has #{expected.label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} has #{expected.label}", **values(snapshot))
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
57
src/spectator/matchers/instance_matcher.cr
Normal file
57
src/spectator/matchers/instance_matcher.cr
Normal file
|
@ -0,0 +1,57 @@
|
|||
require "./matcher"
|
||||
|
||||
module Spectator::Matchers
|
||||
# Matcher that tests a value is of a specified type.
|
||||
struct InstanceMatcher(Expected) < StandardMatcher
|
||||
# Short text about the matcher's purpose.
|
||||
# This explains what condition satisfies the matcher.
|
||||
# The description is used when the one-liner syntax is used.
|
||||
def description : String
|
||||
"is an instance of #{Expected}"
|
||||
end
|
||||
|
||||
# Checks whether the matcher is satisifed with the expression given to it.
|
||||
private def match?(actual : TestExpression(T)) : Bool forall T
|
||||
actual.value.class == Expected
|
||||
end
|
||||
|
||||
# Message displayed when the matcher isn't satisifed.
|
||||
#
|
||||
# This is only called when `#match?` returns false.
|
||||
#
|
||||
# The message should typically only contain the test expression labels.
|
||||
# Actual values should be returned by `#values`.
|
||||
private def failure_message(actual) : String
|
||||
"#{actual.label} is not an instance of #{Expected}"
|
||||
end
|
||||
|
||||
# Message displayed when the matcher isn't satisifed and is negated.
|
||||
# This is essentially what would satisfy the matcher if it wasn't negated.
|
||||
#
|
||||
# This is only called when `#does_not_match?` returns false.
|
||||
#
|
||||
# The message should typically only contain the test expression labels.
|
||||
# Actual values should be returned by `#values`.
|
||||
private def failure_message_when_negated(actual) : String
|
||||
"#{actual.label} is an instance of #{Expected}"
|
||||
end
|
||||
|
||||
# Additional information about the match failure.
|
||||
# The return value is a NamedTuple with Strings for each value.
|
||||
private def values(actual)
|
||||
{
|
||||
expected: Expected.to_s,
|
||||
actual: actual.value.class.inspect,
|
||||
}
|
||||
end
|
||||
|
||||
# Additional information about the match failure when negated.
|
||||
# The return value is a NamedTuple with Strings for each value.
|
||||
private def negated_values(actual)
|
||||
{
|
||||
expected: "Not #{Expected}",
|
||||
actual: actual.value.class.inspect,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,5 +3,10 @@ module Spectator::Matchers
|
|||
abstract struct MatchData
|
||||
# Indicates whether the match as successful or not.
|
||||
abstract def matched? : Bool
|
||||
|
||||
getter description : String
|
||||
|
||||
def initialize(@description : String)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,9 +24,9 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} is not #{expected.label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} is not #{expected.label}", **values(snapshot))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -35,9 +35,9 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
FailedMatchData.new("#{actual.label} is #{expected.label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} is #{expected.label}", **values(snapshot))
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ module Spectator::Matchers
|
|||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual.label} does not respond to #{label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} does not respond to #{label}", **values(snapshot))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -28,9 +28,9 @@ module Spectator::Matchers
|
|||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
snapshot = snapshot_values(actual.value)
|
||||
if match?(snapshot)
|
||||
FailedMatchData.new("#{actual.label} responds to #{label}", **values(snapshot))
|
||||
FailedMatchData.new(description, "#{actual.label} responds to #{label}", **values(snapshot))
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ module Spectator::Matchers
|
|||
# Additionally, `#failure_message` and `#values` are called for a failed match.
|
||||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
if match?(actual)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new(failure_message(actual), values(actual).to_a)
|
||||
FailedMatchData.new(description, failure_message(actual), values(actual).to_a)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -39,10 +39,11 @@ module Spectator::Matchers
|
|||
# Otherwise, a `FailedMatchData` instance is returned.
|
||||
# Additionally, `#failure_message_when_negated` and `#negated_values` are called for a failed match.
|
||||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
# TODO: Invert description.
|
||||
if does_not_match?(actual)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new(failure_message_when_negated(actual), negated_values(actual).to_a)
|
||||
FailedMatchData.new(description, failure_message_when_negated(actual), negated_values(actual).to_a)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ module Spectator::Matchers
|
|||
|
||||
# Actually performs the test against the expression.
|
||||
def match(actual : TestExpression(T)) : MatchData forall T
|
||||
if (value = actual.value).responds_to?(:starts_with?)
|
||||
value = actual.value
|
||||
if value.is_a?(String) || value.responds_to?(:starts_with?)
|
||||
match_starts_with(value, actual.label)
|
||||
else
|
||||
match_first(value, actual.label)
|
||||
|
@ -32,7 +33,8 @@ module Spectator::Matchers
|
|||
# Performs the test against the expression, but inverted.
|
||||
# A successful match with `#match` should normally fail for this method, and vice-versa.
|
||||
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||
if (value = actual.value).responds_to?(:starts_with?)
|
||||
value = actual.value
|
||||
if value.is_a?(String) || value.responds_to?(:starts_with?)
|
||||
negated_match_starts_with(value, actual.label)
|
||||
else
|
||||
negated_match_first(value, actual.label)
|
||||
|
@ -43,9 +45,9 @@ module Spectator::Matchers
|
|||
# This method expects (and uses) the `#starts_with?` method on the value.
|
||||
private def match_starts_with(actual_value, actual_label)
|
||||
if actual_value.starts_with?(expected.value)
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual_label} does not start with #{expected.label} (using #starts_with?)",
|
||||
FailedMatchData.new(description, "#{actual_label} does not start with #{expected.label} (using #starts_with?)",
|
||||
expected: expected.value.inspect,
|
||||
actual: actual_value.inspect
|
||||
)
|
||||
|
@ -59,9 +61,9 @@ module Spectator::Matchers
|
|||
first = list.first
|
||||
|
||||
if expected.value === first
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual_label} does not start with #{expected.label} (using expected === first)",
|
||||
FailedMatchData.new(description, "#{actual_label} does not start with #{expected.label} (using expected === first)",
|
||||
expected: expected.value.inspect,
|
||||
actual: first.inspect,
|
||||
list: list.inspect
|
||||
|
@ -73,12 +75,12 @@ module Spectator::Matchers
|
|||
# This method expects (and uses) the `#starts_with?` method on the value.
|
||||
private def negated_match_starts_with(actual_value, actual_label)
|
||||
if actual_value.starts_with?(expected.value)
|
||||
FailedMatchData.new("#{actual_label} starts with #{expected.label} (using #starts_with?)",
|
||||
expected: expected.value.inspect,
|
||||
FailedMatchData.new(description, "#{actual_label} starts with #{expected.label} (using #starts_with?)",
|
||||
expected: "Not #{expected.value.inspect}",
|
||||
actual: actual_value.inspect
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -89,13 +91,13 @@ module Spectator::Matchers
|
|||
first = list.first
|
||||
|
||||
if expected.value === first
|
||||
FailedMatchData.new("#{actual_label} starts with #{expected.label} (using expected === first)",
|
||||
expected: expected.value.inspect,
|
||||
FailedMatchData.new(description, "#{actual_label} starts with #{expected.label} (using expected === first)",
|
||||
expected: "Not #{expected.value.inspect}",
|
||||
actual: first.inspect,
|
||||
list: list.inspect
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Spectator::Matchers
|
|||
# This explains what condition satisfies the matcher.
|
||||
# The description is used when the one-liner syntax is used.
|
||||
def description : String
|
||||
"is as #{Expected}"
|
||||
"is a #{Expected}"
|
||||
end
|
||||
|
||||
# Checks whether the matcher is satisifed with the expression given to it.
|
||||
|
|
|
@ -25,9 +25,9 @@ module Spectator::Matchers
|
|||
missing, extra = array_diff(expected_elements, actual_elements)
|
||||
|
||||
if missing.empty? && extra.empty?
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
else
|
||||
FailedMatchData.new("#{actual_label} does not contain #{expected.label} (unordered)",
|
||||
FailedMatchData.new(description, "#{actual_label} does not contain #{expected.label} (unordered)",
|
||||
expected: expected_elements.inspect,
|
||||
actual: actual_elements.inspect,
|
||||
missing: missing.inspect,
|
||||
|
@ -44,12 +44,12 @@ module Spectator::Matchers
|
|||
missing, extra = array_diff(expected_elements, actual_elements)
|
||||
|
||||
if missing.empty? && extra.empty?
|
||||
FailedMatchData.new("#{actual_label} contains #{expected.label} (unordered)",
|
||||
FailedMatchData.new(description, "#{actual_label} contains #{expected.label} (unordered)",
|
||||
expected: "Not #{expected_elements.inspect}",
|
||||
actual: actual_elements.inspect,
|
||||
)
|
||||
else
|
||||
SuccessfulMatchData.new
|
||||
SuccessfulMatchData.new(description)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,13 +17,28 @@ module Spectator::Mocks
|
|||
named = false
|
||||
name = definition.name.id
|
||||
params = definition.args
|
||||
|
||||
# Possibly a weird compiler bug, but syntax like this:
|
||||
# stub instance.==(other) { true }
|
||||
# Results in `other` being the call `other { true }`.
|
||||
# This works around the issue by pulling out the block
|
||||
# and setting the parameter to just the name.
|
||||
if params.last.is_a?(Call)
|
||||
body = params.last.block
|
||||
params[-1] = params.last.name
|
||||
end
|
||||
|
||||
args = params.map do |p|
|
||||
n = p.is_a?(TypeDeclaration) ? p.var : p.id
|
||||
r = named ? "#{n}: #{n}".id : n
|
||||
named = true if n.starts_with?('*')
|
||||
r
|
||||
end
|
||||
body = definition.block.is_a?(Nop) ? block : definition.block
|
||||
|
||||
# The unless is here because `||=` can't be used in macros @_@
|
||||
unless body
|
||||
body = definition.block.is_a?(Nop) ? block : definition.block
|
||||
end
|
||||
elsif definition.is_a?(TypeDeclaration) # stub foo : Symbol
|
||||
name = definition.var
|
||||
params = [] of MacroId
|
||||
|
|
|
@ -12,13 +12,19 @@ module Spectator::Mocks
|
|||
named = false
|
||||
name = definition.name.id
|
||||
params = definition.args
|
||||
if params.last.is_a?(Call)
|
||||
body = params.last.block
|
||||
params[-1] = params.last.name
|
||||
end
|
||||
args = params.map do |p|
|
||||
n = p.is_a?(TypeDeclaration) ? p.var : p.id
|
||||
r = named ? "#{n}: #{n}".id : n
|
||||
named = true if n.starts_with?('*')
|
||||
r
|
||||
end
|
||||
body = definition.block.is_a?(Nop) ? block : definition.block
|
||||
unless body
|
||||
body = definition.block.is_a?(Nop) ? block : definition.block
|
||||
end
|
||||
elsif definition.is_a?(TypeDeclaration) # stub foo : Symbol
|
||||
name = definition.var
|
||||
params = [] of MacroId
|
||||
|
|
|
@ -42,7 +42,7 @@ module Spectator
|
|||
# Adds an example type to the current group.
|
||||
# The class name of the example should be passed as an argument.
|
||||
# The example will be instantiated later.
|
||||
def add_example(description : String, source : Source,
|
||||
def add_example(description : String?, source : Source,
|
||||
example_type : ::SpectatorTest.class, &runner : ::SpectatorTest ->) : Nil
|
||||
builder = ->(values : TestValues) { example_type.new(values).as(::SpectatorTest) }
|
||||
factory = RunnableExampleBuilder.new(description, source, builder, runner)
|
||||
|
@ -52,7 +52,7 @@ module Spectator
|
|||
# Adds an example type to the current group.
|
||||
# The class name of the example should be passed as an argument.
|
||||
# The example will be instantiated later.
|
||||
def add_pending_example(description : String, source : Source,
|
||||
def add_pending_example(description : String?, source : Source,
|
||||
example_type : ::SpectatorTest.class, &runner : ::SpectatorTest ->) : Nil
|
||||
builder = ->(values : TestValues) { example_type.new(values).as(::SpectatorTest) }
|
||||
factory = PendingExampleBuilder.new(description, source, builder, runner)
|
||||
|
|
|
@ -6,7 +6,7 @@ module Spectator::SpecBuilder
|
|||
abstract class ExampleBuilder
|
||||
alias FactoryMethod = TestValues -> ::SpectatorTest
|
||||
|
||||
def initialize(@description : String, @source : Source, @builder : FactoryMethod, @runner : TestMethod)
|
||||
def initialize(@description : String?, @source : Source, @builder : FactoryMethod, @runner : TestMethod)
|
||||
end
|
||||
|
||||
abstract def build(group) : ExampleComponent
|
||||
|
|
|
@ -8,13 +8,19 @@ module Spectator
|
|||
# Used to instantiate tests and run them.
|
||||
struct TestWrapper
|
||||
# Description the user provided for the test.
|
||||
getter description
|
||||
def description
|
||||
@description || @source.to_s
|
||||
end
|
||||
|
||||
# Location of the test in source code.
|
||||
getter source
|
||||
|
||||
# Creates a wrapper for the test.
|
||||
def initialize(@description : String, @source : Source, @test : ::SpectatorTest, @runner : TestMethod)
|
||||
def initialize(@description : String?, @source : Source, @test : ::SpectatorTest, @runner : TestMethod)
|
||||
end
|
||||
|
||||
def description?
|
||||
!@description.nil?
|
||||
end
|
||||
|
||||
def run
|
||||
|
|
Loading…
Reference in a new issue