feat(theme): S5 学院蓝主题改造(管理端)——homeLayout编辑器/主题参数表单/站点设置/类型 + 单测
build / build (push) Successful in 2m19s
build / build (push) Successful in 2m19s
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user