init local code

This commit is contained in:
chenxing
2026-09-07 09:11:37 +08:00
commit a3a75c3680
112 changed files with 24837 additions and 0 deletions
+129
View File
@@ -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();
});
});