diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index a7fe0796..3f7e3b77 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -6,11 +6,7 @@ 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; @@ -231,13 +227,6 @@ trait Testable { TestSuite::getInstance()->test = $this; - /** @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()); $description = $method->description; @@ -313,15 +302,6 @@ trait Testable */ protected function tearDown(...$arguments): void { - /** @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; - } - } - $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); if ($this->__afterEach instanceof Closure) { @@ -347,15 +327,6 @@ trait Testable */ private function __runTest(Closure $closure, ...$args): mixed { - /** @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; - } - } - $arguments = $this->__resolveTestArguments($args); $this->__ensureDatasetArgumentNameAndNumberMatches($arguments); diff --git a/src/Contracts/Plugins/AfterEachable.php b/src/Contracts/Plugins/AfterEachable.php deleted file mode 100644 index c0c4a257..00000000 --- a/src/Contracts/Plugins/AfterEachable.php +++ /dev/null @@ -1,20 +0,0 @@ -add(TestSuite::class, $testSuite) ->add(InputInterface::class, $input) ->add(OutputInterface::class, $output) - ->add(Container::class, $container); + ->add(Container::class, $container) + ->add(Tia\Recorder::class, new Tia\Recorder) + ->add(Tia\WatchPatterns::class, new Tia\WatchPatterns); $kernel = new self( new Application, diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index 156247d8..6ee4f568 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -5,20 +5,15 @@ declare(strict_types=1); namespace Pest\Plugins; use Pest\Contracts\Plugins\AddsOutput; -use Pest\Contracts\Plugins\AfterEachable; -use Pest\Contracts\Plugins\BeforeEachable; use Pest\Contracts\Plugins\HandlesArguments; -use Pest\Contracts\Plugins\Runnable; use Pest\Contracts\Plugins\Terminable; -use Pest\Exceptions\NoDirtyTestsFound; -use Pest\Panic; -use Pest\Support\Container; use Pest\Plugins\Tia\ChangedFiles; use Pest\Plugins\Tia\Fingerprint; use Pest\Plugins\Tia\Graph; use Pest\Plugins\Tia\Recorder; -use Pest\Plugins\Tia\State; +use Pest\TestCaseFilters\TiaTestCaseFilter; use Pest\Plugins\Tia\WatchPatterns; +use Pest\Support\Container; use Pest\TestSuite; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -30,7 +25,7 @@ use Throwable; * ----- * - **Record** — no graph (or fingerprint / recording commit drifted). The * full suite runs with PCOV / Xdebug capture per test; the resulting - * `test → [source_file, …]` edges land in `.pest/cache/tia.json`. + * `test → [source_file, …]` edges land in `.temp/tia.json`. * - **Replay** — graph valid. We diff the working tree against the recording * commit, intersect changed files with graph edges, and run only the * affected tests. Newly-added tests unknown to the graph are always @@ -53,7 +48,7 @@ use Throwable; * - **Worker, record**: boots through `bin/worker.php`, which re-runs * `CallsHandleArguments`. We detect the worker context + recording flag, * activate the `Recorder`, and flush the partial graph on `terminate()` - * into `.pest/cache/tia-worker-.json`. + * into `.temp/tia-worker-.json`. * - **Worker, replay**: nothing to do; args already narrowed. * * Guardrails @@ -66,7 +61,7 @@ use Throwable; * * @internal */ -final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArguments, Runnable, Terminable +final class Tia implements AddsOutput, HandlesArguments, Terminable { use Concerns\HandleArguments; @@ -74,11 +69,21 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg private const string REBUILD_OPTION = '--tia-rebuild'; - private const string CACHE_PATH = '.pest/cache/tia.json'; + /** + * TIA cache lives inside Pest's `.temp/` directory (same location as + * PHPUnit's result cache). This directory is gitignored by default in + * Pest's own `.gitignore`, so the graph is never committed. + */ + private const string TEMP_DIR = __DIR__ + .DIRECTORY_SEPARATOR.'..' + .DIRECTORY_SEPARATOR.'..' + .DIRECTORY_SEPARATOR.'.temp'; - private const string AFFECTED_PATH = '.pest/cache/tia-affected.json'; + private const string CACHE_FILE = 'tia.json'; - private const string WORKER_CACHE_PREFIX = '.pest/cache/tia-worker-'; + private const string AFFECTED_FILE = 'tia-affected.json'; + + private const string WORKER_PREFIX = 'tia-worker-'; /** * Global flag toggled by the parent process so workers know to record. @@ -87,13 +92,50 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg /** * Global flag that tells workers to install the TIA filter (replay mode). - * Workers read the affected set from `.pest/cache/tia-affected.json`. + * Workers read the affected set from `.temp/tia-affected.json`. */ private const string REPLAYING_GLOBAL = 'TIA_REPLAYING'; private bool $graphWritten = false; - public function __construct(private readonly OutputInterface $output) {} + private static function tempDir(): string + { + $dir = (string) realpath(self::TEMP_DIR); + + if ($dir === '' || $dir === '.') { + // .temp doesn't exist yet — create it. + @mkdir(self::TEMP_DIR, 0755, true); + $dir = (string) realpath(self::TEMP_DIR); + } + + return $dir; + } + + private static function cachePath(): string + { + return self::tempDir().DIRECTORY_SEPARATOR.self::CACHE_FILE; + } + + private static function affectedPath(): string + { + return self::tempDir().DIRECTORY_SEPARATOR.self::AFFECTED_FILE; + } + + private static function workerPath(string $token): string + { + return self::tempDir().DIRECTORY_SEPARATOR.self::WORKER_PREFIX.$token.'.json'; + } + + private static function workerGlob(): string + { + return self::tempDir().DIRECTORY_SEPARATOR.self::WORKER_PREFIX.'*.json'; + } + + public function __construct( + private readonly OutputInterface $output, + private readonly Recorder $recorder, + private readonly WatchPatterns $watchPatterns, + ) {} /** * {@inheritDoc} @@ -134,28 +176,13 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg return $this->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) { return; } - $recorder = Recorder::instance(); + $recorder = $this->recorder; if (! $recorder->isActive()) { return; @@ -180,7 +207,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg } // Non-parallel record path: straight into the main cache. - $cachePath = $projectRoot.DIRECTORY_SEPARATOR.self::CACHE_PATH; + $cachePath = self::cachePath(); $graph = Graph::load($projectRoot, $cachePath) ?? new Graph($projectRoot); $graph->setFingerprint(Fingerprint::compute($projectRoot)); @@ -198,7 +225,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg $this->output->writeln(sprintf( ' TIA graph recorded (%d test files) at %s', count($perTest), - self::CACHE_PATH, + self::CACHE_FILE, )); $recorder->reset(); @@ -226,7 +253,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg return $exitCode; } - $cachePath = $projectRoot.DIRECTORY_SEPARATOR.self::CACHE_PATH; + $cachePath = self::cachePath(); $graph = Graph::load($projectRoot, $cachePath) ?? new Graph($projectRoot); $graph->setFingerprint(Fingerprint::compute($projectRoot)); @@ -273,7 +300,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg ' TIA graph recorded (%d test files, %d worker partials) at %s', count($finalised), count($partials), - self::CACHE_PATH, + self::CACHE_FILE, )); return $exitCode; @@ -288,9 +315,9 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg // Initialise watch patterns (defaults + any user additions from // tests/Pest.php which has already been loaded by BootFiles at // this point). - WatchPatterns::instance()->useDefaults($projectRoot); + $this->watchPatterns->useDefaults($projectRoot); - $cachePath = $projectRoot.DIRECTORY_SEPARATOR.self::CACHE_PATH; + $cachePath = self::cachePath(); $fingerprint = Fingerprint::compute($projectRoot); $graph = $forceRebuild ? null : Graph::load($projectRoot, $cachePath); @@ -342,7 +369,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg return $arguments; } - $recorder = Recorder::instance(); + $recorder = $this->recorder; if (! $recorder->driverAvailable()) { // Driver availability is per-process. If the driver is missing @@ -358,8 +385,8 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg private function installWorkerReplayFilter(string $projectRoot): void { - $cachePath = $projectRoot.DIRECTORY_SEPARATOR.self::CACHE_PATH; - $affectedPath = $projectRoot.DIRECTORY_SEPARATOR.self::AFFECTED_PATH; + $cachePath = self::cachePath(); + $affectedPath = self::affectedPath(); $graph = Graph::load($projectRoot, $cachePath); @@ -387,73 +414,11 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg } } - State::instance()->activate( - $projectRoot, - $graph, - $affectedSet, - $this->loadPreviousDefects($projectRoot), + TestSuite::getInstance()->tests->addTestCaseFilter( + new TiaTestCaseFilter($projectRoot, $graph, $affectedSet), ); } - /** - * Reads PHPUnit's own result cache and returns the test ids that failed - * or errored in the previous run. These are excluded from replay so the - * user sees current state rather than a stale pass. - * - * @return array - */ - private function loadPreviousDefects(string $projectRoot): array - { - // PHPUnit writes the cache under either `/.phpunit.result.cache` - // (legacy) or `/test-results`. Pest's Cache plugin - // additionally defaults `cacheDirectory` to - // `vendor/pestphp/pest/.temp` when the user hasn't configured one. - // We probe the common locations; if we miss the file, replay falls - // back to its safe default (still runs the test). - $candidates = [ - $projectRoot.'/.phpunit.result.cache', - $projectRoot.'/.phpunit.cache/test-results', - $projectRoot.'/.pest/cache/test-results', - $projectRoot.'/vendor/pestphp/pest/.temp/test-results', - ]; - - $path = null; - - foreach ($candidates as $candidate) { - if (is_file($candidate)) { - $path = $candidate; - - break; - } - } - - if ($path === null) { - return []; - } - - $raw = @file_get_contents($path); - - if ($raw === false) { - return []; - } - - $data = json_decode($raw, true); - - if (! is_array($data) || ! isset($data['defects']) || ! is_array($data['defects'])) { - return []; - } - - $out = []; - - foreach ($data['defects'] as $id => $_status) { - if (is_string($id)) { - $out[$id] = true; - } - } - - return $out; - } - /** * @param array $arguments * @return array @@ -471,48 +436,31 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg } $changed = $changedFiles->since($graph->recordedAtSha()) ?? []; - - // Even with zero changes, we still run through the suite so the user - // sees the previous results reflected (cached passes replay as - // instant passes; failures re-run to surface current state). This - // matches the UX of test runners like NCrunch where every run - // produces a full report regardless of what actually executed. $affected = $changed === [] ? [] : $graph->affected($changed); + $totalKnown = count($graph->allTestFiles()); + $affectedCount = count($affected); + $cachedCount = $totalKnown - $affectedCount; + $testSuite = TestSuite::getInstance(); + $affectedSet = array_fill_keys($affected, true); if (! Parallel::isEnabled()) { - // Series mode: activate replay state. Tests still appear in the - // run (correct counts, coverage aggregation, event timeline); - // unaffected ones short-circuit inside `Testable::__runTest` - // and replay their previous passing status. - $affectedSet = array_fill_keys($affected, true); - - State::instance()->activate( - $projectRoot, - $graph, - $affectedSet, - $this->loadPreviousDefects($projectRoot), + $testSuite->tests->addTestCaseFilter( + new TiaTestCaseFilter($projectRoot, $graph, $affectedSet), ); $this->output->writeln(sprintf( - ' TIA %d changed file(s) → %d affected, remaining tests replay cached result.', + ' TIA %d changed file(s) → %d affected, %d cached.', count($changed), - count($affected), + $affectedCount, + $cachedCount, )); return $arguments; } - // Parallel mode. Paratest's CLI only accepts a single positional - // ``, so we cannot pass the affected set as multiple args. - // Instead, persist the affected set to a cache file and flip a - // global that tells each worker to install the TIA filter on boot. - // - // Cost trade-off: each worker still discovers the full test tree, - // but the filter drops unaffected tests before they ever run. Narrow - // CLI handoff would be ideal; it requires generating a temporary - // phpunit.xml and is out of scope for the MVP. + // Parallel: persist affected set so workers can install the filter. if (! $this->persistAffectedSet($projectRoot, $affected)) { $this->output->writeln( ' TIA failed to persist affected set — running full suite.', @@ -524,9 +472,10 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg Parallel::setGlobal(self::REPLAYING_GLOBAL, '1'); $this->output->writeln(sprintf( - ' TIA %d changed file(s) → %d affected, remaining tests replay cached result (parallel).', + ' TIA %d changed file(s) → %d affected, %d cached (parallel).', count($changed), - count($affected), + $affectedCount, + $cachedCount, )); return $arguments; @@ -537,7 +486,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg */ private function persistAffectedSet(string $projectRoot, array $affected): bool { - $path = $projectRoot.DIRECTORY_SEPARATOR.self::AFFECTED_PATH; + $path = self::affectedPath(); $dir = dirname($path); if (! is_dir($dir) && ! @mkdir($dir, 0755, true) && ! is_dir($dir)) { @@ -588,15 +537,19 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg return $arguments; } - $recorder = Recorder::instance(); + $recorder = $this->recorder; if (! $recorder->driverAvailable()) { - $this->output->writeln( - ' TIA No coverage driver is available. '. - 'Install ext-pcov or enable Xdebug in coverage mode, then rerun with `--tia`.', - ); + $this->output->writeln([ + '', + ' ERROR No coverage driver is available.', + '', + ' TIA requires ext-pcov or Xdebug with coverage mode enabled to', + ' record the dependency graph. Install one and rerun with `--tia`.', + '', + ]); - return $arguments; + exit(1); } $recorder->activate(); @@ -624,7 +577,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg $token = (string) getmypid(); } - $path = $projectRoot.DIRECTORY_SEPARATOR.self::WORKER_CACHE_PREFIX.$token.'.json'; + $path = self::workerPath($token); $dir = dirname($path); if (! is_dir($dir) && ! @mkdir($dir, 0755, true) && ! is_dir($dir)) { @@ -653,7 +606,7 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg */ private function collectWorkerPartials(string $projectRoot): array { - $pattern = $projectRoot.DIRECTORY_SEPARATOR.self::WORKER_CACHE_PREFIX.'*.json'; + $pattern = self::workerGlob(); $matches = glob($pattern); return $matches === false ? [] : $matches; @@ -686,10 +639,12 @@ final class Tia implements AddsOutput, AfterEachable, BeforeEachable, HandlesArg $out = []; foreach ($data as $test => $sources) { - if (! is_string($test) || ! is_array($sources)) { + if (! is_string($test)) { + continue; + } + if (! is_array($sources)) { continue; } - $clean = []; foreach ($sources as $source) { diff --git a/src/Plugins/Tia/ChangedFiles.php b/src/Plugins/Tia/ChangedFiles.php index d2a568aa..18571ada 100644 --- a/src/Plugins/Tia/ChangedFiles.php +++ b/src/Plugins/Tia/ChangedFiles.php @@ -28,9 +28,9 @@ final readonly class ChangedFiles /** * @return array|null `null` when git is unavailable, or when - * the recorded SHA is no longer reachable - * from HEAD (rebase / force-push) — in - * that case the graph should be rebuilt. + * the recorded SHA is no longer reachable + * from HEAD (rebase / force-push) — in + * that case the graph should be rebuilt. */ public function since(?string $sha): ?array { diff --git a/src/Plugins/Tia/Configuration.php b/src/Plugins/Tia/Configuration.php index d711e561..0a0fe401 100644 --- a/src/Plugins/Tia/Configuration.php +++ b/src/Plugins/Tia/Configuration.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; +use Pest\Support\Container; + /** * User-facing TIA configuration, returned by `pest()->tia()`. * @@ -31,7 +33,9 @@ final class Configuration */ public function watch(array $patterns): self { - WatchPatterns::instance()->add($patterns); + /** @var WatchPatterns $watchPatterns */ + $watchPatterns = Container::getInstance()->get(WatchPatterns::class); + $watchPatterns->add($patterns); return $this; } diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index c55d3abf..490c753b 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; +use Pest\Support\Container; + /** * File-level Test Impact Analysis graph. * @@ -127,7 +129,8 @@ final class Graph } // 2. Watch-pattern lookup (non-PHP assets → test directories). - $watchPatterns = WatchPatterns::instance(); + /** @var WatchPatterns $watchPatterns */ + $watchPatterns = Container::getInstance()->get(WatchPatterns::class); $normalised = []; foreach ($changedFiles as $file) { @@ -159,7 +162,7 @@ final class Graph } /** - * @return array All project-relative test files the graph knows. + * @return array All project-relative test files the graph knows. */ public function allTestFiles(): array { diff --git a/src/Plugins/Tia/Recorder.php b/src/Plugins/Tia/Recorder.php index 48a88cae..635b9088 100644 --- a/src/Plugins/Tia/Recorder.php +++ b/src/Plugins/Tia/Recorder.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; use ReflectionClass; -use ReflectionException; /** * Captures per-test file coverage using the PCOV driver. @@ -18,8 +17,6 @@ use ReflectionException; */ final class Recorder { - private static ?self $instance = null; - /** * Test file currently being recorded, or `null` when idle. */ @@ -47,11 +44,6 @@ final class Recorder private string $driver = 'none'; - public static function instance(): self - { - return self::$instance ??= new self; - } - public function activate(): void { $this->active = true; diff --git a/src/Plugins/Tia/State.php b/src/Plugins/Tia/State.php deleted file mode 100644 index 48b829e7..00000000 --- a/src/Plugins/Tia/State.php +++ /dev/null @@ -1,159 +0,0 @@ - - */ - private array $affectedFiles = []; - - /** - * Keys are project-relative test file paths. Known = recorded in graph. - * - * @var array - */ - private array $knownFiles = []; - - /** - * Test ids (class::method) that were in the previous run's defect list. - * - * @var array - */ - private array $previousDefects = []; - - /** - * Canonicalised project root used for relative-path calculations. - */ - private string $projectRoot = ''; - - public static function instance(): self - { - return self::$instance ??= new self; - } - - /** - * Turns on replay mode with the given graph + affected set. - * - * @param array $affectedFiles - * @param array $previousDefects - */ - public function activate(string $projectRoot, Graph $graph, array $affectedFiles, array $previousDefects): void - { - $real = @realpath($projectRoot); - - $this->projectRoot = $real !== false ? $real : $projectRoot; - $this->replayMode = true; - $this->affectedFiles = $affectedFiles; - $this->previousDefects = $previousDefects; - - // Pre-compute the known set from the graph so per-test lookups stay - // O(1). Iterating edges once here beats calling `Graph::knowsTest` - // from every test's `setUp`. - $this->knownFiles = []; - - foreach ($graph->allTestFiles() as $rel) { - $this->knownFiles[$rel] = true; - } - } - - public function isReplayMode(): bool - { - return $this->replayMode; - } - - /** - * Returns `true` when the given absolute test file should replay its - * previous passing result instead of re-executing. `$testId` may be - * `null` when the caller cannot cheaply determine it (e.g. early in - * `setUp` before PHPUnit has published the name) — in that case we - * replay iff the file is safe at the file level, and `__runTest` will - * repeat the check with a proper id. - */ - public function shouldReplayFromCache(string $absoluteTestFile, ?string $testId = null): bool - { - if (! $this->replayMode) { - return false; - } - - $rel = $this->relative($absoluteTestFile); - - if ($rel === null) { - return false; - } - - if (! isset($this->knownFiles[$rel])) { - return false; - } - - if (isset($this->affectedFiles[$rel])) { - return false; - } - - if ($testId !== null && isset($this->previousDefects[$testId])) { - return false; - } - - return true; - } - - public function reset(): void - { - $this->replayMode = false; - $this->affectedFiles = []; - $this->knownFiles = []; - $this->previousDefects = []; - $this->projectRoot = ''; - } - - private function relative(string $path): ?string - { - if ($path === '' || $this->projectRoot === '') { - return null; - } - - $real = @realpath($path); - - if ($real === false) { - $real = $path; - } - - $root = rtrim($this->projectRoot, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; - - if (! str_starts_with($real, $root)) { - return null; - } - - return str_replace(DIRECTORY_SEPARATOR, '/', substr($real, strlen($root))); - } -} diff --git a/src/Plugins/Tia/WatchDefaults/Browser.php b/src/Plugins/Tia/WatchDefaults/Browser.php index 2c712f19..d4f18a67 100644 --- a/src/Plugins/Tia/WatchDefaults/Browser.php +++ b/src/Plugins/Tia/WatchDefaults/Browser.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Pest\Plugins\Tia\WatchDefaults; use Composer\InstalledVersions; +use Pest\Browser\Support\BrowserTestIdentifier; use Pest\Factories\TestCaseFactory; use Pest\TestSuite; @@ -72,7 +73,7 @@ final readonly class Browser implements WatchDefault // Scan TestRepository via BrowserTestIdentifier if pest-plugin-browser // is installed to find tests using `visit()` outside the conventional // Browser/ folder. - if (class_exists(\Pest\Browser\Support\BrowserTestIdentifier::class)) { + if (class_exists(BrowserTestIdentifier::class)) { $repo = TestSuite::getInstance()->tests; foreach ($repo->getFilenames() as $filename) { @@ -83,7 +84,7 @@ final readonly class Browser implements WatchDefault } foreach ($factory->methods as $method) { - if (\Pest\Browser\Support\BrowserTestIdentifier::isBrowserTest($method)) { + if (BrowserTestIdentifier::isBrowserTest($method)) { $rel = $this->fileRelative($projectRoot, $filename); if ($rel !== null) { diff --git a/src/Plugins/Tia/WatchDefaults/WatchDefault.php b/src/Plugins/Tia/WatchDefaults/WatchDefault.php index ce84b5ae..44d8d39f 100644 --- a/src/Plugins/Tia/WatchDefaults/WatchDefault.php +++ b/src/Plugins/Tia/WatchDefaults/WatchDefault.php @@ -22,7 +22,7 @@ interface WatchDefault public function applicable(): bool; /** - * @return array> glob → list of project-relative test dirs + * @return array> glob → list of project-relative test dirs */ public function defaults(string $projectRoot, string $testPath): array; } diff --git a/src/Plugins/Tia/WatchPatterns.php b/src/Plugins/Tia/WatchPatterns.php index 6bff5810..71591bbf 100644 --- a/src/Plugins/Tia/WatchPatterns.php +++ b/src/Plugins/Tia/WatchPatterns.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; use Pest\Plugins\Tia\WatchDefaults\WatchDefault; +use Pest\TestSuite; /** * Maps non-PHP file globs to the test directories they should invalidate. @@ -37,17 +38,10 @@ final class WatchPatterns ]; /** - * @var array> glob → list of project-relative test dirs + * @var array> glob → list of project-relative test dirs */ private array $patterns = []; - private static ?self $instance = null; - - public static function instance(): self - { - return self::$instance ??= new self; - } - /** * Probes every registered `WatchDefault` and merges the patterns of * those that apply. Called once during Tia plugin boot, after BootFiles @@ -56,7 +50,7 @@ final class WatchPatterns */ public function useDefaults(string $projectRoot): void { - $testPath = \Pest\TestSuite::getInstance()->testPath; + $testPath = TestSuite::getInstance()->testPath; foreach (self::DEFAULTS as $class) { $default = new $class; @@ -94,7 +88,7 @@ final class WatchPatterns * * @param string $projectRoot Absolute path. * @param array $changedFiles Project-relative paths. - * @return array Project-relative test directories. + * @return array Project-relative test directories. */ public function matchedDirectories(string $projectRoot, array $changedFiles): array { diff --git a/src/Subscribers/EnsureTiaCoverageIsFlushed.php b/src/Subscribers/EnsureTiaCoverageIsFlushed.php index 1b3e8fa0..2b10ebfe 100644 --- a/src/Subscribers/EnsureTiaCoverageIsFlushed.php +++ b/src/Subscribers/EnsureTiaCoverageIsFlushed.php @@ -14,10 +14,12 @@ use PHPUnit\Event\Test\FinishedSubscriber; * * @internal */ -final class EnsureTiaCoverageIsFlushed implements FinishedSubscriber +final readonly class EnsureTiaCoverageIsFlushed implements FinishedSubscriber { + public function __construct(private Recorder $recorder) {} + public function notify(Finished $event): void { - Recorder::instance()->endTest(); + $this->recorder->endTest(); } } diff --git a/src/Subscribers/EnsureTiaCoverageIsRecorded.php b/src/Subscribers/EnsureTiaCoverageIsRecorded.php index 0d388c98..f7ef815f 100644 --- a/src/Subscribers/EnsureTiaCoverageIsRecorded.php +++ b/src/Subscribers/EnsureTiaCoverageIsRecorded.php @@ -15,13 +15,13 @@ use PHPUnit\Event\Test\PreparedSubscriber; * * @internal */ -final class EnsureTiaCoverageIsRecorded implements PreparedSubscriber +final readonly class EnsureTiaCoverageIsRecorded implements PreparedSubscriber { + public function __construct(private Recorder $recorder) {} + public function notify(Prepared $event): void { - $recorder = Recorder::instance(); - - if (! $recorder->isActive()) { + if (! $this->recorder->isActive()) { return; } @@ -31,6 +31,6 @@ final class EnsureTiaCoverageIsRecorded implements PreparedSubscriber return; } - $recorder->beginTest($test->className(), $test->methodName(), $test->file()); + $this->recorder->beginTest($test->className(), $test->methodName(), $test->file()); } } diff --git a/src/Plugins/Tia/TiaTestCaseFilter.php b/src/TestCaseFilters/TiaTestCaseFilter.php similarity index 98% rename from src/Plugins/Tia/TiaTestCaseFilter.php rename to src/TestCaseFilters/TiaTestCaseFilter.php index d210b4be..682b31fd 100644 --- a/src/Plugins/Tia/TiaTestCaseFilter.php +++ b/src/TestCaseFilters/TiaTestCaseFilter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Pest\Plugins\Tia; +namespace Pest\TestCaseFilters; use Pest\Contracts\TestCaseFilter; use Pest\Plugins\Tia\Graph;