stylesheet = sys_get_temp_dir().'/material-scheme-'.Str::random(8).'.css'; $this->data = Str::replaceLast('.css', '.json', $this->stylesheet); }); afterEach(function () { File::delete([$this->stylesheet, $this->data]); }); it('writes the scheme as a stylesheet and as data', function () { $this->artisan('material:scheme', ['seed' => '#4F46E5', '--output' => $this->stylesheet]) ->assertSuccessful(); $scheme = json_decode(File::get($this->data), true); $stylesheet = File::get($this->stylesheet); expect($scheme) ->seed->toBe('#4f46e5') ->variant->toBe('tonal-spot') ->spec->toBe('2025') ->and(array_keys($scheme['dark']))->toEqual(array_keys($scheme['light'])) ->and($scheme['light'])->toHaveKeys(['primary', 'on-primary', 'tertiary-container', 'surface-container-high', 'outline-variant', 'success', 'on-warning-container', 'info-container']) ->and($scheme['light']['surface'])->not->toBe($scheme['dark']['surface']) ->and($scheme['light']['inverse-success'])->toBe($scheme['dark']['success']); foreach (['light' => "[data-theme='light']", 'dark' => "[data-theme='dark']"] as $theme => $selector) { $block = Str::of($stylesheet)->after($selector)->before('}')->toString(); expect($block)->toContain("color-scheme: {$theme};"); foreach ($scheme[$theme] as $role => $hex) { expect($block)->toContain("--md-sys-color-{$role}: {$hex};"); } } expect($stylesheet)->toContain('php artisan material:scheme "#4f46e5" --variant=tonal-spot'); }); it('generates a different scheme per variant', function () { $this->artisan('material:scheme', ['seed' => '#4f46e5', '--variant' => 'vibrant', '--output' => $this->stylesheet]) ->assertSuccessful(); $vibrant = json_decode(File::get($this->data), true); $this->artisan('material:scheme', ['seed' => '#4f46e5', '--output' => $this->stylesheet]) ->assertSuccessful(); $tonalSpot = json_decode(File::get($this->data), true); expect($vibrant['variant'])->toBe('vibrant') ->and($vibrant['light']['primary'])->not->toBe($tonalSpot['light']['primary']); }); it('generates with the 2021 colour spec when asked, and records it', function () { $this->artisan('material:scheme', [ 'seed' => '#00bc7d', '--variant' => 'vibrant', '--success' => '#00d390', '--warning' => '#fcb700', '--info' => '#00bafe', '--spec' => '2021', '--output' => $this->stylesheet, ])->assertSuccessful(); $scheme = json_decode(File::get($this->data), true); expect($scheme['spec'])->toBe('2021') ->and($scheme['dark'])->toMatchArray([ 'surface' => '#0b1610', 'primary' => '#00e297', 'on-primary' => '#003822', 'success' => '#2ce19c', 'warning' => '#ffbb16', 'info' => '#80cfff', ]) ->and($scheme['light'])->toMatchArray([ 'surface' => '#f0fdf2', 'primary' => '#006c46', 'success' => '#006c48', 'warning' => '#7c5800', 'info' => '#00658c', ]) ->and(File::get($this->stylesheet)) ->toContain('(spec 2021)') ->toContain('php artisan material:scheme "#00bc7d" --variant=vibrant --spec=2021 --success="#00d390" --warning="#fcb700" --info="#00bafe"'."\n"); }); it('refuses an unknown colour spec', function () { $this->artisan('material:scheme', ['seed' => '#4f46e5', '--spec' => '2023', '--output' => $this->stylesheet]) ->expectsOutputToContain('Unknown spec "2023". Use one of: 2021, 2025.') ->assertFailed(); expect(File::exists($this->stylesheet))->toBeFalse(); }); it('refuses a seed that is not a colour', function () { $this->artisan('material:scheme', ['seed' => 'indigo', '--output' => $this->stylesheet]) ->expectsOutputToContain('#rrggbb') ->assertFailed(); expect(File::exists($this->stylesheet))->toBeFalse(); }); it('refuses an unknown variant', function () { $this->artisan('material:scheme', ['seed' => '#4f46e5', '--variant' => 'loud', '--output' => $this->stylesheet]) ->expectsOutputToContain('Unknown variant "loud"') ->assertFailed(); }); it('fails without writing anything when node cannot run', function () { config(['livewire-material.node' => '/nonexistent/node']); $this->artisan('material:scheme', ['seed' => '#4f46e5', '--output' => $this->stylesheet]) ->assertFailed(); expect(File::exists($this->stylesheet))->toBeFalse(); }); it('generates every configured profile into one stylesheet keyed by data-scheme', function () { config([ 'livewire-material.profiles' => [ 'indigo' => ['label' => 'Indigo', 'seed' => '#4f46e5', 'variant' => 'vibrant'], 'teal' => ['label' => 'Teal', 'seed' => '#00897b', 'variant' => 'vibrant'], 'graphite' => ['seed' => '#5f6368', 'variant' => 'neutral'], ], 'livewire-material.profile' => 'teal', ]); $this->artisan('material:scheme', ['--output' => $this->stylesheet])->assertSuccessful(); $scheme = json_decode(File::get($this->data), true); $stylesheet = File::get($this->stylesheet); expect($scheme['default'])->toBe('teal') ->and(array_keys($scheme['profiles']))->toBe(['indigo', 'teal', 'graphite']) ->and($scheme['profiles']['graphite'])->label->toBe('Graphite')->variant->toBe('neutral') ->and($scheme['profiles']['indigo']['light']['primary'])->not->toBe($scheme['profiles']['teal']['light']['primary']) // The top level is the default profile, as a 1.0 reader expects. ->and($scheme['light'])->toBe($scheme['profiles']['teal']['light']) ->and($scheme['dark'])->toBe($scheme['profiles']['teal']['dark']) ->and($scheme['seed'])->toBe('#00897b') ->and($stylesheet)->toContain('php artisan material:scheme'."\n"); // The default's plain blocks come first, so a profile's single-attribute selector follows :root. expect(strpos($stylesheet, ":root,\n[data-theme='light'] {"))->toBeLessThan(strpos($stylesheet, "[data-scheme='indigo'],")); foreach ($scheme['profiles'] as $name => $profile) { foreach (['light' => "[data-scheme='{$name}'] [data-theme='light'] {", 'dark' => "[data-scheme='{$name}'] [data-theme='dark'] {"] as $theme => $selector) { $block = Str::of($stylesheet)->after($selector)->before('}')->toString(); expect($block)->toContain("color-scheme: {$theme};") ->toContain("--md-sys-color-primary: {$profile[$theme]['primary']};") ->toContain("--md-sys-color-surface: {$profile[$theme]['surface']};"); } } }); it('takes a profile\'s own spec and state colours, and the command\'s for a profile without them', function () { config([ 'livewire-material.profiles' => [ 'expressive' => ['seed' => '#00bc7d', 'variant' => 'vibrant'], 'classic' => ['seed' => '#00bc7d', 'variant' => 'vibrant', 'spec' => 2021, 'success' => '#00d390', 'warning' => '#fcb700', 'info' => '#00bafe'], ], ]); $this->artisan('material:scheme', ['--info' => '#00bafe', '--output' => $this->stylesheet])->assertSuccessful(); $profiles = json_decode(File::get($this->data), true)['profiles']; expect($profiles['classic'])->spec->toBe('2021') ->and($profiles['classic']['dark'])->toMatchArray(['primary' => '#00e297', 'success' => '#2ce19c', 'warning' => '#ffbb16', 'info' => '#80cfff']) ->and($profiles['expressive'])->spec->toBe('2025') ->and($profiles['expressive']['dark']['primary'])->not->toBe('#00e297') ->and($profiles['expressive']['dark']['success'])->not->toBe('#2ce19c') ->and($profiles['expressive']['dark']['info'])->toBe('#80cfff') ->and(File::get($this->stylesheet)) ->toContain('(spec 2025)') ->toMatch('/ \* classic\s+#00bc7d, vibrant, spec 2021\n/') ->toMatch('/ \* expressive\s+#00bc7d, vibrant\n/'); }); it('names the profile whose spec is unknown', function () { config(['livewire-material.profiles' => ['indigo' => ['seed' => '#4f46e5', 'spec' => '2019']]]); $this->artisan('material:scheme', ['--output' => $this->stylesheet]) ->expectsOutputToContain('Profile "indigo": Unknown spec "2019"') ->assertFailed(); }); it('names the profile a generator error belongs to', function () { config(['livewire-material.profiles' => ['indigo' => ['seed' => '#4f46e5'], 'broken' => ['seed' => 'teal']]]); $this->artisan('material:scheme', ['--output' => $this->stylesheet]) ->expectsOutputToContain('Profile "broken"') ->assertFailed(); expect(File::exists($this->stylesheet))->toBeFalse(); }); it('asks for a seed or profiles when it has neither', function () { config(['livewire-material.profiles' => []]); $this->artisan('material:scheme', ['--output' => $this->stylesheet]) ->expectsOutputToContain('livewire-material.profiles') ->assertFailed(); }); it('refuses a profile name that cannot be an attribute value', function () { config(['livewire-material.profiles' => ['Ocean Blue' => ['seed' => '#0b57d0']]]); $this->artisan('material:scheme', ['--output' => $this->stylesheet]) ->expectsOutputToContain('"Ocean Blue" is not') ->assertFailed(); });