diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 43b7785b0..37d3b175a 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -61,8 +61,10 @@ trait Testable /** * The test's describing, if any. + * + * @var array */ - 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 086a9f3e1..fb763c758 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 array */ - public ?string $describing = null; + public array $describing = []; /** * The test's description, if any. @@ -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, diff --git a/src/Functions.php b/src/Functions.php index cdba1d0a7..1e12fe7e6 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -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); @@ -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); diff --git a/src/PendingCalls/AfterEachCall.php b/src/PendingCalls/AfterEachCall.php index e6c0142d4..2fdc66794 100644 --- a/src/PendingCalls/AfterEachCall.php +++ b/src/PendingCalls/AfterEachCall.php @@ -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; @@ -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); diff --git a/src/PendingCalls/BeforeEachCall.php b/src/PendingCalls/BeforeEachCall.php index 96b7b1b66..3170d8ea0 100644 --- a/src/PendingCalls/BeforeEachCall.php +++ b/src/PendingCalls/BeforeEachCall.php @@ -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; @@ -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; } } @@ -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); @@ -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); } diff --git a/src/PendingCalls/Concerns/Describable.php b/src/PendingCalls/Concerns/Describable.php index e70bcbb3a..06a7eab71 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 array */ - public string $__describing; + public array $__describing; /** * The describing of the test case. + * + * @var array */ - public ?string $describing = null; + public array $describing = []; } diff --git a/src/PendingCalls/DescribeCall.php b/src/PendingCalls/DescribeCall.php index 267c875a2..b015595c7 100644 --- a/src/PendingCalls/DescribeCall.php +++ b/src/PendingCalls/DescribeCall.php @@ -15,8 +15,10 @@ final class DescribeCall { /** * The current describe call. + * + * @var array */ - private static ?string $describing = null; + private static array $describing = []; /** * The describe "before each" call. @@ -37,8 +39,10 @@ public function __construct( /** * What is the current describing. + * + * @return array */ - public static function describing(): ?string + public static function describing(): array { return self::$describing; } @@ -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); } } @@ -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 diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index c866eb316..a22ff11d6 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -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); @@ -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 { diff --git a/src/Support/Arr.php b/src/Support/Arr.php index aed6c5a75..daf7e3a4f 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -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 + */ + public static function last(array $array): mixed + { + return end($array); + } } diff --git a/src/Support/Str.php b/src/Support/Str.php index 754749e7e..0e654bc80 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -103,10 +103,14 @@ public static function isUuid(string $value): bool /** * Creates a describe block as `$describeDescription` → `$testDescription` format. + * + * @param array $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/.pest/snapshots/Visual/Todo/todo.snap b/tests/.pest/snapshots/Visual/Todo/todo.snap index f7a85c939..5ab4f7fa6 100644 --- a/tests/.pest/snapshots/Visual/Todo/todo.snap +++ b/tests/.pest/snapshots/Visual/Todo/todo.snap @@ -15,7 +15,7 @@ ↓ todo on describe → should not fail ↓ todo on describe → should 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 @@ -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 @@ -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 diff --git a/tests/.pest/snapshots/Visual/Todo/todo_in_parallel.snap b/tests/.pest/snapshots/Visual/Todo/todo_in_parallel.snap index f7a85c939..5ab4f7fa6 100644 --- a/tests/.pest/snapshots/Visual/Todo/todo_in_parallel.snap +++ b/tests/.pest/snapshots/Visual/Todo/todo_in_parallel.snap @@ -15,7 +15,7 @@ ↓ todo on describe → should not fail ↓ todo on describe → should 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 @@ -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 @@ -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 diff --git a/tests/.pest/snapshots/Visual/Todo/todos.snap b/tests/.pest/snapshots/Visual/Todo/todos.snap index f7a85c939..5ab4f7fa6 100644 --- a/tests/.pest/snapshots/Visual/Todo/todos.snap +++ b/tests/.pest/snapshots/Visual/Todo/todos.snap @@ -15,7 +15,7 @@ ↓ todo on describe → should not fail ↓ todo on describe → should 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 @@ -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 @@ -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 diff --git a/tests/.pest/snapshots/Visual/Todo/todos_in_parallel.snap b/tests/.pest/snapshots/Visual/Todo/todos_in_parallel.snap index f7a85c939..5ab4f7fa6 100644 --- a/tests/.pest/snapshots/Visual/Todo/todos_in_parallel.snap +++ b/tests/.pest/snapshots/Visual/Todo/todos_in_parallel.snap @@ -15,7 +15,7 @@ ↓ todo on describe → should not fail ↓ todo on describe → should 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 @@ -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 @@ -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 diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index da75f2d28..1eaf4598e 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,9 @@ 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 + ✓ with expectations → nested block → test + ✓ with expectations → test PASS Tests\Features\BeforeEachProxiesToTestCallWithExpectations ✓ runs 1 @@ -178,6 +183,14 @@ ✓ it may be used with high order with dataset "informal" ✓ it may be used with high order even when bound with dataset "formal" ✓ it may be used with high order even when bound with dataset "informal" + ✓ with on nested describe → nested → before inner describe block with (1) + ✓ with on nested describe → nested → describe → it should include the with value from all parent describe blocks with (1) / (2) + ✓ with on nested describe → nested → describe → should include the with value from all parent describe blocks and the test with (1) / (2) / (3) + ✓ with on nested describe → nested → after inner describe block with (1) + ✓ after describe block with (5) + ✓ it may be used with high order after describe block with dataset "formal" + ✓ it may be used with high order after describe block with dataset "informal" + ✓ after describe block with named dataset with ('after') PASS Tests\Features\Depends ✓ first @@ -188,6 +201,13 @@ ✓ depends run test only once ✓ it asserts true is true ✓ depends works with the correct test name + ✓ describe block → first in describe + ✓ describe block → second in describe + ✓ describe block → third in describe + ✓ describe block → nested describe → first in nested describe + ✓ describe block → nested describe → second in nested describe + ✓ describe block → nested describe → third in nested describe + ✓ depends on test after describe block PASS Tests\Features\DependsInheritance ✓ it is a test @@ -215,6 +235,7 @@ ✓ depends on describe → bar ✓ 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 PASS Tests\Features\DescriptionLess ✓ get 'foo' @@ -1067,9 +1088,22 @@ ✓ nested → it may have static note and runtime note // This is before each static note // This is describe static note + // This is before each describe static note // This is a static note within describe // This is before each runtime note + // This is before each describe runtime note // This is a runtime note within describe + ✓ nested → describe nested within describe → it may have a static note and runtime note + // This is before each static note + // This is describe static note + // This is before each describe static note + // This is a nested describe static note + // This is before each nested describe static note + // This is a static note within a nested describe + // This is before each runtime note + // This is before each describe runtime note + // This is before each nested describe runtime note + // This is a runtime note within a nested describe ✓ multiple notes // This is before each static note // This is before each runtime note @@ -1199,6 +1233,23 @@ ✓ multiple times with repeat iterator with multiple dataset ('c') / ('d') @ repetition 2 of 2 ✓ multiple times with repeat iterator with multiple dataset ('c') / ('e') @ repetition 2 of 2 ✓ multiple times with repeat iterator with multiple dataset ('c') / ('f') @ repetition 2 of 2 + ✓ describe blocks → multiple times @ repetition 1 of 3 + ✓ describe blocks → multiple times @ repetition 2 of 3 + ✓ describe blocks → multiple times @ repetition 3 of 3 + ✓ describe blocks → describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 1 of 3 + ✓ describe blocks → describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 2 of 3 + ✓ describe blocks → describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 3 of 3 + ✓ describe blocks → describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 1 of 2 + ✓ describe blocks → describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 2 of 2 + ✓ describe blocks → describe with repeat → nested describe without repeat → test with no repeat should repeat the number of times specified in the parent's parent describe block @ repetition 1 of 3 + ✓ describe blocks → describe with repeat → nested describe without repeat → test with no repeat should repeat the number of times specified in the parent's parent describe block @ repetition 2 of 3 + ✓ describe blocks → describe with repeat → nested describe without repeat → test with no repeat should repeat the number of times specified in the parent's parent describe block @ repetition 3 of 3 + ✓ describe blocks → describe with repeat → nested describe without repeat → test with repeat should repeat the number of times specified in the test @ repetition 1 of 2 + ✓ describe blocks → describe with repeat → nested describe without repeat → test with repeat should repeat the number of times specified in the test @ repetition 2 of 2 + ✓ describe blocks → describe with repeat → nested describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 1 of 2 + ✓ describe blocks → describe with repeat → nested describe with repeat → test with no repeat should repeat the number of times specified in the parent describe block @ repetition 2 of 2 + ✓ describe blocks → describe with repeat → nested describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 1 of 2 + ✓ describe blocks → describe with repeat → nested describe with repeat → test with repeat should repeat the number of times specified in the test @ repetition 2 of 2 PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory1\TestFileInNestedDirectoryWithDatasetsFile ✓ uses dataset with (1) @@ -1255,6 +1306,14 @@ - it skips when skip after assertion - it can use something in the test case as a condition → This test was skipped - it can user higher order callables and skip + - skip on describe → skipped tests → nested inside skipped block → it should not execute + - skip on describe → skipped tests → it should not execute + ✓ skip on describe → it should execute + - skip on beforeEach → skipped tests → nested inside skipped block → it should not execute + - skip on beforeEach → skipped tests → it should not execute + ✓ skip on beforeEach → it should execute + ✓ it does not skip after the describe block + - it can skip after the describe block WARN Tests\Features\SkipOnPhp ✓ it can run on php version @@ -1275,7 +1334,7 @@ ✓ nested → it may be associated with an ticket #1, #4, #5, #6, #3 // an note between an the ticket - PASS Tests\Features\Todo - 7 todos + PASS Tests\Features\Todo - 28 todos ↓ something todo later ↓ something todo later chained ↓ something todo later chained and with function body @@ -1285,6 +1344,54 @@ ↓ 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 describe → it should 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 beforeEach → it should execute + ↓ todo on test after describe block with beforeEach + ↓ todo with note on test after describe block with beforeEach + // test note WARN Tests\Features\Warnings ! warning → Undefined property: P\Tests\Features\Warnings::$fooqwdfwqdfqw @@ -1433,6 +1540,13 @@ ✓ preset invalid name ✓ preset → myFramework + PASS Tests\Unit\Support\Arr + ✓ last → it should return false for an empty arary + ✓ last → it should return the last element for an array with a single element + ✓ last → it should return the last element for an array without changing the internal pointer + ✓ last → it should return the last element for an associative array without changing the internal pointer + ✓ last → it should return the last element for an mixed key array without changing the internal pointer + PASS Tests\Unit\Support\Backtrace ✓ it gets file name from called file @@ -1584,4 +1698,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 28 skipped, 1095 passed (2648 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1144 passed (2736 assertions) \ No newline at end of file diff --git a/tests/Features/AfterEach.php b/tests/Features/AfterEach.php index 217eaf8d2..ea0dd7ea6 100644 --- a/tests/Features/AfterEach.php +++ b/tests/Features/AfterEach.php @@ -26,3 +26,25 @@ 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 7ef6144b7..04f0dd13e 100644 --- a/tests/Features/BeforeEach.php +++ b/tests/Features/BeforeEach.php @@ -25,3 +25,29 @@ 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); + }); + }); +}); + +describe('with expectations', function () { + beforeEach()->expect(true)->toBeTrue(); + + describe('nested block', function () { + test('test', function () {}); + }); + + test('test', function () {}); +}); diff --git a/tests/Features/DatasetsTests.php b/tests/Features/DatasetsTests.php index bba77ac2d..54cb77a83 100644 --- a/tests/Features/DatasetsTests.php +++ b/tests/Features/DatasetsTests.php @@ -392,3 +392,40 @@ function () { ->with('greeting-bound') ->expect(fn (string $greeting) => $greeting) ->throws(InvalidArgumentException::class); + +describe('with on nested describe', function () { + describe('nested', function () { + test('before inner describe block', function (...$args) { + expect($args)->toBe([1]); + }); + + describe('describe', 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]); + + test('after inner describe block', function (...$args) { + expect($args)->toBe([1]); + }); + })->with([1]); +}); + +test('after describe block', function (...$args) { + expect($args)->toBe([5]); +})->with([5]); + +it('may be used with high order after describe block') + ->with('greeting-string') + ->expect(fn (string $greeting) => $greeting) + ->throwsNoExceptions(); + +dataset('after-describe', ['after']); + +test('after describe block with named dataset', function (...$args) { + expect($args)->toBe(['after']); +})->with('after-describe'); diff --git a/tests/Features/Depends.php b/tests/Features/Depends.php index 03ef85a8f..82f45c05d 100644 --- a/tests/Features/Depends.php +++ b/tests/Features/Depends.php @@ -36,3 +36,43 @@ // Regression tests. See https://github.com/pestphp/pest/pull/216 it('asserts true is true')->assertTrue(true); test('depends works with the correct test name')->assertTrue(true)->depends('it asserts true is true'); + +describe('describe block', function () { + $runCounter = 0; + + test('first in describe', function () use (&$runCounter) { + $runCounter++; + expect(true)->toBeTrue(); + }); + + test('second in describe', function () use (&$runCounter) { + expect($runCounter)->toBe(1); + $runCounter++; + })->depends('first in describe'); + + test('third in describe', function () use (&$runCounter) { + expect($runCounter)->toBe(2); + })->depends('second in describe'); + + describe('nested describe', function () { + $runCounter = 0; + + test('first in nested describe', function () use (&$runCounter) { + $runCounter++; + expect(true)->toBeTrue(); + }); + + test('second in nested describe', function () use (&$runCounter) { + expect($runCounter)->toBe(1); + $runCounter++; + })->depends('first in nested describe'); + + test('third in nested describe', function () use (&$runCounter) { + expect($runCounter)->toBe(2); + })->depends('second in nested describe'); + }); +}); + +test('depends on test after describe block', function () use (&$runCounter) { + expect($runCounter)->toBe(2); +})->depends('first', 'second'); diff --git a/tests/Features/Describe.php b/tests/Features/Describe.php index 64bf34535..836b0fc8b 100644 --- a/tests/Features/Describe.php +++ b/tests/Features/Describe.php @@ -96,3 +96,15 @@ expect($foo + $foo)->toBe(6); })->depends('foo'); })->with([3]); + +describe('with test after describe', function () { + beforeEach(function () { + $this->count++; + }); + + describe('foo', function () {}); + + it('should run the before each', function () { + expect($this->count)->toBe(2); + }); +}); diff --git a/tests/Features/Note.php b/tests/Features/Note.php index 85868d488..571d7c851 100644 --- a/tests/Features/Note.php +++ b/tests/Features/Note.php @@ -21,11 +21,27 @@ })->note('This is a static note'); describe('nested', function () { + beforeEach(function () { + $this->note('This is before each describe runtime note'); + })->note('This is before each describe static note'); + it('may have static note and runtime note', function () { expect(true)->toBeTrue(true); $this->note('This is a runtime note within describe'); })->note('This is a static note within describe'); + + describe('describe nested within describe', function () { + beforeEach(function () { + $this->note('This is before each nested describe runtime note'); + })->note('This is before each nested 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 nested describe'); + })->note('This is a static note within a nested describe'); + })->note('This is a nested describe static note'); })->note('This is describe static note'); test('multiple notes', function () { diff --git a/tests/Features/Repeat.php b/tests/Features/Repeat.php index 811c1185e..5a3370cd7 100644 --- a/tests/Features/Repeat.php +++ b/tests/Features/Repeat.php @@ -43,3 +43,39 @@ ->toBeNumeric() ->toBeGreaterThan(0); })->repeat(times: 2)->with(['a', 'b', 'c'], ['d', 'e', 'f']); + +describe('describe blocks', function () { + test('multiple times', function () { + expect(true)->toBeTrue(); + })->repeat(times: 3); + + describe('describe with repeat', function () { + test('test with no repeat should repeat the number of times specified in the parent describe block', function () { + expect(true)->toBeTrue(); + }); + + test('test with repeat should repeat the number of times specified in the test', function () { + expect(true)->toBeTrue(); + })->repeat(times: 2); + + describe('nested describe without repeat', function () { + test("test with no repeat should repeat the number of times specified in the parent's parent describe block", function () { + expect(true)->toBeTrue(); + }); + + test('test with repeat should repeat the number of times specified in the test', function () { + expect(true)->toBeTrue(); + })->repeat(times: 2); + }); + + describe('nested describe with repeat', function () { + test('test with no repeat should repeat the number of times specified in the parent describe block', function () { + expect(true)->toBeTrue(); + }); + + test('test with repeat should repeat the number of times specified in the test', function () { + expect(true)->toBeTrue(); + })->repeat(times: 2); + })->repeat(times: 2); + })->repeat(times: 3); +}); diff --git a/tests/Features/Skip.php b/tests/Features/Skip.php index 5ac559f96..352189458 100644 --- a/tests/Features/Skip.php +++ b/tests/Features/Skip.php @@ -54,3 +54,81 @@ return $this->shouldSkip; }) ->toBeFalse(); + +describe('skip on describe', function () { + beforeEach(function () { + $this->ran = false; + }); + + afterEach(function () { + match ($this->name()) { + '__pest_evaluable__skip_on_describe__→__skipped_tests__→__nested_inside_skipped_block__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__skip_on_describe__→__skipped_tests__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__skip_on_describe__→_it_should_execute' => expect($this->ran)->toBe(true), + default => $this->fail('Unexpected test name: '.$this->name()), + }; + }); + + describe('skipped tests', function () { + describe('nested inside skipped block', function () { + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + }); + + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + })->skip(); + + it('should execute', function () { + $this->ran = true; + expect($this->ran)->toBe(true); + }); +}); + +describe('skip on beforeEach', function () { + beforeEach(function () { + $this->ran = false; + }); + + afterEach(function () { + match ($this->name()) { + '__pest_evaluable__skip_on_beforeEach__→__skipped_tests__→__nested_inside_skipped_block__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__skip_on_beforeEach__→__skipped_tests__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__skip_on_beforeEach__→_it_should_execute' => expect($this->ran)->toBe(true), + default => $this->fail('Unexpected test name: '.$this->name()), + }; + }); + + describe('skipped tests', function () { + beforeEach()->skip(); + + describe('nested inside skipped block', function () { + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + }); + + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + }); + + it('should execute', function () { + $this->ran = true; + expect($this->ran)->toBe(true); + }); +}); + +it('does not skip after the describe block', function () { + expect(true)->toBeTrue(); +}); + +it('can skip after the describe block', function () { + expect(true)->toBeTrue(); +})->skip(); diff --git a/tests/Features/Todo.php b/tests/Features/Todo.php index 39f79e44c..f979a2ff0 100644 --- a/tests/Features/Todo.php +++ b/tests/Features/Todo.php @@ -27,3 +27,175 @@ it('may have an associated note', function () { expect(true)->toBeTrue(); })->todo(note: 'a note'); + +describe('todo on describe', function () { + beforeEach(function () { + $this->ran = false; + }); + + afterEach(function () { + match ($this->name()) { + '__pest_evaluable__todo_on_describe__→__todo_block__→__nested_inside_todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→__nested_inside_todo_block__→_it_should_set_the_note' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_without_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_with_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_as_well_as_the_note_from_the_test' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_without_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_with_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__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' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→__todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_describe__→_it_should_execute' => expect($this->ran)->toBe(true), + default => $this->fail('Unexpected test name: '.$this->name()), + }; + }); + + describe('todo block', function () { + describe('nested inside todo block', function () { + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + + it('should set the note', function () { + $this->ran = true; + $this->fail(); + })->todo(note: 'hi'); + }); + + describe('describe with note', function () { + it('should apply the note to a test without a todo', function () { + $this->ran = true; + $this->fail(); + }); + + it('should apply the note to a test with a todo', function () { + $this->ran = true; + $this->fail(); + })->todo(); + + it('should apply the note as well as the note from the test', function () { + $this->ran = true; + $this->fail(); + })->todo(note: 'test note'); + + describe('nested describe with note', function () { + it('should apply all parent notes to a test without a todo', function () { + $this->ran = true; + $this->fail(); + }); + + it('should apply all parent notes to a test with a todo', function () { + $this->ran = true; + $this->fail(); + })->todo(); + + it('should apply all parent notes as well as the note from the test', function () { + $this->ran = true; + $this->fail(); + })->todo(note: 'test note'); + })->todo(note: 'nested describe note'); + })->todo(note: 'describe note'); + + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + })->todo(); + + it('should execute', function () { + $this->ran = true; + expect($this->ran)->toBe(true); + }); +}); + +test('todo on test after describe block', function () { + $this->fail(); +})->todo(); + +test('todo with note on test after describe block', function () { + $this->fail(); +})->todo(note: 'test note'); + +describe('todo on beforeEach', function () { + beforeEach(function () { + $this->ran = false; + }); + + afterEach(function () { + match ($this->name()) { + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→__nested_inside_todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→_it_should_not_execute' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_without_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_to_a_test_with_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→_it_should_apply_the_note_as_well_as_the_note_from_the_test' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_without_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→__todo_block__→__describe_with_note__→__nested_describe_with_note__→_it_should_apply_all_parent_notes_to_a_test_with_a_todo' => expect($this->ran)->toBe(false), + '__pest_evaluable__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' => expect($this->ran)->toBe(false), + '__pest_evaluable__todo_on_beforeEach__→_it_should_execute' => expect($this->ran)->toBe(true), + default => $this->fail('Unexpected test name: '.$this->name()), + }; + }); + + describe('todo block', function () { + beforeEach()->todo(); + + describe('nested inside todo block', function () { + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + }); + + describe('describe with note', function () { + it('should apply the note to a test without a todo', function () { + $this->ran = true; + $this->fail(); + }); + + it('should apply the note to a test with a todo', function () { + $this->ran = true; + $this->fail(); + })->todo(); + + it('should apply the note as well as the note from the test', function () { + $this->ran = true; + $this->fail(); + })->todo(note: 'test note'); + + describe('nested describe with note', function () { + it('should apply all parent notes to a test without a todo', function () { + $this->ran = true; + $this->fail(); + }); + + it('should apply all parent notes to a test with a todo', function () { + $this->ran = true; + $this->fail(); + })->todo(); + + it('should apply all parent notes as well as the note from the test', function () { + $this->ran = true; + $this->fail(); + })->todo(note: 'test note'); + })->todo(note: 'nested describe note'); + })->todo(note: 'describe note'); + + it('should not execute', function () { + $this->ran = true; + $this->fail(); + }); + }); + + it('should execute', function () { + $this->ran = true; + expect($this->ran)->toBe(true); + }); +}); + +test('todo on test after describe block with beforeEach', function () { + $this->fail(); +})->todo(); + +test('todo with note on test after describe block with beforeEach', function () { + $this->fail(); +})->todo(note: 'test note'); diff --git a/tests/Unit/Support/Arr.php b/tests/Unit/Support/Arr.php new file mode 100644 index 000000000..6dc6f3bb0 --- /dev/null +++ b/tests/Unit/Support/Arr.php @@ -0,0 +1,49 @@ +toBeFalse(); + }); + + it('should return the last element for an array with a single element', function () { + expect(Arr::last([1]))->toBe(1); + }); + + it('should return the last element for an array without changing the internal pointer', function () { + $array = [1, 2, 3]; + + expect(Arr::last($array))->toBe(3); + expect(current($array))->toBe(1); + + next($array); + expect(current($array))->toBe(2); + expect(Arr::last($array))->toBe(3); + expect(current($array))->toBe(2); + }); + + it('should return the last element for an associative array without changing the internal pointer', function () { + $array = ['first' => 1, 'second' => 2, 'third' => 3]; + + expect(Arr::last($array))->toBe(3); + expect(current($array))->toBe(1); + + next($array); + expect(current($array))->toBe(2); + expect(Arr::last($array))->toBe(3); + expect(current($array))->toBe(2); + }); + + it('should return the last element for an mixed key array without changing the internal pointer', function () { + $array = ['first' => 1, 2, 'third' => 3]; + + expect(Arr::last($array))->toBe(3); + expect(current($array))->toBe(1); + + next($array); + expect(current($array))->toBe(2); + expect(Arr::last($array))->toBe(3); + expect(current($array))->toBe(2); + }); +}); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 59f7263a0..313f82088 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 17 todos, 19 skipped, 1085 passed (2624 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1134 passed (2712 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();