test: 建立 DOCX 自动化验收门禁

This commit is contained in:
SkyJourney
2026-07-30 16:48:41 +08:00
parent f2dfc6ef72
commit aff2830c28
15 changed files with 1452 additions and 12 deletions
@@ -0,0 +1,134 @@
import { describe, expect, it } from "vitest";
import { zipSync } from "fflate";
import { inspectDocxAcceptance } from "../src/index.js";
const encoder = new TextEncoder();
const word =
"http://schemas.openxmlformats.org/wordprocessingml/2006/main";
const relationships =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships";
const packageRelationships =
"http://schemas.openxmlformats.org/package/2006/relationships";
const math =
"http://schemas.openxmlformats.org/officeDocument/2006/math";
const drawing =
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
const png = Uint8Array.of(
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a,
0,
0,
0,
0
);
function xml(value: string) {
return encoder.encode(value);
}
function createAcceptanceDocx(overrides: { altChunk?: boolean } = {}) {
return zipSync({
"[Content_Types].xml": xml(
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/xml"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="png" ContentType="image/png"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/></Types>'
),
"_rels/.rels": xml(
`<Relationships xmlns="${packageRelationships}"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>`
),
"word/document.xml": xml(
`<w:document xmlns:w="${word}" xmlns:r="${relationships}" xmlns:m="${math}" xmlns:wp="${drawing}"><w:body><w:p><w:pPr><w:numPr><w:numId w:val="1"/></w:numPr></w:pPr><w:hyperlink r:id="rIdLink"><w:r><w:t>可编辑正文</w:t></w:r></w:hyperlink><w:r><w:footnoteReference w:id="1"/></w:r><m:oMath><m:r><m:t>x</m:t></m:r></m:oMath></w:p><w:tbl><w:tr><w:tc><w:p><w:r><w:t>原生表格</w:t></w:r></w:p></w:tc></w:tr></w:tbl><w:p><w:r><w:drawing><wp:inline><wp:docPr id="1" name="图片 1" descr="普通图片"/><a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/></wp:inline></w:drawing></w:r></w:p>${overrides.altChunk ? '<w:altChunk r:id="rIdChunk"/>' : ""}<w:sectPr><w:footerReference w:type="default" r:id="rIdFooter"/><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="907" w:right="907" w:bottom="907" w:left="907"/></w:sectPr></w:body></w:document>`
),
"word/styles.xml": xml(
`<w:styles xmlns:w="${word}">${["Normal", "Heading1", "SourceCode", "Table", "Caption"].map((id) => `<w:style w:type="paragraph" w:styleId="${id}"/>`).join("")}</w:styles>`
),
"word/settings.xml": xml(`<w:settings xmlns:w="${word}"/>`),
"word/fontTable.xml": xml(`<w:fonts xmlns:w="${word}"/>`),
"word/numbering.xml": xml(`<w:numbering xmlns:w="${word}"/>`),
"word/theme/theme1.xml": xml(
'<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/>'
),
"word/footer1.xml": xml(
`<w:ftr xmlns:w="${word}"><w:p><w:r><w:instrText> PAGE \\* MERGEFORMAT </w:instrText></w:r></w:p></w:ftr>`
),
"word/footnotes.xml": xml(
`<w:footnotes xmlns:w="${word}"><w:footnote w:id="1"><w:p><w:r><w:t>脚注</w:t></w:r></w:p></w:footnote></w:footnotes>`
),
"word/media/image1.png": png,
"word/_rels/document.xml.rels": xml(
`<Relationships xmlns="${packageRelationships}"><Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/><Relationship Id="rIdFooter" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/><Relationship Id="rIdImage" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/><Relationship Id="rIdLink" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="https://example.invalid/" TargetMode="External"/>${overrides.altChunk ? '<Relationship Id="rIdChunk" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk" Target="chunk.html"/>' : ""}</Relationships>`
),
...(overrides.altChunk
? { "word/chunk.html": encoder.encode("<p>整体 HTML</p>") }
: {})
});
}
const expectation = {
id: "unit-fixture",
page: {
widthTwips: 11906,
heightTwips: 16838,
orientation: "portrait" as const,
marginsTwips: {
top: 907,
right: 907,
bottom: 907,
left: 907
}
},
requiredText: ["可编辑正文", "原生表格"],
requiredFootnoteText: ["脚注"],
minimumParagraphs: 3,
minimumTextRuns: 2,
minimumTables: 1,
minimumNumberedParagraphs: 1,
minimumHyperlinks: 1,
minimumFootnoteReferences: 1,
minimumMathObjects: 1,
minimumPngImages: 1,
requiredImageAltText: ["普通图片"],
requiredStyleIds: ["Normal", "Heading1", "SourceCode", "Table"],
requirePageField: true
};
describe("DOCX 自动验收器", () => {
it("验证纸张、可编辑结构、关系、PNG 和字段", () => {
const report = inspectDocxAcceptance(
createAcceptanceDocx(),
expectation
);
expect(report.page.orientation).toBe("portrait");
expect(report.structure).toMatchObject({
tableCount: 1,
numberedParagraphCount: 1,
hyperlinkCount: 1,
footnoteReferenceCount: 1,
mathObjectCount: 1,
pngImageCount: 1,
pageFieldCount: 1,
altChunkCount: 0
});
expect(report.checks).toEqual(
expect.objectContaining({
editableStructure: true,
pngMedia: true,
noAltChunk: true
})
);
});
it("拒绝通过 altChunk 嵌入的正文 HTML", () => {
expect(() =>
inspectDocxAcceptance(
createAcceptanceDocx({ altChunk: true }),
expectation
)
).toThrow("altChunk");
});
});