Compare commits

..

21 Commits

Author SHA1 Message Date
8871a6f5ef chore: bumps dependencies 2026-03-10 21:04:33 +00:00
e4b6fc08b6 Release 3.8.6 2026-03-10 21:03:11 +00:00
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
a5317c5640 fix: add ignoring clause for App\Features\Concerns on Laravel Preset 2025-02-08 00:07:21 +07:00
1ac594bdf0 Add Attributes to Laravel preset 2024-12-06 16:07:59 +05:00
46 changed files with 179 additions and 178 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

@ -35,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.8.3", "brianium/paratest": "^7.8.5",
"nunomaduro/collision": "^8.8.0", "nunomaduro/collision": "^8.9.1",
"nunomaduro/termwind": "^2.3.0", "nunomaduro/termwind": "^2.4.0",
"pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin": "^3.0.0",
"pestphp/pest-plugin-arch": "^3.1.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.15" "phpunit/phpunit": "^11.5.50"
}, },
"conflict": { "conflict": {
"filp/whoops": "<2.16.0", "filp/whoops": "<2.16.0",
"phpunit/phpunit": ">11.5.15", "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"
}, },
@ -54,8 +54,8 @@
}, },
"require-dev": { "require-dev": {
"pestphp/pest-dev-tools": "^3.4.0", "pestphp/pest-dev-tools": "^3.4.0",
"pestphp/pest-plugin-type-coverage": "^3.5.0", "pestphp/pest-plugin-type-coverage": "^3.6.1",
"symfony/process": "^7.2.5" "symfony/process": "^7.4.5"
}, },
"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

@ -14,6 +14,9 @@ namespace PHPUnit\Logging\JUnit;
use DOMDocument; use DOMDocument;
use DOMElement; use DOMElement;
use Pest\Logging\Converter;
use Pest\Support\Container;
use Pest\TestSuite;
use PHPUnit\Event\Code\Test; use PHPUnit\Event\Code\Test;
use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\Code\TestMethod;
use PHPUnit\Event\EventFacadeIsSealedException; use PHPUnit\Event\EventFacadeIsSealedException;
@ -50,7 +53,7 @@ final class JunitXmlLogger
{ {
private readonly Printer $printer; private readonly Printer $printer;
private readonly \Pest\Logging\Converter $converter; // pest-added private readonly Converter $converter; // pest-added
private DOMDocument $document; private DOMDocument $document;
@ -108,7 +111,7 @@ final class JunitXmlLogger
public function __construct(Printer $printer, Facade $facade) public function __construct(Printer $printer, Facade $facade)
{ {
$this->printer = $printer; $this->printer = $printer;
$this->converter = new \Pest\Logging\Converter(\Pest\Support\Container::getInstance()->get(\Pest\TestSuite::class)->rootPath); // pest-added $this->converter = new Converter(Container::getInstance()->get(TestSuite::class)->rootPath); // pest-added
$this->registerSubscribers($facade); $this->registerSubscribers($facade);
$this->createDocument(); $this->createDocument();

View File

@ -49,7 +49,7 @@ use const DIRECTORY_SEPARATOR;
use const LOCK_EX; 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;
@ -98,28 +98,28 @@ 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 public function mergeWith(self $other): void
@ -179,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 = [

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

@ -66,6 +66,6 @@ trait Pipeable
*/ */
private function pipes(string $name, object $context, string $scope): array private function pipes(string $name, object $context, string $scope): array
{ {
return array_map(fn (Closure $pipe): \Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []); return array_map(fn (Closure $pipe): Closure => $pipe->bindTo($context, $scope), self::$pipes[$name] ?? []);
} }
} }

View File

@ -148,7 +148,7 @@ trait Testable
*/ */
public function __addBeforeAll(?Closure $hook): void public function __addBeforeAll(?Closure $hook): void
{ {
if (! $hook instanceof \Closure) { if (! $hook instanceof Closure) {
return; return;
} }
@ -162,7 +162,7 @@ trait Testable
*/ */
public function __addAfterAll(?Closure $hook): void public function __addAfterAll(?Closure $hook): void
{ {
if (! $hook instanceof \Closure) { if (! $hook instanceof Closure) {
return; return;
} }
@ -192,7 +192,7 @@ trait Testable
*/ */
private function __addHook(string $property, ?Closure $hook): void private function __addHook(string $property, ?Closure $hook): void
{ {
if (! $hook instanceof \Closure) { if (! $hook instanceof Closure) {
return; return;
} }

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

@ -134,7 +134,7 @@ final class Expectation
/** /**
* Dump the expectation value when the result of the condition is truthy. * Dump the expectation value when the result of the condition is truthy.
* *
* @param (\Closure(TValue): bool)|bool $condition * @param (Closure(TValue): bool)|bool $condition
* @return self<TValue> * @return self<TValue>
*/ */
public function ddWhen(Closure|bool $condition, mixed ...$arguments): Expectation public function ddWhen(Closure|bool $condition, mixed ...$arguments): Expectation
@ -151,7 +151,7 @@ final class Expectation
/** /**
* Dump the expectation value when the result of the condition is falsy. * Dump the expectation value when the result of the condition is falsy.
* *
* @param (\Closure(TValue): bool)|bool $condition * @param (Closure(TValue): bool)|bool $condition
* @return self<TValue> * @return self<TValue>
*/ */
public function ddUnless(Closure|bool $condition, mixed ...$arguments): Expectation public function ddUnless(Closure|bool $condition, mixed ...$arguments): Expectation
@ -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

@ -193,7 +193,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -209,7 +209,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -225,7 +225,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -249,7 +249,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -271,7 +271,7 @@ final readonly class OppositeExpectation
$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')),
@ -436,7 +436,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -484,7 +484,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -508,7 +508,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -524,7 +524,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -554,7 +554,7 @@ final readonly class OppositeExpectation
$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;
} }
} }
@ -582,7 +582,7 @@ final readonly class OppositeExpectation
$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;
} }
} }
@ -604,7 +604,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -628,7 +628,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -644,7 +644,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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')),
); );
@ -715,7 +715,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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'))
); );
@ -731,7 +731,7 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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'))
); );
@ -826,7 +826,8 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$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

@ -17,6 +17,7 @@ use Pest\Factories\Concerns\HigherOrderable;
use Pest\Support\Reflection; use Pest\Support\Reflection;
use Pest\Support\Str; use Pest\Support\Str;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use RuntimeException; use RuntimeException;
@ -135,7 +136,7 @@ final class TestCaseFactory
$this->attributes = [ $this->attributes = [
new Attribute( new Attribute(
\PHPUnit\Framework\Attributes\TestDox::class, TestDox::class,
[$this->filename], [$this->filename],
), ),
...$this->attributes, ...$this->attributes,

View File

@ -13,6 +13,9 @@ use Pest\Support\Str;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\Assert; use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@ -192,11 +195,11 @@ final class TestCaseMethodFactory
$this->attributes = [ $this->attributes = [
new Attribute( new Attribute(
\PHPUnit\Framework\Attributes\Test::class, Test::class,
[], [],
), ),
new Attribute( new Attribute(
\PHPUnit\Framework\Attributes\TestDox::class, TestDox::class,
[str_replace('*/', '{@*}', $this->description)], [str_replace('*/', '{@*}', $this->description)],
), ),
...$this->attributes, ...$this->attributes,
@ -206,7 +209,7 @@ final class TestCaseMethodFactory
$depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend)); $depend = Str::evaluable($this->describing === [] ? $depend : Str::describe($this->describing, $depend));
$this->attributes[] = new Attribute( $this->attributes[] = new Attribute(
\PHPUnit\Framework\Attributes\Depends::class, Depends::class,
[$depend], [$depend],
); );
} }

View File

@ -136,7 +136,7 @@ if (! function_exists('test')) {
*/ */
function test(?string $description = null, ?Closure $closure = null): HigherOrderTapProxy|TestCall function test(?string $description = null, ?Closure $closure = null): HigherOrderTapProxy|TestCall
{ {
if ($description === null && TestSuite::getInstance()->test instanceof \PHPUnit\Framework\TestCase) { if ($description === null && TestSuite::getInstance()->test instanceof TestCase) {
return new HigherOrderTapProxy(TestSuite::getInstance()->test); return new HigherOrderTapProxy(TestSuite::getInstance()->test);
} }
@ -232,7 +232,7 @@ if (! function_exists('covers')) {
/** @var MutationTestRunner $runner */ /** @var MutationTestRunner $runner */
$runner = Container::getInstance()->get(MutationTestRunner::class); $runner = Container::getInstance()->get(MutationTestRunner::class);
/** @var \Pest\Mutate\Repositories\ConfigurationRepository $configurationRepository */ /** @var ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class); $configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false; $everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false; $classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;
@ -259,7 +259,7 @@ if (! function_exists('mutates')) {
/** @var MutationTestRunner $runner */ /** @var MutationTestRunner $runner */
$runner = Container::getInstance()->get(MutationTestRunner::class); $runner = Container::getInstance()->get(MutationTestRunner::class);
/** @var \Pest\Mutate\Repositories\ConfigurationRepository $configurationRepository */ /** @var ConfigurationRepository $configurationRepository */
$configurationRepository = Container::getInstance()->get(ConfigurationRepository::class); $configurationRepository = Container::getInstance()->get(ConfigurationRepository::class);
$everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false; $everything = $configurationRepository->cliConfiguration->toArray()['everything'] ?? false;
$classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false; $classes = $configurationRepository->cliConfiguration->toArray()['classes'] ?? false;

View File

@ -151,7 +151,7 @@ final readonly class Converter
{ {
if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) { if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) {
$firstTest = $this->getFirstTest($testSuite); $firstTest = $this->getFirstTest($testSuite);
if ($firstTest instanceof \PHPUnit\Event\Code\TestMethod) { if ($firstTest instanceof TestMethod) {
return $this->getTestMethodNameWithoutDatasetSuffix($firstTest); return $this->getTestMethodNameWithoutDatasetSuffix($firstTest);
} }
} }
@ -179,7 +179,7 @@ final readonly class Converter
public function getTestSuiteLocation(TestSuite $testSuite): ?string public function getTestSuiteLocation(TestSuite $testSuite): ?string
{ {
$firstTest = $this->getFirstTest($testSuite); $firstTest = $this->getFirstTest($testSuite);
if (! $firstTest instanceof \PHPUnit\Event\Code\TestMethod) { if (! $firstTest instanceof TestMethod) {
return null; return null;
} }
$path = $firstTest->testDox()->prettifiedClassName(); $path = $firstTest->testDox()->prettifiedClassName();

View File

@ -200,7 +200,7 @@ final class TeamCityLogger
public function testFinished(Finished $event): void public function testFinished(Finished $event): void
{ {
if (! $this->time instanceof \PHPUnit\Event\Telemetry\HRTime) { if (! $this->time instanceof HRTime) {
throw ShouldNotHappen::fromMessage('Start time has not been set.'); throw ShouldNotHappen::fromMessage('Start time has not been set.');
} }
@ -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

@ -9,6 +9,7 @@ use Closure;
use Countable; use Countable;
use DateTimeInterface; use DateTimeInterface;
use Error; use Error;
use Illuminate\Testing\TestResponse;
use InvalidArgumentException; use InvalidArgumentException;
use JsonSerializable; use JsonSerializable;
use Pest\Exceptions\InvalidExpectationValue; use Pest\Exceptions\InvalidExpectationValue;
@ -846,7 +847,7 @@ final class Expectation
is_object($this->value) && method_exists($this->value, 'toSnapshot') => $this->value->toSnapshot(), is_object($this->value) && method_exists($this->value, 'toSnapshot') => $this->value->toSnapshot(),
is_object($this->value) && method_exists($this->value, '__toString') => $this->value->__toString(), is_object($this->value) && method_exists($this->value, '__toString') => $this->value->__toString(),
is_object($this->value) && method_exists($this->value, 'toString') => $this->value->toString(), is_object($this->value) && method_exists($this->value, 'toString') => $this->value->toString(),
$this->value instanceof \Illuminate\Testing\TestResponse => $this->value->getContent(), // @phpstan-ignore-line $this->value instanceof TestResponse => $this->value->getContent(), // @phpstan-ignore-line
is_array($this->value) => json_encode($this->value, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT), is_array($this->value) => json_encode($this->value, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
$this->value instanceof Traversable => json_encode(iterator_to_array($this->value), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT), $this->value instanceof Traversable => json_encode(iterator_to_array($this->value), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
$this->value instanceof JsonSerializable => json_encode($this->value->jsonSerialize(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT), $this->value instanceof JsonSerializable => json_encode($this->value->jsonSerialize(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT),
@ -987,7 +988,7 @@ final class Expectation
*/ */
private function export(mixed $value): string private function export(mixed $value): string
{ {
if (! $this->exporter instanceof \Pest\Support\Exporter) { if (! $this->exporter instanceof Exporter) {
$this->exporter = Exporter::default(); $this->exporter = Exporter::default();
} }

View File

@ -72,7 +72,7 @@ final class DescribeCall
{ {
$filename = Backtrace::file(); $filename = Backtrace::file();
if (! $this->currentBeforeEachCall instanceof \Pest\PendingCalls\BeforeEachCall) { if (! $this->currentBeforeEachCall instanceof BeforeEachCall) {
$this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename); $this->currentBeforeEachCall = new BeforeEachCall(TestSuite::getInstance(), $filename);
$this->currentBeforeEachCall->describing[] = $this->description; $this->currentBeforeEachCall->describing[] = $this->description;

View File

@ -21,6 +21,11 @@ use Pest\Support\NullClosure;
use Pest\Support\Str; use Pest\Support\Str;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\CoversTrait;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@ -181,7 +186,7 @@ final class TestCall // @phpstan-ignore-line
* Runs the current test multiple times with * Runs the current test multiple times with
* each item of the given `iterable`. * each item of the given `iterable`.
* *
* @param array<\Closure|iterable<int|string, mixed>|string> $data * @param array<Closure|iterable<int|string, mixed>|string> $data
*/ */
public function with(Closure|iterable|string ...$data): self public function with(Closure|iterable|string ...$data): self
{ {
@ -211,7 +216,7 @@ final class TestCall // @phpstan-ignore-line
{ {
foreach ($groups as $group) { foreach ($groups as $group) {
$this->testCaseMethod->attributes[] = new Attribute( $this->testCaseMethod->attributes[] = new Attribute(
\PHPUnit\Framework\Attributes\Group::class, Group::class,
[$group], [$group],
); );
} }
@ -549,7 +554,7 @@ final class TestCall // @phpstan-ignore-line
{ {
foreach ($classes as $class) { foreach ($classes as $class) {
$this->testCaseFactoryAttributes[] = new Attribute( $this->testCaseFactoryAttributes[] = new Attribute(
\PHPUnit\Framework\Attributes\CoversClass::class, CoversClass::class,
[$class], [$class],
); );
} }
@ -572,7 +577,7 @@ final class TestCall // @phpstan-ignore-line
{ {
foreach ($traits as $trait) { foreach ($traits as $trait) {
$this->testCaseFactoryAttributes[] = new Attribute( $this->testCaseFactoryAttributes[] = new Attribute(
\PHPUnit\Framework\Attributes\CoversTrait::class, CoversTrait::class,
[$trait], [$trait],
); );
} }
@ -595,7 +600,7 @@ final class TestCall // @phpstan-ignore-line
{ {
foreach ($functions as $function) { foreach ($functions as $function) {
$this->testCaseFactoryAttributes[] = new Attribute( $this->testCaseFactoryAttributes[] = new Attribute(
\PHPUnit\Framework\Attributes\CoversFunction::class, CoversFunction::class,
[$function], [$function],
); );
} }
@ -609,7 +614,7 @@ final class TestCall // @phpstan-ignore-line
public function coversNothing(): self public function coversNothing(): self
{ {
$this->testCaseMethod->attributes[] = new Attribute( $this->testCaseMethod->attributes[] = new Attribute(
\PHPUnit\Framework\Attributes\CoversNothing::class, CoversNothing::class,
[], [],
); );

View File

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

View File

@ -7,6 +7,7 @@ namespace Pest\Plugins\Parallel\Handlers;
use Closure; use Closure;
use Composer\InstalledVersions; use Composer\InstalledVersions;
use Illuminate\Testing\ParallelRunner; use Illuminate\Testing\ParallelRunner;
use Orchestra\Testbench\TestCase;
use ParaTest\Options; use ParaTest\Options;
use ParaTest\RunnerInterface; use ParaTest\RunnerInterface;
use Pest\Contracts\Plugins\HandlesArguments; use Pest\Contracts\Plugins\HandlesArguments;
@ -39,13 +40,13 @@ final class Laravel implements HandlesArguments
* Executes the given closure when running Laravel. * Executes the given closure when running Laravel.
* *
* @param array<int, string> $arguments * @param array<int, string> $arguments
* @param CLosure(array<int, string>): array<int, string> $closure * @param Closure(array<int, string>): array<int, string> $closure
* @return array<int, string> * @return array<int, string>
*/ */
private function whenUsingLaravel(array $arguments, Closure $closure): array private function whenUsingLaravel(array $arguments, Closure $closure): array
{ {
$isLaravelApplication = InstalledVersions::isInstalled('laravel/framework', false); $isLaravelApplication = InstalledVersions::isInstalled('laravel/framework', false);
$isLaravelPackage = class_exists(\Orchestra\Testbench\TestCase::class); $isLaravelPackage = class_exists(TestCase::class);
if ($isLaravelApplication && ! $isLaravelPackage) { if ($isLaravelApplication && ! $isLaravelPackage) {
return $closure($arguments); return $closure($arguments);

View File

@ -200,6 +200,7 @@ final class DatasetsRepository
throw new DatasetDoesNotExist($name); throw new DatasetDoesNotExist($name);
} }
// @phpstan-ignore-next-line
return $matchingDatasets[$closestScopeDatasetKey]; return $matchingDatasets[$closestScopeDatasetKey];
} }

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

@ -19,14 +19,14 @@ final class Closure
*/ */
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
{ {
if (! $closure instanceof \Closure) { if (! $closure instanceof BaseClosure) {
throw ShouldNotHappen::fromMessage('Could not bind null closure.'); throw ShouldNotHappen::fromMessage('Could not bind null closure.');
} }
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
$closure = BaseClosure::bind($closure, $newThis, $newScope); $closure = BaseClosure::bind($closure, $newThis, $newScope);
if (! $closure instanceof \Closure) { if (! $closure instanceof BaseClosure) {
throw ShouldNotHappen::fromMessage('Could not bind closure.'); throw ShouldNotHappen::fromMessage('Could not bind closure.');
} }

View File

@ -28,7 +28,7 @@ final class Container
*/ */
public static function getInstance(): self public static function getInstance(): self
{ {
if (! self::$instance instanceof \Pest\Support\Container) { if (! self::$instance instanceof Container) {
self::$instance = new self; self::$instance = new self;
} }

View File

@ -46,6 +46,7 @@ final readonly class HigherOrderCallables
*/ */
public function and(mixed $value): Expectation public function and(mixed $value): Expectation
{ {
// @phpstan-ignore-next-line
return $this->expect($value); return $this->expect($value);
} }

View File

@ -8,6 +8,7 @@ use Closure;
use InvalidArgumentException; use InvalidArgumentException;
use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\ShouldNotHappen;
use Pest\TestSuite; use Pest\TestSuite;
use PHPUnit\Framework\TestCase;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
use ReflectionFunction; use ReflectionFunction;
@ -34,8 +35,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')) {
@ -68,7 +67,7 @@ final class Reflection
{ {
$test = TestSuite::getInstance()->test; $test = TestSuite::getInstance()->test;
if (! $test instanceof \PHPUnit\Framework\TestCase) { if (! $test instanceof TestCase) {
return self::bindCallable($callable); return self::bindCallable($callable);
} }
@ -113,8 +112,6 @@ final class Reflection
} }
} }
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($object); return $reflectionProperty->getValue($object);
} }
@ -144,8 +141,6 @@ final class Reflection
} }
} }
} }
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, $value); $reflectionProperty->setValue($object, $value);
} }
@ -227,7 +222,7 @@ final class Reflection
{ {
$getProperties = fn (ReflectionClass $reflectionClass): array => array_filter( $getProperties = fn (ReflectionClass $reflectionClass): array => array_filter(
array_map( array_map(
fn (ReflectionProperty $property): \ReflectionProperty => $property, fn (ReflectionProperty $property): ReflectionProperty => $property,
$reflectionClass->getProperties(), $reflectionClass->getProperties(),
), fn (ReflectionProperty $property): bool => $property->getDeclaringClass()->getName() === $reflectionClass->getName(), ), fn (ReflectionProperty $property): bool => $property->getDeclaringClass()->getName() === $reflectionClass->getName(),
); );
@ -262,7 +257,7 @@ final class Reflection
{ {
$getMethods = fn (ReflectionClass $reflectionClass): array => array_filter( $getMethods = fn (ReflectionClass $reflectionClass): array => array_filter(
array_map( array_map(
fn (ReflectionMethod $method): \ReflectionMethod => $method, fn (ReflectionMethod $method): ReflectionMethod => $method,
$reflectionClass->getMethods($filter), $reflectionClass->getMethods($filter),
), fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $reflectionClass->getName(), ), fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $reflectionClass->getName(),
); );

View File

@ -1,5 +1,5 @@
Pest Testing Framework 3.8.1. Pest Testing Framework 3.8.6.
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.8.1. Pest Testing Framework 3.8.6.

View File

@ -1,5 +1,7 @@
<?php <?php
use Pest\Plugin;
trait PluginTrait trait PluginTrait
{ {
public function assertPluginTraitGotRegistered(): void public function assertPluginTraitGotRegistered(): void
@ -16,8 +18,8 @@ trait SecondPluginTrait
} }
} }
Pest\Plugin::uses(PluginTrait::class); Plugin::uses(PluginTrait::class);
Pest\Plugin::uses(SecondPluginTrait::class); Plugin::uses(SecondPluginTrait::class);
function _assertThat() function _assertThat()
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
$foo = new \stdClass; $foo = new stdClass;
$foo->bar = 0; $foo->bar = 0;
beforeAll(function () use ($foo) { beforeAll(function () use ($foo) {

View File

@ -17,7 +17,7 @@ it('adds coverage if --coverage exist', function () {
$arguments = $plugin->handleArguments(['--coverage']); $arguments = $plugin->handleArguments(['--coverage']);
expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()]) expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()])
->and($plugin->coverage)->toBeTrue(); ->and($plugin->coverage)->toBeTrue();
})->skip(! \Pest\Support\Coverage::isAvailable() || ! function_exists('xdebug_info') || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available'); })->skip(! Coverage::isAvailable() || ! function_exists('xdebug_info') || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available');
it('adds coverage if --min exist', function () { it('adds coverage if --min exist', function () {
$plugin = new CoveragePlugin(new ConsoleOutput); $plugin = new CoveragePlugin(new ConsoleOutput);

View File

@ -39,7 +39,7 @@ it('allows to call underlying protected/private methods', function () {
it('throws error if method do not exist', function () { it('throws error if method do not exist', function () {
test()->foo(); test()->foo();
})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::foo()'); })->throws(ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::foo()');
it('can forward unexpected calls to any global function')->_assertThat(); it('can forward unexpected calls to any global function')->_assertThat();

View File

@ -1,7 +1,9 @@
<?php <?php
use PHPUnit\Framework\TestCase;
/** /**
* @return \PHPUnit\Framework\TestCase * @return TestCase
*/ */
function myAssertTrue($value) function myAssertTrue($value)
{ {

View File

@ -1,8 +1,9 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use PHPUnit\Framework\TestCase;
class MyCustomClassTest extends PHPUnit\Framework\TestCase class MyCustomClassTest extends TestCase
{ {
public function assertTrueIsTrue() public function assertTrueIsTrue()
{ {

View File

@ -1,6 +1,8 @@
<?php <?php
pest()->use(Tests\CustomTestCase\CustomTestCase::class)->in(__DIR__); use Tests\CustomTestCase\CustomTestCase;
pest()->use(CustomTestCase::class)->in(__DIR__);
test('closure was bound to CustomTestCase', function () { test('closure was bound to CustomTestCase', function () {
$this->assertCustomTrue(); $this->assertCustomTrue();

View File

@ -1,5 +1,7 @@
<?php <?php
use PHPUnit\Framework\TestCase;
trait MyCustomTrait trait MyCustomTrait
{ {
public function assertFalseIsFalse() public function assertFalseIsFalse()
@ -8,7 +10,7 @@ trait MyCustomTrait
} }
} }
abstract class MyCustomClass extends PHPUnit\Framework\TestCase abstract class MyCustomClass extends TestCase
{ {
public function assertTrueIsTrue() public function assertTrueIsTrue()
{ {

View File

@ -1,7 +1,9 @@
<?php <?php
use Pest\Configuration\Printer;
it('creates a printer instance', function () { it('creates a printer instance', function () {
$theme = pest()->printer(); $theme = pest()->printer();
expect($theme)->toBeInstanceOf(Pest\Configuration\Printer::class); expect($theme)->toBeInstanceOf(Printer::class);
}); });

View File

@ -1,8 +1,10 @@
<?php <?php
use Symfony\Component\Process\Process;
test('collision', function (array $arguments) { test('collision', function (array $arguments) {
$output = function () use ($arguments) { $output = function () use ($arguments) {
$process = (new Symfony\Component\Process\Process( $process = (new Process(
array_merge(['php', 'bin/pest', 'tests/Fixtures/CollisionTest.php'], $arguments), array_merge(['php', 'bin/pest', 'tests/Fixtures/CollisionTest.php'], $arguments),
null, null,
['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'COLLISION_TEST' => true] ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'COLLISION_TEST' => true]

View File

@ -1,8 +1,10 @@
<?php <?php
use Symfony\Component\Process\Process;
test('visual snapshot of help command output', function () { test('visual snapshot of help command output', function () {
$output = function () { $output = function () {
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--help'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'])); $process = (new Process(['php', 'bin/pest', '--help'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
$process->run(); $process->run();

View File

@ -1,5 +1,7 @@
<?php <?php
use Symfony\Component\Process\Process;
test('visual snapshot of test suite on success', function () { test('visual snapshot of test suite on success', function () {
$testsPath = dirname(__DIR__); $testsPath = dirname(__DIR__);
$snapshot = implode(DIRECTORY_SEPARATOR, [ $snapshot = implode(DIRECTORY_SEPARATOR, [
@ -9,7 +11,7 @@ test('visual snapshot of test suite on success', function () {
]); ]);
$output = function () use ($testsPath) { $output = function () use ($testsPath) {
$process = (new Symfony\Component\Process\Process( $process = (new Process(
['php', 'bin/pest'], ['php', 'bin/pest'],
dirname($testsPath), dirname($testsPath),
['EXCLUDE' => 'integration', '--exclude-group' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], ['EXCLUDE' => 'integration', '--exclude-group' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'],

View File

@ -1,5 +1,7 @@
<?php <?php
use Symfony\Component\Process\Process;
function normalize_windows_os_output(string $text): string function normalize_windows_os_output(string $text): string
{ {
$text = str_replace('\r', '', $text); $text = str_replace('\r', '', $text);
@ -17,7 +19,7 @@ test('visual snapshot of team city', function (string $testFile) {
]); ]);
$output = function () use ($testsPath) { $output = function () use ($testsPath) {
$process = (new Symfony\Component\Process\Process( $process = (new Process(
['php', 'bin/pest', '--teamcity', $testsPath], ['php', 'bin/pest', '--teamcity', $testsPath],
dirname(__DIR__, levels: 2), dirname(__DIR__, levels: 2),
[ [

View File

@ -1,8 +1,10 @@
<?php <?php
use Symfony\Component\Process\Process;
test('visual snapshot of help command output', function () { test('visual snapshot of help command output', function () {
$output = function () { $output = function () {
$process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--version'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'])); $process = (new Process(['php', 'bin/pest', '--version'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true']));
$process->run(); $process->run();