Files
MorphDoc/packages/markdown-echarts/tests/security-policy.test.ts
T
SkyJourney 92a5bfd016 feat: 扩展 ECharts 第二阶段图表支持
新增 tree、treemap、sunburst、graph、sankey、chord、funnel、gauge 和 pictorialBar 系列,并注册对应的 ECharts 按需模块。

完善层级与关系数据校验、节点和边数量限制、默认布局及错误占位规则,同时补充默认 Markdown 与 README 示例。

静态渲染全局关闭 ECharts 动画,修复 Treemap 标签冻结在透明动画帧的问题,并完成快速预览、精确 PDF、容器镜像和 147 项全项目测试验证。
2026-07-27 17:47:28 +08:00

150 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
defaultEChartsSecurityLimits,
normalizeAndValidateEChartsOption
} from "../src/index.js";
describe("normalizeAndValidateEChartsOption", () => {
it("接受首版系列和内置数据转换", () => {
const normalized = normalizeAndValidateEChartsOption({
dataset: {
source: [
["name", "value"],
["A", 1]
],
transform: {
type: "sort",
config: {
dimension: "value",
order: "desc"
}
}
},
series: [
{
type: "bar",
encode: {
x: "name",
y: "value"
}
}
]
});
expect(normalized).toMatchObject({
series: [{ type: "bar" }]
});
expect(Object.getPrototypeOf(normalized)).toBe(
Object.prototype
);
expect(normalized.hasOwnProperty("series")).toBe(true);
});
it("拒绝不支持的系列", () => {
expect(() =>
normalizeAndValidateEChartsOption({
series: [{ type: "custom" }]
})
).toThrow(/暂不支持 custom/u);
});
it("接受第二阶段系列和安全的内嵌矢量路径", () => {
const types = [
"tree",
"treemap",
"sunburst",
"graph",
"sankey",
"chord",
"funnel",
"gauge",
"pictorialBar"
];
expect(
normalizeAndValidateEChartsOption({
series: types.map((type) => ({
type,
...(type === "pictorialBar"
? { symbol: "path://M0,0 L10,0 L10,10 Z" }
: {})
}))
}).series
).toHaveLength(types.length);
});
it("限制层级节点和关系边数量", () => {
const limits = {
...defaultEChartsSecurityLimits,
maxHierarchyNodes: 2,
maxGraphEdges: 1
};
expect(() =>
normalizeAndValidateEChartsOption(
{
series: [
{
type: "tree",
data: [
{
children: [{ value: 1 }, { value: 2 }]
}
]
}
]
},
limits
)
).toThrow(/层级节点数量不能超过 2/u);
expect(() =>
normalizeAndValidateEChartsOption(
{
series: [
{
type: "graph",
data: [{ name: "A" }, { name: "B" }],
links: [
{ source: "A", target: "B" },
{ source: "B", target: "A" }
]
}
]
},
limits
)
).toThrow(/边数量不能超过 1/u);
});
it("拒绝外部资源和危险链接", () => {
expect(() =>
normalizeAndValidateEChartsOption({
title: {
text: "示例",
subtext: "https://example.com/chart"
},
series: [{ type: "line" }]
})
).toThrow(/危险 URL/u);
});
it("拒绝 HTML formatter", () => {
expect(() =>
normalizeAndValidateEChartsOption({
tooltip: {
formatter: "<img src=x onerror=alert(1)>"
},
series: [{ type: "line" }]
})
).toThrow(/不含 HTML/u);
});
it("拒绝原型污染属性", () => {
const option = JSON.parse(
'{"series":[{"type":"line"}],"__proto__":{"polluted":true}}'
);
expect(() => normalizeAndValidateEChartsOption(option)).toThrow(
/禁止属性/u
);
});
});