From 1a7baad3382ceb6f184bde8936b6f41730217a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Sat, 7 Aug 2021 08:22:16 +0300 Subject: [PATCH 01/21] add throwsIf exception --- src/PendingObjects/TestCall.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/PendingObjects/TestCall.php b/src/PendingObjects/TestCall.php index be839bff..f8ada58d 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingObjects/TestCall.php @@ -78,6 +78,26 @@ final class TestCall return $this; } + /** + * Asserts that the test throws the given `$exceptionClass` when called if the given condition is true. + * + * @param Closure|bool|int $condition + */ + public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall + { + $condition = is_callable($condition) + ? $condition + : Closure::fromCallable(function () use ($condition): bool { + return (bool) $condition; + }); + + if ($condition() === true) { + return $this->throws($exception, $exceptionMessage); + } + + return $this; + } + /** * Runs the current test multiple times with * each item of the given `iterable`. From 8e32b88fc8f251674a86032913a70a24a3cdac02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Sat, 7 Aug 2021 08:22:26 +0300 Subject: [PATCH 02/21] add tests --- tests/Features/Exceptions.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Features/Exceptions.php b/tests/Features/Exceptions.php index 37eaaeb9..9970c2a9 100644 --- a/tests/Features/Exceptions.php +++ b/tests/Features/Exceptions.php @@ -17,3 +17,23 @@ it('catch exceptions and messages', function () { it('can just define the message', function () { throw new Exception('Something bad happened'); })->throws('Something bad happened'); + +it('not catch exceptions if given condition is false', function () { + $this->assertTrue(true); +})->throwsIf(false, Exception::class); + +it('catch exceptions if given condition is true', function () { + throw new Exception('Something bad happened'); +})->throwsIf(function () { return true; }, Exception::class); + +it('catch exceptions and messages if given condition is true', function () { + throw new Exception('Something bad happened'); +})->throwsIf(true, Exception::class, 'Something bad happened'); + +it('can just define the message if given condition is true', function () { + throw new Exception('Something bad happened'); +})->throwsIf(true, 'Something bad happened'); + +it('can just define the message if given condition is 1', function () { + throw new Exception('Something bad happened'); +})->throwsIf(1, 'Something bad happened'); From 1d4c1a5359915e8e26ff3e597d506d0fa3eb2357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Sat, 7 Aug 2021 08:22:31 +0300 Subject: [PATCH 03/21] update snapshots --- tests/.snapshots/success.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index a6f69129..90293a29 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -107,6 +107,11 @@ ✓ it catch exceptions ✓ it catch exceptions and messages ✓ it can just define the message + ✓ it not catch exceptions if given condition is false + ✓ it catch exceptions if given condition is true + ✓ it catch exceptions and messages if given condition is true + ✓ it can just define the message if given condition is true + ✓ it can just define the message if given condition is 1 PASS Tests\Features\Expect\HigherOrder\methods ✓ it can access methods @@ -647,5 +652,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 9 skipped, 419 passed + Tests: 4 incompleted, 9 skipped, 424 passed \ No newline at end of file From 12e63c7376659dc57ca33ad03b23a420b6e29499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 16 Sep 2021 18:09:45 +0300 Subject: [PATCH 04/21] add new assertion --- src/Expectation.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 253b1e1e..9ec55142 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -154,9 +154,10 @@ final class Expectation throw new BadMethodCallException('Expectation value is not iterable.'); } - $value = is_array($this->value) ? $this->value : iterator_to_array($this->value); - $keys = array_keys($value); - $values = array_values($value); + $value = is_array($this->value) ? $this->value : iterator_to_array($this->value); + $keys = array_keys($value); + $values = array_values($value); + $callbacksCount = count($callbacks); $index = 0; @@ -165,6 +166,10 @@ final class Expectation $index = $index < count($values) - 1 ? $index + 1 : 0; } + if ($callbacksCount > count($values)) { + Assert::assertLessThanOrEqual(count($value), count($callbacks)); + } + foreach ($values as $key => $item) { if (is_callable($callbacks[$key])) { call_user_func($callbacks[$key], new self($item), new self($keys[$key])); From 3dd10b3c7cc6ef24b8f6cc12db36031f5ab5cb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 16 Sep 2021 18:09:59 +0300 Subject: [PATCH 05/21] change test --- tests/Features/Expect/sequence.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Features/Expect/sequence.php b/tests/Features/Expect/sequence.php index b4f82cb5..4f11b3a3 100644 --- a/tests/Features/Expect/sequence.php +++ b/tests/Features/Expect/sequence.php @@ -1,5 +1,7 @@ each->sequence(); })->throws(BadMethodCallException::class, 'Expectation value is not iterable.'); @@ -26,16 +28,14 @@ test('loops back to the start if it runs out of sequence items', function () { expect(static::getCount())->toBe(16); }); -test('it works if the number of items in the iterable is smaller than the number of expectations', function () { +test('fails if the number of iterable items is greater than the number of expectations', function () { expect([1, 2]) ->sequence( function ($expectation) { $expectation->toBeInt()->toEqual(1); }, function ($expectation) { $expectation->toBeInt()->toEqual(2); }, function ($expectation) { $expectation->toBeInt()->toEqual(3); }, ); - - expect(static::getCount())->toBe(4); -}); +})->throws(ExpectationFailedException::class); test('it works with associative arrays', function () { expect(['foo' => 'bar', 'baz' => 'boom']) From a16a19e1214f4e9e817b775ab395dcb52e45c42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 16 Sep 2021 18:10:15 +0300 Subject: [PATCH 06/21] update snapshots --- tests/.snapshots/success.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 4442bbb9..92cfa162 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -168,7 +168,7 @@ ✓ an exception is thrown if the the type is not iterable ✓ allows for sequences of checks to be run on iterable data ✓ loops back to the start if it runs out of sequence items - ✓ it works if the number of items in the iterable is smaller than the number of expectations + ✓ fails if the number of iterable items is greater than the number of expectations ✓ it works with associative arrays ✓ it can be passed non-callable values ✓ it can be passed a mixture of value types From 847b06e5584f1fe0f4031ec334d0d9a586bc4c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 23 Sep 2021 03:35:41 +0300 Subject: [PATCH 07/21] add `when()` method --- src/Expectation.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Expectation.php b/src/Expectation.php index 253b1e1e..7a1e6057 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -177,6 +177,26 @@ final class Expectation return $this; } + /** + * It skips the tests in the callback if the condition is not truthy. + * + * @param Closure|bool|string $condition + */ + public function when($condition, callable $callback): Expectation + { + $condition = is_callable($condition) + ? $condition + : function () use ($condition) { + return $condition; + }; + + if ($condition()) { + $callback(new self($this->value)); + } + + return $this; + } + /** * Asserts that two variables have the same type and * value. Used on objects, it asserts that two From f41c3ce9ba4da273a780044110d633aa045ec4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 23 Sep 2021 03:35:46 +0300 Subject: [PATCH 08/21] add tests --- tests/Features/Expect/when.php | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/Features/Expect/when.php diff --git a/tests/Features/Expect/when.php b/tests/Features/Expect/when.php new file mode 100644 index 00000000..db9fa4f1 --- /dev/null +++ b/tests/Features/Expect/when.php @@ -0,0 +1,101 @@ +whenObject = new stdClass(); + $this->whenObject->trueValue = true; + $this->whenObject->foo = 'foo'; +}); + +it('pass', function () { + expect('foo') + ->when( + true, + function ($value) { + return $value->toEqual('foo'); + } + ) + ->toEqual('foo'); + + expect(static::getCount())->toBe(2); +}); + +it('failures', function () { + expect('foo') + ->when( + true, + function ($value) { + return $value->toBeTrue(); + } + ) + ->toEqual('foo'); +})->throws(ExpectationFailedException::class, 'is true'); + +it('runs with truthy', function () { + expect($this->whenObject) + ->when( + 1, + function ($value) { + return $value->trueValue->toBeTrue(); + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(2); +}); + +it('skips with falsy', function () { + expect($this->whenObject) + ->when( + 0, + function ($value) { + return $value->trueValue->toBeFalse(); // fails + } + ) + ->when( + false, + function ($value) { + return $value->trueValue->toBeFalse(); // fails + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(1); +}); + +it('runs with truthy closure condition', function () { + expect($this->whenObject) + ->when( + function () { return '1'; }, + function ($value) { + return $value->trueValue->toBeTrue(); + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(2); +}); + +it('skips with falsy closure condition', function () { + expect($this->whenObject) + ->when( + function () { return '0'; }, + function ($value) { + return $value->trueValue->toBeFalse(); // fails + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(1); +}); + +it('can be used in higher order tests') + ->expect(false) + ->when( + function () { return true; }, + function ($value) { + return $value->toBeTrue(); + } + ) + ->throws(ExpectationFailedException::class, 'false is true'); From 1681c1f4f81790b6adcc9474ad6937b3e55f9242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 23 Sep 2021 03:35:56 +0300 Subject: [PATCH 09/21] update snapshots --- tests/.snapshots/success.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 4442bbb9..328c8e9f 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -481,6 +481,15 @@ ✓ closure missing parameter ✓ closure missing type-hint + PASS Tests\Features\Expect\when + ✓ it pass + ✓ it failures + ✓ it runs with truthy + ✓ it skips with falsy + ✓ it runs with truthy closure condition + ✓ it skips with falsy closure condition + ✓ it can be used in higher order tests + PASS Tests\Features\Helpers ✓ it can set/get properties on $this ✓ it throws error if property do not exist @@ -681,5 +690,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 9 skipped, 447 passed + Tests: 4 incompleted, 9 skipped, 454 passed \ No newline at end of file From 543b9542ae56e4e2a0661561cc02e3f651519a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 23 Sep 2021 06:55:28 +0300 Subject: [PATCH 10/21] add new `match()` method --- src/Expectation.php | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/Expectation.php b/src/Expectation.php index 253b1e1e..1d8403ac 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -177,6 +177,56 @@ final class Expectation return $this; } + /** + * If the subject matches one of the expressions, the callback in the expression is run. + * + * @template TMatchValue + * + * @param Closure|bool|string $subject + * @param array $expressions + * + * @return \Pest\Expectation + */ + public function match($subject, array $expressions): Expectation + { + $subject = is_callable($subject) + ? $subject + : function () use ($subject) { + return $subject; + }; + + $subject = $subject(); + $keys = array_keys($expressions); + $matched = false; + + if (in_array($subject, ['0', '1', false, true], true)) { + $subject = (int) $subject; + } + + foreach ($expressions as $key => $callback) { + if ($subject !== $key) { + continue; + } + + $matched = true; + + if (is_callable($callback)) { + $callback(new self($this->value)); + continue; + } + + (new self($this->value))->toEqual($callback); + + break; + } + + if (!$matched) { + test()->addWarning('No item found matching "' . $subject . '".'); + } + + return $this; + } + /** * Asserts that two variables have the same type and * value. Used on objects, it asserts that two From f6131d042b53761b1c827bb811c74d20e8e97314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 23 Sep 2021 06:56:47 +0300 Subject: [PATCH 11/21] add tests The filename is not accepted as `match.php` --- tests/Features/Expect/matchExpectation.php | 160 +++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/Features/Expect/matchExpectation.php diff --git a/tests/Features/Expect/matchExpectation.php b/tests/Features/Expect/matchExpectation.php new file mode 100644 index 00000000..ba397b11 --- /dev/null +++ b/tests/Features/Expect/matchExpectation.php @@ -0,0 +1,160 @@ +matched = null; +}); + +it('pass', function () { + expect('baz') + ->match('foo', [ + 'bar' => function ($value) { + $this->matched = 'bar'; + + return $value->toEqual('bar'); + }, + 'foo' => function ($value) { + $this->matched = 'baz'; + + return $value->toEqual('baz'); + }, + ] + ) + ->toEqual($this->matched); + + expect(static::getCount())->toBe(2); +}); + +it('failures', function () { + expect(true) + ->match('foo', [ + 'bar' => function ($value) { + return $value->toBeTrue(); + }, + 'foo' => function ($value) { + return $value->toBeFalse(); + }, + ] + ); +})->throws(ExpectationFailedException::class, 'true is false'); + +it('adds a warning if no match is found', function () { + expect(true) + ->match('bar', [ + 'foo' => function ($value) { + return $value->toBeFalse(); + }, + ] + ); +}); + +it('runs with truthy', function () { + expect('foo') + ->match(1, [ + 'bar' => function ($value) { + $this->matched = 'bar'; + + return $value->toEqual('bar'); + }, + true => function ($value) { + $this->matched = 'foo'; + + return $value->toEqual('foo'); + }, + ] + ) + ->toEqual($this->matched); + + expect(static::getCount())->toBe(2); +}); + +it('runs with falsy', function () { + expect('foo') + ->match(false, [ + 'bar' => function ($value) { + $this->matched = 'bar'; + + return $value->toEqual('bar'); + }, + false => function ($value) { + $this->matched = 'foo'; + + return $value->toEqual('foo'); + }, + ] + ) + ->toEqual($this->matched); + + expect(static::getCount())->toBe(2); +}); + +it('runs with truthy closure condition', function () { + expect('foo') + ->match( + function () { return '1'; }, [ + 'bar' => function ($value) { + $this->matched = 'bar'; + + return $value->toEqual('bar'); + }, + true => function ($value) { + $this->matched = 'foo'; + + return $value->toEqual('foo'); + }, + ] + ) + ->toEqual($this->matched); + + expect(static::getCount())->toBe(2); +}); + +it('runs with falsy closure condition', function () { + expect('foo') + ->match( + function () { return '0'; }, [ + 'bar' => function ($value) { + $this->matched = 'bar'; + + return $value->toEqual('bar'); + }, + false => function ($value) { + $this->matched = 'foo'; + + return $value->toEqual('foo'); + }, + ] + ) + ->toEqual($this->matched); + + expect(static::getCount())->toBe(2); +}); + +it('can be passed non-callable values', function () { + expect('foo') + ->match('pest', [ + 'bar' => 'foo', + 'pest' => 'baz', + ] + ); +})->throws(ExpectationFailedException::class, 'two strings are equal'); + +it('passes with empty data', function () { + expect('foo') + ->match('bar', []) + ->toEqual('foo'); +}); + +it('can be used in higher order tests') + ->expect(true) + ->match( + function () { return true; }, [ + false => function ($value) { + return $value->toBeFalse(); + }, + true => function ($value) { + return $value->toBeTrue(); + }, + ] + ); From fec11928cfaa5238f2f068f26e470e64b50836fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 23 Sep 2021 06:57:07 +0300 Subject: [PATCH 12/21] update snapshots --- tests/.snapshots/success.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 4442bbb9..c360b31e 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -158,6 +158,18 @@ ✓ it properly parses json string ✓ fails with broken json string + WARN Tests\Features\Expect\matchExpectation + ✓ it pass + ✓ it failures + ! it adds a warning if no match is found → No item found matching "bar". + ✓ it runs with truthy + ✓ it runs with falsy + ✓ it runs with truthy closure condition + ✓ it runs with falsy closure condition + ✓ it can be passed non-callable values + ! it passes with empty data → No item found matching "bar". + ✓ it can be used in higher order tests + PASS Tests\Features\Expect\not ✓ not property calls @@ -681,5 +693,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 9 skipped, 447 passed + Tests: 2 warnings, 4 incompleted, 9 skipped, 455 passed \ No newline at end of file From da258fa89f0f2f78f472ed39e0ed42d1f2b6868d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Fri, 24 Sep 2021 13:55:16 +0300 Subject: [PATCH 13/21] remove the warning --- src/Expectation.php | 7 ------- tests/Features/Expect/matchExpectation.php | 10 ---------- 2 files changed, 17 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 1d8403ac..f903bbd6 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -197,7 +197,6 @@ final class Expectation $subject = $subject(); $keys = array_keys($expressions); - $matched = false; if (in_array($subject, ['0', '1', false, true], true)) { $subject = (int) $subject; @@ -208,8 +207,6 @@ final class Expectation continue; } - $matched = true; - if (is_callable($callback)) { $callback(new self($this->value)); continue; @@ -220,10 +217,6 @@ final class Expectation break; } - if (!$matched) { - test()->addWarning('No item found matching "' . $subject . '".'); - } - return $this; } diff --git a/tests/Features/Expect/matchExpectation.php b/tests/Features/Expect/matchExpectation.php index ba397b11..f577cedc 100644 --- a/tests/Features/Expect/matchExpectation.php +++ b/tests/Features/Expect/matchExpectation.php @@ -39,16 +39,6 @@ it('failures', function () { ); })->throws(ExpectationFailedException::class, 'true is false'); -it('adds a warning if no match is found', function () { - expect(true) - ->match('bar', [ - 'foo' => function ($value) { - return $value->toBeFalse(); - }, - ] - ); -}); - it('runs with truthy', function () { expect('foo') ->match(1, [ From 7f214f9e129579bb60237582a63fca6585848b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Fri, 24 Sep 2021 13:59:00 +0300 Subject: [PATCH 14/21] use `and()` instead of `new self` --- src/Expectation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Expectation.php b/src/Expectation.php index f903bbd6..641b09e4 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -212,7 +212,7 @@ final class Expectation continue; } - (new self($this->value))->toEqual($callback); + $this->and($this->value)->toEqual($callback); break; } From 8d96f975e002b8a959935409f99cf6e81202376a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Fri, 24 Sep 2021 14:37:01 +0300 Subject: [PATCH 15/21] update snapshots --- tests/.snapshots/success.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index c360b31e..082bdcce 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -158,16 +158,15 @@ ✓ it properly parses json string ✓ fails with broken json string - WARN Tests\Features\Expect\matchExpectation + PASS Tests\Features\Expect\matchExpectation ✓ it pass ✓ it failures - ! it adds a warning if no match is found → No item found matching "bar". ✓ it runs with truthy ✓ it runs with falsy ✓ it runs with truthy closure condition ✓ it runs with falsy closure condition ✓ it can be passed non-callable values - ! it passes with empty data → No item found matching "bar". + ✓ it passes with empty data ✓ it can be used in higher order tests PASS Tests\Features\Expect\not @@ -693,5 +692,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 2 warnings, 4 incompleted, 9 skipped, 455 passed + Tests: 4 incompleted, 9 skipped, 456 passed \ No newline at end of file From c99f8f196e162086ac47c1f44d04cd3acaf7297d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Fri, 24 Sep 2021 14:42:26 +0300 Subject: [PATCH 16/21] lint --- src/Expectation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Expectation.php b/src/Expectation.php index 68336471..2e581802 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -219,7 +219,7 @@ final class Expectation return $this; } - + /** * It skips the tests in the callback if the condition is not truthy. * From 3c3e6b160bbfd9f040e4b601fbae369588c7e8e7 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 24 Sep 2021 21:10:02 +0100 Subject: [PATCH 17/21] refactor: expectation when --- src/Expectation.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 7a1e6057..d39d0904 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -178,15 +178,16 @@ final class Expectation } /** - * It skips the tests in the callback if the condition is not truthy. + * Apply the callback if the given "condition" is truthy. * - * @param Closure|bool|string $condition + * @param (callable(): bool)|bool $condition + * @param callable(Expectation): mixed $callback */ public function when($condition, callable $callback): Expectation { $condition = is_callable($condition) ? $condition - : function () use ($condition) { + : static function () use ($condition): mixed { return $condition; }; From 4daf7ee4ab4004e51d385a0ccc1d33efe6db0027 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 24 Sep 2021 21:15:31 +0100 Subject: [PATCH 18/21] refactor: throwsIf method --- src/PendingObjects/TestCall.php | 10 +++++----- tests/.snapshots/success.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PendingObjects/TestCall.php b/src/PendingObjects/TestCall.php index f8ada58d..287ca7a9 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingObjects/TestCall.php @@ -81,17 +81,17 @@ final class TestCall /** * Asserts that the test throws the given `$exceptionClass` when called if the given condition is true. * - * @param Closure|bool|int $condition + * @param (callable(): bool)|bool $condition */ public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall { $condition = is_callable($condition) ? $condition - : Closure::fromCallable(function () use ($condition): bool { - return (bool) $condition; - }); + : static function () use ($condition): mixed { + return $condition; + }; - if ($condition() === true) { + if ($condition()) { return $this->throws($exception, $exceptionMessage); } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index cac11672..40ab0a49 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -695,5 +695,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 9 skipped, 454 passed + Tests: 4 incompleted, 9 skipped, 459 passed \ No newline at end of file From ae029660e3e05905a106fa2a2b65e78718352f53 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 24 Sep 2021 21:18:30 +0100 Subject: [PATCH 19/21] tests: fixes snapshots --- tests/.snapshots/success.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 2e201e93..c1597844 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -706,5 +706,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 9 skipped, 459 passed + Tests: 4 incompleted, 9 skipped, 468 passed \ No newline at end of file From 457972716f5208f34ec9d381befefdd8722e863c Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 24 Sep 2021 22:02:18 +0100 Subject: [PATCH 20/21] refactor: expectation `match` method --- src/Expectation.php | 23 +++++++++++----------- tests/.snapshots/success.txt | 2 +- tests/Features/Expect/matchExpectation.php | 8 +++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index 89d86f53..a45c8892 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -178,14 +178,12 @@ final class Expectation } /** - * If the subject matches one of the expressions, the callback in the expression is run. + * If the subject matches one of the given "expressions", the expression callback will run. * - * @template TMatchValue + * @template TMatchSubject of array-key * - * @param Closure|bool|string $subject - * @param array $expressions - * - * @return \Pest\Expectation + * @param callable(): TMatchSubject|TMatchSubject $subject + * @param array): mixed)|TValue> $expressions */ public function match($subject, array $expressions): Expectation { @@ -196,17 +194,16 @@ final class Expectation }; $subject = $subject(); - $keys = array_keys($expressions); - if (in_array($subject, ['0', '1', false, true], true)) { - $subject = (int) $subject; - } + $matched = false; foreach ($expressions as $key => $callback) { - if ($subject !== $key) { + if ($subject != $key) { continue; } + $matched = true; + if (is_callable($callback)) { $callback(new self($this->value)); continue; @@ -217,6 +214,10 @@ final class Expectation break; } + if ($matched === false) { + throw new ExpectationFailedException('Unhandled match value.'); + } + return $this; } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index c1597844..175f10e8 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -171,7 +171,7 @@ ✓ it runs with truthy closure condition ✓ it runs with falsy closure condition ✓ it can be passed non-callable values - ✓ it passes with empty data + ✓ it fails with unhandled match ✓ it can be used in higher order tests PASS Tests\Features\Expect\not diff --git a/tests/Features/Expect/matchExpectation.php b/tests/Features/Expect/matchExpectation.php index f577cedc..2bea1be5 100644 --- a/tests/Features/Expect/matchExpectation.php +++ b/tests/Features/Expect/matchExpectation.php @@ -130,11 +130,9 @@ it('can be passed non-callable values', function () { ); })->throws(ExpectationFailedException::class, 'two strings are equal'); -it('passes with empty data', function () { - expect('foo') - ->match('bar', []) - ->toEqual('foo'); -}); +it('fails with unhandled match', function () { + expect('foo')->match('bar', []); +})->throws(ExpectationFailedException::class, 'Unhandled match value.'); it('can be used in higher order tests') ->expect(true) From b43a59868d5b790a28cbb29c6110c9f068b0b812 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 24 Sep 2021 22:12:08 +0100 Subject: [PATCH 21/21] feat: adds `unless` expectation --- src/Expectation.php | 19 +++++- tests/.snapshots/success.txt | 11 +++- tests/Features/Expect/unless.php | 101 +++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 tests/Features/Expect/unless.php diff --git a/src/Expectation.php b/src/Expectation.php index a45c8892..13982e59 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -221,6 +221,23 @@ final class Expectation return $this; } + /** + * Apply the callback if the given "condition" is falsy. + * + * @param (callable(): bool)|bool $condition + * @param callable(Expectation): mixed $callback + */ + public function unless($condition, callable $callback): Expectation + { + $condition = is_callable($condition) + ? $condition + : static function () use ($condition): mixed { + return $condition; + }; + + return $this->when(!$condition(), $callback); + } + /** * Apply the callback if the given "condition" is truthy. * @@ -236,7 +253,7 @@ final class Expectation }; if ($condition()) { - $callback(new self($this->value)); + $callback($this->and($this->value)); } return $this; diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 175f10e8..7a0e7d64 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -497,6 +497,15 @@ ✓ closure missing parameter ✓ closure missing type-hint + PASS Tests\Features\Expect\unless + ✓ it pass + ✓ it failures + ✓ it runs with truthy + ✓ it skips with falsy + ✓ it runs with truthy closure condition + ✓ it skips with falsy closure condition + ✓ it can be used in higher order tests + PASS Tests\Features\Expect\when ✓ it pass ✓ it failures @@ -706,5 +715,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 9 skipped, 468 passed + Tests: 4 incompleted, 9 skipped, 475 passed \ No newline at end of file diff --git a/tests/Features/Expect/unless.php b/tests/Features/Expect/unless.php new file mode 100644 index 00000000..9f11c7ef --- /dev/null +++ b/tests/Features/Expect/unless.php @@ -0,0 +1,101 @@ +unlessObject = new stdClass(); + $this->unlessObject->trueValue = true; + $this->unlessObject->foo = 'foo'; +}); + +it('pass', function () { + expect('foo') + ->unless( + true, + function ($value) { + return $value->toEqual('bar'); + } + ) + ->toEqual('foo'); + + expect(static::getCount())->toBe(1); +}); + +it('failures', function () { + expect('foo') + ->unless( + false, + function ($value) { + return $value->toBeTrue(); + } + ) + ->toEqual('foo'); +})->throws(ExpectationFailedException::class, 'is true'); + +it('runs with truthy', function () { + expect($this->unlessObject) + ->unless( + 0, + function ($value) { + return $value->trueValue->toBeTrue(); + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(2); +}); + +it('skips with falsy', function () { + expect($this->unlessObject) + ->unless( + 1, + function ($value) { + return $value->trueValue->toBeFalse(); // fails + } + ) + ->unless( + true, + function ($value) { + return $value->trueValue->toBeFalse(); // fails + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(1); +}); + +it('runs with truthy closure condition', function () { + expect($this->unlessObject) + ->unless( + function () { return '0'; }, + function ($value) { + return $value->trueValue->toBeTrue(); + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(2); +}); + +it('skips with falsy closure condition', function () { + expect($this->unlessObject) + ->unless( + function () { return '1'; }, + function ($value) { + return $value->trueValue->toBeFalse(); // fails + } + ) + ->foo->toEqual('foo'); + + expect(static::getCount())->toBe(1); +}); + +it('can be used in higher order tests') + ->expect(true) + ->unless( + function () { return false; }, + function ($value) { + return $value->toBeFalse(); + } + ) + ->throws(ExpectationFailedException::class, 'true is false');