Fix an issue where beforeEach/afterEach functions were called on other describe blocks with the same name

This commit is contained in:
jshayes
2024-10-12 01:06:16 -04:00
parent 709ecb1ba2
commit b5b8fab09b
17 changed files with 1443 additions and 86 deletions

View File

@ -32,7 +32,7 @@ final class TestCaseMethodFactory
/** /**
* The test's describing, if any. * The test's describing, if any.
* *
* @var array<int, string> * @var array<int, \Pest\Support\Description>
*/ */
public array $describing = []; public array $describing = [];

View File

@ -12,14 +12,14 @@ trait Describable
/** /**
* Note: this is property is not used; however, it gets added automatically by rector php. * Note: this is property is not used; however, it gets added automatically by rector php.
* *
* @var array<int, string> * @var array<int, \Pest\Support\Description>
*/ */
public array $__describing; public array $__describing;
/** /**
* The describing of the test case. * The describing of the test case.
* *
* @var array<int, string> * @var array<int, \Pest\Support\Description>
*/ */
public array $describing = []; public array $describing = [];
} }

View File

@ -6,6 +6,7 @@ namespace Pest\PendingCalls;
use Closure; use Closure;
use Pest\Support\Backtrace; use Pest\Support\Backtrace;
use Pest\Support\Description;
use Pest\TestSuite; use Pest\TestSuite;
/** /**
@ -16,7 +17,7 @@ final class DescribeCall
/** /**
* The current describe call. * The current describe call.
* *
* @var array<int, string> * @var array<int, Description>
*/ */
private static array $describing = []; private static array $describing = [];
@ -25,22 +26,27 @@ final class DescribeCall
*/ */
private ?BeforeEachCall $currentBeforeEachCall = null; private ?BeforeEachCall $currentBeforeEachCall = null;
/**
* The unique description for this describe block
*/
private readonly Description $description;
/** /**
* Creates a new Pending Call. * Creates a new Pending Call.
*/ */
public function __construct( public function __construct(
public readonly TestSuite $testSuite, public readonly TestSuite $testSuite,
public readonly string $filename, public readonly string $filename,
public readonly string $description, string $description,
public readonly Closure $tests public readonly Closure $tests
) { ) {
// $this->description = new Description($description);
} }
/** /**
* What is the current describing. * What is the current describing.
* *
* @return array<int, string> * @return array<int, Description>
*/ */
public static function describing(): array public static function describing(): array
{ {

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Pest\Support;
final readonly class Description implements \Stringable
{
public function __construct(private string $description) {}
public function __toString(): string
{
return $this->description;
}
}

View File

@ -104,7 +104,7 @@ final class Str
/** /**
* Creates a describe block as `$describeDescription` → `$testDescription` format. * Creates a describe block as `$describeDescription` → `$testDescription` format.
* *
* @param array<int, string> $describeDescriptions * @param array<int, Description> $describeDescriptions
*/ */
public static function describe(array $describeDescriptions, string $testDescription): string public static function describe(array $describeDescriptions, string $testDescription): string
{ {

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 28 todos TODO Tests\Features\Todo - 29 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -45,6 +45,7 @@
// nested describe note // nested describe note
// test note // test note
↓ todo on describe → todo block → it should not execute ↓ todo on describe → todo block → it should not execute
↓ todo on describe with matching name → describe block → it should not execute
↓ todo on test after describe block ↓ todo on test after describe block
↓ todo with note on test after describe block ↓ todo with note on test after describe block
// test note // test note
@ -80,6 +81,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 38 todos, 3 passed (20 assertions) Tests: 39 todos, 3 passed (21 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 28 todos TODO Tests\Features\Todo - 29 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -45,6 +45,7 @@
// nested describe note // nested describe note
// test note // test note
↓ todo on describe → todo block → it should not execute ↓ todo on describe → todo block → it should not execute
↓ todo on describe with matching name → describe block → it should not execute
↓ todo on test after describe block ↓ todo on test after describe block
↓ todo with note on test after describe block ↓ todo with note on test after describe block
// test note // test note
@ -80,6 +81,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 38 todos, 3 passed (20 assertions) Tests: 39 todos, 3 passed (21 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 28 todos TODO Tests\Features\Todo - 29 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -45,6 +45,7 @@
// nested describe note // nested describe note
// test note // test note
↓ todo on describe → todo block → it should not execute ↓ todo on describe → todo block → it should not execute
↓ todo on describe with matching name → describe block → it should not execute
↓ todo on test after describe block ↓ todo on test after describe block
↓ todo with note on test after describe block ↓ todo with note on test after describe block
// test note // test note
@ -80,6 +81,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 38 todos, 3 passed (20 assertions) Tests: 39 todos, 3 passed (21 assertions)
Duration: x.xxs Duration: x.xxs

View File

@ -15,7 +15,7 @@
↓ todo on describe → should not fail ↓ todo on describe → should not fail
↓ todo on describe → should run ↓ todo on describe → should run
TODO Tests\Features\Todo - 28 todos TODO Tests\Features\Todo - 29 todos
↓ something todo later ↓ something todo later
↓ something todo later chained ↓ something todo later chained
↓ something todo later chained and with function body ↓ something todo later chained and with function body
@ -45,6 +45,7 @@
// nested describe note // nested describe note
// test note // test note
↓ todo on describe → todo block → it should not execute ↓ todo on describe → todo block → it should not execute
↓ todo on describe with matching name → describe block → it should not execute
↓ todo on test after describe block ↓ todo on test after describe block
↓ todo with note on test after describe block ↓ todo with note on test after describe block
// test note // test note
@ -80,6 +81,6 @@
PASS Tests\CustomTestCase\ParentTest PASS Tests\CustomTestCase\ParentTest
✓ override method ✓ override method
Tests: 38 todos, 3 passed (20 assertions) Tests: 39 todos, 3 passed (21 assertions)
Duration: x.xxs Duration: x.xxs

File diff suppressed because it is too large Load Diff

View File

@ -48,3 +48,55 @@ describe('outer', function () {
}); });
}); });
}); });
describe('matching describe block names', function () {
afterEach(function () {
$this->state->foo = 1;
});
describe('outer', function () {
afterEach(function () {
$this->state->foo++;
});
describe('middle', function () {
afterEach(function () {
$this->state->foo++;
});
describe('inner', function () {
afterEach(function () {
$this->state->foo++;
});
it('does not get executed before the test', function () {
expect($this)->not->toHaveProperty('foo');
});
it('should call all parent afterEach functions', function () {
expect($this->state->foo)->toBe(4);
});
});
});
describe('middle', function () {
it('does not get executed before the test', function () {
expect($this)->not->toHaveProperty('foo');
});
it('should not call afterEach functions for sibling describe blocks with the same name', function () {
expect($this)->not->toHaveProperty('foo');
});
});
describe('inner', function () {
it('does not get executed before the test', function () {
expect($this)->not->toHaveProperty('foo');
});
it('should not call afterEach functions for descendent of sibling describe blocks with the same name', function () {
expect($this)->not->toHaveProperty('foo');
});
});
});
});

View File

@ -51,3 +51,78 @@ describe('with expectations', function () {
test('test', function () {}); test('test', function () {});
}); });
describe('matching describe block names', function () {
beforeEach(function () {
$this->foo = 1;
});
describe('outer', function () {
beforeEach(function () {
$this->foo++;
});
describe('middle', function () {
beforeEach(function () {
$this->foo++;
});
describe('inner', function () {
beforeEach(function () {
$this->foo++;
});
it('should call all parent beforeEach functions', function () {
expect($this->foo)->toBe(4);
});
});
});
describe('middle', function () {
it('should not call beforeEach functions for sibling describe blocks with the same name', function () {
expect($this->foo)->toBe(2);
});
});
describe('inner', function () {
it('should not call beforeEach functions for descendent of sibling describe blocks with the same name', function () {
expect($this->foo)->toBe(2);
});
});
});
});
$matchingNameCalls = 0;
describe('matching name', function () use (&$matchingNameCalls) {
beforeEach(function () use (&$matchingNameCalls) {
$matchingNameCalls++;
});
it('should call the before each', function () use (&$matchingNameCalls) {
expect($matchingNameCalls)->toBe(1);
});
});
describe('matching name', function () use (&$matchingNameCalls) {
it('should not call the before each on the describe block with the same name', function () use (&$matchingNameCalls) {
expect($matchingNameCalls)->toBe(1);
});
});
beforeEach(function () {
$this->baz = 1;
});
describe('called on all tests', function () {
beforeEach(function () {
$this->baz++;
});
test('beforeEach should be called', function () {
expect($this->baz)->toBe(2);
});
test('beforeEach should be called for all tests', function () {
expect($this->baz)->toBe(2);
});
});

View File

@ -415,6 +415,34 @@ describe('with on nested describe', function () {
})->with([1]); })->with([1]);
}); });
describe('matching describe block names', function () {
describe('outer', function () {
test('before inner describe block', function (...$args) {
expect($args)->toBe([1]);
});
describe('inner', function () {
it('should include the with value from all parent describe blocks', function (...$args) {
expect($args)->toBe([1, 2]);
});
test('should include the with value from all parent describe blocks and the test', function (...$args) {
expect($args)->toBe([1, 2, 3]);
})->with([3]);
})->with([2]);
describe('inner', function () {
it('should not include the value from the other describe block with the same name', function (...$args) {
expect($args)->toBe([1]);
});
});
test('after inner describe block', function (...$args) {
expect($args)->toBe([1]);
});
})->with([1]);
});
test('after describe block', function (...$args) { test('after describe block', function (...$args) {
expect($args)->toBe([5]); expect($args)->toBe([5]);
})->with([5]); })->with([5]);

View File

@ -44,6 +44,36 @@ describe('nested', function () {
})->note('This is a nested describe static note'); })->note('This is a nested describe static note');
})->note('This is describe static note'); })->note('This is describe static note');
describe('matching describe names', function () {
beforeEach(function () {
$this->note('This is before each matching describe runtime note');
})->note('This is before each matching describe static note');
describe('describe block', function () {
beforeEach(function () {
$this->note('This is before each matching describe runtime note');
})->note('This is before each matching describe static note');
it('may have a static note and runtime note', function () {
expect(true)->toBeTrue(true);
$this->note('This is a runtime note within a matching describe');
})->note('This is a static note within a matching describe');
})->note('This is a nested matching static note');
describe('describe block', function () {
beforeEach(function () {
$this->note('This is before each matching describe runtime note, and should not contain the matching describe notes');
})->note('This is before each matching describe static note, and should not contain the matching describe notes');
it('may have a static note and runtime note, that are different than the matching describe block', function () {
expect(true)->toBeTrue(true);
$this->note('This is a runtime note within a matching describe, and should not contain the matching describe notes');
})->note('This is a static note within a matching describe, and should not contain the matching describe notes');
})->note('This is a nested matching static note, and should not contain the matching describe notes');
});
test('multiple notes', function () { test('multiple notes', function () {
expect(true)->toBeTrue(true); expect(true)->toBeTrue(true);

View File

@ -79,3 +79,17 @@ describe('describe blocks', function () {
})->repeat(times: 2); })->repeat(times: 2);
})->repeat(times: 3); })->repeat(times: 3);
}); });
describe('matching describe blocks', function () {
describe('describe block', function () {
it('should repeat the number of times specified in the parent describe block', function () {
expect(true)->toBeTrue();
});
})->repeat(times: 3);
describe('describe block', function () {
test('should not repeat the number of times of the describe block with the same name', function () {
expect(true)->toBeTrue();
});
});
});

View File

@ -125,6 +125,74 @@ describe('skip on beforeEach', function () {
}); });
}); });
describe('matching describe with skip', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__matching_describe_with_skip__→__describe_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__matching_describe_with_skip__→__describe_block__→_it_should_execute_a_test_in_a_describe_block_with_the_same_name_as_a_skipped_describe_block' => expect($this->ran)->toBe(true),
'__pest_evaluable__matching_describe_with_skip__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('describe block', function () {
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
})->skip();
describe('describe block', function () {
it('should execute a test in a describe block with the same name as a skipped describe block', function () {
$this->ran = true;
});
});
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
describe('matching describe with skip on beforeEach', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__matching_describe_with_skip_on_beforeEach__→__describe_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__matching_describe_with_skip_on_beforeEach__→__describe_block__→_it_should_execute_a_test_in_a_describe_block_with_the_same_name_as_a_skipped_describe_block' => expect($this->ran)->toBe(true),
'__pest_evaluable__matching_describe_with_skip_on_beforeEach__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('describe block', function () {
beforeEach()->skip();
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
});
describe('describe block', function () {
it('should execute a test in a describe block with the same name as a skipped describe block', function () {
$this->ran = true;
});
});
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
it('does not skip after the describe block', function () { it('does not skip after the describe block', function () {
expect(true)->toBeTrue(); expect(true)->toBeTrue();
}); });

View File

@ -108,6 +108,40 @@ describe('todo on describe', function () {
}); });
}); });
describe('todo on describe with matching name', function () {
beforeEach(function () {
$this->ran = false;
});
afterEach(function () {
match ($this->name()) {
'__pest_evaluable__todo_on_describe_with_matching_name__→__describe_block__→_it_should_not_execute' => expect($this->ran)->toBe(false),
'__pest_evaluable__todo_on_describe_with_matching_name__→__describe_block__→_it_should_execute_a_test_in_a_describe_block_with_the_same_name_as_a_todo_describe_block' => expect($this->ran)->toBe(true),
'__pest_evaluable__todo_on_describe_with_matching_name__→_it_should_execute' => expect($this->ran)->toBe(true),
default => $this->fail('Unexpected test name: '.$this->name()),
};
});
describe('describe block', function () {
it('should not execute', function () {
$this->ran = true;
$this->fail();
});
})->todo();
describe('describe block', function () {
it('should execute a test in a describe block with the same name as a todo describe block', function () {
$this->ran = true;
});
});
it('should execute', function () {
$this->ran = true;
expect($this->ran)->toBe(true);
});
});
test('todo on test after describe block', function () { test('todo on test after describe block', function () {
$this->fail(); $this->fail();
})->todo(); })->todo();