themesPath = storage_path('framework/testing/stylist-themes-'.uniqid()); mkdir($this->themesPath, 0777, true); config(['stylist.themes_path' => $this->themesPath]); app(Loader::class)->flush(); } protected function tearDown(): void { app(Filesystem::class)->deleteDirectory($this->themesPath); parent::tearDown(); } public function test_manifest_is_normalised(): void { $path = $this->themesPath.'/sample'; mkdir($path.'/css', 0777, true); file_put_contents($path.'/theme.json', json_encode(['name' => 'Sample'])); file_put_contents($path.'/css/theme.css', 'body{}'); $theme = app(Loader::class)->fromPath($path); $this->assertSame('sample', $theme->key()); $this->assertSame('css/theme.css', $theme->stylesheet()); $this->assertSame('js/app.js', $theme->script()); $this->assertSame('home', $theme->entryView()); } public function test_unsafe_manifest_path_is_rejected(): void { $path = $this->themesPath.'/unsafe'; mkdir($path, 0777, true); file_put_contents($path.'/theme.json', json_encode([ 'name' => 'Unsafe', 'stylesheet' => '../secret.css', ])); $this->expectException(InvalidThemeManifestException::class); app(Loader::class)->fromPath($path); } public function test_phase_one_manifest_optional_capabilities_are_normalised(): void { $path = $this->themesPath.'/design-gallery'; mkdir($path.'/css', 0777, true); mkdir($path.'/js', 0777, true); file_put_contents($path.'/theme.json', json_encode([ 'key' => 'design-gallery', 'name' => 'Design Gallery', 'stylesheet' => 'css/app.css', 'script' => 'js/app.js', 'appearances' => ['light', 'dark', 'system'], 'accents' => ['blue', 'emerald', 'neutral'], 'default_appearance' => 'system', 'default_accent' => 'neutral', // Phase 1 incorrectly duplicated accents here; Stylist must not // reject the whole theme because of that compact legacy list. 'variants' => ['blue', 'emerald', 'neutral'], ])); file_put_contents($path.'/css/app.css', 'body{}'); file_put_contents($path.'/js/app.js', ''); $theme = app(Loader::class)->fromPath($path); $this->assertSame(['light', 'dark', 'system'], array_keys($theme->appearances())); $this->assertSame(['blue', 'emerald', 'neutral'], array_keys($theme->accents())); $this->assertSame([], $theme->variants()); $this->assertSame('system', $theme->defaultAppearance()); $this->assertSame('neutral', $theme->defaultAccent()); } public function test_legacy_manifest_does_not_gain_appearance_or_accent_capabilities(): void { $path = $this->themesPath.'/legacy'; mkdir($path.'/css', 0777, true); file_put_contents($path.'/theme.json', json_encode(['name' => 'Legacy'])); file_put_contents($path.'/css/theme.css', 'body{}'); $theme = app(Loader::class)->fromPath($path); $this->assertSame([], $theme->appearances()); $this->assertSame([], $theme->accents()); $this->assertSame('', $theme->defaultAppearance()); $this->assertSame('', $theme->defaultAccent()); } public function test_defaults_must_reference_declared_optional_capabilities(): void { $path = $this->themesPath.'/invalid-default'; mkdir($path, 0777, true); file_put_contents($path.'/theme.json', json_encode([ 'name' => 'Invalid Default', 'appearances' => ['light', 'dark'], 'default_appearance' => 'system', ])); $this->expectException(InvalidThemeManifestException::class); app(Loader::class)->fromPath($path); } public function test_manifest_version_and_capabilities_are_normalised(): void { $path = $this->themesPath.'/capable'; mkdir($path.'/css', 0777, true); mkdir($path.'/js', 0777, true); mkdir($path.'/views', 0777, true); file_put_contents($path.'/theme.json', json_encode([ 'manifest_version' => 1, 'name' => 'Capable', 'appearances' => ['light', 'dark'], 'accents' => ['blue'], 'capabilities' => ['marketplace' => true], ])); file_put_contents($path.'/css/theme.css', 'body{}'); file_put_contents($path.'/js/app.js', ''); file_put_contents($path.'/views/home.blade.php', '
Capable
'); $theme = app(Loader::class)->fromPath($path); $this->assertSame(1, $theme->manifestVersion()); $this->assertTrue($theme->hasCapability('appearances')); $this->assertTrue($theme->hasCapability('accents')); $this->assertTrue($theme->hasCapability('marketplace')); $this->assertFalse($theme->hasCapability('preview')); } public function test_unknown_manifest_fields_are_preserved_without_preventing_loading(): void { $path = $this->themesPath.'/future'; mkdir($path.'/css', 0777, true); file_put_contents($path.'/theme.json', json_encode([ 'name' => 'Future', 'future_builder_metadata' => ['anything' => true], ])); file_put_contents($path.'/css/theme.css', 'body{}'); $theme = app(Loader::class)->fromPath($path); $this->assertSame(['anything' => true], $theme->meta()['future_builder_metadata']); } }