From f97222ed14e37bf591b8aa705f823d420be79b20 Mon Sep 17 00:00:00 2001 From: Adam Goulding Date: Tue, 11 Aug 2026 16:12:46 +0100 Subject: [PATCH 1/4] fix: beforeEach/afterEach leaking into sibling describe blocks with same nested name --- src/PendingCalls/AfterEachCall.php | 3 +-- src/PendingCalls/BeforeEachCall.php | 11 ++++------- tests/Features/AfterEach.php | 30 +++++++++++++++++++++++++++++ tests/Features/BeforeEach.php | 26 +++++++++++++++++++++++++ tests/Features/Describe.php | 14 ++++++++++++++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/PendingCalls/AfterEachCall.php b/src/PendingCalls/AfterEachCall.php index 9f7f0b558..6c0ed4ea5 100644 --- a/src/PendingCalls/AfterEachCall.php +++ b/src/PendingCalls/AfterEachCall.php @@ -6,7 +6,6 @@ use Closure; use Pest\PendingCalls\Concerns\Describable; -use Pest\Support\Arr; use Pest\Support\Backtrace; use Pest\Support\ChainableClosure; use Pest\Support\HigherOrderMessageCollection; @@ -43,7 +42,7 @@ public function __destruct() $proxies = $this->proxies; $afterEachTestCase = ChainableClosure::boundWhen( - fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true), + fn (): bool => $describing === [] || (count($describing) <= count($this->__describing) && array_slice($this->__describing, 0, count($describing)) === $describing), ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), )->bindTo($this, self::class); diff --git a/src/PendingCalls/BeforeEachCall.php b/src/PendingCalls/BeforeEachCall.php index 852a1aad5..34f08f3f0 100644 --- a/src/PendingCalls/BeforeEachCall.php +++ b/src/PendingCalls/BeforeEachCall.php @@ -7,7 +7,6 @@ use Closure; use Pest\Exceptions\AfterBeforeTestFunction; use Pest\PendingCalls\Concerns\Describable; -use Pest\Support\Arr; use Pest\Support\Backtrace; use Pest\Support\ChainableClosure; use Pest\Support\HigherOrderMessageCollection; @@ -50,11 +49,9 @@ public function __destruct() $beforeEachTestCall = function (TestCall $testCall) use ($describing): void { if ($this->describing !== []) { - if (Arr::last($describing) !== Arr::last($this->describing)) { - return; - } - - if (! in_array(Arr::last($describing), $testCall->describing, true)) { + $testCallDescribing = $testCall->describing; + if (count($describing) > count($testCallDescribing) || + array_slice($testCallDescribing, 0, count($describing)) !== $describing) { return; } } @@ -63,7 +60,7 @@ public function __destruct() }; $beforeEachTestCase = ChainableClosure::boundWhen( - fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true), + fn (): bool => $describing === [] || (count($describing) <= count($this->__describing) && array_slice($this->__describing, 0, count($describing)) === $describing), ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), )->bindTo($this, self::class); diff --git a/tests/Features/AfterEach.php b/tests/Features/AfterEach.php index 21b975eb3..afbd89a65 100644 --- a/tests/Features/AfterEach.php +++ b/tests/Features/AfterEach.php @@ -100,3 +100,33 @@ }); }); }); + +describe('hierarchical test naming', function (): void { + describe('block one', function (): void { + describe('the same name', function (): void { + it('does not call afterEach from sibling describe with same name', function (): void { + expect($this)->not->toHaveProperty('example'); + }); + }); + }); + + describe('block two', function (): void { + afterEach(function (): void { + expect($this->result)->toBeFalse(); + }); + + describe('the same name', function (): void { + beforeEach(function (): void { + $this->example = false; + }); + + afterEach(function (): void { + $this->result = $this->example; + }); + + it('correctly calls afterEach from own describe hierarchy', function (): void { + expect($this->example)->toBeFalse(); + }); + }); + }); +}); diff --git a/tests/Features/BeforeEach.php b/tests/Features/BeforeEach.php index ef78194e9..e4832d97f 100644 --- a/tests/Features/BeforeEach.php +++ b/tests/Features/BeforeEach.php @@ -126,3 +126,29 @@ expect($this->baz)->toBe(2); }); }); + +describe('hierarchical test naming', function (): void { + describe('block one', function (): void { + describe('the same name', function (): void { + it('does not call beforeEach from sibling describe with same name', function (): void { + expect($this)->not->toHaveProperty('example'); + }); + }); + }); + + describe('block two', function (): void { + beforeEach(function (): void { + $this->example = false; + }); + + describe('the same name', function (): void { + beforeEach(function (): void { + $this->result = $this->example; + }); + + it('correctly calls beforeEach from own describe hierarchy', function (): void { + expect($this->result)->toBeFalse(); + }); + }); + }); +}); diff --git a/tests/Features/Describe.php b/tests/Features/Describe.php index f3fc130d8..ed5de20d8 100644 --- a/tests/Features/Describe.php +++ b/tests/Features/Describe.php @@ -108,3 +108,17 @@ expect($this->count)->toBe(2); }); }); + +describe('sibling describes may share the same it() description', function (): void { + describe('block one', function (): void { + it('can be created', function (): void { + expect(true)->toBeTrue(); + }); + }); + + describe('block two', function (): void { + it('can be created', function (): void { + expect(true)->toBeTrue(); + }); + }); +}); From 5fb8034c1e3ba8cd6eb122f7dfed8c489cf12b0e Mon Sep 17 00:00:00 2001 From: Adam Goulding Date: Mon, 12 Oct 2026 11:38:25 +0100 Subject: [PATCH 2/4] fix: resolve afterEach test ordering issue --- tests/Features/AfterEach.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/Features/AfterEach.php b/tests/Features/AfterEach.php index afbd89a65..6449f2441 100644 --- a/tests/Features/AfterEach.php +++ b/tests/Features/AfterEach.php @@ -111,17 +111,13 @@ }); describe('block two', function (): void { - afterEach(function (): void { - expect($this->result)->toBeFalse(); - }); - describe('the same name', function (): void { beforeEach(function (): void { $this->example = false; }); afterEach(function (): void { - $this->result = $this->example; + expect($this->example)->toBeFalse(); }); it('correctly calls afterEach from own describe hierarchy', function (): void { From cefae66ec93ea426e0723ff356d31f2f76586ae2 Mon Sep 17 00:00:00 2001 From: Adam Goulding Date: Mon, 12 Oct 2026 11:50:28 +0100 Subject: [PATCH 3/4] chore: update snapshots and parallel memory limit after rebase onto upstream/5.x --- tests/.snapshots/success.txt | 8 +++++++- tests/Visual/Parallel.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 0ea8b3f6b..6bb61becb 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -33,6 +33,8 @@ ✓ matching describe block names → outer → middle → it should not call afterEach functions for sibling describe blocks with the same name ✓ matching describe block names → outer → inner → it does not get executed before the test ✓ matching describe block names → outer → inner → it should not call afterEach functions for descendent of sibling describe blocks with the same name + ✓ hierarchical test naming → block one → the same name → it does not call afterEach from sibling describe with same name + ✓ hierarchical test naming → block two → the same name → it correctly calls afterEach from own describe hierarchy PASS Tests\Features\Assignee ✓ it may be associated with an assignee [@nunomaduro, @taylorotwell] @@ -56,6 +58,8 @@ ✓ matching name → it should not call the before each on the describe block with the same name ✓ called on all tests → beforeEach should be called ✓ called on all tests → beforeEach should be called for all tests + ✓ hierarchical test naming → block one → the same name → it does not call beforeEach from sibling describe with same name + ✓ hierarchical test naming → block two → the same name → it correctly calls beforeEach from own describe hierarchy PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations ✓ runs 1 @@ -327,6 +331,8 @@ ✓ depends on describe using with → foo with (3) ✓ depends on describe using with → bar with (3) ✓ with test after describe → it should run the before each + ✓ sibling describes may share the same it() description → block one → it can be created + ✓ sibling describes may share the same it() description → block two → it can be created PASS Tests\Features\DescriptionLess ✓ get 'foo' @@ -2271,4 +2277,4 @@ ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1610 passed (3516 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1610 passed (3516 assertions) diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index a775c9926..8162d9cac 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -4,7 +4,7 @@ $run = function (): ?string { $process = new Process( - array_merge(['php', 'bin/pest', '--parallel', '--processes=3'], func_get_args()), + array_merge(['php', '-d', 'memory_limit=512M', 'bin/pest', '--parallel', '--processes=3', '--passthru-php=-d memory_limit=512M'], func_get_args()), dirname(__DIR__, 2), ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'PAO_DISABLE' => '1'], ); From fdbbb704309f8f80b95f29dca41f38141fb76b4e Mon Sep 17 00:00:00 2001 From: Adam Goulding Date: Tue, 20 Oct 2026 16:03:52 +0100 Subject: [PATCH 4/4] chore: update snapshots and parallel memory limit after rebase onto upstream/5.x --- tests/.snapshots/success.txt | 2 +- tests/Visual/Parallel.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 6bb61becb..0884791ba 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -2277,4 +2277,4 @@ ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1610 passed (3516 assertions) + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1616 passed (3525 assertions) \ No newline at end of file diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 8162d9cac..362bb1892 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -20,7 +20,7 @@ $output = $run('--exclude-group=integration'); $output = implode("\n", array_slice(explode("\n", (string) $output), -10)); - $expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1591 passed (3458 assertions)'; + $expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1597 passed (3467 assertions)'; expect($output) ->toContain("Tests: {$expected}")