From 4f67eff619759caa6ae21ead5eff7bb644dd7940 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 24 Jun 2021 21:38:02 +0100 Subject: [PATCH 1/6] Start of work --- tests/Features/HigherOrderTests.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Features/HigherOrderTests.php b/tests/Features/HigherOrderTests.php index 77e87ce4..d01a18e5 100644 --- a/tests/Features/HigherOrderTests.php +++ b/tests/Features/HigherOrderTests.php @@ -8,4 +8,7 @@ it('is capable doing multiple assertions') ->assertTrue(true) ->assertFalse(false); +//it('can tap into the test') + + afterEach()->assertTrue(true); From acef002a2d53fc0b1c810fb6e217883955920d2f Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 24 Jun 2021 22:57:26 +0100 Subject: [PATCH 2/6] Adds `tap` for Higher Order tests --- src/Support/HigherOrderCallables.php | 33 ++++++++++++++++++++++++++++ src/Support/HigherOrderMessage.php | 19 ++++++++++++++++ src/Support/Reflection.php | 16 +++++++++++--- tests/.snapshots/success.txt | 4 +++- tests/Features/HigherOrderTests.php | 11 +++++++++- 5 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 src/Support/HigherOrderCallables.php diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php new file mode 100644 index 00000000..08231c76 --- /dev/null +++ b/src/Support/HigherOrderCallables.php @@ -0,0 +1,33 @@ +target = $target; + } + + /** + * @template TValue + * + * @param callable(): TValue $callable + * + * @return TValue|object + */ + public function tap(callable $callable) + { + return Reflection::bindCallable($callable) ?? $this->target; + } +} diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 9f363b5a..5c5ddd07 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -70,6 +70,10 @@ final class HigherOrderMessage */ public function call(object $target) { + if (($value = $this->retrieveHigherOrderCallable($target)) !== null) { + return $value; + } + try { return Reflection::call($target, $this->methodName, $this->arguments); } catch (Throwable $throwable) { @@ -88,6 +92,21 @@ final class HigherOrderMessage } } + /** + * Attempts to call one of the available Higher Order callables if it exists. + * + * @return mixed|null + */ + private function retrieveHigherOrderCallable(object $target) + { + if (in_array($this->methodName, get_class_methods(HigherOrderCallables::class), true)) { + /* @phpstan-ignore-next-line */ + return (new HigherOrderCallables($target))->{$this->methodName}(...$this->arguments); + } + + return null; + } + private static function getUndefinedMethodMessage(object $target, string $methodName): string { if (\PHP_MAJOR_VERSION >= 8) { diff --git a/src/Support/Reflection.php b/src/Support/Reflection.php index c25709b9..fbe7ba40 100644 --- a/src/Support/Reflection.php +++ b/src/Support/Reflection.php @@ -41,15 +41,25 @@ final class Reflection } if (is_callable($method)) { - return Closure::fromCallable($method)->bindTo( - TestSuite::getInstance()->test - )(...$args); + return static::bindCallable($method, $args); } throw $exception; } } + /** + * Bind a callable to the TestCase and return the result. + * + * @param array $args + * + * @return mixed + */ + public static function bindCallable(callable $callable, array $args = []) + { + return Closure::fromCallable($callable)->bindTo(TestSuite::getInstance()->test)(...$args); + } + /** * Infers the file name from the given closure. */ diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index e7ecc1a0..36099b43 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -409,6 +409,8 @@ PASS Tests\Features\HigherOrderTests ✓ it proxies calls to object ✓ it is capable doing multiple assertions + ✓ it can tap into the test + ✓ it can use the returned instance from a tap WARN Tests\Features\Incompleted … incompleted @@ -578,5 +580,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 7 skipped, 362 passed + Tests: 4 incompleted, 7 skipped, 364 passed \ No newline at end of file diff --git a/tests/Features/HigherOrderTests.php b/tests/Features/HigherOrderTests.php index d01a18e5..ea5b80e1 100644 --- a/tests/Features/HigherOrderTests.php +++ b/tests/Features/HigherOrderTests.php @@ -1,5 +1,7 @@ assertTrue(true); it('proxies calls to object')->assertTrue(true); @@ -8,7 +10,14 @@ it('is capable doing multiple assertions') ->assertTrue(true) ->assertFalse(false); -//it('can tap into the test') +it('can tap into the test') + ->expect('foo')->toBeString()->toBe('foo') + ->tap(function () { expect($this)->toBeInstanceOf(TestCase::class); }) + ->and('hello world')->toBeString(); +it('can use the returned instance from a tap') + ->expect('foo')->toBeString()->toBe('foo') + ->tap(function () { return expect($this); }) + ->toBeInstanceOf(TestCase::class); afterEach()->assertTrue(true); From f2e56da2da9d88d6851b562fe2b4e113bb93eda9 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 24 Jun 2021 22:58:29 +0100 Subject: [PATCH 3/6] Updates snapshots --- tests/.snapshots/success.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 9b0a5c44..96cd47ea 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -410,6 +410,8 @@ PASS Tests\Features\HigherOrderTests ✓ it proxies calls to object ✓ it is capable doing multiple assertions + ✓ it can tap into the test + ✓ it can use the returned instance from a tap WARN Tests\Features\Incompleted … incompleted @@ -579,5 +581,5 @@ ✓ it is a test ✓ it uses correct parent class - Tests: 4 incompleted, 7 skipped, 363 passed + Tests: 4 incompleted, 7 skipped, 365 passed \ No newline at end of file From c290909eb3ea47544cdfae6e1119492b1a0699a6 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 25 Jun 2021 09:24:16 +0100 Subject: [PATCH 4/6] Adds @mixin for HigherOrderCallables class --- src/PendingObjects/TestCall.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PendingObjects/TestCall.php b/src/PendingObjects/TestCall.php index 39486565..69227aa6 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingObjects/TestCall.php @@ -11,7 +11,8 @@ use Pest\Support\NullClosure; use Pest\TestSuite; use SebastianBergmann\Exporter\Exporter; -/** +/* + * @mixin \Pest\Support\HigherOrderCallables * @method \Pest\Expectations\Expectation expect(mixed $value) * * @internal From 7e9edecc7f0a0ea369650161735b35df310bcd0e Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 6 Jul 2021 14:05:40 +0100 Subject: [PATCH 5/6] Higher Order Tests now resolve callable expectations. The `tap` method now always returns the test case. --- src/PendingObjects/TestCall.php | 8 +++---- src/Plugins/Coverage.php | 1 - src/Support/HigherOrderCallables.php | 34 +++++++++++++++++++++++++++- tests/.snapshots/success.txt | 2 +- tests/Features/HigherOrderTests.php | 16 ++++++++----- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/PendingObjects/TestCall.php b/src/PendingObjects/TestCall.php index 69227aa6..40574ed1 100644 --- a/src/PendingObjects/TestCall.php +++ b/src/PendingObjects/TestCall.php @@ -7,15 +7,15 @@ namespace Pest\PendingObjects; use Closure; use Pest\Factories\TestCaseFactory; use Pest\Support\Backtrace; +use Pest\Support\HigherOrderCallables; use Pest\Support\NullClosure; use Pest\TestSuite; use SebastianBergmann\Exporter\Exporter; -/* - * @mixin \Pest\Support\HigherOrderCallables - * @method \Pest\Expectations\Expectation expect(mixed $value) - * +/** * @internal + * + * @mixin HigherOrderCallables */ final class TestCall { diff --git a/src/Plugins/Coverage.php b/src/Plugins/Coverage.php index cc03bd17..6d993717 100644 --- a/src/Plugins/Coverage.php +++ b/src/Plugins/Coverage.php @@ -81,7 +81,6 @@ final class Coverage implements AddsOutput, HandlesArguments } if ($input->getOption(self::MIN_OPTION) !== null) { - /* @phpstan-ignore-next-line */ $this->coverageMin = (float) $input->getOption(self::MIN_OPTION); } diff --git a/src/Support/HigherOrderCallables.php b/src/Support/HigherOrderCallables.php index 08231c76..46503e67 100644 --- a/src/Support/HigherOrderCallables.php +++ b/src/Support/HigherOrderCallables.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Pest\Support; +use Pest\Expectation; + /** * @internal */ @@ -19,6 +21,34 @@ final class HigherOrderCallables $this->target = $target; } + /** + * @template TValue + * + * Create a new expectation. Callable values will be executed prior to returning the new expectation. + * + * @param callable|TValue $value + * + * @return Expectation + */ + public function expect($value) + { + return new Expectation(is_callable($value) ? Reflection::bindCallable($value) : $value); + } + + /** + * @template TValue + * + * Create a new expectation. Callable values will be executed prior to returning the new expectation. + * + * @param callable|TValue $value + * + * @return Expectation + */ + public function and($value) + { + return $this->expect($value); + } + /** * @template TValue * @@ -28,6 +58,8 @@ final class HigherOrderCallables */ public function tap(callable $callable) { - return Reflection::bindCallable($callable) ?? $this->target; + Reflection::bindCallable($callable); + + return $this->target; } } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 96cd47ea..d943da92 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -410,8 +410,8 @@ PASS Tests\Features\HigherOrderTests ✓ it proxies calls to object ✓ it is capable doing multiple assertions + ✓ it resolves expect callables correctly ✓ it can tap into the test - ✓ it can use the returned instance from a tap WARN Tests\Features\Incompleted … incompleted diff --git a/tests/Features/HigherOrderTests.php b/tests/Features/HigherOrderTests.php index ea5b80e1..f871d919 100644 --- a/tests/Features/HigherOrderTests.php +++ b/tests/Features/HigherOrderTests.php @@ -10,14 +10,18 @@ it('is capable doing multiple assertions') ->assertTrue(true) ->assertFalse(false); +it('resolves expect callables correctly') + ->expect(function () { return 'foo'; }) + ->toBeString() + ->toBe('foo') + ->and('bar') + ->toBeString() + ->toBe('bar'); + it('can tap into the test') - ->expect('foo')->toBeString()->toBe('foo') + ->expect('foo')->toBeString() ->tap(function () { expect($this)->toBeInstanceOf(TestCase::class); }) + ->toBe('foo') ->and('hello world')->toBeString(); -it('can use the returned instance from a tap') - ->expect('foo')->toBeString()->toBe('foo') - ->tap(function () { return expect($this); }) - ->toBeInstanceOf(TestCase::class); - afterEach()->assertTrue(true); From e45c4ff4f1daaeb1b027c04fcfaf4591158f4034 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 8 Jul 2021 17:30:39 +0100 Subject: [PATCH 6/6] Refactors HigherOrderMessage --- src/Support/HigherOrderMessage.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 5c5ddd07..2a51b7ba 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -70,8 +70,9 @@ final class HigherOrderMessage */ public function call(object $target) { - if (($value = $this->retrieveHigherOrderCallable($target)) !== null) { - return $value; + if ($this->hasHigherOrderCallable()) { + /* @phpstan-ignore-next-line */ + return (new HigherOrderCallables($target))->{$this->methodName}(...$this->arguments); } try { @@ -93,18 +94,13 @@ final class HigherOrderMessage } /** - * Attempts to call one of the available Higher Order callables if it exists. + * Determines whether or not there exists a higher order callable with the message name. * - * @return mixed|null + * @return bool */ - private function retrieveHigherOrderCallable(object $target) + private function hasHigherOrderCallable() { - if (in_array($this->methodName, get_class_methods(HigherOrderCallables::class), true)) { - /* @phpstan-ignore-next-line */ - return (new HigherOrderCallables($target))->{$this->methodName}(...$this->arguments); - } - - return null; + return in_array($this->methodName, get_class_methods(HigherOrderCallables::class), true); } private static function getUndefinedMethodMessage(object $target, string $methodName): string