refactor: PHP 8 features

This commit is contained in:
Nuno Maduro
2021-10-24 18:29:59 +01:00
parent e8c2fe6e35
commit 2b687a7269
43 changed files with 283 additions and 635 deletions

View File

@ -15,7 +15,7 @@ trait Extendable
/** /**
* @var array<string, Closure> * @var array<string, Closure>
*/ */
private static $extends = []; private static array $extends = [];
/** /**
* Register a custom extend. * Register a custom extend.

View File

@ -13,61 +13,42 @@ use PHPUnit\Framework\ExecutionOrderDependency;
use Throwable; use Throwable;
/** /**
* To avoid inheritance conflicts, all the fields related to Pest only will be prefixed by double underscore.
*
* @internal * @internal
*/ */
trait Testable trait Testable
{ {
/** /**
* The test case description. Contains the first * The Test Case description.
* argument of global functions like `it` and `test`.
*
* @var string
*/ */
private $__description; private string $__description;
/** /**
* Holds the test closure function. * The Test Case "test" closure.
*
* @var Closure
*/ */
private $__test; private Closure $__test;
/** /**
* Holds a global/shared beforeEach ("set up") closure if one has been * The Test Case "setUp" closure.
* defined.
*
* @var Closure|null
*/ */
private $__beforeEach = null; private ?Closure $__beforeEach = null;
/** /**
* Holds a global/shared afterEach ("tear down") closure if one has been * The Test Case "tearDown" closure.
* defined.
*
* @var Closure|null
*/ */
private $__afterEach = null; private ?Closure $__afterEach = null;
/** /**
* Holds a global/shared beforeAll ("set up before") closure if one has been * The Test Case "setUpBeforeClass" closure.
* defined.
*
* @var Closure|null
*/ */
private static $__beforeAll = null; private static ?Closure $__beforeAll = null;
/** /**
* Holds a global/shared afterAll ("tear down after") closure if one has * The test "tearDownAfterClass" closure.
* been defined.
*
* @var Closure|null
*/ */
private static $__afterAll = null; private static ?Closure $__afterAll = null;
/** /**
* Creates a new instance of the test case. * Creates a new Test Case instance.
*/ */
public function __construct(Closure $test, string $description, array $data) public function __construct(Closure $test, string $description, array $data)
{ {
@ -82,7 +63,7 @@ trait Testable
} }
/** /**
* Adds the groups to the current test case. * Adds groups to the Test Case.
*/ */
public function addGroups(array $groups): void public function addGroups(array $groups): void
{ {
@ -92,14 +73,14 @@ trait Testable
} }
/** /**
* Add dependencies to the test case and map them to instances of ExecutionOrderDependency. * Adds dependencies to the Test Case.
*/ */
public function addDependencies(array $tests): void public function addDependencies(array $tests): void
{ {
$className = get_class($this); $className = $this::class;
$tests = array_map(function (string $test) use ($className): ExecutionOrderDependency { $tests = array_map(static function (string $test) use ($className): ExecutionOrderDependency {
if (strpos($test, '::') === false) { if (!str_contains($test, '::')) {
$test = "{$className}::{$test}"; $test = "{$className}::{$test}";
} }
@ -110,8 +91,7 @@ trait Testable
} }
/** /**
* Add a shared/"global" before all test hook that will execute **before** * Adds a new "setUpBeforeClass" to the Test Case.
* the test defined `beforeAll` hook(s).
*/ */
public function __addBeforeAll(?Closure $hook): void public function __addBeforeAll(?Closure $hook): void
{ {
@ -125,8 +105,7 @@ trait Testable
} }
/** /**
* Add a shared/"global" after all test hook that will execute **before** * Adds a new "tearDownAfterClass" to the Test Case.
* the test defined `afterAll` hook(s).
*/ */
public function __addAfterAll(?Closure $hook): void public function __addAfterAll(?Closure $hook): void
{ {
@ -140,8 +119,7 @@ trait Testable
} }
/** /**
* Add a shared/"global" before each test hook that will execute **before** * Adds a new "setUp" to the Test Case.
* the test defined `beforeEach` hook.
*/ */
public function __addBeforeEach(?Closure $hook): void public function __addBeforeEach(?Closure $hook): void
{ {
@ -149,8 +127,7 @@ trait Testable
} }
/** /**
* Add a shared/"global" after each test hook that will execute **before** * Adds a new "tearDown" to the Test Case.
* the test defined `afterEach` hook.
*/ */
public function __addAfterEach(?Closure $hook): void public function __addAfterEach(?Closure $hook): void
{ {
@ -158,7 +135,7 @@ trait Testable
} }
/** /**
* Add a shared/global hook and compose them if more than one is passed. * Adds a new "hook" to the Test Case.
*/ */
private function __addHook(string $property, ?Closure $hook): void private function __addHook(string $property, ?Closure $hook): void
{ {
@ -172,9 +149,7 @@ trait Testable
} }
/** /**
* Returns the test case name. Note that, in Pest * Gets the Test Case name.
* we ignore withDataset argument as the description
* already contains the dataset description.
*/ */
public function getName(bool $withDataSet = true): string public function getName(bool $withDataSet = true): string
{ {
@ -183,13 +158,16 @@ trait Testable
: $this->__description; : $this->__description;
} }
public static function __getFileName(): string /**
* Gets the Test Case filename.
*/
public static function __getFilename(): string
{ {
return self::$__filename; return self::$__filename;
} }
/** /**
* This method is called before the first test of this test class is run. * This method is called before the first test of this Test Case is run.
*/ */
public static function setUpBeforeClass(): void public static function setUpBeforeClass(): void
{ {
@ -205,7 +183,7 @@ trait Testable
} }
/** /**
* This method is called after the last test of this test class is run. * This method is called after the last test of this Test Case is run.
*/ */
public static function tearDownAfterClass(): void public static function tearDownAfterClass(): void
{ {
@ -221,7 +199,7 @@ trait Testable
} }
/** /**
* Gets executed before the test. * Gets executed before the Test Case.
*/ */
protected function setUp(): void protected function setUp(): void
{ {
@ -239,7 +217,7 @@ trait Testable
} }
/** /**
* Gets executed after the test. * Gets executed after the Test Case.
*/ */
protected function tearDown(): void protected function tearDown(): void
{ {
@ -257,7 +235,7 @@ trait Testable
} }
/** /**
* Returns the test case as string. * Gets the Test Case filename and description.
*/ */
public function toString(): string public function toString(): string
{ {
@ -269,13 +247,11 @@ trait Testable
} }
/** /**
* Runs the test. * Executes the Test Case current test.
*
* @return mixed
* *
* @throws Throwable * @throws Throwable
*/ */
public function __test() public function __test(): mixed
{ {
return $this->__callClosure($this->__test, $this->__resolveTestArguments(func_get_args())); return $this->__callClosure($this->__test, $this->__resolveTestArguments(func_get_args()));
} }
@ -287,23 +263,20 @@ trait Testable
*/ */
private function __resolveTestArguments(array $arguments): array private function __resolveTestArguments(array $arguments): array
{ {
return array_map(function ($data) { return array_map(fn ($data) => $data instanceof Closure ? $this->__callClosure($data, []) : $data, $arguments);
return $data instanceof Closure ? $this->__callClosure($data, []) : $data;
}, $arguments);
} }
/** /**
* @return mixed
*
* @throws Throwable * @throws Throwable
*/ */
private function __callClosure(Closure $closure, array $arguments) private function __callClosure(Closure $closure, array $arguments): mixed
{ {
return ExceptionTrace::ensure(function () use ($closure, $arguments) { return ExceptionTrace::ensure(fn () => call_user_func_array(Closure::bind($closure, $this, $this::class), $arguments));
return call_user_func_array(Closure::bind($closure, $this, get_class($this)), $arguments);
});
} }
/**
* Gets the Test Case name that should be used by printers.
*/
public function getPrintableTestCaseName(): string public function getPrintableTestCaseName(): string
{ {
return ltrim(self::class, 'P\\'); return ltrim(self::class, 'P\\');

View File

@ -20,12 +20,9 @@ final class Help
' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)', ' <info>--group=<fg=cyan><name></></info> Only runs tests from the specified group(s)',
]; ];
/** @var OutputInterface */ public function __construct(private OutputInterface $output)
private $output;
public function __construct(OutputInterface $output)
{ {
$this->output = $output; // ..
} }
public function __invoke(): void public function __invoke(): void

View File

@ -25,12 +25,9 @@ final class Thanks
' <options=bold>https://github.com/sponsors/nunomaduro</>', ' <options=bold>https://github.com/sponsors/nunomaduro</>',
]; ];
/** @var OutputInterface */ public function __construct(private OutputInterface $output)
private $output;
public function __construct(OutputInterface $output)
{ {
$this->output = $output; // ..
} }
/** /**

View File

@ -20,14 +20,14 @@ final class Datasets
* *
* @var array<int|string, Closure|iterable<int|string, mixed>> * @var array<int|string, Closure|iterable<int|string, mixed>>
*/ */
private static $datasets = []; private static array $datasets = [];
/** /**
* Sets the given. * Sets the given.
* *
* @param Closure|iterable<int|string, mixed> $data * @param Closure|iterable<int|string, mixed> $data
*/ */
public static function set(string $name, $data): void public static function set(string $name, Closure|iterable $data): void
{ {
if (array_key_exists($name, self::$datasets)) { if (array_key_exists($name, self::$datasets)) {
throw new DatasetAlreadyExist($name); throw new DatasetAlreadyExist($name);
@ -39,7 +39,7 @@ final class Datasets
/** /**
* @return Closure|iterable<int|string, mixed> * @return Closure|iterable<int|string, mixed>
*/ */
public static function get(string $name) public static function get(string $name): Closure|iterable
{ {
if (!array_key_exists($name, self::$datasets)) { if (!array_key_exists($name, self::$datasets)) {
throw new DatasetDoesNotExist($name); throw new DatasetDoesNotExist($name);
@ -161,10 +161,9 @@ final class Datasets
} }
/** /**
* @param int|string $key
* @param array<int, mixed> $data * @param array<int, mixed> $data
*/ */
private static function getDataSetDescription($key, array $data): string private static function getDataSetDescription(int|string $key, array $data): string
{ {
$exporter = new Exporter(); $exporter = new Exporter();

View File

@ -11,22 +11,14 @@ namespace Pest;
*/ */
final class Each final class Each
{ {
/** private bool $opposite = false;
* @var Expectation
*/
private $original;
/**
* @var bool
*/
private $opposite = false;
/** /**
* Creates an expectation on each item of the iterable "value". * Creates an expectation on each item of the iterable "value".
*/ */
public function __construct(Expectation $original) public function __construct(private Expectation $original)
{ {
$this->original = $original; // ..
} }
/** /**

View File

@ -27,9 +27,7 @@ final class DatasetMissing extends BadFunctionCallException implements Exception
"A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s", "A test with the description '%s' has %d argument(s) ([%s]) and no dataset(s) provided in %s",
$name, $name,
count($args), count($args),
implode(', ', array_map(static function (string $arg, string $type): string { implode(', ', array_map(static fn (string $arg, string $type): string => sprintf('%s $%s', $type, $arg), array_keys($args), $args)),
return sprintf('%s $%s', $type, $arg);
}, array_keys($args), $args)),
$file, $file,
)); ));
} }

View File

@ -29,37 +29,26 @@ use Throwable;
*/ */
final class Expectation final class Expectation
{ {
use Extendable { use RetrievesValues, Extendable {
__call as __extendsCall; __call as __extendsCall;
} }
use RetrievesValues;
/**
* The expectation value.
*
* @readonly
*
* @var mixed
*/
public $value;
/** /**
* The exporter instance, if any. * The exporter instance, if any.
* *
* @readonly * @readonly
*
* @var Exporter|null
*/ */
private $exporter; private ?Exporter $exporter = null;
/** /**
* Creates a new expectation. * Creates a new expectation.
* *
* @param TValue $value * @param TValue $value
*/ */
public function __construct($value) public function __construct(
{ public mixed $value
$this->value = $value; ) {
// ..
} }
/** /**
@ -69,7 +58,7 @@ final class Expectation
* *
* @return Expectation<TValue> * @return Expectation<TValue>
*/ */
public function and($value): Expectation public function and(mixed $value): Expectation
{ {
return new self($value); return new self($value);
} }
@ -103,9 +92,9 @@ final class Expectation
/** /**
* Send the expectation value to Ray along with all given arguments. * Send the expectation value to Ray along with all given arguments.
* *
* @param mixed $arguments * @param ...mixed $arguments
*/ */
public function ray(...$arguments): self public function ray(mixed ...$arguments): self
{ {
if (function_exists('ray')) { if (function_exists('ray')) {
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
@ -146,9 +135,9 @@ final class Expectation
* *
* @template TSequenceValue * @template TSequenceValue
* *
* @param callable(self, self): void|TSequenceValue ...$callbacks * @param (callable(self, self): void)|TSequenceValue ...$callbacks
*/ */
public function sequence(...$callbacks): Expectation public function sequence(mixed ...$callbacks): Expectation
{ {
if (!is_iterable($this->value)) { if (!is_iterable($this->value)) {
throw new BadMethodCallException('Expectation value is not iterable.'); throw new BadMethodCallException('Expectation value is not iterable.');
@ -187,16 +176,14 @@ final class Expectation
* *
* @template TMatchSubject of array-key * @template TMatchSubject of array-key
* *
* @param callable(): TMatchSubject|TMatchSubject $subject * @param (callable(): TMatchSubject)|TMatchSubject $subject
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions * @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
*/ */
public function match($subject, array $expressions): Expectation public function match(mixed $subject, array $expressions): Expectation
{ {
$subject = is_callable($subject) $subject = is_callable($subject)
? $subject ? $subject
: function () use ($subject) { : fn () => $subject;
return $subject;
};
$subject = $subject(); $subject = $subject();
@ -232,12 +219,12 @@ final class Expectation
* @param (callable(): bool)|bool $condition * @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback * @param callable(Expectation<TValue>): mixed $callback
*/ */
public function unless($condition, callable $callback): Expectation public function unless(callable|bool $condition, callable $callback): Expectation
{ {
$condition = is_callable($condition) $condition = is_callable($condition)
? $condition ? $condition
: static function () use ($condition): bool { : static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line return $condition; // @phpstan-ignore-line
}; };
return $this->when(!$condition(), $callback); return $this->when(!$condition(), $callback);
@ -249,12 +236,12 @@ final class Expectation
* @param (callable(): bool)|bool $condition * @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback * @param callable(Expectation<TValue>): mixed $callback
*/ */
public function when($condition, callable $callback): Expectation public function when(callable|bool $condition, callable $callback): Expectation
{ {
$condition = is_callable($condition) $condition = is_callable($condition)
? $condition ? $condition
: static function () use ($condition): bool { : static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line return $condition; // @phpstan-ignore-line
}; };
if ($condition()) { if ($condition()) {
@ -268,10 +255,8 @@ final class Expectation
* Asserts that two variables have the same type and * Asserts that two variables have the same type and
* value. Used on objects, it asserts that two * value. Used on objects, it asserts that two
* variables reference the same object. * variables reference the same object.
*
* @param mixed $expected
*/ */
public function toBe($expected): Expectation public function toBe(mixed $expected): Expectation
{ {
Assert::assertSame($expected, $this->value); Assert::assertSame($expected, $this->value);
@ -330,10 +315,8 @@ final class Expectation
/** /**
* Asserts that the value is greater than $expected. * Asserts that the value is greater than $expected.
*
* @param int|float $expected
*/ */
public function toBeGreaterThan($expected): Expectation public function toBeGreaterThan(int|float $expected): Expectation
{ {
Assert::assertGreaterThan($expected, $this->value); Assert::assertGreaterThan($expected, $this->value);
@ -342,10 +325,8 @@ final class Expectation
/** /**
* Asserts that the value is greater than or equal to $expected. * Asserts that the value is greater than or equal to $expected.
*
* @param int|float $expected
*/ */
public function toBeGreaterThanOrEqual($expected): Expectation public function toBeGreaterThanOrEqual(int|float $expected): Expectation
{ {
Assert::assertGreaterThanOrEqual($expected, $this->value); Assert::assertGreaterThanOrEqual($expected, $this->value);
@ -354,10 +335,8 @@ final class Expectation
/** /**
* Asserts that the value is less than or equal to $expected. * Asserts that the value is less than or equal to $expected.
*
* @param int|float $expected
*/ */
public function toBeLessThan($expected): Expectation public function toBeLessThan(int|float $expected): Expectation
{ {
Assert::assertLessThan($expected, $this->value); Assert::assertLessThan($expected, $this->value);
@ -366,10 +345,8 @@ final class Expectation
/** /**
* Asserts that the value is less than $expected. * Asserts that the value is less than $expected.
*
* @param int|float $expected
*/ */
public function toBeLessThanOrEqual($expected): Expectation public function toBeLessThanOrEqual(int|float $expected): Expectation
{ {
Assert::assertLessThanOrEqual($expected, $this->value); Assert::assertLessThanOrEqual($expected, $this->value);
@ -378,10 +355,8 @@ final class Expectation
/** /**
* Asserts that $needle is an element of the value. * Asserts that $needle is an element of the value.
*
* @param mixed $needles
*/ */
public function toContain(...$needles): Expectation public function toContain(mixed ...$needles): Expectation
{ {
foreach ($needles as $needle) { foreach ($needles as $needle) {
if (is_string($this->value)) { if (is_string($this->value)) {
@ -456,10 +431,8 @@ final class Expectation
/** /**
* Asserts that the value contains the property $name. * Asserts that the value contains the property $name.
*
* @param mixed $value
*/ */
public function toHaveProperty(string $name, $value = null): Expectation public function toHaveProperty(string $name, mixed $value = null): Expectation
{ {
$this->toBeObject(); $this->toBeObject();
@ -489,10 +462,8 @@ final class Expectation
/** /**
* Asserts that two variables have the same value. * Asserts that two variables have the same value.
*
* @param mixed $expected
*/ */
public function toEqual($expected): Expectation public function toEqual(mixed $expected): Expectation
{ {
Assert::assertEquals($expected, $this->value); Assert::assertEquals($expected, $this->value);
@ -507,10 +478,8 @@ final class Expectation
* are sorted before they are compared. When $expected and $this->value * are sorted before they are compared. When $expected and $this->value
* are objects, each object is converted to an array containing all * are objects, each object is converted to an array containing all
* private, protected and public attributes. * private, protected and public attributes.
*
* @param mixed $expected
*/ */
public function toEqualCanonicalizing($expected): Expectation public function toEqualCanonicalizing(mixed $expected): Expectation
{ {
Assert::assertEqualsCanonicalizing($expected, $this->value); Assert::assertEqualsCanonicalizing($expected, $this->value);
@ -520,10 +489,8 @@ final class Expectation
/** /**
* Asserts that the absolute difference between the value and $expected * Asserts that the absolute difference between the value and $expected
* is lower than $delta. * is lower than $delta.
*
* @param mixed $expected
*/ */
public function toEqualWithDelta($expected, float $delta): Expectation public function toEqualWithDelta(mixed $expected, float $delta): Expectation
{ {
Assert::assertEqualsWithDelta($expected, $this->value, $delta); Assert::assertEqualsWithDelta($expected, $this->value, $delta);
@ -555,9 +522,9 @@ final class Expectation
/** /**
* Asserts that the value is an instance of $class. * Asserts that the value is an instance of $class.
* *
* @param string $class * @param class-string $class
*/ */
public function toBeInstanceOf($class): Expectation public function toBeInstanceOf(string $class): Expectation
{ {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
Assert::assertInstanceOf($class, $this->value); Assert::assertInstanceOf($class, $this->value);
@ -708,11 +675,8 @@ final class Expectation
/** /**
* Asserts that the value array has the provided $key. * Asserts that the value array has the provided $key.
*
* @param string|int $key
* @param mixed $value
*/ */
public function toHaveKey($key, $value = null): Expectation public function toHaveKey(string|int $key, mixed $value = null): Expectation
{ {
if (is_object($this->value) && method_exists($this->value, 'toArray')) { if (is_object($this->value) && method_exists($this->value, 'toArray')) {
$array = $this->value->toArray(); $array = $this->value->toArray();
@ -812,9 +776,9 @@ final class Expectation
/** /**
* Asserts that the value array matches the given array subset. * Asserts that the value array matches the given array subset.
* *
* @param array<int|string, mixed> $array * @param iterable<int|string, mixed> $array
*/ */
public function toMatchArray($array): Expectation public function toMatchArray(iterable|object $array): Expectation
{ {
if (is_object($this->value) && method_exists($this->value, 'toArray')) { if (is_object($this->value) && method_exists($this->value, 'toArray')) {
$valueAsArray = $this->value->toArray(); $valueAsArray = $this->value->toArray();
@ -843,9 +807,9 @@ final class Expectation
* Asserts that the value object matches a subset * Asserts that the value object matches a subset
* of the properties of an given object. * of the properties of an given object.
* *
* @param array<string, mixed>|object $object * @param iterable<string, mixed>|object $object
*/ */
public function toMatchObject($object): Expectation public function toMatchObject(iterable|object $object): Expectation
{ {
foreach ((array) $object as $property => $value) { foreach ((array) $object as $property => $value) {
Assert::assertTrue(property_exists($this->value, $property)); Assert::assertTrue(property_exists($this->value, $property));
@ -891,7 +855,7 @@ final class Expectation
* *
* @param (Closure(Throwable): mixed)|string $exception * @param (Closure(Throwable): mixed)|string $exception
*/ */
public function toThrow($exception, string $exceptionMessage = null): Expectation public function toThrow(callable|string $exception, string $exceptionMessage = null): Expectation
{ {
$callback = NullClosure::create(); $callback = NullClosure::create();
@ -938,10 +902,8 @@ final class Expectation
/** /**
* Exports the given value. * Exports the given value.
*
* @param mixed $value
*/ */
private function export($value): string private function export(mixed $value): string
{ {
if ($this->exporter === null) { if ($this->exporter === null) {
$this->exporter = new Exporter(); $this->exporter = new Exporter();
@ -971,10 +933,8 @@ final class Expectation
/** /**
* Dynamically calls methods on the class without any arguments * Dynamically calls methods on the class without any arguments
* or creates a new higher order expectation. * or creates a new higher order expectation.
*
* @return Expectation|HigherOrderExpectation
*/ */
public function __get(string $name) public function __get(string $name): Expectation|OppositeExpectation|Each|HigherOrderExpectation
{ {
if (!method_exists($this, $name) && !static::hasExtend($name)) { if (!method_exists($this, $name) && !static::hasExtend($name)) {
return new HigherOrderExpectation($this, $this->retrieve($name, $this->value)); return new HigherOrderExpectation($this, $this->retrieve($name, $this->value));

View File

@ -22,96 +22,67 @@ use RuntimeException;
*/ */
final class TestCaseFactory final class TestCaseFactory
{ {
/**
* Holds the test filename.
*
* @readonly
*
* @var string
*/
public $filename;
/** /**
* Marks this test case as only. * Marks this test case as only.
* *
* @readonly * @readonly
*
* @var bool
*/ */
public $only = false; public bool $only = false;
/**
* Holds the test description.
*
* If the description is null, means that it
* will be created with the given assertions.
*
* @var string|null
*/
public $description;
/** /**
* Holds the test closure. * Holds the test closure.
* *
* @readonly * @readonly
*
* @var Closure
*/ */
public $test; public Closure $test;
/** /**
* Holds the dataset, if any. * Holds the dataset, if any.
* *
* @var array<Closure|iterable<int|string, mixed>|string> * @var array<Closure|iterable<int|string, mixed>|string>
*/ */
public $datasets = []; public array $datasets = [];
/** /**
* The FQN of the test case class. * The FQN of the test case class.
* *
* @var string * @var class-string
*/ */
public $class = TestCase::class; public string $class = TestCase::class;
/** /**
* An array of FQN of the class traits. * An array of FQN of the class traits.
* *
* @var array <int, string> * @var array <int, string>
*/ */
public $traits = [ public array $traits = [
Concerns\Testable::class, Concerns\Testable::class,
Concerns\Expectable::class, Concerns\Expectable::class,
]; ];
/** /**
* Holds the higher order messages * Holds the higher order messages for the factory that are proxyble.
* for the factory that are proxyble.
*
* @var HigherOrderMessageCollection
*/ */
public $factoryProxies; public HigherOrderMessageCollection $factoryProxies;
/** /**
* Holds the higher order messages that are proxyble. * Holds the higher order messages that are proxyble.
*
* @var HigherOrderMessageCollection
*/ */
public $proxies; public HigherOrderMessageCollection $proxies;
/** /**
* Holds the higher order messages that are chainable. * Holds the higher order messages that are chainable.
*
* @var HigherOrderMessageCollection
*/ */
public $chains; public HigherOrderMessageCollection $chains;
/** /**
* Creates a new anonymous test case pending object. * Creates a new anonymous test case pending object.
*/ */
public function __construct(string $filename, string $description = null, Closure $closure = null) public function __construct(
public string $filename,
public ?string $description = null,
Closure $closure = null)
{ {
$this->filename = $filename;
$this->description = $description;
$this->test = $closure ?? function (): void { $this->test = $closure ?? function (): void {
if (Assert::getCount() === 0) { if (Assert::getCount() === 0) {
self::markTestIncomplete(); // @phpstan-ignore-line self::markTestIncomplete(); // @phpstan-ignore-line
@ -146,7 +117,7 @@ final class TestCaseFactory
$chains->chain($this); $chains->chain($this);
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args()); return call_user_func(Closure::bind($factoryTest, $this, $this::class), ...func_get_args());
}; };
$className = $this->makeClassFromFilename($this->filename); $className = $this->makeClassFromFilename($this->filename);
@ -170,9 +141,7 @@ final class TestCaseFactory
{ {
if ('\\' === DIRECTORY_SEPARATOR) { if ('\\' === DIRECTORY_SEPARATOR) {
// In case Windows, strtolower drive name, like in UsesCall. // In case Windows, strtolower drive name, like in UsesCall.
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', function ($match): string { $filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $filename);
return strtolower($match['drive']);
}, $filename);
} }
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename))); $filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
@ -184,9 +153,7 @@ final class TestCaseFactory
// Strip out any %-encoded octets. // Strip out any %-encoded octets.
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath); $relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
// Remove escaped quote sequences (maintain namespace) // Remove escaped quote sequences (maintain namespace)
$relativePath = str_replace(array_map(function (string $quote): string { $relativePath = str_replace(array_map(fn (string $quote): string => sprintf('\\%s', $quote), ['\'', '"']), '', $relativePath);
return sprintf('\\%s', $quote);
}, ['\'', '"']), '', $relativePath);
// Limit to A-Z, a-z, 0-9, '_', '-'. // Limit to A-Z, a-z, 0-9, '_', '-'.
$relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath); $relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
@ -196,9 +163,7 @@ final class TestCaseFactory
} }
$hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class); $hasPrintableTestCaseClassFQN = sprintf('\%s', HasPrintableTestCaseName::class);
$traitsCode = sprintf('use %s;', implode(', ', array_map(function ($trait): string { $traitsCode = sprintf('use %s;', implode(', ', array_map(fn ($trait): string => sprintf('\%s', $trait), $this->traits)));
return sprintf('\%s', $trait);
}, $this->traits)));
$partsFQN = explode('\\', $classFQN); $partsFQN = explode('\\', $classFQN);
$className = array_pop($partsFQN); $className = array_pop($partsFQN);

View File

@ -18,10 +18,8 @@ use PHPUnit\Framework\TestCase;
* Creates a new expectation. * Creates a new expectation.
* *
* @param mixed $value the Value * @param mixed $value the Value
*
* @return Expectation|Extendable
*/ */
function expect($value = null) function expect($value = null): Expectation|Extendable
{ {
if (func_num_args() === 0) { if (func_num_args() === 0) {
return new Extendable(Expectation::class); return new Extendable(Expectation::class);
@ -60,7 +58,7 @@ if (!function_exists('dataset')) {
* *
* @param Closure|iterable<int|string, mixed> $dataset * @param Closure|iterable<int|string, mixed> $dataset
*/ */
function dataset(string $name, $dataset): void function dataset(string $name, Closure|iterable $dataset): void
{ {
Datasets::set($name, $dataset); Datasets::set($name, $dataset);
} }

View File

@ -17,39 +17,17 @@ final class HigherOrderExpectation
use Expectable; use Expectable;
use RetrievesValues; use RetrievesValues;
/** private Expectation|Each $expectation;
* @var Expectation
*/
private $original;
/** private bool $opposite = false;
* @var Expectation|Each
*/
private $expectation;
/** private bool $shouldReset = false;
* @var bool
*/
private $opposite = false;
/**
* @var bool
*/
private $shouldReset = false;
/**
* @var string
*/
private $name;
/** /**
* Creates a new higher order expectation. * Creates a new higher order expectation.
*
* @param mixed $value
*/ */
public function __construct(Expectation $original, $value) public function __construct(private Expectation $original, mixed $value)
{ {
$this->original = $original;
$this->expectation = $this->expect($value); $this->expectation = $this->expect($value);
} }
@ -72,7 +50,7 @@ final class HigherOrderExpectation
* *
* @return Expectation<TValue> * @return Expectation<TValue>
*/ */
public function and($value): Expectation public function and(mixed $value): Expectation
{ {
return $this->expect($value); return $this->expect($value);
} }
@ -118,10 +96,8 @@ final class HigherOrderExpectation
/** /**
* Retrieve the applicable value based on the current reset condition. * Retrieve the applicable value based on the current reset condition.
*
* @return mixed
*/ */
private function getValue() private function getValue(): mixed
{ {
return $this->shouldReset ? $this->original->value : $this->expectation->value; return $this->shouldReset ? $this->original->value : $this->expectation->value;
} }

View File

@ -16,7 +16,6 @@ use function class_exists;
use DOMDocument; use DOMDocument;
use DOMElement; use DOMElement;
use Exception; use Exception;
use function get_class;
use function method_exists; use function method_exists;
use Pest\Concerns\Testable; use Pest\Concerns\Testable;
use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\AssertionFailedError;
@ -42,65 +41,50 @@ use function trim;
*/ */
final class JUnit extends Printer implements TestListener final class JUnit extends Printer implements TestListener
{ {
/** private DOMDocument $document;
* @var DOMDocument
*/ private DOMElement $root;
private $document;
/** /**
* @var DOMElement * @var array<int, DOMElement>
*/ */
private $root; private array $testSuites = [];
/**
* @var DOMElement[]
*/
private $testSuites = [];
/** /**
* @var int[] * @var int[]
*/ */
private $testSuiteTests = [0]; private array $testSuiteTests = [0];
/** /**
* @var int[] * @var int[]
*/ */
private $testSuiteAssertions = [0]; private array $testSuiteAssertions = [0];
/** /**
* @var int[] * @var int[]
*/ */
private $testSuiteErrors = [0]; private array $testSuiteErrors = [0];
/** /**
* @var int[] * @var int[]
*/ */
private $testSuiteWarnings = [0]; private array $testSuiteWarnings = [0];
/** /**
* @var int[] * @var int[]
*/ */
private $testSuiteFailures = [0]; private array $testSuiteFailures = [0];
/** /**
* @var int[] * @var int[]
*/ */
private $testSuiteSkipped = [0]; private array $testSuiteSkipped = [0];
/** private array $testSuiteTimes = [0];
* @var int[]|float[]
*/
private $testSuiteTimes = [0];
/** private int $testSuiteLevel = 0;
* @var int
*/
private $testSuiteLevel = 0;
/** private ?DOMElement $currentTestCase = null;
* @var DOMElement|null
*/
private $currentTestCase;
public function __construct(string $out) public function __construct(string $out)
{ {
@ -190,7 +174,7 @@ final class JUnit extends Printer implements TestListener
} }
$testSuite->setAttribute('file', $fileName); $testSuite->setAttribute('file', $fileName);
} catch (ReflectionException $e) { } catch (ReflectionException) {
// @ignoreException // @ignoreException
} }
} }
@ -313,7 +297,7 @@ final class JUnit extends Printer implements TestListener
$testCase->setAttribute('class', $test->getPrintableTestCaseName()); $testCase->setAttribute('class', $test->getPrintableTestCaseName());
$testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName())); $testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName()));
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
$testCase->setAttribute('file', $test->__getFileName()); $testCase->setAttribute('file', $test->__getFilename());
} }
$this->currentTestCase = $testCase; $this->currentTestCase = $testCase;
@ -409,7 +393,7 @@ final class JUnit extends Printer implements TestListener
if ($t instanceof ExceptionWrapper) { if ($t instanceof ExceptionWrapper) {
$fault->setAttribute('type', $t->getClassName()); $fault->setAttribute('type', $t->getClassName());
} else { } else {
$fault->setAttribute('type', get_class($t)); $fault->setAttribute('type', $t::class);
} }
$this->currentTestCase->appendChild($fault); $this->currentTestCase->appendChild($fault);

View File

@ -16,9 +16,9 @@ use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite; use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning; use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\DefaultResultPrinter; use PHPUnit\TextUI\DefaultResultPrinter;
use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity as BaseTeamCity;
use function round; use function round;
use function str_replace; use function str_replace;
use function strlen;
use Throwable; use Throwable;
final class TeamCity extends DefaultResultPrinter final class TeamCity extends DefaultResultPrinter
@ -34,22 +34,19 @@ final class TeamCity extends DefaultResultPrinter
private const TEST_STARTED = 'testStarted'; private const TEST_STARTED = 'testStarted';
private const TEST_FINISHED = 'testFinished'; private const TEST_FINISHED = 'testFinished';
/** @var int */ private ?int $flowId = null;
private $flowId;
/** @var bool */ private bool $isSummaryTestCountPrinted = false;
private $isSummaryTestCountPrinted = false;
/** @var \PHPUnit\Util\Log\TeamCity */ private BaseTeamCity $phpunitTeamCity;
private $phpunitTeamCity;
/** /**
* @param resource|string|null $out * Creates a new printer instance.
*/ */
public function __construct($out, bool $verbose, string $colors) public function __construct(resource|string|null $out, bool $verbose, string $colors)
{ {
parent::__construct($out, $verbose, $colors); parent::__construct($out, $verbose, $colors);
$this->phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity($out, $verbose, $colors); $this->phpunitTeamCity = new BaseTeamCity($out, $verbose, $colors);
$this->logo(); $this->logo();
} }
@ -74,9 +71,7 @@ final class TeamCity extends DefaultResultPrinter
'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'], 'passed' => ['count' => $this->successfulTestCount($result), 'color' => 'fg-green'],
]; ];
$filteredResults = array_filter($results, function ($item): bool { $filteredResults = array_filter($results, fn ($item): bool => $item['count'] > 0);
return $item['count'] > 0;
});
foreach ($filteredResults as $key => $info) { foreach ($filteredResults as $key => $info) {
$this->writeWithColor($info['color'], $info['count'] . " $key", false); $this->writeWithColor($info['color'], $info['count'] . " $key", false);
@ -203,7 +198,7 @@ final class TeamCity extends DefaultResultPrinter
*/ */
private static function isPestTestSuite(TestSuite $suite): bool private static function isPestTestSuite(TestSuite $suite): bool
{ {
return strncmp($suite->getName(), 'P\\', strlen('P\\')) === 0; return str_starts_with($suite->getName(), 'P\\');
} }
/** /**

View File

@ -14,17 +14,12 @@ use SebastianBergmann\Exporter\Exporter;
*/ */
final class OppositeExpectation final class OppositeExpectation
{ {
/**
* @var Expectation
*/
private $original;
/** /**
* Creates a new opposite expectation. * Creates a new opposite expectation.
*/ */
public function __construct(Expectation $original) public function __construct(private Expectation $original)
{ {
$this->original = $original; // ..
} }
/** /**
@ -37,7 +32,7 @@ final class OppositeExpectation
foreach ($keys as $key) { foreach ($keys as $key) {
try { try {
$this->original->toHaveKey($key); $this->original->toHaveKey($key);
} catch (ExpectationFailedException $e) { } catch (ExpectationFailedException) {
continue; continue;
} }
@ -57,7 +52,7 @@ final class OppositeExpectation
try { try {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
$this->original->{$name}(...$arguments); $this->original->{$name}(...$arguments);
} catch (ExpectationFailedException $e) { } catch (ExpectationFailedException) {
return $this->original; return $this->original;
} }
@ -73,7 +68,7 @@ final class OppositeExpectation
try { try {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
$this->original->{$name}; $this->original->{$name};
} catch (ExpectationFailedException $e) { } catch (ExpectationFailedException) {
return $this->original; return $this->original;
} }
@ -90,10 +85,8 @@ final class OppositeExpectation
{ {
$exporter = new Exporter(); $exporter = new Exporter();
$toString = function ($argument) use ($exporter): string { $toString = fn ($argument): string => $exporter->shortenedExport($argument);
return $exporter->shortenedExport($argument);
};
throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)), implode(' ', array_map(function ($argument) use ($toString): string { return $toString($argument); }, $arguments)))); throw new ExpectationFailedException(sprintf('Expecting %s not %s %s.', $toString($this->original->value), strtolower((string) preg_replace('/(?<!\ )[A-Z]/', ' $0', $name)), implode(' ', array_map(fn ($argument): string => $toString($argument), $arguments))));
} }
} }

View File

@ -16,41 +16,23 @@ use Pest\TestSuite;
*/ */
final class AfterEachCall final class AfterEachCall
{ {
/**
* Holds the test suite.
*
* @var TestSuite
*/
private $testSuite;
/**
* Holds the filename.
*
* @var string
*/
private $filename;
/** /**
* Holds the before each closure. * Holds the before each closure.
*
* @var Closure
*/ */
private $closure; private Closure $closure;
/** /**
* Holds calls that should be proxied. * Holds calls that should be proxied.
*
* @var HigherOrderMessageCollection
*/ */
private $proxies; private HigherOrderMessageCollection $proxies;
/** /**
* Creates a new instance of before each call. * Creates a new instance of before each call.
*/ */
public function __construct(TestSuite $testSuite, string $filename, Closure $closure = null) public function __construct(
{ private TestSuite $testSuite,
$this->testSuite = $testSuite; private string $filename, Closure $closure = null
$this->filename = $filename; ) {
$this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); $this->closure = $closure instanceof Closure ? $closure : NullClosure::create();
$this->proxies = new HigherOrderMessageCollection(); $this->proxies = new HigherOrderMessageCollection();

View File

@ -16,41 +16,24 @@ use Pest\TestSuite;
*/ */
final class BeforeEachCall final class BeforeEachCall
{ {
/**
* Holds the test suite.
*
* @var TestSuite
*/
private $testSuite;
/**
* Holds the filename.
*
* @var string
*/
private $filename;
/** /**
* Holds the before each closure. * Holds the before each closure.
*
* @var Closure
*/ */
private $closure; private \Closure $closure;
/** /**
* Holds calls that should be proxied. * Holds calls that should be proxied.
*
* @var HigherOrderMessageCollection
*/ */
private $proxies; private HigherOrderMessageCollection $proxies;
/** /**
* Creates a new instance of before each call. * Creates a new instance of before each call.
*/ */
public function __construct(TestSuite $testSuite, string $filename, Closure $closure = null) public function __construct(
{ private TestSuite $testSuite,
$this->testSuite = $testSuite; private string $filename,
$this->filename = $filename; Closure $closure = null
) {
$this->closure = $closure instanceof Closure ? $closure : NullClosure::create(); $this->closure = $closure instanceof Closure ? $closure : NullClosure::create();
$this->proxies = new HigherOrderMessageCollection(); $this->proxies = new HigherOrderMessageCollection();

View File

@ -19,40 +19,30 @@ use SebastianBergmann\Exporter\Exporter;
*/ */
final class TestCall final class TestCall
{ {
/**
* Holds the test suite.
*
* @readonly
*
* @var TestSuite
*/
private $testSuite;
/** /**
* Holds the test case factory. * Holds the test case factory.
* *
* @readonly * @readonly
*
* @var TestCaseFactory
*/ */
private $testCaseFactory; private TestCaseFactory $testCaseFactory;
/** /**
* If test call is descriptionLess. * If test call is descriptionLess.
* *
* @readonly * @readonly
*
* @var bool
*/ */
private $descriptionLess = false; private bool $descriptionLess = false;
/** /**
* Creates a new instance of a pending test call. * Creates a new instance of a pending test call.
*/ */
public function __construct(TestSuite $testSuite, string $filename, string $description = null, Closure $closure = null) public function __construct(
{ private TestSuite $testSuite,
string $filename,
string $description = null,
Closure $closure = null
) {
$this->testCaseFactory = new TestCaseFactory($filename, $description, $closure); $this->testCaseFactory = new TestCaseFactory($filename, $description, $closure);
$this->testSuite = $testSuite;
$this->descriptionLess = $description === null; $this->descriptionLess = $description === null;
} }
@ -83,12 +73,12 @@ final class TestCall
* *
* @param (callable(): bool)|bool $condition * @param (callable(): bool)|bool $condition
*/ */
public function throwsIf($condition, string $exception, string $exceptionMessage = null): TestCall public function throwsIf(callable|bool $condition, string $exception, string $exceptionMessage = null): TestCall
{ {
$condition = is_callable($condition) $condition = is_callable($condition)
? $condition ? $condition
: static function () use ($condition): bool { : static function () use ($condition): bool {
return (bool) $condition; // @phpstan-ignore-line return $condition; // @phpstan-ignore-line
}; };
if ($condition()) { if ($condition()) {
@ -149,10 +139,8 @@ final class TestCall
/** /**
* Skips the current test. * Skips the current test.
*
* @param Closure|bool|string $conditionOrMessage
*/ */
public function skip($conditionOrMessage = true, string $message = ''): TestCall public function skip(Closure|bool|string $conditionOrMessage = true, string $message = ''): TestCall
{ {
$condition = is_string($conditionOrMessage) $condition = is_string($conditionOrMessage)
? NullClosure::create() ? NullClosure::create()
@ -160,9 +148,7 @@ final class TestCall
$condition = is_callable($condition) $condition = is_callable($condition)
? $condition ? $condition
: function () use ($condition) { /* @phpstan-ignore-line */ : fn () => $condition;
return $condition;
};
$message = is_string($conditionOrMessage) $message = is_string($conditionOrMessage)
? $conditionOrMessage ? $conditionOrMessage

View File

@ -24,45 +24,31 @@ final class UsesCall
* *
* @var array<int, Closure> * @var array<int, Closure>
*/ */
private $hooks = []; private array $hooks = [];
/**
* Holds the class and traits.
*
* @var array<int, string>
*/
private $classAndTraits;
/**
* Holds the base dirname here the uses call was performed.
*
* @var string
*/
private $filename;
/** /**
* Holds the targets of the uses. * Holds the targets of the uses.
* *
* @var array<int, string> * @var array<int, string>
*/ */
private $targets; private array $targets;
/** /**
* Holds the groups of the uses. * Holds the groups of the uses.
* *
* @var array<int, string> * @var array<int, string>
*/ */
private $groups = []; private array $groups = [];
/** /**
* Creates a new instance of a pending test uses. * Creates a new instance of a pending test uses.
* *
* @param array<int, string> $classAndTraits * @param array<int, string> $classAndTraits
*/ */
public function __construct(string $filename, array $classAndTraits) public function __construct(
{ private string $filename,
$this->classAndTraits = $classAndTraits; private array $classAndTraits
$this->filename = $filename; ) {
$this->targets = [$filename]; $this->targets = [$filename];
} }
@ -76,14 +62,12 @@ final class UsesCall
$startChar = DIRECTORY_SEPARATOR; $startChar = DIRECTORY_SEPARATOR;
if ('\\' === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0) { if ('\\' === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0) {
$path = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', function ($match): string { $path = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', fn ($match): string => strtolower($match['drive']), $path);
return strtolower($match['drive']);
}, $path);
$startChar = strtolower((string) preg_replace('~^([a-z]+:\\\).*$~i', '$1', __DIR__)); $startChar = strtolower((string) preg_replace('~^([a-z]+:\\\).*$~i', '$1', __DIR__));
} }
return 0 === strpos($path, $startChar) return str_starts_with($path, $startChar)
? $path ? $path
: implode(DIRECTORY_SEPARATOR, [ : implode(DIRECTORY_SEPARATOR, [
dirname($this->filename), dirname($this->filename),

View File

@ -14,7 +14,7 @@ final class Plugin
* *
* @internal * @internal
*/ */
public static $callables = []; public static array $callables = [];
/** /**
* Lazy loads an `uses` call on the context of plugins. * Lazy loads an `uses` call on the context of plugins.

View File

@ -29,31 +29,30 @@ final class Coverage implements AddsOutput, HandlesArguments
/** /**
* Whether should show the coverage or not. * Whether should show the coverage or not.
*
* @var bool
*/ */
public $coverage = false; public bool $coverage = false;
/** /**
* The minimum coverage. * The minimum coverage.
*
* @var float
*/ */
public $coverageMin = 0.0; public float $coverageMin = 0.0;
/** /**
* @var OutputInterface * Creates a new Plugin instance.
*/ */
private $output; public function __construct(private OutputInterface $output)
public function __construct(OutputInterface $output)
{ {
$this->output = $output; // ..
} }
/**
* @param array<int, string> $originals
*
* @return array<int, string>
*/
public function handleArguments(array $originals): array public function handleArguments(array $originals): array
{ {
$arguments = array_merge([''], array_values(array_filter($originals, function ($original): bool { $arguments = [...[''], ...array_values(array_filter($originals, function ($original): bool {
foreach ([self::COVERAGE_OPTION, self::MIN_OPTION] as $option) { foreach ([self::COVERAGE_OPTION, self::MIN_OPTION] as $option) {
if ($original === sprintf('--%s', $option) || Str::startsWith($original, sprintf('--%s=', $option))) { if ($original === sprintf('--%s', $option) || Str::startsWith($original, sprintf('--%s=', $option))) {
return true; return true;
@ -61,7 +60,7 @@ final class Coverage implements AddsOutput, HandlesArguments
} }
return false; return false;
}))); }))];
$originals = array_flip($originals); $originals = array_flip($originals);
foreach ($arguments as $argument) { foreach ($arguments as $argument) {

View File

@ -21,17 +21,10 @@ final class Environment implements HandlesArguments
*/ */
public const LOCAL = 'local'; public const LOCAL = 'local';
/**
* @var \Pest\Plugins\Environment|null
*/
private static $instance;
/** /**
* The current environment. * The current environment.
*
* @var string|null
*/ */
private static $name; private static ?string $name = null;
/** /**
* Allows to handle custom command line arguments. * Allows to handle custom command line arguments.

View File

@ -28,23 +28,14 @@ final class Init implements HandlesArguments
'ExampleTest.php' => 'tests/ExampleTest.php', 'ExampleTest.php' => 'tests/ExampleTest.php',
]; ];
/**
* @var OutputInterface
*/
private $output;
/**
* @var TestSuite
*/
private $testSuite;
/** /**
* Creates a new Plugin instance. * Creates a new Plugin instance.
*/ */
public function __construct(TestSuite $testSuite, OutputInterface $output) public function __construct(
{ private TestSuite $testSuite,
$this->testSuite = $testSuite; private OutputInterface $output
$this->output = $output; ) {
// ..
} }
public function handleArguments(array $arguments): array public function handleArguments(array $arguments): array

View File

@ -13,17 +13,13 @@ use Symfony\Component\Console\Output\OutputInterface;
*/ */
final class Version implements HandlesArguments final class Version implements HandlesArguments
{ {
/**
* @var OutputInterface
*/
private $output;
/** /**
* Creates a new instance of the plugin. * Creates a new instance of the plugin.
*/ */
public function __construct(OutputInterface $output) public function __construct(
{ private OutputInterface $output
$this->output = $output; ) {
// ..
} }
public function handleArguments(array $arguments): array public function handleArguments(array $arguments): array

View File

@ -17,7 +17,7 @@ final class AfterAllRepository
/** /**
* @var array<string, Closure> * @var array<string, Closure>
*/ */
private $state = []; private array $state = [];
/** /**
* Runs the given closure for each after all. * Runs the given closure for each after all.

View File

@ -18,7 +18,7 @@ final class AfterEachRepository
/** /**
* @var array<string, Closure> * @var array<string, Closure>
*/ */
private $state = []; private array $state = [];
/** /**
* Sets a after each closure. * Sets a after each closure.

View File

@ -17,7 +17,7 @@ final class BeforeAllRepository
/** /**
* @var array<string, Closure> * @var array<string, Closure>
*/ */
private $state = []; private array $state = [];
/** /**
* Runs one before all closure, and unsets it from the repository. * Runs one before all closure, and unsets it from the repository.

View File

@ -16,7 +16,7 @@ final class BeforeEachRepository
/** /**
* @var array<string, Closure> * @var array<string, Closure>
*/ */
private $state = []; private array $state = [];
/** /**
* Sets a before each closure. * Sets a before each closure.

View File

@ -30,12 +30,12 @@ final class TestRepository
/** /**
* @var array<string, TestCaseFactory> * @var array<string, TestCaseFactory>
*/ */
private $state = []; private array $state = [];
/** /**
* @var array<string, array<int, array<int, string|Closure>>> * @var array<string, array<int, array<int, string|Closure>>>
*/ */
private $uses = []; private array $uses = [];
/** /**
* Counts the number of test cases. * Counts the number of test cases.
@ -54,9 +54,7 @@ final class TestRepository
{ {
$testsWithOnly = $this->testsUsingOnly(); $testsWithOnly = $this->testsUsingOnly();
return array_values(array_map(function (TestCaseFactory $factory): string { return array_values(array_map(fn (TestCaseFactory $factory): string => $factory->filename, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state));
return $factory->filename;
}, count($testsWithOnly) > 0 ? $testsWithOnly : $this->state));
} }
/** /**
@ -64,9 +62,7 @@ final class TestRepository
*/ */
public function build(TestSuite $testSuite, callable $each): void public function build(TestSuite $testSuite, callable $each): void
{ {
$startsWith = function (string $target, string $directory): bool { $startsWith = fn (string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR);
return Str::startsWith($target, $directory . DIRECTORY_SEPARATOR);
};
foreach ($this->uses as $path => $uses) { foreach ($this->uses as $path => $uses) {
[$classOrTraits, $groups, $hooks] = $uses; [$classOrTraits, $groups, $hooks] = $uses;
@ -123,9 +119,7 @@ final class TestRepository
return []; return [];
} }
return array_filter($this->state, function ($testFactory): bool { return array_filter($this->state, fn ($testFactory): bool => $testFactory->only);
return $testFactory->only;
});
} }
/** /**
@ -147,8 +141,8 @@ final class TestRepository
foreach ($paths as $path) { foreach ($paths as $path) {
if (array_key_exists($path, $this->uses)) { if (array_key_exists($path, $this->uses)) {
$this->uses[$path] = [ $this->uses[$path] = [
array_merge($this->uses[$path][0], $classOrTraits), [...$this->uses[$path][0], ...$classOrTraits],
array_merge($this->uses[$path][1], $groups), [...$this->uses[$path][1], ...$groups],
$this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices $this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices
]; ];
} else { } else {

View File

@ -17,6 +17,6 @@ final class EnsureConfigurationDefaults implements ConfiguredSubscriber
*/ */
public function notify(Configured $event): void public function notify(Configured $event): void
{ {
$configuration = $event->configuration(); // TODO...
} }
} }

View File

@ -18,7 +18,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber
/** /**
* The current test suite, if any. * The current test suite, if any.
*/ */
private static ?TestSuite $testSuite; private static ?TestSuite $testSuite = null;
/** /**
* Runs the subscriber. * Runs the subscriber.
@ -31,7 +31,7 @@ final class EnsureTestsAreLoaded implements LoadedSubscriber
$testSuite = \Pest\TestSuite::getInstance(); $testSuite = \Pest\TestSuite::getInstance();
$testSuite->tests->build($testSuite, function (TestCase $testCase) use (&$testSuites): void { $testSuite->tests->build($testSuite, function (TestCase $testCase) use (&$testSuites): void {
$testCaseClass = get_class($testCase); $testCaseClass = $testCase::class;
if (!array_key_exists($testCaseClass, $testSuites)) { if (!array_key_exists($testCaseClass, $testSuites)) {
$testSuites[$testCaseClass] = []; $testSuites[$testCaseClass] = [];
} }

View File

@ -15,9 +15,8 @@ final class Arr
{ {
/** /**
* @param array<mixed> $array * @param array<mixed> $array
* @param string|int $key
*/ */
public static function has(array $array, $key): bool public static function has(array $array, string|int $key): bool
{ {
$key = (string) $key; $key = (string) $key;
@ -38,12 +37,11 @@ final class Arr
/** /**
* @param array<mixed> $array * @param array<mixed> $array
* @param string|int $key
* @param null $default * @param null $default
* *
* @return array|mixed|null * @return array|mixed|null
*/ */
public static function get(array $array, $key, $default = null) public static function get(array $array, string|int $key, $default = null)
{ {
$key = (string) $key; $key = (string) $key;
@ -51,7 +49,7 @@ final class Arr
return $array[$key]; return $array[$key];
} }
if (strpos($key, '.') === false) { if (!str_contains($key, '.')) {
return $array[$key] ?? $default; return $array[$key] ?? $default;
} }

View File

@ -26,7 +26,6 @@ final class Backtrace
$current = null; $current = null;
foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) {
if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) { if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) {
break; break;
} }

View File

@ -18,9 +18,9 @@ final class ChainableClosure
{ {
return function () use ($closure, $next): void { return function () use ($closure, $next): void {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($closure, $this, get_class($this)), func_get_args()); call_user_func_array(Closure::bind($closure, $this, $this::class), func_get_args());
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args()); call_user_func_array(Closure::bind($next, $this, $this::class), func_get_args());
}; };
} }

View File

@ -13,15 +13,12 @@ use ReflectionParameter;
*/ */
final class Container final class Container
{ {
/** private static ?Container $instance = null;
* @var self
*/
private static $instance;
/** /**
* @var array<string, mixed> * @var array<string, mixed>
*/ */
private $instances = []; private array $instances = [];
/** /**
* Gets a new or already existing container. * Gets a new or already existing container.

View File

@ -162,7 +162,7 @@ final class Coverage
$lastKey = count($array) - 1; $lastKey = count($array) - 1;
if (array_key_exists($lastKey, $array) && strpos($array[$lastKey], '..') !== false) { if (array_key_exists($lastKey, $array) && str_contains($array[$lastKey], '..')) {
[$from] = explode('..', $array[$lastKey]); [$from] = explode('..', $array[$lastKey]);
$array[$lastKey] = $line > $from ? sprintf('%s..%s', $from, $line) : sprintf('%s..%s', $line, $from); $array[$lastKey] = $line > $from ? sprintf('%s..%s', $from, $line) : sprintf('%s..%s', $line, $from);

View File

@ -8,19 +8,13 @@ use Closure;
final class Extendable final class Extendable
{ {
/**
* The extendable class.
*
* @var string
*/
private $extendableClass;
/** /**
* Creates a new extendable instance. * Creates a new extendable instance.
*/ */
public function __construct(string $extendableClass) public function __construct(
{ private string $extendableClass
$this->extendableClass = $extendableClass; ) {
// ..
} }
/** /**

View File

@ -6,8 +6,6 @@ namespace Pest\Support;
use Closure; use Closure;
use Pest\Expectation; use Pest\Expectation;
use Pest\PendingObjects\TestCall;
use PHPUnit\Framework\TestCase;
/** /**
* @internal * @internal
@ -15,13 +13,11 @@ use PHPUnit\Framework\TestCase;
final class HigherOrderCallables final class HigherOrderCallables
{ {
/** /**
* @var object * Creates a new Higher Order Callables instances.
*/ */
private $target; public function __construct(private object $target)
public function __construct(object $target)
{ {
$this->target = $target; // ..
} }
/** /**
@ -33,7 +29,7 @@ final class HigherOrderCallables
* *
* @return Expectation<TValue> * @return Expectation<TValue>
*/ */
public function expect($value) public function expect(mixed $value): Expectation
{ {
return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value); return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value);
} }
@ -47,17 +43,15 @@ final class HigherOrderCallables
* *
* @return Expectation<TValue> * @return Expectation<TValue>
*/ */
public function and($value) public function and(mixed $value)
{ {
return $this->expect($value); return $this->expect($value);
} }
/** /**
* Tap into the test case to perform an action and return the test case. * Tap into the test case to perform an action and return the test case.
*
* @return TestCall|TestCase|object
*/ */
public function tap(callable $callable) public function tap(callable $callable): object
{ {
Reflection::bindCallableWithData($callable); Reflection::bindCallableWithData($callable);

View File

@ -15,68 +15,31 @@ final class HigherOrderMessage
{ {
public const UNDEFINED_METHOD = 'Method %s does not exist'; public const UNDEFINED_METHOD = 'Method %s does not exist';
/**
* The filename where the function was originally called.
*
* @readonly
*
* @var string
*/
public $filename;
/**
* The line where the function was originally called.
*
* @readonly
*
* @var int
*/
public $line;
/**
* The method or property name to access.
*
* @readonly
*
* @var string
*/
public $name;
/**
* The arguments.
*
* @var array<int, mixed>|null
*
* @readonly
*/
public $arguments;
/** /**
* An optional condition that will determine if the message will be executed. * An optional condition that will determine if the message will be executed.
* *
* @var callable(): bool|null * @var (callable(): bool)|null
*/ */
public $condition = null; public $condition;
/** /**
* Creates a new higher order message. * Creates a new higher order message.
* *
* @param array<int, mixed>|null $arguments * @param array<int, mixed>|null $arguments
*/ */
public function __construct(string $filename, int $line, string $methodName, $arguments) public function __construct(
{ public string $filename,
$this->filename = $filename; public int $line,
$this->line = $line; public string $name,
$this->name = $methodName; public ?array $arguments
$this->arguments = $arguments; ) {
// ..
} }
/** /**
* Re-throws the given `$throwable` with the good line and filename. * Re-throws the given `$throwable` with the good line and filename.
*
* @return mixed
*/ */
public function call(object $target) public function call(object $target): mixed
{ {
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) { if (is_callable($this->condition) && call_user_func(Closure::bind($this->condition, $target)) === false) {
@ -91,7 +54,8 @@ final class HigherOrderMessage
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};
/* @phpstan-ignore-line */
} 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);
@ -122,10 +86,8 @@ final class HigherOrderMessage
/** /**
* Determines whether or not there exists a higher order callable with the message name. * Determines whether or not there exists a higher order callable with the message name.
*
* @return bool
*/ */
private function hasHigherOrderCallable() private function hasHigherOrderCallable(): bool
{ {
return in_array($this->name, get_class_methods(HigherOrderCallables::class), true); return in_array($this->name, get_class_methods(HigherOrderCallables::class), true);
} }
@ -133,7 +95,7 @@ final class HigherOrderMessage
private static function getUndefinedMethodMessage(object $target, string $methodName): string private static function getUndefinedMethodMessage(object $target, string $methodName): string
{ {
if (\PHP_MAJOR_VERSION >= 8) { if (\PHP_MAJOR_VERSION >= 8) {
return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', get_class($target), $methodName))); return sprintf(sprintf(self::UNDEFINED_METHOD, sprintf('%s::%s()', $target::class, $methodName)));
} }
return sprintf(self::UNDEFINED_METHOD, $methodName); return sprintf(self::UNDEFINED_METHOD, $methodName);

View File

@ -12,7 +12,7 @@ final class HigherOrderMessageCollection
/** /**
* @var array<int, HigherOrderMessage> * @var array<int, HigherOrderMessage>
*/ */
private $messages = []; private array $messages = [];
/** /**
* Adds a new higher order message to the collection. * Adds a new higher order message to the collection.
@ -63,9 +63,7 @@ final class HigherOrderMessageCollection
{ {
return array_reduce( return array_reduce(
$this->messages, $this->messages,
static function (int $total, HigherOrderMessage $message) use ($name): int { static fn (int $total, HigherOrderMessage $message): int => $total + (int) ($name === $message->name),
return $total + (int) ($name === $message->name);
},
0, 0,
); );
} }

View File

@ -15,19 +15,13 @@ final class HigherOrderTapProxy
{ {
private const UNDEFINED_PROPERTY = 'Undefined property: P\\'; private const UNDEFINED_PROPERTY = 'Undefined property: P\\';
/**
* The target being tapped.
*
* @var TestCase
*/
public $target;
/** /**
* Create a new tap proxy instance. * Create a new tap proxy instance.
*/ */
public function __construct(TestCase $target) public function __construct(
{ public TestCase $target
$this->target = $target; ) {
// ..
} }
/** /**

View File

@ -193,9 +193,7 @@ final class Reflection
} }
$arguments[$parameter->getName()] = implode('|', array_map( $arguments[$parameter->getName()] = implode('|', array_map(
static function (ReflectionNamedType $type): string { static fn (ReflectionNamedType $type): string => $type->getName(),
return $type->getName();
},
($types instanceof ReflectionNamedType) ($types instanceof ReflectionNamedType)
? [$types] // NOTE: normalize as list of to handle unions ? [$types] // NOTE: normalize as list of to handle unions
: $types->getTypes(), : $types->getTypes(),

View File

@ -33,7 +33,7 @@ final class Str
*/ */
public static function startsWith(string $target, string $search): bool public static function startsWith(string $target, string $search): bool
{ {
return substr($target, 0, strlen($search)) === $search; return str_starts_with($target, $search);
} }
/** /**

View File

@ -19,71 +19,51 @@ final class TestSuite
{ {
/** /**
* Holds the current test case. * Holds the current test case.
*
* @var TestCase|null
*/ */
public $test; public ?TestCase $test = null;
/** /**
* Holds the tests repository. * Holds the tests repository.
*
* @var TestRepository
*/ */
public $tests; public TestRepository $tests;
/** /**
* Holds the before each repository. * Holds the before each repository.
*
* @var BeforeEachRepository
*/ */
public $beforeEach; public BeforeEachRepository $beforeEach;
/** /**
* Holds the before all repository. * Holds the before all repository.
*
* @var BeforeAllRepository
*/ */
public $beforeAll; public BeforeAllRepository $beforeAll;
/** /**
* Holds the after each repository. * Holds the after each repository.
*
* @var AfterEachRepository
*/ */
public $afterEach; public AfterEachRepository $afterEach;
/** /**
* Holds the after all repository. * Holds the after all repository.
*
* @var AfterAllRepository
*/ */
public $afterAll; public AfterAllRepository $afterAll;
/** /**
* Holds the root path. * Holds the root path.
*
* @var string
*/ */
public $rootPath; public string $rootPath;
/**
* Holds the test path.
*
* @var string
*/
public $testPath;
/** /**
* Holds an instance of the test suite. * Holds an instance of the test suite.
*
* @var TestSuite
*/ */
private static $instance; private static ?TestSuite $instance = null;
/** /**
* Creates a new instance of the test suite. * Creates a new instance of the test suite.
*/ */
public function __construct(string $rootPath, string $testPath) public function __construct(string $rootPath, /**
* Holds the test path.
*/
public string $testPath)
{ {
$this->beforeAll = new BeforeAllRepository(); $this->beforeAll = new BeforeAllRepository();
$this->beforeEach = new BeforeEachRepository(); $this->beforeEach = new BeforeEachRepository();
@ -92,7 +72,6 @@ final class TestSuite
$this->afterAll = new AfterAllRepository(); $this->afterAll = new AfterAllRepository();
$this->rootPath = (string) realpath($rootPath); $this->rootPath = (string) realpath($rootPath);
$this->testPath = $testPath;
} }
/** /**