mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
feat(describe): improves logic around hooks
This commit is contained in:
@ -23,14 +23,19 @@ use Throwable;
|
|||||||
trait Testable
|
trait Testable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Test method description.
|
* Test method's test description.
|
||||||
*/
|
*/
|
||||||
private string $__description;
|
private string $__testDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test method's describe description, if any.
|
||||||
|
*/
|
||||||
|
public ?string $__describeDescription = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test "latest" method description.
|
* Test "latest" method description.
|
||||||
*/
|
*/
|
||||||
private static string $__latestDescription;
|
private static string $__latestTestDescription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Test Case "test" closure.
|
* The Test Case "test" closure.
|
||||||
@ -77,7 +82,7 @@ trait Testable
|
|||||||
|
|
||||||
if ($test->hasMethod($name)) {
|
if ($test->hasMethod($name)) {
|
||||||
$method = $test->getMethod($name);
|
$method = $test->getMethod($name);
|
||||||
$this->__description = self::$__latestDescription = $method->description;
|
$this->__testDescription = self::$__latestTestDescription = $method->description;
|
||||||
$this->__test = $method->getClosure($this);
|
$this->__test = $method->getClosure($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,7 +235,7 @@ trait Testable
|
|||||||
{
|
{
|
||||||
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
$method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name());
|
||||||
|
|
||||||
$this->__description = self::$__latestDescription = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description;
|
$this->__testDescription = self::$__latestTestDescription = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description;
|
||||||
|
|
||||||
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
$underlyingTest = Reflection::getFunctionVariable($this->__test, 'closure');
|
||||||
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
|
$testParameterTypes = array_values(Reflection::getFunctionArguments($underlyingTest));
|
||||||
@ -315,7 +320,7 @@ trait Testable
|
|||||||
*/
|
*/
|
||||||
public function getPrintableTestCaseMethodName(): string
|
public function getPrintableTestCaseMethodName(): string
|
||||||
{
|
{
|
||||||
return $this->__description;
|
return $this->__testDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -323,6 +328,6 @@ trait Testable
|
|||||||
*/
|
*/
|
||||||
public static function getLatestPrintableTestCaseMethodName(): string
|
public static function getLatestPrintableTestCaseMethodName(): string
|
||||||
{
|
{
|
||||||
return self::$__latestDescription;
|
return self::$__latestTestDescription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/Exceptions/AfterAllWithinDescribe.php
Normal file
24
src/Exceptions/AfterAllWithinDescribe.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class AfterAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new Exception instance.
|
||||||
|
*/
|
||||||
|
public function __construct(string $filename)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The afterAll method can not be used within describe functions. Filename `%s`.', $filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Exceptions/BeforeAllWithinDescribe.php
Normal file
24
src/Exceptions/BeforeAllWithinDescribe.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BeforeAllWithinDescribe extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new Exception instance.
|
||||||
|
*/
|
||||||
|
public function __construct(string $filename)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('The beforeAll method can not be used within describe functions. Filename `%s`.', $filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,6 +21,11 @@ final class TestCaseMethodFactory
|
|||||||
{
|
{
|
||||||
use HigherOrderable;
|
use HigherOrderable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* D=fghjkl
|
||||||
|
*/
|
||||||
|
public ?string $describing = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the Test Case Method is a "todo".
|
* Determines if the Test Case Method is a "todo".
|
||||||
*/
|
*/
|
||||||
@ -84,6 +89,7 @@ final class TestCaseMethodFactory
|
|||||||
|
|
||||||
$testCase = TestSuite::getInstance()->tests->get($this->filename);
|
$testCase = TestSuite::getInstance()->tests->get($this->filename);
|
||||||
|
|
||||||
|
$concrete->__describeDescription = $this->describing; // @phpstan-ignore-line
|
||||||
$testCase->factoryProxies->proxy($concrete);
|
$testCase->factoryProxies->proxy($concrete);
|
||||||
$this->factoryProxies->proxy($concrete);
|
$this->factoryProxies->proxy($concrete);
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\AfterAllWithinDescribe;
|
||||||
|
use Pest\Exceptions\BeforeAllWithinDescribe;
|
||||||
use Pest\Expectation;
|
use Pest\Expectation;
|
||||||
|
use Pest\PendingCalls;
|
||||||
use Pest\PendingCalls\AfterEachCall;
|
use Pest\PendingCalls\AfterEachCall;
|
||||||
use Pest\PendingCalls\BeforeEachCall;
|
use Pest\PendingCalls\BeforeEachCall;
|
||||||
|
use Pest\PendingCalls\DescribeCall;
|
||||||
use Pest\PendingCalls\TestCall;
|
use Pest\PendingCalls\TestCall;
|
||||||
use Pest\PendingCalls\UsesCall;
|
use Pest\PendingCalls\UsesCall;
|
||||||
use Pest\Repositories\DatasetsRepository;
|
use Pest\Repositories\DatasetsRepository;
|
||||||
@ -35,6 +39,12 @@ if (! function_exists('beforeAll')) {
|
|||||||
*/
|
*/
|
||||||
function beforeAll(Closure $closure): void
|
function beforeAll(Closure $closure): void
|
||||||
{
|
{
|
||||||
|
if (! is_null(PendingCalls::$describing)) {
|
||||||
|
$filename = Backtrace::file();
|
||||||
|
|
||||||
|
throw new BeforeAllWithinDescribe($filename);
|
||||||
|
}
|
||||||
|
|
||||||
TestSuite::getInstance()->beforeAll->set($closure);
|
TestSuite::getInstance()->beforeAll->set($closure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,6 +77,26 @@ if (! function_exists('dataset')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! function_exists('describe')) {
|
||||||
|
/**
|
||||||
|
* Adds the given closure as a group of tests. The first argument
|
||||||
|
* is the group description; the second argument is a closure
|
||||||
|
* that contains the group tests.
|
||||||
|
*
|
||||||
|
* @return HigherOrderTapProxy<TestCall|TestCase>|TestCall|TestCase|mixed
|
||||||
|
*/
|
||||||
|
function describe(string $description, Closure $tests): DescribeCall
|
||||||
|
{
|
||||||
|
$filename = Backtrace::testFile();
|
||||||
|
|
||||||
|
PendingCalls::startDescribe(
|
||||||
|
$describeCall = new DescribeCall(TestSuite::getInstance(), $filename, $description, $tests),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $describeCall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! function_exists('uses')) {
|
if (! function_exists('uses')) {
|
||||||
/**
|
/**
|
||||||
* The uses function binds the given
|
* The uses function binds the given
|
||||||
@ -159,6 +189,12 @@ if (! function_exists('afterAll')) {
|
|||||||
*/
|
*/
|
||||||
function afterAll(Closure $closure): void
|
function afterAll(Closure $closure): void
|
||||||
{
|
{
|
||||||
|
if (! is_null(PendingCalls::$describing)) {
|
||||||
|
$filename = Backtrace::file();
|
||||||
|
|
||||||
|
throw new AfterAllWithinDescribe($filename);
|
||||||
|
}
|
||||||
|
|
||||||
TestSuite::getInstance()->afterAll->set($closure);
|
TestSuite::getInstance()->afterAll->set($closure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/PendingCalls.php
Normal file
115
src/PendingCalls.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Pest\PendingCalls\AfterEachCall;
|
||||||
|
use Pest\PendingCalls\BeforeEachCall;
|
||||||
|
use Pest\PendingCalls\DescribeCall;
|
||||||
|
use Pest\PendingCalls\TestCall;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class PendingCalls
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current describe call.
|
||||||
|
*/
|
||||||
|
public static ?string $describing = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of before each pending calls.
|
||||||
|
*
|
||||||
|
* @var array<int, BeforeEachCall>
|
||||||
|
*/
|
||||||
|
public static array $beforeEachCalls = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of test pending calls.
|
||||||
|
*
|
||||||
|
* @var array<int, array{0: TestCall, 1: Closure}>
|
||||||
|
*/
|
||||||
|
public static array $testCalls = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of after each pending calls.
|
||||||
|
*
|
||||||
|
* @var array<int, array{0: AfterEachCall, 1: Closure}>
|
||||||
|
*/
|
||||||
|
public static array $afterEachCalls = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current describe call.
|
||||||
|
*/
|
||||||
|
public static function startDescribe(DescribeCall $describeCall): void
|
||||||
|
{
|
||||||
|
self::$describing = $describeCall->description;
|
||||||
|
|
||||||
|
($describeCall->tests)();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new before each call.
|
||||||
|
*/
|
||||||
|
public static function beforeEach(BeforeEachCall $beforeEachCall, Closure $setter): void
|
||||||
|
{
|
||||||
|
$setter($beforeEachCall->describing = self::$describing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new test call.
|
||||||
|
*/
|
||||||
|
public static function test(TestCall $testCall, Closure $setter): void
|
||||||
|
{
|
||||||
|
if (! is_null($testCall->describing = self::$describing)) {
|
||||||
|
self::$testCalls[] = [$testCall, $setter];
|
||||||
|
} else {
|
||||||
|
$setter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new before each call.
|
||||||
|
*/
|
||||||
|
public static function afterEach(AfterEachCall $afterEachCall, Closure $setter): void
|
||||||
|
{
|
||||||
|
if (! is_null(self::$describing)) {
|
||||||
|
$afterEachCall->describing = self::$describing;
|
||||||
|
|
||||||
|
self::$afterEachCalls[] = [$afterEachCall, $setter];
|
||||||
|
} else {
|
||||||
|
$setter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function endDescribe(DescribeCall $describeCall): void
|
||||||
|
{
|
||||||
|
$describing = self::$describing;
|
||||||
|
|
||||||
|
self::$describing = null;
|
||||||
|
|
||||||
|
foreach (self::$beforeEachCalls as [$beforeEachCall, $setter]) {
|
||||||
|
$setter($describing);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$beforeEachCalls = [];
|
||||||
|
|
||||||
|
foreach (self::$testCalls as [$testCall, $setter]) {
|
||||||
|
/** @var TestCall $testCall */
|
||||||
|
$testCall->testCaseMethod->description = '`'.$describeCall->description.'` '.$testCall->testCaseMethod->description;
|
||||||
|
|
||||||
|
$setter($describing);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$testCalls = [];
|
||||||
|
|
||||||
|
foreach (self::$afterEachCalls as [$afterEachCall, $setter]) {
|
||||||
|
$setter($describing);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$afterEachCalls = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Pest\PendingCalls;
|
namespace Pest\PendingCalls;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Pest\PendingCalls;
|
||||||
|
use Pest\PendingCalls\Concerns\Describable;
|
||||||
use Pest\Support\Backtrace;
|
use Pest\Support\Backtrace;
|
||||||
use Pest\Support\ChainableClosure;
|
use Pest\Support\ChainableClosure;
|
||||||
use Pest\Support\HigherOrderMessageCollection;
|
use Pest\Support\HigherOrderMessageCollection;
|
||||||
@ -16,6 +18,8 @@ use Pest\TestSuite;
|
|||||||
*/
|
*/
|
||||||
final class AfterEachCall
|
final class AfterEachCall
|
||||||
{
|
{
|
||||||
|
use Describable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "afterEach" closure.
|
* The "afterEach" closure.
|
||||||
*/
|
*/
|
||||||
@ -44,14 +48,23 @@ final class AfterEachCall
|
|||||||
*/
|
*/
|
||||||
public function __destruct()
|
public function __destruct()
|
||||||
{
|
{
|
||||||
$proxies = $this->proxies;
|
PendingCalls::afterEach($this, function (string $describing = null) {
|
||||||
|
$proxies = $this->proxies;
|
||||||
|
|
||||||
|
$afterEachTestCase = ChainableClosure::when(
|
||||||
|
fn () => is_null($describing) || $this->__describeDescription === $describing, // @phpstan-ignore-line
|
||||||
|
ChainableClosure::from(fn () => $proxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
|
||||||
|
)->bindTo($this, self::class);
|
||||||
|
|
||||||
|
assert($afterEachTestCase instanceof Closure);
|
||||||
|
|
||||||
|
$this->testSuite->afterEach->set(
|
||||||
|
$this->filename,
|
||||||
|
$this,
|
||||||
|
$afterEachTestCase,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$this->testSuite->afterEach->set(
|
|
||||||
$this->filename,
|
|
||||||
ChainableClosure::from(function () use ($proxies): void {
|
|
||||||
$proxies->chain($this);
|
|
||||||
}, $this->closure)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Pest\PendingCalls;
|
namespace Pest\PendingCalls;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Pest\PendingCalls;
|
||||||
|
use Pest\PendingCalls\Concerns\Describable;
|
||||||
use Pest\Support\Backtrace;
|
use Pest\Support\Backtrace;
|
||||||
use Pest\Support\ChainableClosure;
|
use Pest\Support\ChainableClosure;
|
||||||
use Pest\Support\HigherOrderMessageCollection;
|
use Pest\Support\HigherOrderMessageCollection;
|
||||||
@ -16,6 +18,8 @@ use Pest\TestSuite;
|
|||||||
*/
|
*/
|
||||||
final class BeforeEachCall
|
final class BeforeEachCall
|
||||||
{
|
{
|
||||||
|
use Describable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the before each closure.
|
* Holds the before each closure.
|
||||||
*/
|
*/
|
||||||
@ -35,7 +39,7 @@ final class BeforeEachCall
|
|||||||
* Creates a new Pending Call.
|
* Creates a new Pending Call.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TestSuite $testSuite,
|
public readonly TestSuite $testSuite,
|
||||||
private readonly string $filename,
|
private readonly string $filename,
|
||||||
Closure $closure = null
|
Closure $closure = null
|
||||||
) {
|
) {
|
||||||
@ -50,17 +54,29 @@ final class BeforeEachCall
|
|||||||
*/
|
*/
|
||||||
public function __destruct()
|
public function __destruct()
|
||||||
{
|
{
|
||||||
$testCaseProxies = $this->testCaseProxies;
|
PendingCalls::beforeEach($this, function (string $describing = null) {
|
||||||
|
$testCaseProxies = $this->testCaseProxies;
|
||||||
|
|
||||||
$this->testSuite->beforeEach->set(
|
$beforeEachTestCall = function (TestCall $testCall) use ($describing): void {
|
||||||
$this->filename,
|
if (is_null($describing) || ($this->describing === $testCall->describing && $testCall->describing === $describing)) {
|
||||||
function (TestCall $testCall): void {
|
$this->testCallProxies->chain($testCall);
|
||||||
$this->testCallProxies->chain($testCall);
|
}
|
||||||
},
|
};
|
||||||
ChainableClosure::from(function () use ($testCaseProxies): void {
|
|
||||||
$testCaseProxies->chain($this);
|
$beforeEachTestCase = ChainableClosure::when(
|
||||||
}, $this->closure),
|
fn () => is_null($describing) || $this->__describeDescription === $describing, // @phpstan-ignore-line
|
||||||
);
|
ChainableClosure::from(fn () => $testCaseProxies->chain($this), $this->closure)->bindTo($this, self::class), // @phpstan-ignore-line
|
||||||
|
)->bindTo($this, self::class);
|
||||||
|
|
||||||
|
assert($beforeEachTestCase instanceof Closure);
|
||||||
|
|
||||||
|
$this->testSuite->beforeEach->set(
|
||||||
|
$this->filename,
|
||||||
|
$this,
|
||||||
|
$beforeEachTestCall,
|
||||||
|
$beforeEachTestCase,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,6 +86,7 @@ final class BeforeEachCall
|
|||||||
*/
|
*/
|
||||||
public function __call(string $name, array $arguments): self
|
public function __call(string $name, array $arguments): self
|
||||||
{
|
{
|
||||||
|
|
||||||
if (method_exists(TestCall::class, $name)) {
|
if (method_exists(TestCall::class, $name)) {
|
||||||
$this->testCallProxies->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
|
$this->testCallProxies->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
|
||||||
|
|
||||||
|
|||||||
13
src/PendingCalls/Concerns/Describable.php
Normal file
13
src/PendingCalls/Concerns/Describable.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\PendingCalls\Concerns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
trait Describable
|
||||||
|
{
|
||||||
|
public string|null $describing = null;
|
||||||
|
}
|
||||||
46
src/PendingCalls/DescribeCall.php
Normal file
46
src/PendingCalls/DescribeCall.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\PendingCalls;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Pest\PendingCalls;
|
||||||
|
use Pest\TestSuite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class DescribeCall
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new Pending Call.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public readonly TestSuite $testSuite,
|
||||||
|
public readonly string $filename,
|
||||||
|
public readonly string $description,
|
||||||
|
public readonly Closure $tests
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
PendingCalls::endDescribe($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically calls methods on each test call.
|
||||||
|
*
|
||||||
|
* @param array<int, mixed> $arguments
|
||||||
|
*/
|
||||||
|
public function __call(string $name, array $arguments): self
|
||||||
|
{
|
||||||
|
foreach (PendingCalls::$testCalls as [$testCall]) {
|
||||||
|
$testCall->{$name}(...$arguments); // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,8 @@ use Pest\Factories\Covers\CoversClass;
|
|||||||
use Pest\Factories\Covers\CoversFunction;
|
use Pest\Factories\Covers\CoversFunction;
|
||||||
use Pest\Factories\Covers\CoversNothing;
|
use Pest\Factories\Covers\CoversNothing;
|
||||||
use Pest\Factories\TestCaseMethodFactory;
|
use Pest\Factories\TestCaseMethodFactory;
|
||||||
|
use Pest\PendingCalls;
|
||||||
|
use Pest\PendingCalls\Concerns\Describable;
|
||||||
use Pest\Plugins\Only;
|
use Pest\Plugins\Only;
|
||||||
use Pest\Support\Backtrace;
|
use Pest\Support\Backtrace;
|
||||||
use Pest\Support\Exporter;
|
use Pest\Support\Exporter;
|
||||||
@ -25,10 +27,12 @@ use PHPUnit\Framework\TestCase;
|
|||||||
*/
|
*/
|
||||||
final class TestCall
|
final class TestCall
|
||||||
{
|
{
|
||||||
|
use Describable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Test Case Factory.
|
* The Test Case Factory.
|
||||||
*/
|
*/
|
||||||
private readonly TestCaseMethodFactory $testCaseMethod;
|
public readonly TestCaseMethodFactory $testCaseMethod;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If test call is descriptionLess.
|
* If test call is descriptionLess.
|
||||||
@ -48,7 +52,7 @@ final class TestCall
|
|||||||
|
|
||||||
$this->descriptionLess = $description === null;
|
$this->descriptionLess = $description === null;
|
||||||
|
|
||||||
$this->testSuite->beforeEach->get($filename)[0]($this);
|
$this->testSuite->beforeEach->get($this->filename)[0]($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -316,12 +320,14 @@ final class TestCall
|
|||||||
private function addChain(string $file, int $line, string $name, array $arguments = null): self
|
private function addChain(string $file, int $line, string $name, array $arguments = null): self
|
||||||
{
|
{
|
||||||
$exporter = Exporter::default();
|
$exporter = Exporter::default();
|
||||||
|
|
||||||
$this->testCaseMethod
|
$this->testCaseMethod
|
||||||
->chains
|
->chains
|
||||||
->add($file, $line, $name, $arguments);
|
->add($file, $line, $name, $arguments);
|
||||||
|
|
||||||
if ($this->descriptionLess) {
|
if ($this->descriptionLess) {
|
||||||
Exporter::default();
|
Exporter::default();
|
||||||
|
|
||||||
if ($this->testCaseMethod->description !== null) {
|
if ($this->testCaseMethod->description !== null) {
|
||||||
$this->testCaseMethod->description .= ' → ';
|
$this->testCaseMethod->description .= ' → ';
|
||||||
}
|
}
|
||||||
@ -338,6 +344,10 @@ final class TestCall
|
|||||||
*/
|
*/
|
||||||
public function __destruct()
|
public function __destruct()
|
||||||
{
|
{
|
||||||
$this->testSuite->tests->set($this->testCaseMethod);
|
PendingCalls::test($this, function () {
|
||||||
|
$this->testCaseMethod->describing = $this->describing;
|
||||||
|
|
||||||
|
$this->testSuite->tests->set($this->testCaseMethod);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ namespace Pest\Repositories;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
use Pest\Exceptions\AfterEachAlreadyExist;
|
use Pest\PendingCalls\AfterEachCall;
|
||||||
use Pest\Support\ChainableClosure;
|
use Pest\Support\ChainableClosure;
|
||||||
use Pest\Support\NullClosure;
|
use Pest\Support\NullClosure;
|
||||||
|
|
||||||
@ -23,13 +23,18 @@ final class AfterEachRepository
|
|||||||
/**
|
/**
|
||||||
* Sets a after each closure.
|
* Sets a after each closure.
|
||||||
*/
|
*/
|
||||||
public function set(string $filename, Closure $closure): void
|
public function set(string $filename, AfterEachCall $afterEachCall, Closure $afterEachTestCase): void
|
||||||
{
|
{
|
||||||
if (array_key_exists($filename, $this->state)) {
|
if (array_key_exists($filename, $this->state)) {
|
||||||
throw new AfterEachAlreadyExist($filename);
|
$fromAfterEachTestCase = $this->state[$filename];
|
||||||
|
|
||||||
|
$afterEachTestCase = ChainableClosure::from($fromAfterEachTestCase, $afterEachTestCase)
|
||||||
|
->bindTo($afterEachCall, $afterEachCall::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->state[$filename] = $closure;
|
assert($afterEachTestCase instanceof Closure);
|
||||||
|
|
||||||
|
$this->state[$filename] = $afterEachTestCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,7 +5,8 @@ declare(strict_types=1);
|
|||||||
namespace Pest\Repositories;
|
namespace Pest\Repositories;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Pest\Exceptions\BeforeEachAlreadyExist;
|
use Pest\PendingCalls\BeforeEachCall;
|
||||||
|
use Pest\Support\ChainableClosure;
|
||||||
use Pest\Support\NullClosure;
|
use Pest\Support\NullClosure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,12 +22,18 @@ final class BeforeEachRepository
|
|||||||
/**
|
/**
|
||||||
* Sets a before each closure.
|
* Sets a before each closure.
|
||||||
*/
|
*/
|
||||||
public function set(string $filename, Closure $beforeEachTestCall, Closure $beforeEachTestCase): void
|
public function set(string $filename, BeforeEachCall $beforeEachCall, Closure $beforeEachTestCall, Closure $beforeEachTestCase): void
|
||||||
{
|
{
|
||||||
if (array_key_exists($filename, $this->state)) {
|
if (array_key_exists($filename, $this->state)) {
|
||||||
throw new BeforeEachAlreadyExist($filename);
|
[$fromBeforeEachTestCall, $fromBeforeEachTestCase] = $this->state[$filename];
|
||||||
|
|
||||||
|
$beforeEachTestCall = ChainableClosure::from($fromBeforeEachTestCall, $beforeEachTestCall)->bindTo($beforeEachCall, $beforeEachCall::class);
|
||||||
|
$beforeEachTestCase = ChainableClosure::from($fromBeforeEachTestCase, $beforeEachTestCase)->bindTo($beforeEachCall, $beforeEachCall::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert($beforeEachTestCall instanceof Closure);
|
||||||
|
assert($beforeEachTestCase instanceof Closure);
|
||||||
|
|
||||||
$this->state[$filename] = [$beforeEachTestCall, $beforeEachTestCase];
|
$this->state[$filename] = [$beforeEachTestCall, $beforeEachTestCase];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,22 @@ use Pest\Exceptions\ShouldNotHappen;
|
|||||||
*/
|
*/
|
||||||
final class ChainableClosure
|
final class ChainableClosure
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Calls the given `$closure` when the given condition is true.
|
||||||
|
*/
|
||||||
|
public static function when(Closure $condition, Closure $next): Closure
|
||||||
|
{
|
||||||
|
return function () use ($condition, $next): void {
|
||||||
|
if (! is_object($this)) { // @phpstan-ignore-line
|
||||||
|
throw ShouldNotHappen::fromMessage('$this not bound to chainable closure.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\Pest\Support\Closure::bind($condition, $this, self::class)(...func_get_args())) {
|
||||||
|
\Pest\Support\Closure::bind($next, $this, self::class)(...func_get_args());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the given `$closure` and chains the `$next` closure.
|
* Calls the given `$closure` and chains the `$next` closure.
|
||||||
*/
|
*/
|
||||||
|
|||||||
61
tests/Features/Describe.php
Normal file
61
tests/Features/Describe.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
beforeEach(fn () => $this->count = 1);
|
||||||
|
|
||||||
|
test('before each', function () {
|
||||||
|
expect($this->count)->toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('describable', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('before each', function () {
|
||||||
|
expect($this->count)->toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
expect($this->count)->toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('another describable', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('before each', function () {
|
||||||
|
expect($this->count)->toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
expect($this->count)->toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not fail')->todo()->shouldNotRun();
|
||||||
|
|
||||||
|
test('previous describable before each does not get applied here', function () {
|
||||||
|
expect($this->count)->toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(fn () => expect($this->count)->toBe(is_null($this->__describeDescription) ? 1 : 2));
|
||||||
|
|
||||||
|
describe('todo', function () {
|
||||||
|
beforeEach()->todo();
|
||||||
|
|
||||||
|
test('should not fail')->shouldNotRun();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('todo after describe', function () {
|
||||||
|
test('should not fail')->shouldNotRun();
|
||||||
|
})->todo();
|
||||||
Reference in New Issue
Block a user