mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
Merge branch 'master' into next-1
This commit is contained in:
8
.github/workflows/static.yml
vendored
8
.github/workflows/static.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-version: 8.1
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
run: composer update --no-interaction --no-progress
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: vendor/bin/php-cs-fixer fix -v --allow-risky=yes --dry-run
|
||||
run: PHP_CS_FIXER_IGNORE_ENV=true vendor/bin/php-cs-fixer fix -v --allow-risky=yes --dry-run
|
||||
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-version: 8.1
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
@ -48,4 +48,4 @@ jobs:
|
||||
run: composer update --prefer-stable --no-interaction --no-progress
|
||||
|
||||
- name: Run PHPStan
|
||||
run: vendor/bin/phpstan analyse --no-progress
|
||||
run: vendor/bin/phpstan analyse --no-progress --debug
|
||||
|
||||
@ -60,8 +60,8 @@
|
||||
"bin/pest"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "php-cs-fixer fix -v",
|
||||
"test:lint": "php-cs-fixer fix -v --dry-run",
|
||||
"lint": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix -v",
|
||||
"test:lint": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix -v --dry-run",
|
||||
"test:types": "phpstan analyse --ansi --memory-limit=-1 --debug",
|
||||
"test:unit": "php bin/pest --colors=always --exclude-group=integration",
|
||||
"test:parallel": "exit 1",
|
||||
|
||||
@ -9,7 +9,6 @@ parameters:
|
||||
- src
|
||||
|
||||
checkMissingIterableValueType: true
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
reportUnmatchedIgnoredErrors: true
|
||||
|
||||
ignoreErrors:
|
||||
|
||||
@ -14,6 +14,8 @@ trait RetrievesValues
|
||||
*
|
||||
* Safely retrieve the value at the given key from an object or array.
|
||||
*
|
||||
* @template TRetrievableValue
|
||||
*
|
||||
* @param array<string, TRetrievableValue>|object $value
|
||||
* @param TRetrievableValue|null $default
|
||||
*
|
||||
|
||||
@ -7,6 +7,7 @@ namespace Pest\Concerns;
|
||||
use Closure;
|
||||
use Pest\Support\ChainableClosure;
|
||||
use Pest\Support\ExceptionTrace;
|
||||
use Pest\Support\Reflection;
|
||||
use Pest\TestSuite;
|
||||
use Throwable;
|
||||
|
||||
@ -210,7 +211,28 @@ trait Testable
|
||||
*/
|
||||
private function __resolveTestArguments(array $arguments): array
|
||||
{
|
||||
return array_map(fn ($data) => $data instanceof Closure ? $this->__callClosure($data, []) : $data, $arguments);
|
||||
if (count($arguments) !== 1) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
if (!$arguments[0] instanceof Closure) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
||||
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
|
||||
|
||||
if (in_array($testParameterTypes[0], ['Closure', 'callable'])) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
$boundDatasetResult = $this->__callClosure($arguments[0], []);
|
||||
|
||||
if (count($testParameterTypes) === 1 || !is_array($boundDatasetResult)) {
|
||||
return [$boundDatasetResult];
|
||||
}
|
||||
|
||||
return array_values($boundDatasetResult);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
19
src/Each.php
19
src/Each.php
@ -7,7 +7,9 @@ namespace Pest;
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @mixin Expectation
|
||||
* @template TValue
|
||||
*
|
||||
* @mixin Expectation<TValue>
|
||||
*/
|
||||
final class Each
|
||||
{
|
||||
@ -15,14 +17,21 @@ final class Each
|
||||
|
||||
/**
|
||||
* Creates an expectation on each item of the iterable "value".
|
||||
*
|
||||
* @param Expectation<TValue> $original
|
||||
*/
|
||||
public function __construct(private Expectation $original)
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @template TAndValue
|
||||
*
|
||||
* @param TAndValue $value
|
||||
*
|
||||
* @return Expectation<TAndValue>
|
||||
*/
|
||||
public function and(mixed $value): Expectation
|
||||
{
|
||||
@ -31,6 +40,8 @@ final class Each
|
||||
|
||||
/**
|
||||
* Creates the opposite expectation for the value.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function not(): Each
|
||||
{
|
||||
@ -43,6 +54,8 @@ final class Each
|
||||
* Dynamically calls methods on the class with the given arguments on each item.
|
||||
*
|
||||
* @param array<int|string, mixed> $arguments
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function __call(string $name, array $arguments): Each
|
||||
{
|
||||
@ -58,6 +71,8 @@ final class Each
|
||||
|
||||
/**
|
||||
* Dynamically calls methods on the class without any arguments on each item.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function __get(string $name): Each
|
||||
{
|
||||
|
||||
@ -45,9 +45,11 @@ final class Expectation
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @param TValue $value
|
||||
* @template TAndValue
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
* @param TAndValue $value
|
||||
*
|
||||
* @return self<TAndValue>
|
||||
*/
|
||||
public function and(mixed $value): Expectation
|
||||
{
|
||||
@ -56,6 +58,8 @@ final class Expectation
|
||||
|
||||
/**
|
||||
* Creates a new expectation with the decoded JSON value.
|
||||
*
|
||||
* @return self<mixed>
|
||||
*/
|
||||
public function json(): Expectation
|
||||
{
|
||||
@ -84,6 +88,8 @@ final class Expectation
|
||||
|
||||
/**
|
||||
* Send the expectation value to Ray along with all given arguments.
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function ray(mixed ...$arguments): self
|
||||
{
|
||||
@ -96,6 +102,8 @@ final class Expectation
|
||||
|
||||
/**
|
||||
* Creates the opposite expectation for the value.
|
||||
*
|
||||
* @return OppositeExpectation<TValue>
|
||||
*/
|
||||
public function not(): OppositeExpectation
|
||||
{
|
||||
@ -104,6 +112,8 @@ final class Expectation
|
||||
|
||||
/**
|
||||
* Creates an expectation on each item of the iterable "value".
|
||||
*
|
||||
* @return Each<TValue>
|
||||
*/
|
||||
public function each(callable $callback = null): Each
|
||||
{
|
||||
@ -125,7 +135,9 @@ final class Expectation
|
||||
*
|
||||
* @template TSequenceValue
|
||||
*
|
||||
* @param (callable(self, self): void)|TSequenceValue ...$callbacks
|
||||
* @param (callable(self<TValue>, self<string|int>): void)|TSequenceValue ...$callbacks
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function sequence(mixed ...$callbacks): Expectation
|
||||
{
|
||||
@ -167,15 +179,13 @@ final class Expectation
|
||||
* @template TMatchSubject of array-key
|
||||
*
|
||||
* @param (callable(): TMatchSubject)|TMatchSubject $subject
|
||||
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
|
||||
* @param array<TMatchSubject, (callable(self<TValue>): mixed)|TValue> $expressions
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function match(mixed $subject, array $expressions): Expectation
|
||||
{
|
||||
$subject = is_callable($subject)
|
||||
? $subject
|
||||
: fn () => $subject;
|
||||
|
||||
$subject = $subject();
|
||||
$subject = $subject instanceof Closure ? $subject() : $subject;
|
||||
|
||||
$matched = false;
|
||||
|
||||
@ -208,6 +218,8 @@ final class Expectation
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
* @param callable(Expectation<TValue>): mixed $callback
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function unless(callable|bool $condition, callable $callback): Expectation
|
||||
{
|
||||
@ -224,7 +236,9 @@ final class Expectation
|
||||
* Apply the callback if the given "condition" is truthy.
|
||||
*
|
||||
* @param (callable(): bool)|bool $condition
|
||||
* @param callable(Expectation<TValue>): mixed $callback
|
||||
* @param callable(self<TValue>): mixed $callback
|
||||
*
|
||||
* @return self<TValue>
|
||||
*/
|
||||
public function when(callable|bool $condition, callable $callback): Expectation
|
||||
{
|
||||
|
||||
@ -7,7 +7,6 @@ namespace Pest\Factories;
|
||||
use ParseError;
|
||||
use Pest\Concerns;
|
||||
use Pest\Contracts\HasPrintableTestCaseName;
|
||||
use Pest\Datasets;
|
||||
use Pest\Exceptions\DatasetMissing;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\Exceptions\TestAlreadyExist;
|
||||
@ -73,9 +72,10 @@ final class TestCaseFactory
|
||||
{
|
||||
$methodsUsingOnly = $this->methodsUsingOnly();
|
||||
|
||||
$methods = array_values(array_filter($this->methods, function ($method) use ($methodsUsingOnly) {
|
||||
return count($methodsUsingOnly) === 0 || in_array($method, $methodsUsingOnly, true);
|
||||
}));
|
||||
$methods = array_values(array_filter(
|
||||
$this->methods,
|
||||
fn ($method) => count($methodsUsingOnly) === 0 || in_array($method, $methodsUsingOnly, true)
|
||||
));
|
||||
|
||||
if (count($methods) > 0) {
|
||||
$this->evaluate($this->filename, $methods);
|
||||
@ -141,56 +141,10 @@ final class TestCaseFactory
|
||||
$classFQN .= $className;
|
||||
}
|
||||
|
||||
$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) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$annotations = (new $annotation())->__invoke($method, $annotations);
|
||||
}
|
||||
|
||||
if (count($method->datasets) > 0) {
|
||||
$dataProviderName = $methodName . '_dataset';
|
||||
$annotations[] = "@dataProvider $dataProviderName";
|
||||
|
||||
Datasets::with($method->filename, $methodName, $method->datasets);
|
||||
|
||||
$datasetsCode = <<<EOF
|
||||
|
||||
public function $dataProviderName()
|
||||
{
|
||||
return __PestDatasets::get(self::\$__filename, "$methodName");
|
||||
}
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
$annotations = implode('', array_map(
|
||||
static fn ($annotation) => sprintf("\n * %s", $annotation), $annotations,
|
||||
));
|
||||
|
||||
return <<<EOF
|
||||
|
||||
/**$annotations
|
||||
*/
|
||||
public function $methodName()
|
||||
{
|
||||
return \$this->__runTest(
|
||||
\$this->__test,
|
||||
...func_get_args(),
|
||||
);
|
||||
}
|
||||
|
||||
$datasetsCode
|
||||
EOF;
|
||||
}, $methods));
|
||||
$methodsCode = implode('', array_map(
|
||||
fn (TestCaseMethodFactory $methodFactory) => $methodFactory->buildForEvaluation(self::$annotations),
|
||||
$methods
|
||||
));
|
||||
|
||||
try {
|
||||
eval("
|
||||
|
||||
@ -5,8 +5,10 @@ declare(strict_types=1);
|
||||
namespace Pest\Factories;
|
||||
|
||||
use Closure;
|
||||
use Pest\Datasets;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\Factories\Concerns\HigherOrderable;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -17,6 +19,7 @@ use PHPUnit\Framework\TestCase;
|
||||
final class TestCaseMethodFactory
|
||||
{
|
||||
use HigherOrderable;
|
||||
|
||||
/**
|
||||
* Determines if the Test Case will be the "only" being run.
|
||||
*/
|
||||
@ -51,11 +54,9 @@ final class TestCaseMethodFactory
|
||||
public ?string $description,
|
||||
public ?Closure $closure,
|
||||
) {
|
||||
if ($this->closure === null) {
|
||||
$this->closure = function () {
|
||||
Assert::getCount() > 0 ?: self::markTestIncomplete(); // @phpstan-ignore-line
|
||||
};
|
||||
}
|
||||
$this->closure ??= function () {
|
||||
Assert::getCount() > 0 ?: self::markTestIncomplete(); // @phpstan-ignore-line
|
||||
};
|
||||
|
||||
$this->bootHigherOrderable();
|
||||
}
|
||||
@ -100,4 +101,68 @@ final class TestCaseMethodFactory
|
||||
{
|
||||
return count($this->datasets) > 0 || count($this->depends) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PHPUnit method as a string ready for evaluation.
|
||||
*
|
||||
* @param array<int, class-string> $annotationsToUse
|
||||
*/
|
||||
public function buildForEvaluation(array $annotationsToUse): string
|
||||
{
|
||||
if ($this->description === null) {
|
||||
throw ShouldNotHappen::fromMessage('The test description may not be empty.');
|
||||
}
|
||||
|
||||
$methodName = Str::evaluable($this->description);
|
||||
|
||||
$datasetsCode = '';
|
||||
$annotations = ['@test'];
|
||||
|
||||
foreach ($annotationsToUse as $annotation) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$annotations = (new $annotation())->__invoke($this, $annotations);
|
||||
}
|
||||
|
||||
if (count($this->datasets) > 0) {
|
||||
$dataProviderName = $methodName . '_dataset';
|
||||
$annotations[] = "@dataProvider $dataProviderName";
|
||||
$datasetsCode = $this->buildDatasetForEvaluation($methodName, $dataProviderName);
|
||||
}
|
||||
|
||||
$annotations = implode('', array_map(
|
||||
static fn ($annotation) => sprintf("\n * %s", $annotation), $annotations,
|
||||
));
|
||||
|
||||
return <<<EOF
|
||||
|
||||
/**$annotations
|
||||
*/
|
||||
public function $methodName()
|
||||
{
|
||||
return \$this->__runTest(
|
||||
\$this->__test,
|
||||
...func_get_args(),
|
||||
);
|
||||
}
|
||||
|
||||
$datasetsCode
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PHPUnit Data Provider as a string ready for evaluation.
|
||||
*/
|
||||
private function buildDatasetForEvaluation(string $methodName, string $dataProviderName): string
|
||||
{
|
||||
Datasets::with($this->filename, $methodName, $this->datasets);
|
||||
|
||||
return <<<EOF
|
||||
|
||||
public function $dataProviderName()
|
||||
{
|
||||
return __PestDatasets::get(self::\$__filename, "$methodName");
|
||||
}
|
||||
|
||||
EOF;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,11 @@ if (!function_exists('expect')) {
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @param mixed $value the Value
|
||||
* @template TValue
|
||||
*
|
||||
* @param TValue $value the Value
|
||||
*
|
||||
* @return Expectation<TValue>|Extendable
|
||||
*/
|
||||
function expect($value = null): Expectation|Extendable
|
||||
{
|
||||
|
||||
@ -4,19 +4,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest;
|
||||
|
||||
use Pest\Concerns\Expectable;
|
||||
use Pest\Concerns\RetrievesValues;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @mixin Expectation
|
||||
* @template TOriginalValue
|
||||
* @template TValue
|
||||
*
|
||||
* @mixin Expectation<TOriginalValue>
|
||||
*/
|
||||
final class HigherOrderExpectation
|
||||
{
|
||||
use Expectable;
|
||||
use RetrievesValues;
|
||||
|
||||
/**
|
||||
* @var Expectation<TValue>|Each<TValue>
|
||||
*/
|
||||
private Expectation|Each $expectation;
|
||||
|
||||
private bool $opposite = false;
|
||||
@ -25,6 +29,9 @@ final class HigherOrderExpectation
|
||||
|
||||
/**
|
||||
* Creates a new higher order expectation.
|
||||
*
|
||||
* @param Expectation<TOriginalValue> $original
|
||||
* @param TValue $value
|
||||
*/
|
||||
public function __construct(private Expectation $original, mixed $value)
|
||||
{
|
||||
@ -33,6 +40,8 @@ final class HigherOrderExpectation
|
||||
|
||||
/**
|
||||
* Creates the opposite expectation for the value.
|
||||
*
|
||||
* @return self<TOriginalValue, TValue>
|
||||
*/
|
||||
public function not(): HigherOrderExpectation
|
||||
{
|
||||
@ -41,14 +50,28 @@ final class HigherOrderExpectation
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Expectation.
|
||||
*
|
||||
* @template TExpectValue
|
||||
*
|
||||
* @param TExpectValue $value
|
||||
*
|
||||
* @return Expectation<TExpectValue>
|
||||
*/
|
||||
public function expect(mixed $value): Expectation
|
||||
{
|
||||
return new Expectation($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new expectation.
|
||||
*
|
||||
* @template TValue
|
||||
* @template TExpectValue
|
||||
*
|
||||
* @param TValue $value
|
||||
* @param TExpectValue $value
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
* @return Expectation<TExpectValue>
|
||||
*/
|
||||
public function and(mixed $value): Expectation
|
||||
{
|
||||
@ -59,6 +82,8 @@ final class HigherOrderExpectation
|
||||
* Dynamically calls methods on the class with the given arguments.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
|
||||
*/
|
||||
public function __call(string $name, array $arguments): self
|
||||
{
|
||||
@ -72,6 +97,8 @@ final class HigherOrderExpectation
|
||||
|
||||
/**
|
||||
* Accesses properties in the value or in the expectation.
|
||||
*
|
||||
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
|
||||
*/
|
||||
public function __get(string $name): self
|
||||
{
|
||||
@ -99,9 +126,12 @@ final class HigherOrderExpectation
|
||||
|
||||
/**
|
||||
* Retrieve the applicable value based on the current reset condition.
|
||||
*
|
||||
* @return TOriginalValue|TValue
|
||||
*/
|
||||
private function getValue(): mixed
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->shouldReset ? $this->original->value : $this->expectation->value;
|
||||
}
|
||||
|
||||
@ -109,6 +139,8 @@ final class HigherOrderExpectation
|
||||
* Performs the given assertion with the current expectation.
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return self<TOriginalValue, TValue>
|
||||
*/
|
||||
private function performAssertion(string $name, array $arguments): self
|
||||
{
|
||||
|
||||
@ -10,29 +10,34 @@ use SebastianBergmann\Exporter\Exporter;
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @mixin Expectation
|
||||
* @template TValue
|
||||
*
|
||||
* @mixin Expectation<TValue>
|
||||
*/
|
||||
final class OppositeExpectation
|
||||
{
|
||||
/**
|
||||
* Creates a new opposite expectation.
|
||||
*
|
||||
* @param Expectation<TValue> $original
|
||||
*/
|
||||
public function __construct(private Expectation $original)
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the value array not has the provided $keys.
|
||||
*
|
||||
* @param array<int, int|string> $keys
|
||||
*
|
||||
* @return Expectation<TValue>
|
||||
*/
|
||||
public function toHaveKeys(array $keys): Expectation
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
try {
|
||||
$this->original->toHaveKey($key);
|
||||
} catch (ExpectationFailedException $exception) {
|
||||
} catch (ExpectationFailedException) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -47,14 +52,14 @@ final class OppositeExpectation
|
||||
*
|
||||
* @param array<int, mixed> $arguments
|
||||
*
|
||||
* @return Expectation|never
|
||||
* @return Expectation<TValue>|Expectation<mixed>|never
|
||||
*/
|
||||
public function __call(string $name, array $arguments): Expectation
|
||||
{
|
||||
try {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->original->{$name}(...$arguments);
|
||||
} catch (ExpectationFailedException $exception) {
|
||||
} catch (ExpectationFailedException) {
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
@ -64,13 +69,13 @@ final class OppositeExpectation
|
||||
/**
|
||||
* Handle dynamic properties gets into the original expectation.
|
||||
*
|
||||
* @return Expectation|never
|
||||
* @return Expectation<TValue>|Expectation<mixed>|never
|
||||
*/
|
||||
public function __get(string $name): Expectation
|
||||
{
|
||||
try {
|
||||
$this->original->{$name}; // @phpstan-ignore-line
|
||||
} catch (ExpectationFailedException $exception) { // @phpstan-ignore-line
|
||||
} catch (ExpectationFailedException) { // @phpstan-ignore-line
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
|
||||
@ -38,6 +38,10 @@ final class HigherOrderMessage
|
||||
|
||||
/**
|
||||
* Re-throws the given `$throwable` with the good line and filename.
|
||||
*
|
||||
* @template TValue of object
|
||||
*
|
||||
* @param TValue $target
|
||||
*/
|
||||
public function call(object $target): mixed
|
||||
{
|
||||
@ -59,7 +63,7 @@ final class HigherOrderMessage
|
||||
Reflection::setPropertyValue($throwable, 'line', $this->line);
|
||||
|
||||
if ($throwable->getMessage() === self::getUndefinedMethodMessage($target, $this->name)) {
|
||||
/** @var ReflectionClass $reflection */
|
||||
/** @var ReflectionClass<TValue> $reflection */
|
||||
$reflection = new ReflectionClass($target);
|
||||
/* @phpstan-ignore-next-line */
|
||||
$reflection = $reflection->getParentClass() ?: $reflection;
|
||||
|
||||
@ -118,11 +118,14 @@ final class Reflection
|
||||
/**
|
||||
* Sets the property value of the given object.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @template TValue of object
|
||||
*
|
||||
* @param TValue $object
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function setPropertyValue(object $object, string $property, $value): void
|
||||
{
|
||||
/** @var ReflectionClass $reflectionClass */
|
||||
/** @var ReflectionClass<TValue> $reflectionClass */
|
||||
$reflectionClass = new ReflectionClass($object);
|
||||
|
||||
$reflectionProperty = null;
|
||||
@ -202,4 +205,12 @@ final class Reflection
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getFunctionVariable(Closure $function, string $key)
|
||||
{
|
||||
return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,11 +96,20 @@
|
||||
✓ more than two datasets with (2) / (4) / (5)
|
||||
✓ more than two datasets with (2) / (4) / (6)
|
||||
✓ more than two datasets did the job right
|
||||
✓ it can resolve a dataset after the test case is available with (Closure Object (...))
|
||||
✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #1
|
||||
✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #2
|
||||
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #1
|
||||
✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #2
|
||||
✓ it can resolve a dataset after the test case is available with shared array sets with (Closure Object (...)) #1
|
||||
✓ it can resolve a dataset after the test case is available with shared array sets with (Closure Object (...)) #2
|
||||
✓ it resolves a potential bound dataset logically with ('foo', Closure Object (...))
|
||||
✓ it resolves a potential bound dataset logically even when the closure comes first with (Closure Object (...), 'bar')
|
||||
✓ it will not resolve a closure if it is type hinted as a closure with (Closure Object (...)) #1
|
||||
✓ it will not resolve a closure if it is type hinted as a closure with (Closure Object (...)) #2
|
||||
✓ it will not resolve a closure if it is type hinted as a callable with (Closure Object (...)) #1
|
||||
✓ it will not resolve a closure if it is type hinted as a callable with (Closure Object (...)) #2
|
||||
✓ it can correctly resolve a bound dataset that returns an array with (Closure Object (...))
|
||||
✓ it can correctly resolve a bound dataset that returns an array but wants to be spread with (Closure Object (...))
|
||||
|
||||
PASS Tests\Features\Exceptions
|
||||
✓ it gives access the the underlying expectException
|
||||
|
||||
@ -234,6 +234,7 @@ it('can resolve a dataset after the test case is available', function ($result)
|
||||
expect($result)->toBe('bar');
|
||||
})->with([
|
||||
function () { return $this->foo; },
|
||||
[function () { return $this->foo; }],
|
||||
]);
|
||||
|
||||
it('can resolve a dataset after the test case is available with shared yield sets', function ($result) {
|
||||
@ -243,3 +244,43 @@ it('can resolve a dataset after the test case is available with shared yield set
|
||||
it('can resolve a dataset after the test case is available with shared array sets', function ($result) {
|
||||
expect($result)->toBeInt()->toBeLessThan(3);
|
||||
})->with('bound.array');
|
||||
|
||||
it('resolves a potential bound dataset logically', function ($foo, $bar) {
|
||||
expect($foo)->toBe('foo');
|
||||
expect($bar())->toBe('bar');
|
||||
})->with([
|
||||
['foo', function () { return 'bar'; }], // This should be passed as a closure because we've passed multiple arguments
|
||||
]);
|
||||
|
||||
it('resolves a potential bound dataset logically even when the closure comes first', function ($foo, $bar) {
|
||||
expect($foo())->toBe('foo');
|
||||
expect($bar)->toBe('bar');
|
||||
})->with([
|
||||
[function () { return 'foo'; }, 'bar'], // This should be passed as a closure because we've passed multiple arguments
|
||||
]);
|
||||
|
||||
it('will not resolve a closure if it is type hinted as a closure', function (Closure $data) {
|
||||
expect($data())->toBeString();
|
||||
})->with([
|
||||
function () { return 'foo'; },
|
||||
function () { return 'bar'; },
|
||||
]);
|
||||
|
||||
it('will not resolve a closure if it is type hinted as a callable', function (callable $data) {
|
||||
expect($data())->toBeString();
|
||||
})->with([
|
||||
function () { return 'foo'; },
|
||||
function () { return 'bar'; },
|
||||
]);
|
||||
|
||||
it('can correctly resolve a bound dataset that returns an array', function (array $data) {
|
||||
expect($data)->toBe(['foo', 'bar', 'baz']);
|
||||
})->with([
|
||||
function () { return ['foo', 'bar', 'baz']; },
|
||||
]);
|
||||
|
||||
it('can correctly resolve a bound dataset that returns an array but wants to be spread', function (string $foo, string $bar, string $baz) {
|
||||
expect([$foo, $bar, $baz])->toBe(['foo', 'bar', 'baz']);
|
||||
})->with([
|
||||
function () { return ['foo', 'bar', 'baz']; },
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user