Bundle the error page's fallback instead of hand-writing it
Plan step 40. ErrorPage::fallbackStyles() now inlines Stylesheets::bundle() of the foundation and error-page.css (which imports button.css and shape.css) rather than a hand-copied stylesheet, so the fallback can never drift from the built version. Every @font-face block is dropped structurally (withoutFontFace(), brace-balanced, not a text search) since there is no build to serve the font file; --md-ref-typeface-brand already lists ui-sans-serif, system-ui and sans-serif after the brand name, so the page still gets a sensible system stack. The scheme half comes from Scheme::forStylesheet() drawn through SchemeStylesheet::levels(), in material-scheme.css's own selector shape: standard, the medium and high contrast levels, and a block per colour profile keyed on [data-scheme] (the theme script has already resolved and written the active one to <html> before this stylesheet is read, so nothing here picks one in PHP). Both halves are cached per worker, the scheme half by the scheme file's path and mtime. Tests: both render paths, the inlined CSS's shape (no @import, no relative url(), no @font-face, the button/shape/error-page rules, the scheme's roles, [data-contrast='high'] and a profile block), and a new browser test that forces the fallback and checks the button, the shape and a dark-mode repaint. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3b9a889e7c
commit
e1fe573182
@@ -132,7 +132,38 @@ it('still renders, in the application\'s scheme, when the Vite manifest is missi
|
||||
->assertDontSee('/build/', false);
|
||||
});
|
||||
|
||||
it('draws the active colour profile when the Vite manifest is missing', function () {
|
||||
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'],
|
||||
'dark' => ['surface' => '#101010'],
|
||||
'contrast' => [
|
||||
'standard' => 0,
|
||||
'medium' => ['light' => ['primary' => '#334455']],
|
||||
'high' => ['light' => ['primary' => '#000000'], 'dark' => ['primary' => '#ffffff']],
|
||||
],
|
||||
]));
|
||||
|
||||
config(['livewire-material.scheme' => $this->temporary.'/material-scheme.json']);
|
||||
app()->usePublicPath($this->temporary);
|
||||
Vite::useHotFile($this->temporary.'/hot');
|
||||
|
||||
$this->get('/abort/500')
|
||||
->assertStatus(500)
|
||||
->assertSee("[data-contrast='medium']", false)
|
||||
->assertSee("[data-contrast='high']", false)
|
||||
->assertSee('--md-sys-color-primary: #334455;', false)
|
||||
->assertSee('--md-sys-color-primary: #000000;', false)
|
||||
->assertSee('--md-sys-color-primary: #ffffff;', false);
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan step 40: the fallback no longer resolves the active profile in PHP — `<x-theme-script>`
|
||||
* has already embedded it (`Scheme::profile()`, the resolver included) and writes it to
|
||||
* `<html data-scheme>` before this stylesheet is read — so the fallback draws every profile, in
|
||||
* `material-scheme.css`'s own `[data-scheme]` shape, and lets the attribute already on the page
|
||||
* pick between them.
|
||||
*/
|
||||
it('draws every colour profile when the Vite manifest is missing, keyed on data-scheme', function () {
|
||||
File::put($this->temporary.'/material-scheme.json', json_encode([
|
||||
'light' => ['primary' => '#4f46e5'],
|
||||
'dark' => ['primary' => '#aaaaff'],
|
||||
@@ -151,9 +182,13 @@ it('draws the active colour profile when the Vite manifest is missing', function
|
||||
try {
|
||||
$this->get('/abort/500')
|
||||
->assertStatus(500)
|
||||
->assertSee("[data-scheme='indigo']", false)
|
||||
->assertSee("[data-scheme='teal']", false)
|
||||
->assertSee('--md-sys-color-primary: #4f46e5;', false)
|
||||
->assertSee('--md-sys-color-primary: #00897b;', false)
|
||||
->assertSee('--md-sys-color-primary: #80cbc4;', false)
|
||||
->assertDontSee('#4f46e5', false);
|
||||
// The theme script resolved and embedded the active profile server-side; the CSS above
|
||||
// only has to let [data-scheme] (which it sets on <html>) pick between the two blocks.
|
||||
->assertSee('"scheme":"teal"', false);
|
||||
} finally {
|
||||
Scheme::resolveProfileUsing(null);
|
||||
}
|
||||
@@ -285,5 +320,32 @@ it('styles the body only when it holds the error layout, and turns the shape on
|
||||
->and(ComponentStylesheet::read('error-page')->declarations('body:has(> [data-md-error-page])'))->not->toHaveKey('font-family')
|
||||
->and((string) ErrorPage::fallbackStyles())
|
||||
->toContain('@keyframes material-error-turn')
|
||||
->toContain('@media (prefers-reduced-motion: no-preference) { [data-md-error-shape] { animation: material-error-turn 60s linear infinite; } }');
|
||||
->toContain('animation: material-error-turn 60s linear infinite;');
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan step 40: the fallback is `Stylesheets::bundle()` of the foundation and the error layout
|
||||
* (which pulls in button.css and shape.css) rather than a hand-built stylesheet, so it carries
|
||||
* their rules verbatim, with no `@import` (bundled away), no relative `url()` (the only one in the
|
||||
* bundle, `tokens/font.css`'s, leaves with the `@font-face` block it lives in) and no `@font-face`
|
||||
* itself — the fallback has no build to serve the font file from.
|
||||
*/
|
||||
it('bundles the foundation and the error layout into the fallback, without an import, a relative url, or a font face', function () {
|
||||
withoutSkeletonErrorViews();
|
||||
|
||||
$this->withoutVite();
|
||||
$css = (string) ErrorPage::fallbackStyles();
|
||||
$plain = (string) preg_replace('~/\*.*?\*/~s', '', $css);
|
||||
|
||||
expect($plain)->not->toContain('@import')
|
||||
->not->toContain('@font-face')
|
||||
->and(preg_match('/url\(\s*(?!["\']?(?:data:|https?:|\/))/i', $plain))->toBe(0, 'a relative url() remains in the fallback')
|
||||
// The error layout's own rules, and the button and shape stylesheets it imports.
|
||||
->and($plain)->toContain('[data-md-error-page]')
|
||||
->toContain('[data-md-error-shape]')
|
||||
->toContain('[data-md-button]')
|
||||
->toContain('[data-md-shape]')
|
||||
// The foundation's reset and tokens travelled in too, not only the error layout.
|
||||
->toContain('box-sizing: border-box')
|
||||
->toContain('--md-sys-color-surface');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user