69 / 72 lines covered (95.8%)
1unit module BDD::Behave::Matcher;23role Matcher is export {4 method matches($actual --> Bool) { ... }5 method failure-message($actual --> Str) { Str }6 method failure-message-negated($actual --> Str) { Str }7 method expected-value(--> Mu) { Nil }8 method description(--> Str) { self.^name }910 method and($other, *@rest) {11 my @others = ($other, |@rest);12 for @others -> $m {13 unless $m ~~ Matcher {14 die "Matcher.and requires Matcher arguments";15 }16 }17 ::('BDD::Behave::Matcher::AndMatcher').new(18 :matchers((self, |@others).flat.List),19 );20 }2122 method or($other, *@rest) {23 my @others = ($other, |@rest);24 for @others -> $m {25 unless $m ~~ Matcher {26 die "Matcher.or requires Matcher arguments";27 }28 }29 ::('BDD::Behave::Matcher::OrMatcher').new(30 :matchers((self, |@others).flat.List),31 );32 }33}3435class AndMatcher does Matcher is export {36 has @.matchers;37 has Int $.failing-index is rw;3839 method matches($actual --> Bool) {40 $!failing-index = Int;41 for @!matchers.kv -> $i, $m {42 unless ?$m.matches($actual) {43 $!failing-index = $i;44 return False;45 }46 }47 True;48 }4950 method failing-matcher() {51 return Nil unless $!failing-index.defined;52 @!matchers[$!failing-index];53 }5455 method failure-message($actual --> Str) {56 my $desc = self.description;57 return "expected {$actual.raku} to {$desc}" unless $!failing-index.defined;58 my $failing = @!matchers[$!failing-index];59 my $inner = $failing.failure-message($actual);60 if $inner.defined {61 "expected {$actual.raku} to {$desc}, but {$failing.description} failed: $inner";62 } else {63 "expected {$actual.raku} to {$desc}, but {$failing.description} did not match";64 }65 }6667 method failure-message-negated($actual --> Str) {68 "expected {$actual.raku} not to {self.description}";69 }7071 method expected-value(--> Mu) {72 @!matchers.map(*.expected-value).List;73 }7475 method description(--> Str) {76 @!matchers.map(*.description).join(' and ');77 }7879 method and($other, *@rest) {80 my @others = ($other, |@rest);81 for @others -> $m {82 unless $m ~~ Matcher {83 die "Matcher.and requires Matcher arguments";84 }85 }86 AndMatcher.new(:matchers((|@!matchers, |@others).flat.List));87 }88}8990class OrMatcher does Matcher is export {91 has @.matchers;92 has Int $.matched-index is rw;9394 method matches($actual --> Bool) {95 $!matched-index = Int;96 for @!matchers.kv -> $i, $m {97 if ?$m.matches($actual) {98 $!matched-index = $i;99 return True;100 }101 }102 False;103 }104105 method matched-matcher() {106 return Nil unless $!matched-index.defined;107 @!matchers[$!matched-index];108 }109110 method failure-message($actual --> Str) {111 "expected {$actual.raku} to {self.description}, but none matched";112 }113114 method failure-message-negated($actual --> Str) {115 my $desc = self.description;116 return "expected {$actual.raku} not to {$desc}" unless $!matched-index.defined;117 my $matched = @!matchers[$!matched-index];118 "expected {$actual.raku} not to {$desc}, but {$matched.description} matched";119 }120121 method expected-value(--> Mu) {122 @!matchers.map(*.expected-value).List;123 }124125 method description(--> Str) {126 @!matchers.map(*.description).join(' or ');127 }128129 method or($other, *@rest) {130 my @others = ($other, |@rest);131 for @others -> $m {132 unless $m ~~ Matcher {133 die "Matcher.or requires Matcher arguments";134 }135 }136 OrMatcher.new(:matchers((|@!matchers, |@others).flat.List));137 }138}