@bendyline/squisq-formats 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +20 -0
  3. package/README.md +1 -1
  4. package/dist/{chunk-CRAVSMPZ.js → chunk-26ISNJ7Y.js} +356 -114
  5. package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
  6. package/dist/{chunk-HTW2M27H.js → chunk-3NKXBZSR.js} +193 -42
  7. package/dist/{chunk-U32AG3G3.js → chunk-4V3KCHAP.js} +3 -4
  8. package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
  9. package/dist/{chunk-QRVN6A6E.js → chunk-6S6GU3ZG.js} +5 -6
  10. package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
  11. package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
  12. package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
  13. package/dist/{chunk-XKUMNGBW.js → chunk-EJTNGKEA.js} +5 -8
  14. package/dist/chunk-GX7RAUME.js +121 -0
  15. package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
  16. package/dist/{chunk-ABVI556T.js → chunk-IPN56VLW.js} +83 -58
  17. package/dist/{chunk-LXYLOOST.js → chunk-JE6LSIHE.js} +83 -22
  18. package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
  19. package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
  20. package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
  21. package/dist/chunk-OGS5VCGJ.js +446 -0
  22. package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
  23. package/dist/{chunk-2KPARF2P.js → chunk-PU7REGWV.js} +5 -8
  24. package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
  25. package/dist/{chunk-VSYHZECT.js → chunk-TAAENIRB.js} +5 -8
  26. package/dist/{chunk-WC7WULGV.js → chunk-X2DEAXNK.js} +62 -2
  27. package/dist/container/index.js +1 -2
  28. package/dist/csv/index.d.ts +27 -2
  29. package/dist/csv/index.js +1 -2
  30. package/dist/docx/index.d.ts +5 -1
  31. package/dist/docx/index.js +9 -10
  32. package/dist/epub/index.d.ts +2 -0
  33. package/dist/epub/index.js +5 -6
  34. package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
  35. package/dist/extract-MN7LA3NL.js +13 -0
  36. package/dist/html/index.d.ts +11 -4
  37. package/dist/html/index.js +3 -4
  38. package/dist/images-ESPQKVTW.js +6 -0
  39. package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
  40. package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
  41. package/dist/index.d.ts +7 -7
  42. package/dist/index.js +28 -26
  43. package/dist/infer/index.d.ts +3 -3
  44. package/dist/infer/index.js +7 -9
  45. package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
  46. package/dist/layouts-DRWZGSPD.js +10 -0
  47. package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
  48. package/dist/ooxml/index.d.ts +3 -3
  49. package/dist/ooxml/index.js +14 -13
  50. package/dist/pdf/index.d.ts +18 -0
  51. package/dist/pdf/index.js +2 -3
  52. package/dist/pptx/index.d.ts +4 -4
  53. package/dist/pptx/index.js +11 -13
  54. package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
  55. package/dist/registry/index.d.ts +21 -5
  56. package/dist/registry/index.js +9 -6
  57. package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
  58. package/dist/xlsx/index.d.ts +3 -3
  59. package/dist/xlsx/index.js +6 -7
  60. package/package.json +6 -3
  61. package/dist/chunk-2KPARF2P.js.map +0 -1
  62. package/dist/chunk-4VUWTSGM.js.map +0 -1
  63. package/dist/chunk-6M7Z25LA.js +0 -46
  64. package/dist/chunk-6M7Z25LA.js.map +0 -1
  65. package/dist/chunk-ABVI556T.js.map +0 -1
  66. package/dist/chunk-CRAVSMPZ.js.map +0 -1
  67. package/dist/chunk-FE6OJV6O.js.map +0 -1
  68. package/dist/chunk-GVS2XXV6.js.map +0 -1
  69. package/dist/chunk-HTW2M27H.js.map +0 -1
  70. package/dist/chunk-LXYLOOST.js.map +0 -1
  71. package/dist/chunk-MLX2BOJC.js.map +0 -1
  72. package/dist/chunk-NKAJPJ4G.js.map +0 -1
  73. package/dist/chunk-O3GVVND4.js.map +0 -1
  74. package/dist/chunk-ODL3SSPT.js.map +0 -1
  75. package/dist/chunk-PN52A5AA.js.map +0 -1
  76. package/dist/chunk-QRVN6A6E.js.map +0 -1
  77. package/dist/chunk-RFAPOKHJ.js.map +0 -1
  78. package/dist/chunk-SSUPBUF5.js.map +0 -1
  79. package/dist/chunk-U32AG3G3.js.map +0 -1
  80. package/dist/chunk-U4MRIFKL.js.map +0 -1
  81. package/dist/chunk-VJJM2SSH.js +0 -275
  82. package/dist/chunk-VJJM2SSH.js.map +0 -1
  83. package/dist/chunk-VSYHZECT.js.map +0 -1
  84. package/dist/chunk-WC7WULGV.js.map +0 -1
  85. package/dist/chunk-XKUMNGBW.js.map +0 -1
  86. package/dist/chunk-YRT7GQ5Y.js +0 -28
  87. package/dist/chunk-YRT7GQ5Y.js.map +0 -1
  88. package/dist/container/index.js.map +0 -1
  89. package/dist/csv/index.js.map +0 -1
  90. package/dist/docx/index.js.map +0 -1
  91. package/dist/epub/index.js.map +0 -1
  92. package/dist/extract-H6RXJMHP.js +0 -15
  93. package/dist/extract-H6RXJMHP.js.map +0 -1
  94. package/dist/html/index.js.map +0 -1
  95. package/dist/images-7FBWPKE3.js +0 -7
  96. package/dist/images-7FBWPKE3.js.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/infer/index.js.map +0 -1
  99. package/dist/layouts-QVPK3ZCU.js +0 -12
  100. package/dist/layouts-QVPK3ZCU.js.map +0 -1
  101. package/dist/mapTheme-IR27S6IV.js.map +0 -1
  102. package/dist/ooxml/index.js.map +0 -1
  103. package/dist/pdf/index.js.map +0 -1
  104. package/dist/pptx/index.js.map +0 -1
  105. package/dist/registry/index.js.map +0 -1
  106. package/dist/xlsx/index.js.map +0 -1
  107. package/src/__tests__/container.test.ts +0 -230
  108. package/src/__tests__/convert.test.ts +0 -495
  109. package/src/__tests__/csvImport.test.ts +0 -84
  110. package/src/__tests__/docxExport.test.ts +0 -457
  111. package/src/__tests__/docxImport.test.ts +0 -410
  112. package/src/__tests__/epub.test.ts +0 -649
  113. package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
  114. package/src/__tests__/formatRegistry.test.ts +0 -174
  115. package/src/__tests__/html.test.ts +0 -435
  116. package/src/__tests__/htmlImport.test.ts +0 -57
  117. package/src/__tests__/inferTheme.test.ts +0 -135
  118. package/src/__tests__/lossyWarnings.test.ts +0 -146
  119. package/src/__tests__/ooxml.test.ts +0 -271
  120. package/src/__tests__/ooxmlCancellation.test.ts +0 -113
  121. package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
  122. package/src/__tests__/pdfExport.test.ts +0 -322
  123. package/src/__tests__/pdfImport.test.ts +0 -384
  124. package/src/__tests__/plainHtml.test.ts +0 -417
  125. package/src/__tests__/plainHtmlBundle.test.ts +0 -253
  126. package/src/__tests__/pptxExport.test.ts +0 -138
  127. package/src/__tests__/pptxImport.test.ts +0 -145
  128. package/src/__tests__/pptxInferFixtures.ts +0 -314
  129. package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
  130. package/src/__tests__/roundTrip.test.ts +0 -201
  131. package/src/__tests__/roundTripAssets.test.ts +0 -50
  132. package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
  133. package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
  134. package/src/__tests__/roundTripMatrix.test.ts +0 -142
  135. package/src/__tests__/sharedContainer.test.ts +0 -41
  136. package/src/__tests__/sharedImages.test.ts +0 -61
  137. package/src/__tests__/xlsxExport.test.ts +0 -164
  138. package/src/__tests__/xlsxImport.test.ts +0 -80
  139. package/src/__tests__/zipSafety.test.ts +0 -317
  140. package/src/container/index.ts +0 -94
  141. package/src/csv/index.ts +0 -188
  142. package/src/docx/export.ts +0 -1267
  143. package/src/docx/import.ts +0 -995
  144. package/src/docx/index.ts +0 -26
  145. package/src/docx/styles.ts +0 -145
  146. package/src/epub/export.ts +0 -968
  147. package/src/epub/index.ts +0 -20
  148. package/src/html/docsHtmlBundle.ts +0 -373
  149. package/src/html/htmlTemplate.ts +0 -385
  150. package/src/html/imageUtils.ts +0 -61
  151. package/src/html/import.ts +0 -297
  152. package/src/html/index.ts +0 -212
  153. package/src/html/plainHtml.ts +0 -790
  154. package/src/html/plainHtmlBundle.ts +0 -421
  155. package/src/index.ts +0 -109
  156. package/src/infer/extract.ts +0 -127
  157. package/src/infer/index.ts +0 -199
  158. package/src/infer/mapTheme.ts +0 -176
  159. package/src/infer/types.ts +0 -27
  160. package/src/ooxml/index.ts +0 -111
  161. package/src/ooxml/namespaces.ts +0 -196
  162. package/src/ooxml/readUtils.ts +0 -44
  163. package/src/ooxml/reader.ts +0 -318
  164. package/src/ooxml/themeReader.ts +0 -197
  165. package/src/ooxml/types.ts +0 -103
  166. package/src/ooxml/writer.ts +0 -339
  167. package/src/ooxml/xmlUtils.ts +0 -123
  168. package/src/pdf/export.ts +0 -1084
  169. package/src/pdf/import.ts +0 -1164
  170. package/src/pdf/index.ts +0 -29
  171. package/src/pdf/styles.ts +0 -180
  172. package/src/pptx/export.ts +0 -1184
  173. package/src/pptx/import.ts +0 -455
  174. package/src/pptx/index.ts +0 -52
  175. package/src/pptx/layouts.ts +0 -1222
  176. package/src/pptx/styles.ts +0 -96
  177. package/src/pptx/templates.ts +0 -187
  178. package/src/registry/convert.ts +0 -433
  179. package/src/registry/defaultFormats.ts +0 -413
  180. package/src/registry/errors.ts +0 -46
  181. package/src/registry/index.ts +0 -43
  182. package/src/registry/registry.ts +0 -48
  183. package/src/registry/types.ts +0 -170
  184. package/src/shared/boundedZipArchive.ts +0 -383
  185. package/src/shared/container.ts +0 -28
  186. package/src/shared/fidelity.ts +0 -130
  187. package/src/shared/images.ts +0 -44
  188. package/src/shared/inlineRuns.ts +0 -99
  189. package/src/shared/text.ts +0 -41
  190. package/src/shared/zipEntryCount.ts +0 -151
  191. package/src/shared/zipLimits.ts +0 -296
  192. package/src/shared/zipSafety.ts +0 -19
  193. package/src/xlsx/export.ts +0 -253
  194. package/src/xlsx/import.ts +0 -160
  195. package/src/xlsx/index.ts +0 -35
@@ -1,457 +0,0 @@
1
- /**
2
- * Tests for DOCX export: markdownDocToDocx, docToDocx.
3
- *
4
- * Exports a MarkdownDocument to .docx, then unzips and inspects
5
- * the OOXML structure to verify correctness.
6
- */
7
-
8
- import { describe, it, expect } from 'vitest';
9
- import JSZip from 'jszip';
10
- import type {
11
- MarkdownDocument,
12
- MarkdownHeading,
13
- MarkdownList,
14
- MarkdownListItem,
15
- MarkdownTable,
16
- MarkdownTableRow,
17
- MarkdownTableCell,
18
- MarkdownCodeBlock,
19
- MarkdownBlockquote,
20
- MarkdownThematicBreak,
21
- MarkdownStrong,
22
- MarkdownEmphasis,
23
- MarkdownInlineCode,
24
- MarkdownLink,
25
- } from '@bendyline/squisq/markdown';
26
-
27
- import { markdownDocToDocx, docToDocx } from '../docx/export';
28
-
29
- // ============================================
30
- // Helpers
31
- // ============================================
32
-
33
- async function exportAndParse(
34
- doc: MarkdownDocument,
35
- options?: Parameters<typeof markdownDocToDocx>[1],
36
- ) {
37
- const buffer = await markdownDocToDocx(doc, options);
38
- expect(buffer).toBeInstanceOf(ArrayBuffer);
39
- expect(buffer.byteLength).toBeGreaterThan(0);
40
-
41
- const zip = await JSZip.loadAsync(buffer);
42
- return zip;
43
- }
44
-
45
- async function getDocumentXml(zip: JSZip): Promise<Document> {
46
- const text = await zip.file('word/document.xml')!.async('text');
47
- return new DOMParser().parseFromString(text, 'application/xml');
48
- }
49
-
50
- function _getBodyParagraphs(doc: Document): Element[] {
51
- const body = doc.getElementsByTagName('w:body')[0];
52
- return Array.from(body.children).filter((el) => el.localName === 'p');
53
- }
54
-
55
- // ============================================
56
- // Basic Structure
57
- // ============================================
58
-
59
- describe('markdownDocToDocx', () => {
60
- it('produces a valid zip with required parts', async () => {
61
- const doc: MarkdownDocument = {
62
- type: 'document',
63
- children: [{ type: 'paragraph', children: [{ type: 'text', value: 'Hello' }] }],
64
- };
65
-
66
- const zip = await exportAndParse(doc);
67
-
68
- expect(zip.file('[Content_Types].xml')).not.toBeNull();
69
- expect(zip.file('_rels/.rels')).not.toBeNull();
70
- expect(zip.file('word/document.xml')).not.toBeNull();
71
- expect(zip.file('word/styles.xml')).not.toBeNull();
72
- expect(zip.file('word/settings.xml')).not.toBeNull();
73
- expect(zip.file('word/fontTable.xml')).not.toBeNull();
74
- });
75
-
76
- it('exports an empty document', async () => {
77
- const doc: MarkdownDocument = { type: 'document', children: [] };
78
- const zip = await exportAndParse(doc);
79
- const xmlDoc = await getDocumentXml(zip);
80
- const body = xmlDoc.getElementsByTagName('w:body')[0];
81
- expect(body).toBeDefined();
82
- });
83
-
84
- // ============================================
85
- // Headings
86
- // ============================================
87
-
88
- it('exports headings with correct styles', async () => {
89
- const doc: MarkdownDocument = {
90
- type: 'document',
91
- children: [
92
- {
93
- type: 'heading',
94
- depth: 1,
95
- children: [{ type: 'text', value: 'Title' }],
96
- } satisfies MarkdownHeading,
97
- {
98
- type: 'heading',
99
- depth: 2,
100
- children: [{ type: 'text', value: 'Subtitle' }],
101
- } satisfies MarkdownHeading,
102
- {
103
- type: 'heading',
104
- depth: 3,
105
- children: [{ type: 'text', value: 'Section' }],
106
- } satisfies MarkdownHeading,
107
- ],
108
- };
109
-
110
- const zip = await exportAndParse(doc);
111
- const xmlText = await zip.file('word/document.xml')!.async('text');
112
-
113
- // Verify heading styles are present
114
- expect(xmlText).toContain('w:val="Heading1"');
115
- expect(xmlText).toContain('w:val="Heading2"');
116
- expect(xmlText).toContain('w:val="Heading3"');
117
-
118
- // Verify heading text
119
- expect(xmlText).toContain('Title');
120
- expect(xmlText).toContain('Subtitle');
121
- expect(xmlText).toContain('Section');
122
- });
123
-
124
- // ============================================
125
- // Inline Formatting
126
- // ============================================
127
-
128
- it('exports bold text with w:b element', async () => {
129
- const doc: MarkdownDocument = {
130
- type: 'document',
131
- children: [
132
- {
133
- type: 'paragraph',
134
- children: [
135
- {
136
- type: 'strong',
137
- children: [{ type: 'text', value: 'bold text' }],
138
- } satisfies MarkdownStrong,
139
- ],
140
- },
141
- ],
142
- };
143
-
144
- const zip = await exportAndParse(doc);
145
- const xmlText = await zip.file('word/document.xml')!.async('text');
146
- expect(xmlText).toContain('<w:b/>');
147
- expect(xmlText).toContain('bold text');
148
- });
149
-
150
- it('exports italic text with w:i element', async () => {
151
- const doc: MarkdownDocument = {
152
- type: 'document',
153
- children: [
154
- {
155
- type: 'paragraph',
156
- children: [
157
- {
158
- type: 'emphasis',
159
- children: [{ type: 'text', value: 'italic text' }],
160
- } satisfies MarkdownEmphasis,
161
- ],
162
- },
163
- ],
164
- };
165
-
166
- const zip = await exportAndParse(doc);
167
- const xmlText = await zip.file('word/document.xml')!.async('text');
168
- expect(xmlText).toContain('<w:i/>');
169
- expect(xmlText).toContain('italic text');
170
- });
171
-
172
- it('exports inline code with monospace font', async () => {
173
- const doc: MarkdownDocument = {
174
- type: 'document',
175
- children: [
176
- {
177
- type: 'paragraph',
178
- children: [{ type: 'inlineCode', value: 'console.log()' } satisfies MarkdownInlineCode],
179
- },
180
- ],
181
- };
182
-
183
- const zip = await exportAndParse(doc);
184
- const xmlText = await zip.file('word/document.xml')!.async('text');
185
- expect(xmlText).toContain('Consolas');
186
- expect(xmlText).toContain('console.log()');
187
- });
188
-
189
- // ============================================
190
- // Lists
191
- // ============================================
192
-
193
- it('exports unordered list with numbering', async () => {
194
- const doc: MarkdownDocument = {
195
- type: 'document',
196
- children: [
197
- {
198
- type: 'list',
199
- ordered: false,
200
- children: [
201
- {
202
- type: 'listItem',
203
- children: [{ type: 'paragraph', children: [{ type: 'text', value: 'Item 1' }] }],
204
- } satisfies MarkdownListItem,
205
- {
206
- type: 'listItem',
207
- children: [{ type: 'paragraph', children: [{ type: 'text', value: 'Item 2' }] }],
208
- } satisfies MarkdownListItem,
209
- ],
210
- } satisfies MarkdownList,
211
- ],
212
- };
213
-
214
- const zip = await exportAndParse(doc);
215
-
216
- // Should have numbering.xml
217
- expect(zip.file('word/numbering.xml')).not.toBeNull();
218
-
219
- const xmlText = await zip.file('word/document.xml')!.async('text');
220
- expect(xmlText).toContain('Item 1');
221
- expect(xmlText).toContain('Item 2');
222
- expect(xmlText).toContain('w:numId');
223
-
224
- // Numbering should be bullet type
225
- const numXml = await zip.file('word/numbering.xml')!.async('text');
226
- expect(numXml).toContain('w:val="bullet"');
227
- });
228
-
229
- it('exports ordered list with decimal numbering', async () => {
230
- const doc: MarkdownDocument = {
231
- type: 'document',
232
- children: [
233
- {
234
- type: 'list',
235
- ordered: true,
236
- children: [
237
- {
238
- type: 'listItem',
239
- children: [{ type: 'paragraph', children: [{ type: 'text', value: 'First' }] }],
240
- } satisfies MarkdownListItem,
241
- ],
242
- } satisfies MarkdownList,
243
- ],
244
- };
245
-
246
- const zip = await exportAndParse(doc);
247
- const numXml = await zip.file('word/numbering.xml')!.async('text');
248
- expect(numXml).toContain('w:val="decimal"');
249
- });
250
-
251
- // ============================================
252
- // Tables
253
- // ============================================
254
-
255
- it('exports tables with rows and cells', async () => {
256
- const doc: MarkdownDocument = {
257
- type: 'document',
258
- children: [
259
- {
260
- type: 'table',
261
- children: [
262
- {
263
- type: 'tableRow',
264
- children: [
265
- {
266
- type: 'tableCell',
267
- isHeader: true,
268
- children: [{ type: 'text', value: 'Name' }],
269
- } satisfies MarkdownTableCell,
270
- {
271
- type: 'tableCell',
272
- isHeader: true,
273
- children: [{ type: 'text', value: 'Value' }],
274
- } satisfies MarkdownTableCell,
275
- ],
276
- } satisfies MarkdownTableRow,
277
- {
278
- type: 'tableRow',
279
- children: [
280
- {
281
- type: 'tableCell',
282
- children: [{ type: 'text', value: 'A' }],
283
- } satisfies MarkdownTableCell,
284
- {
285
- type: 'tableCell',
286
- children: [{ type: 'text', value: '1' }],
287
- } satisfies MarkdownTableCell,
288
- ],
289
- } satisfies MarkdownTableRow,
290
- ],
291
- } satisfies MarkdownTable,
292
- ],
293
- };
294
-
295
- const zip = await exportAndParse(doc);
296
- const xmlText = await zip.file('word/document.xml')!.async('text');
297
- expect(xmlText).toContain('<w:tbl>');
298
- expect(xmlText).toContain('<w:tr>');
299
- expect(xmlText).toContain('<w:tc>');
300
- expect(xmlText).toContain('Name');
301
- expect(xmlText).toContain('Value');
302
- });
303
-
304
- // ============================================
305
- // Code Blocks
306
- // ============================================
307
-
308
- it('exports code blocks with code styling', async () => {
309
- const doc: MarkdownDocument = {
310
- type: 'document',
311
- children: [
312
- {
313
- type: 'code',
314
- value: 'const x = 1;\nconst y = 2;',
315
- } satisfies MarkdownCodeBlock,
316
- ],
317
- };
318
-
319
- const zip = await exportAndParse(doc);
320
- const xmlText = await zip.file('word/document.xml')!.async('text');
321
- expect(xmlText).toContain('const x = 1;');
322
- expect(xmlText).toContain('const y = 2;');
323
- expect(xmlText).toContain('Consolas');
324
- });
325
-
326
- // ============================================
327
- // Blockquotes
328
- // ============================================
329
-
330
- it('exports blockquotes with quote style', async () => {
331
- const doc: MarkdownDocument = {
332
- type: 'document',
333
- children: [
334
- {
335
- type: 'blockquote',
336
- children: [{ type: 'paragraph', children: [{ type: 'text', value: 'A wise quote' }] }],
337
- } satisfies MarkdownBlockquote,
338
- ],
339
- };
340
-
341
- const zip = await exportAndParse(doc);
342
- const xmlText = await zip.file('word/document.xml')!.async('text');
343
- expect(xmlText).toContain('Quote');
344
- expect(xmlText).toContain('A wise quote');
345
- });
346
-
347
- // ============================================
348
- // Hyperlinks
349
- // ============================================
350
-
351
- it('exports hyperlinks with relationship', async () => {
352
- const doc: MarkdownDocument = {
353
- type: 'document',
354
- children: [
355
- {
356
- type: 'paragraph',
357
- children: [
358
- {
359
- type: 'link',
360
- url: 'https://example.com',
361
- children: [{ type: 'text', value: 'click here' }],
362
- } satisfies MarkdownLink,
363
- ],
364
- },
365
- ],
366
- };
367
-
368
- const zip = await exportAndParse(doc);
369
- const xmlText = await zip.file('word/document.xml')!.async('text');
370
- expect(xmlText).toContain('<w:hyperlink');
371
- expect(xmlText).toContain('click here');
372
-
373
- // Check relationships
374
- const relsText = await zip.file('word/_rels/document.xml.rels')!.async('text');
375
- expect(relsText).toContain('https://example.com');
376
- expect(relsText).toContain('TargetMode="External"');
377
- });
378
-
379
- // ============================================
380
- // Thematic Break
381
- // ============================================
382
-
383
- it('exports thematic breaks', async () => {
384
- const doc: MarkdownDocument = {
385
- type: 'document',
386
- children: [{ type: 'thematicBreak' } satisfies MarkdownThematicBreak],
387
- };
388
-
389
- const zip = await exportAndParse(doc);
390
- const xmlText = await zip.file('word/document.xml')!.async('text');
391
- expect(xmlText).toContain('w:pBdr');
392
- expect(xmlText).toContain('w:bottom');
393
- });
394
-
395
- // ============================================
396
- // Core Properties
397
- // ============================================
398
-
399
- it('includes core properties when options are provided', async () => {
400
- const doc: MarkdownDocument = {
401
- type: 'document',
402
- children: [{ type: 'paragraph', children: [{ type: 'text', value: 'test' }] }],
403
- };
404
-
405
- const zip = await exportAndParse(doc, {
406
- title: 'My Document',
407
- author: 'Test Author',
408
- });
409
-
410
- const coreXml = await zip.file('docProps/core.xml')?.async('text');
411
- expect(coreXml).toBeDefined();
412
- expect(coreXml).toContain('My Document');
413
- expect(coreXml).toContain('Test Author');
414
- });
415
- });
416
-
417
- // ============================================
418
- // docToDocx convenience wrapper
419
- // ============================================
420
-
421
- describe('docToDocx', () => {
422
- it('converts a Doc to docx', async () => {
423
- const doc = {
424
- articleId: 'test',
425
- duration: 10,
426
- blocks: [
427
- {
428
- id: 'block-1',
429
- startTime: 0,
430
- duration: 5,
431
- audioSegment: 0,
432
- sourceHeading: {
433
- type: 'heading' as const,
434
- depth: 1 as const,
435
- children: [{ type: 'text' as const, value: 'Test Heading' }],
436
- },
437
- contents: [
438
- {
439
- type: 'paragraph' as const,
440
- children: [{ type: 'text' as const, value: 'Body text' }],
441
- },
442
- ],
443
- },
444
- ],
445
- audio: { segments: [] },
446
- };
447
-
448
- const buffer = await docToDocx(doc);
449
- expect(buffer).toBeInstanceOf(ArrayBuffer);
450
- expect(buffer.byteLength).toBeGreaterThan(0);
451
-
452
- const zip = await JSZip.loadAsync(buffer);
453
- const xmlText = await zip.file('word/document.xml')!.async('text');
454
- expect(xmlText).toContain('Test Heading');
455
- expect(xmlText).toContain('Body text');
456
- });
457
- });