Files
livewire-material/tests/Browser/ThemeTest.php
T
Andreas Reinhold / reiniandClaude Sonnet 5 d15312023b Move browser-test probes off Tailwind utility classes
Plan step 38 (last batch), plan step 42's target for tests/: every
Tailwind utility class in a tests/Browser/*.php probe's Blade string
or Livewire component render() (~60 class attributes across 14 files)
becomes an inline style built from --md-sys-color-*/--md-sys-shape-*/
--md-sys-measurement-* tokens or a literal px value for an arbitrary
demo size — PickingTest's clip box, every probe's <body
class="bg-surface">, the grid/stack/row wrapper divs, the carousel
item's coloured filler. No test's assertions, selectors or expected
text change; these are layout containers around the components under
test, not anything a test reads.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-15 05:34:20 +02:00

203 lines
8.5 KiB
PHP

<?php
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Route;
use NoNameWeb\LivewireMaterial\Support\Scheme;
use function Orchestra\Testbench\workbench_path;
/**
* The theme <html> shows. A bare `html` selector would be read as text to search for.
*/
function theme(string $attribute, string $value): string
{
return "document.documentElement.getAttribute('{$attribute}') === '{$value}'";
}
it('follows the operating system while the visitor has not chosen', function () {
visit('/material')->inDarkMode()
->assertScript(theme('data-theme', 'dark'))
->assertScript(theme('data-theme-choice', 'system'))
->assertNoJavaScriptErrors();
visit('/material')->inLightMode()
->assertScript(theme('data-theme', 'light'));
});
it('keeps the visitor\'s choice over the operating system', function () {
// The theme switch waits for Alpine (x-cloak); a press before then would find nothing to press.
$page = visit('/material')->inDarkMode()
->waitForEvent('networkidle')
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'")
->assertScript(theme('data-theme', 'dark'));
$page->click('label:has(input[name="material-theme"][value="light"])')
->assertScript(theme('data-theme', 'light'))
->assertScript('document.querySelector(\'input[name="material-theme"][value="light"]\').checked')
->assertScript("localStorage.getItem('material-theme') === 'light'");
$page->refresh()
->assertScript(theme('data-theme', 'light'))
->assertScript(theme('data-theme-choice', 'light'));
});
it('adopts an earlier toggle\'s choice once', function () {
config(['livewire-material.theme.legacy_keys' => ['mary-theme']]);
$page = visit('/material')->inLightMode()
->assertScript(theme('data-theme', 'light'));
$page->script("localStorage.setItem('mary-theme', JSON.stringify('dark'))");
$page->refresh()
->assertScript(theme('data-theme', 'dark'))
->assertScript("localStorage.getItem('mary-theme') === null")
->assertScript("localStorage.getItem('material-theme') === 'dark'");
});
it('repaints a section that sets its own theme', function () {
$swatch = fn (int $index): string => "getComputedStyle(document.querySelectorAll('#colour [data-theme] [data-md-showcase-swatch=\"primary\"]')[{$index}]).backgroundColor";
visit('/material/colour')->inLightMode()
->assertScript(theme('data-theme', 'light'))
->assertScript("{$swatch(0)} !== {$swatch(1)}");
});
/**
* The page has exactly one theme-color meta without a media query, and it shows the given colour.
*/
function themeColorIs(string $hex): string
{
return "(() => { const metas = document.head.querySelectorAll('meta[name=theme-color]:not([media])'); return metas.length === 1 && metas[0].getAttribute('content') === '{$hex}'; })()";
}
/**
* The surface colour role, as <html> resolves it.
*/
function pageSurfaceIs(string $hex): string
{
return "getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-surface').trim() === '{$hex}'";
}
function themeReady(mixed $page): mixed
{
return $page->waitForEvent('networkidle')
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
}
it('adds a theme-color meta in the painted surface, and follows the theme and the colour profile', function () {
config([
'livewire-material.theme.meta' => true,
'livewire-material.scheme' => workbench_path('resources/css/material-scheme.json'),
]);
$profiles = Scheme::profiles();
$page = themeReady(visit('/material/colour')->inLightMode())
->assertScript(theme('data-scheme', 'baseline'))
->assertScript(themeColorIs($profiles['baseline']['light']['surface']))
->assertScript(pageSurfaceIs($profiles['baseline']['light']['surface']));
$page->click('label:has(input[name="material-theme"][value="dark"])')
->assertScript(theme('data-theme', 'dark'))
->assertScript(themeColorIs($profiles['baseline']['dark']['surface']))
->assertScript(pageSurfaceIs($profiles['baseline']['dark']['surface']));
$page->click('#colour [data-md-scheme-picker-option="rose"]')
->assertScript(theme('data-scheme', 'rose'))
->assertScript(themeColorIs($profiles['rose']['dark']['surface']))
->assertScript(pageSurfaceIs($profiles['rose']['dark']['surface']));
$page->click('label:has(input[name="material-theme"][value="light"])')
->assertScript(themeColorIs($profiles['rose']['light']['surface']))
->assertScript(pageSurfaceIs($profiles['rose']['light']['surface']))
->assertNoJavaScriptErrors();
});
it('picks the high contrast level when the operating system prefers it, until the visitor chooses', function () {
visit('/material', ['contrast' => 'more'])
->assertScript(theme('data-contrast', 'high'))
->assertNoJavaScriptErrors();
// Standard is the absence of the attribute (theme-script.blade.php): the stylesheet's plain
// blocks already are that level.
visit('/material', ['contrast' => 'no-preference'])
->assertScript("document.documentElement.hasAttribute('data-contrast') === false");
});
it('repaints the contrast level at once, and it survives a wire:navigate', function () {
Route::middleware('web')->get('/contrast-probe/{page}', fn (string $page) => Blade::render(<<<'BLADE'
<!DOCTYPE html>
<html>
<head>
<x-theme-script />
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
</head>
<body>
<x-theme-toggle mode="contrast" />
<p id="page">This is page {{ $page }}.</p>
<a id="next" href="/contrast-probe/two" wire:navigate>Next</a>
@livewireScripts
</body>
</html>
BLADE, ['page' => $page]));
// on-surface-variant is one of the roles a contrast level moves most (showcase/sections/colour.blade.php).
$ink = "getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-on-surface-variant').trim()";
$page = themeReady(visit('/contrast-probe/one'));
$standard = $page->script($ink);
$page->click('label:has(input[name="material-contrast"][value="high"])')
->assertScript(theme('data-contrast', 'high'))
->assertScript("{$ink} !== '{$standard}'")
->assertScript("localStorage.getItem('material-contrast') === 'high'");
$page->click('#next')
->assertSeeIn('#page', 'This is page two.')
->assertScript(theme('data-contrast', 'high'))
->assertScript("{$ink} !== '{$standard}'")
->assertNoJavaScriptErrors();
});
it('repaints the page\'s own theme-color meta, and the next page\'s after wire:navigate', function () {
config(['livewire-material.theme.meta' => true]);
Route::middleware('web')->get('/theme-color-probe/{page}', fn (string $page) => Blade::render(<<<'BLADE'
<!DOCTYPE html>
<html>
<head>
<meta name="theme-color" content="#000000" />
<meta name="theme-color" media="print" content="#ffffff" />
<x-theme-script />
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
</head>
<body style="background-color: var(--md-sys-color-surface);">
<p id="page">This is page {{ $page }}.</p>
<a id="next" href="/theme-color-probe/two" wire:navigate>Next</a>
@livewireScripts
</body>
</html>
BLADE, ['page' => $page]));
$scheme = Scheme::load();
$page = themeReady(visit('/theme-color-probe/one')->inLightMode())
->assertScript(themeColorIs($scheme['light']['surface']))
->assertScript("document.head.querySelector('meta[media]').getAttribute('content') === '#ffffff'");
$page->script("window.eval(\"Alpine.store('theme').set('dark'); window.samePage = true\")");
$page->assertScript(themeColorIs($scheme['dark']['surface']));
$page->click('#next')
->assertSeeIn('#page', 'This is page two.')
->assertScript("window.eval('window.samePage') === true")
->assertScript(theme('data-theme', 'dark'))
->assertScript(themeColorIs($scheme['dark']['surface']))
->assertScript("document.head.querySelector('meta[media]').getAttribute('content') === '#ffffff'")
->assertNoJavaScriptErrors();
});