feat: adds after

This commit is contained in:
Nuno Maduro
2024-05-08 01:24:30 +01:00
parent 04b099e87c
commit 8169382362
11 changed files with 312 additions and 16 deletions

View File

@ -39,6 +39,11 @@ trait Testable
*/
public ?string $__describing = null;
/**
* Whether the test has ran or not.
*/
public bool $__ran = false;
/**
* The test's test closure.
*/

View 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 AfterBeforeTestFunction extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
{
/**
* Creates a new Exception instance.
*/
public function __construct(string $filename)
{
parent::__construct('After method cannot be used with before the [test|it] functions in the filename `['.$filename.']`.');
}
}

View File

@ -34,6 +34,11 @@ final class TestCaseMethodFactory
*/
public ?string $describing = null;
/**
* The test's description, if any.
*/
public ?string $description = null;
/**
* The test's number of repetitions.
*/
@ -65,12 +70,16 @@ final class TestCaseMethodFactory
*/
public array $groups = [];
/**
* @see This property is not actually used in the codebase, it's only here to make Rector happy.
*/
public bool $__ran = false;
/**
* Creates a new test case method factory instance.
*/
public function __construct(
public string $filename,
public ?string $description,
public ?Closure $closure,
) {
$this->closure ??= function (): void {
@ -109,6 +118,8 @@ final class TestCaseMethodFactory
$testCase->chains->chain($this);
$method->chains->chain($this);
$this->__ran = true;
return \Pest\Support\Closure::bind($closure, $this, self::class)(...$arguments);
};
}

View File

@ -65,7 +65,6 @@ final class AfterEachCall
$this,
$afterEachTestCase,
);
}
/**

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Pest\PendingCalls;
use Closure;
use Pest\Exceptions\AfterBeforeTestFunction;
use Pest\PendingCalls\Concerns\Describable;
use Pest\Support\Backtrace;
use Pest\Support\ChainableClosure;
@ -83,6 +84,18 @@ final class BeforeEachCall
);
}
/**
* Runs the given closure after the test.
*/
public function after(Closure $closure): self
{
if ($this->describing === null) {
throw new AfterBeforeTestFunction($this->filename);
}
return $this->__call('after', [$closure]);
}
/**
* Saves the calls to be used on the target.
*
@ -91,7 +104,8 @@ final class BeforeEachCall
public function __call(string $name, array $arguments): self
{
if (method_exists(TestCall::class, $name)) {
$this->testCallProxies->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
$this->testCallProxies
->add(Backtrace::file(), Backtrace::line(), $name, $arguments);
return $this;
}

View File

@ -6,6 +6,7 @@ namespace Pest\PendingCalls;
use Closure;
use Pest\Exceptions\InvalidArgumentException;
use Pest\Exceptions\TestDescriptionMissing;
use Pest\Factories\Attribute;
use Pest\Factories\TestCaseMethodFactory;
use Pest\Mutate\Decorators\TestCallDecorator as MutationTestCallDecorator;
@ -52,10 +53,10 @@ final class TestCall
public function __construct(
private readonly TestSuite $testSuite,
private readonly string $filename,
?string $description = null,
private ?string $description = null,
?Closure $closure = null
) {
$this->testCaseMethod = new TestCaseMethodFactory($filename, $description, $closure);
$this->testCaseMethod = new TestCaseMethodFactory($filename, $closure);
$this->descriptionLess = $description === null;
@ -64,6 +65,42 @@ final class TestCall
$this->testSuite->beforeEach->get($this->filename)[0]($this);
}
/**
* Runs the given closure after the test.
*/
public function after(Closure $closure): self
{
if ($this->description === null) {
throw new TestDescriptionMissing($this->filename);
}
$description = is_null($this->describing)
? $this->description
: Str::describe($this->describing, $this->description);
$filename = $this->filename;
$when = function () use ($closure, $filename, $description): void {
if ($this::$__filename !== $filename) { // @phpstan-ignore-line
return;
}
if ($this->__description !== $description) { // @phpstan-ignore-line
return;
}
if ($this->__ran !== true) { // @phpstan-ignore-line
return;
}
$closure->call($this);
};
new AfterEachCall($this->testSuite, $this->filename, $when->bindTo(new \stdClass()));
return $this;
}
/**
* Asserts that the test fails with the given message.
*/
@ -438,10 +475,10 @@ final class TestCall
if ($this->descriptionLess) {
Exporter::default();
if ($this->testCaseMethod->description !== null) {
$this->testCaseMethod->description .= ' → ';
if ($this->description !== null) {
$this->description .= ' → ';
}
$this->testCaseMethod->description .= $arguments === null
$this->description .= $arguments === null
? $name
: sprintf('%s %s', $name, $exporter->shortenedRecursiveExport($arguments));
}
@ -452,7 +489,7 @@ final class TestCall
/**
* Mutates the test.
*/
public function mutate(string $profile = 'default'): self|MutationTestCallDecorator
public function mutate(string $profile = 'default'): self|MutationTestCallDecorator // @phpstan-ignore-line
{
if (class_exists(MutationTestCallDecorator::class)) {
return (new MutationTestCallDecorator($this))
@ -467,9 +504,15 @@ final class TestCall
*/
public function __destruct()
{
if ($this->description === null) {
throw new TestDescriptionMissing($this->filename);
}
if (! is_null($this->describing)) {
$this->testCaseMethod->describing = $this->describing;
$this->testCaseMethod->description = Str::describe($this->describing, $this->testCaseMethod->description); // @phpstan-ignore-line
$this->testCaseMethod->description = Str::describe($this->describing, $this->description);
} else {
$this->testCaseMethod->description = $this->description;
}
$this->testSuite->tests->set($this->testCaseMethod);