135 lines
6.1 KiB
TypeScript
135 lines
6.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import * as themeParamForm from "./themeParamForm";
|
||
import {
|
||
COLOR_RE,
|
||
effectiveValue,
|
||
invalidParamKeys,
|
||
isValidParamValue,
|
||
paramValuesOf,
|
||
} 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 + 工作项 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("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: "",
|
||
});
|
||
});
|
||
|
||
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 正则通过/拒绝", () => {
|
||
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|/yschcms + /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:与应用真实路径对齐,旧 /upload/** 前缀不再放行;
|
||
// 兼容历史 /cms/upload/**(本地/旧数据)与现网 /yschcms/upload/**(projman 部署 context-path)
|
||
expect(isValidParamValue(image, "/cms/upload/cms/logo.png")).toBe(true);
|
||
expect(isValidParamValue(image, "/yschcms/upload/cms/logo.png")).toBe(true);
|
||
expect(isValidParamValue(image, "/upload/cms/logo.png")).toBe(false);
|
||
expect(isValidParamValue(image, "/yschcmsupload/x.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");
|
||
});
|
||
});
|