An application lists named schemes in livewire-material.profiles, and material:scheme without a seed generates them all into one stylesheet: the default profile as the plain blocks, every profile under <html data-scheme>, with descendant selectors so a nested data-theme panel keeps the page's profile. The JSON keeps the 1.0 top level. Scheme::resolveProfileUsing() lets the application name the active profile; it is asked on every use and falls back to the default for an unknown name or a resolver that throws. The head script writes it before the first paint and keeps it through wire:navigate, and the mail theme and the error pages' fallback draw it. <x-scheme-picker> chooses one and previews it through $store.theme.previewScheme(); the showcase previews the Workbench's profiles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
61 lines
2.8 KiB
PHP
61 lines
2.8 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
beforeEach(function () {
|
|
$this->path = sys_get_temp_dir().'/scheme-picker-'.uniqid().'.json';
|
|
File::put($this->path, json_encode([
|
|
'default' => 'indigo',
|
|
'profiles' => [
|
|
'indigo' => ['label' => 'Indigo', 'light' => ['primary' => '#4f46e5', 'secondary' => '#5b5d72', 'tertiary' => '#77536d'], 'dark' => ['primary' => '#c0c1ff', 'secondary' => '#c4c5dd', 'tertiary' => '#e6bad7']],
|
|
'teal' => ['label' => 'Teal', 'light' => ['primary' => '#00897b', 'secondary' => '#4a635f', 'tertiary' => '#456179'], 'dark' => ['primary' => '#80cbc4', 'secondary' => '#b1ccc6', 'tertiary' => '#adcae5']],
|
|
],
|
|
]));
|
|
config(['livewire-material.scheme' => $this->path]);
|
|
});
|
|
|
|
afterEach(function () {
|
|
File::delete($this->path);
|
|
});
|
|
|
|
it('draws a radio per generated profile, bound, named and previewing on change', function () {
|
|
$html = (string) $this->blade('<x-scheme-picker label="Colour profile" hint="Applies after saving" wire:model="colorProfile" />');
|
|
|
|
expect($html)
|
|
->toContain('<legend class="mb-2 type-label-lg text-on-surface-variant">Colour profile</legend>')
|
|
->toContain('Applies after saving')
|
|
->toContain('data-scheme-option="indigo"')
|
|
->toContain('data-scheme-option="teal"')
|
|
->toMatch('/<input\s+wire:model="colorProfile"\s+type="radio"\s+name="colorProfile"\s+value="teal"/')
|
|
->toContain('x-on:change="$store.theme.previewScheme($event.target.value)"')
|
|
->toContain('>Teal</span>')
|
|
->toContain('style="--swatch-light: #00897b; --swatch-dark: #80cbc4"');
|
|
});
|
|
|
|
it('keeps its inline styles to the scheme file\'s checked colours', function () {
|
|
File::put($this->path, json_encode([
|
|
'default' => 'indigo',
|
|
'profiles' => ['indigo' => ['label' => 'Indigo', 'light' => ['primary' => 'red; background: url(x)'], 'dark' => []]],
|
|
]));
|
|
|
|
$html = (string) $this->blade('<x-scheme-picker wire:model="colorProfile" />');
|
|
|
|
preg_match_all('/style="([^"]*)"/', $html, $styles);
|
|
|
|
expect($styles[1])->not->toBeEmpty()
|
|
->each->toMatch('/^--swatch-light: #[0-9a-fA-F]{6}; --swatch-dark: #[0-9a-fA-F]{6}$/');
|
|
});
|
|
|
|
it('shows a validation message for the bound property instead of the hint', function () {
|
|
$this->withViewErrors(['colorProfile' => ['Choose one of the colour profiles.']])
|
|
->blade('<x-scheme-picker wire:model="colorProfile" hint="Applies after saving" />')
|
|
->assertSee('Choose one of the colour profiles.')
|
|
->assertDontSee('Applies after saving');
|
|
});
|
|
|
|
it('renders nothing for a single scheme', function () {
|
|
File::put($this->path, json_encode(['light' => ['primary' => '#123456'], 'dark' => []]));
|
|
|
|
expect(trim((string) $this->blade('<x-scheme-picker wire:model="colorProfile" />')))->toBe('');
|
|
});
|