diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index d6428c53..a7fe0796 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -6,13 +6,16 @@ namespace Pest\Concerns; use Closure; use Pest\Exceptions\DatasetArgumentsMismatch; +use Pest\Contracts\Plugins\AfterEachable; +use Pest\Contracts\Plugins\BeforeEachable; +use Pest\Contracts\Plugins\Runnable; use Pest\Panic; +use Pest\Plugin\Loader; use Pest\Preset; use Pest\Support\ChainableClosure; use Pest\Support\ExceptionTrace; use Pest\Support\Reflection; use Pest\Support\Shell; -use Pest\Plugins\Tia\State as TiaState; use Pest\TestSuite; use PHPUnit\Framework\Attributes\PostCondition; use PHPUnit\Framework\IncompleteTest; @@ -228,14 +231,11 @@ trait Testable { TestSuite::getInstance()->test = $this; - // TIA replay fast-path. When the file is known to the dependency graph - // and none of its deps changed since recording, skip both the - // framework `setUp()` (Laravel app bootstrap, DB refresh, etc.) and - // the user `beforeEach` chain. The matching short-circuit inside - // `__runTest()` ensures the test body never executes, so no - // initialisation is needed. - if (TiaState::instance()->shouldReplayFromCache(self::$__filename, $this::class.'::'.$this->name())) { - return; + /** @var BeforeEachable $plugin */ + foreach (Loader::getPlugins(BeforeEachable::class) as $plugin) { + if ($plugin->beforeEach(self::$__filename, $this::class.'::'.$this->name()) === false) { + return; + } } $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); @@ -313,13 +313,13 @@ trait Testable */ protected function tearDown(...$arguments): void { - // TIA replay: setUp was skipped, the closure never ran — there is - // no matching cleanup to perform here. Keep the framework invariant - // of clearing the "current test" pointer and bail out. - if (TiaState::instance()->shouldReplayFromCache(self::$__filename, $this::class.'::'.$this->name())) { - TestSuite::getInstance()->test = null; + /** @var AfterEachable $plugin */ + foreach (Loader::getPlugins(AfterEachable::class) as $plugin) { + if ($plugin->afterEach(self::$__filename, $this::class.'::'.$this->name()) === false) { + TestSuite::getInstance()->test = null; - return; + return; + } } $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); @@ -347,13 +347,13 @@ trait Testable */ private function __runTest(Closure $closure, ...$args): mixed { - // TIA replay: the file's deps haven't changed and it last passed. - // Bypass the closure entirely and register a synthetic assertion so - // PHPUnit does not emit a "risky: no assertions" warning. - if (TiaState::instance()->shouldReplayFromCache(self::$__filename, $this::class.'::'.$this->name())) { - $this->addToAssertionCount(1); + /** @var Runnable $plugin */ + foreach (Loader::getPlugins(Runnable::class) as $plugin) { + if ($plugin->run(self::$__filename, $this::class.'::'.$this->name()) === false) { + $this->addToAssertionCount(1); - return null; + return null; + } } $arguments = $this->__resolveTestArguments($args); diff --git a/src/Contracts/Plugins/AfterEachable.php b/src/Contracts/Plugins/AfterEachable.php new file mode 100644 index 00000000..c0c4a257 --- /dev/null +++ b/src/Contracts/Plugins/AfterEachable.php @@ -0,0 +1,20 @@ +handleParent($arguments, $projectRoot, $forceRebuild); } + public function beforeEach(string $filename, string $testId): bool + { + return ! State::instance()->shouldReplayFromCache($filename, $testId); + } + + public function run(string $filename, string $testId): bool + { + return ! State::instance()->shouldReplayFromCache($filename, $testId); + } + + public function afterEach(string $filename, string $testId): bool + { + return ! State::instance()->shouldReplayFromCache($filename, $testId); + } + public function terminate(): void { if ($this->graphWritten) {