Inline the error layout's own rules beside the app's build
Plan step 46, from SealShare's breakpoint walk: an application imports the stylesheets its own views render, and none renders the error layout, so under a real build its 403/404/500 pages drew unstyled. With a build the page now keeps the app's Vite tags (foundation, tokens, scheme, font) and inlines Stylesheets::bundle() of components/error-page.css and its imports beside them (ErrorPage::layoutStyles(), cached). Without a build the fallback is unchanged. An application imports nothing for its error pages; one that imports all.css gets the same layered rules twice, which is harmless. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
05f115ab36
commit
70c983b7bb
@@ -3,6 +3,9 @@
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Vite;
|
||||
use Illuminate\Support\Str;
|
||||
use NoNameWeb\LivewireMaterial\Support\Stylesheets;
|
||||
|
||||
use function Orchestra\Testbench\workbench_path;
|
||||
|
||||
/**
|
||||
* The 404 page, reached the way a visitor reaches it: an address nothing answers.
|
||||
@@ -95,3 +98,55 @@ it('draws the button and the shape from the inlined fallback stylesheet, and rep
|
||||
File::deleteDirectory($public);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan step 46: an application's own build, which imports the stylesheets its views render and so
|
||||
* never `error-page.css` — SealShare's didn't, and its error pages drew a shape filling the window
|
||||
* with the headline unstyled in a corner. The probe build here is the foundation and the Workbench's
|
||||
* scheme and nothing else, so everything the layout draws must come from the rules the page inlines
|
||||
* beside the application's `<link>`.
|
||||
*/
|
||||
it('draws the layout from its own inlined rules under a build that never imports them', function () {
|
||||
$public = sys_get_temp_dir().'/livewire-material-build-'.Str::random(8);
|
||||
File::ensureDirectoryExists($public.'/build/assets');
|
||||
|
||||
// The foundation and the scheme, as an application's bundle would carry them; without the font
|
||||
// face, whose relative url would 404 from here and is no part of what this test proves.
|
||||
$css = Stylesheets::bundle([
|
||||
realpath(__DIR__.'/../../resources/css/foundation.css'),
|
||||
workbench_path('resources/css/material-scheme.css'),
|
||||
]);
|
||||
File::put($public.'/build/assets/app-probe.css', (string) preg_replace('/@font-face\s*\{[^}]*\}/', '', $css));
|
||||
File::put($public.'/build/manifest.json', json_encode([
|
||||
'resources/css/app.css' => ['file' => 'assets/app-probe.css', 'src' => 'resources/css/app.css', 'isEntry' => true],
|
||||
]));
|
||||
|
||||
$originalPublicPath = app()->publicPath();
|
||||
$originalVite = config('livewire-material.showcase.vite');
|
||||
app()->usePublicPath($public);
|
||||
Vite::useHotFile($public.'/hot');
|
||||
config(['livewire-material.showcase.vite' => ['resources/css/app.css']]);
|
||||
|
||||
try {
|
||||
missingPage('light')
|
||||
->resize(1280, 800)
|
||||
->assertScript("document.querySelector('link[href*=\"/build/assets/app-probe.css\"]') !== null")
|
||||
->assertScript("document.querySelector('style[data-md-error-styles]') !== null")
|
||||
->assertScript("document.querySelector('style[data-md-error-fallback]') === null")
|
||||
// The layout: a centred column, the art at its 240px from medium, not a shape filling the window.
|
||||
->assertScript("getComputedStyle(document.querySelector('[data-md-error-page]')).display === 'flex'")
|
||||
->assertScript("Math.round(document.querySelector('[data-md-error-art]').getBoundingClientRect().width) === 240")
|
||||
->assertScript("document.querySelector('[data-md-error-shape] svg').getBoundingClientRect().width <= 240")
|
||||
->assertScript("(() => { const r = document.querySelector('[data-md-error-headline]').getBoundingClientRect(); return Math.abs((r.left + r.right) / 2 - window.innerWidth / 2) <= 2; })()")
|
||||
// The button and shape rules the layout imports, and the scheme from the application's build.
|
||||
->assertScript("getComputedStyle(document.querySelector('[data-md-button]')).borderRadius !== '0px'")
|
||||
->assertScript(paintedIn('[data-md-button][data-md-variant=filled]', 'primary'))
|
||||
->assertScript(paintedIn('body', 'surface'))
|
||||
->assertNoJavaScriptErrors();
|
||||
} finally {
|
||||
config(['livewire-material.showcase.vite' => $originalVite]);
|
||||
app()->usePublicPath($originalPublicPath);
|
||||
Vite::useHotFile($originalPublicPath.'/hot');
|
||||
File::deleteDirectory($public);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ use Illuminate\Support\Str;
|
||||
use NoNameWeb\LivewireMaterial\LivewireMaterialServiceProvider;
|
||||
use NoNameWeb\LivewireMaterial\Support\ErrorPage;
|
||||
use NoNameWeb\LivewireMaterial\Support\Scheme;
|
||||
use NoNameWeb\LivewireMaterial\Support\Stylesheets;
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses;
|
||||
|
||||
@@ -94,7 +95,13 @@ it('lets the application\'s own error view win', function () {
|
||||
$this->get('/abort/403')->assertSee('data-md-error-page', false);
|
||||
});
|
||||
|
||||
it('loads the application\'s Vite entries', function () {
|
||||
/**
|
||||
* Plan step 46: an application's entry imports the stylesheets its own views render, and none of
|
||||
* them renders this layout, so with a build the page inlines its own layout's rules beside the
|
||||
* application's tags rather than hoping the entry imported `error-page.css` — SealShare's didn't,
|
||||
* and its 403/404/500 pages drew unstyled under its real build.
|
||||
*/
|
||||
it('loads the application\'s Vite entries and inlines the error layout\'s own rules beside them', function () {
|
||||
File::ensureDirectoryExists($this->temporary.'/build');
|
||||
File::put($this->temporary.'/build/manifest.json', json_encode([
|
||||
'workbench/resources/css/app.css' => ['file' => 'assets/app-probe.css', 'src' => 'workbench/resources/css/app.css', 'isEntry' => true],
|
||||
@@ -104,11 +111,27 @@ it('loads the application\'s Vite entries', function () {
|
||||
app()->usePublicPath($this->temporary);
|
||||
Vite::useHotFile($this->temporary.'/hot');
|
||||
|
||||
$this->get('/abort/404')
|
||||
$html = $this->get('/abort/404')
|
||||
->assertNotFound()
|
||||
->assertSee('build/assets/app-probe.css', false)
|
||||
->assertSee('build/assets/app-probe.js', false)
|
||||
->assertDontSee('data-md-error-fallback', false);
|
||||
->assertSee('<style data-md-error-styles>'.ErrorPage::layoutStyles().'</style>', false)
|
||||
->assertDontSee('data-md-error-fallback', false)
|
||||
->getContent();
|
||||
|
||||
preg_match('~<style data-md-error-styles>(.*?)</style>~s', $html, $inlined);
|
||||
$plain = (string) preg_replace('~/\*.*?\*/~s', '', $inlined[1] ?? '');
|
||||
|
||||
// After the application's stylesheet, so its layer statement has already set the order.
|
||||
expect(strpos($html, '<style data-md-error-styles>'))->toBeGreaterThan(strpos($html, 'build/assets/app-probe.css'))
|
||||
// The layout's rules, and those of the button and shape stylesheets it imports.
|
||||
->and($plain)->toContain('body:has(> [data-md-error-page])')
|
||||
->toContain('[data-md-error-shape] svg')
|
||||
->toContain('[data-md-button]')
|
||||
->toContain('[data-md-shape]')
|
||||
// Not the foundation or a scheme: the application's build brings those.
|
||||
->not->toContain('box-sizing: border-box')
|
||||
->not->toMatch('/--md-sys-color-surface\s*:/');
|
||||
});
|
||||
|
||||
it('still renders, in the application\'s scheme, when the Vite manifest is missing', function () {
|
||||
@@ -132,6 +155,20 @@ it('still renders, in the application\'s scheme, when the Vite manifest is missi
|
||||
->assertDontSee('/build/', false);
|
||||
});
|
||||
|
||||
it('keeps the fallback as the only stylesheet when the Vite manifest is missing', function () {
|
||||
app()->usePublicPath($this->temporary);
|
||||
Vite::useHotFile($this->temporary.'/hot');
|
||||
|
||||
$html = $this->get('/abort/404')
|
||||
->assertNotFound()
|
||||
->assertSee('<style data-md-error-fallback>'.ErrorPage::fallbackStyles().'</style>', false)
|
||||
->assertDontSee('data-md-error-styles', false)
|
||||
->getContent();
|
||||
|
||||
expect(substr_count($html, '<style'))->toBe(1)
|
||||
->and($html)->not->toContain('<link rel="stylesheet"');
|
||||
});
|
||||
|
||||
it('draws the standard, medium and high contrast levels when the Vite manifest is missing', function () {
|
||||
File::put($this->temporary.'/material-scheme.json', json_encode([
|
||||
'light' => ['surface' => '#fafaf0', 'primary' => '#123456'],
|
||||
@@ -349,3 +386,23 @@ it('bundles the foundation and the error layout into the fallback, without an im
|
||||
->toContain('box-sizing: border-box')
|
||||
->toContain('--md-sys-color-surface');
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan step 46: what the page inlines beside a build is `Stylesheets::bundle()` of the error layout
|
||||
* alone. It reaches no `@font-face` — the package's only one is tokens/font.css's, which only the
|
||||
* foundation imports, and the application's build serves it — and no `url()` a request from the
|
||||
* page would have to resolve, so nothing needs dropping from it the way the fallback drops the face.
|
||||
*/
|
||||
it('inlines beside a build the error layout\'s bundle, which holds no font face, url or import', function () {
|
||||
$layout = realpath(__DIR__.'/../../resources/css/components/error-page.css');
|
||||
$css = (string) ErrorPage::layoutStyles();
|
||||
$plain = (string) preg_replace('~/\*.*?\*/~s', '', $css);
|
||||
|
||||
expect($css)->toBe(Stylesheets::bundle([$layout]))
|
||||
->and(array_map(basename(...), Stylesheets::resolvedFiles([$layout])))
|
||||
->toEqualCanonicalizing(['error-page.css', 'button.css', 'icon.css', 'loading.css', 'tooltip.css', 'shape.css'])
|
||||
->and($plain)->not->toContain('@font-face')
|
||||
->not->toContain('@import')
|
||||
->not->toMatch('/\burl\(/i')
|
||||
->toContain('@layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility;');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user