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] .bg-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 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-scheme-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('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'
This is page {{ $page }}.
Next @livewireScripts 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(); });