Files
Mark Gregory 0a74f5b44e
tests / ci (push) Canceled after 0s
initial commmit
2026-09-30 14:24:42 +01:00

167 lines
6.1 KiB
PHP

<?php
namespace Modules\Stylist\Tests\Feature;
use Illuminate\Filesystem\Filesystem;
use Modules\Stylist\Theme\Exceptions\InvalidThemeManifestException;
use Modules\Stylist\Theme\Loader;
use Tests\TestCase;
final class ThemeManifestTest extends TestCase
{
private string $themesPath;
protected function setUp(): void
{
parent::setUp();
$this->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_bundled_default_themes_have_valid_manifests(): void
{
foreach (['backend', 'frontend'] as $key) {
$theme = app(Loader::class)->fromPath(resource_path("themes/{$key}"));
$this->assertSame($key, $theme->key());
}
}
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', '<div>Capable</div>');
$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']);
}
}