mirror of
https://github.com/pestphp/pest.git
synced 2026-03-07 08:17:22 +01:00
implemented pipe closure with $next as the first parameter
This commit is contained in:
@ -29,12 +29,17 @@ trait Extendable
|
||||
static::$extends[$name] = $extend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a a pipe to be applied before an expectation is checked.
|
||||
*/
|
||||
public static function pipe(string $name, Closure $pipe): void
|
||||
{
|
||||
self::$pipes[$name][] = $pipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recister an interceptor that should replace an existing expectation.
|
||||
*
|
||||
* @param string|Closure $filter
|
||||
*/
|
||||
public static function intercept(string $name, $filter, Closure $handler): void
|
||||
@ -46,9 +51,7 @@ trait Extendable
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
self::pipe($name, function (...$arguments) use ($handler, $filter) {
|
||||
$next = array_pop($arguments);
|
||||
|
||||
self::pipe($name, function ($next, ...$arguments) use ($handler, $filter) {
|
||||
//@phpstan-ignore-next-line
|
||||
if ($filter($this->value)) {
|
||||
//@phpstan-ignore-next-line
|
||||
@ -57,7 +60,7 @@ trait Extendable
|
||||
return;
|
||||
}
|
||||
|
||||
$next(...$arguments);
|
||||
$next();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class PipeException extends Exception
|
||||
final class PipeException extends Exception
|
||||
{
|
||||
public static function optionalParmetersShouldBecomeRequired(string $expectationName): PipeException
|
||||
{
|
||||
return new self("You're attempting to pipe '$expectationName', but in pipelines optional parmeters should be declared as required)");
|
||||
}
|
||||
|
||||
public static function expectationNotFound($expectationName): PipeException
|
||||
public static function expectationNotFound(string $expectationName): PipeException
|
||||
{
|
||||
return new self("Expectation $expectationName was not found in Pest");
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ use PHPUnit\Framework\ExpectationFailedException;
|
||||
* @template TValue
|
||||
*
|
||||
* @property Expectation $not Creates the opposite expectation.
|
||||
* @property Each $each Creates an expectation on each element on the traversable value.
|
||||
* @property Each $each Creates an expectation on each element on the traversable value.
|
||||
*
|
||||
* @mixin CoreExpectation
|
||||
*/
|
||||
@ -137,16 +137,16 @@ final class Expectation
|
||||
throw new BadMethodCallException('Expectation value is not iterable.');
|
||||
}
|
||||
|
||||
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
|
||||
$keys = array_keys($value);
|
||||
$values = array_values($value);
|
||||
$value = is_array($this->value) ? $this->value : iterator_to_array($this->value);
|
||||
$keys = array_keys($value);
|
||||
$values = array_values($value);
|
||||
$callbacksCount = count($callbacks);
|
||||
|
||||
$index = 0;
|
||||
|
||||
while (count($callbacks) < count($values)) {
|
||||
$callbacks[] = $callbacks[$index];
|
||||
$index = $index < count($values) - 1 ? $index + 1 : 0;
|
||||
$index = $index < count($values) - 1 ? $index + 1 : 0;
|
||||
}
|
||||
|
||||
if ($callbacksCount > count($values)) {
|
||||
@ -170,7 +170,7 @@ final class Expectation
|
||||
*
|
||||
* @template TMatchSubject of array-key
|
||||
*
|
||||
* @param callable(): TMatchSubject|TMatchSubject $subject
|
||||
* @param callable(): TMatchSubject|TMatchSubject $subject
|
||||
* @param array<TMatchSubject, (callable(Expectation<TValue>): mixed)|TValue> $expressions
|
||||
*/
|
||||
public function match($subject, array $expressions): Expectation
|
||||
@ -273,9 +273,16 @@ final class Expectation
|
||||
private function getExpectationClosure(string $name): Closure
|
||||
{
|
||||
if (method_exists($this->coreExpectation, $name)) {
|
||||
//@phpstan-ignore-next-line
|
||||
return Closure::fromCallable([$this->coreExpectation, $name]);
|
||||
} elseif (self::hasExtend($name)) {
|
||||
return self::$extends[$name];
|
||||
}
|
||||
|
||||
if (self::hasExtend($name)) {
|
||||
$extend = self::$extends[$name]->bindTo($this, Expectation::class);
|
||||
|
||||
if ($extend != false) {
|
||||
return $extend;
|
||||
}
|
||||
}
|
||||
|
||||
throw PipeException::expectationNotFound($name);
|
||||
|
||||
@ -5,8 +5,6 @@ declare(strict_types=1);
|
||||
namespace Pest\Support;
|
||||
|
||||
use Closure;
|
||||
use Pest\Exceptions\PipeException;
|
||||
use ReflectionFunction;
|
||||
|
||||
final class ExpectationPipeline
|
||||
{
|
||||
@ -25,7 +23,7 @@ final class ExpectationPipeline
|
||||
public function __construct(string $expectationName, Closure $expectationClosure)
|
||||
{
|
||||
$this->expectationClosure = $expectationClosure;
|
||||
$this->expectationName = $expectationName;
|
||||
$this->expectationName = $expectationName;
|
||||
}
|
||||
|
||||
public static function for(string $expectationName, Closure $expectationClosure): self
|
||||
@ -39,6 +37,7 @@ final class ExpectationPipeline
|
||||
public function send(...$passable): self
|
||||
{
|
||||
$this->passable = $passable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -57,58 +56,20 @@ final class ExpectationPipeline
|
||||
$pipeline = array_reduce(
|
||||
array_reverse($this->pipes),
|
||||
$this->carry(),
|
||||
$this->expectationClosure
|
||||
function (): void {
|
||||
($this->expectationClosure)(...$this->passable);
|
||||
}
|
||||
);
|
||||
|
||||
$pipeline(...$this->passable);
|
||||
$pipeline();
|
||||
}
|
||||
|
||||
public function carry(): Closure
|
||||
{
|
||||
return function ($stack, $pipe): Closure {
|
||||
return function (...$passable) use ($stack, $pipe) {
|
||||
$this->checkOptionalParametersBecomeRequired($pipe);
|
||||
|
||||
$passable = $this->preparePassable($passable);
|
||||
|
||||
$passable[] = $stack;
|
||||
|
||||
return $pipe(...$passable);
|
||||
return function () use ($stack, $pipe) {
|
||||
return $pipe($stack, ...$this->passable);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private function preparePassable(array $passable): array
|
||||
{
|
||||
$reflection = new ReflectionFunction($this->expectationClosure);
|
||||
|
||||
$requiredParametersCount = $reflection->getNumberOfParameters();
|
||||
|
||||
|
||||
if (count($passable) < $requiredParametersCount) {
|
||||
foreach ($reflection->getParameters() as $index => $parameter) {
|
||||
if (!isset($passable[$index])) {
|
||||
$passable[$index] = $parameter->getDefaultValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $passable;
|
||||
}
|
||||
|
||||
private function checkOptionalParametersBecomeRequired($pipe)
|
||||
{
|
||||
$reflection = new ReflectionFunction($pipe);
|
||||
|
||||
foreach ($reflection->getParameters() as $parameter) {
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
/*
|
||||
* TODO add pipeline blame in the exception message and a stronger clarification like
|
||||
* “You’re attempting to pipe ‘toBe’, but haven’t added the $actual parameter to your pipe handler”
|
||||
*/
|
||||
throw PipeException::optionalParmetersShouldBecomeRequired($this->expectationName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +179,8 @@
|
||||
|
||||
PASS Tests\Features\Expect\pipe
|
||||
✓ pipe is applied and can stop pipeline
|
||||
✓ interceptor works with negated expectation
|
||||
✓ pipe works with negated expectation
|
||||
✓ pipe is run and can let the pipeline keep going
|
||||
✓ intercept is applied
|
||||
✓ intercept stops the pipeline
|
||||
@ -728,5 +730,5 @@
|
||||
✓ it is a test
|
||||
✓ it uses correct parent class
|
||||
|
||||
Tests: 4 incompleted, 9 skipped, 484 passed
|
||||
Tests: 4 incompleted, 9 skipped, 486 passed
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use function PHPUnit\Framework\assertEquals;
|
||||
use function PHPUnit\Framework\assertEqualsIgnoringCase;
|
||||
use function PHPUnit\Framework\assertInstanceOf;
|
||||
use function PHPUnit\Framework\assertIsNumeric;
|
||||
|
||||
@ -37,7 +36,7 @@ class Symbol
|
||||
|
||||
class State
|
||||
{
|
||||
public $runCount = [];
|
||||
public $runCount = [];
|
||||
public $appliedCount = [];
|
||||
|
||||
public function __construct()
|
||||
@ -68,7 +67,7 @@ $state = new State();
|
||||
/*
|
||||
* Overrides toBe to assert two Characters are the same
|
||||
*/
|
||||
expect()->pipe('toBe', function ($expected, $next) use ($state) {
|
||||
expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
||||
$state->runCount['character']++;
|
||||
|
||||
if ($this->value instanceof Character) {
|
||||
@ -79,7 +78,7 @@ expect()->pipe('toBe', function ($expected, $next) use ($state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$next($expected);
|
||||
$next();
|
||||
});
|
||||
|
||||
/*
|
||||
@ -105,7 +104,7 @@ expect()->intercept('toBe', function ($value) {
|
||||
/*
|
||||
* Overrides toBe to assert two Symbols are the same
|
||||
*/
|
||||
expect()->pipe('toBe', function ($expected, $next) use ($state) {
|
||||
expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
||||
$state->runCount['symbol']++;
|
||||
|
||||
if ($this->value instanceof Symbol) {
|
||||
@ -116,16 +115,9 @@ expect()->pipe('toBe', function ($expected, $next) use ($state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$next($expected);
|
||||
$next();
|
||||
});
|
||||
|
||||
expect()->intercept('toHaveProperty', function ($value) {
|
||||
return $value instanceof Symbol && $value->value == '£';
|
||||
}, function (string $propertyName, $propertyValue = null) {
|
||||
assertEquals("£", $this->value->value);
|
||||
});
|
||||
|
||||
|
||||
test('pipe is applied and can stop pipeline', function () use ($state) {
|
||||
$letter = new Character('A');
|
||||
|
||||
@ -145,8 +137,19 @@ test('pipe is applied and can stop pipeline', function () use ($state) {
|
||||
'wildcard' => 0,
|
||||
'symbol' => 0,
|
||||
]);
|
||||
})
|
||||
;
|
||||
});
|
||||
|
||||
test('interceptor works with negated expectation', function () {
|
||||
$letter = new Number(1);
|
||||
|
||||
expect($letter)->not->toBe(new Character('B'));
|
||||
});
|
||||
|
||||
test('pipe works with negated expectation', function () {
|
||||
$letter = new Character('A');
|
||||
|
||||
expect($letter)->not->toBe(new Character('B'));
|
||||
});
|
||||
|
||||
test('pipe is run and can let the pipeline keep going', function () use ($state) {
|
||||
$state->reset();
|
||||
@ -216,8 +219,3 @@ test('intercept can be filtered with a closure', function () use ($state) {
|
||||
->runCount->toHaveKey('wildcard', 1)
|
||||
->appliedCount->toHaveKey('wildcard', 1);
|
||||
});
|
||||
|
||||
test('intercept can handle default values', function(){
|
||||
expect(new Symbol("£"))->toHaveProperty('value');
|
||||
expect(new Symbol("£"))->toHaveProperty('value', '£');
|
||||
})->only();
|
||||
|
||||
Reference in New Issue
Block a user