diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 43b7785b..e94e80b7 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -61,8 +61,10 @@ trait Testable /** * The test's describing, if any. + * + * @var string[] */ - public ?string $__describing = null; + public array $__describing = []; /** * Whether the test has ran or not. diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php index 086a9f3e..425d4950 100644 --- a/src/Factories/TestCaseMethodFactory.php +++ b/src/Factories/TestCaseMethodFactory.php @@ -31,8 +31,10 @@ final class TestCaseMethodFactory /** * The test's describing, if any. + * + * @var string[] */ - public ?string $describing = null; + public array $describing = []; /** * The test's description, if any. @@ -201,7 +203,7 @@ final class TestCaseMethodFactory ]; foreach ($this->depends as $depend) { - $depend = Str::evaluable($this->describing !== null ? Str::describe($this->describing, $depend) : $depend); + $depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend)); $this->attributes[] = new Attribute( \PHPUnit\Framework\Attributes\Depends::class, diff --git a/src/Functions.php b/src/Functions.php index cdba1d0a..1e12fe7e 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -43,7 +43,7 @@ if (! function_exists('beforeAll')) { */ function beforeAll(Closure $closure): void { - if (! is_null(DescribeCall::describing())) { + if (DescribeCall::describing() !== []) { $filename = Backtrace::file(); throw new BeforeAllWithinDescribe($filename); @@ -205,7 +205,7 @@ if (! function_exists('afterAll')) { */ function afterAll(Closure $closure): void { - if (! is_null(DescribeCall::describing())) { + if (DescribeCall::describing() !== []) { $filename = Backtrace::file(); throw new AfterAllWithinDescribe($filename); diff --git a/src/PendingCalls/AfterEachCall.php b/src/PendingCalls/AfterEachCall.php index e6c0142d..643ab924 100644 --- a/src/PendingCalls/AfterEachCall.php +++ b/src/PendingCalls/AfterEachCall.php @@ -54,7 +54,7 @@ final class AfterEachCall $proxies = $this->proxies; $afterEachTestCase = ChainableClosure::boundWhen( - fn (): bool => is_null($describing) || $this->__describing === $describing, + fn (): bool => $describing === [] || in_array(end($describing), $this->__describing, true), ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line )->bindTo($this, self::class); diff --git a/src/PendingCalls/BeforeEachCall.php b/src/PendingCalls/BeforeEachCall.php index 96b7b1b6..4582cbbc 100644 --- a/src/PendingCalls/BeforeEachCall.php +++ b/src/PendingCalls/BeforeEachCall.php @@ -63,12 +63,12 @@ final class BeforeEachCall $beforeEachTestCall = function (TestCall $testCall) use ($describing): void { - if ($this->describing !== null) { - if ($describing !== $this->describing) { + if ($this->describing !== []) { + if (end($describing) !== end($this->describing)) { return; } - if ($describing !== $testCall->describing) { + if (! in_array(end($describing), $testCall->describing, true)) { return; } } @@ -77,7 +77,7 @@ final class BeforeEachCall }; $beforeEachTestCase = ChainableClosure::boundWhen( - fn (): bool => is_null($describing) || $this->__describing === $describing, + fn (): bool => $describing === [] || in_array(end($describing), $this->__describing, true), ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line )->bindTo($this, self::class); @@ -96,7 +96,7 @@ final class BeforeEachCall */ public function after(Closure $closure): self { - if ($this->describing === null) { + if ($this->describing === []) { throw new AfterBeforeTestFunction($this->filename); } diff --git a/src/PendingCalls/Concerns/Describable.php b/src/PendingCalls/Concerns/Describable.php index e70bcbb3..b114d15d 100644 --- a/src/PendingCalls/Concerns/Describable.php +++ b/src/PendingCalls/Concerns/Describable.php @@ -11,11 +11,15 @@ trait Describable { /** * Note: this is property is not used; however, it gets added automatically by rector php. + * + * @var string[] */ - public string $__describing; + public array $__describing; /** * The describing of the test case. + * + * @var string[] */ - public ?string $describing = null; + public array $describing = []; } diff --git a/src/PendingCalls/DescribeCall.php b/src/PendingCalls/DescribeCall.php index 4735ecce..decd6451 100644 --- a/src/PendingCalls/DescribeCall.php +++ b/src/PendingCalls/DescribeCall.php @@ -39,10 +39,12 @@ final class DescribeCall /** * What is the current describing. + * + * @return string[] */ - public static function describing(): ?string + public static function describing(): array { - return self::$describing[count(self::$describing) - 1] ?? null; + return self::$describing; } /** @@ -73,7 +75,7 @@ final class DescribeCall if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) { $this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename); - $this->currentBeforeEachCall->describing = $this->description; + $this->currentBeforeEachCall->describing[] = $this->description; } $this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index c866eb31..a22ff11d 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -76,7 +76,7 @@ final class TestCall // @phpstan-ignore-line throw new TestDescriptionMissing($this->filename); } - $description = is_null($this->describing) + $description = $this->describing === [] ? $this->description : Str::describe($this->describing, $this->description); @@ -683,7 +683,7 @@ final class TestCall // @phpstan-ignore-line throw new TestDescriptionMissing($this->filename); } - if (! is_null($this->describing)) { + if ($this->describing !== []) { $this->testCaseMethod->describing = $this->describing; $this->testCaseMethod->description = Str::describe($this->describing, $this->description); } else { diff --git a/src/Support/Str.php b/src/Support/Str.php index 754749e7..570d58ea 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -103,10 +103,14 @@ final class Str /** * Creates a describe block as `$describeDescription` → `$testDescription` format. + * + * @param string[] $describeDescriptions */ - public static function describe(string $describeDescription, string $testDescription): string + public static function describe(array $describeDescriptions, string $testDescription): string { - return sprintf('`%s` → %s', $describeDescription, $testDescription); + $descriptionComponents = [...$describeDescriptions, $testDescription]; + + return sprintf(str_repeat('`%s` → ', count($describeDescriptions)).'%s', ...$descriptionComponents); } /** diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index d0e4c943..50b7226b 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -27,6 +27,8 @@ PASS Tests\Features\AfterEach ✓ it does not get executed before the test ✓ it gets executed after the test + ✓ outer → inner → it does not get executed before the test + ✓ outer → inner → it should call all parent afterEach functions PASS Tests\Features\Assignee ✓ it may be associated with an assignee [@nunomaduro, @taylorotwell] @@ -40,6 +42,7 @@ PASS Tests\Features\BeforeEach ✓ it gets executed before each test ✓ it gets executed before each test once again + ✓ outer → inner → it should call all parent beforeEach functions PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations ✓ runs 1 @@ -1585,4 +1588,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1096 passed (2649 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1099 passed (2656 assertions) \ No newline at end of file diff --git a/tests/Features/AfterEach.php b/tests/Features/AfterEach.php index 217eaf8d..ea0dd7ea 100644 --- a/tests/Features/AfterEach.php +++ b/tests/Features/AfterEach.php @@ -26,3 +26,25 @@ it('gets executed after the test', function () { afterEach(function () { $this->state->bar = 2; }); + +describe('outer', function () { + afterEach(function () { + $this->state->bar++; + }); + + describe('inner', function () { + afterEach(function () { + $this->state->bar++; + }); + + it('does not get executed before the test', function () { + expect($this->state)->toHaveProperty('bar'); + expect($this->state->bar)->toBe(2); + }); + + it('should call all parent afterEach functions', function () { + expect($this->state)->toHaveProperty('bar'); + expect($this->state->bar)->toBe(4); + }); + }); +}); diff --git a/tests/Features/BeforeEach.php b/tests/Features/BeforeEach.php index 7ef6144b..8ce78200 100644 --- a/tests/Features/BeforeEach.php +++ b/tests/Features/BeforeEach.php @@ -25,3 +25,19 @@ it('gets executed before each test once again', function () { beforeEach(function () { $this->bar++; }); + +describe('outer', function () { + beforeEach(function () { + $this->bar++; + }); + + describe('inner', function () { + beforeEach(function () { + $this->bar++; + }); + + it('should call all parent beforeEach functions', function () { + expect($this->bar)->toBe(3); + }); + }); +}); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 8efb514c..b3de6f5d 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1086 passed (2625 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1089 passed (2632 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();