-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout.test.ts
More file actions
129 lines (120 loc) · 8.06 KB
/
Copy pathlayout.test.ts
File metadata and controls
129 lines (120 loc) · 8.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import JSZip from "jszip";
import { referenceFixture } from "./fixture.js";
import { renderDocx } from "./render-docx.js";
import { renderWeb } from "./render-web.js";
import { renderWebFlow } from "./render-web.js";
import { resolveLayout } from "./resolve.js";
import { canonicalizeDocxPackaging, generateHyperlinkFixture } from "./docx-package-reproducibility.js";
import { pageFlowFixture } from "./page-flow-fixture.js";
import { renderImageLineSpacingDocx } from "./image-line-spacing-fixture.js";
import { renderListNumberingDocx, renderListNumberingWeb } from "./list-numbering-fixture.js";
import { renderTableCellSpacingDocx } from "./table-cell-spacing-fixture.js";
test("relative line height resolves once to a format-neutral physical value", () => {
const resolved = resolveLayout(referenceFixture);
assert.equal(resolved.styles.body.lineHeight.unit, "pt");
assert.equal(resolved.styles.body.lineHeight.value, 15.225);
const representationWithoutContent = JSON.stringify({ page: resolved.page, styles: resolved.styles });
assert.doesNotMatch(representationWithoutContent, /twip|half.?point|dxa/i);
});
test("the Web adapter emits two physical A4 pages from the semantic break", () => {
const web = renderWeb(resolveLayout(referenceFixture));
assert.match(web, /@page\{size:A4 portrait;margin:0\}/);
assert.match(web, /style="font-family:'Malgun Gothic',sans-serif;font-size:17\.01pt/);
assert.doesNotMatch(web, /style="font-family:"/);
assert.match(web, /line-height:15\.225pt/);
assert.equal((web.match(/<article data-page=/g) ?? []).length, 2);
assert.match(web, /상대적인 줄 높이/);
});
test("the DOCX owns exact OOXML units, fonts, numbering, table geometry, and page break", async () => {
const zip = await JSZip.loadAsync(await renderDocx(resolveLayout(referenceFixture)));
const documentXml = await zip.file("word/document.xml")?.async("string");
const stylesXml = await zip.file("word/styles.xml")?.async("string");
const numberingXml = await zip.file("word/numbering.xml")?.async("string");
assert.ok(documentXml && stylesXml && numberingXml);
assert.match(documentXml, /w:type="page"/);
assert.match(documentXml, /w:line="305" w:lineRule="exact"/);
assert.match(documentXml, /<w:tblLayout w:type="fixed"/);
assert.match(documentXml, /<w:tblGrid>/);
assert.match(documentXml, /<w:pgSz(?=[^>]*w:w="11905")(?=[^>]*w:h="16837")[^>]*\/>/);
assert.match(documentXml, /<w:pgMar(?=[^>]*w:top="1133")(?=[^>]*w:right="1133")(?=[^>]*w:bottom="1133")(?=[^>]*w:left="1133")[^>]*\/>/);
assert.match(stylesXml, /w:eastAsia="Malgun Gothic"/);
assert.match(numberingXml, /<w:numFmt w:val="bullet"/);
assert.match(numberingXml, /<w:ind w:left="720" w:hanging="360"/);
});
test("fixture-specific anchors live in configuration, not the comparison engine", async () => {
const engine = await readFile(new URL("../tools/compare-layout.py", import.meta.url), "utf8");
const config = await readFile(new URL("../config/anchors.json", import.meta.url), "utf8");
assert.doesNotMatch(engine, /Second page checkpoint|15\.225 pt exact/);
assert.match(config, /Second page checkpoint/);
});
test("DOCX packaging canonicalization makes repeated hyperlink fixtures byte-identical", async () => {
const first = await generateHyperlinkFixture();
const second = await generateHyperlinkFixture();
assert.notDeepEqual(first, second, "docx 9.7.1 emits variable package bytes for identical input");
const [canonicalFirst, canonicalSecond] = await Promise.all([canonicalizeDocxPackaging(first), canonicalizeDocxPackaging(second)]);
assert.deepEqual(canonicalFirst, canonicalSecond);
const zip = await JSZip.loadAsync(canonicalFirst);
const relationships = await zip.file("word/_rels/document.xml.rels")?.async("string");
const document = await zip.file("word/document.xml")?.async("string");
assert.match(relationships ?? "", /Id="rIdCanonicalExternal1"[^>]*Target="https:\/\/example\.com\/layout"/);
assert.match(document ?? "", /r:id="rIdCanonicalExternal1"/);
});
test("the page-flow fixture delegates pagination without inserting an explicit break", async () => {
const resolved = resolveLayout(pageFlowFixture);
const web = renderWebFlow(resolved);
const zip = await JSZip.loadAsync(await renderDocx(resolved));
const documentXml = await zip.file("word/document.xml")?.async("string");
assert.ok(documentXml);
assert.doesNotMatch(web, /data-page=|break-after:page/);
assert.match(web, /@page\{size:210mm 297mm;margin:25\.4mm 25\.4mm 25\.4mm 25\.4mm\}/);
assert.doesNotMatch(documentXml, /w:type="page"/);
assert.match(web, /checkpoint 63/);
assert.match(documentXml, /checkpoint 63/);
assert.match(web, /Final independent flow marker remains present/);
assert.match(documentXml, /Final independent flow marker remains present/);
assert.throws(() => renderWebFlow(resolveLayout(referenceFixture)), /without explicit page breaks/);
});
test("inline image paragraphs explicitly override inherited exact body spacing", async () => {
const inheritedZip = await JSZip.loadAsync(await renderImageLineSpacingDocx("inherited-exact"));
const autoZip = await JSZip.loadAsync(await renderImageLineSpacingDocx("explicit-auto"));
const inheritedDocument = await inheritedZip.file("word/document.xml")?.async("string");
const autoDocument = await autoZip.file("word/document.xml")?.async("string");
const inheritedStyles = await inheritedZip.file("word/styles.xml")?.async("string");
assert.ok(inheritedDocument && autoDocument && inheritedStyles);
const inheritedImageParagraph = inheritedDocument.split("</w:p>").find((paragraph) => paragraph.includes("<w:drawing"));
const autoImageParagraph = autoDocument.split("</w:p>").find((paragraph) => paragraph.includes("<w:drawing"));
assert.ok(inheritedImageParagraph && autoImageParagraph);
assert.match(inheritedStyles, /w:line="305" w:lineRule="exact"/);
assert.doesNotMatch(inheritedImageParagraph, /w:lineRule=/);
assert.match(autoImageParagraph, /w:line="240" w:lineRule="auto"/);
assert.match(autoImageParagraph, /wp:extent cx="1438275" cy="723900"/);
});
test("recursive list traversal maps the first visible list to ilvl zero and preserves starts", async () => {
const web = renderListNumberingWeb();
assert.match(web, /data-list-id="outer-ordered" start="4"/);
assert.match(web, /data-list-id="nested-ordered" start="3"/);
const zip = await JSZip.loadAsync(await renderListNumberingDocx());
const documentXml = await zip.file("word/document.xml")?.async("string");
const numberingXml = await zip.file("word/numbering.xml")?.async("string");
assert.ok(documentXml && numberingXml);
const topParagraph = documentXml.split("</w:p>").find((paragraph) => paragraph.includes("Top item four"));
const nestedParagraph = documentXml.split("</w:p>").find((paragraph) => paragraph.includes("Nested item three"));
assert.match(topParagraph ?? "", /<w:ilvl w:val="0"/);
assert.match(nestedParagraph ?? "", /<w:ilvl w:val="1"/);
assert.match(numberingXml, /<w:start w:val="4"/);
assert.match(numberingXml, /<w:start w:val="3"/);
});
test("table cells keep block spacing outside their internal paragraph line box", async () => {
const inherited = await JSZip.loadAsync(await renderTableCellSpacingDocx("inherited-block-spacing"));
const corrected = await JSZip.loadAsync(await renderTableCellSpacingDocx("cell-zero-spacing"));
const inheritedXml = await inherited.file("word/document.xml")?.async("string");
const correctedXml = await corrected.file("word/document.xml")?.async("string");
assert.ok(inheritedXml && correctedXml);
assert.match(inheritedXml, /<w:spacing w:after="200" w:before="140" w:line="257" w:lineRule="exact"\/>/);
assert.match(correctedXml, /<w:spacing w:after="0" w:before="0" w:line="257" w:lineRule="exact"\/>/);
assert.match(correctedXml, /<w:tcMar><w:top w:type="dxa" w:w="100"\/><w:left w:type="dxa" w:w="100"\/><w:bottom w:type="dxa" w:w="100"\/><w:right w:type="dxa" w:w="100"\/><\/w:tcMar>/);
assert.match(correctedXml, /<w:tblGrid><w:gridCol w:w="4478"\/><w:gridCol w:w="4478"\/><\/w:tblGrid>/);
});