feat(theme): S5 学院蓝主题改造(管理端)——homeLayout编辑器/主题参数表单/站点设置/类型 + 单测
build / build (push) Successful in 2m19s

This commit is contained in:
chenxing
2026-09-22 18:34:50 +08:00
parent abccca4397
commit d05272b820
14 changed files with 893 additions and 186 deletions
+61 -59
View File
@@ -1,11 +1,11 @@
import { describe, expect, it } from "vitest";
import * as themeParamForm from "./themeParamForm";
import {
COLOR_RE,
formModelFromTheme,
effectiveValue,
invalidParamKeys,
isValidParamValue,
mergeThemeParamsIntoConfigJson,
themeParamsFromConfigJson,
paramValuesOf,
} from "./themeParamForm";
import { ThemeVo } from "@/types/cms";
@@ -30,26 +30,69 @@ function theme(): ThemeVo {
};
}
describe("themeParamForm(方案 §6.3/§8.4)", () => {
it("T28: schema→表单模型:四类型各映射为对应生效值 + 默认值兜底", () => {
const model = formModelFromTheme(theme());
expect(model.primary).toBe("#b01a2e");
expect(model.bg).toBe("#f7f5f2");
describe("themeParamForm(方案 §6.3/§8.4 + 工作项 T 包级参数)", () => {
it("T-T-08: paramValuesOf 以包 schema + paramValues 组装,缺项回落 defaultValue", () => {
const t = theme();
t.paramValues = { primary: "#123456" };
const model = paramValuesOf(t);
expect(model.primary).toBe("#123456"); // 包级微调值
expect(model.bg).toBe("#f7f5f2"); // 未微调 → theme.json 默认值
expect(model.headerStyle).toBe("banner");
expect(model.logoImage).toBe("");
});
it("T28: 已有合法值保留,非法显式值回退默认", () => {
const model = formModelFromTheme(theme(), {
primary: "#123456", // 合法,保留
bg: "red;}", // 非法,回退默认
headerStyle: "plain", // select 枚举内合法
logoImage: "javascript:alert(1)", // 非法 image
it("T-T-08: 覆盖层里的非法值回退默认、未声明 key 不入模型(与后端 filterValidValues 同语义)", () => {
const t = theme();
t.paramValues = { primary: "red;}", bg: "#000000", zzhack: "zzvalue" };
const model = paramValuesOf(t);
expect(model.primary).toBe("#b01a2e"); // 非法显式值 → 默认
expect(model.bg).toBe("#000000");
expect(model.zzhack).toBeUndefined();
});
it("T-T-08: 无 paramValues(新包/未微调)全量回落默认", () => {
expect(paramValuesOf(theme())).toEqual({
primary: "#b01a2e",
bg: "#f7f5f2",
headerStyle: "banner",
logoImage: "",
});
expect(model.primary).toBe("#123456");
expect(model.bg).toBe("#f7f5f2");
expect(model.headerStyle).toBe("plain");
expect(model.logoImage).toBe("");
});
it("R5-1 根治:paramValuesOf 只有「一个包」一个入参(无跨包 existing 通道),换包即取新包自身值", () => {
expect(paramValuesOf.length).toBe(1);
const nextPack = theme();
nextPack.name = "another-pack";
nextPack.params = nextPack.params.map(p => ({ ...p, defaultValue: p.type === "color" ? "#0a5c36" : p.defaultValue }));
// 上一个包微调成 #123456 后切到未微调的新包:只能是新包默认值,旧包值无处带入
expect(paramValuesOf(nextPack).primary).toBe("#0a5c36");
expect(paramValuesOf(nextPack).primary).not.toBe("#123456");
});
it("T-T-07: 站点级装拆随参数迁包退役(formModelFromTheme / merge…IntoConfigJson / …FromConfigJson 不再导出)", () => {
const mod = themeParamForm as unknown as Record<string, unknown>;
expect(mod.formModelFromTheme).toBeUndefined();
expect(mod.mergeThemeParamsIntoConfigJson).toBeUndefined();
expect(mod.themeParamsFromConfigJson).toBeUndefined();
// 模块出口收敛为「纯 schema + 值校验」:不再感知 configJson
expect(Object.keys(mod).sort()).toEqual(
[
"COLOR_RE",
"KEY_RE",
"effectiveValue",
"hasParamType",
"invalidParamKeys",
"isValidParamValue",
"paramValuesOf",
].sort()
);
});
it("T28: 生效值兜底 —— 合法显式值优先,非法/缺失回退默认", () => {
const primary = theme().params.find(p => p.key === "primary")!;
expect(effectiveValue(primary, { primary: "#123456" })).toBe("#123456");
expect(effectiveValue(primary, { primary: "red;}" })).toBe("#b01a2e");
expect(effectiveValue(primary, {})).toBe("#b01a2e");
});
it("T29: 值校验 —— color 正则通过/拒绝", () => {
@@ -88,45 +131,4 @@ describe("themeParamForm(方案 §6.3/§8.4)", () => {
expect(bad).toContain("headerStyle");
expect(bad).not.toContain("primary");
});
it("T30: themeParams↔configJson 装拆 —— 合并保留其余键 + 丢弃未声明键", () => {
const t = theme();
const merged = mergeThemeParamsIntoConfigJson(
'{"logo":"/upload/cms/logo.png","contact":"0571-88888888"}',
t,
{ primary: "#ff0000", headerStyle: "plain", zzhack: "zzvalue" },
);
const parsed = JSON.parse(merged);
expect(parsed.logo).toBe("/upload/cms/logo.png");
expect(parsed.contact).toBe("0571-88888888");
expect(parsed.theme_params.primary).toBe("#ff0000");
expect(parsed.theme_params.headerStyle).toBe("plain");
expect(parsed.theme_params).not.toHaveProperty("zzhack"); // 未声明 key 丢弃(后端同语义)
});
it("T30: 空/非法 configJson 兼容;无有效参数不写入 theme_params 键", () => {
const t = theme();
const merged = mergeThemeParamsIntoConfigJson("{bad json", t, {});
expect(JSON.parse(merged)).toEqual({});
const merged2 = mergeThemeParamsIntoConfigJson(
'{"theme_params":{"primary":"#b01a2e"},"logo":"x"}',
t,
{ headerStyle: "banner" },
);
const parsed2 = JSON.parse(merged2);
expect(parsed2.logo).toBe("x");
expect(parsed2.theme_params).toEqual({ headerStyle: "banner" }); // 旧的非法/未更新值被替换
});
it("T30: configJson → themeParams 编辑回显(仅声明 key + 非法值丢弃)", () => {
const t = theme();
const model = themeParamsFromConfigJson(
'{"theme_params":{"primary":"#b01a2e","zzhack":"x","headerStyle":"plain"}}',
t,
);
expect(model.primary).toBe("#b01a2e");
expect(model.headerStyle).toBe("plain");
expect(model.zzhack).toBeUndefined();
});
});