init local code
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
COLOR_RE,
|
||||
formModelFromTheme,
|
||||
invalidParamKeys,
|
||||
isValidParamValue,
|
||||
mergeThemeParamsIntoConfigJson,
|
||||
themeParamsFromConfigJson,
|
||||
} from "./themeParamForm";
|
||||
import { ThemeVo } from "@/types/cms";
|
||||
|
||||
/** 与后端 default/t-demo-red schema 对齐的夹具(§4.2) */
|
||||
function theme(): ThemeVo {
|
||||
return {
|
||||
name: "t-demo-red",
|
||||
title: "测试红(夹具)",
|
||||
screenshotUrl: "/api/theme/t-demo-red/asset/screenshot.png",
|
||||
params: [
|
||||
{ key: "primary", cssVar: "--primary", label: "主色", type: "color", defaultValue: "#b01a2e" },
|
||||
{ key: "bg", cssVar: "--bg", label: "页面底色", type: "color", defaultValue: "#f7f5f2" },
|
||||
{
|
||||
key: "headerStyle",
|
||||
label: "头部样式",
|
||||
type: "select",
|
||||
defaultValue: "banner",
|
||||
options: [{ value: "banner", label: "横幅头图" }, { value: "plain", label: "简洁顶栏" }],
|
||||
},
|
||||
{ key: "logoImage", label: "头图", type: "image", defaultValue: "" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
describe("themeParamForm(方案 §6.3/§8.4)", () => {
|
||||
it("T28: schema→表单模型:四类型各映射为对应生效值 + 默认值兜底", () => {
|
||||
const model = formModelFromTheme(theme());
|
||||
expect(model.primary).toBe("#b01a2e");
|
||||
expect(model.bg).toBe("#f7f5f2");
|
||||
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
|
||||
});
|
||||
expect(model.primary).toBe("#123456");
|
||||
expect(model.bg).toBe("#f7f5f2");
|
||||
expect(model.headerStyle).toBe("plain");
|
||||
expect(model.logoImage).toBe("");
|
||||
});
|
||||
|
||||
it("T29: 值校验 —— color 正则通过/拒绝", () => {
|
||||
expect(COLOR_RE.test("#b01a2e")).toBe(true);
|
||||
expect(COLOR_RE.test("#fff")).toBe(true);
|
||||
expect(COLOR_RE.test("#b01a2eff")).toBe(true);
|
||||
expect(COLOR_RE.test("red")).toBe(false);
|
||||
expect(COLOR_RE.test("red;}")).toBe(false);
|
||||
expect(COLOR_RE.test("#gggggg")).toBe(false);
|
||||
expect(COLOR_RE.test("javascript:alert(1)")).toBe(false);
|
||||
});
|
||||
|
||||
it("T29: select 枚举外拒绝;image 非 /cms/upload/** 拒绝;空值一律不合法", () => {
|
||||
const t = theme();
|
||||
const select = t.params.find(p => p.key === "headerStyle")!;
|
||||
const image = t.params.find(p => p.key === "logoImage")!;
|
||||
const color = t.params.find(p => p.key === "primary")!;
|
||||
expect(isValidParamValue(select, "plain")).toBe(true);
|
||||
expect(isValidParamValue(select, "hacker")).toBe(false);
|
||||
// B-7:与应用真实路径对齐(context-path=/cms),旧 /upload/** 前缀不再放行
|
||||
expect(isValidParamValue(image, "/cms/upload/cms/logo.png")).toBe(true);
|
||||
expect(isValidParamValue(image, "/upload/cms/logo.png")).toBe(false);
|
||||
expect(isValidParamValue(image, "https://evil/x.png")).toBe(false);
|
||||
expect(isValidParamValue(color, "")).toBe(false);
|
||||
expect(isValidParamValue(color, undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("T29: invalidParamKeys 收集非法值 key", () => {
|
||||
const bad = invalidParamKeys(theme(), {
|
||||
primary: "#123456",
|
||||
headerStyle: "not-in-enum",
|
||||
});
|
||||
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