- 新增可复用的 markdown-echarts 工作区,支持安全的 YAML 围栏、默认值、语义校验、错误占位和 SVG 渲染 - 将 ECharts 接入统一 Markdown、快速预览、精确预览与 PDF 链路,并复用 Mermaid 的不可跨页和超高图单页适配策略 - 默认示例加入 ECharts,增加源文本折叠、顶部文档状态、页码输入跳转与滚动同步 - 快速预览改用外层容器滚动,使滚动条与精确预览统一贴在预览区域右侧,并兼容显示缩放 - 完善 Docker 工作区复制、可配置 Compose 镜像、测试覆盖和进度文档
162 lines
4.2 KiB
TypeScript
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 链接");
|
|
});
|
|
});
|