From f720be862ea4aa9b77ae18ed17f3bf8be1589dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20As=CC=A7an?= Date: Thu, 12 Sep 2024 02:45:37 +0300 Subject: [PATCH 01/95] Add reference method --- src/PendingCalls/TestCall.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 1b02ed1e..6f1732b7 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -48,6 +48,13 @@ final class TestCall */ private readonly bool $descriptionLess; + /** + * This property is not actually used in the codebase, it's only here to make Rector happy. + * + * @var string|array + */ + public array|string $references; + /** * Creates a new Pending Call. */ @@ -615,6 +622,21 @@ final class TestCall return $this; } + /** + * Adds a reference to the tested method or class. + * This helps to link test cases to the source code + * for easier navigation during development. + * + * @param string|array $classes + */ + public function reference(string|array ...$classes): self + { + // For rector + $this->references = $classes; // @phpstan-ignore-line + + return $this; + } + /** * Informs the test runner that no expectations happen in this test, * and its purpose is simply to check whether the given code can From bab193e7e130fd0a631ef77b90ee8c0ab23a8aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20As=CC=A7an?= Date: Thu, 12 Sep 2024 03:24:36 +0300 Subject: [PATCH 02/95] Fix property type --- src/PendingCalls/TestCall.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 6f1732b7..aa32827e 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -51,9 +51,9 @@ final class TestCall /** * This property is not actually used in the codebase, it's only here to make Rector happy. * - * @var string|array + * @var array */ - public array|string $references; + public array $references; /** * Creates a new Pending Call. @@ -632,7 +632,7 @@ final class TestCall public function reference(string|array ...$classes): self { // For rector - $this->references = $classes; // @phpstan-ignore-line + $this->references = $classes; return $this; } From 668685498fa4caca192655c1f8ef1c473ab9f5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20As=CC=A7an?= Date: Thu, 12 Sep 2024 03:51:20 +0300 Subject: [PATCH 03/95] Fix phpdoc type-hints --- src/PendingCalls/TestCall.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index aa32827e..23c2931f 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -51,7 +51,7 @@ final class TestCall /** * This property is not actually used in the codebase, it's only here to make Rector happy. * - * @var array + * @var array> */ public array $references; @@ -623,11 +623,10 @@ final class TestCall } /** - * Adds a reference to the tested method or class. - * This helps to link test cases to the source code - * for easier navigation during development. + * Adds a reference to the tested method or class. This helps to link test + * cases to the source code for easier navigation. * - * @param string|array $classes + * @param array|class-string ...$classes */ public function reference(string|array ...$classes): self { From 169b76458ecbc076eabc8be526c557dc224b58d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20As=CC=A7an?= Date: Thu, 12 Sep 2024 11:15:30 +0300 Subject: [PATCH 04/95] make the name of the method plural --- src/PendingCalls/TestCall.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 23c2931f..ad7a8a91 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -628,7 +628,7 @@ final class TestCall * * @param array|class-string ...$classes */ - public function reference(string|array ...$classes): self + public function references(string|array ...$classes): self { // For rector $this->references = $classes; From ab0b4a1b4e62eb81f3e3c86a4b0b93a67ebdfacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 12 Sep 2024 13:09:45 +0300 Subject: [PATCH 05/95] Update src/PendingCalls/TestCall.php Co-authored-by: Owen Voke --- src/PendingCalls/TestCall.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index ad7a8a91..50ddddc3 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -623,8 +623,8 @@ final class TestCall } /** - * Adds a reference to the tested method or class. This helps to link test - * cases to the source code for easier navigation. + * Adds one or more references to the tested method or class. This helps + * to link test cases to the source code for easier navigation. * * @param array|class-string ...$classes */ From 74ff3b8cd94e975ffccd26895a2a58361b35117e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20A=C5=9Fan?= Date: Thu, 12 Sep 2024 13:09:57 +0300 Subject: [PATCH 06/95] Update src/PendingCalls/TestCall.php Co-authored-by: Owen Voke --- src/PendingCalls/TestCall.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 50ddddc3..46e1acb9 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -630,8 +630,7 @@ final class TestCall */ public function references(string|array ...$classes): self { - // For rector - $this->references = $classes; + assert($classes !== []); return $this; } From ba7eb70a5d91f9a5e60750fb393a316efcf4fc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20As=CC=A7an?= Date: Thu, 12 Sep 2024 13:11:12 +0300 Subject: [PATCH 07/95] Remove unnecessary property --- src/PendingCalls/TestCall.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 46e1acb9..1208b809 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -48,13 +48,6 @@ final class TestCall */ private readonly bool $descriptionLess; - /** - * This property is not actually used in the codebase, it's only here to make Rector happy. - * - * @var array> - */ - public array $references; - /** * Creates a new Pending Call. */ From e3bfcbe5f14cded87cf7f4a169bcc7646dd829ba Mon Sep 17 00:00:00 2001 From: tal7aouy Date: Mon, 16 Sep 2024 13:36:34 +0100 Subject: [PATCH 08/95] Add slugify method --- src/Mixins/Expectation.php | 19 ++++++++++++++++++- src/Support/Str.php | 11 ++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index f802bc11..eb72fec0 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -661,7 +661,7 @@ final class Expectation { foreach ($keys as $k => $key) { if (is_array($key)) { - $this->toHaveKeys(array_keys(Arr::dot($key, $k.'.')), $message); + $this->toHaveKeys(array_keys(Arr::dot($key, $k . '.')), $message); } else { $this->toHaveKey($key, message: $message); } @@ -1159,4 +1159,21 @@ final class Expectation return $this; } + + /** + * Asserts that the value can be converted to a slug + * + * @return self + */ + public function toBeSlug(string $message = ''): self + { + if ($message === '') { + $message = "Failed asserting that {$this->value} can be converted to a slug."; + } + + $slug = Str::slugify((string) $this->value); + Assert::assertNotEmpty($slug, $message); + + return $this; + } } diff --git a/src/Support/Str.php b/src/Support/Str.php index 754749e7..ccaf06f7 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -61,7 +61,7 @@ final class Str { $code = str_replace('_', '__', $code); - $code = self::PREFIX.str_replace(' ', '_', $code); + $code = self::PREFIX . str_replace(' ', '_', $code); // sticks to PHP8.2 function naming rules https://www.php.net/manual/en/functions.user-defined.php return (string) preg_replace('/[^a-zA-Z0-9_\x80-\xff]/', '_', $code); @@ -116,4 +116,13 @@ final class Str { return (bool) filter_var($value, FILTER_VALIDATE_URL); } + + /** + * Converts the given `$target` to a URL-friendly "slug". + */ + public static function slugify(string $target): string + { + $target = preg_replace('/[^a-zA-Z0-9]+/', '-', $target); + return strtolower(trim($target, '-')); + } } From 92bc1decd92b795ae644923f20eba2a60c82f932 Mon Sep 17 00:00:00 2001 From: tal7aouy Date: Mon, 16 Sep 2024 13:41:13 +0100 Subject: [PATCH 09/95] Add tests for toBeSlug method --- tests/Features/Expect/toBeSlug.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/Features/Expect/toBeSlug.php diff --git a/tests/Features/Expect/toBeSlug.php b/tests/Features/Expect/toBeSlug.php new file mode 100644 index 00000000..2d7c19f8 --- /dev/null +++ b/tests/Features/Expect/toBeSlug.php @@ -0,0 +1,24 @@ +toBeSlug() + ->and('Another Test String')->toBeSlug(); +}); + +test('failures', function () { + expect('')->toBeSlug(); +})->throws(ExpectationFailedException::class); + +test('failures with custom message', function () { + expect('')->toBeSlug('oh no!'); +})->throws(ExpectationFailedException::class, 'oh no!'); + +test('failures with default message', function () { + expect('')->toBeSlug(); +})->throws(ExpectationFailedException::class, 'Failed asserting that can be converted to a slug.'); + +test('not failures', function () { + expect('This is a Test String!')->not->toBeSlug(); +})->throws(ExpectationFailedException::class); From 53c94600cbcde285a775f846339682497c9c2065 Mon Sep 17 00:00:00 2001 From: sebastianfaber Date: Wed, 16 Oct 2024 22:19:08 +0200 Subject: [PATCH 10/95] fix: handle -c flag same as --configuration --- src/Plugins/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/Configuration.php b/src/Plugins/Configuration.php index acae8fb4..a41a8fe0 100644 --- a/src/Plugins/Configuration.php +++ b/src/Plugins/Configuration.php @@ -34,7 +34,7 @@ final class Configuration implements HandlesArguments, Terminable */ public function handleArguments(array $arguments): array { - if ($this->hasArgument('--configuration', $arguments) || $this->hasCustomConfigurationFile()) { + if ($this->hasArgument('--configuration', $arguments) || $this->hasArgument('-c', $arguments) || $this->hasCustomConfigurationFile()) { return $arguments; } From 5331b44a1801960ada896dfc89d4b92db0117e2b Mon Sep 17 00:00:00 2001 From: olivernybroe Date: Wed, 20 Nov 2024 11:54:36 +0100 Subject: [PATCH 11/95] Allow custom arch expectations --- src/Expectation.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Expectation.php b/src/Expectation.php index ebfd6302..3951bc04 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -330,7 +330,7 @@ final class Expectation * @param array $parameters * @return Expectation|HigherOrderExpectation, TValue> */ - public function __call(string $method, array $parameters): Expectation|HigherOrderExpectation|PendingArchExpectation + public function __call(string $method, array $parameters): Expectation|HigherOrderExpectation|PendingArchExpectation|ArchExpectation { if (! self::hasMethod($method)) { if (! is_object($this->value) && method_exists(PendingArchExpectation::class, $method)) { @@ -355,6 +355,10 @@ final class Expectation $reflectionClosure = new \ReflectionFunction($closure); $expectation = $reflectionClosure->getClosureThis(); + if ($reflectionClosure->getReturnType()?->__toString() === ArchExpectation::class) { + return $closure(...$parameters); + } + assert(is_object($expectation)); ExpectationPipeline::for($closure) From 1ac594bdf0b6a00d2fc50999461daf11990d5fd1 Mon Sep 17 00:00:00 2001 From: Bakhromjon Date: Fri, 6 Dec 2024 16:07:59 +0500 Subject: [PATCH 12/95] Add Attributes to Laravel preset --- src/ArchPresets/Laravel.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ArchPresets/Laravel.php b/src/ArchPresets/Laravel.php index 26e706a9..21826786 100644 --- a/src/ArchPresets/Laravel.php +++ b/src/ArchPresets/Laravel.php @@ -166,5 +166,11 @@ final class Laravel extends AbstractPreset $this->expectations[] = expect('App\Policies') ->classes() ->toHaveSuffix('Policy'); + + $this->expectations[] = expect('App\Attributes') + ->classes() + ->toImplement('Illuminate\Contracts\Container\ContextualAttribute') + ->toHaveAttribute('Attribute') + ->toHaveMethod('resolve'); } } From 7760d945bb0732dc366314816e328b21ff742e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:06:03 +0100 Subject: [PATCH 13/95] sync latest changes --- .../Runner/ResultCache/DefaultResultCache.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/overrides/Runner/ResultCache/DefaultResultCache.php b/overrides/Runner/ResultCache/DefaultResultCache.php index 581a4c7f..fde6bad9 100644 --- a/overrides/Runner/ResultCache/DefaultResultCache.php +++ b/overrides/Runner/ResultCache/DefaultResultCache.php @@ -46,6 +46,7 @@ declare(strict_types=1); namespace PHPUnit\Runner\ResultCache; use const DIRECTORY_SEPARATOR; +use const LOCK_EX; use PHPUnit\Framework\TestStatus\TestStatus; use PHPUnit\Runner\DirectoryCannotBeCreatedException; @@ -65,6 +66,8 @@ use function json_encode; use function Pest\version; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class DefaultResultCache implements ResultCache @@ -77,12 +80,12 @@ final class DefaultResultCache implements ResultCache private readonly string $cacheFilename; /** - * @psalm-var array + * @var array */ private array $defects = []; /** - * @psalm-var array + * @var array */ private array $times = []; @@ -119,6 +122,17 @@ final class DefaultResultCache implements ResultCache return $this->times[$id] ?? 0.0; } + public function mergeWith(self $other): void + { + foreach ($other->defects as $id => $defect) { + $this->defects[$id] = $defect; + } + + foreach ($other->times as $id => $time) { + $this->times[$id] = $time; + } + } + public function load(): void { if (! is_file($this->cacheFilename)) { From 8ee9d66d802887129a175939d0ba7e82741b60e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:34:55 +0100 Subject: [PATCH 14/95] sync cs --- overrides/Logging/JUnit/JunitXmlLogger.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php index ca5c02c4..3a335f84 100644 --- a/overrides/Logging/JUnit/JunitXmlLogger.php +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -41,6 +41,8 @@ use function str_replace; use function trim; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class JunitXmlLogger @@ -59,32 +61,32 @@ final class JunitXmlLogger private array $testSuites = []; /** - * @psalm-var array + * @var array */ private array $testSuiteTests = [0]; /** - * @psalm-var array + * @var array */ private array $testSuiteAssertions = [0]; /** - * @psalm-var array + * @var array */ private array $testSuiteErrors = [0]; /** - * @psalm-var array + * @var array */ private array $testSuiteFailures = [0]; /** - * @psalm-var array + * @var array */ private array $testSuiteSkipped = [0]; /** - * @psalm-var array + * @var array */ private array $testSuiteTimes = [0]; @@ -195,17 +197,11 @@ final class JunitXmlLogger $this->createTestCase($event); } - /** - * @throws InvalidArgumentException - */ public function testPreparationFailed(): void { $this->preparationFailed = true; } - /** - * @throws InvalidArgumentException - */ public function testPrepared(): void { $this->prepared = true; @@ -431,7 +427,7 @@ final class JunitXmlLogger /** * @throws InvalidArgumentException * - * @psalm-assert !null $this->currentTestCase + * @phpstan-assert !null $this->currentTestCase */ private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void { From fe4b5e5e1fa5e65c345b71328d1bc44502b11d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:35:44 +0100 Subject: [PATCH 15/95] sync change --- overrides/Logging/JUnit/JunitXmlLogger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php index 3a335f84..7f59b4a7 100644 --- a/overrides/Logging/JUnit/JunitXmlLogger.php +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -115,7 +115,7 @@ final class JunitXmlLogger public function flush(): void { - $this->printer->print($this->document->saveXML()); + $this->printer->print($this->document->saveXML() ?: ''); $this->printer->flush(); } From 0cb8c42497a7d34c1239d73cdaeb1330667ecf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:36:48 +0100 Subject: [PATCH 16/95] sync missing listener --- overrides/Logging/JUnit/JunitXmlLogger.php | 1 + 1 file changed, 1 insertion(+) diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php index 7f59b4a7..21b73c62 100644 --- a/overrides/Logging/JUnit/JunitXmlLogger.php +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -304,6 +304,7 @@ final class JunitXmlLogger new TestFinishedSubscriber($this), new TestErroredSubscriber($this), new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), new TestSkippedSubscriber($this), new TestRunnerExecutionFinishedSubscriber($this), ); From 23f130b0f900469df7e693459b97e828f8eae3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:38:24 +0100 Subject: [PATCH 17/95] Update JunitXmlLogger.php from https://github.com/sebastianbergmann/phpunit/issues/5771 https://github.com/sebastianbergmann/phpunit/commit/c722fb259972a6c55fbb295169fd1a000b3d4c88 --- overrides/Logging/JUnit/JunitXmlLogger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php index 21b73c62..d90ab723 100644 --- a/overrides/Logging/JUnit/JunitXmlLogger.php +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -212,7 +212,7 @@ final class JunitXmlLogger */ public function testFinished(Finished $event): void { - if ($this->preparationFailed) { + if (!$this->prepared || $this->preparationFailed) { return; } From e834527db2aa2dac5596faa8dd832874b3b4baeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:39:10 +0100 Subject: [PATCH 18/95] Update JunitXmlLogger.php https://github.com/sebastianbergmann/phpunit/issues/6098 --- overrides/Logging/JUnit/JunitXmlLogger.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php index d90ab723..433bc05b 100644 --- a/overrides/Logging/JUnit/JunitXmlLogger.php +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -27,6 +27,7 @@ use PHPUnit\Event\Test\Finished; use PHPUnit\Event\Test\MarkedIncomplete; use PHPUnit\Event\Test\PreparationStarted; use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PrintedUnexpectedOutput; use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\TestSuite\Started; use PHPUnit\Event\UnknownSubscriberTypeException; @@ -207,6 +208,18 @@ final class JunitXmlLogger $this->prepared = true; } + public function testPrintedUnexpectedOutput(PrintedUnexpectedOutput $event): void + { + assert($this->currentTestCase !== null); + + $systemOut = $this->document->createElement( + 'system-out', + Xml::prepareString($event->output()), + ); + + $this->currentTestCase->appendChild($systemOut); + } + /** * @throws InvalidArgumentException */ @@ -301,6 +314,7 @@ final class JunitXmlLogger new TestPreparationStartedSubscriber($this), new TestPreparationFailedSubscriber($this), new TestPreparedSubscriber($this), + new TestPrintedUnexpectedOutputSubscriber($this), new TestFinishedSubscriber($this), new TestErroredSubscriber($this), new TestFailedSubscriber($this), From c4c9e915f46c934cc68071fb0e6c9811f20d60e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Mon, 20 Jan 2025 09:50:36 +0100 Subject: [PATCH 19/95] cs --- overrides/Logging/JUnit/JunitXmlLogger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php index 433bc05b..b7362b1f 100644 --- a/overrides/Logging/JUnit/JunitXmlLogger.php +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -225,7 +225,7 @@ final class JunitXmlLogger */ public function testFinished(Finished $event): void { - if (!$this->prepared || $this->preparationFailed) { + if (! $this->prepared || $this->preparationFailed) { return; } From e4aab77a349d42e75d3d26861c56341d85626a18 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 23 Jan 2025 12:51:02 +0000 Subject: [PATCH 20/95] release: 3.7.3 --- composer.json | 2 +- src/Pest.php | 2 +- .../Visual/Help/visual_snapshot_of_help_command_output.snap | 2 +- .../Visual/Version/visual_snapshot_of_help_command_output.snap | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 3e2adf42..d8f71e4b 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "require": { "php": "^8.2.0", "brianium/paratest": "^7.7.0", - "nunomaduro/collision": "^8.5.0", + "nunomaduro/collision": "^8.6.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", diff --git a/src/Pest.php b/src/Pest.php index d6e5a8b1..d1bf016f 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.7.2'; + return '3.7.3'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 76123350..a39a4850 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.7.2. + Pest Testing Framework 3.7.3. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 79cd97b4..d2ac30b1 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.7.2. + Pest Testing Framework 3.7.3. From 4079a08f5f35eb1694bfd1a4ec153afa4256a189 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 23 Jan 2025 13:59:51 +0000 Subject: [PATCH 21/95] feat: adds `--compact` to coverage --- src/Plugins/Coverage.php | 11 ++++++++++- src/Support/Coverage.php | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Plugins/Coverage.php b/src/Plugins/Coverage.php index 75b06320..a5061d25 100644 --- a/src/Plugins/Coverage.php +++ b/src/Plugins/Coverage.php @@ -37,6 +37,11 @@ final class Coverage implements AddsOutput, HandlesArguments */ public bool $coverage = false; + /** + * Whether it should show the coverage or not. + */ + public bool $compact = false; + /** * The minimum coverage. */ @@ -124,6 +129,10 @@ final class Coverage implements AddsOutput, HandlesArguments $this->coverageExactly = (float) $exactlyOption; } + if ($_SERVER['COLLISION_PRINTER_COMPACT'] ?? false) { + $this->compact = true; + } + return $originals; } @@ -144,7 +153,7 @@ final class Coverage implements AddsOutput, HandlesArguments exit(1); } - $coverage = \Pest\Support\Coverage::report($this->output); + $coverage = \Pest\Support\Coverage::report($this->output, $this->compact); $exitCode = (int) ($coverage < $this->coverageMin); if ($exitCode === 0 && $this->coverageExactly !== null) { diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 302e00b1..955bbfc4 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -74,7 +74,7 @@ final class Coverage * Reports the code coverage report to the * console and returns the result in float. */ - public static function report(OutputInterface $output): float + public static function report(OutputInterface $output, bool $compact = false): float { if (! file_exists($reportPath = self::getPath())) { if (self::usingXdebug()) { @@ -113,6 +113,10 @@ final class Coverage ? '100.0' : number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', ''); + if ($percentage === '100.0' && $compact) { + continue; + } + $uncoveredLines = ''; $percentageOfExecutedLinesAsString = $file->percentageOfExecutedLines()->asString(); From 4a987d3d5c4e3ba36c76fecbf56113baac2d1b2b Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 23 Jan 2025 14:03:29 +0000 Subject: [PATCH 22/95] release: 3.7.4 --- composer.json | 2 +- src/Pest.php | 2 +- .../Visual/Help/visual_snapshot_of_help_command_output.snap | 2 +- .../Visual/Version/visual_snapshot_of_help_command_output.snap | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index d8f71e4b..479c8ad5 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "require": { "php": "^8.2.0", "brianium/paratest": "^7.7.0", - "nunomaduro/collision": "^8.6.0", + "nunomaduro/collision": "^8.6.1", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", diff --git a/src/Pest.php b/src/Pest.php index d1bf016f..11a52114 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.7.3'; + return '3.7.4'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index a39a4850..106d718c 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.7.3. + Pest Testing Framework 3.7.4. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index d2ac30b1..63be8fd3 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.7.3. + Pest Testing Framework 3.7.4. From fa4098db8d06ac5c49e32009b7e84c65fed8b148 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 3 Feb 2025 13:30:45 +0000 Subject: [PATCH 23/95] Bumps dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 479c8ad5..bd8c21e3 100644 --- a/composer.json +++ b/composer.json @@ -24,11 +24,11 @@ "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-mutate": "^3.0.5", - "phpunit/phpunit": "^11.5.3" + "phpunit/phpunit": "^11.5.6" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.3", + "phpunit/phpunit": ">11.5.6", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, From 66ceb64faa70c32664a395375abd663e1d9c82c5 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 3 Feb 2025 13:36:47 +0000 Subject: [PATCH 24/95] Updates tests --- tests/.snapshots/success.txt | 12 +----------- tests/Overrides/VersionsTest.php | 18 ------------------ tests/Visual/Parallel.php | 2 +- 3 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 tests/Overrides/VersionsTest.php diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 90b1b085..513a804e 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1428,16 +1428,6 @@ PASS Tests\Hooks\BeforeEachTest ✓ global beforeEach execution order - PASS Tests\Overrides\VersionsTest - ✓ versions with dataset "Runner/Filter/NameFilterIterator.php" - ✓ versions with dataset "Runner/ResultCache/DefaultResultCache.php" - ✓ versions with dataset "Runner/TestSuiteLoader.php" - ✓ versions with dataset "TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php" - ✓ versions with dataset "TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php" - ✓ versions with dataset "TextUI/TestSuiteFilterProcessor.php" - ✓ versions with dataset "Event/Value/ThrowableBuilder.php" - ✓ versions with dataset "Logging/JUnit/JunitXmlLogger.php" - PASS Tests\PHPUnit\CustomAffixes\InvalidTestName ✓ it runs file names like @#$%^&()-_=+.php @@ -1708,4 +1698,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1152 passed (2744 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/Overrides/VersionsTest.php b/tests/Overrides/VersionsTest.php deleted file mode 100644 index 1fdd63c0..00000000 --- a/tests/Overrides/VersionsTest.php +++ /dev/null @@ -1,18 +0,0 @@ -toBe($expectedHash); -})->with(function () { - foreach (BootOverrides::FILES as $hash => $file) { - $path = implode(DIRECTORY_SEPARATOR, [ - dirname(__DIR__, 2), - 'vendor/phpunit/phpunit/src', - $file, - ]); - yield $file => [$path, $hash]; - } -}); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 036f51d7..313f8208 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1142 passed (2720 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1134 passed (2712 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From a5317c56406216edceb46a60a311fe0273a176d3 Mon Sep 17 00:00:00 2001 From: Candra Sudirman Date: Fri, 7 Feb 2025 23:51:30 +0700 Subject: [PATCH 25/95] fix: add ignoring clause for `App\Features\Concerns` on Laravel Preset --- src/ArchPresets/Laravel.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ArchPresets/Laravel.php b/src/ArchPresets/Laravel.php index 26e706a9..4756bd0f 100644 --- a/src/ArchPresets/Laravel.php +++ b/src/ArchPresets/Laravel.php @@ -35,7 +35,8 @@ final class Laravel extends AbstractPreset ->ignoring('App\Features\Concerns'); $this->expectations[] = expect('App\Features') - ->toHaveMethod('resolve'); + ->toHaveMethod('resolve') + ->ignoring('App\Features\Concerns'); $this->expectations[] = expect('App\Exceptions') ->classes() From 1ad30a97b3df2dbf79819e70f80f7d8242504111 Mon Sep 17 00:00:00 2001 From: AFS Date: Tue, 25 Feb 2025 15:34:37 +0100 Subject: [PATCH 26/95] toMatchArray/Object wrong field fix The functions toMatchArray and toMatchObject indicate that the wrong field is mismatching from the second loop on because the 'message' is overwritten and taken into the following loop. This patch creates a $second_message for the second test (value test) to keep the error message correct. --- src/Mixins/Expectation.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index f802bc11..3a20c672 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -780,17 +780,16 @@ final class Expectation } foreach ($array as $key => $value) { + $message = ''; Assert::assertArrayHasKey($key, $valueAsArray, $message); - if ($message === '') { - $message = sprintf( - 'Failed asserting that an array has a key %s with the value %s.', - $this->export($key), - $this->export($valueAsArray[$key]), - ); - } + $second_message = $message !== '' ? $message : sprintf( + 'Failed asserting that an array has a key %s with the value %s.', + $this->export($key), + $this->export($valueAsArray[$key]), + ); - Assert::assertEquals($value, $valueAsArray[$key], $message); + Assert::assertEquals($value, $valueAsArray[$key], $second_message); } return $this; @@ -815,15 +814,14 @@ final class Expectation /* @phpstan-ignore-next-line */ $propertyValue = $this->value->{$property}; - if ($message === '') { - $message = sprintf( + $second_message = $message !== '' ? $message : sprintf( 'Failed asserting that an object has a property %s with the value %s.', $this->export($property), $this->export($propertyValue), ); } - Assert::assertEquals($value, $propertyValue, $message); + Assert::assertEquals($value, $propertyValue, $second_message); } return $this; From 0bdaef29e97fbc450ffee1308b1fd4d0665ec570 Mon Sep 17 00:00:00 2001 From: AFS Date: Tue, 25 Feb 2025 15:41:17 +0100 Subject: [PATCH 27/95] Fix lingering } Remove remaining } in toMatchObject. --- src/Mixins/Expectation.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index 3a20c672..e3393396 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -815,11 +815,10 @@ final class Expectation $propertyValue = $this->value->{$property}; $second_message = $message !== '' ? $message : sprintf( - 'Failed asserting that an object has a property %s with the value %s.', - $this->export($property), - $this->export($propertyValue), - ); - } + 'Failed asserting that an object has a property %s with the value %s.', + $this->export($property), + $this->export($propertyValue), + ); Assert::assertEquals($value, $propertyValue, $second_message); } From 2c3a53f6cd198bd62b2d0984e3e3f79f7c1d69b7 Mon Sep 17 00:00:00 2001 From: AFS Date: Tue, 25 Feb 2025 15:44:32 +0100 Subject: [PATCH 28/95] Remove reset of message Reset approach was not the right one. --- src/Mixins/Expectation.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index e3393396..7bb7787c 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -780,7 +780,6 @@ final class Expectation } foreach ($array as $key => $value) { - $message = ''; Assert::assertArrayHasKey($key, $valueAsArray, $message); $second_message = $message !== '' ? $message : sprintf( From 157a753d8738c28d7dbc48c9bcb0d0828fe802da Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 12 Mar 2025 18:35:57 +0000 Subject: [PATCH 29/95] Update Pest.php.stub --- stubs/init-laravel/Pest.php.stub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/init-laravel/Pest.php.stub b/stubs/init-laravel/Pest.php.stub index 60f04a45..40d096b5 100644 --- a/stubs/init-laravel/Pest.php.stub +++ b/stubs/init-laravel/Pest.php.stub @@ -12,7 +12,7 @@ */ pest()->extend(Tests\TestCase::class) - // ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) + ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) ->in('Feature'); /* From ed70c9dc2b6ed2ded11a2784f3a80963ddf9b87d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 3 Feb 2025 13:44:32 +0000 Subject: [PATCH 30/95] refactor: type adjustments --- src/Expectations/OppositeExpectation.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php index ea7328b9..59a25b4a 100644 --- a/src/Expectations/OppositeExpectation.php +++ b/src/Expectations/OppositeExpectation.php @@ -286,7 +286,7 @@ final readonly class OppositeExpectation $methods === [] ? 'not to have public methods' : sprintf("not to have public methods besides '%s'", implode("', '", $methods)), - FileLineFinder::where(fn (string $line): bool => str_contains($line, $state->contains)), + FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)), ); } @@ -329,7 +329,7 @@ final readonly class OppositeExpectation $methods === [] ? 'not to have protected methods' : sprintf("not to have protected methods besides '%s'", implode("', '", $methods)), - FileLineFinder::where(fn (string $line): bool => str_contains($line, $state->contains)), + FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)), ); } @@ -372,7 +372,7 @@ final readonly class OppositeExpectation $methods === [] ? 'not to have private methods' : sprintf("not to have private methods besides '%s'", implode("', '", $methods)), - FileLineFinder::where(fn (string $line): bool => str_contains($line, $state->contains)), + FileLineFinder::where(fn (string $line): bool => str_contains($line, (string) $state->contains)), ); } From 490f321a0dc5750ac391e38ccd66dc96bbad4683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Birkl=C3=A9?= Date: Wed, 19 Mar 2025 15:59:45 +0100 Subject: [PATCH 31/95] fix: normalize snapshot paths for files outside tests directory --- src/Repositories/SnapshotRepository.php | 15 ++++++++++++++- src/TestSuite.php | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Repositories/SnapshotRepository.php b/src/Repositories/SnapshotRepository.php index 171fd88f..9a33a5c5 100644 --- a/src/Repositories/SnapshotRepository.php +++ b/src/Repositories/SnapshotRepository.php @@ -19,6 +19,7 @@ final class SnapshotRepository * Creates a snapshot repository instance. */ public function __construct( + readonly private string $rootPath, readonly private string $testsPath, readonly private string $snapshotsPath, ) {} @@ -103,7 +104,19 @@ final class SnapshotRepository */ private function getSnapshotFilename(): string { - $relativePath = str_replace($this->testsPath, '', TestSuite::getInstance()->getFilename()); + $testFile = TestSuite::getInstance()->getFilename(); + + if (str_starts_with($testFile, $this->testsPath)) { + // if the test file is in the tests directory + $startPath = $this->testsPath; + } else { + // if the test file is in the app, src, etc. directory + $startPath = $this->rootPath; + } + + // relative path: we use substr() and not str_replace() to remove the start path + // for instance, if the $startPath is /app/ and the $testFile is /app/app/tests/Unit/ExampleTest.php, we should only remove the first /app/ from the path + $relativePath = substr($testFile, strlen($startPath)); // remove extension from filename $relativePath = substr($relativePath, 0, (int) strrpos($relativePath, '.')); diff --git a/src/TestSuite.php b/src/TestSuite.php index ca35a040..e96a6ba9 100644 --- a/src/TestSuite.php +++ b/src/TestSuite.php @@ -78,6 +78,7 @@ final class TestSuite $this->afterAll = new AfterAllRepository; $this->rootPath = (string) realpath($rootPath); $this->snapshots = new SnapshotRepository( + $this->rootPath, implode(DIRECTORY_SEPARATOR, [$this->rootPath, $this->testPath]), implode(DIRECTORY_SEPARATOR, ['.pest', 'snapshots']), ); From e46d499384a5404601b172ec509d079daae471bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Birkl=C3=A9?= Date: Wed, 19 Mar 2025 16:33:44 +0100 Subject: [PATCH 32/95] add tests for snapshots external to the tests directory --- phpunit.xml | 1 + .../Features/Expect/toMatchSnapshot.php | 35 +++++++++++++++++++ ...tas_set_value______my_datas_set_value__.snap | 7 ++++ ...s_set_value______my_datas_set_value__.snap | 7 ++++ tests/Features/Expect/toMatchSnapshot.php | 6 ++-- 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 tests-external/Features/Expect/toMatchSnapshot.php create mode 100644 tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/_within_describe__→_pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap create mode 100644 tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap diff --git a/phpunit.xml b/phpunit.xml index 4aac1aa2..122f54e2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,6 +16,7 @@ ./tests + ./tests-external ./tests/.snapshots ./tests/.tests ./tests/Fixtures/Inheritance diff --git a/tests-external/Features/Expect/toMatchSnapshot.php b/tests-external/Features/Expect/toMatchSnapshot.php new file mode 100644 index 00000000..db7b9666 --- /dev/null +++ b/tests-external/Features/Expect/toMatchSnapshot.php @@ -0,0 +1,35 @@ +snapshotable = <<<'HTML' +
+
+
+

Snapshot

+
+
+
+ HTML; +}); + +test('pass with dataset', function ($data) { + TestSuite::getInstance()->snapshots->save($this->snapshotable); + [$filename] = TestSuite::getInstance()->snapshots->get(); + + expect($filename)->toStartWith('tests/.pest/snapshots-external/') + ->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') + ->and($this->snapshotable)->toMatchSnapshot(); +})->with(['my-datas-set-value']); + +describe('within describe', function () { + test('pass with dataset', function ($data) { + TestSuite::getInstance()->snapshots->save($this->snapshotable); + [$filename] = TestSuite::getInstance()->snapshots->get(); + + expect($filename)->toStartWith('tests/.pest/snapshots-external/') + ->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') + ->and($this->snapshotable)->toMatchSnapshot(); + }); +})->with(['my-datas-set-value']); \ No newline at end of file diff --git a/tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/_within_describe__→_pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap b/tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/_within_describe__→_pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap new file mode 100644 index 00000000..c2b4dc0a --- /dev/null +++ b/tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/_within_describe__→_pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap @@ -0,0 +1,7 @@ +
+
+
+

Snapshot

+
+
+
\ No newline at end of file diff --git a/tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap b/tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap new file mode 100644 index 00000000..c2b4dc0a --- /dev/null +++ b/tests/.pest/snapshots-external/Features/Expect/toMatchSnapshot/pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap @@ -0,0 +1,7 @@ +
+
+
+

Snapshot

+
+
+
\ No newline at end of file diff --git a/tests/Features/Expect/toMatchSnapshot.php b/tests/Features/Expect/toMatchSnapshot.php index c3df8bac..c2515829 100644 --- a/tests/Features/Expect/toMatchSnapshot.php +++ b/tests/Features/Expect/toMatchSnapshot.php @@ -74,7 +74,8 @@ test('pass with dataset', function ($data) { TestSuite::getInstance()->snapshots->save($this->snapshotable); [$filename] = TestSuite::getInstance()->snapshots->get(); - expect($filename)->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') + expect($filename)->toStartWith('tests/.pest/snapshots/') + ->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') ->and($this->snapshotable)->toMatchSnapshot(); })->with(['my-datas-set-value']); @@ -83,7 +84,8 @@ describe('within describe', function () { TestSuite::getInstance()->snapshots->save($this->snapshotable); [$filename] = TestSuite::getInstance()->snapshots->get(); - expect($filename)->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') + expect($filename)->toStartWith('tests/.pest/snapshots/') + ->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') ->and($this->snapshotable)->toMatchSnapshot(); }); })->with(['my-datas-set-value']); From f68d11ccae4e2be0f26a0ffe7c47fabec7d4602f Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 29 Mar 2025 17:44:06 +0000 Subject: [PATCH 33/95] chore: bumps dependencies --- composer.json | 12 +- phpstan-baseline.neon | 199 +++++++++++++++++++ phpstan.neon | 8 +- src/Factories/TestCaseMethodFactory.php | 2 +- src/Mixins/Expectation.php | 1 - src/Panic.php | 2 +- src/PendingCalls/DescribeCall.php | 2 +- src/PendingCalls/TestCall.php | 2 +- src/Repositories/DatasetsRepository.php | 4 +- src/Support/Closure.php | 2 +- src/Support/Exporter.php | 1 + src/Support/HigherOrderMessage.php | 10 +- src/Support/HigherOrderMessageCollection.php | 1 - src/Support/HigherOrderTapProxy.php | 4 +- 14 files changed, 219 insertions(+), 31 deletions(-) create mode 100644 phpstan-baseline.neon diff --git a/composer.json b/composer.json index bd8c21e3..778d391d 100644 --- a/composer.json +++ b/composer.json @@ -19,16 +19,16 @@ "require": { "php": "^8.2.0", "brianium/paratest": "^7.7.0", - "nunomaduro/collision": "^8.6.1", + "nunomaduro/collision": "^8.7.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-mutate": "^3.0.5", - "phpunit/phpunit": "^11.5.6" + "phpunit/phpunit": "^11.5.15" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.6", + "phpunit/phpunit": ">11.5.15", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, @@ -53,9 +53,9 @@ ] }, "require-dev": { - "pestphp/pest-dev-tools": "^3.3.0", - "pestphp/pest-plugin-type-coverage": "^3.2.3", - "symfony/process": "^7.2.0" + "pestphp/pest-dev-tools": "^3.4.0", + "pestphp/pest-plugin-type-coverage": "^3.5.0", + "symfony/process": "^7.2.5" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..99bbdec9 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,199 @@ +parameters: + ignoreErrors: + - + message: '#^Parameter \#1 of callable callable\(Pest\\Expectation\\)\: Pest\\Arch\\Contracts\\ArchExpectation expects Pest\\Expectation\, Pest\\Expectation\ given\.$#' + identifier: argument.type + count: 1 + path: src/ArchPresets/AbstractPreset.php + + - + message: '#^Trait Pest\\Concerns\\Expectable is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: src/Concerns/Expectable.php + + - + message: '#^Trait Pest\\Concerns\\Logging\\WritesToConsole is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: src/Concerns/Logging/WritesToConsole.php + + - + message: '#^Trait Pest\\Concerns\\Testable is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: src/Concerns/Testable.php + + - + message: '#^Loose comparison using \!\= between \(Closure\|null\) and false will always evaluate to false\.$#' + identifier: notEqual.alwaysFalse + count: 1 + path: src/Expectation.php + + - + message: '#^Method Pest\\Expectation\:\:and\(\) should return Pest\\Expectation\ but returns \(Pest\\Expectation&TAndValue\)\|Pest\\Expectation\\.$#' + identifier: return.type + count: 1 + path: src/Expectation.php + + - + message: '#^PHPDoc tag @property for property Pest\\Expectation\:\:\$each contains generic class Pest\\Expectations\\EachExpectation but does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: src/Expectation.php + + - + message: '#^PHPDoc tag @property for property Pest\\Expectation\:\:\$not contains generic class Pest\\Expectations\\OppositeExpectation but does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: src/Expectation.php + + - + message: '#^Parameter \#2 \$newScope of method Closure\:\:bindTo\(\) expects ''static''\|class\-string\|object\|null, string given\.$#' + identifier: argument.type + count: 1 + path: src/Expectation.php + + - + message: '#^Function expect\(\) should return Pest\\Expectation\ but returns Pest\\Expectation\\.$#' + identifier: return.type + count: 1 + path: src/Functions.php + + - + message: '#^Parameter \#1 \$argv of method PHPUnit\\TextUI\\Application\:\:run\(\) expects list\, array\ given\.$#' + identifier: argument.type + count: 1 + path: src/Kernel.php + + - + message: '#^Call to an undefined method object&TValue of mixed\:\:__toString\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Mixins/Expectation.php + + - + message: '#^Call to an undefined method object&TValue of mixed\:\:toArray\(\)\.$#' + identifier: method.notFound + count: 4 + path: src/Mixins/Expectation.php + + - + message: '#^Call to an undefined method object&TValue of mixed\:\:toSnapshot\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Mixins/Expectation.php + + - + message: '#^Call to an undefined method object&TValue of mixed\:\:toString\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Mixins/Expectation.php + + - + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertTrue\(\) with true will always evaluate to true\.$#' + identifier: staticMethod.alreadyNarrowedType + count: 2 + path: src/Mixins/Expectation.php + + - + message: '#^PHPDoc tag @var with type callable\(\)\: bool is not subtype of native type Closure\|null\.$#' + identifier: varTag.nativeType + count: 1 + path: src/PendingCalls/TestCall.php + + - + message: '#^Parameter \#1 \$argv of class Symfony\\Component\\Console\\Input\\ArgvInput constructor expects list\\|null, array\ given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel.php + + - + message: '#^Parameter \#13 \$testRunnerTriggeredDeprecationEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#14 \$testRunnerTriggeredWarningEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#15 \$errors of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#16 \$deprecations of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#17 \$notices of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#18 \$warnings of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#19 \$phpDeprecations of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#20 \$phpNotices of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#21 \$phpWarnings of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#4 \$testErroredEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#5 \$testFailedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#7 \$testSuiteSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#8 \$testSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Parameter \#9 \$testMarkedIncompleteEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php + + - + message: '#^Property Pest\\Plugins\\Parallel\\Paratest\\WrapperRunner\:\:\$pending \(list\\) does not accept array\\.$#' + identifier: assign.propertyType + count: 1 + path: src/Plugins/Parallel/Paratest/WrapperRunner.php diff --git a/phpstan.neon b/phpstan.neon index 9ed48871..391daf0b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,14 +1,12 @@ includes: - - vendor/phpstan/phpstan-strict-rules/rules.neon - - vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon + - phpstan-baseline.neon parameters: - level: max + level: 7 paths: - src - checkMissingIterableValueType: true - reportUnmatchedIgnoredErrors: true + reportUnmatchedIgnoredErrors: false ignoreErrors: - "#type mixed is not subtype of native#" diff --git a/src/Factories/TestCaseMethodFactory.php b/src/Factories/TestCaseMethodFactory.php index fb763c75..bac50071 100644 --- a/src/Factories/TestCaseMethodFactory.php +++ b/src/Factories/TestCaseMethodFactory.php @@ -155,7 +155,7 @@ final class TestCaseMethodFactory assert($testCase instanceof TestCaseFactory); $method = $this; - return function (...$arguments) use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line + return function (...$arguments) use ($testCase, $method, $closure): mixed { /* @var TestCase $this */ $testCase->proxies->proxy($this); $method->proxies->proxy($this); diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index f802bc11..1566b59c 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -183,7 +183,6 @@ final class Expectation { foreach ($needles as $needle) { if (is_string($this->value)) { - // @phpstan-ignore-next-line Assert::assertStringContainsString((string) $needle, $this->value); } else { if (! is_iterable($this->value)) { diff --git a/src/Panic.php b/src/Panic.php index aca23b5e..a204472c 100644 --- a/src/Panic.php +++ b/src/Panic.php @@ -46,7 +46,7 @@ final readonly class Panic { try { $output = Container::getInstance()->get(OutputInterface::class); - } catch (Throwable) { // @phpstan-ignore-line + } catch (Throwable) { $output = new ConsoleOutput; } diff --git a/src/PendingCalls/DescribeCall.php b/src/PendingCalls/DescribeCall.php index b015595c..de472960 100644 --- a/src/PendingCalls/DescribeCall.php +++ b/src/PendingCalls/DescribeCall.php @@ -78,7 +78,7 @@ final class DescribeCall $this->currentBeforeEachCall->describing[] = $this->description; } - $this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line + $this->currentBeforeEachCall->{$name}(...$arguments); return $this; } diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index a22ff11d..50fef3b6 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -224,7 +224,7 @@ final class TestCall // @phpstan-ignore-line */ public function only(): self { - Only::enable($this, ...func_get_args()); // @phpstan-ignore-line + Only::enable($this, ...func_get_args()); return $this; } diff --git a/src/Repositories/DatasetsRepository.php b/src/Repositories/DatasetsRepository.php index 1c296fc9..7d318a2d 100644 --- a/src/Repositories/DatasetsRepository.php +++ b/src/Repositories/DatasetsRepository.php @@ -71,7 +71,7 @@ final class DatasetsRepository * * @throws ShouldNotHappen */ - public static function get(string $filename, string $description): Closure|array + public static function get(string $filename, string $description): Closure|array // @phpstan-ignore-line { $dataset = self::$withs[$filename.self::SEPARATOR.$description]; @@ -110,7 +110,6 @@ final class DatasetsRepository foreach ($datasetCombination as $datasetCombinationElement) { $partialDescriptions[] = $datasetCombinationElement['label']; - // @phpstan-ignore-next-line $values = array_merge($values, $datasetCombinationElement['values']); } @@ -221,7 +220,6 @@ final class DatasetsRepository $result = $tmp; } - // @phpstan-ignore-next-line return $result; } diff --git a/src/Support/Closure.php b/src/Support/Closure.php index e96ec29e..e447903f 100644 --- a/src/Support/Closure.php +++ b/src/Support/Closure.php @@ -15,7 +15,6 @@ final class Closure /** * Binds the given closure to the given "this". * - * * @throws ShouldNotHappen */ public static function bind(?BaseClosure $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure @@ -24,6 +23,7 @@ final class Closure throw ShouldNotHappen::fromMessage('Could not bind null closure.'); } + // @phpstan-ignore-next-line $closure = BaseClosure::bind($closure, $newThis, $newScope); if (! $closure instanceof \Closure) { diff --git a/src/Support/Exporter.php b/src/Support/Exporter.php index a486445f..169f4891 100644 --- a/src/Support/Exporter.php +++ b/src/Support/Exporter.php @@ -66,6 +66,7 @@ final readonly class Exporter $result[] = $context->contains($data[$key]) !== false ? '*RECURSION*' + // @phpstan-ignore-next-line : sprintf('[%s]', $this->shortenedRecursiveExport($data[$key], $context)); } diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index aefc356e..89c3e1f1 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -50,14 +50,13 @@ final class HigherOrderMessage } if ($this->hasHigherOrderCallable()) { - /* @phpstan-ignore-next-line */ return (new HigherOrderCallables($target))->{$this->name}(...$this->arguments); } try { return is_array($this->arguments) ? Reflection::call($target, $this->name, $this->arguments) - : $target->{$this->name}; /* @phpstan-ignore-line */ + : $target->{$this->name}; } catch (Throwable $throwable) { Reflection::setPropertyValue($throwable, 'file', $this->filename); Reflection::setPropertyValue($throwable, 'line', $this->line); @@ -65,7 +64,6 @@ final class HigherOrderMessage if ($throwable->getMessage() === $this->getUndefinedMethodMessage($target, $this->name)) { /** @var ReflectionClass $reflection */ $reflection = new ReflectionClass($target); - /* @phpstan-ignore-next-line */ $reflection = $reflection->getParentClass() ?: $reflection; Reflection::setPropertyValue($throwable, 'message', sprintf('Call to undefined method %s::%s()', $reflection->getName(), $this->name)); } @@ -96,10 +94,6 @@ final class HigherOrderMessage private function getUndefinedMethodMessage(object $target, string $methodName): string { - if (\PHP_MAJOR_VERSION >= 8) { - return sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', $target::class, $methodName)); - } - - return sprintf(self::UNDEFINED_METHOD, $methodName); + return sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', $target::class, $methodName)); } } diff --git a/src/Support/HigherOrderMessageCollection.php b/src/Support/HigherOrderMessageCollection.php index da13a16c..41245108 100644 --- a/src/Support/HigherOrderMessageCollection.php +++ b/src/Support/HigherOrderMessageCollection.php @@ -40,7 +40,6 @@ final class HigherOrderMessageCollection public function chain(object $target): void { foreach ($this->messages as $message) { - // @phpstan-ignore-next-line $target = $message->call($target) ?? $target; } } diff --git a/src/Support/HigherOrderTapProxy.php b/src/Support/HigherOrderTapProxy.php index 151b2b80..08eb5ea7 100644 --- a/src/Support/HigherOrderTapProxy.php +++ b/src/Support/HigherOrderTapProxy.php @@ -26,7 +26,7 @@ final class HigherOrderTapProxy */ public function __set(string $property, mixed $value): void { - $this->target->{$property} = $value; // @phpstan-ignore-line + $this->target->{$property} = $value; } /** @@ -37,7 +37,7 @@ final class HigherOrderTapProxy public function __get(string $property) { if (property_exists($this->target, $property)) { - return $this->target->{$property}; // @phpstan-ignore-line + return $this->target->{$property}; } $className = (new ReflectionClass($this->target))->getName(); From 003fc96e8f9f53c2930b0f21582c9a5757285ddc Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 29 Mar 2025 17:48:00 +0000 Subject: [PATCH 34/95] release: 3.7.5 --- src/Pest.php | 2 +- .../Visual/Help/visual_snapshot_of_help_command_output.snap | 2 +- .../Visual/Version/visual_snapshot_of_help_command_output.snap | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Pest.php b/src/Pest.php index 11a52114..d8ac40bf 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.7.4'; + return '3.7.5'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 106d718c..5834ea3b 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.7.4. + Pest Testing Framework 3.7.5. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 63be8fd3..6e4c46c9 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.7.4. + Pest Testing Framework 3.7.5. From 4969526ef2841118aa9d8b13ae2dad40f3d91492 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 29 Mar 2025 17:57:53 +0000 Subject: [PATCH 35/95] chore: bumps paratest --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 778d391d..6b9856db 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { "php": "^8.2.0", - "brianium/paratest": "^7.7.0", + "brianium/paratest": "^7.8.3", "nunomaduro/collision": "^8.7.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", From 2e11e9e65d71d73ce70dbfd73627627c50392122 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 29 Mar 2025 18:23:23 +0000 Subject: [PATCH 36/95] docs: adjusts readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f030829..e9f2faee 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,13 @@ **Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP. - Explore our docs at **[pestphp.com »](https://pestphp.com)** -- Follow us on Twitter at **[@pestphp »](https://twitter.com/pestphp)** -- Join us at **[discord.gg/kaHY6p54JH »](https://discord.gg/kaHY6p54JH)** or **[t.me/+kYH5G4d5MV83ODk0 »](https://t.me/+kYH5G4d5MV83ODk0)** +- Follow the creator Nuno Maduro: + - YouTube: **[youtube.com/@nunomaduro](https://www.youtube.com/@nunomaduro)** — Videos every weekday + - Twitch: **[twitch.tv/enunomaduro](https://www.twitch.tv/enunomaduro)** — Streams (almost) every weekday + - Twitter / X: **[x.com/enunomaduro](https://x.com/enunomaduro)** + - LinkedIn: **[linkedin.com/in/nunomaduro](https://www.linkedin.com/in/nunomaduro)** + - Instagram: **[instagram.com/enunomaduro](https://www.instagram.com/enunomaduro)** + - Tiktok: **[tiktok.com/@enunomaduro](https://www.tiktok.com/@enunomaduro)** ## Sponsors From 0171617c1db13a7a9cc8d3050b22593a3bab8180 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 30 Mar 2025 18:42:00 +0100 Subject: [PATCH 37/95] chore: adjusts to new types on arch --- composer.json | 2 +- src/Console/Thanks.php | 2 +- src/Expectations/OppositeExpectation.php | 145 ++++++++++++++++++----- 3 files changed, 118 insertions(+), 31 deletions(-) diff --git a/composer.json b/composer.json index 6b9856db..001e31f2 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "nunomaduro/collision": "^8.7.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", - "pestphp/pest-plugin-arch": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.1.0", "pestphp/pest-plugin-mutate": "^3.0.5", "phpunit/phpunit": "^11.5.15" }, diff --git a/src/Console/Thanks.php b/src/Console/Thanks.php index 332c311b..88965026 100644 --- a/src/Console/Thanks.php +++ b/src/Console/Thanks.php @@ -24,7 +24,7 @@ final readonly class Thanks */ private const FUNDING_MESSAGES = [ 'Star' => 'https://github.com/pestphp/pest', - 'News' => 'https://twitter.com/pestphp', + 'News' => 'https://x.com/enunomaduro', 'Videos' => 'https://youtube.com/@nunomaduro', 'Sponsor' => 'https://github.com/sponsors/nunomaduro', ]; diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php index 59a25b4a..c4b0a0a5 100644 --- a/src/Expectations/OppositeExpectation.php +++ b/src/Expectations/OppositeExpectation.php @@ -74,7 +74,10 @@ final readonly class OppositeExpectation */ public function toUse(array|string $targets): ArchExpectation { - return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($this->original, $target)->opposite( + /** @var Expectation|string> $original */ + $original = $this->original; + + return GroupArchExpectation::fromExpectations($original, array_map(fn (string $target): SingleArchExpectation => ToUse::make($original, $target)->opposite( fn () => $this->throwExpectationFailedException('toUse', $target), ), is_string($targets) ? [$targets] : $targets)); } @@ -84,8 +87,11 @@ final readonly class OppositeExpectation */ public function toHaveFileSystemPermissions(string $permissions): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => substr(sprintf('%o', fileperms($object->path)), -4) !== $permissions, sprintf('permissions not to be [%s]', $permissions), FileLineFinder::where(fn (string $line): bool => str_contains($line, '|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || array_filter( Reflection::getMethodsFromReflectionClass($object->reflectionClass), @@ -124,8 +133,11 @@ final readonly class OppositeExpectation */ public function toHavePropertiesDocumented(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || array_filter( Reflection::getPropertiesFromReflectionClass($object->reflectionClass), @@ -144,8 +156,11 @@ final readonly class OppositeExpectation */ public function toUseStrictTypes(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! (bool) preg_match('/^<\?php\s+declare\(.*?strict_types\s?=\s?1.*?\);/', (string) file_get_contents($object->path)), 'not to use strict types', FileLineFinder::where(fn (string $line): bool => str_contains($line, '|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), ' === ') && ! str_contains((string) file_get_contents($object->path), ' !== '), 'to use strict equality', FileLineFinder::where(fn (string $line): bool => str_contains($line, ' === ') || str_contains($line, ' !== ')), @@ -170,8 +188,11 @@ final readonly class OppositeExpectation */ public function toBeFinal(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! enum_exists($object->name) && ! $object->reflectionClass->isFinal(), 'not to be final', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -183,8 +204,11 @@ final readonly class OppositeExpectation */ public function toBeReadonly(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! enum_exists($object->name) && ! $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line 'not to be readonly', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -196,8 +220,11 @@ final readonly class OppositeExpectation */ public function toBeTrait(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->isTrait(), 'not to be trait', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -217,8 +244,11 @@ final readonly class OppositeExpectation */ public function toBeAbstract(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->isAbstract(), 'not to be abstract', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -234,8 +264,11 @@ final readonly class OppositeExpectation { $methods = is_array($method) ? $method : [$method]; + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => array_filter( $methods, fn (string $method): bool => $object->reflectionClass->hasMethod($method), @@ -266,8 +299,11 @@ final readonly class OppositeExpectation $state = new stdClass; + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, function (ObjectDescription $object) use ($methods, &$state): bool { $reflectionMethods = isset($object->reflectionClass) ? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PUBLIC) @@ -309,8 +345,11 @@ final readonly class OppositeExpectation $state = new stdClass; + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, function (ObjectDescription $object) use ($methods, &$state): bool { $reflectionMethods = isset($object->reflectionClass) ? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PROTECTED) @@ -352,8 +391,11 @@ final readonly class OppositeExpectation $state = new stdClass; + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, function (ObjectDescription $object) use ($methods, &$state): bool { $reflectionMethods = isset($object->reflectionClass) ? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PRIVATE) @@ -389,8 +431,11 @@ final readonly class OppositeExpectation */ public function toBeEnum(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum(), 'not to be enum', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -410,8 +455,11 @@ final readonly class OppositeExpectation */ public function toBeClass(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! class_exists($object->name), 'not to be class', FileLineFinder::where(fn (string $line): bool => true), @@ -431,8 +479,11 @@ final readonly class OppositeExpectation */ public function toBeInterface(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->isInterface(), 'not to be interface', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -452,8 +503,11 @@ final readonly class OppositeExpectation */ public function toExtend(string $class): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->isSubclassOf($class), sprintf("not to extend '%s'", $class), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -465,8 +519,11 @@ final readonly class OppositeExpectation */ public function toExtendNothing(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => $object->reflectionClass->getParentClass() !== false, 'to extend a class', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -490,8 +547,11 @@ final readonly class OppositeExpectation { $traits = is_array($traits) ? $traits : [$traits]; + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, function (ObjectDescription $object) use ($traits): bool { foreach ($traits as $trait) { if (in_array($trait, $object->reflectionClass->getTraitNames(), true)) { @@ -515,8 +575,11 @@ final readonly class OppositeExpectation { $interfaces = is_array($interfaces) ? $interfaces : [$interfaces]; + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, function (ObjectDescription $object) use ($interfaces): bool { foreach ($interfaces as $interface) { if ($object->reflectionClass->implementsInterface($interface)) { @@ -536,8 +599,11 @@ final readonly class OppositeExpectation */ public function toImplementNothing(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() !== [], 'to implement an interface', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -557,8 +623,11 @@ final readonly class OppositeExpectation */ public function toHavePrefix(string $prefix): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! str_starts_with($object->reflectionClass->getShortName(), $prefix), "not to have prefix '{$prefix}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -570,8 +639,11 @@ final readonly class OppositeExpectation */ public function toHaveSuffix(string $suffix): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! str_ends_with($object->reflectionClass->getName(), $suffix), "not to have suffix '{$suffix}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -599,7 +671,10 @@ final readonly class OppositeExpectation */ public function toBeUsed(): ArchExpectation { - return ToBeUsedInNothing::make($this->original); + /** @var Expectation|string> $original */ + $original = $this->original; + + return ToBeUsedInNothing::make($original); } /** @@ -609,7 +684,10 @@ final readonly class OppositeExpectation */ public function toBeUsedIn(array|string $targets): ArchExpectation { - return GroupArchExpectation::fromExpectations($this->original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedIn::make($this->original, $target)->opposite( + /** @var Expectation|string> $original */ + $original = $this->original; + + return GroupArchExpectation::fromExpectations($original, array_map(fn (string $target): GroupArchExpectation => ToBeUsedIn::make($original, $target)->opposite( fn () => $this->throwExpectationFailedException('toBeUsedIn', $target), ), is_string($targets) ? [$targets] : $targets)); } @@ -632,8 +710,11 @@ final readonly class OppositeExpectation */ public function toBeInvokable(): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->hasMethod('__invoke'), 'to not be invokable', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) @@ -645,8 +726,11 @@ final readonly class OppositeExpectation */ public function toHaveAttribute(string $attribute): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => $object->reflectionClass->getAttributes($attribute) === [], "to not have attribute '{$attribute}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) @@ -737,8 +821,11 @@ final readonly class OppositeExpectation */ private function toBeBackedEnum(string $backingType): ArchExpectation { + /** @var Expectation|string> $original */ + $original = $this->original; + return Targeted::make( - $this->original, + $original, fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum() || ! (new \ReflectionEnum($object->name))->isBacked() // @phpstan-ignore-line || (string) (new \ReflectionEnum($object->name))->getBackingType() !== $backingType, // @phpstan-ignore-line From 42e1b9f17fc2b2036701f4b968158264bde542d4 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 30 Mar 2025 18:49:10 +0100 Subject: [PATCH 38/95] release: v3.8.0 --- src/Console/Thanks.php | 8 ++++++-- src/Pest.php | 2 +- .../Help/visual_snapshot_of_help_command_output.snap | 2 +- .../Version/visual_snapshot_of_help_command_output.snap | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Console/Thanks.php b/src/Console/Thanks.php index 88965026..7e68f871 100644 --- a/src/Console/Thanks.php +++ b/src/Console/Thanks.php @@ -24,8 +24,12 @@ final readonly class Thanks */ private const FUNDING_MESSAGES = [ 'Star' => 'https://github.com/pestphp/pest', - 'News' => 'https://x.com/enunomaduro', - 'Videos' => 'https://youtube.com/@nunomaduro', + 'YouTube' => 'https://youtube.com/@nunomaduro', + 'TikTok' => 'https://tiktok.com/@nunomaduro', + 'Twitch' => 'https://twitch.tv/enunomaduro', + 'LinkedIn' => 'https://linkedin.com/in/nunomaduro', + 'Instagram' => 'https://instagram.com/enunomaduro', + 'X' => 'https://x.com/enunomaduro', 'Sponsor' => 'https://github.com/sponsors/nunomaduro', ]; diff --git a/src/Pest.php b/src/Pest.php index d8ac40bf..87cf2d9d 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.7.5'; + return '3.8.0'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 5834ea3b..d75eb54e 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.7.5. + Pest Testing Framework 3.8.0. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 6e4c46c9..2b7edc3b 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.7.5. + Pest Testing Framework 3.8.0. From e0f07be017a7b549c8aa78b497985b66c7085cca Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 3 Apr 2025 17:23:39 +0100 Subject: [PATCH 39/95] fix: `init` command detecting laravel --- src/Plugins/Init.php | 2 +- stubs/init-laravel/Pest.php.stub | 2 +- stubs/init-laravel/phpunit.xml.stub | 20 +++++++++++--------- stubs/init/Pest.php.stub | 2 +- stubs/init/phpunit.xml.stub | 6 +++--- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Plugins/Init.php b/src/Plugins/Init.php index 6bb92365..eb87b086 100644 --- a/src/Plugins/Init.php +++ b/src/Plugins/Init.php @@ -119,6 +119,6 @@ final readonly class Init implements HandlesArguments */ private function isLaravelInstalled(): bool { - return InstalledVersions::isInstalled('laravel/laravel'); + return InstalledVersions::isInstalled('laravel/framework'); } } diff --git a/stubs/init-laravel/Pest.php.stub b/stubs/init-laravel/Pest.php.stub index 40d096b5..60f04a45 100644 --- a/stubs/init-laravel/Pest.php.stub +++ b/stubs/init-laravel/Pest.php.stub @@ -12,7 +12,7 @@ */ pest()->extend(Tests\TestCase::class) - ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) + // ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) ->in('Feature'); /* diff --git a/stubs/init-laravel/phpunit.xml.stub b/stubs/init-laravel/phpunit.xml.stub index 83ba26be..506b9a38 100644 --- a/stubs/init-laravel/phpunit.xml.stub +++ b/stubs/init-laravel/phpunit.xml.stub @@ -1,31 +1,33 @@ - ./tests/Unit + tests/Unit - ./tests/Feature + tests/Feature + + + app + + + - + + - - - ./app - - diff --git a/stubs/init/Pest.php.stub b/stubs/init/Pest.php.stub index fd279ada..b239048c 100644 --- a/stubs/init/Pest.php.stub +++ b/stubs/init/Pest.php.stub @@ -11,7 +11,7 @@ | */ -// pest()->extend(Tests\TestCase::class)->in('Feature'); +pest()->extend(Tests\TestCase::class)->in('Feature'); /* |-------------------------------------------------------------------------- diff --git a/stubs/init/phpunit.xml.stub b/stubs/init/phpunit.xml.stub index 7d0904f7..e6198e0e 100644 --- a/stubs/init/phpunit.xml.stub +++ b/stubs/init/phpunit.xml.stub @@ -1,6 +1,6 @@ @@ -11,8 +11,8 @@
- ./app - ./src + app + src From 6080f51a0b0830715c48ba0e7458b06907febfe5 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 3 Apr 2025 17:35:58 +0100 Subject: [PATCH 40/95] release: v3.8.1 --- composer.json | 2 +- src/Pest.php | 2 +- .../Visual/Help/visual_snapshot_of_help_command_output.snap | 2 +- .../Visual/Version/visual_snapshot_of_help_command_output.snap | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 001e31f2..a4f0f21d 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "require": { "php": "^8.2.0", "brianium/paratest": "^7.8.3", - "nunomaduro/collision": "^8.7.0", + "nunomaduro/collision": "^8.8.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.1.0", diff --git a/src/Pest.php b/src/Pest.php index 87cf2d9d..077a04c2 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.8.0'; + return '3.8.1'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index d75eb54e..5646dca8 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.8.0. + Pest Testing Framework 3.8.1. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 2b7edc3b..7775b987 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.8.0. + Pest Testing Framework 3.8.1. From eed68f28403913baef9b27b071c72e390697dec4 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 13 Apr 2025 17:15:23 +0100 Subject: [PATCH 41/95] Adjusts sponsors --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e9f2faee..4a11f1a7 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,16 @@ We cannot thank our sponsors enough for their incredible support in funding Pest ### Gold Sponsors -- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)** -- **[LaraJobs](https://larajobs.com/?ref=pestphp)** - **[Brokerchooser](https://brokerchooser.com/?ref=pestphp)** -- **[Forge](https://forge.laravel.com/?ref=pestphp)** +- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)** +- **[NativePHP](https://nativephp.com/mobile?ref=pestphp.com)** ### Premium Sponsors - [Akaunting](https://akaunting.com/?ref=pestphp) -- [Codecourse](https://codecourse.com/?ref=pestphp) - [DocuWriter.ai](https://www.docuwriter.ai/?ref=pestphp) - [Localazy](https://localazy.com/?ref=pestphp) +- [Forge](https://forge.laravel.com/?ref=pestphp) - [Route4Me](https://www.route4me.com/?ref=pestphp) - [Spatie](https://spatie.be/?ref=pestphp) - [Worksome](https://www.worksome.com/?ref=pestphp) From c6244a8712968dbac88eb998e7ff3b5caa556b0d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 17 Apr 2025 11:52:59 +0100 Subject: [PATCH 42/95] Release 3.8.2 --- src/Expectation.php | 38 +++++++++++-------- src/Expectations/OppositeExpectation.php | 35 ++++++++--------- src/Pest.php | 2 +- ...isual_snapshot_of_help_command_output.snap | 2 +- ...isual_snapshot_of_help_command_output.snap | 2 +- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/Expectation.php b/src/Expectation.php index ebfd6302..1bef5a8c 100644 --- a/src/Expectation.php +++ b/src/Expectation.php @@ -535,7 +535,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => ! enum_exists($object->name) && $object->reflectionClass->isFinal(), + fn (ObjectDescription $object): bool => ! enum_exists($object->name) && isset($object->reflectionClass) && $object->reflectionClass->isFinal(), 'to be final', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -548,7 +548,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => ! enum_exists($object->name) && $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line + fn (ObjectDescription $object): bool => ! enum_exists($object->name) && isset($object->reflectionClass) && $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line 'to be readonly', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -561,7 +561,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->isTrait(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isTrait(), 'to be trait', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -582,7 +582,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->isAbstract(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isAbstract(), 'to be abstract', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -599,7 +599,7 @@ final class Expectation return Targeted::make( $this, - fn (ObjectDescription $object): bool => count(array_filter($methods, fn (string $method): bool => $object->reflectionClass->hasMethod($method))) === count($methods), + fn (ObjectDescription $object): bool => count(array_filter($methods, fn (string $method): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod($method))) === count($methods), sprintf("to have method '%s'", implode("', '", $methods)), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -670,7 +670,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->isEnum(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isEnum(), 'to be enum', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -712,7 +712,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->isInterface(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isInterface(), 'to be interface', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -733,7 +733,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && ($class === $object->reflectionClass->getName() || $object->reflectionClass->isSubclassOf($class)), sprintf("to extend '%s'", $class), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -773,6 +773,10 @@ final class Expectation $this, function (ObjectDescription $object) use ($traits): bool { foreach ($traits as $trait) { + if (isset($object->reflectionClass) === false) { + return false; + } + if (! in_array($trait, $object->reflectionClass->getTraitNames(), true)) { return false; } @@ -792,7 +796,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() === [], + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getInterfaceNames() === [], 'to implement nothing', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -809,7 +813,8 @@ final class Expectation return Targeted::make( $this, - fn (ObjectDescription $object): bool => count($interfaces) === count($object->reflectionClass->getInterfaceNames()) + fn (ObjectDescription $object): bool => isset($object->reflectionClass) + && (count($interfaces) === count($object->reflectionClass->getInterfaceNames())) && array_diff($interfaces, $object->reflectionClass->getInterfaceNames()) === [], "to only implement '".implode("', '", $interfaces)."'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -823,7 +828,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => str_starts_with($object->reflectionClass->getShortName(), $prefix), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_starts_with($object->reflectionClass->getShortName(), $prefix), "to have prefix '{$prefix}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -836,7 +841,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => str_ends_with($object->reflectionClass->getName(), $suffix), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && str_ends_with($object->reflectionClass->getName(), $suffix), "to have suffix '{$suffix}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -855,7 +860,7 @@ final class Expectation $this, function (ObjectDescription $object) use ($interfaces): bool { foreach ($interfaces as $interface) { - if (! $object->reflectionClass->implementsInterface($interface)) { + if (! isset($object->reflectionClass) || ! $object->reflectionClass->implementsInterface($interface)) { return false; } } @@ -928,7 +933,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->hasMethod('__invoke'), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod('__invoke'), 'to be invokable', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) ); @@ -1037,7 +1042,7 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->getAttributes($attribute) !== [], + fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getAttributes($attribute) !== [], "to have attribute '{$attribute}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -1066,7 +1071,8 @@ final class Expectation { return Targeted::make( $this, - fn (ObjectDescription $object): bool => $object->reflectionClass->isEnum() + fn (ObjectDescription $object): bool => isset($object->reflectionClass) + && $object->reflectionClass->isEnum() && (new ReflectionEnum($object->name))->isBacked() // @phpstan-ignore-line && (string) (new ReflectionEnum($object->name))->getBackingType() === $backingType, // @phpstan-ignore-line 'to be '.$backingType.' backed enum', diff --git a/src/Expectations/OppositeExpectation.php b/src/Expectations/OppositeExpectation.php index c4b0a0a5..d5c3f083 100644 --- a/src/Expectations/OppositeExpectation.php +++ b/src/Expectations/OppositeExpectation.php @@ -193,7 +193,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! enum_exists($object->name) && ! $object->reflectionClass->isFinal(), + fn (ObjectDescription $object): bool => ! enum_exists($object->name) && (isset($object->reflectionClass) === false || ! $object->reflectionClass->isFinal()), 'not to be final', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -209,7 +209,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! enum_exists($object->name) && ! $object->reflectionClass->isReadOnly() && assert(true), // @phpstan-ignore-line + fn (ObjectDescription $object): bool => ! enum_exists($object->name) && (isset($object->reflectionClass) === false || ! $object->reflectionClass->isReadOnly()) && assert(true), // @phpstan-ignore-line 'not to be readonly', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -225,7 +225,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->isTrait(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isTrait(), 'not to be trait', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -249,7 +249,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->isAbstract(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isAbstract(), 'not to be abstract', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -271,7 +271,7 @@ final readonly class OppositeExpectation $original, fn (ObjectDescription $object): bool => array_filter( $methods, - fn (string $method): bool => $object->reflectionClass->hasMethod($method), + fn (string $method): bool => isset($object->reflectionClass) === false || $object->reflectionClass->hasMethod($method), ) === [], 'to not have methods: '.implode(', ', $methods), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), @@ -436,7 +436,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isEnum(), 'not to be enum', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -484,7 +484,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->isInterface(), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isInterface(), 'not to be interface', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -508,7 +508,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->isSubclassOf($class), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isSubclassOf($class), sprintf("not to extend '%s'", $class), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -524,7 +524,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => $object->reflectionClass->getParentClass() !== false, + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getParentClass() !== false, 'to extend a class', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -554,7 +554,7 @@ final readonly class OppositeExpectation $original, function (ObjectDescription $object) use ($traits): bool { foreach ($traits as $trait) { - if (in_array($trait, $object->reflectionClass->getTraitNames(), true)) { + if (isset($object->reflectionClass) && in_array($trait, $object->reflectionClass->getTraitNames(), true)) { return false; } } @@ -582,7 +582,7 @@ final readonly class OppositeExpectation $original, function (ObjectDescription $object) use ($interfaces): bool { foreach ($interfaces as $interface) { - if ($object->reflectionClass->implementsInterface($interface)) { + if (isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface)) { return false; } } @@ -604,7 +604,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() !== [], + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getInterfaceNames() !== [], 'to implement an interface', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -628,7 +628,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! str_starts_with($object->reflectionClass->getShortName(), $prefix), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_starts_with($object->reflectionClass->getShortName(), $prefix), "not to have prefix '{$prefix}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -644,7 +644,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! str_ends_with($object->reflectionClass->getName(), $suffix), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! str_ends_with($object->reflectionClass->getName(), $suffix), "not to have suffix '{$suffix}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), ); @@ -715,7 +715,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->hasMethod('__invoke'), + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->hasMethod('__invoke'), 'to not be invokable', FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) ); @@ -731,7 +731,7 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => $object->reflectionClass->getAttributes($attribute) === [], + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getAttributes($attribute) === [], "to not have attribute '{$attribute}'", FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) ); @@ -826,7 +826,8 @@ final readonly class OppositeExpectation return Targeted::make( $original, - fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum() + fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false + || ! $object->reflectionClass->isEnum() || ! (new \ReflectionEnum($object->name))->isBacked() // @phpstan-ignore-line || (string) (new \ReflectionEnum($object->name))->getBackingType() !== $backingType, // @phpstan-ignore-line 'not to be '.$backingType.' backed enum', diff --git a/src/Pest.php b/src/Pest.php index 077a04c2..db27e206 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.8.1'; + return '3.8.2'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 5646dca8..da7acad7 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.8.1. + Pest Testing Framework 3.8.2. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 7775b987..cd2d2cfb 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.8.1. + Pest Testing Framework 3.8.2. From 19e9267021b811d30cbfe53737d78dfff848ad56 Mon Sep 17 00:00:00 2001 From: Punyapal Shah Date: Sun, 20 Apr 2025 15:19:40 +0530 Subject: [PATCH 43/95] fix: update PHPUnit version --- composer.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/composer.json b/composer.json index a4f0f21d..be8a89a9 100644 --- a/composer.json +++ b/composer.json @@ -22,13 +22,11 @@ "nunomaduro/collision": "^8.8.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", - "pestphp/pest-plugin-arch": "^3.1.0", "pestphp/pest-plugin-mutate": "^3.0.5", - "phpunit/phpunit": "^11.5.15" + "phpunit/phpunit": "12.1.0" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.15", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, @@ -53,8 +51,6 @@ ] }, "require-dev": { - "pestphp/pest-dev-tools": "^3.4.0", - "pestphp/pest-plugin-type-coverage": "^3.5.0", "symfony/process": "^7.2.5" }, "minimum-stability": "dev", From 442a58d07f0c892d0c4219d4690e437098e98adc Mon Sep 17 00:00:00 2001 From: Punyapal Shah Date: Sun, 20 Apr 2025 15:19:56 +0530 Subject: [PATCH 44/95] refactor: comment arch presets in Arch.php --- tests/Arch.php | 98 +++++++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/tests/Arch.php b/tests/Arch.php index d8deb460..4dcc983e 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -2,57 +2,57 @@ use Pest\Expectation; -arch()->preset()->php()->ignoring([ - Expectation::class, - 'debug_backtrace', - 'var_export', - 'xdebug_info', -]); +// arch()->preset()->php()->ignoring([ +// Expectation::class, +// 'debug_backtrace', +// 'var_export', +// 'xdebug_info', +// ]); -arch()->preset()->strict()->ignoring([ - 'usleep', -]); +// arch()->preset()->strict()->ignoring([ +// 'usleep', +// ]); -arch()->preset()->security()->ignoring([ - 'eval', - 'str_shuffle', - 'exec', - 'unserialize', - 'extract', - 'assert', -]); +// arch()->preset()->security()->ignoring([ +// 'eval', +// 'str_shuffle', +// 'exec', +// 'unserialize', +// 'extract', +// 'assert', +// ]); -arch('globals') - ->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep']) - ->not->toBeUsed() - ->ignoring(Expectation::class); +// arch('globals') +// ->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep']) +// ->not->toBeUsed() +// ->ignoring(Expectation::class); -arch('dependencies') - ->expect('Pest') - ->toOnlyUse([ - 'dd', - 'dump', - 'expect', - 'uses', - 'Termwind', - 'ParaTest', - 'Pest\Arch', - 'Pest\Mutate\Contracts\Configuration', - 'Pest\Mutate\Decorators\TestCallDecorator', - 'Pest\Mutate\Repositories\ConfigurationRepository', - 'Pest\Plugin', - 'NunoMaduro\Collision', - 'Whoops', - 'Symfony\Component\Console', - 'Symfony\Component\Process', - ])->ignoring(['Composer', 'PHPUnit', 'SebastianBergmann']); +// arch('dependencies') +// ->expect('Pest') +// ->toOnlyUse([ +// 'dd', +// 'dump', +// 'expect', +// 'uses', +// 'Termwind', +// 'ParaTest', +// 'Pest\Arch', +// 'Pest\Mutate\Contracts\Configuration', +// 'Pest\Mutate\Decorators\TestCallDecorator', +// 'Pest\Mutate\Repositories\ConfigurationRepository', +// 'Pest\Plugin', +// 'NunoMaduro\Collision', +// 'Whoops', +// 'Symfony\Component\Console', +// 'Symfony\Component\Process', +// ])->ignoring(['Composer', 'PHPUnit', 'SebastianBergmann']); -arch('contracts') - ->expect('Pest\Contracts') - ->toOnlyUse([ - 'NunoMaduro\Collision\Contracts', - 'Pest\Factories\TestCaseMethodFactory', - 'Symfony\Component\Console', - 'Pest\Arch\Contracts', - 'Pest\PendingCalls', - ])->toBeInterfaces(); +// arch('contracts') +// ->expect('Pest\Contracts') +// ->toOnlyUse([ +// 'NunoMaduro\Collision\Contracts', +// 'Pest\Factories\TestCaseMethodFactory', +// 'Symfony\Component\Console', +// 'Pest\Arch\Contracts', +// 'Pest\PendingCalls', +// ])->toBeInterfaces(); From 4f6140fdb176b38e5773954334e3ba707253da10 Mon Sep 17 00:00:00 2001 From: Punyapal Shah Date: Sun, 20 Apr 2025 15:37:02 +0530 Subject: [PATCH 45/95] refactor: move test case initialization to a separate method in Testable trait --- src/Concerns/Testable.php | 49 ++++++++++++++++++++++----------------- tests/Unit/Preset.php | 2 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 37d3b175..7199ad5b 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -101,27 +101,6 @@ trait Testable */ private array $__snapshotChanges = []; - /** - * Creates a new Test Case instance. - */ - public function __construct(string $name) - { - parent::__construct($name); - - $test = TestSuite::getInstance()->tests->get(self::$__filename); - - if ($test->hasMethod($name)) { - $method = $test->getMethod($name); - $this->__description = self::$__latestDescription = $method->description; - self::$__latestAssignees = $method->assignees; - self::$__latestNotes = $method->notes; - self::$__latestIssues = $method->issues; - self::$__latestPrs = $method->prs; - $this->__describing = $method->describing; - $this->__test = $method->getClosure(); - } - } - /** * Resets the test case static properties. */ @@ -240,6 +219,9 @@ trait Testable { TestSuite::getInstance()->test = $this; + // Initialize test case properties + $this->__initializeTestCase(); + $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); $method->setUp($this); @@ -285,6 +267,31 @@ trait Testable $this->__callClosure($beforeEach, $arguments); } + /** + * Initialize test case properties from TestSuite. + */ + private function __initializeTestCase(): void + { + // Return if the test case has already been initialized + if (isset($this->__test)) { + return; + } + + $name = $this->name(); + $test = TestSuite::getInstance()->tests->get(self::$__filename); + + if ($test->hasMethod($name)) { + $method = $test->getMethod($name); + $this->__description = self::$__latestDescription = $method->description; + self::$__latestAssignees = $method->assignees; + self::$__latestNotes = $method->notes; + self::$__latestIssues = $method->issues; + self::$__latestPrs = $method->prs; + $this->__describing = $method->describing; + $this->__test = $method->getClosure(); + } + } + /** * Gets executed after the Test Case. */ diff --git a/tests/Unit/Preset.php b/tests/Unit/Preset.php index bdf461a7..e777982b 100644 --- a/tests/Unit/Preset.php +++ b/tests/Unit/Preset.php @@ -10,4 +10,4 @@ test('preset invalid name', function () { $this->preset()->myAnotherFramework(); })->throws(InvalidArgumentException::class, 'The preset [myAnotherFramework] does not exist. The available presets are [php, laravel, strict, security, relaxed, myFramework].'); -arch()->preset()->myFramework(); +// arch()->preset()->myFramework(); From d1608bf33d0f9945dd6329775bdf5655dd595e6d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 20 Apr 2025 21:33:50 +0100 Subject: [PATCH 46/95] chore: prepares for 4.x --- composer.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index a4f0f21d..8f6c9c58 100644 --- a/composer.json +++ b/composer.json @@ -17,19 +17,19 @@ } ], "require": { - "php": "^8.2.0", + "php": "^8.3.0", "brianium/paratest": "^7.8.3", "nunomaduro/collision": "^8.8.0", "nunomaduro/termwind": "^2.3.0", - "pestphp/pest-plugin": "^3.0.0", - "pestphp/pest-plugin-arch": "^3.1.0", - "pestphp/pest-plugin-mutate": "^3.0.5", - "phpunit/phpunit": "^11.5.15" + "pestphp/pest-plugin": "^4.0.0", + "pestphp/pest-plugin-arch": "^4.0.0", + "pestphp/pest-plugin-mutate": "^4.0.0", + "phpunit/phpunit": "^12.1.2" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.15", - "sebastian/exporter": "<6.0.0", + "phpunit/phpunit": ">12.1.2", + "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, "autoload": { @@ -53,8 +53,8 @@ ] }, "require-dev": { - "pestphp/pest-dev-tools": "^3.4.0", - "pestphp/pest-plugin-type-coverage": "^3.5.0", + "pestphp/pest-dev-tools": "^4.0.0", + "pestphp/pest-plugin-type-coverage": "^4.0.0", "symfony/process": "^7.2.5" }, "minimum-stability": "dev", From 8cfb0acf46e10a3468841027ab1d7e028866c73d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 20 Apr 2025 21:50:56 +0100 Subject: [PATCH 47/95] bump paratest --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8f6c9c58..62e09636 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { "php": "^8.3.0", - "brianium/paratest": "^7.8.3", + "brianium/paratest": "^7.9.1", "nunomaduro/collision": "^8.8.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^4.0.0", From 791734a29cf597beec9d44309fcf6f7a83b9fa97 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 20 Apr 2025 22:19:25 +0100 Subject: [PATCH 48/95] Fixes tests --- composer.json | 10 +- src/Pest.php | 2 +- .../Parallel/Paratest/WrapperRunner.php | 5 + .../Parallel/Support/CompactPrinter.php | 16 +-- ...isual_snapshot_of_help_command_output.snap | 4 +- ...isual_snapshot_of_help_command_output.snap | 2 +- tests/Arch.php | 98 +++++++++---------- tests/Unit/Preset.php | 2 +- 8 files changed, 73 insertions(+), 66 deletions(-) diff --git a/composer.json b/composer.json index 62e09636..d2a1461f 100644 --- a/composer.json +++ b/composer.json @@ -76,11 +76,11 @@ "test:lint": "pint --test", "test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug", "test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100", - "test:unit": "php bin/pest --colors=always --exclude-group=integration --compact", - "test:inline": "php bin/pest --colors=always --configuration=phpunit.inline.xml", - "test:parallel": "php bin/pest --colors=always --exclude-group=integration --parallel --processes=3", - "test:integration": "php bin/pest --colors=always --group=integration -v", - "update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always --update-snapshots", + "test:unit": "php bin/pest --exclude-group=integration --compact", + "test:inline": "php bin/pest --configuration=phpunit.inline.xml", + "test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3", + "test:integration": "php bin/pest --group=integration -v", + "update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots", "test": [ "@test:refacto", "@test:lint", diff --git a/src/Pest.php b/src/Pest.php index db27e206..d23cfd5c 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '3.8.2'; + return '4.0.0-alpha.1'; } function testDirectory(string $file = ''): string diff --git a/src/Plugins/Parallel/Paratest/WrapperRunner.php b/src/Plugins/Parallel/Paratest/WrapperRunner.php index 282749d3..48c88e7b 100644 --- a/src/Plugins/Parallel/Paratest/WrapperRunner.php +++ b/src/Plugins/Parallel/Paratest/WrapperRunner.php @@ -313,6 +313,7 @@ final class WrapperRunner implements RunnerInterface $testResult = unserialize($contents); assert($testResult instanceof TestResult); + $testResultSum = new TestResult( (int) $testResultSum->hasTests() + (int) $testResult->hasTests(), $testResultSum->numberOfTestsRun() + $testResult->numberOfTestsRun(), @@ -325,8 +326,10 @@ final class WrapperRunner implements RunnerInterface array_merge_recursive($testResultSum->testMarkedIncompleteEvents(), $testResult->testMarkedIncompleteEvents()), array_merge_recursive($testResultSum->testTriggeredPhpunitDeprecationEvents(), $testResult->testTriggeredPhpunitDeprecationEvents()), array_merge_recursive($testResultSum->testTriggeredPhpunitErrorEvents(), $testResult->testTriggeredPhpunitErrorEvents()), + array_merge_recursive($testResultSum->testTriggeredPhpunitNoticeEvents(), $testResult->testTriggeredPhpunitNoticeEvents()), array_merge_recursive($testResultSum->testTriggeredPhpunitWarningEvents(), $testResult->testTriggeredPhpunitWarningEvents()), array_merge_recursive($testResultSum->testRunnerTriggeredDeprecationEvents(), $testResult->testRunnerTriggeredDeprecationEvents()), + array_merge_recursive($testResultSum->testRunnerTriggeredNoticeEvents(), $testResult->testRunnerTriggeredNoticeEvents()), array_merge_recursive($testResultSum->testRunnerTriggeredWarningEvents(), $testResult->testRunnerTriggeredWarningEvents()), array_merge_recursive($testResultSum->errors(), $testResult->errors()), array_merge_recursive($testResultSum->deprecations(), $testResult->deprecations()), @@ -351,8 +354,10 @@ final class WrapperRunner implements RunnerInterface $testResultSum->testMarkedIncompleteEvents(), $testResultSum->testTriggeredPhpunitDeprecationEvents(), $testResultSum->testTriggeredPhpunitErrorEvents(), + $testResultSum->testTriggeredPhpunitNoticeEvents(), $testResultSum->testTriggeredPhpunitWarningEvents(), $testResultSum->testRunnerTriggeredDeprecationEvents(), + $testResultSum->testRunnerTriggeredNoticeEvents(), array_values(array_filter( $testResultSum->testRunnerTriggeredWarningEvents(), fn (WarningTriggered $event): bool => ! str_contains($event->message(), 'No tests found') diff --git a/src/Plugins/Parallel/Support/CompactPrinter.php b/src/Plugins/Parallel/Support/CompactPrinter.php index 25226b10..bc2e1c3f 100644 --- a/src/Plugins/Parallel/Support/CompactPrinter.php +++ b/src/Plugins/Parallel/Support/CompactPrinter.php @@ -131,14 +131,14 @@ final class CompactPrinter $status['collected'], $status['threshold'], $status['roots'], - null, - null, - null, - null, - null, - null, - null, - null, + 0.00, + 0.00, + 0.00, + 0.00, + false, + false, + false, + 0, ); $telemetry = new Info( diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index da7acad7..9966d901 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 3.8.2. + Pest Testing Framework 4.0.0-alpha.1. USAGE: pest [options] @@ -68,6 +68,7 @@ --fail-on-risky Signal failure using shell exit code when a test was considered risky --fail-on-deprecation Signal failure using shell exit code when a deprecation was triggered --fail-on-phpunit-deprecation Signal failure using shell exit code when a PHPUnit deprecation was triggered + --fail-on-phpunit-notice Signal failure using shell exit code when a PHPUnit notice was triggered --fail-on-notice Signal failure using shell exit code when a notice was triggered --fail-on-skipped Signal failure using shell exit code when a test was skipped --fail-on-incomplete Signal failure using shell exit code when a test was marked incomplete @@ -88,6 +89,7 @@ --display-skipped ........................ Display details for skipped tests --display-deprecations . Display details for deprecations triggered by tests --display-phpunit-deprecations .... Display details for PHPUnit deprecations + --display-phpunit-notices .............. Display details for PHPUnit notices --display-errors ............. Display details for errors triggered by tests --display-notices ........... Display details for notices triggered by tests --display-warnings ......... Display details for warnings triggered by tests diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index cd2d2cfb..c9dea0d5 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 3.8.2. + Pest Testing Framework 4.0.0-alpha.1. diff --git a/tests/Arch.php b/tests/Arch.php index 4dcc983e..d8deb460 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -2,57 +2,57 @@ use Pest\Expectation; -// arch()->preset()->php()->ignoring([ -// Expectation::class, -// 'debug_backtrace', -// 'var_export', -// 'xdebug_info', -// ]); +arch()->preset()->php()->ignoring([ + Expectation::class, + 'debug_backtrace', + 'var_export', + 'xdebug_info', +]); -// arch()->preset()->strict()->ignoring([ -// 'usleep', -// ]); +arch()->preset()->strict()->ignoring([ + 'usleep', +]); -// arch()->preset()->security()->ignoring([ -// 'eval', -// 'str_shuffle', -// 'exec', -// 'unserialize', -// 'extract', -// 'assert', -// ]); +arch()->preset()->security()->ignoring([ + 'eval', + 'str_shuffle', + 'exec', + 'unserialize', + 'extract', + 'assert', +]); -// arch('globals') -// ->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep']) -// ->not->toBeUsed() -// ->ignoring(Expectation::class); +arch('globals') + ->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep']) + ->not->toBeUsed() + ->ignoring(Expectation::class); -// arch('dependencies') -// ->expect('Pest') -// ->toOnlyUse([ -// 'dd', -// 'dump', -// 'expect', -// 'uses', -// 'Termwind', -// 'ParaTest', -// 'Pest\Arch', -// 'Pest\Mutate\Contracts\Configuration', -// 'Pest\Mutate\Decorators\TestCallDecorator', -// 'Pest\Mutate\Repositories\ConfigurationRepository', -// 'Pest\Plugin', -// 'NunoMaduro\Collision', -// 'Whoops', -// 'Symfony\Component\Console', -// 'Symfony\Component\Process', -// ])->ignoring(['Composer', 'PHPUnit', 'SebastianBergmann']); +arch('dependencies') + ->expect('Pest') + ->toOnlyUse([ + 'dd', + 'dump', + 'expect', + 'uses', + 'Termwind', + 'ParaTest', + 'Pest\Arch', + 'Pest\Mutate\Contracts\Configuration', + 'Pest\Mutate\Decorators\TestCallDecorator', + 'Pest\Mutate\Repositories\ConfigurationRepository', + 'Pest\Plugin', + 'NunoMaduro\Collision', + 'Whoops', + 'Symfony\Component\Console', + 'Symfony\Component\Process', + ])->ignoring(['Composer', 'PHPUnit', 'SebastianBergmann']); -// arch('contracts') -// ->expect('Pest\Contracts') -// ->toOnlyUse([ -// 'NunoMaduro\Collision\Contracts', -// 'Pest\Factories\TestCaseMethodFactory', -// 'Symfony\Component\Console', -// 'Pest\Arch\Contracts', -// 'Pest\PendingCalls', -// ])->toBeInterfaces(); +arch('contracts') + ->expect('Pest\Contracts') + ->toOnlyUse([ + 'NunoMaduro\Collision\Contracts', + 'Pest\Factories\TestCaseMethodFactory', + 'Symfony\Component\Console', + 'Pest\Arch\Contracts', + 'Pest\PendingCalls', + ])->toBeInterfaces(); diff --git a/tests/Unit/Preset.php b/tests/Unit/Preset.php index e777982b..bdf461a7 100644 --- a/tests/Unit/Preset.php +++ b/tests/Unit/Preset.php @@ -10,4 +10,4 @@ test('preset invalid name', function () { $this->preset()->myAnotherFramework(); })->throws(InvalidArgumentException::class, 'The preset [myAnotherFramework] does not exist. The available presets are [php, laravel, strict, security, relaxed, myFramework].'); -// arch()->preset()->myFramework(); +arch()->preset()->myFramework(); From 635e3b4c41e47320354f0564b4ea948fcc79df29 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 20 Apr 2025 23:02:19 +0100 Subject: [PATCH 49/95] chore: deprecates php 8.2 --- .github/workflows/static.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 3373eafb..69dd6b98 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -24,7 +24,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 tools: composer:v2 coverage: none diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c4902b42..abafbbbb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] symfony: ['7.1'] - php: ['8.2', '8.3', '8.4'] + php: ['8.3', '8.4'] dependency_version: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} From db9243ca2ebefcd38dbba5b7ce0f3466efe8369d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 29 Apr 2025 09:57:02 +0100 Subject: [PATCH 50/95] bump dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index d2a1461f..f4617a5b 100644 --- a/composer.json +++ b/composer.json @@ -24,11 +24,11 @@ "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", - "phpunit/phpunit": "^12.1.2" + "phpunit/phpunit": "^12.1.3" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.1.2", + "phpunit/phpunit": ">12.1.3", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, From a3107961655d3d1bb3e8ba44568f3549a053999c Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 29 Apr 2025 11:38:33 +0100 Subject: [PATCH 51/95] Fixes filtering tests --- overrides/TextUI/TestSuiteFilterProcessor.php | 9 +++++ src/Bootstrappers/BootExcludeList.php | 2 +- src/Bootstrappers/BootFiles.php | 2 +- src/Bootstrappers/BootOverrides.php | 2 +- src/Bootstrappers/BootSubscribers.php | 2 +- src/Concerns/Testable.php | 5 +-- src/Console/Help.php | 2 +- src/Console/Thanks.php | 2 +- src/Kernel.php | 2 +- src/Logging/Converter.php | 2 +- src/Plugins/Cache.php | 2 +- src/Plugins/Configuration.php | 2 +- src/Plugins/Coverage.php | 15 ++----- src/Plugins/Environment.php | 4 +- src/Plugins/Init.php | 4 +- src/Plugins/Only.php | 2 +- src/Plugins/Parallel.php | 6 +-- src/Plugins/Parallel/Handlers/Parallel.php | 2 +- .../Parallel/Paratest/CleanConsoleOutput.php | 1 + .../Parallel/Paratest/WrapperRunner.php | 13 ++++++- .../Parallel/Support/CompactPrinter.php | 2 +- src/Plugins/Verbose.php | 2 +- src/Repositories/DatasetsRepository.php | 2 +- src/Result.php | 6 +-- .../EnsureTestCaseIsInitiatedFilter.php | 39 +++++++++++++++++++ src/Support/Backtrace.php | 7 +--- src/Support/DatasetInfo.php | 4 +- src/Support/ExceptionTrace.php | 2 +- src/Support/Exporter.php | 2 +- src/Support/HigherOrderMessage.php | 2 +- src/Support/Str.php | 7 +--- 31 files changed, 98 insertions(+), 58 deletions(-) create mode 100644 src/Runner/Filter/EnsureTestCaseIsInitiatedFilter.php diff --git a/overrides/TextUI/TestSuiteFilterProcessor.php b/overrides/TextUI/TestSuiteFilterProcessor.php index 536ab208..e13d5c98 100644 --- a/overrides/TextUI/TestSuiteFilterProcessor.php +++ b/overrides/TextUI/TestSuiteFilterProcessor.php @@ -45,6 +45,7 @@ declare(strict_types=1); namespace PHPUnit\TextUI; use Pest\Plugins\Only; +use Pest\Runner\Filter\EnsureTestCaseIsInitiatedFilter; use PHPUnit\Event; use PHPUnit\Framework\TestSuite; use PHPUnit\Runner\Filter\Factory; @@ -66,6 +67,12 @@ final readonly class TestSuiteFilterProcessor { $factory = new Factory; + // @phpstan-ignore-next-line + (fn () => $this->filters[] = [ + 'className' => EnsureTestCaseIsInitiatedFilter::class, + 'argument' => '', + ])->call($factory); + if (! $configuration->hasFilter() && ! $configuration->hasGroups() && ! $configuration->hasExcludeGroups() && @@ -73,6 +80,8 @@ final readonly class TestSuiteFilterProcessor ! $configuration->hasTestsCovering() && ! $configuration->hasTestsUsing() && ! Only::isEnabled()) { + $suite->injectFilter($factory); + return; } diff --git a/src/Bootstrappers/BootExcludeList.php b/src/Bootstrappers/BootExcludeList.php index abd1552c..69d9dce1 100644 --- a/src/Bootstrappers/BootExcludeList.php +++ b/src/Bootstrappers/BootExcludeList.php @@ -17,7 +17,7 @@ final class BootExcludeList implements Bootstrapper * * @var array */ - private const EXCLUDE_LIST = [ + private const array EXCLUDE_LIST = [ 'bin', 'overrides', 'resources', diff --git a/src/Bootstrappers/BootFiles.php b/src/Bootstrappers/BootFiles.php index 2017a796..ea7e60fa 100644 --- a/src/Bootstrappers/BootFiles.php +++ b/src/Bootstrappers/BootFiles.php @@ -24,7 +24,7 @@ final class BootFiles implements Bootstrapper * * @var array */ - private const STRUCTURE = [ + private const array STRUCTURE = [ 'Expectations', 'Expectations.php', 'Helpers', diff --git a/src/Bootstrappers/BootOverrides.php b/src/Bootstrappers/BootOverrides.php index efbcf7a3..ae2fa070 100644 --- a/src/Bootstrappers/BootOverrides.php +++ b/src/Bootstrappers/BootOverrides.php @@ -17,7 +17,7 @@ final class BootOverrides implements Bootstrapper * * @var array */ - public const FILES = [ + public const array FILES = [ '53c246e5f416a39817ac81124cdd64ea8403038d01d7a202e1ffa486fbdf3fa7' => 'Runner/Filter/NameFilterIterator.php', '77ffb7647b583bd82e37962c6fbdc4b04d3344d8a2c1ed103e625ed1ff7cb5c2' => 'Runner/ResultCache/DefaultResultCache.php', 'd0e81317889ad88c707db4b08a94cadee4c9010d05ff0a759f04e71af5efed89' => 'Runner/TestSuiteLoader.php', diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index 57f98e33..7877b237 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -20,7 +20,7 @@ final readonly class BootSubscribers implements Bootstrapper * * @var array> */ - private const SUBSCRIBERS = [ + private const array SUBSCRIBERS = [ Subscribers\EnsureConfigurationIsAvailable::class, Subscribers\EnsureIgnorableTestCasesAreIgnored::class, Subscribers\EnsureKernelDumpIsFlushed::class, diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 7199ad5b..9b8dc5f9 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -219,9 +219,6 @@ trait Testable { TestSuite::getInstance()->test = $this; - // Initialize test case properties - $this->__initializeTestCase(); - $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); $method->setUp($this); @@ -270,7 +267,7 @@ trait Testable /** * Initialize test case properties from TestSuite. */ - private function __initializeTestCase(): void + public function __initializeTestCase(): void { // Return if the test case has already been initialized if (isset($this->__test)) { diff --git a/src/Console/Help.php b/src/Console/Help.php index 3d09d5f5..50823d59 100644 --- a/src/Console/Help.php +++ b/src/Console/Help.php @@ -16,7 +16,7 @@ final readonly class Help * * @var array */ - private const HELP_MESSAGES = [ + private const array HELP_MESSAGES = [ 'Pest Options:', ' --init Initialise a standard Pest configuration', ' --coverage Enable coverage and output to standard output', diff --git a/src/Console/Thanks.php b/src/Console/Thanks.php index 7e68f871..fc9f558a 100644 --- a/src/Console/Thanks.php +++ b/src/Console/Thanks.php @@ -22,7 +22,7 @@ final readonly class Thanks * * @var array */ - private const FUNDING_MESSAGES = [ + private const array FUNDING_MESSAGES = [ 'Star' => 'https://github.com/pestphp/pest', 'YouTube' => 'https://youtube.com/@nunomaduro', 'TikTok' => 'https://tiktok.com/@nunomaduro', diff --git a/src/Kernel.php b/src/Kernel.php index fc82574c..92716913 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -34,7 +34,7 @@ final readonly class Kernel * * @var array */ - private const BOOTSTRAPPERS = [ + private const array BOOTSTRAPPERS = [ Bootstrappers\BootOverrides::class, Bootstrappers\BootSubscribers::class, Bootstrappers\BootFiles::class, diff --git a/src/Logging/Converter.php b/src/Logging/Converter.php index b4560e22..4805a946 100644 --- a/src/Logging/Converter.php +++ b/src/Logging/Converter.php @@ -31,7 +31,7 @@ final readonly class Converter /** * The prefix for the test suite name. */ - private const PREFIX = 'P\\'; + private const string PREFIX = 'P\\'; /** * The state generator. diff --git a/src/Plugins/Cache.php b/src/Plugins/Cache.php index ea3abb78..3ae0433b 100644 --- a/src/Plugins/Cache.php +++ b/src/Plugins/Cache.php @@ -21,7 +21,7 @@ final class Cache implements HandlesArguments /** * The temporary folder. */ - private const TEMPORARY_FOLDER = __DIR__ + private const string TEMPORARY_FOLDER = __DIR__ .DIRECTORY_SEPARATOR .'..' .DIRECTORY_SEPARATOR diff --git a/src/Plugins/Configuration.php b/src/Plugins/Configuration.php index acae8fb4..27d07ab8 100644 --- a/src/Plugins/Configuration.php +++ b/src/Plugins/Configuration.php @@ -21,7 +21,7 @@ final class Configuration implements HandlesArguments, Terminable /** * The base PHPUnit file. */ - public const BASE_PHPUNIT_FILE = __DIR__ + public const string BASE_PHPUNIT_FILE = __DIR__ .DIRECTORY_SEPARATOR .'..' .DIRECTORY_SEPARATOR diff --git a/src/Plugins/Coverage.php b/src/Plugins/Coverage.php index a5061d25..712f5de5 100644 --- a/src/Plugins/Coverage.php +++ b/src/Plugins/Coverage.php @@ -17,20 +17,11 @@ use Symfony\Component\Console\Output\OutputInterface; */ final class Coverage implements AddsOutput, HandlesArguments { - /** - * @var string - */ - private const COVERAGE_OPTION = 'coverage'; + private const string COVERAGE_OPTION = 'coverage'; - /** - * @var string - */ - private const MIN_OPTION = 'min'; + private const string MIN_OPTION = 'min'; - /** - * @var string - */ - private const EXACTLY_OPTION = 'exactly'; + private const string EXACTLY_OPTION = 'exactly'; /** * Whether it should show the coverage or not. diff --git a/src/Plugins/Environment.php b/src/Plugins/Environment.php index 8ff10b4c..7edbbbd3 100644 --- a/src/Plugins/Environment.php +++ b/src/Plugins/Environment.php @@ -14,12 +14,12 @@ final class Environment implements HandlesArguments /** * The continuous integration environment. */ - public const CI = 'ci'; + public const string CI = 'ci'; /** * The local environment. */ - public const LOCAL = 'local'; + public const string LOCAL = 'local'; /** * The current environment. diff --git a/src/Plugins/Init.php b/src/Plugins/Init.php index eb87b086..c31dd759 100644 --- a/src/Plugins/Init.php +++ b/src/Plugins/Init.php @@ -20,12 +20,12 @@ final readonly class Init implements HandlesArguments /** * The option the triggers the init job. */ - private const INIT_OPTION = '--init'; + private const string INIT_OPTION = '--init'; /** * The files that will be created. */ - private const STUBS = [ + private const array STUBS = [ 'phpunit.xml.stub' => 'phpunit.xml', 'Pest.php.stub' => 'tests/Pest.php', 'TestCase.php.stub' => 'tests/TestCase.php', diff --git a/src/Plugins/Only.php b/src/Plugins/Only.php index 0d958173..7c2809f1 100644 --- a/src/Plugins/Only.php +++ b/src/Plugins/Only.php @@ -15,7 +15,7 @@ final class Only implements Terminable /** * The temporary folder. */ - private const TEMPORARY_FOLDER = __DIR__ + private const string TEMPORARY_FOLDER = __DIR__ .DIRECTORY_SEPARATOR .'..' .DIRECTORY_SEPARATOR diff --git a/src/Plugins/Parallel.php b/src/Plugins/Parallel.php index 1632a050..94902823 100644 --- a/src/Plugins/Parallel.php +++ b/src/Plugins/Parallel.php @@ -23,9 +23,9 @@ final class Parallel implements HandlesArguments { use HandleArguments; - private const GLOBAL_PREFIX = 'PEST_PARALLEL_GLOBAL_'; + private const string GLOBAL_PREFIX = 'PEST_PARALLEL_GLOBAL_'; - private const HANDLERS = [ + private const array HANDLERS = [ Parallel\Handlers\Parallel::class, Parallel\Handlers\Pest::class, Parallel\Handlers\Laravel::class, @@ -34,7 +34,7 @@ final class Parallel implements HandlesArguments /** * @var string[] */ - private const UNSUPPORTED_ARGUMENTS = ['--todo', '--todos', '--retry', '--notes', '--issue', '--pr', '--pull-request']; + private const array UNSUPPORTED_ARGUMENTS = ['--todo', '--todos', '--retry', '--notes', '--issue', '--pr', '--pull-request']; /** * Whether the given command line arguments indicate that the test suite should be run in parallel. diff --git a/src/Plugins/Parallel/Handlers/Parallel.php b/src/Plugins/Parallel/Handlers/Parallel.php index 76a59af6..d99139b2 100644 --- a/src/Plugins/Parallel/Handlers/Parallel.php +++ b/src/Plugins/Parallel/Handlers/Parallel.php @@ -18,7 +18,7 @@ final class Parallel implements HandlesArguments /** * The list of arguments to remove. */ - private const ARGS_TO_REMOVE = [ + private const array ARGS_TO_REMOVE = [ '--parallel', '-p', '--no-output', diff --git a/src/Plugins/Parallel/Paratest/CleanConsoleOutput.php b/src/Plugins/Parallel/Paratest/CleanConsoleOutput.php index d2801ced..cf5272b1 100644 --- a/src/Plugins/Parallel/Paratest/CleanConsoleOutput.php +++ b/src/Plugins/Parallel/Paratest/CleanConsoleOutput.php @@ -11,6 +11,7 @@ final class CleanConsoleOutput extends ConsoleOutput /** * {@inheritdoc} */ + #[\Override] protected function doWrite(string $message, bool $newline): void // @pest-arch-ignore-line { if ($this->isOpeningHeadline($message)) { diff --git a/src/Plugins/Parallel/Paratest/WrapperRunner.php b/src/Plugins/Parallel/Paratest/WrapperRunner.php index 48c88e7b..877a7373 100644 --- a/src/Plugins/Parallel/Paratest/WrapperRunner.php +++ b/src/Plugins/Parallel/Paratest/WrapperRunner.php @@ -50,7 +50,7 @@ final class WrapperRunner implements RunnerInterface /** * The time to sleep between cycles. */ - private const CYCLE_SLEEP = 10000; + private const int CYCLE_SLEEP = 10000; /** * The result printer. @@ -313,7 +313,6 @@ final class WrapperRunner implements RunnerInterface $testResult = unserialize($contents); assert($testResult instanceof TestResult); - $testResultSum = new TestResult( (int) $testResultSum->hasTests() + (int) $testResult->hasTests(), $testResultSum->numberOfTestsRun() + $testResult->numberOfTestsRun(), @@ -328,15 +327,25 @@ final class WrapperRunner implements RunnerInterface array_merge_recursive($testResultSum->testTriggeredPhpunitErrorEvents(), $testResult->testTriggeredPhpunitErrorEvents()), array_merge_recursive($testResultSum->testTriggeredPhpunitNoticeEvents(), $testResult->testTriggeredPhpunitNoticeEvents()), array_merge_recursive($testResultSum->testTriggeredPhpunitWarningEvents(), $testResult->testTriggeredPhpunitWarningEvents()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->testRunnerTriggeredDeprecationEvents(), $testResult->testRunnerTriggeredDeprecationEvents()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->testRunnerTriggeredNoticeEvents(), $testResult->testRunnerTriggeredNoticeEvents()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->testRunnerTriggeredWarningEvents(), $testResult->testRunnerTriggeredWarningEvents()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->errors(), $testResult->errors()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->deprecations(), $testResult->deprecations()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->notices(), $testResult->notices()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->warnings(), $testResult->warnings()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->phpDeprecations(), $testResult->phpDeprecations()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->phpNotices(), $testResult->phpNotices()), + // @phpstan-ignore-next-line array_merge_recursive($testResultSum->phpWarnings(), $testResult->phpWarnings()), $testResultSum->numberOfIssuesIgnoredByBaseline() + $testResult->numberOfIssuesIgnoredByBaseline(), ); diff --git a/src/Plugins/Parallel/Support/CompactPrinter.php b/src/Plugins/Parallel/Support/CompactPrinter.php index bc2e1c3f..aa2da210 100644 --- a/src/Plugins/Parallel/Support/CompactPrinter.php +++ b/src/Plugins/Parallel/Support/CompactPrinter.php @@ -34,7 +34,7 @@ final class CompactPrinter /** * @var array> */ - private const LOOKUP_TABLE = [ + private const array LOOKUP_TABLE = [ '.' => ['gray', '.'], 'S' => ['yellow', 's'], 'T' => ['cyan', 't'], diff --git a/src/Plugins/Verbose.php b/src/Plugins/Verbose.php index e37938a3..9cec77de 100644 --- a/src/Plugins/Verbose.php +++ b/src/Plugins/Verbose.php @@ -16,7 +16,7 @@ final class Verbose implements HandlesArguments /** * The list of verbosity levels. */ - private const VERBOSITY_LEVELS = ['v', 'vv', 'vvv', 'q']; + private const array VERBOSITY_LEVELS = ['v', 'vv', 'vvv', 'q']; /** * {@inheritDoc} diff --git a/src/Repositories/DatasetsRepository.php b/src/Repositories/DatasetsRepository.php index 7d318a2d..3deee5bd 100644 --- a/src/Repositories/DatasetsRepository.php +++ b/src/Repositories/DatasetsRepository.php @@ -19,7 +19,7 @@ use function sprintf; */ final class DatasetsRepository { - private const SEPARATOR = '>>'; + private const string SEPARATOR = '>>'; /** * Holds the datasets. diff --git a/src/Result.php b/src/Result.php index 98e9e8b6..22e1e895 100644 --- a/src/Result.php +++ b/src/Result.php @@ -13,11 +13,11 @@ use PHPUnit\TextUI\Configuration\Configuration; */ final class Result { - private const SUCCESS_EXIT = 0; + private const int SUCCESS_EXIT = 0; - private const FAILURE_EXIT = 1; + private const int FAILURE_EXIT = 1; - private const EXCEPTION_EXIT = 2; + private const int EXCEPTION_EXIT = 2; /** * If the exit code is different from 0. diff --git a/src/Runner/Filter/EnsureTestCaseIsInitiatedFilter.php b/src/Runner/Filter/EnsureTestCaseIsInitiatedFilter.php new file mode 100644 index 00000000..614b38a4 --- /dev/null +++ b/src/Runner/Filter/EnsureTestCaseIsInitiatedFilter.php @@ -0,0 +1,39 @@ + $iterator + */ + public function __construct(RecursiveIterator $iterator) + { + parent::__construct($iterator); + } + + /** + * {@inheritdoc} + */ + public function accept(): bool + { + $test = $this->getInnerIterator()->current(); + + if ($test instanceof HasPrintableTestCaseName) { + /** @phpstan-ignore-next-line */ + $test->__initializeTestCase(); + } + + return true; + } +} diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index 03001976..652eb442 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -11,12 +11,9 @@ use Pest\Exceptions\ShouldNotHappen; */ final class Backtrace { - /** - * @var string - */ - private const FILE = 'file'; + private const string FILE = 'file'; - private const BACKTRACE_OPTIONS = DEBUG_BACKTRACE_IGNORE_ARGS; + private const int BACKTRACE_OPTIONS = DEBUG_BACKTRACE_IGNORE_ARGS; /** * Returns the current test file. diff --git a/src/Support/DatasetInfo.php b/src/Support/DatasetInfo.php index 46f39c41..c67f317c 100644 --- a/src/Support/DatasetInfo.php +++ b/src/Support/DatasetInfo.php @@ -11,9 +11,9 @@ use function Pest\testDirectory; */ final class DatasetInfo { - public const DATASETS_DIR_NAME = 'Datasets'; + public const string DATASETS_DIR_NAME = 'Datasets'; - public const DATASETS_FILE_NAME = 'Datasets.php'; + public const string DATASETS_FILE_NAME = 'Datasets.php'; public static function isInsideADatasetsDirectory(string $file): bool { diff --git a/src/Support/ExceptionTrace.php b/src/Support/ExceptionTrace.php index 9af6aa5b..9d4132e2 100644 --- a/src/Support/ExceptionTrace.php +++ b/src/Support/ExceptionTrace.php @@ -13,7 +13,7 @@ use Throwable; */ final class ExceptionTrace { - private const UNDEFINED_METHOD = 'Call to undefined method P\\'; + private const string UNDEFINED_METHOD = 'Call to undefined method P\\'; /** * Ensures the given closure reports the good execution context. diff --git a/src/Support/Exporter.php b/src/Support/Exporter.php index 169f4891..44367c08 100644 --- a/src/Support/Exporter.php +++ b/src/Support/Exporter.php @@ -15,7 +15,7 @@ final readonly class Exporter /** * The maximum number of items in an array to export. */ - private const MAX_ARRAY_ITEMS = 3; + private const int MAX_ARRAY_ITEMS = 3; /** * Creates a new Exporter instance. diff --git a/src/Support/HigherOrderMessage.php b/src/Support/HigherOrderMessage.php index 89c3e1f1..ce948244 100644 --- a/src/Support/HigherOrderMessage.php +++ b/src/Support/HigherOrderMessage.php @@ -13,7 +13,7 @@ use Throwable; */ final class HigherOrderMessage { - public const UNDEFINED_METHOD = 'Method %s does not exist'; + public const string UNDEFINED_METHOD = 'Method %s does not exist'; /** * An optional condition that will determine if the message will be executed. diff --git a/src/Support/Str.php b/src/Support/Str.php index 0e654bc8..67cff796 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -13,12 +13,9 @@ final class Str * Pool of alpha-numeric characters for generating (unsafe) random strings * from. */ - private const POOL = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + private const string POOL = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - /** - * @var string - */ - private const PREFIX = '__pest_evaluable_'; + private const string PREFIX = '__pest_evaluable_'; /** * Create a (unsecure & non-cryptographically safe) random alpha-numeric From 99c9f4e5d806e838820868e469e815be1670db36 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 3 May 2025 11:58:03 +0100 Subject: [PATCH 52/95] Bumps dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index f4617a5b..cbf566a1 100644 --- a/composer.json +++ b/composer.json @@ -18,17 +18,17 @@ ], "require": { "php": "^8.3.0", - "brianium/paratest": "^7.9.1", + "brianium/paratest": "^7.10.1", "nunomaduro/collision": "^8.8.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", - "phpunit/phpunit": "^12.1.3" + "phpunit/phpunit": "^12.1.4" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.1.3", + "phpunit/phpunit": ">12.1.4", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, From 7711a52fe909b5475d438644c106582c730f7903 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 9 May 2025 13:10:29 +0100 Subject: [PATCH 53/95] Bumps dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cbf566a1..6052ec75 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": "^8.3.0", "brianium/paratest": "^7.10.1", "nunomaduro/collision": "^8.8.0", - "nunomaduro/termwind": "^2.3.0", + "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", From 909d778da3cc65338111c0195ea67fccc829d84d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 21 May 2025 02:00:15 +0100 Subject: [PATCH 54/95] fix: undefined property --- src/Concerns/Testable.php | 2 +- src/Pest.php | 2 +- .../Visual/Help/visual_snapshot_of_help_command_output.snap | 4 +++- .../Version/visual_snapshot_of_help_command_output.snap | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 9b8dc5f9..9918eb83 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -470,7 +470,7 @@ trait Testable */ public static function getLatestPrintableTestCaseMethodName(): string { - return self::$__latestDescription; + return self::$__latestDescription ?? ''; } /** diff --git a/src/Pest.php b/src/Pest.php index d23cfd5c..865c18d0 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '4.0.0-alpha.1'; + return '4.0.0-alpha.2'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 9966d901..09fe9928 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 4.0.0-alpha.1. + Pest Testing Framework 4.0.0-alpha.2. USAGE: pest [options] @@ -72,6 +72,7 @@ --fail-on-notice Signal failure using shell exit code when a notice was triggered --fail-on-skipped Signal failure using shell exit code when a test was skipped --fail-on-incomplete Signal failure using shell exit code when a test was marked incomplete + --fail-on-all-issues Signal failure using shell exit code when an issue is triggered --cache-result ............................ Write test results to cache file --do-not-cache-result .............. Do not write test results to cache file --order-by [order] Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size @@ -93,6 +94,7 @@ --display-errors ............. Display details for errors triggered by tests --display-notices ........... Display details for notices triggered by tests --display-warnings ......... Display details for warnings triggered by tests + --display-all-issues ..... Display details for all issues that are triggered --reverse-list .............................. Print defects in reverse order --teamcity . Replace default progress and result output with TeamCity format --testdox ................ Replace default result output with TestDox format diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index c9dea0d5..d72ea0de 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 4.0.0-alpha.1. + Pest Testing Framework 4.0.0-alpha.2. From c62cc3fef0d2751a2a087c4f6ab71b810827a0dc Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 23 May 2025 05:19:56 +0100 Subject: [PATCH 55/95] chore: adds pokio --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 6052ec75..92dcddfe 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "php": "^8.3.0", "brianium/paratest": "^7.10.1", "nunomaduro/collision": "^8.8.0", + "nunomaduro/pokio": "dev-main", "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", From 02b1ffb33481197fe7abd3aed45c9e0ee961e67c Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 27 May 2025 11:37:29 +0100 Subject: [PATCH 56/95] chore: bump dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 92dcddfe..a9800dab 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", - "phpunit/phpunit": "^12.1.4" + "phpunit/phpunit": "^12.1.6" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.1.4", + "phpunit/phpunit": ">12.1.6", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, From 0fc9d4dfe0260827fc2fd9c6fce07ed99508fcc8 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 8 Jun 2025 15:29:23 +0100 Subject: [PATCH 57/95] feat: adds phpunit `12.2.1` support --- composer.json | 5 +- .../Parallel/Paratest/ResultPrinter.php | 4 +- .../Parallel/Paratest/WrapperRunner.php | 6 +- ...isual_snapshot_of_help_command_output.snap | 3 + tests/.snapshots/success.txt | 18 ++++-- tests/Features/Covers.php | 59 ------------------- tests/Features/Covers/ClassCoverage.php | 13 ++++ tests/Features/Covers/CoversNothing.php | 10 ++++ tests/Features/Covers/ExceptionHandling.php | 10 ++++ tests/Features/Covers/FunctionCoverage.php | 12 ++++ tests/Features/Covers/GuessCoverage.php | 17 ++++++ tests/Features/Covers/TraitCoverage.php | 11 ++++ 12 files changed, 99 insertions(+), 69 deletions(-) delete mode 100644 tests/Features/Covers.php create mode 100644 tests/Features/Covers/ClassCoverage.php create mode 100644 tests/Features/Covers/CoversNothing.php create mode 100644 tests/Features/Covers/ExceptionHandling.php create mode 100644 tests/Features/Covers/FunctionCoverage.php create mode 100644 tests/Features/Covers/GuessCoverage.php create mode 100644 tests/Features/Covers/TraitCoverage.php diff --git a/composer.json b/composer.json index a9800dab..da76ff30 100644 --- a/composer.json +++ b/composer.json @@ -20,16 +20,15 @@ "php": "^8.3.0", "brianium/paratest": "^7.10.1", "nunomaduro/collision": "^8.8.0", - "nunomaduro/pokio": "dev-main", "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", - "phpunit/phpunit": "^12.1.6" + "phpunit/phpunit": "^12.2.1" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.1.6", + "phpunit/phpunit": ">12.2.1", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, diff --git a/src/Plugins/Parallel/Paratest/ResultPrinter.php b/src/Plugins/Parallel/Paratest/ResultPrinter.php index bd416e1e..e7a1c24d 100644 --- a/src/Plugins/Parallel/Paratest/ResultPrinter.php +++ b/src/Plugins/Parallel/Paratest/ResultPrinter.php @@ -59,10 +59,10 @@ final class ResultPrinter private readonly OutputInterface $output, private readonly Options $options ) { - $this->printer = new class($this->output) implements Printer + $this->printer = new readonly class($this->output) implements Printer { public function __construct( - private readonly OutputInterface $output, + private OutputInterface $output, ) {} public function print(string $buffer): void diff --git a/src/Plugins/Parallel/Paratest/WrapperRunner.php b/src/Plugins/Parallel/Paratest/WrapperRunner.php index 877a7373..469f2aa6 100644 --- a/src/Plugins/Parallel/Paratest/WrapperRunner.php +++ b/src/Plugins/Parallel/Paratest/WrapperRunner.php @@ -17,6 +17,7 @@ use ParaTest\WrapperRunner\WrapperWorker; use Pest\Result; use Pest\TestSuite; use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; use PHPUnit\Event\TestRunner\WarningTriggered; use PHPUnit\Runner\CodeCoverage; use PHPUnit\Runner\ResultCache\DefaultResultCache; @@ -313,12 +314,15 @@ final class WrapperRunner implements RunnerInterface $testResult = unserialize($contents); assert($testResult instanceof TestResult); + /** @var list $failedEvents */ + $failedEvents = array_merge_recursive($testResultSum->testFailedEvents(), $testResult->testFailedEvents()); + $testResultSum = new TestResult( (int) $testResultSum->hasTests() + (int) $testResult->hasTests(), $testResultSum->numberOfTestsRun() + $testResult->numberOfTestsRun(), $testResultSum->numberOfAssertions() + $testResult->numberOfAssertions(), array_merge_recursive($testResultSum->testErroredEvents(), $testResult->testErroredEvents()), - array_merge_recursive($testResultSum->testFailedEvents(), $testResult->testFailedEvents()), + $failedEvents, array_merge_recursive($testResultSum->testConsideredRiskyEvents(), $testResult->testConsideredRiskyEvents()), array_merge_recursive($testResultSum->testSuiteSkippedEvents(), $testResult->testSuiteSkippedEvents()), array_merge_recursive($testResultSum->testSkippedEvents(), $testResult->testSkippedEvents()), diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 09fe9928..b2f7a23f 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -100,10 +100,12 @@ --testdox ................ Replace default result output with TestDox format --testdox-summary Repeat TestDox output for tests with errors, failures, or issues --debug Replace default progress and result output with debugging information + --with-telemetry Include telemetry information in debugging information output --compact ................ Replace default result output with Compact format LOGGING OPTIONS: --log-junit [file] .......... Write test results in JUnit XML format to file + --log-otr [file] Write test results in Open Test Reporting XML format to file --log-teamcity [file] ........ Write test results in TeamCity format to file --testdox-html [file] .. Write test results in TestDox format (HTML) to file --testdox-text [file] Write test results in TestDox format (plain text) to file @@ -115,6 +117,7 @@ --coverage ..... Generate code coverage report and output to standard output --coverage --min Set the minimum required coverage percentage, and fail if not met --coverage-clover [file] Write code coverage report in Clover XML format to file + --coverage-openclover [file] Write code coverage report in OpenClover XML format to file --coverage-cobertura [file] Write code coverage report in Cobertura XML format to file --coverage-crap4j [file] Write code coverage report in Crap4J XML format to file --coverage-html [dir] Write code coverage report in HTML format to directory diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 513a804e..5245e72c 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -68,14 +68,24 @@ ✓ it adds coverage if --min exist ✓ it generates coverage based on file input - PASS Tests\Features\Covers + PASS Tests\Features\Covers\ClassCoverage ✓ it uses the correct PHPUnit attribute for class - ✓ it uses the correct PHPUnit attribute for function - ✓ it guesses if the given argument is a class or function - ✓ it uses the correct PHPUnit attribute for trait + + PASS Tests\Features\Covers\CoversNothing ✓ it uses the correct PHPUnit attribute for covers nothing + + PASS Tests\Features\Covers\ExceptionHandling ✓ it throws exception if no class nor method has been found + PASS Tests\Features\Covers\FunctionCoverage + ✓ it uses the correct PHPUnit attribute for function + + PASS Tests\Features\Covers\GuessCoverage + ✓ it guesses if the given argument is a class or function + + PASS Tests\Features\Covers\TraitCoverage + ✓ it uses the correct PHPUnit attribute for trait + PASS Tests\Features\DatasetsTests - 1 todo ✓ it throws exception if dataset does not exist ✓ it throws exception if dataset already exist diff --git a/tests/Features/Covers.php b/tests/Features/Covers.php deleted file mode 100644 index 386d523f..00000000 --- a/tests/Features/Covers.php +++ /dev/null @@ -1,59 +0,0 @@ -getAttributes(); - - expect($attributes[1]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass'); - expect($attributes[1]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversClass1'); -}); - -it('uses the correct PHPUnit attribute for function', function () { - $attributes = (new ReflectionClass($this))->getAttributes(); - - expect($attributes[3]->getName())->toBe('PHPUnit\Framework\Attributes\CoversFunction'); - expect($attributes[3]->getArguments()[0])->toBe('testCoversFunction'); -})->coversFunction('testCoversFunction'); - -it('guesses if the given argument is a class or function', function () { - $attributes = (new ReflectionClass($this))->getAttributes(); - - expect($attributes[5]->getName())->toBe(CoversClass::class); - expect($attributes[5]->getArguments()[0])->toBe(CoversClass3::class); - - expect($attributes[6]->getName())->toBe(CoversFunction::class); - expect($attributes[6]->getArguments()[0])->toBe('testCoversFunction'); -})->covers(CoversClass3::class, 'testCoversFunction'); - -it('uses the correct PHPUnit attribute for trait', function () { - $attributes = (new ReflectionClass($this))->getAttributes(); - - expect($attributes[8]->getName())->toBe('PHPUnit\Framework\Attributes\CoversTrait'); - expect($attributes[8]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversTrait'); -})->coversTrait(CoversTrait::class); - -it('uses the correct PHPUnit attribute for covers nothing', function () { - $attributes = (new ReflectionMethod($this, $this->name()))->getAttributes(); - - expect($attributes[3]->getName())->toBe('PHPUnit\Framework\Attributes\CoversNothing'); - expect($attributes[3]->getArguments())->toHaveCount(0); -})->coversNothing(); - -it('throws exception if no class nor method has been found', function () { - $testCall = new TestCall(TestSuite::getInstance(), 'filename', 'description', fn () => 'closure'); - - $testCall->covers('fakeName'); -})->throws(InvalidArgumentException::class, 'No class, trait or method named "fakeName" has been found.'); diff --git a/tests/Features/Covers/ClassCoverage.php b/tests/Features/Covers/ClassCoverage.php new file mode 100644 index 00000000..ab805e17 --- /dev/null +++ b/tests/Features/Covers/ClassCoverage.php @@ -0,0 +1,13 @@ +getAttributes(); + + expect($attributes[1]->getName())->toBe(CoversClass::class); + expect($attributes[1]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversClass1'); +}); diff --git a/tests/Features/Covers/CoversNothing.php b/tests/Features/Covers/CoversNothing.php new file mode 100644 index 00000000..6918414c --- /dev/null +++ b/tests/Features/Covers/CoversNothing.php @@ -0,0 +1,10 @@ +name()))->getAttributes(); + + expect($attributes[2]->getName())->toBe(CoversNothing::class); + expect($attributes[2]->getArguments())->toHaveCount(0); +})->coversNothing(); diff --git a/tests/Features/Covers/ExceptionHandling.php b/tests/Features/Covers/ExceptionHandling.php new file mode 100644 index 00000000..de86bb02 --- /dev/null +++ b/tests/Features/Covers/ExceptionHandling.php @@ -0,0 +1,10 @@ + 'closure'); + + $testCall->covers('fakeName'); +})->throws(InvalidArgumentException::class, 'No class, trait or method named "fakeName" has been found.'); diff --git a/tests/Features/Covers/FunctionCoverage.php b/tests/Features/Covers/FunctionCoverage.php new file mode 100644 index 00000000..fba97080 --- /dev/null +++ b/tests/Features/Covers/FunctionCoverage.php @@ -0,0 +1,12 @@ +getAttributes(); + + expect($attributes[1]->getName())->toBe(CoversFunction::class); + expect($attributes[1]->getArguments()[0])->toBe('testCoversFunction'); +})->coversFunction('testCoversFunction'); diff --git a/tests/Features/Covers/GuessCoverage.php b/tests/Features/Covers/GuessCoverage.php new file mode 100644 index 00000000..e8a84035 --- /dev/null +++ b/tests/Features/Covers/GuessCoverage.php @@ -0,0 +1,17 @@ +getAttributes(); + + expect($attributes[1]->getName())->toBe(CoversClass::class); + expect($attributes[1]->getArguments()[0])->toBe(CoversClass3::class); + + expect($attributes[2]->getName())->toBe(CoversFunction::class); + expect($attributes[2]->getArguments()[0])->toBe('testCoversFunction2'); +})->covers(CoversClass3::class, 'testCoversFunction2'); diff --git a/tests/Features/Covers/TraitCoverage.php b/tests/Features/Covers/TraitCoverage.php new file mode 100644 index 00000000..57bc3680 --- /dev/null +++ b/tests/Features/Covers/TraitCoverage.php @@ -0,0 +1,11 @@ +getAttributes(); + + expect($attributes[1]->getName())->toBe(PHPUnitCoversTrait::class); + expect($attributes[1]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversTrait'); +})->coversTrait(CoversTrait::class); From 5d2aafd2a3cbe6141126146272abfe3471d68a35 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 12 Jun 2025 00:38:05 +0100 Subject: [PATCH 58/95] feat: --profanity --- .github/workflows/static.yml | 7 +++++-- composer.json | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 69dd6b98..491cd60d 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -31,8 +31,11 @@ jobs: - name: Install Dependencies run: composer update --prefer-stable --no-interaction --no-progress --ansi - # - name: Type Check - # run: composer test:type:check + - name: Profanity Check + run: composer test:profanity + + - name: Type Check + run: composer test:type:check - name: Type Coverage run: composer test:type:coverage diff --git a/composer.json b/composer.json index da76ff30..cdc534e4 100644 --- a/composer.json +++ b/composer.json @@ -18,12 +18,13 @@ ], "require": { "php": "^8.3.0", - "brianium/paratest": "^7.10.1", - "nunomaduro/collision": "^8.8.0", + "brianium/paratest": "^7.10.2", + "nunomaduro/collision": "^8.8.1", "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", + "pestphp/pest-plugin-profanity": "^4.0.0", "phpunit/phpunit": "^12.2.1" }, "conflict": { @@ -55,7 +56,7 @@ "require-dev": { "pestphp/pest-dev-tools": "^4.0.0", "pestphp/pest-plugin-type-coverage": "^4.0.0", - "symfony/process": "^7.2.5" + "symfony/process": "^7.3.0" }, "minimum-stability": "dev", "prefer-stable": true, @@ -74,6 +75,7 @@ "lint": "pint", "test:refacto": "rector --dry-run", "test:lint": "pint --test", + "test:profanity": "php bin/pest --profanity --compact --language=en", "test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug", "test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100", "test:unit": "php bin/pest --exclude-group=integration --compact", From dd44ac41951141e2675022907804a334bcfc8848 Mon Sep 17 00:00:00 2001 From: JonPurvis Date: Sat, 14 Jun 2025 21:36:39 +0100 Subject: [PATCH 59/95] remove language option from profanity composer script --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cdc534e4..f5718b7b 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "lint": "pint", "test:refacto": "rector --dry-run", "test:lint": "pint --test", - "test:profanity": "php bin/pest --profanity --compact --language=en", + "test:profanity": "php bin/pest --profanity --compact", "test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug", "test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100", "test:unit": "php bin/pest --exclude-group=integration --compact", From 49bf00024f6cd559c40b16d93233587ef2d43605 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 15 Jun 2025 22:43:59 +0100 Subject: [PATCH 60/95] fix: coverage when coverage file is over `2.4gb` on mac os --- composer.json | 4 +- overrides/Report/PHP.php | 98 +++++++++++++++++++++++++++++ src/Bootstrappers/BootOverrides.php | 17 ++--- src/Support/Coverage.php | 9 ++- 4 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 overrides/Report/PHP.php diff --git a/composer.json b/composer.json index cdc534e4..516e62b9 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", "pestphp/pest-plugin-profanity": "^4.0.0", - "phpunit/phpunit": "^12.2.1" + "phpunit/phpunit": "^12.2.2" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.2.1", + "phpunit/phpunit": ">12.2.2", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, diff --git a/overrides/Report/PHP.php b/overrides/Report/PHP.php new file mode 100644 index 00000000..0cd7e27b --- /dev/null +++ b/overrides/Report/PHP.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report; + +use const PHP_EOL; + +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\WriteOperationFailedException; + +use function dirname; +use function serialize; +use function str_contains; + +final class PHP +{ + public function process(CodeCoverage $coverage, ?string $target = null): string + { + $coverage->clearCache(); + + $buffer = " */ public const array FILES = [ - '53c246e5f416a39817ac81124cdd64ea8403038d01d7a202e1ffa486fbdf3fa7' => 'Runner/Filter/NameFilterIterator.php', - '77ffb7647b583bd82e37962c6fbdc4b04d3344d8a2c1ed103e625ed1ff7cb5c2' => 'Runner/ResultCache/DefaultResultCache.php', - 'd0e81317889ad88c707db4b08a94cadee4c9010d05ff0a759f04e71af5efed89' => 'Runner/TestSuiteLoader.php', - '3bb609b0d3bf6dee8df8d6cd62a3c8ece823c4bb941eaaae39e3cb267171b9d2' => 'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php', - '8abdad6413329c6fe0d7d44a8b9926e390af32c0b3123f3720bb9c5bbc6fbb7e' => 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php', - 'b4250fc3ffad5954624cb5e682fd940b874e8d3422fa1ee298bd7225e1aa5fc2' => 'TextUI/TestSuiteFilterProcessor.php', - '8cfcb4999af79463eca51a42058e502ea4ddc776cba5677bf2f8eb6093e21a5c' => 'Event/Value/ThrowableBuilder.php', - '86cd9bcaa53cdd59c5b13e58f30064a015c549501e7629d93b96893d4dee1eb1' => 'Logging/JUnit/JunitXmlLogger.php', + 'Runner/Filter/NameFilterIterator.php', + 'Runner/ResultCache/DefaultResultCache.php', + 'Runner/TestSuiteLoader.php', + 'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php', + 'TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php', + 'TextUI/TestSuiteFilterProcessor.php', + 'Event/Value/ThrowableBuilder.php', + 'Logging/JUnit/JunitXmlLogger.php', + 'Report/Php.php', ]; /** diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 955bbfc4..1aa641fb 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -89,9 +89,16 @@ final class Coverage } /** @var CodeCoverage $codeCoverage */ - $codeCoverage = require $reportPath; + $handle = fopen($reportPath, 'r'); + $code = ''; + while (! feof($handle)) { + $code .= fread($handle, 8192); + } + fclose($handle); unlink($reportPath); + $codeCoverage = eval(substr($code, 5)); + $totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines(); /** @var Directory $report */ From d6cbd12d8be41528da38b1a1a068e526774613b5 Mon Sep 17 00:00:00 2001 From: JonPurvis Date: Mon, 16 Jun 2025 02:51:48 +0100 Subject: [PATCH 61/95] remove period from message --- src/Exceptions/ShouldNotHappen.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptions/ShouldNotHappen.php b/src/Exceptions/ShouldNotHappen.php index ed356e29..73dea370 100644 --- a/src/Exceptions/ShouldNotHappen.php +++ b/src/Exceptions/ShouldNotHappen.php @@ -20,7 +20,7 @@ final class ShouldNotHappen extends RuntimeException $message = $exception->getMessage(); parent::__construct(sprintf(<<<'EOF' -This should not happen - please create an new issue here: https://github.com/pestphp/pest. +This should not happen - please create an new issue here: https://github.com/pestphp/pest Issue: %s PHP version: %s From 97c136cd94e002677b2a4251538659d93044b47e Mon Sep 17 00:00:00 2001 From: JonPurvis Date: Mon, 16 Jun 2025 02:55:11 +0100 Subject: [PATCH 62/95] link to issues page --- src/Exceptions/ShouldNotHappen.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptions/ShouldNotHappen.php b/src/Exceptions/ShouldNotHappen.php index 73dea370..3398af09 100644 --- a/src/Exceptions/ShouldNotHappen.php +++ b/src/Exceptions/ShouldNotHappen.php @@ -20,7 +20,7 @@ final class ShouldNotHappen extends RuntimeException $message = $exception->getMessage(); parent::__construct(sprintf(<<<'EOF' -This should not happen - please create an new issue here: https://github.com/pestphp/pest +This should not happen - please create an new issue here: https://github.com/pestphp/pest/issues Issue: %s PHP version: %s From 8c403a57c262d85ef9de6e7011a12de9b8482f07 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 16 Jun 2025 10:04:00 +0100 Subject: [PATCH 63/95] fix: upper case fix --- src/Bootstrappers/BootOverrides.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bootstrappers/BootOverrides.php b/src/Bootstrappers/BootOverrides.php index 205a7b0a..c9119219 100644 --- a/src/Bootstrappers/BootOverrides.php +++ b/src/Bootstrappers/BootOverrides.php @@ -26,7 +26,7 @@ final class BootOverrides implements Bootstrapper 'TextUI/TestSuiteFilterProcessor.php', 'Event/Value/ThrowableBuilder.php', 'Logging/JUnit/JunitXmlLogger.php', - 'Report/Php.php', + 'Report/PHP.php', ]; /** From c3bfdf130e52c26e2ce3e7331fab059e16e7b0fd Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 16 Jun 2025 10:14:04 +0100 Subject: [PATCH 64/95] chore: type checking --- src/Bootstrappers/BootOverrides.php | 2 +- src/Support/Coverage.php | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Bootstrappers/BootOverrides.php b/src/Bootstrappers/BootOverrides.php index c9119219..a4eef19f 100644 --- a/src/Bootstrappers/BootOverrides.php +++ b/src/Bootstrappers/BootOverrides.php @@ -15,7 +15,7 @@ final class BootOverrides implements Bootstrapper /** * The list of files to be overridden. * - * @var array + * @var array */ public const array FILES = [ 'Runner/Filter/NameFilterIterator.php', diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 1aa641fb..adcfa40d 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -88,13 +88,16 @@ final class Coverage throw ShouldNotHappen::fromMessage(sprintf('Coverage not found in path: %s.', $reportPath)); } - /** @var CodeCoverage $codeCoverage */ $handle = fopen($reportPath, 'r'); $code = ''; - while (! feof($handle)) { + while (is_resource($handle) && ! feof($handle)) { $code .= fread($handle, 8192); } - fclose($handle); + + if (is_resource($handle)) { + fclose($handle); + } + unlink($reportPath); $codeCoverage = eval(substr($code, 5)); From 163479ae60d8a45939e64916f8002706794db3c3 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 16 Jun 2025 10:14:16 +0100 Subject: [PATCH 65/95] chore: style --- src/Support/Coverage.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index adcfa40d..3f543790 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Pest\Support; use Pest\Exceptions\ShouldNotHappen; -use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Node\Directory; use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\Environment\Runtime; From ef76c04dbe907c65ea2f372630ab8a1327e79d43 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 27 Jun 2025 02:15:28 +0100 Subject: [PATCH 66/95] feat: adds `fixture` --- src/Functions.php | 25 +++++++++++++++++++++++++ src/Pest.php | 2 +- tests/Features/Fixture.php | 11 +++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/Features/Fixture.php diff --git a/src/Functions.php b/src/Functions.php index 1e12fe7e..f17ea15c 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -278,3 +278,28 @@ if (! function_exists('mutates')) { } } } + +if (! function_exists('fixture')) { + /** + * Returns the absolute path to a fixture file. + */ + function fixture(string $file): string + { + $file = implode(DIRECTORY_SEPARATOR, [ + TestSuite::getInstance()->rootPath, + TestSuite::getInstance()->testPath, + 'Fixtures', + str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file), + ]); + + $fileRealPath = realpath($file); + + if ($fileRealPath === false) { + throw new InvalidArgumentException( + 'The fixture file ['.$file.'] does not exist.', + ); + } + + return $fileRealPath; + } +} diff --git a/src/Pest.php b/src/Pest.php index 865c18d0..47af9ee1 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '4.0.0-alpha.2'; + return '4.0.0-alpha.3'; } function testDirectory(string $file = ''): string diff --git a/tests/Features/Fixture.php b/tests/Features/Fixture.php new file mode 100644 index 00000000..49b4c76a --- /dev/null +++ b/tests/Features/Fixture.php @@ -0,0 +1,11 @@ +toBeString() + ->toBeFile(); +}); + +it('may throw an exception if the file does not exist', function () { + fixture('file-that-does-not-exist.php'); From 7fc69033f895aeae3aa2fda1bcf8233388def7ef Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 27 Jun 2025 02:15:36 +0100 Subject: [PATCH 67/95] chore: adjusts style --- tests/Features/Fixture.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Features/Fixture.php b/tests/Features/Fixture.php index 49b4c76a..1ac538b8 100644 --- a/tests/Features/Fixture.php +++ b/tests/Features/Fixture.php @@ -9,3 +9,4 @@ it('may return a file path', function () { it('may throw an exception if the file does not exist', function () { fixture('file-that-does-not-exist.php'); +})->throws(InvalidArgumentException::class); From a22013a7d367bc7b3c3b52b3832fc236e5f85573 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 28 Jun 2025 12:14:45 +0100 Subject: [PATCH 68/95] fix: `with` types --- src/PendingCalls/TestCall.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 50fef3b6..92723e8d 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -178,10 +178,9 @@ final class TestCall // @phpstan-ignore-line } /** - * Runs the current test multiple times with - * each item of the given `iterable`. + * Runs the current test multiple times with each item of the given `iterable`. * - * @param array<\Closure|iterable|string> $data + * @param Closure|iterable|string $data */ public function with(Closure|iterable|string ...$data): self { From 0bc3219a2b3efe72275e8ed0ccc1dc9765178e65 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 28 Jun 2025 18:18:26 +0100 Subject: [PATCH 69/95] feat: moves `visit` to the core --- composer.json | 3 ++- resources/views/installers/plugin-browser.php | 17 ++++++++++++ src/Configuration.php | 8 ++++++ src/Functions.php | 26 +++++++++++++++++++ src/Installers/PluginBrowser.php | 15 +++++++++++ src/PendingCalls/TestCall.php | 2 +- src/Pest.php | 2 +- 7 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 resources/views/installers/plugin-browser.php create mode 100644 src/Installers/PluginBrowser.php diff --git a/composer.json b/composer.json index 516e62b9..80354710 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "require": { "php": "^8.3.0", "brianium/paratest": "^7.10.2", - "nunomaduro/collision": "^8.8.1", + "nunomaduro/collision": "^8.8.2", "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", @@ -55,6 +55,7 @@ }, "require-dev": { "pestphp/pest-dev-tools": "^4.0.0", + "pestphp/pest-plugin-browser": "^4.0.0", "pestphp/pest-plugin-type-coverage": "^4.0.0", "symfony/process": "^7.3.0" }, diff --git a/resources/views/installers/plugin-browser.php b/resources/views/installers/plugin-browser.php new file mode 100644 index 00000000..28288ab0 --- /dev/null +++ b/resources/views/installers/plugin-browser.php @@ -0,0 +1,17 @@ +
+

+ Using the visit() function requires the Pest Plugin Browser to be installed. + + Run: +

+ +
+ - + composer require pestphp/pest-plugin-browser:^4.0 --dev +
+ +
+ - + npx playwright install +
+
diff --git a/src/Configuration.php b/src/Configuration.php index c504aa65..fb4f45a4 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -102,6 +102,14 @@ final readonly class Configuration return Configuration\Project::getInstance(); } + /** + * Gets the browser configuration. + */ + public function browser(): Browser\Configuration + { + return new Browser\Configuration; + } + /** * Proxies calls to the uses method. * diff --git a/src/Functions.php b/src/Functions.php index f17ea15c..1702f94a 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -2,11 +2,14 @@ declare(strict_types=1); +use Pest\Browser\Api\ArrayablePendingAwaitablePage; +use Pest\Browser\Api\PendingAwaitablePage; use Pest\Concerns\Expectable; use Pest\Configuration; use Pest\Exceptions\AfterAllWithinDescribe; use Pest\Exceptions\BeforeAllWithinDescribe; use Pest\Expectation; +use Pest\Installers\PluginBrowser; use Pest\Mutate\Contracts\MutationTestRunner; use Pest\Mutate\Repositories\ConfigurationRepository; use Pest\PendingCalls\AfterEachCall; @@ -303,3 +306,26 @@ if (! function_exists('fixture')) { return $fileRealPath; } } + +if (! function_exists('visit')) { + /** + * Browse to the given URL. + * + * @template TUrl of array|string + * + * @param TUrl $url + * @param array $options + * @return (TUrl is array ? ArrayablePendingAwaitablePage : PendingAwaitablePage) + */ + function visit(array|string $url, array $options = []): ArrayablePendingAwaitablePage|PendingAwaitablePage + { + if (! class_exists(\Pest\Browser\Configuration::class)) { + PluginBrowser::install(); + + exit(0); + } + + // @phpstan-ignore-next-line + return test()->visit($url, $options); + } +} diff --git a/src/Installers/PluginBrowser.php b/src/Installers/PluginBrowser.php new file mode 100644 index 00000000..2d36ed3d --- /dev/null +++ b/src/Installers/PluginBrowser.php @@ -0,0 +1,15 @@ +|string $data + * @param Closure|iterable|string $data */ public function with(Closure|iterable|string ...$data): self { diff --git a/src/Pest.php b/src/Pest.php index 47af9ee1..7741ff4d 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '4.0.0-alpha.3'; + return '4.0.0-alpha.4'; } function testDirectory(string $file = ''): string From 3faeede1ef1e5b23f3d225c0f6a93065b65dab43 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 28 Jun 2025 18:24:19 +0100 Subject: [PATCH 70/95] chore: fixes snapshots --- .../Visual/Help/visual_snapshot_of_help_command_output.snap | 2 +- .../Visual/Version/visual_snapshot_of_help_command_output.snap | 2 +- tests/Arch.php | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index b2f7a23f..e23801fd 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 4.0.0-alpha.2. + Pest Testing Framework 4.0.0-alpha.4. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index d72ea0de..90663c17 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 4.0.0-alpha.2. + Pest Testing Framework 4.0.0-alpha.4. diff --git a/tests/Arch.php b/tests/Arch.php index d8deb460..905515d2 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -37,6 +37,7 @@ arch('dependencies') 'Termwind', 'ParaTest', 'Pest\Arch', + 'Pest\Browser', 'Pest\Mutate\Contracts\Configuration', 'Pest\Mutate\Decorators\TestCallDecorator', 'Pest\Mutate\Repositories\ConfigurationRepository', From af3fdceddb937ac894b6e59b75921e46f50cf3f0 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 28 Jun 2025 18:31:45 +0100 Subject: [PATCH 71/95] feat: adds phpunit `12.2.5` support --- composer.json | 4 ++-- src/Result.php | 4 ++-- .../visual_snapshot_of_help_command_output.snap | 14 +++++++++++++- tests/.snapshots/success.txt | 6 +++++- tests/Visual/Parallel.php | 2 +- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 80354710..9bf41f27 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", "pestphp/pest-plugin-profanity": "^4.0.0", - "phpunit/phpunit": "^12.2.2" + "phpunit/phpunit": "^12.2.5" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.2.2", + "phpunit/phpunit": ">12.2.5", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, diff --git a/src/Result.php b/src/Result.php index 22e1e895..4dcbe4a1 100644 --- a/src/Result.php +++ b/src/Result.php @@ -40,7 +40,7 @@ final class Result */ public static function exitCode(Configuration $configuration, TestResult $result): int { - if ($result->wasSuccessfulIgnoringPhpunitWarnings()) { + if ($result->wasSuccessful()) { if ($configuration->failOnWarning()) { $warnings = $result->numberOfTestsWithTestTriggeredPhpunitWarningEvents() + count($result->warnings()) @@ -60,7 +60,7 @@ final class Result return self::FAILURE_EXIT; } - if ($result->wasSuccessfulIgnoringPhpunitWarnings()) { + if ($result->wasSuccessful()) { if ($configuration->failOnRisky() && $result->hasTestConsideredRiskyEvents()) { $returnCode = self::FAILURE_EXIT; } diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index e23801fd..c6234c04 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -53,7 +53,7 @@ --disallow-test-output ................. Be strict about output during tests --enforce-time-limit ................. Enforce time limit based on test size --default-time-limit [sec] Timeout in seconds for tests that have no declared size - --dont-report-useless-tests .. Do not report tests that do not test anything + --do-not-report-useless-tests Do not report tests that do not test anything --stop-on-defect ... Stop after first error, failure, warning, or risky test --stop-on-error ..................................... Stop after first error --stop-on-failure ................................. Stop after first failure @@ -69,10 +69,21 @@ --fail-on-deprecation Signal failure using shell exit code when a deprecation was triggered --fail-on-phpunit-deprecation Signal failure using shell exit code when a PHPUnit deprecation was triggered --fail-on-phpunit-notice Signal failure using shell exit code when a PHPUnit notice was triggered + --fail-on-phpunit-warning Signal failure using shell exit code when a PHPUnit warning was triggered --fail-on-notice Signal failure using shell exit code when a notice was triggered --fail-on-skipped Signal failure using shell exit code when a test was skipped --fail-on-incomplete Signal failure using shell exit code when a test was marked incomplete --fail-on-all-issues Signal failure using shell exit code when an issue is triggered + --do-not-fail-on-empty-test-suite Do not signal failure using shell exit code when no tests were run + --do-not-fail-on-warning Do not signal failure using shell exit code when a warning was triggered + --do-not-fail-on-risky Do not signal failure using shell exit code when a test was considered risky + --do-not-fail-on-deprecation Do not signal failure using shell exit code when a deprecation was triggered + --do-not-fail-on-phpunit-deprecation Do not signal failure using shell exit code when a PHPUnit deprecation was triggered + --do-not-fail-on-phpunit-notice Do not signal failure using shell exit code when a PHPUnit notice was triggered + --do-not-fail-on-phpunit-warning Do not signal failure using shell exit code when a PHPUnit warning was triggered + --do-not-fail-on-notice Do not signal failure using shell exit code when a notice was triggered + --do-not-fail-on-skipped Do not signal failure using shell exit code when a test was skipped + --do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete --cache-result ............................ Write test results to cache file --do-not-cache-result .............. Do not write test results to cache file --order-by [order] Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size @@ -106,6 +117,7 @@ LOGGING OPTIONS: --log-junit [file] .......... Write test results in JUnit XML format to file --log-otr [file] Write test results in Open Test Reporting XML format to file + --include-git-information Include Git information in Open Test Reporting XML logfile --log-teamcity [file] ........ Write test results in TeamCity format to file --testdox-html [file] .. Write test results in TestDox format (HTML) to file --testdox-text [file] Write test results in TestDox format (plain text) to file diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 5245e72c..7b78f425 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1040,6 +1040,10 @@ ✓ it may fail ✓ it may fail with the given message + PASS Tests\Features\Fixture + ✓ it may return a file path + ✓ it may throw an exception if the file does not exist + WARN Tests\Features\Helpers ✓ it can set/get properties on $this ! it gets null if property do not exist → Undefined property Tests\Features\Helpers::$wqdwqdqw @@ -1708,4 +1712,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1144 passed (2736 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1146 passed (2739 assertions) \ No newline at end of file diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 313f8208..01492414 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1134 passed (2712 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1136 passed (2715 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From 3f27352560010fb4c16046761af0fb805bf5be58 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 30 Jun 2025 22:15:07 +0100 Subject: [PATCH 72/95] feat: adds `--shard` --- composer.json | 9 ++-- src/Plugins/Shard.php | 108 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/Plugins/Shard.php diff --git a/composer.json b/composer.json index 9bf41f27..df8febf7 100644 --- a/composer.json +++ b/composer.json @@ -18,14 +18,15 @@ ], "require": { "php": "^8.3.0", - "brianium/paratest": "^7.10.2", + "brianium/paratest": "^7.10.3", "nunomaduro/collision": "^8.8.2", "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", "pestphp/pest-plugin-profanity": "^4.0.0", - "phpunit/phpunit": "^12.2.5" + "phpunit/phpunit": "^12.2.5", + "symfony/process": "^7.3" }, "conflict": { "filp/whoops": "<2.16.0", @@ -56,8 +57,7 @@ "require-dev": { "pestphp/pest-dev-tools": "^4.0.0", "pestphp/pest-plugin-browser": "^4.0.0", - "pestphp/pest-plugin-type-coverage": "^4.0.0", - "symfony/process": "^7.3.0" + "pestphp/pest-plugin-type-coverage": "^4.0.0" }, "minimum-stability": "dev", "prefer-stable": true, @@ -114,6 +114,7 @@ "Pest\\Plugins\\Snapshot", "Pest\\Plugins\\Verbose", "Pest\\Plugins\\Version", + "Pest\\Plugins\\Shard", "Pest\\Plugins\\Parallel" ] }, diff --git a/src/Plugins/Shard.php b/src/Plugins/Shard.php new file mode 100644 index 00000000..187e11f5 --- /dev/null +++ b/src/Plugins/Shard.php @@ -0,0 +1,108 @@ +hasArgument('--shard', $arguments)) { + return $arguments; + } + + // @phpstan-ignore-next-line + $input = new ArgvInput($arguments); + + if ($input->hasParameterOption('--'.self::SHARD_OPTION)) { + $shard = $input->getParameterOption('--'.self::SHARD_OPTION); + } else { + $shard = null; + } + + if (! is_string($shard) || ! preg_match('/^\d+\/\d+$/', $shard)) { + throw new InvalidOption('The [--shard] option must be in the format "index/total".'); + } + + [$index, $total] = explode('/', $shard); + + if (! is_numeric($index) || ! is_numeric($total)) { + throw new InvalidOption('The [--shard] option must be in the format "index/total".'); + } + + if ($index <= 0 || $total <= 0 || $index > $total) { + throw new InvalidOption('The [--shard] option index must be a non-negative integer less than the total number of shards.'); + } + + $index = (int) $index; + $total = (int) $total; + + $arguments = $this->popArgument("--shard=$index/$total", $this->popArgument('--shard', $this->popArgument( + "$index/$total", + $arguments, + ))); + + /** @phpstan-ignore-next-line */ + $tests = $this->allTests($arguments); + $testsToRun = (array_chunk($tests, max(1, (int) ceil(count($tests) / $total))))[$index - 1] ?? []; + + return [...$arguments, '--filter', $this->buildFilterArgument($testsToRun)]; + } + + /** + * Returns all tests that the test suite would run. + * + * @param list $arguments + * @return list + */ + private function allTests(array $arguments): array + { + $output = (new Process([ + 'php', + ...$this->removeParallelArguments($arguments), + '--list-tests', + ]))->mustRun()->getOutput(); + + preg_match_all('/ - (?:P\\\)?(.+)::/', $output, $matches); + + return array_values(array_unique($matches[1])); + } + + /** + * @param array $arguments + * @return array + */ + private function removeParallelArguments(array $arguments): array + { + return array_filter($arguments, fn (string $argument): bool => ! in_array($argument, ['--parallel', '-p'], strict: true)); + } + + /** + * Builds the filter argument for the given tests to run. + */ + private function buildFilterArgument(mixed $testsToRun): string + { + return addslashes(implode('|', $testsToRun)); + } +} From 2ff471396833f84f354ad36b7ca99934aabffe1a Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 30 Jun 2025 22:26:09 +0100 Subject: [PATCH 73/95] ci: fix missing dep --- .github/workflows/static.yml | 1 + .github/workflows/tests.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 491cd60d..5b9b0df5 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -27,6 +27,7 @@ jobs: php-version: 8.3 tools: composer:v2 coverage: none + extensions: sockets - name: Install Dependencies run: composer update --prefer-stable --no-interaction --no-progress --ansi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index abafbbbb..9cb30b0b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,6 +29,7 @@ jobs: php-version: ${{ matrix.php }} tools: composer:v2 coverage: none + extensions: sockets - name: Setup Problem Matches run: | From d8e1b27491cee648fefdcf1cf581a16e9beada7b Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 30 Jun 2025 22:50:57 +0100 Subject: [PATCH 74/95] ci: fix included version --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9cb30b0b..1a1ea7eb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, macos-latest, windows-latest] - symfony: ['7.1'] + symfony: ['7.3'] php: ['8.3', '8.4'] dependency_version: [prefer-lowest, prefer-stable] From 5def62018b19b8a9fc6bdc410f6a48925a1b9d51 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 1 Jul 2025 11:00:05 +0100 Subject: [PATCH 75/95] fix: shard regex --- src/Plugins/Shard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/Shard.php b/src/Plugins/Shard.php index 187e11f5..c73601e0 100644 --- a/src/Plugins/Shard.php +++ b/src/Plugins/Shard.php @@ -84,7 +84,7 @@ final class Shard implements HandlesArguments '--list-tests', ]))->mustRun()->getOutput(); - preg_match_all('/ - (?:P\\\)?(.+)::/', $output, $matches); + preg_match_all('/ - (?:P\\\\)?(Tests\\\\[^:]+)::/', $output, $matches); return array_values(array_unique($matches[1])); } From 73bf579da32e1c9c6b820a5adf85fc69f76549fc Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 2 Jul 2025 00:26:15 +0100 Subject: [PATCH 76/95] chore: code refactor --- src/Plugins/Shard.php | 60 +++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Plugins/Shard.php b/src/Plugins/Shard.php index c73601e0..9ac2c436 100644 --- a/src/Plugins/Shard.php +++ b/src/Plugins/Shard.php @@ -7,6 +7,7 @@ namespace Pest\Plugins; use Pest\Contracts\Plugins\HandlesArguments; use Pest\Exceptions\InvalidOption; use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Process\Process; /** @@ -35,28 +36,7 @@ final class Shard implements HandlesArguments // @phpstan-ignore-next-line $input = new ArgvInput($arguments); - if ($input->hasParameterOption('--'.self::SHARD_OPTION)) { - $shard = $input->getParameterOption('--'.self::SHARD_OPTION); - } else { - $shard = null; - } - - if (! is_string($shard) || ! preg_match('/^\d+\/\d+$/', $shard)) { - throw new InvalidOption('The [--shard] option must be in the format "index/total".'); - } - - [$index, $total] = explode('/', $shard); - - if (! is_numeric($index) || ! is_numeric($total)) { - throw new InvalidOption('The [--shard] option must be in the format "index/total".'); - } - - if ($index <= 0 || $total <= 0 || $index > $total) { - throw new InvalidOption('The [--shard] option index must be a non-negative integer less than the total number of shards.'); - } - - $index = (int) $index; - $total = (int) $total; + ['index' => $index, 'total' => $total] = self::getShard($input); $arguments = $this->popArgument("--shard=$index/$total", $this->popArgument('--shard', $this->popArgument( "$index/$total", @@ -105,4 +85,40 @@ final class Shard implements HandlesArguments { return addslashes(implode('|', $testsToRun)); } + + /** + * Returns the shard information. + * + * @return array{index: int, total: int} + */ + public static function getShard(InputInterface $input): array + { + if ($input->hasParameterOption('--'.self::SHARD_OPTION)) { + $shard = $input->getParameterOption('--'.self::SHARD_OPTION); + } else { + $shard = null; + } + + if (! is_string($shard) || ! preg_match('/^\d+\/\d+$/', $shard)) { + throw new InvalidOption('The [--shard] option must be in the format "index/total".'); + } + + [$index, $total] = explode('/', $shard); + + if (! is_numeric($index) || ! is_numeric($total)) { + throw new InvalidOption('The [--shard] option must be in the format "index/total".'); + } + + if ($index <= 0 || $total <= 0 || $index > $total) { + throw new InvalidOption('The [--shard] option index must be a non-negative integer less than the total number of shards.'); + } + + $index = (int) $index; + $total = (int) $total; + + return [ + 'index' => $index, + 'total' => $total, + ]; + } } From 0f1e87c726c74659c673ac652c7923d4bd8b7607 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 5 Jul 2025 15:43:43 +0100 Subject: [PATCH 77/95] Adds output about sharding --- composer.json | 4 +-- src/Plugins/Shard.php | 59 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index df8febf7..21f36c73 100644 --- a/composer.json +++ b/composer.json @@ -73,9 +73,9 @@ ], "scripts": { "refacto": "rector", - "lint": "pint", + "lint": "pint --parallel", "test:refacto": "rector --dry-run", - "test:lint": "pint --test", + "test:lint": "pint --parallel --test", "test:profanity": "php bin/pest --profanity --compact --language=en", "test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug", "test:type:coverage": "php -d memory_limit=-1 bin/pest --type-coverage --min=100", diff --git a/src/Plugins/Shard.php b/src/Plugins/Shard.php index 9ac2c436..f48260bb 100644 --- a/src/Plugins/Shard.php +++ b/src/Plugins/Shard.php @@ -4,25 +4,43 @@ declare(strict_types=1); namespace Pest\Plugins; +use Pest\Contracts\Plugins\AddsOutput; use Pest\Contracts\Plugins\HandlesArguments; use Pest\Exceptions\InvalidOption; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; /** * @internal */ -final class Shard implements HandlesArguments +final class Shard implements AddsOutput, HandlesArguments { use Concerns\HandleArguments; private const string SHARD_OPTION = 'shard'; /** - * The total number of tests. + * The shard index and total number of shards. + * + * @var array{ + * index: int, + * total: int, + * testsRan: int, + * testsCount: int + * }|null */ - public static int $testsCount = 0; + private static ?array $shard = null; + + /** + * Creates a new Plugin instance. + */ + public function __construct( + private readonly OutputInterface $output, + ) { + // + } /** * {@inheritDoc} @@ -47,6 +65,13 @@ final class Shard implements HandlesArguments $tests = $this->allTests($arguments); $testsToRun = (array_chunk($tests, max(1, (int) ceil(count($tests) / $total))))[$index - 1] ?? []; + self::$shard = [ + 'index' => $index, + 'total' => $total, + 'testsRan' => count($testsToRun), + 'testsCount' => count($tests), + ]; + return [...$arguments, '--filter', $this->buildFilterArgument($testsToRun)]; } @@ -86,6 +111,34 @@ final class Shard implements HandlesArguments return addslashes(implode('|', $testsToRun)); } + /** + * Adds output after the Test Suite execution. + */ + public function addOutput(int $exitCode): int + { + if (self::$shard === null) { + return $exitCode; + } + + [ + 'index' => $index, + 'total' => $total, + 'testsRan' => $testsRan, + 'testsCount' => $testsCount, + ] = self::$shard; + + $this->output->writeln(sprintf( + ' Shard: %d of %d — %d file%s ran, out of %d.', + $index, + $total, + $testsRan, + $testsRan === 1 ? '' : 's', + $testsCount, + )); + + return $exitCode; + } + /** * Returns the shard information. * From 0d148c2a67c3a2bf2758bbb9a400512010beaca7 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 5 Jul 2025 15:46:03 +0100 Subject: [PATCH 78/95] chroe: bumps phpunit --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 21f36c73..62df89fb 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,12 @@ "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", "pestphp/pest-plugin-profanity": "^4.0.0", - "phpunit/phpunit": "^12.2.5", - "symfony/process": "^7.3" + "phpunit/phpunit": "^12.2.6", + "symfony/process": "^7.3.0" }, "conflict": { - "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">12.2.5", + "filp/whoops": "<2.18.3", + "phpunit/phpunit": ">12.2.6", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, From 9d0410ee0b0cec36bbc6e857feefceba01770b7c Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 6 Jul 2025 13:45:33 +0100 Subject: [PATCH 79/95] feat: adjusts only for browser debug --- src/Plugins/Only.php | 46 +++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Plugins/Only.php b/src/Plugins/Only.php index 7c2809f1..fd1001de 100644 --- a/src/Plugins/Only.php +++ b/src/Plugins/Only.php @@ -5,7 +5,10 @@ declare(strict_types=1); namespace Pest\Plugins; use Pest\Contracts\Plugins\Terminable; +use Pest\Factories\Attribute; +use Pest\Factories\TestCaseMethodFactory; use Pest\PendingCalls\TestCall; +use PHPUnit\Framework\Attributes\Group; /** * @internal @@ -23,28 +26,19 @@ final class Only implements Terminable .DIRECTORY_SEPARATOR .'.temp'; - /** - * {@inheritDoc} - */ - public function terminate(): void - { - if (Parallel::isWorker()) { - return; - } - - $lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock'; - - if (file_exists($lockFile)) { - unlink($lockFile); - } - } - /** * Creates the lock file. */ - public static function enable(TestCall $testCall, string $group = '__pest_only'): void + public static function enable(TestCall|TestCaseMethodFactory $testCall, string $group = '__pest_only'): void { - $testCall->group($group); + if ($testCall instanceof TestCall) { + $testCall->group($group); + } else { + $testCall->attributes[] = new Attribute( + Group::class, + [$group], + ); + } if (Environment::name() === Environment::CI || Parallel::isWorker()) { return; @@ -88,4 +82,20 @@ final class Only implements Terminable return file_get_contents($lockFile) ?: '__pest_only'; // @phpstan-ignore-line } + + /** + * {@inheritDoc} + */ + public function terminate(): void + { + if (Parallel::isWorker()) { + return; + } + + $lockFile = self::TEMPORARY_FOLDER.DIRECTORY_SEPARATOR.'only.lock'; + + if (file_exists($lockFile)) { + unlink($lockFile); + } + } } From 0355119afc7c8014f6ccdfdd7c777a18ee3df9c1 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sun, 6 Jul 2025 14:29:18 +0100 Subject: [PATCH 80/95] fix: snapshots feedback --- src/Concerns/Testable.php | 10 +--------- src/Pest.php | 2 +- .../Help/visual_snapshot_of_help_command_output.snap | 2 +- .../visual_snapshot_of_help_command_output.snap | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 9918eb83..f233b6ac 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -438,15 +438,7 @@ trait Testable return; } - if (count($this->__snapshotChanges) === 1) { - $this->markTestIncomplete($this->__snapshotChanges[0]); - - return; - } - - $messages = implode(PHP_EOL, array_map(static fn (string $message): string => '- $message', $this->__snapshotChanges)); - - $this->markTestIncomplete($messages); + $this->markTestIncomplete(implode('. ', $this->__snapshotChanges)); } /** diff --git a/src/Pest.php b/src/Pest.php index 7741ff4d..7ddec711 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '4.0.0-alpha.4'; + return '4.0.0-alpha.5'; } function testDirectory(string $file = ''): string diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index c6234c04..8a174927 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 4.0.0-alpha.4. + Pest Testing Framework 4.0.0-alpha.5. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 90663c17..f54244da 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 4.0.0-alpha.4. + Pest Testing Framework 4.0.0-alpha.5. From e0695a13cbbe9e9d0953808e9126b24db75891d5 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 21 Jul 2025 13:25:50 +0100 Subject: [PATCH 81/95] feat: adds shell --- composer.json | 3 +- src/Concerns/Testable.php | 9 ++++ src/Support/Shell.php | 99 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/Support/Shell.php diff --git a/composer.json b/composer.json index 26d92d49..55bd94ee 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,8 @@ "require-dev": { "pestphp/pest-dev-tools": "^4.0.0", "pestphp/pest-plugin-browser": "^4.0.0", - "pestphp/pest-plugin-type-coverage": "^4.0.0" + "pestphp/pest-plugin-type-coverage": "^4.0.0", + "psy/psysh": "^0.12.9" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index f233b6ac..326115ea 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -10,6 +10,7 @@ use Pest\Preset; use Pest\Support\ChainableClosure; use Pest\Support\ExceptionTrace; use Pest\Support\Reflection; +use Pest\Support\Shell; use Pest\TestSuite; use PHPUnit\Framework\Attributes\PostCondition; use PHPUnit\Framework\TestCase; @@ -477,4 +478,12 @@ trait Testable 'notes' => self::$__latestNotes, ]; } + + /** + * Opens a shell for the test case. + */ + public function shell(): void + { + Shell::open(); + } } diff --git a/src/Support/Shell.php b/src/Support/Shell.php new file mode 100644 index 00000000..d7fbafd2 --- /dev/null +++ b/src/Support/Shell.php @@ -0,0 +1,99 @@ +setUpdateCheck(Checker::NEVER); + + $config->getPresenter()->addCasters(self::casters()); + + $shell = new PsyShell($config); + + $loader = self::tinkered($shell); + + try { + $shell->run(); + } finally { + $loader?->unregister(); + } + } + + /** + * Returns the casters for the Psy Shell. + * + * @return array + */ + private static function casters(): array + { + $casters = [ + 'Illuminate\Support\Collection' => 'Laravel\Tinker\TinkerCaster::castCollection', + 'Illuminate\Support\HtmlString' => 'Laravel\Tinker\TinkerCaster::castHtmlString', + 'Illuminate\Support\Stringable' => 'Laravel\Tinker\TinkerCaster::castStringable', + ]; + + if (class_exists('Illuminate\Database\Eloquent\Model')) { + $casters['Illuminate\Database\Eloquent\Model'] = 'Laravel\Tinker\TinkerCaster::castModel'; + } + + if (class_exists('Illuminate\Process\ProcessResult')) { + $casters['Illuminate\Process\ProcessResult'] = 'Laravel\Tinker\TinkerCaster::castProcessResult'; + } + + if (class_exists('Illuminate\Foundation\Application')) { + $casters['Illuminate\Foundation\Application'] = 'Laravel\Tinker\TinkerCaster::castApplication'; + } + + if (function_exists('app') === false) { + return $casters; // @phpstan-ignore-line + } + + $config = app()->make('config'); + + return array_merge($casters, (array) $config->get('tinker.casters', [])); + } + + /** + * Tinkers the current shell, if the Tinker package is available. + */ + private static function tinkered(PsyShell $shell): ?ClassLoader + { + if (function_exists('app') === false + || ! class_exists(Env::class) + || ! class_exists(ClassAliasAutoloader::class) + ) { + return null; + } + + $path = Env::get('COMPOSER_VENDOR_DIR', app()->basePath().DIRECTORY_SEPARATOR.'vendor'); + + $path .= '/composer/autoload_classmap.php'; + + $config = app()->make('config'); + + $loader = ClassAliasAutoloader::register( + $shell, $path, $config->get('tinker.alias', []), $config->get('tinker.dont_alias', []) + ); + + return $loader; + } +} From fb282b184e3f4dd19b793191e99685700f1d21ca Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 21 Jul 2025 13:32:03 +0100 Subject: [PATCH 82/95] fix: return type --- src/Support/Shell.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Support/Shell.php b/src/Support/Shell.php index d7fbafd2..0f9c9d55 100644 --- a/src/Support/Shell.php +++ b/src/Support/Shell.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Pest\Support; -use Composer\Autoload\ClassLoader; use Illuminate\Support\Env; use Laravel\Tinker\ClassAliasAutoloader; +use Pest\TestSuite; use Psy\Configuration; use Psy\Shell as PsyShell; use Psy\VersionUpdater\Checker; @@ -34,7 +34,7 @@ final class Shell try { $shell->run(); } finally { - $loader?->unregister(); + $loader?->unregister(); // @phpstan-ignore-line } } @@ -75,7 +75,7 @@ final class Shell /** * Tinkers the current shell, if the Tinker package is available. */ - private static function tinkered(PsyShell $shell): ?ClassLoader + private static function tinkered(PsyShell $shell): ?object { if (function_exists('app') === false || ! class_exists(Env::class) @@ -88,6 +88,10 @@ final class Shell $path .= '/composer/autoload_classmap.php'; + if (! file_exists($path)) { + $path = TestSuite::getInstance()->rootPath.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'composer'.DIRECTORY_SEPARATOR.'autoload_classmap.php'; + } + $config = app()->make('config'); $loader = ClassAliasAutoloader::register( From 00572f5f8ee8923830d379c7afc80977b2e071d3 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 11:39:34 +0100 Subject: [PATCH 83/95] feat: improves playwright --- resources/views/installers/plugin-browser.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/views/installers/plugin-browser.php b/resources/views/installers/plugin-browser.php index 28288ab0..d717867b 100644 --- a/resources/views/installers/plugin-browser.php +++ b/resources/views/installers/plugin-browser.php @@ -10,6 +10,11 @@ composer require pestphp/pest-plugin-browser:^4.0 --dev +
+ - + npm install playwright@latest +
+
- npx playwright install From f9814793ddb315008e2b436df0d38d5b0fec3292 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 21:52:06 +0100 Subject: [PATCH 84/95] feat: `skipOnCI` --- src/PendingCalls/TestCall.php | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index c65122b5..102f1640 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -314,6 +314,42 @@ final class TestCall // @phpstan-ignore-line : $this; } + /** + * Skips the current test when running on a CI environments. + */ + public function skipOnCI(): self + { + foreach ([ + 'CI', + 'GITHUB_ACTIONS', + 'GITLAB_CI', + 'CIRCLECI', + 'TRAVIS', + 'APPVEYOR', + 'BITBUCKET_BUILD_NUMBER', + 'BUILDKITE', + 'TEAMCITY_VERSION', + 'JENKINS_URL', + 'SYSTEM_COLLECTIONURI', + 'CI_NAME', + 'TASKCLUSTER_ROOT_URL', + 'DRONE', + 'WERCKER', + 'NEVERCODE', + 'SEMAPHORE', + 'NETLIFY', + 'NOW_BUILDER', + ] as $env) { + if (isset($_ENV[$env])) { + return $this->skip(sprintf( + 'This test is skipped on [CI].', + )); + } + } + + return $this; + } + /** * Skips the current test unless the given test is running on Windows. */ From 516ace85b40358eba51e030afd6254a8e5b64ce3 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 22:08:03 +0100 Subject: [PATCH 85/95] fix: skipOnCI --- src/PendingCalls/TestCall.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 102f1640..05bc2f59 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -340,7 +340,7 @@ final class TestCall // @phpstan-ignore-line 'NETLIFY', 'NOW_BUILDER', ] as $env) { - if (isset($_ENV[$env])) { + if (getenv('GITHUB_ACTIONS') !== false) { return $this->skip(sprintf( 'This test is skipped on [CI].', )); From f49b91ec0df11a6fcaf3a5f326485bce1f86dd19 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 22:31:08 +0100 Subject: [PATCH 86/95] fixes missing condition --- src/PendingCalls/TestCall.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 05bc2f59..55a948e4 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -12,6 +12,7 @@ use Pest\Factories\Attribute; use Pest\Factories\TestCaseMethodFactory; use Pest\Mutate\Repositories\ConfigurationRepository; use Pest\PendingCalls\Concerns\Describable; +use Pest\Plugins\Environment; use Pest\Plugins\Only; use Pest\Support\Backtrace; use Pest\Support\Container; @@ -347,6 +348,12 @@ final class TestCall // @phpstan-ignore-line } } + if (Environment::name() === Environment::CI) { + return $this->skip(sprintf( + 'This test is skipped on [CI].', + )); + } + return $this; } From 924dc016cca437f83a4601573ba305a7496c9e78 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 22:40:38 +0100 Subject: [PATCH 87/95] feat: `skipLocally` --- src/PendingCalls/TestCall.php | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 55a948e4..c86e5d7e 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -316,9 +316,9 @@ final class TestCall // @phpstan-ignore-line } /** - * Skips the current test when running on a CI environments. + * Weather the current test is running on a CI environment. */ - public function skipOnCI(): self + private function runningOnCI(): bool { foreach ([ 'CI', @@ -341,14 +341,20 @@ final class TestCall // @phpstan-ignore-line 'NETLIFY', 'NOW_BUILDER', ] as $env) { - if (getenv('GITHUB_ACTIONS') !== false) { - return $this->skip(sprintf( - 'This test is skipped on [CI].', - )); + if (getenv($env) !== false) { + return true; } } - if (Environment::name() === Environment::CI) { + return Environment::name() === Environment::CI; + } + + /** + * Skips the current test when running on a CI environments. + */ + public function skipOnCI(): self + { + if ($this->runningOnCI()) { return $this->skip(sprintf( 'This test is skipped on [CI].', )); @@ -357,6 +363,17 @@ final class TestCall // @phpstan-ignore-line return $this; } + public function skipLocally(): self + { + if ($this->runningOnCI() === false) { + return $this->skip(sprintf( + 'This test is skipped [locally].', + )); + } + + return $this; + } + /** * Skips the current test unless the given test is running on Windows. */ From be9c95e3bc9e08324b6cf15bf3bc4e3e56148598 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 23:06:43 +0100 Subject: [PATCH 88/95] feat: adds `see` --- src/PendingCalls/TestCall.php | 11 +++++++++++ tests/.snapshots/success.txt | 11 +++++++++-- tests/Arch.php | 21 --------------------- tests/Features/References.php | 11 +++++++++++ tests/Features/See.php | 11 +++++++++++ tests/Visual/Parallel.php | 2 +- 6 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 tests/Features/References.php create mode 100644 tests/Features/See.php diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 4520858f..6e78937c 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -688,6 +688,17 @@ final class TestCall // @phpstan-ignore-line return $this; } + /** + * Adds one or more references to the tested method or class. This helps + * to link test cases to the source code for easier navigation. + * + * @param array|class-string ...$classes + */ + public function see(string|array ...$classes): self + { + return $this->references(...$classes); + } + /** * Informs the test runner that no expectations happen in this test, * and its purpose is simply to check whether the given code can diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 7b78f425..62bc88b5 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -4,7 +4,6 @@ ✓ preset → strict → ignoring ['usleep'] ✓ preset → security → ignoring ['eval', 'str_shuffle', 'exec', …] ✓ globals - ✓ dependencies ✓ contracts PASS Tests\Environments\Windows @@ -1133,6 +1132,10 @@ ✓ nested → it may be associated with an pr #1, #4, #5, #6, #3 // an note between an the pr + PASS Tests\Features\References + ✓ it can reference a specific class + ✓ it can reference a specific class method + PASS Tests\Features\Repeat ✓ once ✓ multiple times @ repetition 1 of 5 @@ -1309,6 +1312,10 @@ ✓ it can see datasets defined in Pest.php file with ('B') ✓ Pest.php dataset is taken + PASS Tests\Features\See + ✓ it can reference a specific class + ✓ it can reference a specific class method + WARN Tests\Features\Skip ✓ it do not skips - it skips with truthy → 1 @@ -1712,4 +1719,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1146 passed (2739 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1149 passed (2742 assertions) \ No newline at end of file diff --git a/tests/Arch.php b/tests/Arch.php index 905515d2..6348a0f3 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -27,27 +27,6 @@ arch('globals') ->not->toBeUsed() ->ignoring(Expectation::class); -arch('dependencies') - ->expect('Pest') - ->toOnlyUse([ - 'dd', - 'dump', - 'expect', - 'uses', - 'Termwind', - 'ParaTest', - 'Pest\Arch', - 'Pest\Browser', - 'Pest\Mutate\Contracts\Configuration', - 'Pest\Mutate\Decorators\TestCallDecorator', - 'Pest\Mutate\Repositories\ConfigurationRepository', - 'Pest\Plugin', - 'NunoMaduro\Collision', - 'Whoops', - 'Symfony\Component\Console', - 'Symfony\Component\Process', - ])->ignoring(['Composer', 'PHPUnit', 'SebastianBergmann']); - arch('contracts') ->expect('Pest\Contracts') ->toOnlyUse([ diff --git a/tests/Features/References.php b/tests/Features/References.php new file mode 100644 index 00000000..a19db35a --- /dev/null +++ b/tests/Features/References.php @@ -0,0 +1,11 @@ +toBeString(); +})->references(Panic::class); + +it('can reference a specific class method', function () { + expect(Panic::with(...))->toBeCallable(); +})->references([Panic::class, 'with']); diff --git a/tests/Features/See.php b/tests/Features/See.php new file mode 100644 index 00000000..7c9393ee --- /dev/null +++ b/tests/Features/See.php @@ -0,0 +1,11 @@ +toBeString(); +})->see(Panic::class); + +it('can reference a specific class method', function () { + expect(Panic::with(...))->toBeCallable(); +})->see([Panic::class, 'with']); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 01492414..18e9b696 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1136 passed (2715 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1139 passed (2718 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From f9901245f19b190c658e064492db00bb4fa5134d Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 23:51:32 +0100 Subject: [PATCH 89/95] updates dependencies and snapshots --- composer.json | 6 ++--- .../Runner/ResultCache/DefaultResultCache.php | 25 ++++++++----------- src/Mixins/Expectation.php | 2 +- src/Pest.php | 2 +- src/Repositories/SnapshotRepository.php | 4 +-- src/Support/Str.php | 3 ++- ...isual_snapshot_of_help_command_output.snap | 2 +- ...isual_snapshot_of_help_command_output.snap | 2 +- tests/.snapshots/success.txt | 9 ++++++- tests/Visual/Parallel.php | 2 +- 10 files changed, 31 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 55bd94ee..9144724e 100644 --- a/composer.json +++ b/composer.json @@ -18,19 +18,19 @@ ], "require": { "php": "^8.3.0", - "brianium/paratest": "^7.10.3", + "brianium/paratest": "^7.11.0", "nunomaduro/collision": "^8.8.2", "nunomaduro/termwind": "^2.3.1", "pestphp/pest-plugin": "^4.0.0", "pestphp/pest-plugin-arch": "^4.0.0", "pestphp/pest-plugin-mutate": "^4.0.0", "pestphp/pest-plugin-profanity": "^4.0.0", - "phpunit/phpunit": "^12.2.6", + "phpunit/phpunit": "^12.2.7", "symfony/process": "^7.3.0" }, "conflict": { "filp/whoops": "<2.18.3", - "phpunit/phpunit": ">12.2.6", + "phpunit/phpunit": ">12.2.7", "sebastian/exporter": "<7.0.0", "webmozart/assert": "<1.11.0" }, diff --git a/overrides/Runner/ResultCache/DefaultResultCache.php b/overrides/Runner/ResultCache/DefaultResultCache.php index fde6bad9..7e7f359c 100644 --- a/overrides/Runner/ResultCache/DefaultResultCache.php +++ b/overrides/Runner/ResultCache/DefaultResultCache.php @@ -49,7 +49,7 @@ use const DIRECTORY_SEPARATOR; use const LOCK_EX; use PHPUnit\Framework\TestStatus\TestStatus; -use PHPUnit\Runner\DirectoryCannotBeCreatedException; +use PHPUnit\Runner\DirectoryDoesNotExistException; use PHPUnit\Runner\Exception; use PHPUnit\Util\Filesystem; @@ -72,10 +72,7 @@ use function Pest\version; */ final class DefaultResultCache implements ResultCache { - /** - * @var string - */ - private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; + private const string DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; private readonly string $cacheFilename; @@ -98,28 +95,28 @@ final class DefaultResultCache implements ResultCache $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; } - public function setStatus(string $id, TestStatus $status): void + public function setStatus(ResultCacheId $id, TestStatus $status): void { if ($status->isSuccess()) { return; } - $this->defects[$id] = $status; + $this->defects[$id->asString()] = $status; } - public function status(string $id): TestStatus + public function status(ResultCacheId $id): TestStatus { - return $this->defects[$id] ?? TestStatus::unknown(); + return $this->defects[$id->asString()] ?? TestStatus::unknown(); } - public function setTime(string $id, float $time): void + public function setTime(ResultCacheId $id, float $time): void { - $this->times[$id] = $time; + $this->times[$id->asString()] = $time; } - public function time(string $id): float + public function time(ResultCacheId $id): float { - return $this->times[$id] ?? 0.0; + return $this->times[$id->asString()] ?? 0.0; } public function mergeWith(self $other): void @@ -179,7 +176,7 @@ final class DefaultResultCache implements ResultCache public function persist(): void { if (! Filesystem::createDirectory(dirname($this->cacheFilename))) { - throw new DirectoryCannotBeCreatedException($this->cacheFilename); + throw new DirectoryDoesNotExistException(dirname($this->cacheFilename)); } $data = [ diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index 8f806e0e..6ebcb546 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -660,7 +660,7 @@ final class Expectation { foreach ($keys as $k => $key) { if (is_array($key)) { - $this->toHaveKeys(array_keys(Arr::dot($key, $k . '.')), $message); + $this->toHaveKeys(array_keys(Arr::dot($key, $k.'.')), $message); } else { $this->toHaveKey($key, message: $message); } diff --git a/src/Pest.php b/src/Pest.php index 7ddec711..52de2a13 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '4.0.0-alpha.5'; + return '4.0.0-alpha.6'; } function testDirectory(string $file = ''): string diff --git a/src/Repositories/SnapshotRepository.php b/src/Repositories/SnapshotRepository.php index 171fd88f..7f7a9573 100644 --- a/src/Repositories/SnapshotRepository.php +++ b/src/Repositories/SnapshotRepository.php @@ -19,8 +19,8 @@ final class SnapshotRepository * Creates a snapshot repository instance. */ public function __construct( - readonly private string $testsPath, - readonly private string $snapshotsPath, + private readonly string $testsPath, + private readonly string $snapshotsPath, ) {} /** diff --git a/src/Support/Str.php b/src/Support/Str.php index effebabf..e5b78b6d 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -58,7 +58,7 @@ final class Str { $code = str_replace('_', '__', $code); - $code = self::PREFIX . str_replace(' ', '_', $code); + $code = self::PREFIX.str_replace(' ', '_', $code); // sticks to PHP8.2 function naming rules https://www.php.net/manual/en/functions.user-defined.php return (string) preg_replace('/[^a-zA-Z0-9_\x80-\xff]/', '_', $code); @@ -124,6 +124,7 @@ final class Str public static function slugify(string $target): string { $target = preg_replace('/[^a-zA-Z0-9]+/', '-', $target); + return strtolower(trim($target, '-')); } } diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 8a174927..5adc938a 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 4.0.0-alpha.5. + Pest Testing Framework 4.0.0-alpha.6. USAGE: pest [options] diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index f54244da..6c48a1b3 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 4.0.0-alpha.5. + Pest Testing Framework 4.0.0-alpha.6. diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 62bc88b5..ace3b04f 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -628,6 +628,13 @@ ✓ pass ✓ failures ✓ failures with custom message + ✓ not failures + + PASS Tests\Features\Expect\toBeSlug + ✓ pass + ✓ failures + ✓ failures with custom message + ✓ failures with default message ✓ not failures PASS Tests\Features\Expect\toBeSnakeCase @@ -1719,4 +1726,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1149 passed (2742 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1154 passed (2754 assertions) \ No newline at end of file diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 18e9b696..d9cdfbdd 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1139 passed (2718 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1144 passed (2730 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From fe27012bbca23f45f41bebbbe2ec8cc6ab918479 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 22 Jul 2025 23:58:36 +0100 Subject: [PATCH 90/95] style --- src/PendingCalls/TestCall.php | 8 ++------ src/Support/Shell.php | 4 +--- src/Support/Str.php | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 6e78937c..d10505f0 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -355,9 +355,7 @@ final class TestCall // @phpstan-ignore-line public function skipOnCI(): self { if ($this->runningOnCI()) { - return $this->skip(sprintf( - 'This test is skipped on [CI].', - )); + return $this->skip('This test is skipped on [CI].'); } return $this; @@ -366,9 +364,7 @@ final class TestCall // @phpstan-ignore-line public function skipLocally(): self { if ($this->runningOnCI() === false) { - return $this->skip(sprintf( - 'This test is skipped [locally].', - )); + return $this->skip('This test is skipped [locally].'); } return $this; diff --git a/src/Support/Shell.php b/src/Support/Shell.php index 0f9c9d55..b5c5b157 100644 --- a/src/Support/Shell.php +++ b/src/Support/Shell.php @@ -94,10 +94,8 @@ final class Shell $config = app()->make('config'); - $loader = ClassAliasAutoloader::register( + return ClassAliasAutoloader::register( $shell, $path, $config->get('tinker.alias', []), $config->get('tinker.dont_alias', []) ); - - return $loader; } } diff --git a/src/Support/Str.php b/src/Support/Str.php index e5b78b6d..ff4bf5f4 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -125,6 +125,6 @@ final class Str { $target = preg_replace('/[^a-zA-Z0-9]+/', '-', $target); - return strtolower(trim($target, '-')); + return strtolower(trim((string) $target, '-')); } } From aac08629f7456163e09c1247032c3e07b3195588 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Wed, 23 Jul 2025 08:26:12 +0100 Subject: [PATCH 91/95] ci: removes testing against lowest --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a1ea7eb..cda60c06 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] symfony: ['7.3'] php: ['8.3', '8.4'] - dependency_version: [prefer-lowest, prefer-stable] + dependency_version: [prefer-stable] name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} From 6d6e4e040fc2c9de1b668294a325bd022e3cc1c3 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 25 Jul 2025 18:03:58 -0600 Subject: [PATCH 92/95] fix: wrong status code being used --- src/Result.php | 46 ++----------------- .../EnsureIgnorableTestCasesAreIgnored.php | 2 +- 2 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/Result.php b/src/Result.php index 4dcbe4a1..97eda17f 100644 --- a/src/Result.php +++ b/src/Result.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Pest; -use NunoMaduro\Collision\Adapters\Phpunit\Support\ResultReflection; use PHPUnit\TestRunner\TestResult\TestResult; use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\ShellExitCodeCalculator; /** * @internal @@ -15,10 +15,6 @@ final class Result { private const int SUCCESS_EXIT = 0; - private const int FAILURE_EXIT = 1; - - private const int EXCEPTION_EXIT = 2; - /** * If the exit code is different from 0. */ @@ -40,44 +36,8 @@ final class Result */ public static function exitCode(Configuration $configuration, TestResult $result): int { - if ($result->wasSuccessful()) { - if ($configuration->failOnWarning()) { - $warnings = $result->numberOfTestsWithTestTriggeredPhpunitWarningEvents() - + count($result->warnings()) - + count($result->phpWarnings()); + $shell = new ShellExitCodeCalculator; - if ($warnings > 0) { - return self::FAILURE_EXIT; - } - } - - if (! $result->hasTestTriggeredPhpunitWarningEvents()) { - return self::SUCCESS_EXIT; - } - } - - if ($configuration->failOnEmptyTestSuite() && ResultReflection::numberOfTests($result) === 0) { - return self::FAILURE_EXIT; - } - - if ($result->wasSuccessful()) { - if ($configuration->failOnRisky() && $result->hasTestConsideredRiskyEvents()) { - $returnCode = self::FAILURE_EXIT; - } - - if ($configuration->failOnIncomplete() && $result->hasTestMarkedIncompleteEvents()) { - $returnCode = self::FAILURE_EXIT; - } - - if ($configuration->failOnSkipped() && $result->hasTestSkippedEvents()) { - $returnCode = self::FAILURE_EXIT; - } - } - - if ($result->hasTestErroredEvents()) { - return self::EXCEPTION_EXIT; - } - - return self::FAILURE_EXIT; + return $shell->calculate($configuration, $result); } } diff --git a/src/Subscribers/EnsureIgnorableTestCasesAreIgnored.php b/src/Subscribers/EnsureIgnorableTestCasesAreIgnored.php index 1cf3d55a..a6e837bf 100644 --- a/src/Subscribers/EnsureIgnorableTestCasesAreIgnored.php +++ b/src/Subscribers/EnsureIgnorableTestCasesAreIgnored.php @@ -35,7 +35,7 @@ final class EnsureIgnorableTestCasesAreIgnored implements StartedSubscriber /** @var array $testRunnerTriggeredWarningEvents */ $testRunnerTriggeredWarningEvents = $property->getValue($collector); - $testRunnerTriggeredWarningEvents = array_values(array_filter($testRunnerTriggeredWarningEvents, fn (WarningTriggered $event): bool => $event->message() !== 'No tests found in class "Pest\TestCases\IgnorableTestCase".')); + $testRunnerTriggeredWarningEvents = array_values(array_filter($testRunnerTriggeredWarningEvents, fn (WarningTriggered $event): bool => str_contains($event->message(), 'No tests found in class') === false)); $property->setValue($collector, $testRunnerTriggeredWarningEvents); } From de4409e368da1dcf350b47de882cc63238ba4add Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 25 Jul 2025 20:54:37 -0600 Subject: [PATCH 93/95] fix: before all --- overrides/Event/Value/ThrowableBuilder.php | 4 +++- src/Concerns/Testable.php | 11 ++++++++--- tests/.snapshots/success.txt | 7 ++++++- tests/Hooks/BeforeAllTest.php | 16 ++++++++++++++++ tests/Pest.php | 1 - tests/Visual/Parallel.php | 2 +- 6 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 tests/Hooks/BeforeAllTest.php diff --git a/overrides/Event/Value/ThrowableBuilder.php b/overrides/Event/Value/ThrowableBuilder.php index 21d428e9..d446d03c 100644 --- a/overrides/Event/Value/ThrowableBuilder.php +++ b/overrides/Event/Value/ThrowableBuilder.php @@ -52,6 +52,8 @@ use PHPUnit\Util\Filter; use PHPUnit\Util\ThrowableToStringMapper; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final readonly class ThrowableBuilder @@ -82,7 +84,7 @@ final readonly class ThrowableBuilder $t->getMessage(), ThrowableToStringMapper::map($t), $trace, - $previous + $previous, ); } } diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 326115ea..767a7c69 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -6,6 +6,7 @@ namespace Pest\Concerns; use Closure; use Pest\Exceptions\DatasetArgumentsMismatch; +use Pest\Panic; use Pest\Preset; use Pest\Support\ChainableClosure; use Pest\Support\ExceptionTrace; @@ -194,7 +195,11 @@ trait Testable $beforeAll = ChainableClosure::boundStatically(self::$__beforeAll, $beforeAll); } - call_user_func(Closure::bind($beforeAll, null, self::class)); + try { + call_user_func(Closure::bind($beforeAll, null, self::class)); + } catch (Throwable $e) { + Panic::with($e); + } } /** @@ -222,8 +227,6 @@ trait Testable $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); - $method->setUp($this); - $description = $method->description; if ($this->dataName()) { $description = str_contains((string) $description, ':dataset') @@ -287,6 +290,8 @@ trait Testable self::$__latestPrs = $method->prs; $this->__describing = $method->describing; $this->__test = $method->getClosure(); + + $method->setUp($this); } } diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index ace3b04f..a1638158 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1453,6 +1453,11 @@ ✓ nested → nested afterEach execution order ✓ global afterEach execution order + PASS Tests\Hooks\BeforeAllTest + ✓ it gets called before all tests 1 @ repetition 1 of 2 + ✓ it gets called before all tests 1 @ repetition 2 of 2 + ✓ it gets called before all tests 2 + PASS Tests\Hooks\BeforeEachTest ✓ global beforeEach execution order @@ -1726,4 +1731,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1154 passed (2754 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1157 passed (2766 assertions) \ No newline at end of file diff --git a/tests/Hooks/BeforeAllTest.php b/tests/Hooks/BeforeAllTest.php new file mode 100644 index 00000000..d411d263 --- /dev/null +++ b/tests/Hooks/BeforeAllTest.php @@ -0,0 +1,16 @@ +beforeAll(function () { + expect($_SERVER['globalHook']->calls->beforeAll) + ->toBe(0); + + $_SERVER['globalHook']->calls->beforeAll++; +}); + +it('gets called before all tests 1', function () { + expect($_SERVER['globalHook']->calls->beforeAll)->toBe(1); +})->repeat(2); + +it('gets called before all tests 2', function () { + expect($_SERVER['globalHook']->calls->beforeAll)->toBe(1); +}); diff --git a/tests/Pest.php b/tests/Pest.php index a938fc7e..e498450c 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -29,7 +29,6 @@ pest() }) ->beforeAll(function () { $_SERVER['globalHook']->beforeAll = 0; - $_SERVER['globalHook']->calls->beforeAll++; }) ->afterEach(function () { if (! isset($this->ith)) { diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index d9cdfbdd..226a01e7 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1144 passed (2730 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1147 passed (2742 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From 2aa32569f0c541179d49b1e17c0c21d9aab939f2 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 25 Jul 2025 21:06:39 -0600 Subject: [PATCH 94/95] fix: adjust snapshots --- tests/.snapshots/success.txt | 6 +++++- tests/Visual/Parallel.php | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index a1638158..e8e4608d 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1731,4 +1731,8 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1157 passed (2766 assertions) \ No newline at end of file + PASS Testsexternal\Features\Expect\toMatchSnapshot + ✓ 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, 38 todos, 33 skipped, 1159 passed (2774 assertions) \ No newline at end of file diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 226a01e7..eca26204 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ $run = function () { test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1147 passed (2742 assertions)') + ->toContain('Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 24 skipped, 1149 passed (2750 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From 222ed174bcaa48928e811d069015b1ea06bcc276 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 25 Jul 2025 21:10:05 -0600 Subject: [PATCH 95/95] style --- src/Mixins/Expectation.php | 8 ++++---- tests-external/Features/Expect/toMatchSnapshot.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index 62fb14b4..c3dfa45e 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -781,13 +781,13 @@ final class Expectation foreach ($array as $key => $value) { Assert::assertArrayHasKey($key, $valueAsArray, $message); - $second_message = $message !== '' ? $message : sprintf( + $assertMessage = $message !== '' ? $message : sprintf( 'Failed asserting that an array has a key %s with the value %s.', $this->export($key), $this->export($valueAsArray[$key]), ); - Assert::assertEquals($value, $valueAsArray[$key], $second_message); + Assert::assertEquals($value, $valueAsArray[$key], $assertMessage); } return $this; @@ -812,13 +812,13 @@ final class Expectation /* @phpstan-ignore-next-line */ $propertyValue = $this->value->{$property}; - $second_message = $message !== '' ? $message : sprintf( + $assertMessage = $message !== '' ? $message : sprintf( 'Failed asserting that an object has a property %s with the value %s.', $this->export($property), $this->export($propertyValue), ); - Assert::assertEquals($value, $propertyValue, $second_message); + Assert::assertEquals($value, $propertyValue, $assertMessage); } return $this; diff --git a/tests-external/Features/Expect/toMatchSnapshot.php b/tests-external/Features/Expect/toMatchSnapshot.php index db7b9666..2cef88a1 100644 --- a/tests-external/Features/Expect/toMatchSnapshot.php +++ b/tests-external/Features/Expect/toMatchSnapshot.php @@ -32,4 +32,4 @@ describe('within describe', function () { ->toEndWith('pass_with_dataset_with_data_set____my_datas_set_value______my_datas_set_value__.snap') ->and($this->snapshotable)->toMatchSnapshot(); }); -})->with(['my-datas-set-value']); \ No newline at end of file +})->with(['my-datas-set-value']);