153 lines
8.3 KiB
TypeScript
153 lines
8.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
EXTRA_KEY_RE,
|
||
SITE_CONFIG_FIELD_DEFS,
|
||
extraRowError,
|
||
firstExtraError,
|
||
parseSiteConfig,
|
||
serializeSiteConfig,
|
||
validateFieldValue,
|
||
} from "./siteConfigForm";
|
||
|
||
/** docs/09 §3.1 站点配置结构化:parse/serialize/校验 */
|
||
describe("siteConfigForm", () => {
|
||
it("parse:已知键入 fields、未知字符串键入扩展行、theme_params/对象键无损入 preserved", () => {
|
||
const json = JSON.stringify({
|
||
logo: "/cms/upload/logo.png",
|
||
contact: "电话 0571-88888888",
|
||
domain: "www.xxx.edu.cn",
|
||
public_base_url: "https://www.xxx.edu.cn",
|
||
my_ext: "自定义值",
|
||
theme_params: { primary: "#b01a2e" },
|
||
wechat: { appid: "wx123" },
|
||
});
|
||
const m = parseSiteConfig(json);
|
||
expect(m.fields.logo).toBe("/cms/upload/logo.png");
|
||
expect(m.fields.contact).toBe("电话 0571-88888888");
|
||
expect(m.fields.domain).toBe("www.xxx.edu.cn");
|
||
expect(m.fields.public_base_url).toBe("https://www.xxx.edu.cn");
|
||
expect(m.extras).toEqual([{ key: "my_ext", value: "自定义值" }]);
|
||
expect(m.preserved.theme_params).toEqual({ primary: "#b01a2e" });
|
||
expect(m.preserved.wechat).toEqual({ appid: "wx123" });
|
||
});
|
||
|
||
it("parse:非法 JSON / 空值按空配置处理,已知键非字符串值无损保留", () => {
|
||
expect(parseSiteConfig("这不是json{").fields).toEqual({});
|
||
expect(parseSiteConfig(null).extras).toEqual([]);
|
||
expect(parseSiteConfig("").preserved).toEqual({});
|
||
const m = parseSiteConfig(JSON.stringify({ logo: 123, other: [1, 2] }));
|
||
expect(m.fields.logo).toBeUndefined();
|
||
expect(m.preserved.logo).toBe(123);
|
||
expect(m.preserved.other).toEqual([1, 2]);
|
||
});
|
||
|
||
it("serialize:空值剔除、扩展行空键/空值剔除、preserved 永不丢失", () => {
|
||
const m = parseSiteConfig(JSON.stringify({ logo: "/cms/upload/logo.png", theme_params: { primary: "#fff" }, wechat: { x: 1 } }));
|
||
m.fields.contact = " "; // 空白 → 剔除
|
||
m.extras.push({ key: "k1", value: "v1" });
|
||
m.extras.push({ key: "", value: "被剔除" });
|
||
m.extras.push({ key: "k2", value: " " }); // 空值 → 剔除
|
||
const out = JSON.parse(serializeSiteConfig(m));
|
||
expect(out.logo).toBe("/cms/upload/logo.png");
|
||
expect(out).not.toHaveProperty("contact");
|
||
expect(out.k1).toBe("v1");
|
||
expect(out).not.toHaveProperty("k2");
|
||
expect(out.theme_params).toEqual({ primary: "#fff" });
|
||
expect(out.wechat).toEqual({ x: 1 });
|
||
});
|
||
|
||
it("roundtrip:parse→serialize→parse 内容不丢(含扩展行与 preserved)", () => {
|
||
const json = JSON.stringify({
|
||
copyright: "XX学校 · 版权所有 © 2026",
|
||
seo_description: "校讯",
|
||
my_ext: "v",
|
||
theme_params: { bg: "#f7f5f2" },
|
||
});
|
||
const once = parseSiteConfig(json);
|
||
once.fields.slogan = "厚德博学";
|
||
const twice = parseSiteConfig(serializeSiteConfig(once));
|
||
expect(twice.fields.slogan).toBe("厚德博学");
|
||
expect(twice.fields.copyright).toBe("XX学校 · 版权所有 © 2026");
|
||
expect(twice.extras).toEqual([{ key: "my_ext", value: "v" }]);
|
||
expect(twice.preserved.theme_params).toEqual({ bg: "#f7f5f2" });
|
||
});
|
||
|
||
it("validateFieldValue:url/host/asset/text 各类型通过与拒绝", () => {
|
||
const def = (kind: string, maxLength?: number) =>
|
||
SITE_CONFIG_FIELD_DEFS.find(d => d.key === { url: "public_base_url", host: "domain", asset: "logo", text: "copyright" }[kind])!;
|
||
expect(validateFieldValue(def("url"), "https://www.xxx.edu.cn")).toBe("");
|
||
expect(validateFieldValue(def("url"), "www.xxx.edu.cn")).toContain("http");
|
||
expect(validateFieldValue(def("host"), "www.xxx.edu.cn")).toBe("");
|
||
expect(validateFieldValue(def("host"), "https://www.xxx.edu.cn")).toContain("域名");
|
||
expect(validateFieldValue(def("asset"), "/cms/upload/a.png")).toBe("");
|
||
expect(validateFieldValue(def("asset"), "https://cdn.example.com/a.png")).toBe("");
|
||
expect(validateFieldValue(def("asset"), "javascript:alert(1)")).toContain("路径");
|
||
expect(validateFieldValue(def("text"), "正常文字")).toBe("");
|
||
expect(validateFieldValue({ key: "t", label: "t", group: "footer", kind: "text", maxLength: 5 }, "超过五个字的文案")).toContain("5");
|
||
expect(validateFieldValue(def("url"), "")).toBe(""); // 空值一律放行(非必填)
|
||
});
|
||
|
||
it("extraRowError:格式/重名/重复校验;空键行放行", () => {
|
||
const rows = [{ key: "my_ext", value: "v" }];
|
||
expect(extraRowError(rows, 0)).toBe("");
|
||
expect(extraRowError([{ key: "", value: "v" }], 0)).toBe("");
|
||
expect(extraRowError([{ key: "my-ext", value: "v" }], 0)).toContain("下划线");
|
||
expect(extraRowError([{ key: "1bad", value: "v" }], 0)).toContain("下划线");
|
||
expect(extraRowError([{ key: "logo", value: "v" }], 0)).toContain("重名");
|
||
expect(extraRowError([{ key: "theme_params", value: "v" }], 0)).toContain("重名");
|
||
expect(extraRowError([{ key: "a", value: "1" }, { key: "a", value: "2" }], 1)).toContain("重复");
|
||
expect(EXTRA_KEY_RE.test("wechat_appid")).toBe(true);
|
||
expect(EXTRA_KEY_RE.test("_priv")).toBe(true);
|
||
expect(EXTRA_KEY_RE.test("9bad")).toBe(false);
|
||
});
|
||
|
||
it("firstExtraError:返回第一条错误;全部合法返回空串", () => {
|
||
expect(firstExtraError([{ key: "ok_key", value: "1" }, { key: "bad-key", value: "2" }])).toContain("下划线");
|
||
expect(firstExtraError([{ key: "a", value: "1" }, { key: "b", value: "2" }])).toBe("");
|
||
expect(firstExtraError([])).toBe("");
|
||
});
|
||
|
||
it("已知键清单与 docs/09 §0 键盘点表一致(防漂移)", () => {
|
||
expect(SITE_CONFIG_FIELD_DEFS.map(d => d.key)).toEqual([
|
||
"logo", "favicon", "slogan", "colleges_label",
|
||
"news_bg_image", "gallery_title", "gallery_subtitle",
|
||
"contact", "footer_addresses", "qr_wechat", "qr_weibo",
|
||
"social_wechat", "social_weibo", "social_douyin", "social_bilibili",
|
||
"copyright", "beian",
|
||
"seo_description",
|
||
"domain", "public_base_url",
|
||
]);
|
||
});
|
||
it("docs/12:页脚增强与首页视觉新键 parse/serialize/校验(qr_* asset、social_* url)", () => {
|
||
const json = JSON.stringify({
|
||
footer_addresses: "潭山校区:南湖路 237 号 邮编:464000\n淮河校区:大学路 1 号 邮编:464300",
|
||
qr_wechat: "/cms/upload/qr-wechat.png",
|
||
social_weibo: "https://weibo.com/xxx",
|
||
social_douyin: "not-a-url",
|
||
gallery_title: "线上展厅",
|
||
});
|
||
const m = parseSiteConfig(json);
|
||
expect(m.fields.footer_addresses).toContain("淮河校区");
|
||
expect(m.fields.qr_wechat).toBe("/cms/upload/qr-wechat.png");
|
||
expect(m.fields.social_weibo).toBe("https://weibo.com/xxx");
|
||
expect(m.extras).toEqual([]); // 新键均为已知键,不落扩展行
|
||
const qrDef = SITE_CONFIG_FIELD_DEFS.find(d => d.key === "qr_weibo")!;
|
||
const socialDef = SITE_CONFIG_FIELD_DEFS.find(d => d.key === "social_douyin")!;
|
||
expect(validateFieldValue(qrDef, "javascript:alert(1)")).toContain("路径");
|
||
expect(validateFieldValue(socialDef, "not-a-url")).toContain("http");
|
||
// 空值统一剔除:不污染 config_json
|
||
m.fields.social_bilibili = " ";
|
||
const out = JSON.parse(serializeSiteConfig(m));
|
||
expect(out.footer_addresses).toContain("邮编:464300");
|
||
expect(out).not.toHaveProperty("social_bilibili");
|
||
expect(out.gallery_title).toBe("线上展厅");
|
||
});
|
||
it("B3:custom_css 为保留键,不入扩展行,parse/serialize 无损往返", () => {
|
||
const json = JSON.stringify({ custom_css: ".site-header{padding:16px 0}", my_ext: "v" });
|
||
const m = parseSiteConfig(json);
|
||
expect(m.extras).toEqual([{ key: "my_ext", value: "v" }]);
|
||
expect(m.preserved.custom_css).toBe(".site-header{padding:16px 0}");
|
||
expect(JSON.parse(serializeSiteConfig(m)).custom_css).toBe(".site-header{padding:16px 0}");
|
||
});
|
||
});
|