Compare commits

..

2 Commits
5.x ... 4.x

Author SHA1 Message Date
f7015fe59c chore: adjusts sponsors 2026-02-24 10:44:48 +00:00
7281e0ded7 Add SerpApi to the list of sponsors
Add SerpApi as a sponsor in the README.
2026-02-20 01:18:43 +00:00
19 changed files with 82 additions and 48 deletions

View File

@ -2,7 +2,7 @@ name: Static Analysis
on: on:
push: push:
branches: [5.x] branches: [4.x]
pull_request: pull_request:
schedule: schedule:
- cron: '0 0 * * *' - cron: '0 0 * * *'
@ -29,7 +29,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: 8.4 php-version: 8.3
tools: composer:v2 tools: composer:v2
coverage: none coverage: none
extensions: sockets extensions: sockets
@ -43,10 +43,10 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
key: static-php-8.4-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json') }} key: static-php-8.3-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: | restore-keys: |
static-php-8.4-${{ matrix.dependency-version }}-composer- static-php-8.3-${{ matrix.dependency-version }}-composer-
static-php-8.4-composer- static-php-8.3-composer-
- name: Install Dependencies - name: Install Dependencies
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi

View File

@ -2,7 +2,7 @@ name: Tests
on: on:
push: push:
branches: [5.x] branches: [4.x]
pull_request: pull_request:
concurrency: concurrency:
@ -18,9 +18,12 @@ jobs:
fail-fast: true fail-fast: true
matrix: matrix:
os: [ubuntu-latest, macos-latest] # windows-latest os: [ubuntu-latest, macos-latest] # windows-latest
symfony: ['8.0'] symfony: ['7.4', '8.0']
php: ['8.4', '8.5'] php: ['8.3', '8.4', '8.5']
dependency_version: [prefer-stable] dependency_version: [prefer-stable]
exclude:
- php: '8.3'
symfony: '8.0'
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

@ -32,8 +32,8 @@ We cannot thank our sponsors enough for their incredible support in funding Pest
### Platinum Sponsors ### Platinum Sponsors
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)** - **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
- **[Devin](https://devin.ai/?ref=nunomaduro)**
- **[Mailtrap](https://l.rw.rw/pestphp)** - **[Mailtrap](https://l.rw.rw/pestphp)**
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
- **[Tighten](https://tighten.com/?ref=nunomaduro)** - **[Tighten](https://tighten.com/?ref=nunomaduro)**
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)** - **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**

View File

@ -17,20 +17,20 @@
} }
], ],
"require": { "require": {
"php": "^8.4", "php": "^8.3.0",
"brianium/paratest": "^7.19.0", "brianium/paratest": "^7.19.0",
"nunomaduro/collision": "^8.9.1", "nunomaduro/collision": "^8.9.0",
"nunomaduro/termwind": "^2.4.0", "nunomaduro/termwind": "^2.4.0",
"pestphp/pest-plugin": "^5.0.0", "pestphp/pest-plugin": "^4.0.0",
"pestphp/pest-plugin-arch": "^5.0.0", "pestphp/pest-plugin-arch": "^4.0.0",
"pestphp/pest-plugin-mutate": "^5.0.0", "pestphp/pest-plugin-mutate": "^4.0.1",
"pestphp/pest-plugin-profanity": "^5.0.0", "pestphp/pest-plugin-profanity": "^4.2.1",
"phpunit/phpunit": "^13.0.3", "phpunit/phpunit": "^12.5.12",
"symfony/process": "^8.0.5" "symfony/process": "^7.4.5|^8.0.5"
}, },
"conflict": { "conflict": {
"filp/whoops": "<2.18.3", "filp/whoops": "<2.18.3",
"phpunit/phpunit": ">13.0.3", "phpunit/phpunit": ">12.5.12",
"sebastian/exporter": "<7.0.0", "sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0" "webmozart/assert": "<1.11.0"
}, },
@ -55,9 +55,9 @@
] ]
}, },
"require-dev": { "require-dev": {
"pestphp/pest-dev-tools": "^5.0.0", "pestphp/pest-dev-tools": "^4.1.0",
"pestphp/pest-plugin-browser": "^5.0.0", "pestphp/pest-plugin-browser": "^4.3.0",
"pestphp/pest-plugin-type-coverage": "^5.0.0", "pestphp/pest-plugin-type-coverage": "^4.0.3",
"psy/psysh": "^0.12.20" "psy/psysh": "^0.12.20"
}, },
"minimum-stability": "dev", "minimum-stability": "dev",

View File

@ -33,7 +33,7 @@ final readonly class Configuration
*/ */
public function in(string ...$targets): UsesCall public function in(string ...$targets): UsesCall
{ {
return new UsesCall($this->filename, [])->in(...$targets); return (new UsesCall($this->filename, []))->in(...$targets);
} }
/** /**
@ -60,7 +60,7 @@ final readonly class Configuration
*/ */
public function group(string ...$groups): UsesCall public function group(string ...$groups): UsesCall
{ {
return new UsesCall($this->filename, [])->group(...$groups); return (new UsesCall($this->filename, []))->group(...$groups);
} }
/** /**
@ -68,7 +68,7 @@ final readonly class Configuration
*/ */
public function only(): void public function only(): void
{ {
new BeforeEachCall(TestSuite::getInstance(), $this->filename)->only(); (new BeforeEachCall(TestSuite::getInstance(), $this->filename))->only();
} }
/** /**

View File

@ -237,7 +237,7 @@ final class Expectation
if ($callbacks[$index] instanceof Closure) { if ($callbacks[$index] instanceof Closure) {
$callbacks[$index](new self($value), new self($key)); $callbacks[$index](new self($value), new self($key));
} else { } else {
new self($value)->toEqual($callbacks[$index]); (new self($value))->toEqual($callbacks[$index]);
} }
$index = isset($callbacks[$index + 1]) ? $index + 1 : 0; $index = isset($callbacks[$index + 1]) ? $index + 1 : 0;
@ -864,7 +864,15 @@ final class Expectation
return Targeted::make( return Targeted::make(
$this, $this,
fn (ObjectDescription $object): bool => array_all($interfaces, fn (string $interface): bool => isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface)), function (ObjectDescription $object) use ($interfaces): bool {
foreach ($interfaces as $interface) {
if (! isset($object->reflectionClass) || ! $object->reflectionClass->implementsInterface($interface)) {
return false;
}
}
return true;
},
"to implement '".implode("', '", $interfaces)."'", "to implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -1079,8 +1087,8 @@ final class Expectation
$this, $this,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) fn (ObjectDescription $object): bool => isset($object->reflectionClass)
&& $object->reflectionClass->isEnum() && $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',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );

View File

@ -576,7 +576,15 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$original, $original,
fn (ObjectDescription $object): bool => array_all($traits, fn (string $trait): bool => ! (isset($object->reflectionClass) && in_array($trait, $object->reflectionClass->getTraitNames(), true))), function (ObjectDescription $object) use ($traits): bool {
foreach ($traits as $trait) {
if (isset($object->reflectionClass) && in_array($trait, $object->reflectionClass->getTraitNames(), true)) {
return false;
}
}
return true;
},
"not to use traits '".implode("', '", $traits)."'", "not to use traits '".implode("', '", $traits)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -596,7 +604,15 @@ final readonly class OppositeExpectation
return Targeted::make( return Targeted::make(
$original, $original,
fn (ObjectDescription $object): bool => array_all($interfaces, fn (string $interface): bool => ! (isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface))), function (ObjectDescription $object) use ($interfaces): bool {
foreach ($interfaces as $interface) {
if (isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface)) {
return false;
}
}
return true;
},
"not to implement '".implode("', '", $interfaces)."'", "not to implement '".implode("', '", $interfaces)."'",
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );
@ -836,8 +852,8 @@ final readonly class OppositeExpectation
$original, $original,
fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false fn (ObjectDescription $object): bool => isset($object->reflectionClass) === false
|| ! $object->reflectionClass->isEnum() || ! $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',
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')), FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
); );

View File

@ -191,7 +191,7 @@ final class TestCaseFactory
if ( if (
$method->closure instanceof \Closure && $method->closure instanceof \Closure &&
new \ReflectionFunction($method->closure)->isStatic() (new \ReflectionFunction($method->closure))->isStatic()
) { ) {
throw new TestClosureMustNotBeStatic($method); throw new TestClosureMustNotBeStatic($method);

View File

@ -921,7 +921,7 @@ final class Expectation
if ($exception instanceof Closure) { if ($exception instanceof Closure) {
$callback = $exception; $callback = $exception;
$parameters = new ReflectionFunction($exception)->getParameters(); $parameters = (new ReflectionFunction($exception))->getParameters();
if (count($parameters) !== 1) { if (count($parameters) !== 1) {
throw new InvalidArgumentException('The given closure must have a single parameter type-hinted as the class string.'); throw new InvalidArgumentException('The given closure must have a single parameter type-hinted as the class string.');

View File

@ -53,7 +53,9 @@ final class UsesCall
$this->targets = [$filename]; $this->targets = [$filename];
} }
#[\Deprecated(message: 'Use `pest()->printer()->compact()` instead.')] /**
* @deprecated Use `pest()->printer()->compact()` instead.
*/
public function compact(): self public function compact(): self
{ {
DefaultPrinter::compact(true); DefaultPrinter::compact(true);

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string function version(): string
{ {
return '5.0.0-rc.1'; return '4.4.1';
} }
function testDirectory(string $file = ''): string function testDirectory(string $file = ''): string

View File

@ -176,7 +176,13 @@ final class Parallel implements HandlesArguments
{ {
$arguments = new ArgvInput; $arguments = new ArgvInput;
return array_any(self::UNSUPPORTED_ARGUMENTS, fn (string|array $unsupportedArgument): bool => $arguments->hasParameterOption($unsupportedArgument)); foreach (self::UNSUPPORTED_ARGUMENTS as $unsupportedArgument) {
if ($arguments->hasParameterOption($unsupportedArgument)) {
return true;
}
}
return false;
} }
/** /**

View File

@ -83,11 +83,11 @@ final class Shard implements AddsOutput, HandlesArguments
*/ */
private function allTests(array $arguments): array private function allTests(array $arguments): array
{ {
$output = new Process([ $output = (new Process([
'php', 'php',
...$this->removeParallelArguments($arguments), ...$this->removeParallelArguments($arguments),
'--list-tests', '--list-tests',
])->mustRun()->getOutput(); ]))->mustRun()->getOutput();
preg_match_all('/ - (?:P\\\\)?(Tests\\\\[^:]+)::/', $output, $matches); preg_match_all('/ - (?:P\\\\)?(Tests\\\\[^:]+)::/', $output, $matches);

View File

@ -50,7 +50,7 @@ final class HigherOrderMessage
} }
if ($this->hasHigherOrderCallable()) { if ($this->hasHigherOrderCallable()) {
return new HigherOrderCallables($target)->{$this->name}(...$this->arguments); return (new HigherOrderCallables($target))->{$this->name}(...$this->arguments);
} }
try { try {

View File

@ -31,7 +31,7 @@ final class HigherOrderMessageCollection
*/ */
public function addWhen(callable $condition, string $filename, int $line, string $name, ?array $arguments): void public function addWhen(callable $condition, string $filename, int $line, string $name, ?array $arguments): void
{ {
$this->messages[] = new HigherOrderMessage($filename, $line, $name, $arguments)->when($condition); $this->messages[] = (new HigherOrderMessage($filename, $line, $name, $arguments))->when($condition);
} }
/** /**

View File

@ -38,7 +38,7 @@ final class HigherOrderTapProxy
return $this->target->{$property}; return $this->target->{$property};
} }
$className = new ReflectionClass($this->target)->getName(); $className = (new ReflectionClass($this->target))->getName();
if (str_starts_with($className, 'P\\')) { if (str_starts_with($className, 'P\\')) {
$className = substr($className, 2); $className = substr($className, 2);
@ -60,7 +60,7 @@ final class HigherOrderTapProxy
$filename = Backtrace::file(); $filename = Backtrace::file();
$line = Backtrace::line(); $line = Backtrace::line();
return new HigherOrderMessage($filename, $line, $methodName, $arguments) return (new HigherOrderMessage($filename, $line, $methodName, $arguments))
->call($this->target); ->call($this->target);
} }
} }

View File

@ -180,7 +180,7 @@ final class Reflection
*/ */
public static function getFunctionArguments(Closure $function): array public static function getFunctionArguments(Closure $function): array
{ {
$parameters = new ReflectionFunction($function)->getParameters(); $parameters = (new ReflectionFunction($function))->getParameters();
$arguments = []; $arguments = [];
foreach ($parameters as $parameter) { foreach ($parameters as $parameter) {
@ -206,7 +206,7 @@ final class Reflection
public static function getFunctionVariable(Closure $function, string $key): mixed public static function getFunctionVariable(Closure $function, string $key): mixed
{ {
return new ReflectionFunction($function)->getStaticVariables()[$key] ?? null; return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null;
} }
/** /**

View File

@ -1,5 +1,5 @@
Pest Testing Framework 5.0.0-rc.1. Pest Testing Framework 4.4.1.
USAGE: pest <file> [options] USAGE: pest <file> [options]
@ -44,7 +44,6 @@
--filter [pattern] ............................... Filter which tests to run --filter [pattern] ............................... Filter which tests to run
--exclude-filter [pattern] .. Exclude tests for the specified filter pattern --exclude-filter [pattern] .. Exclude tests for the specified filter pattern
--test-suffix [suffixes] Only search for test in files with specified suffix(es). Default: Test.php,.phpt --test-suffix [suffixes] Only search for test in files with specified suffix(es). Default: Test.php,.phpt
--test-files-file [file] Only run test files listed in file (one file by line)
EXECUTION OPTIONS: EXECUTION OPTIONS:
--parallel ........................................... Run tests in parallel --parallel ........................................... Run tests in parallel

View File

@ -1,3 +1,3 @@
Pest Testing Framework 5.0.0-rc.1. Pest Testing Framework 4.4.1.