Files
MorphDoc/packages/markdown-echarts/tests/browser-render.test.ts
T
SkyJourney f5a46f79f1 feat: 增加 ECharts 渲染与预览交互增强
- 新增可复用的 markdown-echarts 工作区,支持安全的 YAML 围栏、默认值、语义校验、错误占位和 SVG 渲染
- 将 ECharts 接入统一 Markdown、快速预览、精确预览与 PDF 链路,并复用 Mermaid 的不可跨页和超高图单页适配策略
- 默认示例加入 ECharts,增加源文本折叠、顶部文档状态、页码输入跳转与滚动同步
- 快速预览改用外层容器滚动,使滚动条与精确预览统一贴在预览区域右侧,并兼容显示缩放
- 完善 Docker 工作区复制、可配置 Compose 镜像、测试覆盖和进度文档
2026-07-27 16:05:14 +08:00

162 lines
4.2 KiB
TypeScript

// @vitest-environment happy-dom
import MarkdownIt from "markdown-it";
import { describe, expect, it, vi } from "vitest";
import {
markdownItECharts
} from "../src/index.js";
import {
disposeEChartsBlocks,
renderEChartsBlocks,
type EChartsEngine,
type EChartsInstanceLike
} from "../src/browser.js";
function createDocumentWithChart() {
const markdown = new MarkdownIt().use(markdownItECharts);
document.body.innerHTML = markdown.render(`
\`\`\`echarts
version: 1
height: 80mm
caption: 测试图表
option:
xAxis:
type: category
data: [A, B]
yAxis:
type: value
series:
- type: bar
data: [1, 2]
\`\`\`
`);
}
function fakeEngine(
configureSvg?: (svg: SVGSVGElement) => void
) {
const dispose = vi.fn();
const resize = vi.fn();
const init = vi.fn(
(container: HTMLElement): EChartsInstanceLike => {
dispose.mockImplementation(() => container.replaceChildren());
const handlers = new Map<string, () => void>();
return {
setOption() {
const svg = document.createElementNS(
"http://www.w3.org/2000/svg",
"svg"
);
svg.setAttribute("viewBox", "0 0 400 300");
const text = document.createElementNS(
"http://www.w3.org/2000/svg",
"text"
);
text.textContent = "测试图表";
svg.append(text);
configureSvg?.(svg);
container.replaceChildren(svg);
handlers.get("finished")?.();
},
on(eventName, handler) {
handlers.set(eventName, handler);
},
off(eventName) {
handlers.delete(eventName);
},
resize,
dispose
};
}
);
return {
engine: { init } satisfies EChartsEngine,
init,
dispose,
resize
};
}
describe("浏览器 ECharts 渲染", () => {
it("生成安全的内联 SVG 并释放实例", async () => {
createDocumentWithChart();
const { engine, init, dispose } = fakeEngine();
const outcomes = await renderEChartsBlocks(document, {
engine,
outputMode: "inline-svg"
});
expect(outcomes).toHaveLength(1);
expect(outcomes[0]?.success).toBe(true);
expect(init).toHaveBeenCalledWith(
expect.any(HTMLElement),
undefined,
expect.objectContaining({ renderer: "svg" })
);
expect(document.querySelector(".md-echarts-host svg")).not.toBeNull();
expect(dispose).toHaveBeenCalledOnce();
expect(
document.querySelector(".md-echarts")?.getAttribute(
"data-echarts-pending"
)
).toBeNull();
});
it("生成 SVG Data URL 图片", async () => {
createDocumentWithChart();
const { engine } = fakeEngine();
const outcomes = await renderEChartsBlocks(document, {
engine,
outputMode: "svg-image"
});
expect(outcomes[0]?.success).toBe(true);
const image = document.querySelector<HTMLImageElement>(
".md-echarts-svg-image"
);
expect(image?.src).toMatch(/^data:image\/svg\+xml/u);
expect(image?.alt).toBe("测试图表");
});
it("交互模式保留实例并支持统一销毁", async () => {
createDocumentWithChart();
const { engine, dispose } = fakeEngine();
const outcomes = await renderEChartsBlocks(document, {
engine,
outputMode: "interactive"
});
expect(outcomes[0]?.instance).toBeDefined();
expect(dispose).not.toHaveBeenCalled();
disposeEChartsBlocks(document);
expect(dispose).toHaveBeenCalledOnce();
});
it("拒绝渲染器产生的外部 SVG 资源", async () => {
createDocumentWithChart();
const onError = vi.fn();
const { engine } = fakeEngine((svg) => {
const image = document.createElementNS(
"http://www.w3.org/2000/svg",
"image"
);
image.setAttribute("href", "https://example.com/chart.png");
svg.append(image);
});
const outcomes = await renderEChartsBlocks(document, {
engine,
outputMode: "inline-svg",
onError
});
expect(outcomes[0]?.success).toBe(false);
expect(onError).toHaveBeenCalledOnce();
expect(document.querySelector(".md-echarts-error")).not.toBeNull();
expect(document.body.textContent).toContain("外部 SVG 链接");
});
});