feat(theme): S5 学院蓝主题改造——home-blocks/service/guide页+区块CSS+ThemeRegistry增强
build / build-push (push) Successful in 53s

This commit is contained in:
chenxing
2026-09-22 17:35:38 +08:00
parent 82d6ff4602
commit bc794ccce3
28 changed files with 1818 additions and 86 deletions
@@ -208,6 +208,32 @@ class ThemeRegistryTest {
.doesNotContainKey("heroImage");
}
// ===== P-T1 参数迁包级(工作项 T):.meta 覆盖层承载 paramValues =====
@Test
@DisplayName("T-T-01: .meta 覆盖层的 paramValues 被注册表读取并经 paramValuesOf 暴露")
void metaOverlayParamValuesExposed() throws Exception {
java.nio.file.Path dir = java.nio.file.Files.createTempDirectory("pt1-themes");
writeExternalPackage(dir, "t-ext-params", null);
java.nio.file.Files.createDirectories(dir.resolve(".meta"));
java.nio.file.Files.write(dir.resolve(".meta").resolve("t-ext-params.json"),
"{\"paramValues\":{\"primary\":\"#0a5c36\"}}".getBytes(java.nio.charset.StandardCharsets.UTF_8));
ThemeRegistry r = new ThemeRegistry();
r.setThemeDir(dir.toString());
r.load();
assertThat(r.get("t-ext-params").getParamValues())
.as("覆盖层 paramValues 必须进入注册表(包级参数值的唯一来源)")
.containsEntry("primary", "#0a5c36");
assertThat(r.paramValuesOf("t-ext-params")).containsEntry("primary", "#0a5c36");
assertThat(r.paramValuesOf("t-demo-red"))
.as("无覆盖层的包返回空 Map(值回落 theme.json defaultValue)").isEmpty();
assertThat(r.paramValuesOf("no-such-theme"))
.as("未注册主题返回空 Map,调用方免判空").isEmpty();
deleteRecursively(dir);
}
// ===== W1 外部主题目录(方案 §5.3 T-W1-01..04)=====
/** 在临时目录构造一个合规外部包(基于 classpath 夹具 t-demo-red 文件拷贝改名) */
@@ -308,6 +334,65 @@ class ThemeRegistryTest {
deleteRecursively(dir);
}
// ===== v2 工作项 B:首页区块门禁补强(手册 §2.5 坑⑤:引用一致而非 8 形态强制)=====
@Test
@DisplayName("T-B-10 坑⑤回归:home.html 未引用本包 home-blocks 且无该文件的外部包必须照常注册")
void packageWithoutOwnHomeBlocksIsNotEvicted() throws Exception {
java.nio.file.Path dir = java.nio.file.Files.createTempDirectory("gate-themes");
java.nio.file.Path pkg = writeExternalPackage(dir, "t-ext-nohb", null);
assertThat(java.nio.file.Files.exists(pkg.resolve("home.html"))).isTrue();
assertThat(java.nio.file.Files.exists(pkg.resolve("fragments").resolve("home-blocks.html")))
.as("夹具包本就没有 home-blocks(B 档包同构)").isFalse();
ThemeRegistry r = new ThemeRegistry();
r.setThemeDir(dir.toString());
r.load();
assertThat(r.contains("t-ext-nohb"))
.as("缺 home-blocks 不得成为剔除理由,否则 7 套 B 档包静默回落 default").isTrue();
deleteRecursively(dir);
}
@Test
@DisplayName("T-B-11 home.html 把未定义的形态分发给本包 home-blocks → 该包剔除,同目录健康包不受影响")
void danglingHomeBlockReferenceEvictsOnlyThatPackage() throws Exception {
java.nio.file.Path dir = java.nio.file.Files.createTempDirectory("gate-themes");
java.nio.file.Path pkg = writeExternalPackage(dir, "t-ext-dangling", null);
// compact 有定义、image_wall 无定义(模拟派生包补形态时漏写一个)
java.nio.file.Files.write(pkg.resolve("fragments").resolve("home-blocks.html"),
"<section th:fragment=\"compact(block)\">c</section>".getBytes(java.nio.charset.StandardCharsets.UTF_8));
String home = new String(java.nio.file.Files.readAllBytes(pkg.resolve("home.html")),
java.nio.charset.StandardCharsets.UTF_8);
java.nio.file.Files.write(pkg.resolve("home.html"),
(home + dispatchToOwnHomeBlocks("compact") + dispatchToOwnHomeBlocks("image_wall"))
.getBytes(java.nio.charset.StandardCharsets.UTF_8));
writeExternalPackage(dir, "t-ext-neighbour", null);
ThemeRegistry r = new ThemeRegistry();
r.setThemeDir(dir.toString());
r.load();
assertThat(r.contains("t-ext-dangling"))
.as("引用了本包不存在的 fragment → 首页必 500,必须剔除").isFalse();
assertThat(r.contains("t-ext-neighbour"))
.as("剔除范围仅限坏包,不得连带误杀").isTrue();
deleteRecursively(dir);
}
@Test
@DisplayName("T-B-12 门禁补强后 13 套内置包零剔除(含只做 5 形态的 campus-glow)")
void allBuiltinPackagesSurviveTheGate() {
assertThat(registry.list()).extracting(ThemeInfo::getName).contains(
"default", "classic-red", "blue-white", "warm-kinder", "fresh-mint", "mint-breeze",
"scholar-blue", "campus-glow", "academic-navy", "campus-azure", "poly-blue",
"agri-forest", "jade-normal");
}
/** 与 poly-blue/home.html 同构的分发写法:~{${themeBase} + '/fragments/home-blocks' :: 形态(${block})} */
private static String dispatchToOwnHomeBlocks(String form) {
return "<th:block th:case=\"'" + form + "'\"><th:block th:replace=\"~{${themeBase} + "
+ "'/fragments/home-blocks' :: " + form + "(${block})}\" /></th:block>";
}
/** JDK8 递归删除(测试临时目录专用) */
private static void deleteRecursively(java.nio.file.Path path) {
if (!java.nio.file.Files.exists(path)) {