143 lines
5.9 KiB
TypeScript
143 lines
5.9 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { markdownToBlockAST } from './markdownToBlock';
|
|
import {
|
|
buildDocxModel,
|
|
exportDocumentToDocx,
|
|
parseInlineContent,
|
|
latexToOmml,
|
|
sanitizeMermaidCode,
|
|
cleanSvgForCanvas,
|
|
} from './docxRenderer';
|
|
import { buildXlsxWorkbook, exportDocumentToXlsx } from './xlsxRenderer';
|
|
import { Packer, ImportedXmlComponent } from 'docx';
|
|
import JSZip from 'jszip';
|
|
|
|
const SAMPLE_MD = `# 4K超高清荧光内窥镜系统立项与可行性报告
|
|
|
|
本产品主要用于微创腹腔手术中的组织成像与荧光导航。
|
|
|
|
> **[合规依据]** 本项目需严格遵守 GB 9706.1-2020 医用电气安全标准。
|
|
|
|
## 关键技术指标与参数对比
|
|
|
|
| 参数项 | 指标要求 | 检验依据 | 验证结果 |
|
|
| 光学分辨率 | **4K (3840x2160)** | YY 0068.1 | 符合 |
|
|
| 荧光波长 | 805nm 近红外 | 临床验证 | 符合 |
|
|
| 延迟时间 | < 35ms 超低延迟 | 内部实测 | 优于竞品 |
|
|
|
|
## 系统拓扑架构与数据流
|
|
|
|
\`\`\`mermaid
|
|
graph LR
|
|
A["近红外激光光源"] --> B["物镜系统"]
|
|
B --> C["双CMOS分光棱镜"]
|
|
\`\`\`
|
|
|
|
## 研发工时与费用测算
|
|
|
|
| 阶段 | 责任工程师 | 预计工时 (h) | 费率 (元/h) | 小计金额 (万元) |
|
|
| V0.1 需求摸底 | 光学方案专家 | 80 | 600 | 4.80 |
|
|
| V0.5 方案对齐 | FPGA硬件工程师 | 160 | 450 | 7.20 |
|
|
| V1.0 样机研制 | 嵌入式与算法架构师 | 240 | 500 | 12.00 |
|
|
|
|
$$
|
|
SNR_{fluorescence} = \\frac{S_{emission}}{\\sqrt{\\sigma_{ambient}^2 + \\sigma_{dark}^2 + \\sigma_{read}^2}} \\ge 18.5\\,\\text{dB}
|
|
$$
|
|
|
|
式中:$S_{emission}$ 为荧光激发光通量,$\\sigma_{ambient}$ 为无影灯环境噪声。
|
|
`;
|
|
|
|
test('DOCX 渲染器行内文本与公式解析测试 (parseInlineContent)', () => {
|
|
const text = '依据 **GB 9706.1-2020** 检验,$S_{emission}$ 信号优于 `35ms`。';
|
|
const tokens = parseInlineContent(text);
|
|
|
|
assert.ok(tokens.length >= 4);
|
|
// 验证包含行内 OMML 组件与 TextRun
|
|
const hasOmml = tokens.some((t) => t instanceof ImportedXmlComponent);
|
|
assert.ok(hasOmml, '应成功将 $S_{emission}$ 解析为 Word 原生 OMML 数学公式组件');
|
|
});
|
|
|
|
test('DOCX 渲染器 LaTeX 原生 OMML 转换引擎测试 (latexToOmml)', () => {
|
|
const latex = 'SNR = 10 \\cdot \\log_{10}\\left(\\frac{P_{signal}}{P_{noise}}\\right)';
|
|
const comp = latexToOmml(latex);
|
|
assert.ok(comp, '公式应当成功转换为 m:oMath 组件');
|
|
assert.equal((comp as any).rootKey, 'm:oMath', 'rootKey 必须为 m:oMath,绝不能为 undefined');
|
|
|
|
const inlineComp = latexToOmml('\\sigma_{ambient}');
|
|
assert.ok(inlineComp, '行内公式应当成功转换为 m:oMath 组件');
|
|
assert.equal((inlineComp as any).rootKey, 'm:oMath');
|
|
});
|
|
|
|
test('DOCX 渲染器 Mermaid 语法净化与容错测试 (sanitizeMermaidCode)', () => {
|
|
const raw = 'FPGA --> Display[4K医用级超低延迟监视器 (延时 < 35ms)]';
|
|
const sanitized = sanitizeMermaidCode(raw);
|
|
assert.ok(sanitized.includes('Display["4K医用级超低延迟监视器 (延时 < 35ms)"]'));
|
|
});
|
|
|
|
test('DOCX 渲染器 SVG 净化防 Canvas 污染测试 (cleanSvgForCanvas)', () => {
|
|
const dirtySvg = `<svg><style>@import url("https://fonts.googleapis.com/css?family=Open+Sans");</style><foreignObject width="100" height="40"><div><span>双CMOS分光棱镜</span></div></foreignObject></svg>`;
|
|
const cleaned = cleanSvgForCanvas(dirtySvg);
|
|
|
|
assert.ok(!cleaned.includes('@import'), '应彻底剥离 @import 外部网络字体');
|
|
assert.ok(!cleaned.includes('<foreignObject'), '应彻底移除或转换为 SVG 原生 <text>,杜绝 Tainted Canvas');
|
|
assert.ok(cleaned.includes('双CMOS分光棱镜'), '必须保留节点文本内容');
|
|
});
|
|
|
|
test('DOCX 渲染器全量编译测试 (buildDocxModel & exportDocumentToDocx)', async () => {
|
|
const ast = markdownToBlockAST(SAMPLE_MD, {
|
|
docId: 'doc_docx_test_001',
|
|
taskKey: 'feasibility_reg',
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
version: 'V1.0',
|
|
lifecycleState: 'drafting',
|
|
summary: '4K荧光内窥镜系统立项可行性评估。',
|
|
});
|
|
|
|
const doc = await buildDocxModel(ast);
|
|
assert.ok(doc);
|
|
|
|
// 验证 Packer 编译输出 Buffer 大小正常
|
|
const buffer = await Packer.toBuffer(doc);
|
|
assert.ok(buffer.length > 5000, `生成的 DOCX Buffer 应当大于 5KB,实际为 ${buffer.length} 字节`);
|
|
|
|
// 关键回归防线:解压验证 word/document.xml 中杜绝 <undefined> 且包含合法的 <m:oMath>
|
|
const zip = await JSZip.loadAsync(buffer);
|
|
const docXml = await zip.file('word/document.xml')!.async('text');
|
|
assert.ok(!docXml.includes('<undefined>'), 'Word document.xml 绝对不得包含 <undefined> 破坏性标签');
|
|
assert.ok(docXml.includes('<m:oMath'), 'Word document.xml 必须包含合法的微软 Office Math (<m:oMath>) 节点');
|
|
|
|
// 验证 exportDocumentToDocx 输出 Blob 正常
|
|
const blob = await exportDocumentToDocx(ast);
|
|
assert.equal(blob.type, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
|
assert.ok(blob.size > 5000);
|
|
});
|
|
|
|
test('XLSX 渲染器多表提取与工作簿编译测试 (buildXlsxWorkbook & exportDocumentToXlsx)', async () => {
|
|
const ast = markdownToBlockAST(SAMPLE_MD, {
|
|
docId: 'doc_xlsx_test_001',
|
|
taskKey: 'feasibility_reg',
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
version: 'V1.0',
|
|
lifecycleState: 'drafting',
|
|
});
|
|
|
|
const wb = buildXlsxWorkbook(ast);
|
|
assert.ok(wb);
|
|
assert.ok(wb.SheetNames.length >= 3, `应该包含概览 Sheet 和 2 个表格 Sheet,实际有: ${wb.SheetNames.join(', ')}`);
|
|
assert.equal(wb.SheetNames[0], '文档概览与签署');
|
|
|
|
// 验证表 1 数据
|
|
const sheet1 = wb.Sheets[wb.SheetNames[1]];
|
|
assert.ok(sheet1);
|
|
|
|
// 验证 exportDocumentToXlsx 输出 Blob 正常
|
|
const blob = await exportDocumentToXlsx(ast);
|
|
assert.equal(blob.type, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
assert.ok(blob.size > 1000);
|
|
});
|