Files
YschcmsFront/src/utils/themeParamForm.spec.ts
T

135 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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");
});
});