The error page's shape turns once a minute (material-error-turn), and getBoundingClientRect() returns the axis-aligned box of what is drawn, which is wider than the 240px art box as soon as the turn has begun. The browser test compared that rect with 240: Chrome sampled it before the first frame of the animation (currentTime 0) and passed, while Firefox (33ms, 240.83px) and Safari (4ms, 240.20px) had already turned a fraction of a degree and failed. The layout itself was right in all three: the SVG's computed width is 240px. The test now reads the SVG's computed width, which the rotation does not touch. It still guards the regression it was written for: with the inlined layout rules removed, the SVG computes to 1280px in every engine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
155 lines
8.0 KiB
PHP
155 lines
8.0 KiB
PHP
<?php
|
|
|
|
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.
|
|
*/
|
|
function missingPage(string $colorScheme = 'light')
|
|
{
|
|
$page = visit('/nothing-lives-here');
|
|
$page = $colorScheme === 'dark' ? $page->inDarkMode() : $page->inLightMode();
|
|
|
|
return $page->waitForEvent('networkidle')
|
|
->assertScript("document.readyState === 'complete' && document.querySelector('[data-md-error-page]') !== null");
|
|
}
|
|
|
|
/**
|
|
* Whether the element's computed background is the colour a role resolves to on this page.
|
|
*/
|
|
function paintedIn(string $selector, string $role): string
|
|
{
|
|
return <<<JS
|
|
(() => {
|
|
const probe = document.createElement('div');
|
|
probe.style.backgroundColor = 'var(--md-sys-color-{$role})';
|
|
document.body.append(probe);
|
|
const expected = getComputedStyle(probe).backgroundColor;
|
|
probe.remove();
|
|
|
|
return expected !== 'rgba(0, 0, 0, 0)' && getComputedStyle(document.querySelector('{$selector}')).backgroundColor === expected;
|
|
})()
|
|
JS;
|
|
}
|
|
|
|
it('draws the 404 page in the scheme, in light and dark', function () {
|
|
missingPage('light')
|
|
->assertSee('Page not found')
|
|
->assertVisible('[data-md-error-headline]')
|
|
->assertScript("document.documentElement.getAttribute('data-theme') === 'light'")
|
|
->assertScript(paintedIn('body', 'surface'))
|
|
->assertScript("getComputedStyle(document.querySelector('[data-md-error-shape] svg')).color !== getComputedStyle(document.body).color")
|
|
->assertNoJavaScriptErrors();
|
|
|
|
missingPage('dark')
|
|
->assertScript("document.documentElement.getAttribute('data-theme') === 'dark'")
|
|
->assertScript(paintedIn('body', 'surface'))
|
|
->assertScript("getComputedStyle(document.body).backgroundColor !== 'rgb(253, 247, 254)'");
|
|
});
|
|
|
|
it('fits a 400px screen without scrolling sideways', function () {
|
|
missingPage()
|
|
->resize(400, 800)
|
|
->assertVisible('[data-md-error-headline]')
|
|
->assertScript('document.documentElement.scrollWidth <= window.innerWidth')
|
|
->assertScript("document.querySelector('[data-md-error-actions] a').getBoundingClientRect().right <= 400");
|
|
});
|
|
|
|
/**
|
|
* Plan step 40: the fallback path, the same server serving the request `LaravelHttpServer` runs
|
|
* in-process, so a config change here reaches it exactly as it reaches a Feature test's client —
|
|
* an empty temporary directory has no `build/manifest.json` and no `hot` file, so `ErrorPage::assets()`
|
|
* throws and returns null (tests/Feature/ErrorPagesTest.php does the same for the HTTP client).
|
|
*/
|
|
it('draws the button and the shape from the inlined fallback stylesheet, and repaints in dark mode', function () {
|
|
$public = sys_get_temp_dir().'/livewire-material-fallback-'.Str::random(8);
|
|
File::ensureDirectoryExists($public);
|
|
|
|
$originalPublicPath = app()->publicPath();
|
|
app()->usePublicPath($public);
|
|
Vite::useHotFile($public.'/hot');
|
|
|
|
try {
|
|
missingPage('light')
|
|
->assertScript("document.querySelector('style[data-md-error-fallback]') !== null")
|
|
->assertScript("document.querySelector('link[href*=\"/build/\"]') === null")
|
|
// The button: button.css's round, filled shape, not a bare unstyled <a>.
|
|
->assertScript("getComputedStyle(document.querySelector('[data-md-button]')).borderRadius !== '0px'")
|
|
->assertScript(paintedIn('[data-md-button][data-md-variant=filled]', 'primary'))
|
|
// The shape: shape.css's sizing and the primary-container tint error-page.css paints its svg with.
|
|
->assertScript("getComputedStyle(document.querySelector('[data-md-error-shape] svg')).color !== getComputedStyle(document.body).color")
|
|
->assertScript(paintedIn('body', 'surface'))
|
|
->assertNoJavaScriptErrors();
|
|
|
|
// The [data-theme='dark'] block the fallback also inlines repaints the page, exactly as the
|
|
// built stylesheet does for the test above.
|
|
missingPage('dark')
|
|
->assertScript("document.documentElement.getAttribute('data-theme') === 'dark'")
|
|
->assertScript(paintedIn('body', 'surface'))
|
|
->assertScript("getComputedStyle(document.body).backgroundColor !== 'rgb(253, 247, 254)'");
|
|
} finally {
|
|
app()->usePublicPath($originalPublicPath);
|
|
Vite::useHotFile($originalPublicPath.'/hot');
|
|
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")
|
|
// The shape's laid-out width, not its bounding rect: the shape turns once a minute, and a
|
|
// rotated box's axis-aligned rect is wider than the box as soon as the turn has begun.
|
|
->assertScript("parseFloat(getComputedStyle(document.querySelector('[data-md-error-shape] svg')).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);
|
|
}
|
|
});
|