Files
MorphDoc/packages/docx-engine/tests/pandoc-structure.test.ts
T
SkyJourney 2c5c1bd317 release: 发布 v0.6.1 DOCX 视觉一致性修复
新增通用 CSS 到 OOXML 翻译修复,统一字体、字距、精确行距、段落、列表、表格、引用、代码块与行内代码连续性,不引入按主题 ID 分支。

新增 MdTP Mono 并统一 Serif、Sans、Mono 三字体包的 Chromium 与 DOCX 使用链;字体声明、嵌入部件和 Word/WPS 实际采用均进入硬门禁。

重建封面整页及正文语义块视觉差分,14 套主题、纵横两个方向、五组页边距共 140 个真实场景全部通过,阻断失败和诊断失败均为零。

源码服务、Docker Web API 与实际安装 Desktop 的 red-briefing 导出均包含 5 个字体部件;Word/WPS 原生渲染和逐页复核通过。修复 Docker 构建上下文与运行层复用软链接,并完善 v0.6.1 版本、发行说明和发布归集。

验证:npm test(116 个文件、616 项测试)、npm run typecheck、npm run build、git diff --check 全部通过。Desktop 安装器与 ZIP、Docker v0.6.1 镜像已生成;Windows 产物仍为未签名内部发行。
2026-08-04 10:30:44 +08:00

278 lines
6.9 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 type {
SemanticDocumentModel,
SemanticDocumentNode
} from "@md-to-pdf/core";
import type {
DocxResolvedStyleSlot,
DocxStyleSlotName,
DocxThemeTokenSet
} from "@md-to-pdf/docx-theme-engine";
import {
createPandocStructurePlan,
type PandocStructureBlock
} from "../src/index.js";
function slot(
name: DocxStyleSlotName,
hidden = false
): DocxResolvedStyleSlot {
return {
slot: name,
source: "computed-css",
confidence: "exact",
style: {
fontCandidates: [],
...(hidden ? { hidden: true } : {})
}
};
}
function tokens(
slots: DocxResolvedStyleSlot[] = []
): DocxThemeTokenSet {
return {
schemaVersion: 1,
themeId: "test-theme",
themeFingerprint: "c".repeat(64),
mode: "auto-with-overrides",
basePreset: "technical",
slots,
diagnostics: []
};
}
function text(
role: Extract<SemanticDocumentNode, { kind: "text" }>["role"],
value: string,
label?: string
): SemanticDocumentNode {
return {
kind: "text",
role,
text: value,
...(label ? { label } : {})
};
}
function paragraphSegments(block: PandocStructureBlock): string[] {
if (block.kind === "paragraph") {
return block.segments;
}
if (block.kind === "container") {
return block.blocks.flatMap(paragraphSegments);
}
return [];
}
describe("Pandoc 语义结构投影", () => {
it("将简报抬头文字映射到父级抬头样式", () => {
const model: SemanticDocumentModel = {
schemaVersion: 1,
profile: "briefing",
titlePolicy: {
metadataTitle: "suppress",
firstBodyHeading: "keep"
},
regions: [{
kind: "prefix",
nodes: [{
kind: "group",
role: "briefing-masthead",
children: [text("briefing-masthead-text", "工作简报")]
}]
}]
};
const plan = createPandocStructurePlan(model, tokens(), {
markerSeed: "briefing"
});
expect(plan.prefix[0]).toMatchObject({
kind: "container",
blocks: [{
kind: "paragraph",
styleId: "MdBriefingMastheadText",
segments: ["工作简报"]
}]
});
});
it("将公文分组投影为固定 Word 样式和可编辑段落", () => {
const model: SemanticDocumentModel = {
schemaVersion: 1,
profile: "official",
titlePolicy: {
metadataTitle: "suppress",
firstBodyHeading: "suppress"
},
regions: [
{
kind: "prefix",
nodes: [
{
kind: "group",
role: "official-masthead",
children: [
{
kind: "group",
role: "official-classification",
layout: "space-between",
children: [
text("official-secrecy", "秘密"),
text("official-urgency", "特急")
]
},
text("official-issuer", "示例单位"),
{
kind: "group",
role: "official-issue-row",
layout: "space-between",
children: [
text("official-number", "示例〔20261号"),
text(
"official-signatory",
"张三",
"签发人:"
)
]
}
]
},
text("official-title", "关于开展工作的通知")
]
}
]
};
const plan = createPandocStructurePlan(model, tokens(), {
markerSeed: "official"
});
expect(plan.metadataPolicy.author).toBe("suppress");
expect(plan.titlePolicy.firstBodyHeading).toBe("suppress");
expect(plan.prefix[0]).toMatchObject({
kind: "container",
styleId: "MdOfficialMasthead",
slot: "official-masthead",
startMarker:
"MD_TO_PDF_CONTAINER_START_official_1",
endMarker: "MD_TO_PDF_CONTAINER_END_official_2",
blocks: [
{
kind: "paragraph",
styleId: "MdOfficialClassification",
layout: "space-between",
segments: ["秘密", "特急"],
separator: "tab"
},
{
kind: "paragraph",
styleId: "MdOfficialIssuer",
segments: ["示例单位"]
},
{
kind: "paragraph",
styleId: "MdOfficialIssueRow",
layout: "space-between",
segments: ["示例〔20261号", "签发人:张三"],
separator: "tab"
}
]
});
expect(plan.prefix[1]).toMatchObject({
kind: "paragraph",
styleId: "MdOfficialTitle",
segments: ["关于开展工作的通知"]
});
});
it("按主题隐藏令牌过滤标书字段并保留封面分页", () => {
const model: SemanticDocumentModel = {
schemaVersion: 1,
profile: "tender",
titlePolicy: {
metadataTitle: "suppress",
firstBodyHeading: "suppress"
},
regions: [
{
kind: "cover",
section: {
headerFooter: "none",
pageNumber: "hidden",
breakAfter: "next-page",
followingPageNumberStart: 1
},
nodes: [
{
kind: "group",
role: "tender-cover",
children: [
text("tender-title", "投标文件"),
text("tender-bidder", "投标单位"),
text(
"tender-representative",
"授权代表"
),
text("tender-date", "2026年7月")
]
}
]
}
]
};
const plan = createPandocStructurePlan(
model,
tokens([
slot("tender-bidder", true),
slot("tender-representative", true)
]),
{ markerSeed: "tender" }
);
expect(plan.prefix.at(-1)).toEqual({
kind: "section-break",
marker: "MD_TO_PDF_SECTION_BREAK_tender_3",
headerFooter: "none",
pageNumber: "hidden",
followingPageNumberStart: 1
});
expect(plan.prefix[0]).toMatchObject({
kind: "container",
styleId: "MdTenderCover"
});
expect(paragraphSegments(plan.prefix[0]!)).toEqual([
"投标文件",
"2026年7月"
]);
});
it("没有结构区域时只传递标题策略", () => {
const plan = createPandocStructurePlan(
{
schemaVersion: 1,
titlePolicy: {
metadataTitle: "emit",
firstBodyHeading: "keep"
},
regions: []
},
tokens(),
{ markerSeed: "empty" }
);
expect(plan).toEqual({
schemaVersion: 1,
metadataPolicy: {
author: "suppress"
},
titlePolicy: {
metadataTitle: "emit",
firstBodyHeading: "keep"
},
prefix: [],
suffix: []
});
});
});