From 3e8616ec647ce0e101892cc3d31d4e6ffc758b10 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 26 May 2023 19:29:46 +0100 Subject: [PATCH] feat(describe): continues work around hooks --- src/Concerns/Testable.php | 6 +- src/Functions.php | 11 +-- src/PendingCalls.php | 91 +---------------------- src/PendingCalls/AfterEachCall.php | 29 ++++---- src/PendingCalls/BeforeEachCall.php | 41 +++++----- src/PendingCalls/DescribeCall.php | 38 ++++++++-- src/PendingCalls/TestCall.php | 10 ++- src/Repositories/AfterEachRepository.php | 4 +- src/Repositories/BeforeEachRepository.php | 4 +- src/Support/ChainableClosure.php | 13 +++- tests/Features/Describe.php | 57 +++++++++----- 11 files changed, 132 insertions(+), 172 deletions(-) diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 48da241f..9c1f716d 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -141,7 +141,7 @@ trait Testable } $this->{$property} = ($this->{$property} instanceof Closure) - ? ChainableClosure::from($this->{$property}, $hook) + ? ChainableClosure::fromSameObject($this->{$property}, $hook) : $hook; } @@ -189,7 +189,7 @@ trait Testable $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1]; if ($this->__beforeEach instanceof Closure) { - $beforeEach = ChainableClosure::from($this->__beforeEach, $beforeEach); + $beforeEach = ChainableClosure::fromSameObject($this->__beforeEach, $beforeEach); } $this->__callClosure($beforeEach, func_get_args()); @@ -203,7 +203,7 @@ trait Testable $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); if ($this->__afterEach instanceof Closure) { - $afterEach = ChainableClosure::from($this->__afterEach, $afterEach); + $afterEach = ChainableClosure::fromSameObject($this->__afterEach, $afterEach); } $this->__callClosure($afterEach, func_get_args()); diff --git a/src/Functions.php b/src/Functions.php index d9b15fdf..03fa778f 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -5,7 +5,6 @@ declare(strict_types=1); use Pest\Exceptions\AfterAllWithinDescribe; use Pest\Exceptions\BeforeAllWithinDescribe; use Pest\Expectation; -use Pest\PendingCalls; use Pest\PendingCalls\AfterEachCall; use Pest\PendingCalls\BeforeEachCall; use Pest\PendingCalls\DescribeCall; @@ -39,7 +38,7 @@ if (! function_exists('beforeAll')) { */ function beforeAll(Closure $closure): void { - if (! is_null(PendingCalls::$describing)) { + if (! is_null(DescribeCall::describing())) { $filename = Backtrace::file(); throw new BeforeAllWithinDescribe($filename); @@ -89,11 +88,7 @@ if (! function_exists('describe')) { { $filename = Backtrace::testFile(); - PendingCalls::startDescribe( - $describeCall = new DescribeCall(TestSuite::getInstance(), $filename, $description, $tests), - ); - - return $describeCall; + return new DescribeCall(TestSuite::getInstance(), $filename, $description, $tests); } } @@ -189,7 +184,7 @@ if (! function_exists('afterAll')) { */ function afterAll(Closure $closure): void { - if (! is_null(PendingCalls::$describing)) { + if (! is_null(DescribeCall::describing())) { $filename = Backtrace::file(); throw new AfterAllWithinDescribe($filename); diff --git a/src/PendingCalls.php b/src/PendingCalls.php index 1fba1c55..886166bf 100644 --- a/src/PendingCalls.php +++ b/src/PendingCalls.php @@ -4,11 +4,7 @@ declare(strict_types=1); namespace Pest; -use Closure; -use Pest\PendingCalls\AfterEachCall; -use Pest\PendingCalls\BeforeEachCall; use Pest\PendingCalls\DescribeCall; -use Pest\PendingCalls\TestCall; /** * @internal @@ -20,96 +16,11 @@ final class PendingCalls */ public static ?string $describing = null; - /** - * The list of before each pending calls. - * - * @var array - */ - public static array $beforeEachCalls = []; - - /** - * The list of test pending calls. - * - * @var array - */ - public static array $testCalls = []; - - /** - * The list of after each pending calls. - * - * @var array - */ - public static array $afterEachCalls = []; - /** * Sets the current describe call. */ - public static function startDescribe(DescribeCall $describeCall): void + public static function describe(DescribeCall $describeCall): void { - self::$describing = $describeCall->description; - ($describeCall->tests)(); - } - - /** - * Adds a new before each call. - */ - public static function beforeEach(BeforeEachCall $beforeEachCall, Closure $setter): void - { - $setter($beforeEachCall->describing = self::$describing); - } - - /** - * Adds a new test call. - */ - public static function test(TestCall $testCall, Closure $setter): void - { - if (! is_null($testCall->describing = self::$describing)) { - self::$testCalls[] = [$testCall, $setter]; - } else { - $setter(); - } - } - - /** - * Adds a new before each call. - */ - public static function afterEach(AfterEachCall $afterEachCall, Closure $setter): void - { - if (! is_null(self::$describing)) { - $afterEachCall->describing = self::$describing; - - self::$afterEachCalls[] = [$afterEachCall, $setter]; - } else { - $setter(); - } - } - - public static function endDescribe(DescribeCall $describeCall): void - { - $describing = self::$describing; - - self::$describing = null; - - foreach (self::$beforeEachCalls as [$beforeEachCall, $setter]) { - $setter($describing); - } - - self::$beforeEachCalls = []; - - foreach (self::$testCalls as [$testCall, $setter]) { - /** @var TestCall $testCall */ - $testCall->testCaseMethod->description = '`'.$describeCall->description.'` '.$testCall->testCaseMethod->description; - - $setter($describing); - } - - self::$testCalls = []; - - foreach (self::$afterEachCalls as [$afterEachCall, $setter]) { - $setter($describing); - } - - self::$afterEachCalls = []; } } diff --git a/src/PendingCalls/AfterEachCall.php b/src/PendingCalls/AfterEachCall.php index 55da4381..5388eedf 100644 --- a/src/PendingCalls/AfterEachCall.php +++ b/src/PendingCalls/AfterEachCall.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Pest\PendingCalls; use Closure; -use Pest\PendingCalls; use Pest\PendingCalls\Concerns\Describable; use Pest\Support\Backtrace; use Pest\Support\ChainableClosure; @@ -41,6 +40,8 @@ final class AfterEachCall $this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); $this->proxies = new HigherOrderMessageCollection(); + + $this->describing = DescribeCall::describing(); } /** @@ -48,22 +49,22 @@ final class AfterEachCall */ public function __destruct() { - PendingCalls::afterEach($this, function (string $describing = null) { - $proxies = $this->proxies; + $describing = $this->describing; - $afterEachTestCase = ChainableClosure::when( - fn () => is_null($describing) || $this->__describeDescription === $describing, // @phpstan-ignore-line - ChainableClosure::from(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line - )->bindTo($this, self::class); + $proxies = $this->proxies; - assert($afterEachTestCase instanceof Closure); + $afterEachTestCase = ChainableClosure::when( + fn () => is_null($describing) || $this->__describeDescription === $describing, // @phpstan-ignore-line + ChainableClosure::fromSameObject(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line + )->bindTo($this, self::class); - $this->testSuite->afterEach->set( - $this->filename, - $this, - $afterEachTestCase, - ); - }); + assert($afterEachTestCase instanceof Closure); + + $this->testSuite->afterEach->set( + $this->filename, + $this, + $afterEachTestCase, + ); } diff --git a/src/PendingCalls/BeforeEachCall.php b/src/PendingCalls/BeforeEachCall.php index 44aa85b3..b9c88f8f 100644 --- a/src/PendingCalls/BeforeEachCall.php +++ b/src/PendingCalls/BeforeEachCall.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Pest\PendingCalls; use Closure; -use Pest\PendingCalls; use Pest\PendingCalls\Concerns\Describable; use Pest\Support\Backtrace; use Pest\Support\ChainableClosure; @@ -47,6 +46,8 @@ final class BeforeEachCall $this->testCallProxies = new HigherOrderMessageCollection(); $this->testCaseProxies = new HigherOrderMessageCollection(); + + $this->describing = DescribeCall::describing(); } /** @@ -54,29 +55,28 @@ final class BeforeEachCall */ public function __destruct() { - PendingCalls::beforeEach($this, function (string $describing = null) { - $testCaseProxies = $this->testCaseProxies; + $describing = $this->describing; + $testCaseProxies = $this->testCaseProxies; - $beforeEachTestCall = function (TestCall $testCall) use ($describing): void { - if (is_null($describing) || ($this->describing === $testCall->describing && $testCall->describing === $describing)) { - $this->testCallProxies->chain($testCall); - } - }; + $beforeEachTestCall = function (TestCall $testCall) use ($describing): void { + if ($describing === $this->describing && $describing === $testCall->describing) { + $this->testCallProxies->chain($testCall); + } + }; - $beforeEachTestCase = ChainableClosure::when( - fn () => is_null($describing) || $this->__describeDescription === $describing, // @phpstan-ignore-line - ChainableClosure::from(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line - )->bindTo($this, self::class); + $beforeEachTestCase = ChainableClosure::when( + fn () => is_null($describing) || $this->__describeDescription === $describing, // @phpstan-ignore-line + ChainableClosure::fromSameObject(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line + )->bindTo($this, self::class); - assert($beforeEachTestCase instanceof Closure); + assert($beforeEachTestCase instanceof Closure); - $this->testSuite->beforeEach->set( - $this->filename, - $this, - $beforeEachTestCall, - $beforeEachTestCase, - ); - }); + $this->testSuite->beforeEach->set( + $this->filename, + $this, + $beforeEachTestCall, + $beforeEachTestCase, + ); } /** @@ -86,7 +86,6 @@ final class BeforeEachCall */ public function __call(string $name, array $arguments): self { - if (method_exists(TestCall::class, $name)) { $this->testCallProxies->add(Backtrace::file(), Backtrace::line(), $name, $arguments); diff --git a/src/PendingCalls/DescribeCall.php b/src/PendingCalls/DescribeCall.php index 0f1f217b..f1e503ee 100644 --- a/src/PendingCalls/DescribeCall.php +++ b/src/PendingCalls/DescribeCall.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Pest\PendingCalls; use Closure; -use Pest\PendingCalls; +use Pest\Support\Backtrace; use Pest\TestSuite; /** @@ -13,6 +13,11 @@ use Pest\TestSuite; */ final class DescribeCall { + /** + * The current describe call. + */ + private static ?string $describing = null; + /** * Creates a new Pending Call. */ @@ -25,9 +30,26 @@ final class DescribeCall // } + /** + * What is the current describing. + */ + public static function describing(): ?string + { + return self::$describing; + } + + /** + * Creates the Call. + */ public function __destruct() { - PendingCalls::endDescribe($this); + self::$describing = $this->description; + + try { + ($this->tests)(); + } finally { + self::$describing = null; + } } /** @@ -35,12 +57,14 @@ final class DescribeCall * * @param array $arguments */ - public function __call(string $name, array $arguments): self + public function __call(string $name, array $arguments): BeforeEachCall { - foreach (PendingCalls::$testCalls as [$testCall]) { - $testCall->{$name}(...$arguments); // @phpstan-ignore-line - } + $filename = Backtrace::file(); - return $this; + $beforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename); + + $beforeEachCall->describing = $this->description; + + return $beforeEachCall->{$name}(...$arguments); } } diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index cb0359ad..649cdffd 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -10,7 +10,6 @@ use Pest\Factories\Covers\CoversClass; use Pest\Factories\Covers\CoversFunction; use Pest\Factories\Covers\CoversNothing; use Pest\Factories\TestCaseMethodFactory; -use Pest\PendingCalls; use Pest\PendingCalls\Concerns\Describable; use Pest\Plugins\Only; use Pest\Support\Backtrace; @@ -52,6 +51,8 @@ final class TestCall $this->descriptionLess = $description === null; + $this->describing = DescribeCall::describing(); + $this->testSuite->beforeEach->get($this->filename)[0]($this); } @@ -344,10 +345,11 @@ final class TestCall */ public function __destruct() { - PendingCalls::test($this, function () { + if ($this->describing) { + $this->testCaseMethod->description = '`'.$this->describing.'` '.$this->testCaseMethod->description; $this->testCaseMethod->describing = $this->describing; + } - $this->testSuite->tests->set($this->testCaseMethod); - }); + $this->testSuite->tests->set($this->testCaseMethod); } } diff --git a/src/Repositories/AfterEachRepository.php b/src/Repositories/AfterEachRepository.php index b5a55135..a3e823b2 100644 --- a/src/Repositories/AfterEachRepository.php +++ b/src/Repositories/AfterEachRepository.php @@ -28,7 +28,7 @@ final class AfterEachRepository if (array_key_exists($filename, $this->state)) { $fromAfterEachTestCase = $this->state[$filename]; - $afterEachTestCase = ChainableClosure::from($fromAfterEachTestCase, $afterEachTestCase) + $afterEachTestCase = ChainableClosure::fromSameObject($fromAfterEachTestCase, $afterEachTestCase) ->bindTo($afterEachCall, $afterEachCall::class); } @@ -44,7 +44,7 @@ final class AfterEachRepository { $afterEach = $this->state[$filename] ?? NullClosure::create(); - return ChainableClosure::from(function (): void { + return ChainableClosure::fromSameObject(function (): void { if (class_exists(Mockery::class)) { if ($container = Mockery::getContainer()) { /* @phpstan-ignore-next-line */ diff --git a/src/Repositories/BeforeEachRepository.php b/src/Repositories/BeforeEachRepository.php index d5454d32..02fcc8a0 100644 --- a/src/Repositories/BeforeEachRepository.php +++ b/src/Repositories/BeforeEachRepository.php @@ -27,8 +27,8 @@ final class BeforeEachRepository if (array_key_exists($filename, $this->state)) { [$fromBeforeEachTestCall, $fromBeforeEachTestCase] = $this->state[$filename]; - $beforeEachTestCall = ChainableClosure::from($fromBeforeEachTestCall, $beforeEachTestCall)->bindTo($beforeEachCall, $beforeEachCall::class); - $beforeEachTestCase = ChainableClosure::from($fromBeforeEachTestCase, $beforeEachTestCase)->bindTo($beforeEachCall, $beforeEachCall::class); + $beforeEachTestCall = ChainableClosure::fromDifferentObjects($fromBeforeEachTestCall, $beforeEachTestCall); + $beforeEachTestCase = ChainableClosure::fromSameObject($fromBeforeEachTestCase, $beforeEachTestCase)->bindTo($beforeEachCall, $beforeEachCall::class); } assert($beforeEachTestCall instanceof Closure); diff --git a/src/Support/ChainableClosure.php b/src/Support/ChainableClosure.php index 7acc0953..f5f27c66 100644 --- a/src/Support/ChainableClosure.php +++ b/src/Support/ChainableClosure.php @@ -31,7 +31,7 @@ final class ChainableClosure /** * Calls the given `$closure` and chains the `$next` closure. */ - public static function from(Closure $closure, Closure $next): Closure + public static function fromSameObject(Closure $closure, Closure $next): Closure { return function () use ($closure, $next): void { if (! is_object($this)) { // @phpstan-ignore-line @@ -43,6 +43,17 @@ final class ChainableClosure }; } + /** + * Calls the given `$closure` and chains the `$next` closure. + */ + public static function fromDifferentObjects(Closure $closure, Closure $next): Closure + { + return function () use ($closure, $next): void { + $closure(...func_get_args()); + $next(...func_get_args()); + }; + } + /** * Call the given static `$closure` and chains the `$next` closure. */ diff --git a/tests/Features/Describe.php b/tests/Features/Describe.php index f3740abe..6173a4dd 100644 --- a/tests/Features/Describe.php +++ b/tests/Features/Describe.php @@ -6,56 +6,73 @@ test('before each', function () { expect($this->count)->toBe(1); }); -describe('describable', function () { +describe('hooks', function () { beforeEach(function () { $this->count++; }); - test('basic', function () { - expect(true)->toBeTrue(); - }); - - test('before each', function () { + test('value', function () { expect($this->count)->toBe(2); + $this->count++; }); afterEach(function () { - expect($this->count)->toBe(2); + expect($this->count)->toBe(3); }); }); -describe('another describable', function () { +describe('hooks in different orders', function () { beforeEach(function () { $this->count++; }); - test('basic', function () { - expect(true)->toBeTrue(); - }); - - test('before each', function () { - expect($this->count)->toBe(2); + test('value', function () { + expect($this->count)->toBe(3); + $this->count++; }); afterEach(function () { - expect($this->count)->toBe(2); + expect($this->count)->toBe(4); + }); + + beforeEach(function () { + $this->count++; }); }); -test('should not fail')->todo()->shouldNotRun(); +test('todo')->todo()->shouldNotRun(); test('previous describable before each does not get applied here', function () { expect($this->count)->toBe(1); }); -afterEach(fn () => expect($this->count)->toBe(is_null($this->__describeDescription) ? 1 : 2)); - -describe('todo', function () { +describe('todo on hook', function () { beforeEach()->todo(); test('should not fail')->shouldNotRun(); + test('should run')->expect(true)->toBeTrue(); }); -describe('todo after describe', function () { +describe('todo on describe', function () { test('should not fail')->shouldNotRun(); + + test('should run')->expect(true)->toBeTrue(); })->todo(); + +test('should run')->expect(true)->toBeTrue(); + +test('with', fn ($foo) => expect($foo)->toBe(1))->with([1]); + +describe('with on hook', function () { + beforeEach()->with([2]); + + test('value', function ($foo) { + expect($foo)->toBe(2); + }); +}); + +describe('with on describe', function () { + test('value', function ($foo) { + expect($foo)->toBe(3); + }); +})->with([3]);