mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
@ -4,7 +4,7 @@ includes:
|
||||
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
|
||||
|
||||
parameters:
|
||||
level: 5
|
||||
level: max
|
||||
paths:
|
||||
- src
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ final class BootSubscribers
|
||||
/**
|
||||
* The Kernel subscribers.
|
||||
*
|
||||
* @var array<int, class-string>
|
||||
* @var array<int, class-string<\PHPUnit\Event\Subscriber>>
|
||||
*/
|
||||
private static array $subscribers = [
|
||||
Subscribers\EnsureConfigurationIsValid::class,
|
||||
|
||||
@ -7,7 +7,9 @@ namespace Pest;
|
||||
use Closure;
|
||||
use Pest\Exceptions\DatasetAlreadyExist;
|
||||
use Pest\Exceptions\DatasetDoesNotExist;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
use function sprintf;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
@ -18,14 +20,14 @@ final class Datasets
|
||||
/**
|
||||
* Holds the datasets.
|
||||
*
|
||||
* @var array<int|string, Closure|iterable<int|string, mixed>>
|
||||
* @var array<string, Closure|iterable<int|string, mixed>>
|
||||
*/
|
||||
private static array $datasets = [];
|
||||
|
||||
/**
|
||||
* Holds the withs.
|
||||
*
|
||||
* @var array<string, \Closure|iterable|string>
|
||||
* @var array<array<string, Closure|iterable<int|string, mixed>|string>>
|
||||
*/
|
||||
private static array $withs = [];
|
||||
|
||||
@ -44,23 +46,31 @@ final class Datasets
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given.
|
||||
* Sets the given "with".
|
||||
*
|
||||
* @param Closure|iterable<int|string, mixed>|string $with
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $with
|
||||
*/
|
||||
public static function with(string $filename, string $description, Closure|iterable|string $with): void
|
||||
public static function with(string $filename, string $description, array $with): void
|
||||
{
|
||||
self::$withs[$filename . '>>>' . $description] = $with;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Closure|iterable<int|string, mixed>
|
||||
* @return Closure|iterable<int|string, mixed>|never
|
||||
*
|
||||
* @throws ShouldNotHappen
|
||||
*/
|
||||
public static function get(string $filename, string $description): Closure|iterable
|
||||
{
|
||||
$dataset = self::$withs[$filename . '>>>' . $description];
|
||||
|
||||
return self::resolve($description, $dataset);
|
||||
$dataset = self::resolve($description, $dataset);
|
||||
|
||||
if ($dataset === null) {
|
||||
throw ShouldNotHappen::fromMessage('Dataset [%s] not resolvable.');
|
||||
}
|
||||
|
||||
return $dataset;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,38 +89,40 @@ final class Datasets
|
||||
|
||||
$dataset = self::processDatasets($dataset);
|
||||
|
||||
$datasetCombinations = self::getDataSetsCombinations($dataset);
|
||||
$datasetCombinations = self::getDatasetsCombinations($dataset);
|
||||
|
||||
$dataSetDescriptions = [];
|
||||
$dataSetValues = [];
|
||||
$datasetDescriptions = [];
|
||||
$datasetValues = [];
|
||||
|
||||
foreach ($datasetCombinations as $datasetCombination) {
|
||||
$partialDescriptions = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($datasetCombination as $dataset_data) {
|
||||
$partialDescriptions[] = $dataset_data['label'];
|
||||
$values = array_merge($values, $dataset_data['values']);
|
||||
foreach ($datasetCombination as $datasetCombinationElement) {
|
||||
$partialDescriptions[] = $datasetCombinationElement['label'];
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$values = array_merge($values, $datasetCombinationElement['values']);
|
||||
}
|
||||
|
||||
$dataSetDescriptions[] = $description . ' with ' . implode(' / ', $partialDescriptions);
|
||||
$dataSetValues[] = $values;
|
||||
$datasetDescriptions[] = $description . ' with ' . implode(' / ', $partialDescriptions);
|
||||
$datasetValues[] = $values;
|
||||
}
|
||||
|
||||
foreach (array_count_values($dataSetDescriptions) as $descriptionToCheck => $count) {
|
||||
foreach (array_count_values($datasetDescriptions) as $descriptionToCheck => $count) {
|
||||
if ($count > 1) {
|
||||
$index = 1;
|
||||
foreach ($dataSetDescriptions as $i => $dataSetDescription) {
|
||||
if ($dataSetDescription === $descriptionToCheck) {
|
||||
$dataSetDescriptions[$i] .= sprintf(' #%d', $index++);
|
||||
foreach ($datasetDescriptions as $i => $datasetDescription) {
|
||||
if ($datasetDescription === $descriptionToCheck) {
|
||||
$datasetDescriptions[$i] .= sprintf(' #%d', $index++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$namedData = [];
|
||||
foreach ($dataSetDescriptions as $i => $dataSetDescription) {
|
||||
$namedData[$dataSetDescription] = $dataSetValues[$i];
|
||||
foreach ($datasetDescriptions as $i => $datasetDescription) {
|
||||
$namedData[$datasetDescription] = $datasetValues[$i];
|
||||
}
|
||||
|
||||
return $namedData;
|
||||
@ -119,7 +131,7 @@ final class Datasets
|
||||
/**
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
|
||||
*
|
||||
* @return array<array>
|
||||
* @return array<array<mixed>>
|
||||
*/
|
||||
private static function processDatasets(array $datasets): array
|
||||
{
|
||||
@ -144,10 +156,11 @@ final class Datasets
|
||||
$datasets[$index] = iterator_to_array($datasets[$index]);
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
foreach ($datasets[$index] as $key => $values) {
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
$processedDataset[] = [
|
||||
'label' => self::getDataSetDescription($key, $values),
|
||||
'label' => self::getDatasetDescription($key, $values), //@phpstan-ignore-line
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
@ -159,11 +172,11 @@ final class Datasets
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array> $combinations
|
||||
* @param array<array<mixed>> $combinations
|
||||
*
|
||||
* @return array<array>
|
||||
* @return array<array<array<mixed>>>
|
||||
*/
|
||||
private static function getDataSetsCombinations(array $combinations): array
|
||||
private static function getDatasetsCombinations(array $combinations): array
|
||||
{
|
||||
$result = [[]];
|
||||
foreach ($combinations as $index => $values) {
|
||||
@ -176,20 +189,21 @@ final class Datasets
|
||||
$result = $tmp;
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $data
|
||||
*/
|
||||
private static function getDataSetDescription(int|string $key, array $data): string
|
||||
private static function getDatasetDescription(int|string $key, array $data): string
|
||||
{
|
||||
$exporter = new Exporter();
|
||||
|
||||
if (is_int($key)) {
|
||||
return \sprintf('(%s)', $exporter->shortenedRecursiveExport($data));
|
||||
return sprintf('(%s)', $exporter->shortenedRecursiveExport($data));
|
||||
}
|
||||
|
||||
return \sprintf('data set "%s"', $key);
|
||||
return sprintf('data set "%s"', $key);
|
||||
}
|
||||
}
|
||||
|
||||
23
src/Exceptions/InvalidExpectationValue.php
Normal file
23
src/Exceptions/InvalidExpectationValue.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidExpectationValue extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @return never
|
||||
*
|
||||
* @throws self
|
||||
*/
|
||||
public static function expected(string $type): void
|
||||
{
|
||||
throw new self(sprintf('Invalid expectation value type. Expected [%s].', $type));
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ use Closure;
|
||||
use InvalidArgumentException;
|
||||
use Pest\Concerns\Extendable;
|
||||
use Pest\Concerns\RetrievesValues;
|
||||
use Pest\Exceptions\InvalidExpectationValue;
|
||||
use Pest\Support\Arr;
|
||||
use Pest\Support\NullClosure;
|
||||
use PHPUnit\Framework\Assert;
|
||||
@ -68,6 +69,10 @@ final class Expectation
|
||||
*/
|
||||
public function json(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
return $this->toBeJson()->and(json_decode($this->value, true));
|
||||
}
|
||||
|
||||
@ -355,8 +360,12 @@ final class Expectation
|
||||
{
|
||||
foreach ($needles as $needle) {
|
||||
if (is_string($this->value)) {
|
||||
Assert::assertStringContainsString($needle, $this->value);
|
||||
// @phpstan-ignore-next-line
|
||||
Assert::assertStringContainsString((string) $needle, $this->value);
|
||||
} else {
|
||||
if (!is_iterable($this->value)) {
|
||||
InvalidExpectationValue::expected('iterable');
|
||||
}
|
||||
Assert::assertContains($needle, $this->value);
|
||||
}
|
||||
}
|
||||
@ -371,6 +380,10 @@ final class Expectation
|
||||
*/
|
||||
public function toStartWith(string $expected): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertStringStartsWith($expected, $this->value);
|
||||
|
||||
return $this;
|
||||
@ -383,6 +396,10 @@ final class Expectation
|
||||
*/
|
||||
public function toEndWith(string $expected): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertStringEndsWith($expected, $this->value);
|
||||
|
||||
return $this;
|
||||
@ -423,6 +440,10 @@ final class Expectation
|
||||
*/
|
||||
public function toHaveCount(int $count): Expectation
|
||||
{
|
||||
if (!is_countable($this->value) && !is_iterable($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertCount($count, $this->value);
|
||||
|
||||
return $this;
|
||||
@ -435,6 +456,7 @@ final class Expectation
|
||||
{
|
||||
$this->toBeObject();
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
Assert::assertTrue(property_exists($this->value, $name));
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
@ -646,6 +668,8 @@ final class Expectation
|
||||
public function toBeJson(): Expectation
|
||||
{
|
||||
Assert::assertIsString($this->value);
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
Assert::assertJson($this->value);
|
||||
|
||||
return $this;
|
||||
@ -716,6 +740,10 @@ final class Expectation
|
||||
*/
|
||||
public function toBeDirectory(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertDirectoryExists($this->value);
|
||||
|
||||
return $this;
|
||||
@ -726,6 +754,10 @@ final class Expectation
|
||||
*/
|
||||
public function toBeReadableDirectory(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertDirectoryIsReadable($this->value);
|
||||
|
||||
return $this;
|
||||
@ -736,6 +768,10 @@ final class Expectation
|
||||
*/
|
||||
public function toBeWritableDirectory(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertDirectoryIsWritable($this->value);
|
||||
|
||||
return $this;
|
||||
@ -746,6 +782,10 @@ final class Expectation
|
||||
*/
|
||||
public function toBeFile(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertFileExists($this->value);
|
||||
|
||||
return $this;
|
||||
@ -756,6 +796,10 @@ final class Expectation
|
||||
*/
|
||||
public function toBeReadableFile(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
|
||||
Assert::assertFileIsReadable($this->value);
|
||||
|
||||
return $this;
|
||||
@ -766,6 +810,9 @@ final class Expectation
|
||||
*/
|
||||
public function toBeWritableFile(): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
Assert::assertFileIsWritable($this->value);
|
||||
|
||||
return $this;
|
||||
@ -810,6 +857,10 @@ final class Expectation
|
||||
public function toMatchObject(iterable|object $object): Expectation
|
||||
{
|
||||
foreach ((array) $object as $property => $value) {
|
||||
if (!is_object($this->value) && !is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('object|string');
|
||||
}
|
||||
|
||||
Assert::assertTrue(property_exists($this->value, $property));
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
@ -833,6 +884,9 @@ final class Expectation
|
||||
*/
|
||||
public function toMatch(string $expression): Expectation
|
||||
{
|
||||
if (!is_string($this->value)) {
|
||||
InvalidExpectationValue::expected('string');
|
||||
}
|
||||
Assert::assertMatchesRegularExpression($expression, $this->value);
|
||||
|
||||
return $this;
|
||||
@ -892,10 +946,10 @@ final class Expectation
|
||||
}
|
||||
|
||||
if (!class_exists($exception)) {
|
||||
throw new ExpectationFailedException("Exception with message \"{$exception}\" not thrown.");
|
||||
throw new ExpectationFailedException("Exception with message \"$exception\" not thrown.");
|
||||
}
|
||||
|
||||
throw new ExpectationFailedException("Exception \"{$exception}\" not thrown.");
|
||||
throw new ExpectationFailedException("Exception \"$exception\" not thrown.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -920,7 +974,7 @@ final class Expectation
|
||||
*/
|
||||
public function __call(string $method, array $parameters)
|
||||
{
|
||||
if (!static::hasExtend($method)) {
|
||||
if (!Expectation::hasExtend($method)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
return new HigherOrderExpectation($this, $this->value->$method(...$parameters));
|
||||
}
|
||||
@ -934,7 +988,8 @@ final class Expectation
|
||||
*/
|
||||
public function __get(string $name): Expectation|OppositeExpectation|Each|HigherOrderExpectation
|
||||
{
|
||||
if (!method_exists($this, $name) && !static::hasExtend($name)) {
|
||||
if (!method_exists($this, $name) && !Expectation::hasExtend($name)) {
|
||||
//@phpstan-ignore-next-line
|
||||
return new HigherOrderExpectation($this, $this->retrieve($name, $this->value));
|
||||
}
|
||||
|
||||
|
||||
@ -14,8 +14,12 @@ final class Depends
|
||||
{
|
||||
/**
|
||||
* Adds annotations regarding the "depends" feature.
|
||||
*
|
||||
* @param array<int, string> $annotations
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function add(TestCaseMethodFactory $method, array $annotations): array
|
||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
|
||||
{
|
||||
foreach ($method->depends as $depend) {
|
||||
$depend = Str::evaluable($depend);
|
||||
|
||||
@ -13,8 +13,12 @@ final class Groups
|
||||
{
|
||||
/**
|
||||
* Adds annotations regarding the "groups" feature.
|
||||
*
|
||||
* @param array<int, string> $annotations
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function add(TestCaseMethodFactory $method, array $annotations): array
|
||||
public function __invoke(TestCaseMethodFactory $method, array $annotations): array
|
||||
{
|
||||
foreach ($method->groups as $group) {
|
||||
$annotations[] = "@group $group";
|
||||
|
||||
@ -73,9 +73,9 @@ final class TestCaseFactory
|
||||
{
|
||||
$methodsUsingOnly = $this->methodsUsingOnly();
|
||||
|
||||
$methods = array_filter($this->methods, function ($method) use ($methodsUsingOnly) {
|
||||
$methods = array_values(array_filter($this->methods, function ($method) use ($methodsUsingOnly) {
|
||||
return count($methodsUsingOnly) === 0 || in_array($method, $methodsUsingOnly, true);
|
||||
});
|
||||
}));
|
||||
|
||||
if (count($methods) > 0) {
|
||||
$this->evaluate($this->filename, $methods);
|
||||
@ -98,6 +98,8 @@ final class TestCaseFactory
|
||||
|
||||
/**
|
||||
* Creates a Test Case class using a runtime evaluate.
|
||||
*
|
||||
* @param array<int, TestCaseMethodFactory> $methods
|
||||
*/
|
||||
public function evaluate(string $filename, array $methods): string
|
||||
{
|
||||
@ -140,13 +142,18 @@ final class TestCaseFactory
|
||||
}
|
||||
|
||||
$methodsCode = implode('', array_map(static function (TestCaseMethodFactory $method): string {
|
||||
if ($method->description === null) {
|
||||
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
|
||||
}
|
||||
|
||||
$methodName = Str::evaluable($method->description);
|
||||
|
||||
$datasetsCode = '';
|
||||
$annotations = ['@test'];
|
||||
|
||||
foreach (self::$annotations as $annotation) {
|
||||
$annotations = (new $annotation())->add($method, $annotations);
|
||||
/** @phpstan-ignore-next-line */
|
||||
$annotations = (new $annotation())->__invoke($method, $annotations);
|
||||
}
|
||||
|
||||
if (count($method->datasets) > 0) {
|
||||
@ -221,6 +228,10 @@ EOF;
|
||||
}
|
||||
|
||||
if (!$method->receivesArguments()) {
|
||||
if ($method->closure === null) {
|
||||
throw ShouldNotHappen::fromMessage('The test closure may not be empty.');
|
||||
}
|
||||
|
||||
$arguments = Reflection::getFunctionArguments($method->closure);
|
||||
|
||||
if (count($arguments) > 0) {
|
||||
@ -237,6 +248,10 @@ EOF;
|
||||
public function getMethod(string $methodName): TestCaseMethodFactory
|
||||
{
|
||||
foreach ($this->methods as $method) {
|
||||
if ($method->description === null) {
|
||||
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
|
||||
}
|
||||
|
||||
if (Str::evaluable($method->description) === $methodName) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ final class TestCaseMethodFactory
|
||||
$testCase->chains->chain($this);
|
||||
$method->chains->chain($this);
|
||||
|
||||
return call_user_func(Closure::bind($closure, $this, $this::class), ...func_get_args());
|
||||
return \Pest\Support\Closure::bind($closure, $this, $this::class)(...func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -70,12 +70,14 @@ if (!function_exists('uses')) {
|
||||
/**
|
||||
* The uses function binds the given
|
||||
* arguments to test closures.
|
||||
*
|
||||
* @param class-string ...$classAndTraits
|
||||
*/
|
||||
function uses(string ...$classAndTraits): UsesCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
return new UsesCall($filename, $classAndTraits);
|
||||
return new UsesCall($filename, array_values($classAndTraits));
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +113,10 @@ if (!function_exists('it')) {
|
||||
{
|
||||
$description = sprintf('it %s', $description);
|
||||
|
||||
return test($description, $closure);
|
||||
/** @var TestCall $test */
|
||||
$test = test($description, $closure);
|
||||
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -80,7 +80,10 @@ final class HigherOrderExpectation
|
||||
}
|
||||
|
||||
if (!$this->expectationHasMethod($name)) {
|
||||
return new self($this->original, $this->retrieve($name, $this->getValue()));
|
||||
/** @var array<string, mixed>|object $value */
|
||||
$value = $this->getValue();
|
||||
|
||||
return new self($this->original, $this->retrieve($name, $value));
|
||||
}
|
||||
|
||||
return $this->performAssertion($name, []);
|
||||
|
||||
@ -37,6 +37,7 @@ final class Kernel
|
||||
public static function boot(): self
|
||||
{
|
||||
foreach (self::$bootstrappers as $bootstrapper) {
|
||||
//@phpstan-ignore-next-line
|
||||
(new $bootstrapper())->__invoke();
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ final class OppositeExpectation
|
||||
foreach ($keys as $key) {
|
||||
try {
|
||||
$this->original->toHaveKey($key);
|
||||
} catch (ExpectationFailedException) {
|
||||
} catch (ExpectationFailedException $exception) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ final class OppositeExpectation
|
||||
try {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->original->{$name}(...$arguments);
|
||||
} catch (ExpectationFailedException) {
|
||||
} catch (ExpectationFailedException $exception) {
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ final class OppositeExpectation
|
||||
{
|
||||
try {
|
||||
$this->original->{$name}; // @phpstan-ignore-line
|
||||
} catch (ExpectationFailedException) { // @phpstan-ignore-line
|
||||
} catch (ExpectationFailedException $exception) { // @phpstan-ignore-line
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ final class UsesCall
|
||||
*/
|
||||
public function group(string ...$groups): UsesCall
|
||||
{
|
||||
$this->groups = $groups;
|
||||
$this->groups = array_values($groups);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ final class Plugin
|
||||
|
||||
/**
|
||||
* Lazy loads an `uses` call on the context of plugins.
|
||||
*
|
||||
* @param class-string ...$traits
|
||||
*/
|
||||
public static function uses(string ...$traits): void
|
||||
{
|
||||
|
||||
@ -80,7 +80,10 @@ final class Coverage implements AddsOutput, HandlesArguments
|
||||
}
|
||||
|
||||
if ($input->getOption(self::MIN_OPTION) !== null) {
|
||||
$this->coverageMin = (float) $input->getOption(self::MIN_OPTION);
|
||||
/** @var int|float $minOption */
|
||||
$minOption = $input->getOption(self::MIN_OPTION);
|
||||
|
||||
$this->coverageMin = (float) $minOption;
|
||||
}
|
||||
|
||||
return $originals;
|
||||
|
||||
@ -23,7 +23,7 @@ final class TestRepository
|
||||
private array $testCases = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, array<int, string|Closure>>>
|
||||
* @var array<string, array{0: array<int, string>, 1: array<int, string>, 2: array<int, string|Closure>}>
|
||||
*/
|
||||
private array $uses = [];
|
||||
|
||||
@ -80,7 +80,7 @@ final class TestRepository
|
||||
}
|
||||
}
|
||||
|
||||
public function get($filename): TestCaseFactory
|
||||
public function get(string $filename): TestCaseFactory
|
||||
{
|
||||
return $this->testCases[$filename];
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ final class Arr
|
||||
{
|
||||
/**
|
||||
* Checks if the given array has the given key.
|
||||
*
|
||||
* @param array<array-key, mixed> $array
|
||||
*/
|
||||
public static function has(array $array, string|int $key): bool
|
||||
{
|
||||
@ -33,6 +35,8 @@ final class Arr
|
||||
|
||||
/**
|
||||
* Gets the given key value.
|
||||
*
|
||||
* @param array<array-key, mixed> $array
|
||||
*/
|
||||
public static function get(array $array, string|int $key, mixed $default = null): mixed
|
||||
{
|
||||
|
||||
@ -22,8 +22,8 @@ final class ChainableClosure
|
||||
throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.');
|
||||
}
|
||||
|
||||
call_user_func_array(Closure::bind($closure, $this, $this::class), func_get_args());
|
||||
call_user_func_array(Closure::bind($next, $this, $this::class), func_get_args());
|
||||
\Pest\Support\Closure::bind($closure, $this, $this::class)(...func_get_args());
|
||||
\Pest\Support\Closure::bind($next, $this, $this::class)(...func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
@ -33,8 +33,8 @@ final class ChainableClosure
|
||||
public static function fromStatic(Closure $closure, Closure $next): Closure
|
||||
{
|
||||
return static function () use ($closure, $next): void {
|
||||
call_user_func_array(Closure::bind($closure, null, self::class), func_get_args());
|
||||
call_user_func_array(Closure::bind($next, null, self::class), func_get_args());
|
||||
\Pest\Support\Closure::bind($closure, null, self::class)(...func_get_args());
|
||||
\Pest\Support\Closure::bind($next, null, self::class)(...func_get_args());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
36
src/Support/Closure.php
Normal file
36
src/Support/Closure.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Support;
|
||||
|
||||
use Closure as BaseClosure;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Closure
|
||||
{
|
||||
/**
|
||||
* Binds the given closure to the given "this".
|
||||
*
|
||||
* @return BaseClosure|never
|
||||
*
|
||||
* @throws ShouldNotHappen
|
||||
*/
|
||||
public static function bind(BaseClosure|null $closure, ?object $newThis, object|string|null $newScope = 'static'): BaseClosure
|
||||
{
|
||||
if ($closure == null) {
|
||||
throw ShouldNotHappen::fromMessage('Could not bind null closure.');
|
||||
}
|
||||
|
||||
$closure = BaseClosure::bind($closure, $newThis, $newScope);
|
||||
|
||||
if ($closure == false) {
|
||||
throw ShouldNotHappen::fromMessage('Could not bind closure.');
|
||||
}
|
||||
|
||||
return $closure;
|
||||
}
|
||||
}
|
||||
@ -35,16 +35,16 @@ final class Container
|
||||
/**
|
||||
* Gets a dependency from the container.
|
||||
*
|
||||
* @return object
|
||||
* @param class-string $id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $id)
|
||||
{
|
||||
if (array_key_exists($id, $this->instances)) {
|
||||
return $this->instances[$id];
|
||||
if (!array_key_exists($id, $this->instances)) {
|
||||
$this->instances[$id] = $this->build($id);
|
||||
}
|
||||
|
||||
$this->instances[$id] = $this->build($id);
|
||||
|
||||
return $this->instances[$id];
|
||||
}
|
||||
|
||||
@ -60,6 +60,8 @@ final class Container
|
||||
|
||||
/**
|
||||
* Tries to build the given instance.
|
||||
*
|
||||
* @param class-string $id
|
||||
*/
|
||||
private function build(string $id): object
|
||||
{
|
||||
@ -83,6 +85,7 @@ final class Container
|
||||
}
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
return $this->get($candidate);
|
||||
},
|
||||
$constructor->getParameters()
|
||||
|
||||
@ -50,6 +50,8 @@ final class ExceptionTrace
|
||||
|
||||
$property = new ReflectionProperty($t, 'serializableTrace');
|
||||
$property->setAccessible(true);
|
||||
|
||||
/** @var array<int, array<string, string>> $trace */
|
||||
$trace = $property->getValue($t);
|
||||
|
||||
$cleanedTrace = [];
|
||||
|
||||
@ -25,13 +25,16 @@ final class HigherOrderCallables
|
||||
*
|
||||
* Create a new expectation. Callable values will be executed prior to returning the new expectation.
|
||||
*
|
||||
* @param (callable():TValue)|TValue $value
|
||||
* @param (Closure():TValue)|TValue $value
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function expect(mixed $value): Expectation
|
||||
{
|
||||
return new Expectation($value instanceof Closure ? Reflection::bindCallableWithData($value) : $value);
|
||||
/** @var TValue $value */
|
||||
$value = $value instanceof Closure ? Reflection::bindCallableWithData($value) : $value;
|
||||
|
||||
return new Expectation($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -77,7 +77,7 @@ final class HigherOrderMessage
|
||||
*/
|
||||
public function when(callable $condition): self
|
||||
{
|
||||
$this->condition = $condition;
|
||||
$this->condition = Closure::fromCallable($condition);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ final class HigherOrderMessageCollection
|
||||
public function chain(object $target): void
|
||||
{
|
||||
foreach ($this->messages as $message) {
|
||||
//@phpstan-ignore-next-line
|
||||
$target = $message->call($target) ?? $target;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user