mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 10:52:14 +02:00
wip
This commit is contained in:
@ -88,7 +88,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
* flag forces an immediate retry (e.g. right after publishing a
|
* flag forces an immediate retry (e.g. right after publishing a
|
||||||
* baseline from CI for the first time).
|
* baseline from CI for the first time).
|
||||||
*/
|
*/
|
||||||
private const string REFETCH_OPTION = '--tia-refetch';
|
private const string REFETCH_OPTION = '--refetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State keys under which TIA persists its blobs. Kept here as constants
|
* State keys under which TIA persists its blobs. Kept here as constants
|
||||||
@ -123,7 +123,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
* Cooldown marker keyed by `BaselineSync` after a failed fetch. Holds
|
* Cooldown marker keyed by `BaselineSync` after a failed fetch. Holds
|
||||||
* `{"until": <unix>}` — subsequent runs within the window skip the
|
* `{"until": <unix>}` — subsequent runs within the window skip the
|
||||||
* fetch attempt (and its `gh run list` network hop) until the
|
* fetch attempt (and its `gh run list` network hop) until the
|
||||||
* cooldown expires or the user passes `--tia-refetch`.
|
* cooldown expires or the user passes `--refetch`.
|
||||||
*/
|
*/
|
||||||
public const string KEY_FETCH_COOLDOWN = 'fetch-cooldown.json';
|
public const string KEY_FETCH_COOLDOWN = 'fetch-cooldown.json';
|
||||||
|
|
||||||
@ -224,11 +224,22 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
private bool $recordingActive = false;
|
private bool $recordingActive = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when `--tia-refetch` is in the current argv — `BaselineSync`
|
* True when `--refetch` is in the current argv — `BaselineSync`
|
||||||
* uses it to bypass the post-failure fetch cooldown.
|
* uses it to bypass the post-failure fetch cooldown.
|
||||||
*/
|
*/
|
||||||
private bool $forceRefetch = false;
|
private bool $forceRefetch = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when `--fresh` is in the current argv — record-mode paths
|
||||||
|
* use it to gate `Graph::pruneMissingTests()`. On a partial record
|
||||||
|
* (default `--tia` after a branch switch, etc.) the working tree may
|
||||||
|
* not contain every test the shared graph knows about, so pruning
|
||||||
|
* would silently delete edges for tests that exist on other
|
||||||
|
* branches. `--fresh` rebuilds from scratch anyway, so pruning
|
||||||
|
* there is both safe and useful for cleaning up stale entries.
|
||||||
|
*/
|
||||||
|
private bool $freshRebuild = false;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly OutputInterface $output,
|
private readonly OutputInterface $output,
|
||||||
private readonly Recorder $recorder,
|
private readonly Recorder $recorder,
|
||||||
@ -348,6 +359,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
// silently ignore it here and let whatever else consumes it
|
// silently ignore it here and let whatever else consumes it
|
||||||
// handle it. The flag isn't popped in that branch.
|
// handle it. The flag isn't popped in that branch.
|
||||||
$forceRebuild = $freshRequested && ($enabled || $recordingGlobal || $replayingGlobal);
|
$forceRebuild = $freshRequested && ($enabled || $recordingGlobal || $replayingGlobal);
|
||||||
|
$this->freshRebuild = $forceRebuild;
|
||||||
|
|
||||||
if (! $enabled && ! $this->forceRefetch && ! $recordingGlobal && ! $replayingGlobal) {
|
if (! $enabled && ! $this->forceRefetch && ! $recordingGlobal && ! $replayingGlobal) {
|
||||||
return $arguments;
|
return $arguments;
|
||||||
@ -444,7 +456,17 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
$graph->replaceTestTables($perTestTables);
|
$graph->replaceTestTables($perTestTables);
|
||||||
$graph->replaceTestInertiaComponents($perTestInertia);
|
$graph->replaceTestInertiaComponents($perTestInertia);
|
||||||
$graph->replaceJsFileToComponents(JsModuleGraph::build($projectRoot));
|
$graph->replaceJsFileToComponents(JsModuleGraph::build($projectRoot));
|
||||||
|
|
||||||
|
// Pruning checks the local filesystem for each known test file —
|
||||||
|
// on a partial record (no `--fresh`) the current checkout may
|
||||||
|
// legitimately be missing tests that exist on other branches
|
||||||
|
// sharing this graph, so pruning would silently delete their
|
||||||
|
// edges. Stale entries for genuinely-deleted tests are harmless
|
||||||
|
// (test discovery never finds the file) and get cleaned up on
|
||||||
|
// the next `--fresh` rebuild.
|
||||||
|
if ($this->freshRebuild) {
|
||||||
$graph->pruneMissingTests();
|
$graph->pruneMissingTests();
|
||||||
|
}
|
||||||
|
|
||||||
// Fold in the results collected during this same record run. The
|
// Fold in the results collected during this same record run. The
|
||||||
// `AddsOutput` pass that runs `snapshotTestResults` fires *before*
|
// `AddsOutput` pass that runs `snapshotTestResults` fires *before*
|
||||||
@ -612,7 +634,14 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
$graph->replaceTestTables($finalisedTables);
|
$graph->replaceTestTables($finalisedTables);
|
||||||
$graph->replaceTestInertiaComponents($finalisedInertia);
|
$graph->replaceTestInertiaComponents($finalisedInertia);
|
||||||
$graph->replaceJsFileToComponents(JsModuleGraph::build($projectRoot));
|
$graph->replaceJsFileToComponents(JsModuleGraph::build($projectRoot));
|
||||||
|
|
||||||
|
// See `terminate()` — same rationale: pruning by current
|
||||||
|
// working-tree presence would silently drop edges for tests
|
||||||
|
// owned by other branches sharing this graph. Only safe on
|
||||||
|
// `--fresh` rebuilds.
|
||||||
|
if ($this->freshRebuild) {
|
||||||
$graph->pruneMissingTests();
|
$graph->pruneMissingTests();
|
||||||
|
}
|
||||||
|
|
||||||
if (! $this->saveGraph($graph)) {
|
if (! $this->saveGraph($graph)) {
|
||||||
$this->output->writeln(' <fg=red>TIA</> failed to write graph.');
|
$this->output->writeln(' <fg=red>TIA</> failed to write graph.');
|
||||||
|
|||||||
@ -67,7 +67,7 @@ final readonly class BaselineSync
|
|||||||
* Rationale: when the remote workflow hasn't published yet, every
|
* Rationale: when the remote workflow hasn't published yet, every
|
||||||
* `pest --tia` invocation would otherwise re-hit `gh run list` and
|
* `pest --tia` invocation would otherwise re-hit `gh run list` and
|
||||||
* re-print the publish instructions — noisy + slow. Back off for a
|
* re-print the publish instructions — noisy + slow. Back off for a
|
||||||
* day, let the user override with `--tia-refetch`.
|
* day, let the user override with `--refetch`.
|
||||||
*/
|
*/
|
||||||
private const int FETCH_COOLDOWN_SECONDS = 86400;
|
private const int FETCH_COOLDOWN_SECONDS = 86400;
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ final readonly class BaselineSync
|
|||||||
* landed; coverage is best-effort since plain `--tia` (no `--coverage`)
|
* landed; coverage is best-effort since plain `--tia` (no `--coverage`)
|
||||||
* never reads it.
|
* never reads it.
|
||||||
*
|
*
|
||||||
* `$force = true` (driven by `--tia-refetch`) ignores the post-failure
|
* `$force = true` (driven by `--refetch`) ignores the post-failure
|
||||||
* cooldown so the user can retry on demand without waiting out the
|
* cooldown so the user can retry on demand without waiting out the
|
||||||
* 24h window.
|
* 24h window.
|
||||||
*/
|
*/
|
||||||
@ -97,7 +97,7 @@ final readonly class BaselineSync
|
|||||||
if (! $force && ($remaining = $this->cooldownRemaining()) !== null) {
|
if (! $force && ($remaining = $this->cooldownRemaining()) !== null) {
|
||||||
$this->output->writeln(sprintf(
|
$this->output->writeln(sprintf(
|
||||||
' <fg=yellow>TIA</> last fetch found no baseline — next auto-retry in %s. '
|
' <fg=yellow>TIA</> last fetch found no baseline — next auto-retry in %s. '
|
||||||
.'Override with <fg=cyan>--tia-refetch</>.',
|
.'Override with <fg=cyan>--refetch</>.',
|
||||||
$this->formatDuration($remaining),
|
$this->formatDuration($remaining),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@ -226,17 +226,18 @@ final class Graph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inertia page-component routing. When a Vue/React/Svelte page
|
// Inertia page-component routing. When a page under
|
||||||
// under `resources/js/Pages/` changes, map it to the component
|
// `resources/js/Pages/` changes, map it to the component name
|
||||||
// name Inertia would use (the path relative to `Pages/`, with
|
// Inertia would use (the path relative to `Pages/`, extension
|
||||||
// the extension stripped) and intersect with the captured
|
// stripped) and intersect with the captured component edges.
|
||||||
// component edges. Only invalidates tests that actually
|
// Only invalidates tests that actually rendered the page.
|
||||||
// rendered the page. Pages with no captured edges (never
|
// Pages with no captured edges (never rendered during record,
|
||||||
// rendered during record, brand-new on this branch) fall
|
// brand-new on this branch) fall through to the watch-pattern
|
||||||
// through to the watch-pattern fallback via
|
// fallback — safe over-run. Pages handled here are tracked in
|
||||||
// `$unknownPageComponents` — safe over-run.
|
// `$preciselyHandledPages` so the watch broadcast and JS-dep
|
||||||
|
// lookup don't re-route them.
|
||||||
$changedComponents = [];
|
$changedComponents = [];
|
||||||
$unknownPageComponents = [];
|
$preciselyHandledPages = [];
|
||||||
|
|
||||||
foreach ($nonMigrationPaths as $rel) {
|
foreach ($nonMigrationPaths as $rel) {
|
||||||
$component = $this->componentForInertiaPage($rel);
|
$component = $this->componentForInertiaPage($rel);
|
||||||
@ -247,20 +248,6 @@ final class Graph
|
|||||||
|
|
||||||
if ($this->anyTestUses($this->testInertiaComponents, $component)) {
|
if ($this->anyTestUses($this->testInertiaComponents, $component)) {
|
||||||
$changedComponents[$component] = true;
|
$changedComponents[$component] = true;
|
||||||
} else {
|
|
||||||
$unknownPageComponents[] = $rel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pages whose component already resolved precisely via the
|
|
||||||
// direct Inertia edges path must not leak back through any
|
|
||||||
// broader mechanism (either the JS-dep lookup below, or the
|
|
||||||
// watch pattern further down).
|
|
||||||
$preciselyHandledPages = [];
|
|
||||||
foreach ($nonMigrationPaths as $rel) {
|
|
||||||
$component = $this->componentForInertiaPage($rel);
|
|
||||||
|
|
||||||
if ($component !== null && isset($changedComponents[$component])) {
|
|
||||||
$preciselyHandledPages[$rel] = true;
|
$preciselyHandledPages[$rel] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,68 +347,6 @@ final class Graph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blade orphan detection. Mirror of the JS orphan check: a
|
|
||||||
// newly-added `.blade.php` that literally nothing references
|
|
||||||
// (no `@include('x.y')`, no `view('x.y')`, no `Route::view`)
|
|
||||||
// can't affect any test, so the broad `resources/views/**`
|
|
||||||
// watch broadcast is wasted work. We only do this for blades
|
|
||||||
// *outside* the auto-resolved directories where Laravel /
|
|
||||||
// Livewire / Flux map class/tag names to file paths without
|
|
||||||
// a literal reference — there the absence of a string match
|
|
||||||
// isn't evidence of orphanhood.
|
|
||||||
$newBlades = [];
|
|
||||||
foreach ($nonMigrationPaths as $rel) {
|
|
||||||
if (isset($preciselyHandledPages[$rel])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isset($sharedFilesResolved[$rel])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (! str_ends_with(strtolower($rel), '.blade.php')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isset($this->fileIds[$rel])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (! $this->isBladeOrphanEligible($rel)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (! is_file($this->projectRoot.DIRECTORY_SEPARATOR.$rel)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$newBlades[] = $rel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($newBlades !== []) {
|
|
||||||
$needles = [];
|
|
||||||
foreach ($newBlades as $rel) {
|
|
||||||
$name = $this->viewNameFromBladePath($rel);
|
|
||||||
if ($name !== null) {
|
|
||||||
$needles[$name] = $rel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$referenced = $this->findReferencedViewNames($needles, $newBlades);
|
|
||||||
|
|
||||||
foreach ($newBlades as $rel) {
|
|
||||||
$name = $this->viewNameFromBladePath($rel);
|
|
||||||
if ($name === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (! isset($referenced[$name])) {
|
|
||||||
// No `@include`, `view(…)`, or `Route::view(…)`
|
|
||||||
// mentions this view name anywhere in the project.
|
|
||||||
// Dynamic includes (`@include($var)`) can't be
|
|
||||||
// proven against — we accept the tradeoff of
|
|
||||||
// under-invalidation in the narrow case where a
|
|
||||||
// view is loaded exclusively via runtime
|
|
||||||
// composition. The watch pattern still fires for
|
|
||||||
// blades in components/, livewire/, pages/ etc.
|
|
||||||
$sharedFilesResolved[$rel] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($changedComponents !== []) {
|
if ($changedComponents !== []) {
|
||||||
foreach ($this->testInertiaComponents as $testFile => $components) {
|
foreach ($this->testInertiaComponents as $testFile => $components) {
|
||||||
if (isset($affectedSet[$testFile])) {
|
if (isset($affectedSet[$testFile])) {
|
||||||
@ -805,9 +730,13 @@ final class Graph
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the whole JS dep map. Called at record time with the
|
* Replaces the whole JS dep map. Called at record time with the
|
||||||
* output of `JsModuleGraph::build()`. Unlike the test-level
|
* output of `JsModuleGraph::build()`. Empty input is treated as a
|
||||||
* replacements above this is a wholesale overwrite — the
|
* resolver failure (Node missing, Vite refused to load, transient
|
||||||
* resolver produces the full graph on every run.
|
* `npm install`) rather than a legitimate "no JS pages" signal —
|
||||||
|
* we keep the previous map. Stale entries for genuinely-deleted
|
||||||
|
* pages are harmless because deleted files never enter the
|
||||||
|
* changed set; over-broadcasting every JS edit through the watch
|
||||||
|
* pattern after a flaky Node run would be a real regression.
|
||||||
*
|
*
|
||||||
* @param array<string, array<int, string>> $fileToComponents
|
* @param array<string, array<int, string>> $fileToComponents
|
||||||
*/
|
*/
|
||||||
@ -836,6 +765,10 @@ final class Graph
|
|||||||
$out[$path] = $keys;
|
$out[$path] = $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($out === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ksort($out);
|
ksort($out);
|
||||||
|
|
||||||
$this->jsFileToComponents = $out;
|
$this->jsFileToComponents = $out;
|
||||||
@ -904,7 +837,7 @@ final class Graph
|
|||||||
|
|
||||||
$extension = substr($tail, $dot + 1);
|
$extension = substr($tail, $dot + 1);
|
||||||
|
|
||||||
if (! in_array($extension, ['vue', 'tsx', 'jsx', 'svelte'], true)) {
|
if (! in_array($extension, ['vue', 'tsx', 'jsx', 'svelte', 'ts', 'js'], true)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -931,142 +864,6 @@ final class Graph
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Blades inside auto-resolving directories (Blade components,
|
|
||||||
* Livewire components, Volt pages, Flux UI, vendor packages) are
|
|
||||||
* referenced by *class name* or *tag name* at runtime rather than
|
|
||||||
* by literal path string. Absence of a string match therefore
|
|
||||||
* can't prove orphanhood — we skip them and let the watch pattern
|
|
||||||
* do its job.
|
|
||||||
*/
|
|
||||||
private function isBladeOrphanEligible(string $rel): bool
|
|
||||||
{
|
|
||||||
$prefix = 'resources/views/';
|
|
||||||
if (! str_starts_with($rel, $prefix)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tail = substr($rel, strlen($prefix));
|
|
||||||
|
|
||||||
foreach (['components/', 'livewire/', 'pages/', 'flux/', 'vendor/'] as $autoResolved) {
|
|
||||||
if (str_starts_with($tail, $autoResolved)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps `resources/views/admin/reports.blade.php` →
|
|
||||||
* `admin.reports` (Laravel's dot-notation view name). Returns
|
|
||||||
* null for anything that isn't a regular `.blade.php` under
|
|
||||||
* `resources/views/`.
|
|
||||||
*/
|
|
||||||
private function viewNameFromBladePath(string $rel): ?string
|
|
||||||
{
|
|
||||||
$prefix = 'resources/views/';
|
|
||||||
$suffix = '.blade.php';
|
|
||||||
|
|
||||||
if (! str_starts_with($rel, $prefix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (! str_ends_with(strtolower($rel), $suffix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tail = substr($rel, strlen($prefix));
|
|
||||||
$base = substr($tail, 0, strlen($tail) - strlen($suffix));
|
|
||||||
|
|
||||||
if ($base === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return str_replace('/', '.', $base);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scans every `.php` / `.blade.php` file under `app/`, `routes/`,
|
|
||||||
* `tests/`, and `resources/views/` for a literal occurrence of
|
|
||||||
* each needle (the dot-notation view name). Returns the set of
|
|
||||||
* needles that were found at least once.
|
|
||||||
*
|
|
||||||
* @param array<string, string> $needles view name → blade path (target itself, skipped during scan)
|
|
||||||
* @param array<int, string> $skipPaths project-relative paths to skip (the new blades themselves)
|
|
||||||
* @return array<string, true>
|
|
||||||
*/
|
|
||||||
private function findReferencedViewNames(array $needles, array $skipPaths): array
|
|
||||||
{
|
|
||||||
if ($needles === []) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$skipSet = array_fill_keys($skipPaths, true);
|
|
||||||
$found = [];
|
|
||||||
|
|
||||||
$roots = [
|
|
||||||
$this->projectRoot.DIRECTORY_SEPARATOR.'app',
|
|
||||||
$this->projectRoot.DIRECTORY_SEPARATOR.'routes',
|
|
||||||
$this->projectRoot.DIRECTORY_SEPARATOR.'tests',
|
|
||||||
$this->projectRoot.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'views',
|
|
||||||
];
|
|
||||||
|
|
||||||
$rootLen = strlen(rtrim($this->projectRoot, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR);
|
|
||||||
|
|
||||||
foreach ($roots as $root) {
|
|
||||||
if (! is_dir($root)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($found) === count($needles)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iterator = new \RecursiveIteratorIterator(
|
|
||||||
new \RecursiveDirectoryIterator($root, \FilesystemIterator::SKIP_DOTS),
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($iterator as $fileInfo) {
|
|
||||||
if (count($found) === count($needles)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (! $fileInfo->isFile()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $fileInfo->getPathname();
|
|
||||||
$lower = strtolower((string) $path);
|
|
||||||
|
|
||||||
if (! str_ends_with($lower, '.php')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$relPath = str_replace(DIRECTORY_SEPARATOR, '/', substr((string) $path, $rootLen));
|
|
||||||
|
|
||||||
if (isset($skipSet[$relPath])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$content = @file_get_contents($path);
|
|
||||||
|
|
||||||
if ($content === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array_keys($needles) as $needle) {
|
|
||||||
if (isset($found[$needle])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (str_contains($content, $needle)) {
|
|
||||||
$found[$needle] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drops edges whose test file no longer exists on disk. Prevents the graph
|
* Drops edges whose test file no longer exists on disk. Prevents the graph
|
||||||
* from keeping stale entries for deleted / renamed tests that would later
|
* from keeping stale entries for deleted / renamed tests that would later
|
||||||
|
|||||||
@ -51,6 +51,13 @@ final readonly class Php implements WatchDefault
|
|||||||
// record-mode graph rebuild.
|
// record-mode graph rebuild.
|
||||||
$testPath.'/Pest.php' => [$testPath],
|
$testPath.'/Pest.php' => [$testPath],
|
||||||
|
|
||||||
|
// Pest dataset definitions are loaded once at boot, outside
|
||||||
|
// the per-test coverage window — no edge captures them. A
|
||||||
|
// change to a shared dataset can flip the result of any test
|
||||||
|
// that uses it, so broadcast every dataset edit to the full
|
||||||
|
// suite.
|
||||||
|
$testPath.'/Datasets/**/*.php' => [$testPath],
|
||||||
|
|
||||||
// Test fixtures — JSON, CSV, XML, TXT data files consumed by
|
// Test fixtures — JSON, CSV, XML, TXT data files consumed by
|
||||||
// assertions. A fixture change can flip a test result.
|
// assertions. A fixture change can flip a test result.
|
||||||
$testPath.'/Fixtures/**/*.json' => [$testPath],
|
$testPath.'/Fixtures/**/*.json' => [$testPath],
|
||||||
|
|||||||
Reference in New Issue
Block a user