Skip to content

Commit

Permalink
Merge pull request #1295 from jshayes/nested-describe
Browse files Browse the repository at this point in the history
Support for nested describe blocks
  • Loading branch information
nunomaduro authored Oct 22, 2024
2 parents 0b7f4f2 + 6968094 commit cf57ea1
Show file tree
Hide file tree
Showing 26 changed files with 847 additions and 33 deletions.
4 changes: 3 additions & 1 deletion src/Concerns/Testable.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ trait Testable

/**
* The test's describing, if any.
*
* @var array<int, string>
*/
public ?string $__describing = null;
public array $__describing = [];

/**
* Whether the test has ran or not.
Expand Down
6 changes: 4 additions & 2 deletions src/Factories/TestCaseMethodFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ final class TestCaseMethodFactory

/**
* The test's describing, if any.
*
* @var array<int, string>
*/
public ?string $describing = null;
public array $describing = [];

/**
* The test's description, if any.
Expand Down Expand Up @@ -201,7 +203,7 @@ public function buildForEvaluation(): string
];

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,
Expand Down
4 changes: 2 additions & 2 deletions src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function expect(mixed $value = null): Expectation
*/
function beforeAll(Closure $closure): void
{
if (! is_null(DescribeCall::describing())) {
if (DescribeCall::describing() !== []) {
$filename = Backtrace::file();

throw new BeforeAllWithinDescribe($filename);
Expand Down Expand Up @@ -205,7 +205,7 @@ function afterEach(?Closure $closure = null): AfterEachCall
*/
function afterAll(Closure $closure): void
{
if (! is_null(DescribeCall::describing())) {
if (DescribeCall::describing() !== []) {
$filename = Backtrace::file();

throw new AfterAllWithinDescribe($filename);
Expand Down
3 changes: 2 additions & 1 deletion src/PendingCalls/AfterEachCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Closure;
use Pest\PendingCalls\Concerns\Describable;
use Pest\Support\Arr;
use Pest\Support\Backtrace;
use Pest\Support\ChainableClosure;
use Pest\Support\HigherOrderMessageCollection;
Expand Down Expand Up @@ -54,7 +55,7 @@ public function __destruct()
$proxies = $this->proxies;

$afterEachTestCase = ChainableClosure::boundWhen(
fn (): bool => is_null($describing) || $this->__describing === $describing,
fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
)->bindTo($this, self::class);

Expand Down
11 changes: 6 additions & 5 deletions src/PendingCalls/BeforeEachCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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;
Expand Down Expand Up @@ -63,12 +64,12 @@ public function __destruct()

$beforeEachTestCall = function (TestCall $testCall) use ($describing): void {

if ($this->describing !== null) {
if ($describing !== $this->describing) {
if ($this->describing !== []) {
if (Arr::last($describing) !== Arr::last($this->describing)) {
return;
}

if ($describing !== $testCall->describing) {
if (! in_array(Arr::last($describing), $testCall->describing, true)) {
return;
}
}
Expand All @@ -77,7 +78,7 @@ public function __destruct()
};

$beforeEachTestCase = ChainableClosure::boundWhen(
fn (): bool => is_null($describing) || $this->__describing === $describing,
fn (): bool => $describing === [] || in_array(Arr::last($describing), $this->__describing, true),
ChainableClosure::bound(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
)->bindTo($this, self::class);

Expand All @@ -96,7 +97,7 @@ public function __destruct()
*/
public function after(Closure $closure): self
{
if ($this->describing === null) {
if ($this->describing === []) {
throw new AfterBeforeTestFunction($this->filename);
}

Expand Down
8 changes: 6 additions & 2 deletions src/PendingCalls/Concerns/Describable.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ trait Describable
{
/**
* Note: this is property is not used; however, it gets added automatically by rector php.
*
* @var array<int, string>
*/
public string $__describing;
public array $__describing;

/**
* The describing of the test case.
*
* @var array<int, string>
*/
public ?string $describing = null;
public array $describing = [];
}
14 changes: 9 additions & 5 deletions src/PendingCalls/DescribeCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ final class DescribeCall
{
/**
* The current describe call.
*
* @var array<int, string>
*/
private static ?string $describing = null;
private static array $describing = [];

/**
* The describe "before each" call.
Expand All @@ -37,8 +39,10 @@ public function __construct(

/**
* What is the current describing.
*
* @return array<int, string>
*/
public static function describing(): ?string
public static function describing(): array
{
return self::$describing;
}
Expand All @@ -50,12 +54,12 @@ public function __destruct()
{
unset($this->currentBeforeEachCall);

self::$describing = $this->description;
self::$describing[] = $this->description;

try {
($this->tests)();
} finally {
self::$describing = null;
array_pop(self::$describing);
}
}

Expand All @@ -71,7 +75,7 @@ public function __call(string $name, array $arguments): self
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
Expand Down
4 changes: 2 additions & 2 deletions src/PendingCalls/TestCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function after(Closure $closure): self
throw new TestDescriptionMissing($this->filename);
}

$description = is_null($this->describing)
$description = $this->describing === []
? $this->description
: Str::describe($this->describing, $this->description);

Expand Down Expand Up @@ -683,7 +683,7 @@ public function __destruct()
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 {
Expand Down
10 changes: 10 additions & 0 deletions src/Support/Arr.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,14 @@ public static function dot(array $array, string $prepend = ''): array

return $results;
}

/**
* Returns the value of the last element or false for empty array
*
* @param array<array-key, mixed> $array
*/
public static function last(array $array): mixed
{
return end($array);
}
}
8 changes: 6 additions & 2 deletions src/Support/Str.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,14 @@ public static function isUuid(string $value): bool

/**
* Creates a describe block as `$describeDescription` → `$testDescription` format.
*
* @param array<int, 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);
}

/**
Expand Down
50 changes: 48 additions & 2 deletions tests/.pest/snapshots/Visual/Todo/todo.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
todo on describeshould not fail
todo on describeshould run

TODO Tests\Features\Todo - 7 todos
TODO Tests\Features\Todo - 28 todos
something todo later
something todo later chained
something todo later chained and with function body
Expand All @@ -24,6 +24,52 @@
↓ it may have an associated PR #1
↓ it may have an associated note
// a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note

PASS Tests\CustomTestCase\ChildTest
✓ override method
Expand All @@ -34,6 +80,6 @@
PASS Tests\CustomTestCase\ParentTest
✓ override method

Tests: 17 todos, 3 passed (3 assertions)
Tests: 38 todos, 3 passed (20 assertions)
Duration: x.xxs

50 changes: 48 additions & 2 deletions tests/.pest/snapshots/Visual/Todo/todo_in_parallel.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
todo on describeshould not fail
todo on describeshould run

TODO Tests\Features\Todo - 7 todos
TODO Tests\Features\Todo - 28 todos
something todo later
something todo later chained
something todo later chained and with function body
Expand All @@ -24,6 +24,52 @@
↓ it may have an associated PR #1
↓ it may have an associated note
// a note
↓ todo on describe → todo block → nested inside todo block → it should not execute
↓ todo on describe → todo block → nested inside todo block → it should set the note
// hi
↓ todo on describe → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on describe → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on describe → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on describe → todo block → it should not execute
↓ todo on test after describe block
↓ todo with note on test after describe block
// test note
↓ todo on beforeEach → todo block → nested inside todo block → it should not execute
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test without a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note to a test with a todo
// describe note
↓ todo on beforeEach → todo block → describe with note → it should apply the note as well as the note from the test
// describe note
// test note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test without a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes to a test with a todo
// describe note
// nested describe note
↓ todo on beforeEach → todo block → describe with note → nested describe with note → it should apply all parent notes as well as the note from the test
// describe note
// nested describe note
// test note
↓ todo on beforeEach → todo block → it should not execute
↓ todo on test after describe block with beforeEach
↓ todo with note on test after describe block with beforeEach
// test note

PASS Tests\CustomTestCase\ChildTest
✓ override method
Expand All @@ -34,6 +80,6 @@
PASS Tests\CustomTestCase\ParentTest
✓ override method

Tests: 17 todos, 3 passed (3 assertions)
Tests: 38 todos, 3 passed (20 assertions)
Duration: x.xxs

Loading

0 comments on commit cf57ea1

Please sign in to comment.