$primaries profile => light primary */ function writeProfiles(string $path, array $primaries, string $default): void { $profiles = collect($primaries)->map(fn (string $primary, string $name): array => [ 'label' => Str::headline($name), 'seed' => $primary, 'variant' => 'vibrant', 'spec' => '2025', 'contrast' => 0, 'light' => ['primary' => $primary, 'surface' => '#fafafa'], 'dark' => ['primary' => '#eeeeee', 'surface' => '#111111'], ])->all(); File::put($path, json_encode([...collect($profiles[$default])->except('label')->all(), 'default' => $default, 'profiles' => $profiles])); } beforeEach(function () { $this->path = sys_get_temp_dir().'/material-profiles-'.Str::random(8).'.json'; writeProfiles($this->path, ['indigo' => '#4f46e5', 'teal' => '#00897b', 'rose' => '#c2185b'], 'indigo'); }); afterEach(function () { Scheme::resolveProfileUsing(null); File::delete($this->path); }); it('lists the generated profiles with their labels and roles, filling missing roles from the default', function () { $profiles = Scheme::profiles($this->path); expect(array_keys($profiles))->toBe(['indigo', 'teal', 'rose']) ->and($profiles['teal']['label'])->toBe('Teal') ->and($profiles['teal']['light']['primary'])->toBe('#00897b') ->and($profiles['teal']['light'])->toHaveKey('on-surface-variant') ->and(Scheme::profiles(sys_get_temp_dir().'/missing-scheme.json'))->toBe([]); }); it('follows the resolver to the active profile, and falls back to the default', function () { expect(Scheme::profile($this->path))->toBe('indigo'); Scheme::resolveProfileUsing(fn (): string => 'teal'); expect(Scheme::profile($this->path))->toBe('teal') ->and(Scheme::light($this->path)['primary'])->toBe('#00897b') ->and(Scheme::load($this->path)['dark']['surface'])->toBe('#111111'); Scheme::resolveProfileUsing(fn (): string => 'ocean'); expect(Scheme::profile($this->path))->toBe('indigo'); Scheme::resolveProfileUsing(fn (): ?string => null); expect(Scheme::profile($this->path))->toBe('indigo'); Scheme::resolveProfileUsing(fn () => throw new RuntimeException('The database is down.')); expect(Scheme::profile($this->path))->toBe('indigo'); }); it('loads a profile by name, whatever is active', function () { Scheme::resolveProfileUsing(fn (): string => 'teal'); expect(Scheme::light($this->path, 'rose')['primary'])->toBe('#c2185b') ->and(Scheme::light($this->path, 'ocean')['primary'])->toBe('#00897b'); }); it('has no profile for a single scheme, and loads it as before', function () { File::put($this->path, json_encode(['light' => ['primary' => '#123456'], 'dark' => ['primary' => '#abcdef']])); Scheme::resolveProfileUsing(fn (): string => 'teal'); expect(Scheme::profile($this->path))->toBeNull() ->and(Scheme::profiles($this->path))->toBe([]) ->and(Scheme::light($this->path)['primary'])->toBe('#123456') ->and(Scheme::light($this->path))->toHaveKey('on-surface'); });