Compare commits

...

41 Commits

Author SHA1 Message Date
7796630eaf chore: only runs CI against stable 2026-01-28 01:33:45 +00:00
3aec2b26ef chore: runs only unit tests 2026-01-28 01:30:45 +00:00
7a7c35292c release: 3.8.5 2026-01-28 01:23:37 +00:00
72cf695554 release: 3.8.4 2025-08-20 20:12:42 +01:00
027f4e4832 chore: bumps dependencies 2025-08-20 14:21:14 +01:00
165c879fe6 release: 3.8.3 2025-08-19 11:11:21 +01:00
4c8bf4b2fd chore: uses phpunit v11.5.33 2025-08-19 11:11:10 +01:00
1b0a846a81 Update README.md 2025-08-15 17:11:53 +01:00
f692be3637 chore: bumps dependencies 2025-07-26 07:34:25 -06:00
127ad618d3 chore: style 2025-07-26 07:34:19 -06:00
55218bcf78 Merge pull request #1324 from bibrokhim/add-attributes-to-laravel-preset
Add Attributes to Laravel preset
2025-07-26 04:19:54 +01:00
2a47b514ec Merge pull request #1351 from cndrsdrmn/patch-1
fix: add ignoring clause for `App\Features\Concerns` on Laravel Preset
2025-07-26 04:17:21 +01:00
7d77bbf1bb Merge pull request #1410 from JonPurvis/remove-period
Remove Period from `ShouldNotHappen` message
2025-06-23 19:29:01 +01:00
97c136cd94 link to issues page 2025-06-16 02:55:11 +01:00
d6cbd12d8b remove period from message 2025-06-16 02:51:48 +01:00
c6244a8712 Release 3.8.2 2025-04-17 11:53:02 +01:00
eed68f2840 Adjusts sponsors 2025-04-13 17:15:23 +01:00
6080f51a0b release: v3.8.1 2025-04-03 17:35:58 +01:00
e0f07be017 fix: init command detecting laravel 2025-04-03 17:23:39 +01:00
42e1b9f17f release: v3.8.0 2025-03-30 18:49:10 +01:00
0171617c1d chore: adjusts to new types on arch 2025-03-30 18:42:00 +01:00
2e11e9e65d docs: adjusts readme 2025-03-29 18:23:23 +00:00
4969526ef2 chore: bumps paratest 2025-03-29 17:57:53 +00:00
d7b1c36fdd Merge pull request #1341 from nuernbergerA/phpunit-overrides
chore: Sync overrides
2025-03-29 17:52:57 +00:00
003fc96e8f release: 3.7.5 2025-03-29 17:48:00 +00:00
f68d11ccae chore: bumps dependencies 2025-03-29 17:44:06 +00:00
ed70c9dc2b refactor: type adjustments 2025-03-14 22:40:39 +00:00
157a753d87 Update Pest.php.stub 2025-03-12 18:35:57 +00:00
a5317c5640 fix: add ignoring clause for App\Features\Concerns on Laravel Preset 2025-02-08 00:07:21 +07:00
66ceb64faa Updates tests 2025-02-03 13:36:47 +00:00
fa4098db8d Bumps dependencies 2025-02-03 13:30:45 +00:00
4a987d3d5c release: 3.7.4 2025-01-23 14:03:29 +00:00
4079a08f5f feat: adds --compact to coverage 2025-01-23 13:59:51 +00:00
c4c9e915f4 cs 2025-01-20 09:50:36 +01:00
e834527db2 Update JunitXmlLogger.php
https://github.com/sebastianbergmann/phpunit/issues/6098
2025-01-20 09:39:10 +01:00
23f130b0f9 Update JunitXmlLogger.php
from https://github.com/sebastianbergmann/phpunit/issues/5771
c722fb2599
2025-01-20 09:38:24 +01:00
0cb8c42497 sync missing listener 2025-01-20 09:36:48 +01:00
fe4b5e5e1f sync change 2025-01-20 09:35:44 +01:00
8ee9d66d80 sync cs 2025-01-20 09:34:55 +01:00
7760d945bb sync latest changes 2025-01-20 09:34:23 +01:00
1ac594bdf0 Add Attributes to Laravel preset 2024-12-06 16:07:59 +05:00
41 changed files with 512 additions and 253 deletions

View File

@ -1,44 +0,0 @@
name: Static Analysis
on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'
jobs:
static:
if: github.event_name != 'schedule' || github.repository == 'pestphp/pest'
name: Static Tests
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
dependency-version: [prefer-lowest, prefer-stable]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
tools: composer:v2
coverage: none
- name: Install Dependencies
run: composer update --prefer-stable --no-interaction --no-progress --ansi
# - name: Type Check
# run: composer test:type:check
- name: Type Coverage
run: composer test:type:coverage
- name: Refacto
run: composer test:refacto
- name: Style
run: composer test:lint

View File

@ -15,7 +15,7 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
symfony: ['7.1'] symfony: ['7.1']
php: ['8.2', '8.3', '8.4'] php: ['8.2', '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 }} name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }}

View File

@ -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. **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)** - Explore our docs at **[pestphp.com »](https://pestphp.com)**
- Follow us on Twitter at **[@pestphp »](https://twitter.com/pestphp)** - Follow the creator Nuno Maduro:
- Join us at **[discord.gg/kaHY6p54JH »](https://discord.gg/kaHY6p54JH)** or **[t.me/+kYH5G4d5MV83ODk0 »](https://t.me/+kYH5G4d5MV83ODk0)** - 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 ## Sponsors
@ -30,16 +35,15 @@ We cannot thank our sponsors enough for their incredible support in funding Pest
### Gold Sponsors ### Gold Sponsors
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)** - **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
- **[LaraJobs](https://larajobs.com/?ref=pestphp)** - **[NativePHP](https://nativephp.com/mobile?ref=pestphp.com)**
- **[Brokerchooser](https://brokerchooser.com/?ref=pestphp)** - **[CMS Max](https://cmsmax.com/?ref=pestphp)**
- **[Forge](https://forge.laravel.com/?ref=pestphp)**
### Premium Sponsors ### Premium Sponsors
- [Akaunting](https://akaunting.com/?ref=pestphp) - [Akaunting](https://akaunting.com/?ref=pestphp)
- [Codecourse](https://codecourse.com/?ref=pestphp)
- [DocuWriter.ai](https://www.docuwriter.ai/?ref=pestphp) - [DocuWriter.ai](https://www.docuwriter.ai/?ref=pestphp)
- [Localazy](https://localazy.com/?ref=pestphp) - [Localazy](https://localazy.com/?ref=pestphp)
- [Forge](https://forge.laravel.com/?ref=pestphp)
- [Route4Me](https://www.route4me.com/?ref=pestphp) - [Route4Me](https://www.route4me.com/?ref=pestphp)
- [Spatie](https://spatie.be/?ref=pestphp) - [Spatie](https://spatie.be/?ref=pestphp)
- [Worksome](https://www.worksome.com/?ref=pestphp) - [Worksome](https://www.worksome.com/?ref=pestphp)

View File

@ -18,17 +18,17 @@
], ],
"require": { "require": {
"php": "^8.2.0", "php": "^8.2.0",
"brianium/paratest": "^7.7.0", "brianium/paratest": "^7.8.5",
"nunomaduro/collision": "^8.6.0", "nunomaduro/collision": "^8.8.3",
"nunomaduro/termwind": "^2.3.0", "nunomaduro/termwind": "^2.3.3",
"pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin": "^3.0.0",
"pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-arch": "^3.1.1",
"pestphp/pest-plugin-mutate": "^3.0.5", "pestphp/pest-plugin-mutate": "^3.0.5",
"phpunit/phpunit": "^11.5.3" "phpunit/phpunit": "^11.5.50"
}, },
"conflict": { "conflict": {
"filp/whoops": "<2.16.0", "filp/whoops": "<2.16.0",
"phpunit/phpunit": ">11.5.3", "phpunit/phpunit": ">11.5.50",
"sebastian/exporter": "<6.0.0", "sebastian/exporter": "<6.0.0",
"webmozart/assert": "<1.11.0" "webmozart/assert": "<1.11.0"
}, },
@ -53,9 +53,9 @@
] ]
}, },
"require-dev": { "require-dev": {
"pestphp/pest-dev-tools": "^3.3.0", "pestphp/pest-dev-tools": "^3.4.0",
"pestphp/pest-plugin-type-coverage": "^3.2.3", "pestphp/pest-plugin-type-coverage": "^3.6.1",
"symfony/process": "^7.2.0" "symfony/process": "^7.4.4"
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true, "prefer-stable": true,
@ -71,21 +71,12 @@
], ],
"scripts": { "scripts": {
"refacto": "rector", "refacto": "rector",
"lint": "pint",
"test:refacto": "rector --dry-run",
"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:unit": "php bin/pest --colors=always --exclude-group=integration --compact",
"test:inline": "php bin/pest --colors=always --configuration=phpunit.inline.xml", "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:parallel": "php bin/pest --colors=always --exclude-group=integration --parallel --processes=3",
"test:integration": "php bin/pest --colors=always --group=integration -v", "test:integration": "php bin/pest --colors=always --group=integration -v",
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always --update-snapshots", "update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --colors=always --update-snapshots",
"test": [ "test": [
"@test:refacto",
"@test:lint",
"@test:type:check",
"@test:type:coverage",
"@test:unit", "@test:unit",
"@test:parallel", "@test:parallel",
"@test:integration" "@test:integration"

View File

@ -27,6 +27,7 @@ use PHPUnit\Event\Test\Finished;
use PHPUnit\Event\Test\MarkedIncomplete; use PHPUnit\Event\Test\MarkedIncomplete;
use PHPUnit\Event\Test\PreparationStarted; use PHPUnit\Event\Test\PreparationStarted;
use PHPUnit\Event\Test\Prepared; use PHPUnit\Event\Test\Prepared;
use PHPUnit\Event\Test\PrintedUnexpectedOutput;
use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\Test\Skipped;
use PHPUnit\Event\TestSuite\Started; use PHPUnit\Event\TestSuite\Started;
use PHPUnit\Event\UnknownSubscriberTypeException; use PHPUnit\Event\UnknownSubscriberTypeException;
@ -41,6 +42,8 @@ use function str_replace;
use function trim; 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 * @internal This class is not covered by the backward compatibility promise for PHPUnit
*/ */
final class JunitXmlLogger final class JunitXmlLogger
@ -59,32 +62,32 @@ final class JunitXmlLogger
private array $testSuites = []; private array $testSuites = [];
/** /**
* @psalm-var array<int,int> * @var array<int,int>
*/ */
private array $testSuiteTests = [0]; private array $testSuiteTests = [0];
/** /**
* @psalm-var array<int,int> * @var array<int,int>
*/ */
private array $testSuiteAssertions = [0]; private array $testSuiteAssertions = [0];
/** /**
* @psalm-var array<int,int> * @var array<int,int>
*/ */
private array $testSuiteErrors = [0]; private array $testSuiteErrors = [0];
/** /**
* @psalm-var array<int,int> * @var array<int,int>
*/ */
private array $testSuiteFailures = [0]; private array $testSuiteFailures = [0];
/** /**
* @psalm-var array<int,int> * @var array<int,int>
*/ */
private array $testSuiteSkipped = [0]; private array $testSuiteSkipped = [0];
/** /**
* @psalm-var array<int,int> * @var array<int,int>
*/ */
private array $testSuiteTimes = [0]; private array $testSuiteTimes = [0];
@ -113,7 +116,7 @@ final class JunitXmlLogger
public function flush(): void public function flush(): void
{ {
$this->printer->print($this->document->saveXML()); $this->printer->print($this->document->saveXML() ?: '');
$this->printer->flush(); $this->printer->flush();
} }
@ -195,28 +198,34 @@ final class JunitXmlLogger
$this->createTestCase($event); $this->createTestCase($event);
} }
/**
* @throws InvalidArgumentException
*/
public function testPreparationFailed(): void public function testPreparationFailed(): void
{ {
$this->preparationFailed = true; $this->preparationFailed = true;
} }
/**
* @throws InvalidArgumentException
*/
public function testPrepared(): void public function testPrepared(): void
{ {
$this->prepared = true; $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 * @throws InvalidArgumentException
*/ */
public function testFinished(Finished $event): void public function testFinished(Finished $event): void
{ {
if ($this->preparationFailed) { if (! $this->prepared || $this->preparationFailed) {
return; return;
} }
@ -305,9 +314,11 @@ final class JunitXmlLogger
new TestPreparationStartedSubscriber($this), new TestPreparationStartedSubscriber($this),
new TestPreparationFailedSubscriber($this), new TestPreparationFailedSubscriber($this),
new TestPreparedSubscriber($this), new TestPreparedSubscriber($this),
new TestPrintedUnexpectedOutputSubscriber($this),
new TestFinishedSubscriber($this), new TestFinishedSubscriber($this),
new TestErroredSubscriber($this), new TestErroredSubscriber($this),
new TestFailedSubscriber($this), new TestFailedSubscriber($this),
new TestMarkedIncompleteSubscriber($this),
new TestSkippedSubscriber($this), new TestSkippedSubscriber($this),
new TestRunnerExecutionFinishedSubscriber($this), new TestRunnerExecutionFinishedSubscriber($this),
); );
@ -431,7 +442,7 @@ final class JunitXmlLogger
/** /**
* @throws InvalidArgumentException * @throws InvalidArgumentException
* *
* @psalm-assert !null $this->currentTestCase * @phpstan-assert !null $this->currentTestCase
*/ */
private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void
{ {

View File

@ -46,9 +46,10 @@ declare(strict_types=1);
namespace PHPUnit\Runner\ResultCache; namespace PHPUnit\Runner\ResultCache;
use const DIRECTORY_SEPARATOR; use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use PHPUnit\Framework\TestStatus\TestStatus; use PHPUnit\Framework\TestStatus\TestStatus;
use PHPUnit\Runner\DirectoryCannotBeCreatedException; use PHPUnit\Runner\DirectoryDoesNotExistException;
use PHPUnit\Runner\Exception; use PHPUnit\Runner\Exception;
use PHPUnit\Util\Filesystem; use PHPUnit\Util\Filesystem;
@ -65,6 +66,8 @@ use function json_encode;
use function Pest\version; 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 * @internal This class is not covered by the backward compatibility promise for PHPUnit
*/ */
final class DefaultResultCache implements ResultCache final class DefaultResultCache implements ResultCache
@ -77,12 +80,12 @@ final class DefaultResultCache implements ResultCache
private readonly string $cacheFilename; private readonly string $cacheFilename;
/** /**
* @psalm-var array<string, TestStatus> * @var array<string, TestStatus>
*/ */
private array $defects = []; private array $defects = [];
/** /**
* @psalm-var array<string, float> * @var array<string, float>
*/ */
private array $times = []; private array $times = [];
@ -95,28 +98,39 @@ final class DefaultResultCache implements ResultCache
$this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; $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()) { if ($status->isSuccess()) {
return; 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
{
foreach ($other->defects as $id => $defect) {
$this->defects[$id] = $defect;
}
foreach ($other->times as $id => $time) {
$this->times[$id] = $time;
}
} }
public function load(): void public function load(): void
@ -165,7 +179,7 @@ final class DefaultResultCache implements ResultCache
public function persist(): void public function persist(): void
{ {
if (! Filesystem::createDirectory(dirname($this->cacheFilename))) { if (! Filesystem::createDirectory(dirname($this->cacheFilename))) {
throw new DirectoryCannotBeCreatedException($this->cacheFilename); throw new DirectoryDoesNotExistException(dirname($this->cacheFilename));
} }
$data = [ $data = [

199
phpstan-baseline.neon Normal file
View File

@ -0,0 +1,199 @@
parameters:
ignoreErrors:
-
message: '#^Parameter \#1 of callable callable\(Pest\\Expectation\<string\|null\>\)\: Pest\\Arch\\Contracts\\ArchExpectation expects Pest\\Expectation\<string\|null\>, Pest\\Expectation\<string\|null\> 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\<TAndValue\> but returns \(Pest\\Expectation&TAndValue\)\|Pest\\Expectation\<TAndValue of mixed\>\.$#'
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\<TValue\|null\> but returns Pest\\Expectation\<TValue\|null\>\.$#'
identifier: return.type
count: 1
path: src/Functions.php
-
message: '#^Parameter \#1 \$argv of method PHPUnit\\TextUI\\Application\:\:run\(\) expects list\<string\>, array\<int, string\> 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\<string\>\|null, array\<int, string\> given\.$#'
identifier: argument.type
count: 1
path: src/Plugins/Parallel.php
-
message: '#^Parameter \#13 \$testRunnerTriggeredDeprecationEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\<PHPUnit\\Event\\TestRunner\\DeprecationTriggered\>, 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\<PHPUnit\\Event\\TestRunner\\WarningTriggered\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\TestRunner\\TestResult\\Issues\\Issue\>, 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\<PHPUnit\\Event\\Test\\AfterLastTestMethodErrored\|PHPUnit\\Event\\Test\\BeforeFirstTestMethodErrored\|PHPUnit\\Event\\Test\\Errored\>, 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\<PHPUnit\\Event\\Test\\Failed\>, 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\<PHPUnit\\Event\\TestSuite\\Skipped\>, 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\<PHPUnit\\Event\\Test\\Skipped\>, 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\<PHPUnit\\Event\\Test\\MarkedIncomplete\>, array given\.$#'
identifier: argument.type
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php
-
message: '#^Property Pest\\Plugins\\Parallel\\Paratest\\WrapperRunner\:\:\$pending \(list\<non\-empty\-string\>\) does not accept array\<int, non\-empty\-string\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Plugins/Parallel/Paratest/WrapperRunner.php

View File

@ -1,14 +1,12 @@
includes: includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon - phpstan-baseline.neon
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
parameters: parameters:
level: max level: 7
paths: paths:
- src - src
checkMissingIterableValueType: true reportUnmatchedIgnoredErrors: false
reportUnmatchedIgnoredErrors: true
ignoreErrors: ignoreErrors:
- "#type mixed is not subtype of native#" - "#type mixed is not subtype of native#"

View File

@ -35,7 +35,8 @@ final class Laravel extends AbstractPreset
->ignoring('App\Features\Concerns'); ->ignoring('App\Features\Concerns');
$this->expectations[] = expect('App\Features') $this->expectations[] = expect('App\Features')
->toHaveMethod('resolve'); ->toHaveMethod('resolve')
->ignoring('App\Features\Concerns');
$this->expectations[] = expect('App\Exceptions') $this->expectations[] = expect('App\Exceptions')
->classes() ->classes()
@ -166,5 +167,11 @@ final class Laravel extends AbstractPreset
$this->expectations[] = expect('App\Policies') $this->expectations[] = expect('App\Policies')
->classes() ->classes()
->toHaveSuffix('Policy'); ->toHaveSuffix('Policy');
$this->expectations[] = expect('App\Attributes')
->classes()
->toImplement('Illuminate\Contracts\Container\ContextualAttribute')
->toHaveAttribute('Attribute')
->toHaveMethod('resolve');
} }
} }

View File

@ -24,8 +24,12 @@ final readonly class Thanks
*/ */
private const FUNDING_MESSAGES = [ private const FUNDING_MESSAGES = [
'Star' => 'https://github.com/pestphp/pest', 'Star' => 'https://github.com/pestphp/pest',
'News' => 'https://twitter.com/pestphp', 'YouTube' => 'https://youtube.com/@nunomaduro',
'Videos' => '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', 'Sponsor' => 'https://github.com/sponsors/nunomaduro',
]; ];

View File

@ -20,7 +20,7 @@ final class ShouldNotHappen extends RuntimeException
$message = $exception->getMessage(); $message = $exception->getMessage();
parent::__construct(sprintf(<<<'EOF' 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 Issue: %s
PHP version: %s PHP version: %s

View File

@ -535,7 +535,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $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', 'to be final',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -548,7 +548,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $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', 'to be readonly',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -561,7 +561,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isTrait(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isTrait(),
'to be trait', 'to be trait',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -582,7 +582,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isAbstract(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isAbstract(),
'to be abstract', 'to be abstract',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -599,7 +599,7 @@ final class Expectation
return Targeted::make( return Targeted::make(
$this, $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)), sprintf("to have method '%s'", implode("', '", $methods)),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -670,7 +670,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isEnum(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isEnum(),
'to be enum', 'to be enum',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -712,7 +712,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->isInterface(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->isInterface(),
'to be interface', 'to be interface',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -733,7 +733,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $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), sprintf("to extend '%s'", $class),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -773,6 +773,10 @@ final class Expectation
$this, $this,
function (ObjectDescription $object) use ($traits): bool { function (ObjectDescription $object) use ($traits): bool {
foreach ($traits as $trait) { foreach ($traits as $trait) {
if (isset($object->reflectionClass) === false) {
return false;
}
if (! in_array($trait, $object->reflectionClass->getTraitNames(), true)) { if (! in_array($trait, $object->reflectionClass->getTraitNames(), true)) {
return false; return false;
} }
@ -792,7 +796,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() === [], fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getInterfaceNames() === [],
'to implement nothing', 'to implement nothing',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -809,7 +813,8 @@ final class Expectation
return Targeted::make( return Targeted::make(
$this, $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()) === [], && array_diff($interfaces, $object->reflectionClass->getInterfaceNames()) === [],
"to only implement '".implode("', '", $interfaces)."'", "to only implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
@ -823,7 +828,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $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}'", "to have prefix '{$prefix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -836,7 +841,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $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}'", "to have suffix '{$suffix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -855,7 +860,7 @@ final class Expectation
$this, $this,
function (ObjectDescription $object) use ($interfaces): bool { function (ObjectDescription $object) use ($interfaces): bool {
foreach ($interfaces as $interface) { foreach ($interfaces as $interface) {
if (! $object->reflectionClass->implementsInterface($interface)) { if (! isset($object->reflectionClass) || ! $object->reflectionClass->implementsInterface($interface)) {
return false; return false;
} }
} }
@ -928,7 +933,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->hasMethod('__invoke'), fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->hasMethod('__invoke'),
'to be invokable', 'to be invokable',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
); );
@ -1037,7 +1042,7 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => $object->reflectionClass->getAttributes($attribute) !== [], fn (ObjectDescription $object): bool => isset($object->reflectionClass) && $object->reflectionClass->getAttributes($attribute) !== [],
"to have attribute '{$attribute}'", "to have attribute '{$attribute}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -1066,7 +1071,8 @@ final class Expectation
{ {
return Targeted::make( return Targeted::make(
$this, $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 && (new ReflectionEnum($object->name))->isBacked() // @phpstan-ignore-line
&& (string) (new ReflectionEnum($object->name))->getBackingType() === $backingType, // @phpstan-ignore-line && (string) (new ReflectionEnum($object->name))->getBackingType() === $backingType, // @phpstan-ignore-line
'to be '.$backingType.' backed enum', 'to be '.$backingType.' backed enum',

View File

@ -74,7 +74,10 @@ final readonly class OppositeExpectation
*/ */
public function toUse(array|string $targets): ArchExpectation 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<array<int, string>|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), fn () => $this->throwExpectationFailedException('toUse', $target),
), is_string($targets) ? [$targets] : $targets)); ), is_string($targets) ? [$targets] : $targets));
} }
@ -84,8 +87,11 @@ final readonly class OppositeExpectation
*/ */
public function toHaveFileSystemPermissions(string $permissions): ArchExpectation public function toHaveFileSystemPermissions(string $permissions): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => substr(sprintf('%o', fileperms($object->path)), -4) !== $permissions, fn (ObjectDescription $object): bool => substr(sprintf('%o', fileperms($object->path)), -4) !== $permissions,
sprintf('permissions not to be [%s]', $permissions), sprintf('permissions not to be [%s]', $permissions),
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')), FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
@ -105,8 +111,11 @@ final readonly class OppositeExpectation
*/ */
public function toHaveMethodsDocumented(): ArchExpectation public function toHaveMethodsDocumented(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter( || array_filter(
Reflection::getMethodsFromReflectionClass($object->reflectionClass), Reflection::getMethodsFromReflectionClass($object->reflectionClass),
@ -124,8 +133,11 @@ final readonly class OppositeExpectation
*/ */
public function toHavePropertiesDocumented(): ArchExpectation public function toHavePropertiesDocumented(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| array_filter( || array_filter(
Reflection::getPropertiesFromReflectionClass($object->reflectionClass), Reflection::getPropertiesFromReflectionClass($object->reflectionClass),
@ -144,8 +156,11 @@ final readonly class OppositeExpectation
*/ */
public function toUseStrictTypes(): ArchExpectation public function toUseStrictTypes(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( 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)), 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', 'not to use strict types',
FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')), FileLineFinder::where(fn (string $line): bool => str_contains($line, '<?php')),
@ -157,8 +172,11 @@ final readonly class OppositeExpectation
*/ */
public function toUseStrictEquality(): ArchExpectation public function toUseStrictEquality(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( 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), ' !== '), fn (ObjectDescription $object): bool => ! str_contains((string) file_get_contents($object->path), ' === ') && ! str_contains((string) file_get_contents($object->path), ' !== '),
'to use strict equality', 'to use strict equality',
FileLineFinder::where(fn (string $line): bool => str_contains($line, ' === ') || str_contains($line, ' !== ')), FileLineFinder::where(fn (string $line): bool => str_contains($line, ' === ') || str_contains($line, ' !== ')),
@ -170,9 +188,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeFinal(): ArchExpectation public function toBeFinal(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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', 'not to be final',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -183,9 +204,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeReadonly(): ArchExpectation public function toBeReadonly(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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', 'not to be readonly',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -196,9 +220,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeTrait(): ArchExpectation public function toBeTrait(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isTrait(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isTrait(),
'not to be trait', 'not to be trait',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -217,9 +244,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeAbstract(): ArchExpectation public function toBeAbstract(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isAbstract(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isAbstract(),
'not to be abstract', 'not to be abstract',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -234,11 +264,14 @@ final readonly class OppositeExpectation
{ {
$methods = is_array($method) ? $method : [$method]; $methods = is_array($method) ? $method : [$method];
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => array_filter( fn (ObjectDescription $object): bool => array_filter(
$methods, $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), 'to not have methods: '.implode(', ', $methods),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
@ -266,8 +299,11 @@ final readonly class OppositeExpectation
$state = new stdClass; $state = new stdClass;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
function (ObjectDescription $object) use ($methods, &$state): bool { function (ObjectDescription $object) use ($methods, &$state): bool {
$reflectionMethods = isset($object->reflectionClass) $reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PUBLIC) ? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PUBLIC)
@ -286,7 +322,7 @@ final readonly class OppositeExpectation
$methods === [] $methods === []
? 'not to have public methods' ? 'not to have public methods'
: sprintf("not to have public methods besides '%s'", implode("', '", $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)),
); );
} }
@ -309,8 +345,11 @@ final readonly class OppositeExpectation
$state = new stdClass; $state = new stdClass;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
function (ObjectDescription $object) use ($methods, &$state): bool { function (ObjectDescription $object) use ($methods, &$state): bool {
$reflectionMethods = isset($object->reflectionClass) $reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PROTECTED) ? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PROTECTED)
@ -329,7 +368,7 @@ final readonly class OppositeExpectation
$methods === [] $methods === []
? 'not to have protected methods' ? 'not to have protected methods'
: sprintf("not to have protected methods besides '%s'", implode("', '", $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)),
); );
} }
@ -352,8 +391,11 @@ final readonly class OppositeExpectation
$state = new stdClass; $state = new stdClass;
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
function (ObjectDescription $object) use ($methods, &$state): bool { function (ObjectDescription $object) use ($methods, &$state): bool {
$reflectionMethods = isset($object->reflectionClass) $reflectionMethods = isset($object->reflectionClass)
? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PRIVATE) ? Reflection::getMethodsFromReflectionClass($object->reflectionClass, ReflectionMethod::IS_PRIVATE)
@ -372,7 +414,7 @@ final readonly class OppositeExpectation
$methods === [] $methods === []
? 'not to have private methods' ? 'not to have private methods'
: sprintf("not to have private methods besides '%s'", implode("', '", $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)),
); );
} }
@ -389,9 +431,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeEnum(): ArchExpectation public function toBeEnum(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isEnum(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isEnum(),
'not to be enum', 'not to be enum',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -410,8 +455,11 @@ final readonly class OppositeExpectation
*/ */
public function toBeClass(): ArchExpectation public function toBeClass(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => ! class_exists($object->name), fn (ObjectDescription $object): bool => ! class_exists($object->name),
'not to be class', 'not to be class',
FileLineFinder::where(fn (string $line): bool => true), FileLineFinder::where(fn (string $line): bool => true),
@ -431,9 +479,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeInterface(): ArchExpectation public function toBeInterface(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => ! $object->reflectionClass->isInterface(), fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || ! $object->reflectionClass->isInterface(),
'not to be interface', 'not to be interface',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -452,9 +503,12 @@ final readonly class OppositeExpectation
*/ */
public function toExtend(string $class): ArchExpectation public function toExtend(string $class): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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), sprintf("not to extend '%s'", $class),
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -465,9 +519,12 @@ final readonly class OppositeExpectation
*/ */
public function toExtendNothing(): ArchExpectation public function toExtendNothing(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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', 'to extend a class',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -490,11 +547,14 @@ final readonly class OppositeExpectation
{ {
$traits = is_array($traits) ? $traits : [$traits]; $traits = is_array($traits) ? $traits : [$traits];
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
function (ObjectDescription $object) use ($traits): bool { function (ObjectDescription $object) use ($traits): bool {
foreach ($traits as $trait) { 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; return false;
} }
} }
@ -515,11 +575,14 @@ final readonly class OppositeExpectation
{ {
$interfaces = is_array($interfaces) ? $interfaces : [$interfaces]; $interfaces = is_array($interfaces) ? $interfaces : [$interfaces];
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
function (ObjectDescription $object) use ($interfaces): bool { function (ObjectDescription $object) use ($interfaces): bool {
foreach ($interfaces as $interface) { foreach ($interfaces as $interface) {
if ($object->reflectionClass->implementsInterface($interface)) { if (isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface)) {
return false; return false;
} }
} }
@ -536,9 +599,12 @@ final readonly class OppositeExpectation
*/ */
public function toImplementNothing(): ArchExpectation public function toImplementNothing(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $original,
fn (ObjectDescription $object): bool => $object->reflectionClass->getInterfaceNames() !== [], fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false || $object->reflectionClass->getInterfaceNames() !== [],
'to implement an interface', 'to implement an interface',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -557,9 +623,12 @@ final readonly class OppositeExpectation
*/ */
public function toHavePrefix(string $prefix): ArchExpectation public function toHavePrefix(string $prefix): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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}'", "not to have prefix '{$prefix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -570,9 +639,12 @@ final readonly class OppositeExpectation
*/ */
public function toHaveSuffix(string $suffix): ArchExpectation public function toHaveSuffix(string $suffix): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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}'", "not to have suffix '{$suffix}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -599,7 +671,10 @@ final readonly class OppositeExpectation
*/ */
public function toBeUsed(): ArchExpectation public function toBeUsed(): ArchExpectation
{ {
return ToBeUsedInNothing::make($this->original); /** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return ToBeUsedInNothing::make($original);
} }
/** /**
@ -609,7 +684,10 @@ final readonly class OppositeExpectation
*/ */
public function toBeUsedIn(array|string $targets): ArchExpectation 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<array<int, string>|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), fn () => $this->throwExpectationFailedException('toBeUsedIn', $target),
), is_string($targets) ? [$targets] : $targets)); ), is_string($targets) ? [$targets] : $targets));
} }
@ -632,9 +710,12 @@ final readonly class OppositeExpectation
*/ */
public function toBeInvokable(): ArchExpectation public function toBeInvokable(): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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', 'to not be invokable',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
); );
@ -645,9 +726,12 @@ final readonly class OppositeExpectation
*/ */
public function toHaveAttribute(string $attribute): ArchExpectation public function toHaveAttribute(string $attribute): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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}'", "to not have attribute '{$attribute}'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')) FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class'))
); );
@ -737,9 +821,13 @@ final readonly class OppositeExpectation
*/ */
private function toBeBackedEnum(string $backingType): ArchExpectation private function toBeBackedEnum(string $backingType): ArchExpectation
{ {
/** @var Expectation<array<int, string>|string> $original */
$original = $this->original;
return Targeted::make( return Targeted::make(
$this->original, $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 || ! (new \ReflectionEnum($object->name))->isBacked() // @phpstan-ignore-line
|| (string) (new \ReflectionEnum($object->name))->getBackingType() !== $backingType, // @phpstan-ignore-line || (string) (new \ReflectionEnum($object->name))->getBackingType() !== $backingType, // @phpstan-ignore-line
'not to be '.$backingType.' backed enum', 'not to be '.$backingType.' backed enum',

View File

@ -155,7 +155,7 @@ final class TestCaseMethodFactory
assert($testCase instanceof TestCaseFactory); assert($testCase instanceof TestCaseFactory);
$method = $this; $method = $this;
return function (...$arguments) use ($testCase, $method, $closure): mixed { // @phpstan-ignore-line return function (...$arguments) use ($testCase, $method, $closure): mixed {
/* @var TestCase $this */ /* @var TestCase $this */
$testCase->proxies->proxy($this); $testCase->proxies->proxy($this);
$method->proxies->proxy($this); $method->proxies->proxy($this);

View File

@ -232,7 +232,6 @@ final class TeamCityLogger
$reflector = new ReflectionClass($telemetry); $reflector = new ReflectionClass($telemetry);
$property = $reflector->getProperty('current'); $property = $reflector->getProperty('current');
$property->setAccessible(true);
$snapshot = $property->getValue($telemetry); $snapshot = $property->getValue($telemetry);
assert($snapshot instanceof Snapshot); assert($snapshot instanceof Snapshot);

View File

@ -183,7 +183,6 @@ final class Expectation
{ {
foreach ($needles as $needle) { foreach ($needles as $needle) {
if (is_string($this->value)) { if (is_string($this->value)) {
// @phpstan-ignore-next-line
Assert::assertStringContainsString((string) $needle, $this->value); Assert::assertStringContainsString((string) $needle, $this->value);
} else { } else {
if (! is_iterable($this->value)) { if (! is_iterable($this->value)) {

View File

@ -46,7 +46,7 @@ final readonly class Panic
{ {
try { try {
$output = Container::getInstance()->get(OutputInterface::class); $output = Container::getInstance()->get(OutputInterface::class);
} catch (Throwable) { // @phpstan-ignore-line } catch (Throwable) {
$output = new ConsoleOutput; $output = new ConsoleOutput;
} }

View File

@ -78,7 +78,7 @@ final class DescribeCall
$this->currentBeforeEachCall->describing[] = $this->description; $this->currentBeforeEachCall->describing[] = $this->description;
} }
$this->currentBeforeEachCall->{$name}(...$arguments); // @phpstan-ignore-line $this->currentBeforeEachCall->{$name}(...$arguments);
return $this; return $this;
} }

View File

@ -224,7 +224,7 @@ final class TestCall // @phpstan-ignore-line
*/ */
public function only(): self public function only(): self
{ {
Only::enable($this, ...func_get_args()); // @phpstan-ignore-line Only::enable($this, ...func_get_args());
return $this; return $this;
} }

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string function version(): string
{ {
return '3.7.3'; return '3.8.5';
} }
function testDirectory(string $file = ''): string function testDirectory(string $file = ''): string

View File

@ -37,6 +37,11 @@ final class Coverage implements AddsOutput, HandlesArguments
*/ */
public bool $coverage = false; public bool $coverage = false;
/**
* Whether it should show the coverage or not.
*/
public bool $compact = false;
/** /**
* The minimum coverage. * The minimum coverage.
*/ */
@ -124,6 +129,10 @@ final class Coverage implements AddsOutput, HandlesArguments
$this->coverageExactly = (float) $exactlyOption; $this->coverageExactly = (float) $exactlyOption;
} }
if ($_SERVER['COLLISION_PRINTER_COMPACT'] ?? false) {
$this->compact = true;
}
return $originals; return $originals;
} }
@ -144,7 +153,7 @@ final class Coverage implements AddsOutput, HandlesArguments
exit(1); exit(1);
} }
$coverage = \Pest\Support\Coverage::report($this->output); $coverage = \Pest\Support\Coverage::report($this->output, $this->compact);
$exitCode = (int) ($coverage < $this->coverageMin); $exitCode = (int) ($coverage < $this->coverageMin);
if ($exitCode === 0 && $this->coverageExactly !== null) { if ($exitCode === 0 && $this->coverageExactly !== null) {

View File

@ -119,6 +119,6 @@ final readonly class Init implements HandlesArguments
*/ */
private function isLaravelInstalled(): bool private function isLaravelInstalled(): bool
{ {
return InstalledVersions::isInstalled('laravel/laravel'); return InstalledVersions::isInstalled('laravel/framework');
} }
} }

View File

@ -71,7 +71,7 @@ final class DatasetsRepository
* *
* @throws ShouldNotHappen * @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]; $dataset = self::$withs[$filename.self::SEPARATOR.$description];
@ -110,7 +110,6 @@ final class DatasetsRepository
foreach ($datasetCombination as $datasetCombinationElement) { foreach ($datasetCombination as $datasetCombinationElement) {
$partialDescriptions[] = $datasetCombinationElement['label']; $partialDescriptions[] = $datasetCombinationElement['label'];
// @phpstan-ignore-next-line
$values = array_merge($values, $datasetCombinationElement['values']); $values = array_merge($values, $datasetCombinationElement['values']);
} }
@ -221,7 +220,6 @@ final class DatasetsRepository
$result = $tmp; $result = $tmp;
} }
// @phpstan-ignore-next-line
return $result; return $result;
} }

View File

@ -19,8 +19,8 @@ final class SnapshotRepository
* Creates a snapshot repository instance. * Creates a snapshot repository instance.
*/ */
public function __construct( public function __construct(
readonly private string $testsPath, private readonly string $testsPath,
readonly private string $snapshotsPath, private readonly string $snapshotsPath,
) {} ) {}
/** /**

View File

@ -40,7 +40,7 @@ final class Result
*/ */
public static function exitCode(Configuration $configuration, TestResult $result): int public static function exitCode(Configuration $configuration, TestResult $result): int
{ {
if ($result->wasSuccessfulIgnoringPhpunitWarnings()) { if ($result->wasSuccessful()) {
if ($configuration->failOnWarning()) { if ($configuration->failOnWarning()) {
$warnings = $result->numberOfTestsWithTestTriggeredPhpunitWarningEvents() $warnings = $result->numberOfTestsWithTestTriggeredPhpunitWarningEvents()
+ count($result->warnings()) + count($result->warnings())
@ -60,7 +60,7 @@ final class Result
return self::FAILURE_EXIT; return self::FAILURE_EXIT;
} }
if ($result->wasSuccessfulIgnoringPhpunitWarnings()) { if ($result->wasSuccessful()) {
if ($configuration->failOnRisky() && $result->hasTestConsideredRiskyEvents()) { if ($configuration->failOnRisky() && $result->hasTestConsideredRiskyEvents()) {
$returnCode = self::FAILURE_EXIT; $returnCode = self::FAILURE_EXIT;
} }

View File

@ -23,14 +23,12 @@ final class EnsureIgnorableTestCasesAreIgnored implements StartedSubscriber
{ {
$reflection = new ReflectionClass(Facade::class); $reflection = new ReflectionClass(Facade::class);
$property = $reflection->getProperty('collector'); $property = $reflection->getProperty('collector');
$property->setAccessible(true);
$collector = $property->getValue(); $collector = $property->getValue();
assert($collector instanceof Collector); assert($collector instanceof Collector);
$reflection = new ReflectionClass($collector); $reflection = new ReflectionClass($collector);
$property = $reflection->getProperty('testRunnerTriggeredWarningEvents'); $property = $reflection->getProperty('testRunnerTriggeredWarningEvents');
$property->setAccessible(true);
/** @var array<int, WarningTriggered> $testRunnerTriggeredWarningEvents */ /** @var array<int, WarningTriggered> $testRunnerTriggeredWarningEvents */
$testRunnerTriggeredWarningEvents = $property->getValue($collector); $testRunnerTriggeredWarningEvents = $property->getValue($collector);

View File

@ -15,7 +15,6 @@ final class Closure
/** /**
* Binds the given closure to the given "this". * Binds the given closure to the given "this".
* *
*
* @throws ShouldNotHappen * @throws ShouldNotHappen
*/ */
public static function bind(?BaseClosure $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure 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.'); throw ShouldNotHappen::fromMessage('Could not bind null closure.');
} }
// @phpstan-ignore-next-line
$closure = BaseClosure::bind($closure, $newThis, $newScope); $closure = BaseClosure::bind($closure, $newThis, $newScope);
if (! $closure instanceof \Closure) { if (! $closure instanceof \Closure) {

View File

@ -74,7 +74,7 @@ final class Coverage
* Reports the code coverage report to the * Reports the code coverage report to the
* console and returns the result in float. * 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 (! file_exists($reportPath = self::getPath())) {
if (self::usingXdebug()) { if (self::usingXdebug()) {
@ -113,6 +113,10 @@ final class Coverage
? '100.0' ? '100.0'
: number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', ''); : number_format($file->percentageOfExecutedLines()->asFloat(), 1, '.', '');
if ($percentage === '100.0' && $compact) {
continue;
}
$uncoveredLines = ''; $uncoveredLines = '';
$percentageOfExecutedLinesAsString = $file->percentageOfExecutedLines()->asString(); $percentageOfExecutedLinesAsString = $file->percentageOfExecutedLines()->asString();

View File

@ -66,6 +66,7 @@ final readonly class Exporter
$result[] = $context->contains($data[$key]) !== false $result[] = $context->contains($data[$key]) !== false
? '*RECURSION*' ? '*RECURSION*'
// @phpstan-ignore-next-line
: sprintf('[%s]', $this->shortenedRecursiveExport($data[$key], $context)); : sprintf('[%s]', $this->shortenedRecursiveExport($data[$key], $context));
} }

View File

@ -50,14 +50,13 @@ final class HigherOrderMessage
} }
if ($this->hasHigherOrderCallable()) { if ($this->hasHigherOrderCallable()) {
/* @phpstan-ignore-next-line */
return (new HigherOrderCallables($target))->{$this->name}(...$this->arguments); return (new HigherOrderCallables($target))->{$this->name}(...$this->arguments);
} }
try { try {
return is_array($this->arguments) return is_array($this->arguments)
? Reflection::call($target, $this->name, $this->arguments) ? Reflection::call($target, $this->name, $this->arguments)
: $target->{$this->name}; /* @phpstan-ignore-line */ : $target->{$this->name};
} catch (Throwable $throwable) { } catch (Throwable $throwable) {
Reflection::setPropertyValue($throwable, 'file', $this->filename); Reflection::setPropertyValue($throwable, 'file', $this->filename);
Reflection::setPropertyValue($throwable, 'line', $this->line); Reflection::setPropertyValue($throwable, 'line', $this->line);
@ -65,7 +64,6 @@ final class HigherOrderMessage
if ($throwable->getMessage() === $this->getUndefinedMethodMessage($target, $this->name)) { if ($throwable->getMessage() === $this->getUndefinedMethodMessage($target, $this->name)) {
/** @var ReflectionClass<TValue> $reflection */ /** @var ReflectionClass<TValue> $reflection */
$reflection = new ReflectionClass($target); $reflection = new ReflectionClass($target);
/* @phpstan-ignore-next-line */
$reflection = $reflection->getParentClass() ?: $reflection; $reflection = $reflection->getParentClass() ?: $reflection;
Reflection::setPropertyValue($throwable, 'message', sprintf('Call to undefined method %s::%s()', $reflection->getName(), $this->name)); 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 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, sprintf('%s::%s()', $target::class, $methodName));
}
return sprintf(self::UNDEFINED_METHOD, $methodName);
} }
} }

View File

@ -40,7 +40,6 @@ final class HigherOrderMessageCollection
public function chain(object $target): void public function chain(object $target): void
{ {
foreach ($this->messages as $message) { foreach ($this->messages as $message) {
// @phpstan-ignore-next-line
$target = $message->call($target) ?? $target; $target = $message->call($target) ?? $target;
} }
} }

View File

@ -26,7 +26,7 @@ final class HigherOrderTapProxy
*/ */
public function __set(string $property, mixed $value): void 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) public function __get(string $property)
{ {
if (property_exists($this->target, $property)) { if (property_exists($this->target, $property)) {
return $this->target->{$property}; // @phpstan-ignore-line return $this->target->{$property};
} }
$className = (new ReflectionClass($this->target))->getName(); $className = (new ReflectionClass($this->target))->getName();

View File

@ -34,8 +34,6 @@ final class Reflection
try { try {
$reflectionMethod = $reflectionClass->getMethod($method); $reflectionMethod = $reflectionClass->getMethod($method);
$reflectionMethod->setAccessible(true);
return $reflectionMethod->invoke($object, ...$args); return $reflectionMethod->invoke($object, ...$args);
} catch (ReflectionException $exception) { } catch (ReflectionException $exception) {
if (method_exists($object, '__call')) { if (method_exists($object, '__call')) {
@ -113,8 +111,6 @@ final class Reflection
} }
} }
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($object); return $reflectionProperty->getValue($object);
} }
@ -144,8 +140,6 @@ final class Reflection
} }
} }
} }
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, $value); $reflectionProperty->setValue($object, $value);
} }

View File

@ -1,31 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
> >
<testsuites> <testsuites>
<testsuite name="Unit"> <testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory> <directory>tests/Unit</directory>
</testsuite> </testsuite>
<testsuite name="Feature"> <testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory> <directory>tests/Feature</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php> <php>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/> <env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> --> <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> --> <!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="MAIL_MAILER" value="array"/> <env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/> <env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/> <env name="TELESCOPE_ENABLED" value="false"/>
</php> </php>
<source>
<include>
<directory suffix=".php">./app</directory>
</include>
</source>
</phpunit> </phpunit>

View File

@ -11,7 +11,7 @@
| |
*/ */
// pest()->extend(Tests\TestCase::class)->in('Feature'); pest()->extend(Tests\TestCase::class)->in('Feature');
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
> >
@ -11,8 +11,8 @@
</testsuites> </testsuites>
<source> <source>
<include> <include>
<directory suffix=".php">./app</directory> <directory>app</directory>
<directory suffix=".php">./src</directory> <directory>src</directory>
</include> </include>
</source> </source>
</phpunit> </phpunit>

View File

@ -1,5 +1,5 @@
Pest Testing Framework 3.7.3. Pest Testing Framework 3.8.5.
USAGE: pest <file> [options] USAGE: pest <file> [options]
@ -53,7 +53,7 @@
--disallow-test-output ................. Be strict about output during tests --disallow-test-output ................. Be strict about output during tests
--enforce-time-limit ................. Enforce time limit based on test size --enforce-time-limit ................. Enforce time limit based on test size
--default-time-limit [sec] Timeout in seconds for tests that have no declared 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-defect ... Stop after first error, failure, warning, or risky test
--stop-on-error ..................................... Stop after first error --stop-on-error ..................................... Stop after first error
--stop-on-failure ................................. Stop after first failure --stop-on-failure ................................. Stop after first failure
@ -68,9 +68,20 @@
--fail-on-risky Signal failure using shell exit code when a test was considered risky --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-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-deprecation Signal failure using shell exit code when a PHPUnit deprecation 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-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-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-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-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 --cache-result ............................ Write test results to cache file
--do-not-cache-result .............. Do not 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 --order-by [order] Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size
@ -91,6 +102,7 @@
--display-errors ............. Display details for errors triggered by tests --display-errors ............. Display details for errors triggered by tests
--display-notices ........... Display details for notices triggered by tests --display-notices ........... Display details for notices triggered by tests
--display-warnings ......... Display details for warnings 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 --reverse-list .............................. Print defects in reverse order
--teamcity . Replace default progress and result output with TeamCity format --teamcity . Replace default progress and result output with TeamCity format
--testdox ................ Replace default result output with TestDox format --testdox ................ Replace default result output with TestDox format

View File

@ -1,3 +1,3 @@
Pest Testing Framework 3.7.3. Pest Testing Framework 3.8.5.

View File

@ -1428,16 +1428,6 @@
PASS Tests\Hooks\BeforeEachTest PASS Tests\Hooks\BeforeEachTest
✓ global beforeEach execution order ✓ 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 PASS Tests\PHPUnit\CustomAffixes\InvalidTestName
✓ it runs file names like @#$%^&()-_=+.php ✓ it runs file names like @#$%^&()-_=+.php
@ -1708,4 +1698,4 @@
WARN Tests\Visual\Version WARN Tests\Visual\Version
- visual snapshot of help command output - visual snapshot of help command output
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1152 passed (2744 assertions) Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 38 todos, 33 skipped, 1144 passed (2736 assertions)

View File

@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
use Pest\Bootstrappers\BootOverrides;
test('versions', function (string $vendorPath, string $expectedHash) {
expect(hash_file('sha256', $vendorPath))->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];
}
});

View File

@ -16,7 +16,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) 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'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();