2026-10-22 17:14:44 +08:00
|
|
|
"""Theme discovery must be a single source of truth."""
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from app import core, theme_pro, theme_store
|
|
|
|
|
|
|
|
|
|
ROOT = Path(core.__file__).resolve().parent.parent
|
|
|
|
|
|
|
|
|
|
def test_discover_returns_builtin_themes_with_manifests():
|
|
|
|
|
ids = core.discover_themes()
|
|
|
|
|
for tid in ('automotive', 'industrial', 'corporate', 'minimal'):
|
|
|
|
|
assert tid in ids
|
|
|
|
|
for tid in ids:
|
|
|
|
|
manifest = json.loads((ROOT / 'themes' / tid / 'manifest.json').read_text(encoding='utf-8'))
|
|
|
|
|
assert manifest.get('id') == tid
|
|
|
|
|
|
|
|
|
|
def test_three_whitelists_agree():
|
2026-10-22 17:28:57 +08:00
|
|
|
# Tripwire: the three names must stay the same object. Fails only if someone
|
|
|
|
|
# reintroduces a hardcoded copy of the list anywhere.
|
2026-10-22 17:14:44 +08:00
|
|
|
assert set(core.THEMES) == set(theme_pro.THEMES) == set(theme_store.BUILTIN)
|
|
|
|
|
assert isinstance(core.THEMES, tuple)
|
2026-10-22 17:28:57 +08:00
|
|
|
|
|
|
|
|
def test_discover_skips_broken_theme_dirs(tmp_path, monkeypatch):
|
|
|
|
|
themes = tmp_path / 'themes'
|
|
|
|
|
good = themes / 'good-one'; good.mkdir(parents=True)
|
|
|
|
|
(good / 'manifest.json').write_text(json.dumps({'id': 'good-one'}), encoding='utf-8')
|
|
|
|
|
broken = themes / 'broken'; broken.mkdir()
|
|
|
|
|
(broken / 'manifest.json').write_text('{not json', encoding='utf-8')
|
|
|
|
|
mismatch = themes / 'mismatch'; mismatch.mkdir()
|
|
|
|
|
(mismatch / 'manifest.json').write_text(json.dumps({'id': 'other-name'}), encoding='utf-8')
|
|
|
|
|
no_manifest = themes / 'no-manifest'; no_manifest.mkdir()
|
|
|
|
|
underscore = themes / '_shared'; underscore.mkdir()
|
|
|
|
|
monkeypatch.setattr(core, 'ROOT', tmp_path)
|
|
|
|
|
assert core.discover_themes() == ('good-one',)
|
|
|
|
|
|
|
|
|
|
def test_discover_fails_fast_when_themes_dir_missing(tmp_path, monkeypatch):
|
|
|
|
|
monkeypatch.setattr(core, 'ROOT', tmp_path)
|
|
|
|
|
import pytest
|
|
|
|
|
with pytest.raises(RuntimeError, match='themes directory missing'):
|
|
|
|
|
core.discover_themes()
|
2026-10-22 17:33:05 +08:00
|
|
|
|
|
|
|
|
def test_env_for_theme_prefers_theme_dir_then_shared_root():
|
|
|
|
|
from app import theme_pro
|
|
|
|
|
env = theme_pro.env_for('industrial')
|
|
|
|
|
paths = [str(p).replace('\\', '/') for p in env.loader.searchpath]
|
|
|
|
|
assert paths[0].endswith('themes/industrial')
|
|
|
|
|
assert paths[1].endswith('themes')
|
|
|
|
|
# 共享宏库必须仍可解析(回落路径)
|
|
|
|
|
assert env.get_template('_professional_components.html') is not None
|
|
|
|
|
|
|
|
|
|
def test_env_for_is_cached_per_theme():
|
|
|
|
|
from app import theme_pro
|
|
|
|
|
assert theme_pro.env_for('industrial') is theme_pro.env_for('industrial')
|
|
|
|
|
|
|
|
|
|
def test_env_for_pro_template_loads_unprefixed():
|
|
|
|
|
from app import theme_pro
|
|
|
|
|
assert theme_pro.env_for('industrial').get_template('pro.html') is not None
|