Files
MorphDoc/scripts/docx-layout-visual-matrix-cases.test.mjs
T
SkyJourney 64445322eb release: 发布 v0.6.2 DOCX 真实文档修复
新增能力:将 DOCX 发布验收拆分为四套独立 140,支持真实语料冻结、指纹复用、失败与基础设施错误独立统计,并为表格换行、全 JSON 围栏、代码连续性和长文档分页建立通用门禁。

问题修复:冻结 Paged.js 分片前的逻辑表格列轨并传递打印几何,统一 Markdown 表格换行、代码、段落与 OOXML 翻译;改进 PDF 文本流排序、语义块映射、颜色与栅格比较,消除窄字符重叠和跨行范围符号误报。

兼容与部署:版本统一为 0.6.2;正式 Docker 镜像内置固定 Chromium、Pandoc 3.9.0.2 和 Serif/Sans/Mono 字体;Desktop NSIS 与 ZIP 继续直接内置字体,无需系统字体安装。

验证结果:合成基线与长庆严格 280/280,M4N 140/140;健康数据残余误报 6/115(5.22%),均核查为重复表头自动对齐/取样误报且基础设施错误为 0。全项目测试、类型检查、生产构建和 git diff --check 通过;正式 Docker、NSIS、ZIP、离线镜像、部署包、清单及 SHA-256 均已生成并校验。
2026-08-26 10:50:20 +08:00

198 lines
6.5 KiB
JavaScript
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 assert from "node:assert/strict";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import {
createDocxLayoutVisualMatrixCases,
DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS,
DOCX_LAYOUT_MATRIX_ORIENTATIONS,
getDocxFontGateFailures,
isCorpusTextColorEquivalent,
readBundledThemeMatrixDefinitions,
resolveEffectiveCoverPageCount,
summarizeDocxLayoutVisualMatrix
} from "./docx-layout-visual-matrix-cases.mjs";
const repositoryDirectory = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
".."
);
const themesDirectory = path.join(repositoryDirectory, "themes");
function matrix() {
const themes = readBundledThemeMatrixDefinitions(themesDirectory);
const cases = createDocxLayoutVisualMatrixCases(themes);
return { themes, cases, summary: summarizeDocxLayoutVisualMatrix(themes, cases) };
}
test("生成 14×2×5 的完整 DOCX 布局视觉矩阵", () => {
const { cases, summary } = matrix();
assert.equal(summary.themeCount, 14);
assert.equal(summary.orientationCount, 2);
assert.equal(summary.marginScenarioCount, 5);
assert.equal(summary.expectedCaseCount, 140);
assert.equal(cases.length, 140);
assert.equal(new Set(cases.map((entry) => entry.id)).size, 140);
assert.deepEqual(summary.orientations, ["portrait", "landscape"]);
});
test("字体门禁区分缺少声明、缺少嵌入和 Office 未实际采用", () => {
const missing = getDocxFontGateFailures({
theme: { id: "missing", docxFontFaces: [] },
inspection: { embeddedFontPartCount: 0, embeddedFontNames: [] },
renderedFonts: [],
engine: "word"
});
assert.equal(missing.length, 2);
const fallback = getDocxFontGateFailures({
theme: {
id: "declared",
docxFontFaces: [{ family: "FandolSong", aliases: [], weight: 400, style: "normal" }]
},
inspection: { embeddedFontPartCount: 1, embeddedFontNames: ["MdTP Serif SC"] },
renderedFonts: [{ name: "SimSun" }],
engine: "word"
});
assert.deepEqual(fallback, ["Microsoft Word 输出未实际使用 DOCX 嵌入字体"]);
const embedded = getDocxFontGateFailures({
theme: {
id: "declared",
docxFontFaces: [{ family: "FandolSong", aliases: [], weight: 400, style: "normal" }]
},
inspection: { embeddedFontPartCount: 1, embeddedFontNames: ["MdTP Serif SC"] },
renderedFonts: [{ name: "___WRD_EMBED_SUB_65" }],
engine: "word"
});
assert.deepEqual(embedded, []);
const embeddedWithEngineFaces = getDocxFontGateFailures({
theme: {
id: "engine-faces",
docxFontFaces: [{ family: "FandolSong", aliases: [], weight: 400, style: "normal" }]
},
inspection: {
embeddedFontPartCount: 3,
embeddedFontNames: ["MdTP Serif SC", "MdTP Mono"]
},
renderedFonts: [{ name: "WPSEMBED1" }],
engine: "wps"
});
assert.deepEqual(embeddedWithEngineFaces, []);
});
test("14 套内置主题都声明字体且中文字体面不超过三个", () => {
const { themes } = matrix();
const cjkFamilies = /(?:Fandol|MdTP|Song|Hei|Kai|Fang|Sim|YaHei|CJK|Han)/iu;
for (const theme of themes) {
assert.ok(theme.docxFontFaces.length > 0, `${theme.id} 缺少 DOCX 字体声明`);
assert.ok(theme.docxFontFaces.length <= 16, `${theme.id} 字体面超过协议上限`);
const cjkFaces = theme.docxFontFaces.filter((face) =>
[face.family, ...face.aliases].some((family) => cjkFamilies.test(family))
);
assert.ok(cjkFaces.length <= 3, `${theme.id} 中文字体面超过三个`);
}
});
test("每套主题都覆盖两个方向和五组边距", () => {
const { themes, cases } = matrix();
for (const theme of themes) {
const themeCases = cases.filter((entry) => entry.themeId === theme.id);
assert.equal(themeCases.length, 10, theme.id);
assert.deepEqual(
[...new Set(themeCases.map((entry) => entry.orientation))],
DOCX_LAYOUT_MATRIX_ORIENTATIONS
);
assert.deepEqual(
[...new Set(themeCases.map((entry) => entry.marginScenarioId))],
DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS.map((scenario) => scenario.id)
);
}
});
test("主题默认边距始终保留 theme 模式且不写 custom 数值", () => {
const { themes, cases } = matrix();
const themeDefaultCases = cases.filter(
(entry) => entry.marginScenarioId === "theme-default"
);
assert.equal(themeDefaultCases.length, 28);
for (const entry of themeDefaultCases) {
assert.equal(entry.config.paper.marginMode, "theme");
assert.equal("margins" in entry.config.paper, false);
const theme = themes.find((candidate) => candidate.id === entry.themeId);
assert.deepEqual(entry.themeDefaultMargins, theme?.themeDefaultMargins);
}
});
test("显式边距场景全部使用 custom 模式并保留稳定数值", () => {
const { cases } = matrix();
for (const scenario of DOCX_LAYOUT_MATRIX_MARGIN_SCENARIOS.slice(1)) {
const scenarioCases = cases.filter(
(entry) => entry.marginScenarioId === scenario.id
);
assert.equal(scenarioCases.length, 28, scenario.id);
for (const entry of scenarioCases) {
assert.equal(entry.config.paper.marginMode, "custom");
assert.deepEqual(entry.config.paper.margins, scenario.margins);
}
}
});
test("四套独立封面主题扩展为 40 个严格封面场景", () => {
const { cases, summary } = matrix();
assert.equal(summary.coverThemeCount, 4);
assert.equal(summary.coverCaseCount, 40);
assert.deepEqual(
[...new Set(
cases
.filter((entry) => entry.coverPageCount === 1)
.map((entry) => entry.themeId)
)],
[
"formal-feasibility",
"tender-blind",
"tender-business-blue",
"tender-classic"
]
);
});
test("真实语料按文档语义而不是主题能力判定独立封面", () => {
const caseDefinition = { coverPageCount: 1 };
assert.equal(resolveEffectiveCoverPageCount({
caseDefinition,
scope: "full",
sourceMetadata: {}
}), 1);
assert.equal(resolveEffectiveCoverPageCount({
caseDefinition,
scope: "corpus",
sourceMetadata: {}
}), 0);
assert.equal(resolveEffectiveCoverPageCount({
caseDefinition,
scope: "corpus",
sourceMetadata: { document: { profile: "tender" } }
}), 1);
});
test("短文本仅在墨迹边缘等价且像素误差低时忽略抗锯齿色差", () => {
assert.equal(isCorpusTextColorEquivalent({
inkIou: 1,
edgeIou: 1,
meanAbsoluteError: 6.9
}), true);
assert.equal(isCorpusTextColorEquivalent({
inkIou: 1,
edgeIou: 0.999,
meanAbsoluteError: 23
}), false);
assert.equal(isCorpusTextColorEquivalent({
inkIou: 0.8,
edgeIou: 1,
meanAbsoluteError: 4
}), false);
});