This commit is contained in:
nuno maduro
2026-04-28 21:28:46 +01:00
parent b944ee5841
commit 405d8d4406
9 changed files with 421 additions and 64 deletions

View File

@ -12,9 +12,8 @@ use Pest\TestSuite;
/**
* Watch patterns for frontend assets that affect browser tests.
*
* Uses `BrowserTestIdentifier` from pest-plugin-browser (if installed) to
* auto-discover directories containing browser tests. Falls back to the
* `tests/Browser` convention when the plugin is absent.
* Uses `BrowserTestIdentifier` from pest-plugin-browser to auto-discover tests
* using `visit()`. Also keeps the `tests/Browser` convention when present.
*
* @internal
*/
@ -31,7 +30,7 @@ final readonly class Browser implements WatchDefault
public function defaults(string $projectRoot, string $testPath): array
{
$browserDirs = $this->detectBrowserTestDirs($projectRoot, $testPath);
$browserTargets = self::detectBrowserTestTargets($projectRoot, $testPath);
$globs = [
'resources/js/**/*.js',
@ -51,7 +50,7 @@ final readonly class Browser implements WatchDefault
$patterns = [];
foreach ($globs as $glob) {
$patterns[$glob] = $browserDirs;
$patterns[$glob] = $browserTargets;
}
return $patterns;
@ -60,19 +59,19 @@ final readonly class Browser implements WatchDefault
/**
* @return array<int, string>
*/
private function detectBrowserTestDirs(string $projectRoot, string $testPath): array
public static function detectBrowserTestTargets(string $projectRoot, string $testPath): array
{
$dirs = [];
$targets = [];
$candidate = $testPath.'/Browser';
if (is_dir($projectRoot.DIRECTORY_SEPARATOR.$candidate)) {
$dirs[] = $candidate;
$targets[] = $candidate;
}
// Scan TestRepository via BrowserTestIdentifier if pest-plugin-browser
// is installed to find tests using `visit()` outside the conventional
// Browser/ folder.
// is installed to find exact tests using `visit()` outside the
// conventional Browser/ folder.
if (class_exists(BrowserTestIdentifier::class)) {
$repo = TestSuite::getInstance()->tests;
@ -85,10 +84,10 @@ final readonly class Browser implements WatchDefault
foreach ($factory->methods as $method) {
if (BrowserTestIdentifier::isBrowserTest($method)) {
$rel = $this->fileRelative($projectRoot, $filename);
$rel = self::fileRelative($projectRoot, $filename);
if ($rel !== null) {
$dirs[] = dirname($rel);
$targets[] = $rel;
}
break;
@ -97,10 +96,10 @@ final readonly class Browser implements WatchDefault
}
}
return array_values(array_unique($dirs === [] ? [$testPath] : $dirs));
return array_values(array_unique($targets));
}
private function fileRelative(string $projectRoot, string $path): ?string
private static function fileRelative(string $projectRoot, string $path): ?string
{
$real = @realpath($path);

View File

@ -26,12 +26,10 @@ final readonly class Inertia implements WatchDefault
public function defaults(string $projectRoot, string $testPath): array
{
$browserDir = is_dir($projectRoot.DIRECTORY_SEPARATOR.$testPath.'/Browser')
? $testPath.'/Browser'
: $testPath;
$browserTargets = Browser::detectBrowserTestTargets($projectRoot, $testPath);
// Inertia page components (React / Vue / Svelte). Scoped to
// `$browserDir` only — a Vue/React edit cannot change the
// browser tests only — a Vue/React edit cannot change the
// output of a server-side Inertia test (those assert on the
// component *name* returned by `Inertia::render()`, not its
// client-side implementation). Broad invalidation is only
@ -47,21 +45,21 @@ final readonly class Inertia implements WatchDefault
foreach (['Pages', 'pages'] as $pages) {
foreach (['vue', 'tsx', 'jsx', 'svelte', 'ts', 'js'] as $ext) {
$patterns["resources/js/{$pages}/**/*.{$ext}"] = [$browserDir];
$patterns["resources/js/{$pages}/**/*.{$ext}"] = $browserTargets;
}
}
foreach (['Layouts', 'layouts', 'Components', 'components'] as $shared) {
foreach (['vue', 'tsx', 'ts', 'js'] as $ext) {
$patterns["resources/js/{$shared}/**/*.{$ext}"] = [$browserDir];
$patterns["resources/js/{$shared}/**/*.{$ext}"] = $browserTargets;
}
}
// SSR entry point.
$patterns['resources/js/ssr.js'] = [$browserDir];
$patterns['resources/js/ssr.ts'] = [$browserDir];
$patterns['resources/js/app.js'] = [$browserDir];
$patterns['resources/js/app.ts'] = [$browserDir];
$patterns['resources/js/ssr.js'] = $browserTargets;
$patterns['resources/js/ssr.ts'] = $browserTargets;
$patterns['resources/js/app.js'] = $browserTargets;
$patterns['resources/js/app.ts'] = $browserTargets;
return $patterns;
}

View File

@ -27,10 +27,6 @@ final readonly class Laravel implements WatchDefault
public function defaults(string $projectRoot, string $testPath): array
{
$featurePath = is_dir($projectRoot.DIRECTORY_SEPARATOR.$testPath.'/Feature')
? $testPath.'/Feature'
: $testPath;
return [
// Config — loaded during app boot (setUp), invisible to coverage.
// Affects both Feature and Unit: Pest.php commonly binds fakes
@ -39,8 +35,8 @@ final readonly class Laravel implements WatchDefault
'config/**/*.php' => [$testPath],
// Routes — loaded during boot. HTTP/Feature tests depend on them.
'routes/*.php' => [$featurePath],
'routes/**/*.php' => [$featurePath],
'routes/*.php' => [$testPath],
'routes/**/*.php' => [$testPath],
// Service providers / bootstrap — loaded during boot, affect
// bindings, middleware, event listeners, scheduled tasks.
@ -59,27 +55,27 @@ final readonly class Laravel implements WatchDefault
'database/factories/**/*.php' => [$testPath],
// Blade templates — compiled to cache, source file not executed.
'resources/views/**/*.blade.php' => [$featurePath],
'resources/views/**/*.blade.php' => [$testPath],
// Email templates are nested under views/email or views/emails
// by convention and power mailable tests that render markup.
'resources/views/email/**/*.blade.php' => [$featurePath],
'resources/views/emails/**/*.blade.php' => [$featurePath],
'resources/views/email/**/*.blade.php' => [$testPath],
'resources/views/emails/**/*.blade.php' => [$testPath],
// Translations — JSON translations read via file_get_contents,
// PHP translations loaded via include (but during boot).
'lang/**/*.php' => [$featurePath],
'lang/**/*.json' => [$featurePath],
'resources/lang/**/*.php' => [$featurePath],
'resources/lang/**/*.json' => [$featurePath],
'lang/**/*.php' => [$testPath],
'lang/**/*.json' => [$testPath],
'resources/lang/**/*.php' => [$testPath],
'resources/lang/**/*.json' => [$testPath],
// Build tool config — affects compiled assets consumed by
// browser and Inertia tests.
'vite.config.js' => [$featurePath],
'vite.config.ts' => [$featurePath],
'webpack.mix.js' => [$featurePath],
'tailwind.config.js' => [$featurePath],
'tailwind.config.ts' => [$featurePath],
'postcss.config.js' => [$featurePath],
'vite.config.js' => [$testPath],
'vite.config.ts' => [$testPath],
'webpack.mix.js' => [$testPath],
'tailwind.config.js' => [$testPath],
'tailwind.config.ts' => [$testPath],
'postcss.config.js' => [$testPath],
];
}
}