Files
YschcmsFront/src/utils/siteConfigForm.spec.ts
T
chenxing 6ee9ab5684
build / build (push) Successful in 2m43s
feat(site-layer): 学校站点分层方案C落地(docs/13)+栏目管理增强+管理端三修复+首页视觉升级管理端+审计复检T1~T5(docs/15)整合批次-URL站点段/site/{siteCode}无component镜像层(去name防重名,router-view跳过推进渲染)+身份条SiteSwitcher自绘(徽章#577afb/#ff9900+分组下拉✓+path次级列+Esc/外点收起)+URL唯一事实源syncSiteFromRoute(非法/跨校/主站码当子站段告警打回同页主站,localStorage降级刷新首帧兜底)+菜单剥段高亮stripSiteSeg/withSiteSeg补段+menuRoute镜像层防重影+跳转收敛adminPathOf(工作台进入/全部站点总览/站点管理行进入管理纯URL切站);栏目管理-上级栏目可选可改(禁选自身及后代防成环)+批量添加BatchAddCategory(行序写排序号单行失败不中断)+单页wangEditor富文本预览字数分区+categoryIds契约前端/types/mock三处统一+SchoolLayout高度链.layout-wrap修复+ArticleListPanel fixed模式;管理端三修复-皮肤预览Modal全屏(ThemePreviewFrame可选fill prop)+SiteManage补themeUnregistered声明+长图文编辑器随内容增高;管理端视觉升级配套-SiteConfigForm 12新键+BannerManage gallery展示位切换+栏目homeStyle下拉+PortalPreview侧栏同步;审计复检-新增siteRouter.spec真实路由集成7用例(镜像层matched3层/守卫子站码定向主站前缀/越权回工作台/旧链带id重定向)+sitePath空白容错+siteLayer持久化round-trip与身份条次级入口(docs/15);vitest 99/99+build通过+S1~S10 GUI走查全过
2026-09-10 11:11:23 +08:00

153 lines
8.3 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 {
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}");
});
});