@pyreon/document 0.11.4 → 0.11.6

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 (50) hide show
  1. package/README.md +7 -4
  2. package/lib/confluence-Bd3ua1Ut.js.map +1 -1
  3. package/lib/csv-COrS4qdy.js.map +1 -1
  4. package/lib/discord-BLUnkEh9.js.map +1 -1
  5. package/lib/docx-uNAel545.js.map +1 -1
  6. package/lib/email-D0bbfWq4.js.map +1 -1
  7. package/lib/google-chat-CkKCBUWC.js.map +1 -1
  8. package/lib/html-B5biprN2.js.map +1 -1
  9. package/lib/index.js.map +1 -1
  10. package/lib/markdown-CdtlFGC0.js.map +1 -1
  11. package/lib/notion-iG2C5bEY.js.map +1 -1
  12. package/lib/pdf-IuBgTb3T.js.map +1 -1
  13. package/lib/pptx-DXiMiYFM.js.map +1 -1
  14. package/lib/sanitize-O_3j1mNJ.js.map +1 -1
  15. package/lib/slack-BI3EQwYm.js.map +1 -1
  16. package/lib/svg-BKxumy-p.js.map +1 -1
  17. package/lib/teams-Cwz9lce0.js.map +1 -1
  18. package/lib/telegram-gYFqyMXb.js.map +1 -1
  19. package/lib/text-l1XNXBOC.js.map +1 -1
  20. package/lib/types/index.d.ts +27 -27
  21. package/lib/whatsapp-CjSGoOKx.js.map +1 -1
  22. package/lib/xlsx-Cvu4LBNy.js.map +1 -1
  23. package/package.json +21 -21
  24. package/src/builder.ts +36 -36
  25. package/src/download.ts +32 -32
  26. package/src/index.ts +5 -10
  27. package/src/nodes.ts +45 -45
  28. package/src/render.ts +43 -43
  29. package/src/renderers/confluence.ts +63 -63
  30. package/src/renderers/csv.ts +10 -10
  31. package/src/renderers/discord.ts +37 -37
  32. package/src/renderers/docx.ts +57 -57
  33. package/src/renderers/email.ts +72 -72
  34. package/src/renderers/google-chat.ts +34 -34
  35. package/src/renderers/html.ts +76 -76
  36. package/src/renderers/markdown.ts +42 -42
  37. package/src/renderers/notion.ts +60 -60
  38. package/src/renderers/pdf.ts +78 -78
  39. package/src/renderers/pptx.ts +51 -51
  40. package/src/renderers/slack.ts +48 -48
  41. package/src/renderers/svg.ts +47 -47
  42. package/src/renderers/teams.ts +67 -67
  43. package/src/renderers/telegram.ts +39 -39
  44. package/src/renderers/text.ts +43 -43
  45. package/src/renderers/whatsapp.ts +33 -33
  46. package/src/renderers/xlsx.ts +35 -35
  47. package/src/sanitize.ts +20 -20
  48. package/src/tests/document.test.ts +1302 -1302
  49. package/src/tests/stress.test.ts +110 -110
  50. package/src/types.ts +61 -61
@@ -1,4 +1,4 @@
1
- import { afterEach, describe, expect, it } from "vitest"
1
+ import { afterEach, describe, expect, it } from 'vitest'
2
2
  import {
3
3
  _resetRenderers,
4
4
  Button,
@@ -24,7 +24,7 @@ import {
24
24
  Table,
25
25
  Text,
26
26
  unregisterRenderer,
27
- } from "../index"
27
+ } from '../index'
28
28
 
29
29
  afterEach(() => {
30
30
  _resetRenderers()
@@ -32,844 +32,844 @@ afterEach(() => {
32
32
 
33
33
  // ─── Node Construction ──────────────────────────────────────────────────────
34
34
 
35
- describe("node construction", () => {
36
- it("Document creates a document node", () => {
37
- const doc = Document({ title: "Test", children: "hello" })
38
- expect(doc.type).toBe("document")
39
- expect(doc.props.title).toBe("Test")
40
- expect(doc.children).toEqual(["hello"])
35
+ describe('node construction', () => {
36
+ it('Document creates a document node', () => {
37
+ const doc = Document({ title: 'Test', children: 'hello' })
38
+ expect(doc.type).toBe('document')
39
+ expect(doc.props.title).toBe('Test')
40
+ expect(doc.children).toEqual(['hello'])
41
41
  })
42
42
 
43
- it("Page creates a page node", () => {
44
- const page = Page({ size: "A4", margin: 40, children: "content" })
45
- expect(page.type).toBe("page")
46
- expect(page.props.size).toBe("A4")
43
+ it('Page creates a page node', () => {
44
+ const page = Page({ size: 'A4', margin: 40, children: 'content' })
45
+ expect(page.type).toBe('page')
46
+ expect(page.props.size).toBe('A4')
47
47
  expect(page.props.margin).toBe(40)
48
48
  })
49
49
 
50
- it("Section with direction", () => {
51
- const section = Section({ direction: "row", gap: 20, children: "a" })
52
- expect(section.type).toBe("section")
53
- expect(section.props.direction).toBe("row")
50
+ it('Section with direction', () => {
51
+ const section = Section({ direction: 'row', gap: 20, children: 'a' })
52
+ expect(section.type).toBe('section')
53
+ expect(section.props.direction).toBe('row')
54
54
  expect(section.props.gap).toBe(20)
55
55
  })
56
56
 
57
- it("Row and Column", () => {
57
+ it('Row and Column', () => {
58
58
  const row = Row({
59
59
  gap: 10,
60
- children: [Column({ width: "50%", children: "left" })],
60
+ children: [Column({ width: '50%', children: 'left' })],
61
61
  })
62
- expect(row.type).toBe("row")
62
+ expect(row.type).toBe('row')
63
63
  expect(row.children).toHaveLength(1)
64
64
  const col = row.children[0]!
65
- expect(typeof col).not.toBe("string")
66
- if (typeof col !== "string") {
67
- expect(col.type).toBe("column")
68
- expect(col.props.width).toBe("50%")
65
+ expect(typeof col).not.toBe('string')
66
+ if (typeof col !== 'string') {
67
+ expect(col.type).toBe('column')
68
+ expect(col.props.width).toBe('50%')
69
69
  }
70
70
  })
71
71
 
72
- it("Heading defaults to level 1", () => {
73
- const h = Heading({ children: "Title" })
74
- expect(h.type).toBe("heading")
72
+ it('Heading defaults to level 1', () => {
73
+ const h = Heading({ children: 'Title' })
74
+ expect(h.type).toBe('heading')
75
75
  expect(h.props.level).toBe(1)
76
76
  })
77
77
 
78
- it("Heading with custom level", () => {
79
- const h = Heading({ level: 3, children: "Subtitle" })
78
+ it('Heading with custom level', () => {
79
+ const h = Heading({ level: 3, children: 'Subtitle' })
80
80
  expect(h.props.level).toBe(3)
81
81
  })
82
82
 
83
- it("Text with formatting", () => {
83
+ it('Text with formatting', () => {
84
84
  const t = Text({
85
85
  bold: true,
86
86
  italic: true,
87
87
  size: 14,
88
- color: "#333",
89
- children: "hello",
88
+ color: '#333',
89
+ children: 'hello',
90
90
  })
91
- expect(t.type).toBe("text")
91
+ expect(t.type).toBe('text')
92
92
  expect(t.props.bold).toBe(true)
93
93
  expect(t.props.italic).toBe(true)
94
94
  expect(t.props.size).toBe(14)
95
95
  })
96
96
 
97
- it("Link", () => {
98
- const l = Link({ href: "https://example.com", children: "click" })
99
- expect(l.type).toBe("link")
100
- expect(l.props.href).toBe("https://example.com")
97
+ it('Link', () => {
98
+ const l = Link({ href: 'https://example.com', children: 'click' })
99
+ expect(l.type).toBe('link')
100
+ expect(l.props.href).toBe('https://example.com')
101
101
  })
102
102
 
103
- it("Image with all props", () => {
103
+ it('Image with all props', () => {
104
104
  const img = Image({
105
- src: "/logo.png",
105
+ src: '/logo.png',
106
106
  width: 100,
107
107
  height: 50,
108
- alt: "Logo",
109
- caption: "Company logo",
108
+ alt: 'Logo',
109
+ caption: 'Company logo',
110
110
  })
111
- expect(img.type).toBe("image")
112
- expect(img.props.src).toBe("/logo.png")
111
+ expect(img.type).toBe('image')
112
+ expect(img.props.src).toBe('/logo.png')
113
113
  expect(img.props.width).toBe(100)
114
- expect(img.props.caption).toBe("Company logo")
114
+ expect(img.props.caption).toBe('Company logo')
115
115
  expect(img.children).toEqual([])
116
116
  })
117
117
 
118
- it("Table with columns and rows", () => {
118
+ it('Table with columns and rows', () => {
119
119
  const t = Table({
120
- columns: ["Name", { header: "Price", align: "right" }],
121
- rows: [["Widget", "$10"]],
120
+ columns: ['Name', { header: 'Price', align: 'right' }],
121
+ rows: [['Widget', '$10']],
122
122
  striped: true,
123
123
  })
124
- expect(t.type).toBe("table")
124
+ expect(t.type).toBe('table')
125
125
  expect(t.props.columns).toHaveLength(2)
126
126
  expect(t.props.rows).toHaveLength(1)
127
127
  expect(t.props.striped).toBe(true)
128
128
  })
129
129
 
130
- it("List with items", () => {
130
+ it('List with items', () => {
131
131
  const l = List({
132
132
  ordered: true,
133
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
133
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
134
134
  })
135
- expect(l.type).toBe("list")
135
+ expect(l.type).toBe('list')
136
136
  expect(l.props.ordered).toBe(true)
137
137
  expect(l.children).toHaveLength(2)
138
138
  })
139
139
 
140
- it("Code", () => {
141
- const c = Code({ language: "typescript", children: "const x = 1" })
142
- expect(c.type).toBe("code")
143
- expect(c.props.language).toBe("typescript")
140
+ it('Code', () => {
141
+ const c = Code({ language: 'typescript', children: 'const x = 1' })
142
+ expect(c.type).toBe('code')
143
+ expect(c.props.language).toBe('typescript')
144
144
  })
145
145
 
146
- it("Divider", () => {
147
- const d = Divider({ color: "#ccc", thickness: 2 })
148
- expect(d.type).toBe("divider")
149
- expect(d.props.color).toBe("#ccc")
146
+ it('Divider', () => {
147
+ const d = Divider({ color: '#ccc', thickness: 2 })
148
+ expect(d.type).toBe('divider')
149
+ expect(d.props.color).toBe('#ccc')
150
150
  })
151
151
 
152
- it("Divider with defaults", () => {
152
+ it('Divider with defaults', () => {
153
153
  const d = Divider()
154
- expect(d.type).toBe("divider")
154
+ expect(d.type).toBe('divider')
155
155
  })
156
156
 
157
- it("Spacer", () => {
157
+ it('Spacer', () => {
158
158
  const s = Spacer({ height: 30 })
159
- expect(s.type).toBe("spacer")
159
+ expect(s.type).toBe('spacer')
160
160
  expect(s.props.height).toBe(30)
161
161
  })
162
162
 
163
- it("Button", () => {
164
- const b = Button({ href: "/pay", background: "#4f46e5", children: "Pay" })
165
- expect(b.type).toBe("button")
166
- expect(b.props.href).toBe("/pay")
167
- expect(b.props.background).toBe("#4f46e5")
163
+ it('Button', () => {
164
+ const b = Button({ href: '/pay', background: '#4f46e5', children: 'Pay' })
165
+ expect(b.type).toBe('button')
166
+ expect(b.props.href).toBe('/pay')
167
+ expect(b.props.background).toBe('#4f46e5')
168
168
  })
169
169
 
170
- it("Quote", () => {
171
- const q = Quote({ borderColor: "#blue", children: "wise words" })
172
- expect(q.type).toBe("quote")
173
- expect(q.props.borderColor).toBe("#blue")
170
+ it('Quote', () => {
171
+ const q = Quote({ borderColor: '#blue', children: 'wise words' })
172
+ expect(q.type).toBe('quote')
173
+ expect(q.props.borderColor).toBe('#blue')
174
174
  })
175
175
 
176
- it("isDocNode returns true for nodes", () => {
177
- expect(isDocNode(Heading({ children: "hi" }))).toBe(true)
176
+ it('isDocNode returns true for nodes', () => {
177
+ expect(isDocNode(Heading({ children: 'hi' }))).toBe(true)
178
178
  })
179
179
 
180
- it("isDocNode returns false for non-nodes", () => {
181
- expect(isDocNode("string")).toBe(false)
180
+ it('isDocNode returns false for non-nodes', () => {
181
+ expect(isDocNode('string')).toBe(false)
182
182
  expect(isDocNode(null)).toBe(false)
183
183
  expect(isDocNode(42)).toBe(false)
184
184
  expect(isDocNode({})).toBe(false)
185
185
  })
186
186
 
187
- it("normalizes nested children", () => {
187
+ it('normalizes nested children', () => {
188
188
  const doc = Document({
189
- children: [Heading({ children: "A" }), [Text({ children: "B" }), Text({ children: "C" })]],
189
+ children: [Heading({ children: 'A' }), [Text({ children: 'B' }), Text({ children: 'C' })]],
190
190
  })
191
191
  expect(doc.children).toHaveLength(3)
192
192
  })
193
193
 
194
- it("handles null/undefined/false children", () => {
195
- const doc = Document({ children: [null, undefined, false, "text"] })
196
- expect(doc.children).toEqual(["text"])
194
+ it('handles null/undefined/false children', () => {
195
+ const doc = Document({ children: [null, undefined, false, 'text'] })
196
+ expect(doc.children).toEqual(['text'])
197
197
  })
198
198
 
199
- it("converts numbers to strings in children", () => {
199
+ it('converts numbers to strings in children', () => {
200
200
  const t = Text({ children: 42 as unknown as string })
201
- expect(t.children).toEqual(["42"])
201
+ expect(t.children).toEqual(['42'])
202
202
  })
203
203
  })
204
204
 
205
205
  // ─── HTML Renderer ──────────────────────────────────────────────────────────
206
206
 
207
- describe("HTML renderer", () => {
208
- it("renders a simple document", async () => {
207
+ describe('HTML renderer', () => {
208
+ it('renders a simple document', async () => {
209
209
  const doc = Document({
210
- title: "Test",
211
- children: Page({ children: Heading({ children: "Hello" }) }),
210
+ title: 'Test',
211
+ children: Page({ children: Heading({ children: 'Hello' }) }),
212
212
  })
213
- const html = (await render(doc, "html")) as string
214
- expect(html).toContain("<!DOCTYPE html>")
215
- expect(html).toContain("<title>Test</title>")
216
- expect(html).toContain("<h1")
217
- expect(html).toContain("Hello")
213
+ const html = (await render(doc, 'html')) as string
214
+ expect(html).toContain('<!DOCTYPE html>')
215
+ expect(html).toContain('<title>Test</title>')
216
+ expect(html).toContain('<h1')
217
+ expect(html).toContain('Hello')
218
218
  })
219
219
 
220
- it("renders text with formatting", async () => {
220
+ it('renders text with formatting', async () => {
221
221
  const doc = Document({
222
222
  children: Text({
223
223
  bold: true,
224
- color: "#f00",
224
+ color: '#f00',
225
225
  size: 20,
226
- align: "center",
227
- children: "Bold Red",
226
+ align: 'center',
227
+ children: 'Bold Red',
228
228
  }),
229
229
  })
230
- const html = (await render(doc, "html")) as string
231
- expect(html).toContain("font-weight:bold")
232
- expect(html).toContain("color:#f00")
233
- expect(html).toContain("font-size:20px")
234
- expect(html).toContain("text-align:center")
230
+ const html = (await render(doc, 'html')) as string
231
+ expect(html).toContain('font-weight:bold')
232
+ expect(html).toContain('color:#f00')
233
+ expect(html).toContain('font-size:20px')
234
+ expect(html).toContain('text-align:center')
235
235
  })
236
236
 
237
- it("renders a table", async () => {
237
+ it('renders a table', async () => {
238
238
  const doc = Document({
239
239
  children: Table({
240
- columns: ["Name", { header: "Price", align: "right" }],
240
+ columns: ['Name', { header: 'Price', align: 'right' }],
241
241
  rows: [
242
- ["Widget", "$10"],
243
- ["Gadget", "$20"],
242
+ ['Widget', '$10'],
243
+ ['Gadget', '$20'],
244
244
  ],
245
245
  striped: true,
246
- headerStyle: { background: "#000", color: "#fff" },
246
+ headerStyle: { background: '#000', color: '#fff' },
247
247
  }),
248
248
  })
249
- const html = (await render(doc, "html")) as string
250
- expect(html).toContain("<table")
251
- expect(html).toContain("Widget")
252
- expect(html).toContain("$10")
253
- expect(html).toContain("background:#000")
254
- expect(html).toContain("color:#fff")
249
+ const html = (await render(doc, 'html')) as string
250
+ expect(html).toContain('<table')
251
+ expect(html).toContain('Widget')
252
+ expect(html).toContain('$10')
253
+ expect(html).toContain('background:#000')
254
+ expect(html).toContain('color:#fff')
255
255
  })
256
256
 
257
- it("renders an image with caption", async () => {
257
+ it('renders an image with caption', async () => {
258
258
  const doc = Document({
259
259
  children: Image({
260
- src: "/img.png",
260
+ src: '/img.png',
261
261
  width: 200,
262
- alt: "Photo",
263
- caption: "A photo",
262
+ alt: 'Photo',
263
+ caption: 'A photo',
264
264
  }),
265
265
  })
266
- const html = (await render(doc, "html")) as string
267
- expect(html).toContain("<img")
266
+ const html = (await render(doc, 'html')) as string
267
+ expect(html).toContain('<img')
268
268
  expect(html).toContain('src="/img.png"')
269
- expect(html).toContain("<figcaption>")
270
- expect(html).toContain("A photo")
269
+ expect(html).toContain('<figcaption>')
270
+ expect(html).toContain('A photo')
271
271
  })
272
272
 
273
- it("renders a link", async () => {
273
+ it('renders a link', async () => {
274
274
  const doc = Document({
275
- children: Link({ href: "https://example.com", children: "Click me" }),
275
+ children: Link({ href: 'https://example.com', children: 'Click me' }),
276
276
  })
277
- const html = (await render(doc, "html")) as string
277
+ const html = (await render(doc, 'html')) as string
278
278
  expect(html).toContain('href="https://example.com"')
279
- expect(html).toContain("Click me")
279
+ expect(html).toContain('Click me')
280
280
  })
281
281
 
282
- it("renders a list", async () => {
282
+ it('renders a list', async () => {
283
283
  const doc = Document({
284
284
  children: List({
285
285
  ordered: true,
286
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
286
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
287
287
  }),
288
288
  })
289
- const html = (await render(doc, "html")) as string
290
- expect(html).toContain("<ol>")
291
- expect(html).toContain("<li>one</li>")
289
+ const html = (await render(doc, 'html')) as string
290
+ expect(html).toContain('<ol>')
291
+ expect(html).toContain('<li>one</li>')
292
292
  })
293
293
 
294
- it("renders code blocks", async () => {
295
- const doc = Document({ children: Code({ children: "const x = 1" }) })
296
- const html = (await render(doc, "html")) as string
297
- expect(html).toContain("<pre")
298
- expect(html).toContain("<code>")
299
- expect(html).toContain("const x = 1")
294
+ it('renders code blocks', async () => {
295
+ const doc = Document({ children: Code({ children: 'const x = 1' }) })
296
+ const html = (await render(doc, 'html')) as string
297
+ expect(html).toContain('<pre')
298
+ expect(html).toContain('<code>')
299
+ expect(html).toContain('const x = 1')
300
300
  })
301
301
 
302
- it("renders divider", async () => {
303
- const doc = Document({ children: Divider({ color: "#ccc", thickness: 2 }) })
304
- const html = (await render(doc, "html")) as string
305
- expect(html).toContain("<hr")
306
- expect(html).toContain("2px solid #ccc")
302
+ it('renders divider', async () => {
303
+ const doc = Document({ children: Divider({ color: '#ccc', thickness: 2 }) })
304
+ const html = (await render(doc, 'html')) as string
305
+ expect(html).toContain('<hr')
306
+ expect(html).toContain('2px solid #ccc')
307
307
  })
308
308
 
309
- it("renders spacer", async () => {
309
+ it('renders spacer', async () => {
310
310
  const doc = Document({ children: Spacer({ height: 30 }) })
311
- const html = (await render(doc, "html")) as string
312
- expect(html).toContain("height:30px")
311
+ const html = (await render(doc, 'html')) as string
312
+ expect(html).toContain('height:30px')
313
313
  })
314
314
 
315
- it("renders button", async () => {
315
+ it('renders button', async () => {
316
316
  const doc = Document({
317
317
  children: Button({
318
- href: "/pay",
319
- background: "#4f46e5",
320
- color: "#fff",
321
- children: "Pay",
318
+ href: '/pay',
319
+ background: '#4f46e5',
320
+ color: '#fff',
321
+ children: 'Pay',
322
322
  }),
323
323
  })
324
- const html = (await render(doc, "html")) as string
324
+ const html = (await render(doc, 'html')) as string
325
325
  expect(html).toContain('href="/pay"')
326
- expect(html).toContain("background:#4f46e5")
327
- expect(html).toContain("Pay")
326
+ expect(html).toContain('background:#4f46e5')
327
+ expect(html).toContain('Pay')
328
328
  })
329
329
 
330
- it("renders blockquote", async () => {
331
- const doc = Document({ children: Quote({ children: "A wise quote" }) })
332
- const html = (await render(doc, "html")) as string
333
- expect(html).toContain("<blockquote")
334
- expect(html).toContain("A wise quote")
330
+ it('renders blockquote', async () => {
331
+ const doc = Document({ children: Quote({ children: 'A wise quote' }) })
332
+ const html = (await render(doc, 'html')) as string
333
+ expect(html).toContain('<blockquote')
334
+ expect(html).toContain('A wise quote')
335
335
  })
336
336
 
337
- it("renders section with row direction", async () => {
337
+ it('renders section with row direction', async () => {
338
338
  const doc = Document({
339
339
  children: Section({
340
- direction: "row",
340
+ direction: 'row',
341
341
  gap: 20,
342
- background: "#f5f5f5",
343
- children: [Text({ children: "A" }), Text({ children: "B" })],
342
+ background: '#f5f5f5',
343
+ children: [Text({ children: 'A' }), Text({ children: 'B' })],
344
344
  }),
345
345
  })
346
- const html = (await render(doc, "html")) as string
347
- expect(html).toContain("display:flex")
348
- expect(html).toContain("flex-direction:row")
346
+ const html = (await render(doc, 'html')) as string
347
+ expect(html).toContain('display:flex')
348
+ expect(html).toContain('flex-direction:row')
349
349
  })
350
350
 
351
- it("renders image with center alignment", async () => {
351
+ it('renders image with center alignment', async () => {
352
352
  const doc = Document({
353
- children: Image({ src: "/img.png", align: "center" }),
353
+ children: Image({ src: '/img.png', align: 'center' }),
354
354
  })
355
- const html = (await render(doc, "html")) as string
356
- expect(html).toContain("margin:0 auto")
355
+ const html = (await render(doc, 'html')) as string
356
+ expect(html).toContain('margin:0 auto')
357
357
  })
358
358
 
359
- it("renders table with bordered option", async () => {
359
+ it('renders table with bordered option', async () => {
360
360
  const doc = Document({
361
- children: Table({ columns: ["A"], rows: [["1"]], bordered: true }),
361
+ children: Table({ columns: ['A'], rows: [['1']], bordered: true }),
362
362
  })
363
- const html = (await render(doc, "html")) as string
364
- expect(html).toContain("border:1px solid #ddd")
363
+ const html = (await render(doc, 'html')) as string
364
+ expect(html).toContain('border:1px solid #ddd')
365
365
  })
366
366
 
367
- it("renders table with caption", async () => {
367
+ it('renders table with caption', async () => {
368
368
  const doc = Document({
369
- children: Table({ columns: ["A"], rows: [["1"]], caption: "My Table" }),
369
+ children: Table({ columns: ['A'], rows: [['1']], caption: 'My Table' }),
370
370
  })
371
- const html = (await render(doc, "html")) as string
372
- expect(html).toContain("<caption>My Table</caption>")
371
+ const html = (await render(doc, 'html')) as string
372
+ expect(html).toContain('<caption>My Table</caption>')
373
373
  })
374
374
 
375
- it("escapes HTML in text", async () => {
375
+ it('escapes HTML in text', async () => {
376
376
  const doc = Document({
377
- children: Text({ children: "<script>alert(1)</script>" }),
377
+ children: Text({ children: '<script>alert(1)</script>' }),
378
378
  })
379
- const html = (await render(doc, "html")) as string
380
- expect(html).not.toContain("<script>")
381
- expect(html).toContain("&lt;script&gt;")
379
+ const html = (await render(doc, 'html')) as string
380
+ expect(html).not.toContain('<script>')
381
+ expect(html).toContain('&lt;script&gt;')
382
382
  })
383
383
 
384
- it("renders text with underline and strikethrough", async () => {
384
+ it('renders text with underline and strikethrough', async () => {
385
385
  const ul = Document({
386
- children: Text({ underline: true, children: "underlined" }),
386
+ children: Text({ underline: true, children: 'underlined' }),
387
387
  })
388
388
  const st = Document({
389
- children: Text({ strikethrough: true, children: "struck" }),
389
+ children: Text({ strikethrough: true, children: 'struck' }),
390
390
  })
391
- expect((await render(ul, "html")) as string).toContain("text-decoration:underline")
392
- expect((await render(st, "html")) as string).toContain("text-decoration:line-through")
391
+ expect((await render(ul, 'html')) as string).toContain('text-decoration:underline')
392
+ expect((await render(st, 'html')) as string).toContain('text-decoration:line-through')
393
393
  })
394
394
 
395
- it("renders image with right alignment", async () => {
396
- const doc = Document({ children: Image({ src: "/x.png", align: "right" }) })
397
- const html = (await render(doc, "html")) as string
398
- expect(html).toContain("margin-left:auto")
395
+ it('renders image with right alignment', async () => {
396
+ const doc = Document({ children: Image({ src: '/x.png', align: 'right' }) })
397
+ const html = (await render(doc, 'html')) as string
398
+ expect(html).toContain('margin-left:auto')
399
399
  })
400
400
  })
401
401
 
402
402
  // ─── Email Renderer ─────────────────────────────────────────────────────────
403
403
 
404
- describe("email renderer", () => {
405
- it("renders email-safe HTML", async () => {
404
+ describe('email renderer', () => {
405
+ it('renders email-safe HTML', async () => {
406
406
  const doc = Document({
407
- title: "Welcome",
408
- children: [Heading({ children: "Hello!" }), Text({ children: "Welcome to our service." })],
407
+ title: 'Welcome',
408
+ children: [Heading({ children: 'Hello!' }), Text({ children: 'Welcome to our service.' })],
409
409
  })
410
- const html = (await render(doc, "email")) as string
411
- expect(html).toContain("<!DOCTYPE html>")
412
- expect(html).toContain("max-width:600px")
413
- expect(html).toContain("Hello!")
410
+ const html = (await render(doc, 'email')) as string
411
+ expect(html).toContain('<!DOCTYPE html>')
412
+ expect(html).toContain('max-width:600px')
413
+ expect(html).toContain('Hello!')
414
414
  // Should have Outlook conditional comments
415
- expect(html).toContain("<!--[if mso]>")
415
+ expect(html).toContain('<!--[if mso]>')
416
416
  })
417
417
 
418
- it("renders bulletproof buttons", async () => {
418
+ it('renders bulletproof buttons', async () => {
419
419
  const doc = Document({
420
420
  children: Button({
421
- href: "/pay",
422
- background: "#4f46e5",
423
- children: "Pay Now",
421
+ href: '/pay',
422
+ background: '#4f46e5',
423
+ children: 'Pay Now',
424
424
  }),
425
425
  })
426
- const html = (await render(doc, "email")) as string
426
+ const html = (await render(doc, 'email')) as string
427
427
  // VML for Outlook
428
- expect(html).toContain("v:roundrect")
428
+ expect(html).toContain('v:roundrect')
429
429
  // CSS for others
430
- expect(html).toContain("background-color:#4f46e5")
431
- expect(html).toContain("Pay Now")
430
+ expect(html).toContain('background-color:#4f46e5')
431
+ expect(html).toContain('Pay Now')
432
432
  })
433
433
 
434
- it("renders table with inline styles", async () => {
434
+ it('renders table with inline styles', async () => {
435
435
  const doc = Document({
436
436
  children: Table({
437
- columns: ["Name", "Price"],
438
- rows: [["Widget", "$10"]],
439
- headerStyle: { background: "#000", color: "#fff" },
437
+ columns: ['Name', 'Price'],
438
+ rows: [['Widget', '$10']],
439
+ headerStyle: { background: '#000', color: '#fff' },
440
440
  }),
441
441
  })
442
- const html = (await render(doc, "email")) as string
443
- expect(html).toContain("background-color:#000")
444
- expect(html).toContain("color:#fff")
445
- expect(html).toContain("Widget")
442
+ const html = (await render(doc, 'email')) as string
443
+ expect(html).toContain('background-color:#000')
444
+ expect(html).toContain('color:#fff')
445
+ expect(html).toContain('Widget')
446
446
  })
447
447
 
448
- it("renders section with row direction using tables", async () => {
448
+ it('renders section with row direction using tables', async () => {
449
449
  const doc = Document({
450
450
  children: Section({
451
- direction: "row",
452
- children: [Text({ children: "Left" }), Text({ children: "Right" })],
451
+ direction: 'row',
452
+ children: [Text({ children: 'Left' }), Text({ children: 'Right' })],
453
453
  }),
454
454
  })
455
- const html = (await render(doc, "email")) as string
455
+ const html = (await render(doc, 'email')) as string
456
456
  // Should use table layout, not flexbox
457
- expect(html).not.toContain("display:flex")
458
- expect(html).toContain("<table")
459
- expect(html).toContain("Left")
460
- expect(html).toContain("Right")
457
+ expect(html).not.toContain('display:flex')
458
+ expect(html).toContain('<table')
459
+ expect(html).toContain('Left')
460
+ expect(html).toContain('Right')
461
461
  })
462
462
 
463
- it("renders divider using table", async () => {
463
+ it('renders divider using table', async () => {
464
464
  const doc = Document({ children: Divider() })
465
- const html = (await render(doc, "email")) as string
466
- expect(html).toContain("border-top:1px solid")
465
+ const html = (await render(doc, 'email')) as string
466
+ expect(html).toContain('border-top:1px solid')
467
467
  })
468
468
 
469
- it("renders quote using table with border-left", async () => {
470
- const doc = Document({ children: Quote({ children: "A quote" }) })
471
- const html = (await render(doc, "email")) as string
472
- expect(html).toContain("border-left:4px solid")
469
+ it('renders quote using table with border-left', async () => {
470
+ const doc = Document({ children: Quote({ children: 'A quote' }) })
471
+ const html = (await render(doc, 'email')) as string
472
+ expect(html).toContain('border-left:4px solid')
473
473
  })
474
474
  })
475
475
 
476
476
  // ─── Markdown Renderer ──────────────────────────────────────────────────────
477
477
 
478
- describe("markdown renderer", () => {
479
- it("renders headings with # prefix", async () => {
478
+ describe('markdown renderer', () => {
479
+ it('renders headings with # prefix', async () => {
480
480
  const doc = Document({
481
481
  children: [
482
- Heading({ level: 1, children: "Title" }),
483
- Heading({ level: 3, children: "Subtitle" }),
482
+ Heading({ level: 1, children: 'Title' }),
483
+ Heading({ level: 3, children: 'Subtitle' }),
484
484
  ],
485
485
  })
486
- const md = (await render(doc, "md")) as string
487
- expect(md).toContain("# Title")
488
- expect(md).toContain("### Subtitle")
486
+ const md = (await render(doc, 'md')) as string
487
+ expect(md).toContain('# Title')
488
+ expect(md).toContain('### Subtitle')
489
489
  })
490
490
 
491
- it("renders bold and italic text", async () => {
491
+ it('renders bold and italic text', async () => {
492
492
  const doc = Document({
493
493
  children: [
494
- Text({ bold: true, children: "bold" }),
495
- Text({ italic: true, children: "italic" }),
496
- Text({ strikethrough: true, children: "struck" }),
494
+ Text({ bold: true, children: 'bold' }),
495
+ Text({ italic: true, children: 'italic' }),
496
+ Text({ strikethrough: true, children: 'struck' }),
497
497
  ],
498
498
  })
499
- const md = (await render(doc, "md")) as string
500
- expect(md).toContain("**bold**")
501
- expect(md).toContain("*italic*")
502
- expect(md).toContain("~~struck~~")
499
+ const md = (await render(doc, 'md')) as string
500
+ expect(md).toContain('**bold**')
501
+ expect(md).toContain('*italic*')
502
+ expect(md).toContain('~~struck~~')
503
503
  })
504
504
 
505
- it("renders tables as pipe tables", async () => {
505
+ it('renders tables as pipe tables', async () => {
506
506
  const doc = Document({
507
507
  children: Table({
508
- columns: ["Name", { header: "Price", align: "right" }],
509
- rows: [["Widget", "$10"]],
508
+ columns: ['Name', { header: 'Price', align: 'right' }],
509
+ rows: [['Widget', '$10']],
510
510
  }),
511
511
  })
512
- const md = (await render(doc, "md")) as string
513
- expect(md).toContain("| Name | Price |")
514
- expect(md).toContain("| --- | ---: |")
515
- expect(md).toContain("| Widget | $10 |")
512
+ const md = (await render(doc, 'md')) as string
513
+ expect(md).toContain('| Name | Price |')
514
+ expect(md).toContain('| --- | ---: |')
515
+ expect(md).toContain('| Widget | $10 |')
516
516
  })
517
517
 
518
- it("renders links in markdown format", async () => {
518
+ it('renders links in markdown format', async () => {
519
519
  const doc = Document({
520
- children: Link({ href: "https://example.com", children: "click" }),
520
+ children: Link({ href: 'https://example.com', children: 'click' }),
521
521
  })
522
- const md = (await render(doc, "md")) as string
523
- expect(md).toContain("[click](https://example.com)")
522
+ const md = (await render(doc, 'md')) as string
523
+ expect(md).toContain('[click](https://example.com)')
524
524
  })
525
525
 
526
- it("renders images", async () => {
526
+ it('renders images', async () => {
527
527
  const doc = Document({
528
- children: Image({ src: "/img.png", alt: "Photo", caption: "A photo" }),
528
+ children: Image({ src: '/img.png', alt: 'Photo', caption: 'A photo' }),
529
529
  })
530
- const md = (await render(doc, "md")) as string
531
- expect(md).toContain("![Photo](/img.png)")
532
- expect(md).toContain("*A photo*")
530
+ const md = (await render(doc, 'md')) as string
531
+ expect(md).toContain('![Photo](/img.png)')
532
+ expect(md).toContain('*A photo*')
533
533
  })
534
534
 
535
- it("renders code blocks with language", async () => {
535
+ it('renders code blocks with language', async () => {
536
536
  const doc = Document({
537
- children: Code({ language: "typescript", children: "const x = 1" }),
537
+ children: Code({ language: 'typescript', children: 'const x = 1' }),
538
538
  })
539
- const md = (await render(doc, "md")) as string
540
- expect(md).toContain("```typescript")
541
- expect(md).toContain("const x = 1")
542
- expect(md).toContain("```")
539
+ const md = (await render(doc, 'md')) as string
540
+ expect(md).toContain('```typescript')
541
+ expect(md).toContain('const x = 1')
542
+ expect(md).toContain('```')
543
543
  })
544
544
 
545
- it("renders ordered and unordered lists", async () => {
545
+ it('renders ordered and unordered lists', async () => {
546
546
  const ol = Document({
547
547
  children: List({
548
548
  ordered: true,
549
- children: [ListItem({ children: "first" }), ListItem({ children: "second" })],
549
+ children: [ListItem({ children: 'first' }), ListItem({ children: 'second' })],
550
550
  }),
551
551
  })
552
552
  const ul = Document({
553
553
  children: List({
554
- children: [ListItem({ children: "a" }), ListItem({ children: "b" })],
554
+ children: [ListItem({ children: 'a' }), ListItem({ children: 'b' })],
555
555
  }),
556
556
  })
557
- const orderedMd = (await render(ol, "md")) as string
558
- const unorderedMd = (await render(ul, "md")) as string
559
- expect(orderedMd).toContain("1. first")
560
- expect(orderedMd).toContain("2. second")
561
- expect(unorderedMd).toContain("- a")
562
- expect(unorderedMd).toContain("- b")
557
+ const orderedMd = (await render(ol, 'md')) as string
558
+ const unorderedMd = (await render(ul, 'md')) as string
559
+ expect(orderedMd).toContain('1. first')
560
+ expect(orderedMd).toContain('2. second')
561
+ expect(unorderedMd).toContain('- a')
562
+ expect(unorderedMd).toContain('- b')
563
563
  })
564
564
 
565
- it("renders divider as ---", async () => {
565
+ it('renders divider as ---', async () => {
566
566
  const doc = Document({ children: Divider() })
567
- const md = (await render(doc, "md")) as string
568
- expect(md).toContain("---")
567
+ const md = (await render(doc, 'md')) as string
568
+ expect(md).toContain('---')
569
569
  })
570
570
 
571
- it("renders button as link", async () => {
571
+ it('renders button as link', async () => {
572
572
  const doc = Document({
573
- children: Button({ href: "/pay", children: "Pay" }),
573
+ children: Button({ href: '/pay', children: 'Pay' }),
574
574
  })
575
- const md = (await render(doc, "md")) as string
576
- expect(md).toContain("[Pay](/pay)")
575
+ const md = (await render(doc, 'md')) as string
576
+ expect(md).toContain('[Pay](/pay)')
577
577
  })
578
578
 
579
- it("renders quote with >", async () => {
580
- const doc = Document({ children: Quote({ children: "wise" }) })
581
- const md = (await render(doc, "md")) as string
582
- expect(md).toContain("> wise")
579
+ it('renders quote with >', async () => {
580
+ const doc = Document({ children: Quote({ children: 'wise' }) })
581
+ const md = (await render(doc, 'md')) as string
582
+ expect(md).toContain('> wise')
583
583
  })
584
584
 
585
- it("renders table with caption", async () => {
585
+ it('renders table with caption', async () => {
586
586
  const doc = Document({
587
- children: Table({ columns: ["A"], rows: [["1"]], caption: "My Table" }),
587
+ children: Table({ columns: ['A'], rows: [['1']], caption: 'My Table' }),
588
588
  })
589
- const md = (await render(doc, "md")) as string
590
- expect(md).toContain("*My Table*")
589
+ const md = (await render(doc, 'md')) as string
590
+ expect(md).toContain('*My Table*')
591
591
  })
592
592
  })
593
593
 
594
594
  // ─── Text Renderer ──────────────────────────────────────────────────────────
595
595
 
596
- describe("text renderer", () => {
597
- it("renders headings with underlines", async () => {
596
+ describe('text renderer', () => {
597
+ it('renders headings with underlines', async () => {
598
598
  const doc = Document({
599
- children: [Heading({ level: 1, children: "Title" }), Heading({ level: 2, children: "Sub" })],
599
+ children: [Heading({ level: 1, children: 'Title' }), Heading({ level: 2, children: 'Sub' })],
600
600
  })
601
- const text = (await render(doc, "text")) as string
602
- expect(text).toContain("TITLE")
603
- expect(text).toContain("=====")
604
- expect(text).toContain("Sub")
605
- expect(text).toContain("---")
601
+ const text = (await render(doc, 'text')) as string
602
+ expect(text).toContain('TITLE')
603
+ expect(text).toContain('=====')
604
+ expect(text).toContain('Sub')
605
+ expect(text).toContain('---')
606
606
  })
607
607
 
608
- it("renders aligned table columns", async () => {
608
+ it('renders aligned table columns', async () => {
609
609
  const doc = Document({
610
610
  children: Table({
611
611
  columns: [
612
- { header: "Name", align: "left" },
613
- { header: "Price", align: "right" },
612
+ { header: 'Name', align: 'left' },
613
+ { header: 'Price', align: 'right' },
614
614
  ],
615
- rows: [["Widget", "$10"]],
615
+ rows: [['Widget', '$10']],
616
616
  }),
617
617
  })
618
- const text = (await render(doc, "text")) as string
619
- expect(text).toContain("Name")
620
- expect(text).toContain("Price")
621
- expect(text).toContain("Widget")
618
+ const text = (await render(doc, 'text')) as string
619
+ expect(text).toContain('Name')
620
+ expect(text).toContain('Price')
621
+ expect(text).toContain('Widget')
622
622
  })
623
623
 
624
- it("renders button as link reference", async () => {
624
+ it('renders button as link reference', async () => {
625
625
  const doc = Document({
626
- children: Button({ href: "/pay", children: "Pay" }),
626
+ children: Button({ href: '/pay', children: 'Pay' }),
627
627
  })
628
- const text = (await render(doc, "text")) as string
629
- expect(text).toContain("[Pay]")
630
- expect(text).toContain("/pay")
628
+ const text = (await render(doc, 'text')) as string
629
+ expect(text).toContain('[Pay]')
630
+ expect(text).toContain('/pay')
631
631
  })
632
632
 
633
- it("renders image as placeholder", async () => {
633
+ it('renders image as placeholder', async () => {
634
634
  const doc = Document({
635
- children: Image({ src: "/x.png", alt: "Photo", caption: "Nice" }),
635
+ children: Image({ src: '/x.png', alt: 'Photo', caption: 'Nice' }),
636
636
  })
637
- const text = (await render(doc, "text")) as string
638
- expect(text).toContain("[Photo — Nice]")
637
+ const text = (await render(doc, 'text')) as string
638
+ expect(text).toContain('[Photo — Nice]')
639
639
  })
640
640
  })
641
641
 
642
642
  // ─── CSV Renderer ───────────────────────────────────────────────────────────
643
643
 
644
- describe("CSV renderer", () => {
645
- it("extracts tables as CSV", async () => {
644
+ describe('CSV renderer', () => {
645
+ it('extracts tables as CSV', async () => {
646
646
  const doc = Document({
647
647
  children: Table({
648
- columns: ["Name", "Price"],
648
+ columns: ['Name', 'Price'],
649
649
  rows: [
650
- ["Widget", "$10"],
651
- ["Gadget", "$20"],
650
+ ['Widget', '$10'],
651
+ ['Gadget', '$20'],
652
652
  ],
653
653
  }),
654
654
  })
655
- const csv = (await render(doc, "csv")) as string
656
- expect(csv).toContain("Name,Price")
657
- expect(csv).toContain("Widget,$10")
658
- expect(csv).toContain("Gadget,$20")
655
+ const csv = (await render(doc, 'csv')) as string
656
+ expect(csv).toContain('Name,Price')
657
+ expect(csv).toContain('Widget,$10')
658
+ expect(csv).toContain('Gadget,$20')
659
659
  })
660
660
 
661
- it("escapes commas and quotes", async () => {
661
+ it('escapes commas and quotes', async () => {
662
662
  const doc = Document({
663
663
  children: Table({
664
- columns: ["Name"],
665
- rows: [["Widget, Inc."], ['He said "hello"']],
664
+ columns: ['Name'],
665
+ rows: [['Widget, Inc.'], ['He said "hello"']],
666
666
  }),
667
667
  })
668
- const csv = (await render(doc, "csv")) as string
668
+ const csv = (await render(doc, 'csv')) as string
669
669
  expect(csv).toContain('"Widget, Inc."')
670
670
  expect(csv).toContain('"He said ""hello"""')
671
671
  })
672
672
 
673
- it("returns message when no tables", async () => {
674
- const doc = Document({ children: Text({ children: "no tables here" }) })
675
- const csv = (await render(doc, "csv")) as string
676
- expect(csv).toContain("No tables found")
673
+ it('returns message when no tables', async () => {
674
+ const doc = Document({ children: Text({ children: 'no tables here' }) })
675
+ const csv = (await render(doc, 'csv')) as string
676
+ expect(csv).toContain('No tables found')
677
677
  })
678
678
 
679
- it("handles multiple tables", async () => {
679
+ it('handles multiple tables', async () => {
680
680
  const doc = Document({
681
681
  children: [
682
- Table({ columns: ["A"], rows: [["1"]] }),
683
- Table({ columns: ["B"], rows: [["2"]] }),
682
+ Table({ columns: ['A'], rows: [['1']] }),
683
+ Table({ columns: ['B'], rows: [['2']] }),
684
684
  ],
685
685
  })
686
- const csv = (await render(doc, "csv")) as string
687
- expect(csv).toContain("A")
688
- expect(csv).toContain("B")
686
+ const csv = (await render(doc, 'csv')) as string
687
+ expect(csv).toContain('A')
688
+ expect(csv).toContain('B')
689
689
  })
690
690
 
691
- it("adds caption as comment", async () => {
691
+ it('adds caption as comment', async () => {
692
692
  const doc = Document({
693
- children: Table({ columns: ["A"], rows: [["1"]], caption: "My Data" }),
693
+ children: Table({ columns: ['A'], rows: [['1']], caption: 'My Data' }),
694
694
  })
695
- const csv = (await render(doc, "csv")) as string
696
- expect(csv).toContain("# My Data")
695
+ const csv = (await render(doc, 'csv')) as string
696
+ expect(csv).toContain('# My Data')
697
697
  })
698
698
  })
699
699
 
700
700
  // ─── Builder Pattern ────────────────────────────────────────────────────────
701
701
 
702
- describe("createDocument builder", () => {
703
- it("builds a document with heading and text", async () => {
704
- const doc = createDocument({ title: "Test" }).heading("Title").text("Hello world")
702
+ describe('createDocument builder', () => {
703
+ it('builds a document with heading and text', async () => {
704
+ const doc = createDocument({ title: 'Test' }).heading('Title').text('Hello world')
705
705
 
706
706
  const node = doc.build()
707
- expect(node.type).toBe("document")
708
- expect(node.props.title).toBe("Test")
707
+ expect(node.type).toBe('document')
708
+ expect(node.props.title).toBe('Test')
709
709
  })
710
710
 
711
- it("renders to HTML", async () => {
711
+ it('renders to HTML', async () => {
712
712
  const doc = createDocument()
713
- .heading("Report")
714
- .text("Summary text")
713
+ .heading('Report')
714
+ .text('Summary text')
715
715
  .table({
716
- columns: ["Name", "Value"],
717
- rows: [["A", "1"]],
716
+ columns: ['Name', 'Value'],
717
+ rows: [['A', '1']],
718
718
  })
719
719
 
720
720
  const html = await doc.toHtml()
721
- expect(html).toContain("Report")
722
- expect(html).toContain("Summary text")
723
- expect(html).toContain("<table")
721
+ expect(html).toContain('Report')
722
+ expect(html).toContain('Summary text')
723
+ expect(html).toContain('<table')
724
724
  })
725
725
 
726
- it("renders to markdown", async () => {
726
+ it('renders to markdown', async () => {
727
727
  const doc = createDocument()
728
- .heading("Title")
729
- .text("Body", { bold: true })
730
- .list(["item 1", "item 2"])
728
+ .heading('Title')
729
+ .text('Body', { bold: true })
730
+ .list(['item 1', 'item 2'])
731
731
 
732
732
  const md = await doc.toMarkdown()
733
- expect(md).toContain("# Title")
734
- expect(md).toContain("**Body**")
735
- expect(md).toContain("- item 1")
733
+ expect(md).toContain('# Title')
734
+ expect(md).toContain('**Body**')
735
+ expect(md).toContain('- item 1')
736
736
  })
737
737
 
738
- it("renders to text", async () => {
739
- const doc = createDocument().heading("Title").text("Body")
738
+ it('renders to text', async () => {
739
+ const doc = createDocument().heading('Title').text('Body')
740
740
 
741
741
  const text = await doc.toText()
742
- expect(text).toContain("TITLE")
743
- expect(text).toContain("Body")
742
+ expect(text).toContain('TITLE')
743
+ expect(text).toContain('Body')
744
744
  })
745
745
 
746
- it("renders to CSV", async () => {
747
- const doc = createDocument().table({ columns: ["X"], rows: [["1"], ["2"]] })
746
+ it('renders to CSV', async () => {
747
+ const doc = createDocument().table({ columns: ['X'], rows: [['1'], ['2']] })
748
748
 
749
749
  const csv = await doc.toCsv()
750
- expect(csv).toContain("X")
751
- expect(csv).toContain("1")
750
+ expect(csv).toContain('X')
751
+ expect(csv).toContain('1')
752
752
  })
753
753
 
754
- it("supports all builder methods", () => {
754
+ it('supports all builder methods', () => {
755
755
  const doc = createDocument()
756
- .heading("H")
757
- .text("T")
758
- .paragraph("P")
759
- .image("/img.png")
760
- .table({ columns: ["A"], rows: [["1"]] })
761
- .list(["a", "b"])
762
- .code("x = 1", { language: "python" })
756
+ .heading('H')
757
+ .text('T')
758
+ .paragraph('P')
759
+ .image('/img.png')
760
+ .table({ columns: ['A'], rows: [['1']] })
761
+ .list(['a', 'b'])
762
+ .code('x = 1', { language: 'python' })
763
763
  .divider()
764
764
  .spacer(20)
765
- .quote("Q")
766
- .button("Click", { href: "/go" })
767
- .link("Link", { href: "/link" })
765
+ .quote('Q')
766
+ .button('Click', { href: '/go' })
767
+ .link('Link', { href: '/link' })
768
768
 
769
769
  const node = doc.build()
770
- expect(node.type).toBe("document")
770
+ expect(node.type).toBe('document')
771
771
  })
772
772
 
773
- it("chart without instance shows placeholder", async () => {
773
+ it('chart without instance shows placeholder', async () => {
774
774
  const doc = createDocument().chart(null)
775
775
 
776
776
  const html = await doc.toHtml()
777
- expect(html).toContain("[Chart]")
777
+ expect(html).toContain('[Chart]')
778
778
  })
779
779
 
780
- it("flow without instance shows placeholder", async () => {
780
+ it('flow without instance shows placeholder', async () => {
781
781
  const doc = createDocument().flow(null)
782
782
 
783
783
  const html = await doc.toHtml()
784
- expect(html).toContain("[Flow Diagram]")
784
+ expect(html).toContain('[Flow Diagram]')
785
785
  })
786
786
 
787
- it("chart with getDataURL captures image", async () => {
787
+ it('chart with getDataURL captures image', async () => {
788
788
  const mockChart = {
789
- getDataURL: () => "data:image/png;base64,abc123",
789
+ getDataURL: () => 'data:image/png;base64,abc123',
790
790
  }
791
791
  const doc = createDocument().chart(mockChart, { width: 400 })
792
792
  const html = await doc.toHtml()
793
- expect(html).toContain("data:image/png;base64,abc123")
793
+ expect(html).toContain('data:image/png;base64,abc123')
794
794
  })
795
795
 
796
- it("flow with toSVG captures image", async () => {
796
+ it('flow with toSVG captures image', async () => {
797
797
  const mockFlow = {
798
- toSVG: () => "<svg><rect/></svg>",
798
+ toSVG: () => '<svg><rect/></svg>',
799
799
  }
800
800
  const doc = createDocument().flow(mockFlow, { width: 500 })
801
801
  const html = await doc.toHtml()
802
- expect(html).toContain("data:image/svg+xml")
802
+ expect(html).toContain('data:image/svg+xml')
803
803
  })
804
804
  })
805
805
 
806
806
  // ─── Custom Renderers ───────────────────────────────────────────────────────
807
807
 
808
- describe("custom renderers", () => {
809
- it("registerRenderer adds a custom format", async () => {
810
- registerRenderer("custom", {
808
+ describe('custom renderers', () => {
809
+ it('registerRenderer adds a custom format', async () => {
810
+ registerRenderer('custom', {
811
811
  async render(node) {
812
812
  return `CUSTOM:${node.type}`
813
813
  },
814
814
  })
815
815
 
816
- const doc = Document({ children: "hello" })
817
- const result = await render(doc, "custom")
818
- expect(result).toBe("CUSTOM:document")
816
+ const doc = Document({ children: 'hello' })
817
+ const result = await render(doc, 'custom')
818
+ expect(result).toBe('CUSTOM:document')
819
819
  })
820
820
 
821
- it("unregisterRenderer removes a format", () => {
822
- registerRenderer("temp", {
821
+ it('unregisterRenderer removes a format', () => {
822
+ registerRenderer('temp', {
823
823
  async render() {
824
- return "x"
824
+ return 'x'
825
825
  },
826
826
  })
827
- unregisterRenderer("temp")
828
- expect(render(Document({ children: "x" }), "temp")).rejects.toThrow("No renderer registered")
827
+ unregisterRenderer('temp')
828
+ expect(render(Document({ children: 'x' }), 'temp')).rejects.toThrow('No renderer registered')
829
829
  })
830
830
 
831
- it("throws for unknown format", () => {
832
- expect(render(Document({ children: "x" }), "unknown")).rejects.toThrow("No renderer registered")
831
+ it('throws for unknown format', () => {
832
+ expect(render(Document({ children: 'x' }), 'unknown')).rejects.toThrow('No renderer registered')
833
833
  })
834
834
 
835
- it("lazy renderer is cached after first use", async () => {
835
+ it('lazy renderer is cached after first use', async () => {
836
836
  let loadCount = 0
837
- registerRenderer("lazy", async () => {
837
+ registerRenderer('lazy', async () => {
838
838
  loadCount++
839
839
  return {
840
840
  async render() {
841
- return "lazy-result"
841
+ return 'lazy-result'
842
842
  },
843
843
  }
844
844
  })
845
845
 
846
- await render(Document({ children: "x" }), "lazy")
847
- await render(Document({ children: "x" }), "lazy")
846
+ await render(Document({ children: 'x' }), 'lazy')
847
+ await render(Document({ children: 'x' }), 'lazy')
848
848
  expect(loadCount).toBe(1)
849
849
  })
850
850
  })
851
851
 
852
852
  // ─── Real-World Document ────────────────────────────────────────────────────
853
853
 
854
- describe("real-world document", () => {
854
+ describe('real-world document', () => {
855
855
  function createInvoice() {
856
856
  return Document({
857
- title: "Invoice #1234",
858
- author: "Acme Corp",
857
+ title: 'Invoice #1234',
858
+ author: 'Acme Corp',
859
859
  children: Page({
860
- size: "A4",
860
+ size: 'A4',
861
861
  margin: 40,
862
862
  children: [
863
863
  Row({
864
864
  gap: 20,
865
865
  children: [
866
866
  Column({
867
- children: Image({ src: "/logo.png", width: 80, alt: "Logo" }),
867
+ children: Image({ src: '/logo.png', width: 80, alt: 'Logo' }),
868
868
  }),
869
869
  Column({
870
870
  children: [
871
- Heading({ children: "Invoice #1234" }),
872
- Text({ color: "#666", children: "March 23, 2026" }),
871
+ Heading({ children: 'Invoice #1234' }),
872
+ Text({ color: '#666', children: 'March 23, 2026' }),
873
873
  ],
874
874
  }),
875
875
  ],
@@ -877,544 +877,544 @@ describe("real-world document", () => {
877
877
  Spacer({ height: 30 }),
878
878
  Table({
879
879
  columns: [
880
- { header: "Item", width: "50%" },
881
- { header: "Qty", width: "15%", align: "center" },
882
- { header: "Price", width: "15%", align: "right" },
883
- { header: "Total", width: "20%", align: "right" },
880
+ { header: 'Item', width: '50%' },
881
+ { header: 'Qty', width: '15%', align: 'center' },
882
+ { header: 'Price', width: '15%', align: 'right' },
883
+ { header: 'Total', width: '20%', align: 'right' },
884
884
  ],
885
885
  rows: [
886
- ["Widget Pro", "2", "$50", "$100"],
887
- ["Gadget Plus", "1", "$75", "$75"],
888
- ["Service Fee", "1", "$25", "$25"],
886
+ ['Widget Pro', '2', '$50', '$100'],
887
+ ['Gadget Plus', '1', '$75', '$75'],
888
+ ['Service Fee', '1', '$25', '$25'],
889
889
  ],
890
890
  striped: true,
891
- headerStyle: { background: "#1a1a2e", color: "#fff" },
891
+ headerStyle: { background: '#1a1a2e', color: '#fff' },
892
892
  }),
893
893
  Spacer({ height: 20 }),
894
894
  Text({
895
895
  bold: true,
896
- align: "right",
896
+ align: 'right',
897
897
  size: 18,
898
- children: "Total: $200",
898
+ children: 'Total: $200',
899
899
  }),
900
900
  Divider(),
901
901
  Text({
902
- color: "#999",
902
+ color: '#999',
903
903
  size: 12,
904
- children: "Thank you for your business!",
904
+ children: 'Thank you for your business!',
905
905
  }),
906
906
  Button({
907
- href: "https://acme.com/pay/1234",
908
- background: "#4f46e5",
909
- align: "center",
910
- children: "Pay Now",
907
+ href: 'https://acme.com/pay/1234',
908
+ background: '#4f46e5',
909
+ align: 'center',
910
+ children: 'Pay Now',
911
911
  }),
912
912
  ],
913
913
  }),
914
914
  })
915
915
  }
916
916
 
917
- it("renders as HTML", async () => {
918
- const html = (await render(createInvoice(), "html")) as string
919
- expect(html).toContain("Invoice #1234")
920
- expect(html).toContain("Widget Pro")
921
- expect(html).toContain("Total: $200")
922
- expect(html).toContain("Pay Now")
917
+ it('renders as HTML', async () => {
918
+ const html = (await render(createInvoice(), 'html')) as string
919
+ expect(html).toContain('Invoice #1234')
920
+ expect(html).toContain('Widget Pro')
921
+ expect(html).toContain('Total: $200')
922
+ expect(html).toContain('Pay Now')
923
923
  })
924
924
 
925
- it("renders as email", async () => {
926
- const html = (await render(createInvoice(), "email")) as string
927
- expect(html).toContain("Invoice #1234")
928
- expect(html).toContain("max-width:600px")
929
- expect(html).toContain("v:roundrect") // Outlook button
925
+ it('renders as email', async () => {
926
+ const html = (await render(createInvoice(), 'email')) as string
927
+ expect(html).toContain('Invoice #1234')
928
+ expect(html).toContain('max-width:600px')
929
+ expect(html).toContain('v:roundrect') // Outlook button
930
930
  })
931
931
 
932
- it("renders as markdown", async () => {
933
- const md = (await render(createInvoice(), "md")) as string
934
- expect(md).toContain("# Invoice #1234")
935
- expect(md).toContain("| Widget Pro")
936
- expect(md).toContain("**Total: $200**")
932
+ it('renders as markdown', async () => {
933
+ const md = (await render(createInvoice(), 'md')) as string
934
+ expect(md).toContain('# Invoice #1234')
935
+ expect(md).toContain('| Widget Pro')
936
+ expect(md).toContain('**Total: $200**')
937
937
  })
938
938
 
939
- it("renders as text", async () => {
940
- const text = (await render(createInvoice(), "text")) as string
941
- expect(text).toContain("INVOICE #1234")
942
- expect(text).toContain("Widget Pro")
943
- expect(text).toContain("Total: $200")
939
+ it('renders as text', async () => {
940
+ const text = (await render(createInvoice(), 'text')) as string
941
+ expect(text).toContain('INVOICE #1234')
942
+ expect(text).toContain('Widget Pro')
943
+ expect(text).toContain('Total: $200')
944
944
  })
945
945
 
946
- it("renders as CSV", async () => {
947
- const csv = (await render(createInvoice(), "csv")) as string
948
- expect(csv).toContain("Item,Qty,Price,Total")
949
- expect(csv).toContain("Widget Pro,2,$50,$100")
946
+ it('renders as CSV', async () => {
947
+ const csv = (await render(createInvoice(), 'csv')) as string
948
+ expect(csv).toContain('Item,Qty,Price,Total')
949
+ expect(csv).toContain('Widget Pro,2,$50,$100')
950
950
  })
951
951
  })
952
952
 
953
953
  // ─── Text Renderer — additional coverage ────────────────────────────────────
954
954
 
955
- describe("text renderer — additional", () => {
956
- it("renders link with URL", async () => {
955
+ describe('text renderer — additional', () => {
956
+ it('renders link with URL', async () => {
957
957
  const doc = Document({
958
- children: Link({ href: "https://x.com", children: "Link" }),
958
+ children: Link({ href: 'https://x.com', children: 'Link' }),
959
959
  })
960
- const text = (await render(doc, "text")) as string
961
- expect(text).toContain("Link (https://x.com)")
960
+ const text = (await render(doc, 'text')) as string
961
+ expect(text).toContain('Link (https://x.com)')
962
962
  })
963
963
 
964
- it("renders table with caption", async () => {
964
+ it('renders table with caption', async () => {
965
965
  const doc = Document({
966
- children: Table({ columns: ["A"], rows: [["1"]], caption: "Data" }),
966
+ children: Table({ columns: ['A'], rows: [['1']], caption: 'Data' }),
967
967
  })
968
- const text = (await render(doc, "text")) as string
969
- expect(text).toContain("Data")
968
+ const text = (await render(doc, 'text')) as string
969
+ expect(text).toContain('Data')
970
970
  })
971
971
 
972
- it("renders ordered list", async () => {
972
+ it('renders ordered list', async () => {
973
973
  const doc = Document({
974
974
  children: List({
975
975
  ordered: true,
976
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
976
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
977
977
  }),
978
978
  })
979
- const text = (await render(doc, "text")) as string
980
- expect(text).toContain("1. one")
981
- expect(text).toContain("2. two")
979
+ const text = (await render(doc, 'text')) as string
980
+ expect(text).toContain('1. one')
981
+ expect(text).toContain('2. two')
982
982
  })
983
983
 
984
- it("renders unordered list", async () => {
984
+ it('renders unordered list', async () => {
985
985
  const doc = Document({
986
986
  children: List({
987
- children: [ListItem({ children: "a" }), ListItem({ children: "b" })],
987
+ children: [ListItem({ children: 'a' }), ListItem({ children: 'b' })],
988
988
  }),
989
989
  })
990
- const text = (await render(doc, "text")) as string
991
- expect(text).toContain("* a")
992
- expect(text).toContain("* b")
990
+ const text = (await render(doc, 'text')) as string
991
+ expect(text).toContain('* a')
992
+ expect(text).toContain('* b')
993
993
  })
994
994
 
995
- it("renders code block", async () => {
996
- const doc = Document({ children: Code({ children: "x = 1" }) })
997
- const text = (await render(doc, "text")) as string
998
- expect(text).toContain("x = 1")
995
+ it('renders code block', async () => {
996
+ const doc = Document({ children: Code({ children: 'x = 1' }) })
997
+ const text = (await render(doc, 'text')) as string
998
+ expect(text).toContain('x = 1')
999
999
  })
1000
1000
 
1001
- it("renders divider", async () => {
1001
+ it('renders divider', async () => {
1002
1002
  const doc = Document({ children: Divider() })
1003
- const text = (await render(doc, "text")) as string
1004
- expect(text).toContain("")
1003
+ const text = (await render(doc, 'text')) as string
1004
+ expect(text).toContain('')
1005
1005
  })
1006
1006
 
1007
- it("renders spacer as newline", async () => {
1007
+ it('renders spacer as newline', async () => {
1008
1008
  const doc = Document({
1009
- children: [Text({ children: "A" }), Spacer({ height: 20 }), Text({ children: "B" })],
1009
+ children: [Text({ children: 'A' }), Spacer({ height: 20 }), Text({ children: 'B' })],
1010
1010
  })
1011
- const text = (await render(doc, "text")) as string
1012
- expect(text).toContain("A")
1013
- expect(text).toContain("B")
1011
+ const text = (await render(doc, 'text')) as string
1012
+ expect(text).toContain('A')
1013
+ expect(text).toContain('B')
1014
1014
  })
1015
1015
 
1016
- it("renders quote with indentation", async () => {
1017
- const doc = Document({ children: Quote({ children: "wise" }) })
1018
- const text = (await render(doc, "text")) as string
1016
+ it('renders quote with indentation', async () => {
1017
+ const doc = Document({ children: Quote({ children: 'wise' }) })
1018
+ const text = (await render(doc, 'text')) as string
1019
1019
  expect(text).toContain('"wise"')
1020
1020
  })
1021
1021
 
1022
- it("renders section/row/column", async () => {
1022
+ it('renders section/row/column', async () => {
1023
1023
  const doc = Document({
1024
1024
  children: Section({
1025
1025
  children: Row({
1026
- children: Column({ children: Text({ children: "nested" }) }),
1026
+ children: Column({ children: Text({ children: 'nested' }) }),
1027
1027
  }),
1028
1028
  }),
1029
1029
  })
1030
- const text = (await render(doc, "text")) as string
1031
- expect(text).toContain("nested")
1030
+ const text = (await render(doc, 'text')) as string
1031
+ expect(text).toContain('nested')
1032
1032
  })
1033
1033
 
1034
- it("renders heading level 3+", async () => {
1035
- const doc = Document({ children: Heading({ level: 4, children: "Sub" }) })
1036
- const text = (await render(doc, "text")) as string
1037
- expect(text).toContain("Sub")
1034
+ it('renders heading level 3+', async () => {
1035
+ const doc = Document({ children: Heading({ level: 4, children: 'Sub' }) })
1036
+ const text = (await render(doc, 'text')) as string
1037
+ expect(text).toContain('Sub')
1038
1038
  // Level 3+ should not have underline
1039
- expect(text).not.toContain("===")
1040
- expect(text).not.toContain("---")
1039
+ expect(text).not.toContain('===')
1040
+ expect(text).not.toContain('---')
1041
1041
  })
1042
1042
 
1043
- it("renders table with center aligned column", async () => {
1043
+ it('renders table with center aligned column', async () => {
1044
1044
  const doc = Document({
1045
1045
  children: Table({
1046
- columns: [{ header: "Name", align: "center" }],
1047
- rows: [["X"]],
1046
+ columns: [{ header: 'Name', align: 'center' }],
1047
+ rows: [['X']],
1048
1048
  }),
1049
1049
  })
1050
- const text = (await render(doc, "text")) as string
1051
- expect(text).toContain("Name")
1052
- expect(text).toContain("X")
1050
+ const text = (await render(doc, 'text')) as string
1051
+ expect(text).toContain('Name')
1052
+ expect(text).toContain('X')
1053
1053
  })
1054
1054
  })
1055
1055
 
1056
1056
  // ─── Builder — additional coverage ───────────────────────────────────────────
1057
1057
 
1058
- describe("builder — additional", () => {
1059
- it("pageBreak wraps content", () => {
1060
- const doc = createDocument().heading("Page 1").pageBreak().heading("Page 2")
1058
+ describe('builder — additional', () => {
1059
+ it('pageBreak wraps content', () => {
1060
+ const doc = createDocument().heading('Page 1').pageBreak().heading('Page 2')
1061
1061
  const node = doc.build()
1062
- expect(node.type).toBe("document")
1062
+ expect(node.type).toBe('document')
1063
1063
  })
1064
1064
 
1065
- it("toEmail renders", async () => {
1066
- const html = await createDocument().heading("Hi").toEmail()
1067
- expect(html).toContain("Hi")
1068
- expect(html).toContain("max-width:600px")
1065
+ it('toEmail renders', async () => {
1066
+ const html = await createDocument().heading('Hi').toEmail()
1067
+ expect(html).toContain('Hi')
1068
+ expect(html).toContain('max-width:600px')
1069
1069
  })
1070
1070
 
1071
- it("toCsv renders", async () => {
1071
+ it('toCsv renders', async () => {
1072
1072
  const csv = await createDocument()
1073
- .table({ columns: ["X"], rows: [["1"]] })
1073
+ .table({ columns: ['X'], rows: [['1']] })
1074
1074
  .toCsv()
1075
- expect(csv).toContain("X")
1075
+ expect(csv).toContain('X')
1076
1076
  })
1077
1077
 
1078
- it("toText renders", async () => {
1079
- const text = await createDocument().heading("Hi").toText()
1080
- expect(text).toContain("HI")
1078
+ it('toText renders', async () => {
1079
+ const text = await createDocument().heading('Hi').toText()
1080
+ expect(text).toContain('HI')
1081
1081
  })
1082
1082
  })
1083
1083
 
1084
1084
  // ─── Markdown — additional branch coverage ──────────────────────────────────
1085
1085
 
1086
- describe("markdown — additional branches", () => {
1087
- it("renders table with center aligned column", async () => {
1086
+ describe('markdown — additional branches', () => {
1087
+ it('renders table with center aligned column', async () => {
1088
1088
  const doc = Document({
1089
1089
  children: Table({
1090
- columns: [{ header: "X", align: "center" }],
1091
- rows: [["1"]],
1090
+ columns: [{ header: 'X', align: 'center' }],
1091
+ rows: [['1']],
1092
1092
  }),
1093
1093
  })
1094
- const md = (await render(doc, "md")) as string
1095
- expect(md).toContain(":---:")
1094
+ const md = (await render(doc, 'md')) as string
1095
+ expect(md).toContain(':---:')
1096
1096
  })
1097
1097
 
1098
- it("renders table with left aligned column (default)", async () => {
1098
+ it('renders table with left aligned column (default)', async () => {
1099
1099
  const doc = Document({
1100
1100
  children: Table({
1101
- columns: [{ header: "X", align: "left" }],
1102
- rows: [["1"]],
1101
+ columns: [{ header: 'X', align: 'left' }],
1102
+ rows: [['1']],
1103
1103
  }),
1104
1104
  })
1105
- const md = (await render(doc, "md")) as string
1106
- expect(md).toContain("| --- |")
1105
+ const md = (await render(doc, 'md')) as string
1106
+ expect(md).toContain('| --- |')
1107
1107
  })
1108
1108
 
1109
- it("renders empty table gracefully", async () => {
1109
+ it('renders empty table gracefully', async () => {
1110
1110
  const doc = Document({
1111
1111
  children: Table({ columns: [], rows: [] }),
1112
1112
  })
1113
- const md = (await render(doc, "md")) as string
1113
+ const md = (await render(doc, 'md')) as string
1114
1114
  expect(md).toBeDefined()
1115
1115
  })
1116
1116
 
1117
- it("renders image without caption", async () => {
1118
- const doc = Document({ children: Image({ src: "/x.png", alt: "X" }) })
1119
- const md = (await render(doc, "md")) as string
1120
- expect(md).toContain("![X](/x.png)")
1121
- expect(md).not.toContain("*")
1117
+ it('renders image without caption', async () => {
1118
+ const doc = Document({ children: Image({ src: '/x.png', alt: 'X' }) })
1119
+ const md = (await render(doc, 'md')) as string
1120
+ expect(md).toContain('![X](/x.png)')
1121
+ expect(md).not.toContain('*')
1122
1122
  })
1123
1123
 
1124
- it("renders image without alt", async () => {
1125
- const doc = Document({ children: Image({ src: "/x.png" }) })
1126
- const md = (await render(doc, "md")) as string
1127
- expect(md).toContain("![](/x.png)")
1124
+ it('renders image without alt', async () => {
1125
+ const doc = Document({ children: Image({ src: '/x.png' }) })
1126
+ const md = (await render(doc, 'md')) as string
1127
+ expect(md).toContain('![](/x.png)')
1128
1128
  })
1129
1129
 
1130
- it("renders code without language", async () => {
1131
- const doc = Document({ children: Code({ children: "x = 1" }) })
1132
- const md = (await render(doc, "md")) as string
1133
- expect(md).toContain("```\nx = 1\n```")
1130
+ it('renders code without language', async () => {
1131
+ const doc = Document({ children: Code({ children: 'x = 1' }) })
1132
+ const md = (await render(doc, 'md')) as string
1133
+ expect(md).toContain('```\nx = 1\n```')
1134
1134
  })
1135
1135
 
1136
- it("renders spacer as newline", async () => {
1136
+ it('renders spacer as newline', async () => {
1137
1137
  const doc = Document({ children: Spacer({ height: 20 }) })
1138
- const md = (await render(doc, "md")) as string
1138
+ const md = (await render(doc, 'md')) as string
1139
1139
  expect(md).toBeDefined()
1140
1140
  })
1141
1141
  })
1142
1142
 
1143
1143
  // ─── Email — additional branch coverage ─────────────────────────────────────
1144
1144
 
1145
- describe("email — additional branches", () => {
1146
- it("renders section with background and padding", async () => {
1145
+ describe('email — additional branches', () => {
1146
+ it('renders section with background and padding', async () => {
1147
1147
  const doc = Document({
1148
1148
  children: Section({
1149
- background: "#f00",
1149
+ background: '#f00',
1150
1150
  padding: [10, 20],
1151
1151
  borderRadius: 8,
1152
- children: Text({ children: "hi" }),
1152
+ children: Text({ children: 'hi' }),
1153
1153
  }),
1154
1154
  })
1155
- const html = (await render(doc, "email")) as string
1156
- expect(html).toContain("background-color:#f00")
1157
- expect(html).toContain("border-radius:8px")
1155
+ const html = (await render(doc, 'email')) as string
1156
+ expect(html).toContain('background-color:#f00')
1157
+ expect(html).toContain('border-radius:8px')
1158
1158
  })
1159
1159
 
1160
- it("renders image with right alignment", async () => {
1161
- const doc = Document({ children: Image({ src: "/x.png", align: "right" }) })
1162
- const html = (await render(doc, "email")) as string
1163
- expect(html).toContain("text-align:right")
1160
+ it('renders image with right alignment', async () => {
1161
+ const doc = Document({ children: Image({ src: '/x.png', align: 'right' }) })
1162
+ const html = (await render(doc, 'email')) as string
1163
+ expect(html).toContain('text-align:right')
1164
1164
  })
1165
1165
 
1166
- it("renders striped table", async () => {
1166
+ it('renders striped table', async () => {
1167
1167
  const doc = Document({
1168
1168
  children: Table({
1169
- columns: ["A"],
1170
- rows: [["1"], ["2"], ["3"]],
1169
+ columns: ['A'],
1170
+ rows: [['1'], ['2'], ['3']],
1171
1171
  striped: true,
1172
1172
  }),
1173
1173
  })
1174
- const html = (await render(doc, "email")) as string
1175
- expect(html).toContain("background-color:#f9f9f9")
1174
+ const html = (await render(doc, 'email')) as string
1175
+ expect(html).toContain('background-color:#f9f9f9')
1176
1176
  })
1177
1177
 
1178
- it("renders heading level 2", async () => {
1179
- const doc = Document({ children: Heading({ level: 2, children: "Sub" }) })
1180
- const html = (await render(doc, "email")) as string
1181
- expect(html).toContain("<h2")
1182
- expect(html).toContain("font-size:24px")
1178
+ it('renders heading level 2', async () => {
1179
+ const doc = Document({ children: Heading({ level: 2, children: 'Sub' }) })
1180
+ const html = (await render(doc, 'email')) as string
1181
+ expect(html).toContain('<h2')
1182
+ expect(html).toContain('font-size:24px')
1183
1183
  })
1184
1184
 
1185
- it("renders text with all formatting options", async () => {
1185
+ it('renders text with all formatting options', async () => {
1186
1186
  const doc = Document({
1187
1187
  children: Text({
1188
1188
  size: 16,
1189
1189
  bold: true,
1190
1190
  italic: true,
1191
1191
  underline: true,
1192
- align: "center",
1192
+ align: 'center',
1193
1193
  lineHeight: 2,
1194
- children: "styled",
1194
+ children: 'styled',
1195
1195
  }),
1196
1196
  })
1197
- const html = (await render(doc, "email")) as string
1198
- expect(html).toContain("font-size:16px")
1199
- expect(html).toContain("font-weight:bold")
1200
- expect(html).toContain("font-style:italic")
1201
- expect(html).toContain("text-decoration:underline")
1202
- expect(html).toContain("text-align:center")
1197
+ const html = (await render(doc, 'email')) as string
1198
+ expect(html).toContain('font-size:16px')
1199
+ expect(html).toContain('font-weight:bold')
1200
+ expect(html).toContain('font-style:italic')
1201
+ expect(html).toContain('text-decoration:underline')
1202
+ expect(html).toContain('text-align:center')
1203
1203
  })
1204
1204
 
1205
- it("renders text with strikethrough", async () => {
1205
+ it('renders text with strikethrough', async () => {
1206
1206
  const doc = Document({
1207
- children: Text({ strikethrough: true, children: "old" }),
1207
+ children: Text({ strikethrough: true, children: 'old' }),
1208
1208
  })
1209
- const html = (await render(doc, "email")) as string
1210
- expect(html).toContain("text-decoration:line-through")
1209
+ const html = (await render(doc, 'email')) as string
1210
+ expect(html).toContain('text-decoration:line-through')
1211
1211
  })
1212
1212
 
1213
- it("renders button with custom alignment", async () => {
1213
+ it('renders button with custom alignment', async () => {
1214
1214
  const doc = Document({
1215
- children: Button({ href: "/x", align: "center", children: "Go" }),
1215
+ children: Button({ href: '/x', align: 'center', children: 'Go' }),
1216
1216
  })
1217
- const html = (await render(doc, "email")) as string
1218
- expect(html).toContain("text-align:center")
1217
+ const html = (await render(doc, 'email')) as string
1218
+ expect(html).toContain('text-align:center')
1219
1219
  })
1220
1220
 
1221
- it("renders section column direction (default)", async () => {
1221
+ it('renders section column direction (default)', async () => {
1222
1222
  const doc = Document({
1223
- children: Section({ children: Text({ children: "content" }) }),
1223
+ children: Section({ children: Text({ children: 'content' }) }),
1224
1224
  })
1225
- const html = (await render(doc, "email")) as string
1226
- expect(html).toContain("content")
1225
+ const html = (await render(doc, 'email')) as string
1226
+ expect(html).toContain('content')
1227
1227
  })
1228
1228
 
1229
- it("renders section with gap in row", async () => {
1229
+ it('renders section with gap in row', async () => {
1230
1230
  const doc = Document({
1231
1231
  children: Section({
1232
- direction: "row",
1232
+ direction: 'row',
1233
1233
  gap: 16,
1234
- children: [Text({ children: "a" }), Text({ children: "b" })],
1234
+ children: [Text({ children: 'a' }), Text({ children: 'b' })],
1235
1235
  }),
1236
1236
  })
1237
- const html = (await render(doc, "email")) as string
1238
- expect(html).toContain("padding:0 8px")
1237
+ const html = (await render(doc, 'email')) as string
1238
+ expect(html).toContain('padding:0 8px')
1239
1239
  })
1240
1240
  })
1241
1241
 
1242
1242
  // ─── HTML — additional branch coverage ──────────────────────────────────────
1243
1243
 
1244
- describe("html — additional branches", () => {
1245
- it("renders section column direction (default)", async () => {
1244
+ describe('html — additional branches', () => {
1245
+ it('renders section column direction (default)', async () => {
1246
1246
  const doc = Document({
1247
- children: Section({ children: Text({ children: "x" }) }),
1247
+ children: Section({ children: Text({ children: 'x' }) }),
1248
1248
  })
1249
- const html = (await render(doc, "html")) as string
1250
- expect(html).not.toContain("display:flex")
1249
+ const html = (await render(doc, 'html')) as string
1250
+ expect(html).not.toContain('display:flex')
1251
1251
  })
1252
1252
 
1253
- it("renders page with margin as array", async () => {
1253
+ it('renders page with margin as array', async () => {
1254
1254
  const doc = Document({
1255
- children: Page({ margin: [10, 20], children: Text({ children: "hi" }) }),
1255
+ children: Page({ margin: [10, 20], children: Text({ children: 'hi' }) }),
1256
1256
  })
1257
- const html = (await render(doc, "html")) as string
1258
- expect(html).toContain("10px 20px")
1257
+ const html = (await render(doc, 'html')) as string
1258
+ expect(html).toContain('10px 20px')
1259
1259
  })
1260
1260
 
1261
- it("renders page with 4-value margin", async () => {
1261
+ it('renders page with 4-value margin', async () => {
1262
1262
  const doc = Document({
1263
1263
  children: Page({
1264
1264
  margin: [10, 20, 30, 40],
1265
- children: Text({ children: "hi" }),
1265
+ children: Text({ children: 'hi' }),
1266
1266
  }),
1267
1267
  })
1268
- const html = (await render(doc, "html")) as string
1269
- expect(html).toContain("10px 20px 30px 40px")
1268
+ const html = (await render(doc, 'html')) as string
1269
+ expect(html).toContain('10px 20px 30px 40px')
1270
1270
  })
1271
1271
 
1272
- it("renders text with lineHeight", async () => {
1272
+ it('renders text with lineHeight', async () => {
1273
1273
  const doc = Document({
1274
- children: Text({ lineHeight: 1.8, children: "text" }),
1274
+ children: Text({ lineHeight: 1.8, children: 'text' }),
1275
1275
  })
1276
- const html = (await render(doc, "html")) as string
1277
- expect(html).toContain("line-height:1.8")
1276
+ const html = (await render(doc, 'html')) as string
1277
+ expect(html).toContain('line-height:1.8')
1278
1278
  })
1279
1279
  })
1280
1280
 
1281
1281
  // ─── CSV — additional branch coverage ───────────────────────────────────────
1282
1282
 
1283
- describe("csv — additional branches", () => {
1284
- it("finds tables nested in pages", async () => {
1283
+ describe('csv — additional branches', () => {
1284
+ it('finds tables nested in pages', async () => {
1285
1285
  const doc = Document({
1286
1286
  children: Page({
1287
1287
  children: Section({
1288
- children: Table({ columns: ["Nested"], rows: [["val"]] }),
1288
+ children: Table({ columns: ['Nested'], rows: [['val']] }),
1289
1289
  }),
1290
1290
  }),
1291
1291
  })
1292
- const csv = (await render(doc, "csv")) as string
1293
- expect(csv).toContain("Nested")
1294
- expect(csv).toContain("val")
1292
+ const csv = (await render(doc, 'csv')) as string
1293
+ expect(csv).toContain('Nested')
1294
+ expect(csv).toContain('val')
1295
1295
  })
1296
1296
  })
1297
1297
 
1298
1298
  // ─── Render Dispatcher — additional coverage ────────────────────────────────
1299
1299
 
1300
- describe("render dispatcher — additional", () => {
1301
- it("error message includes available formats", async () => {
1300
+ describe('render dispatcher — additional', () => {
1301
+ it('error message includes available formats', async () => {
1302
1302
  try {
1303
- await render(Document({ children: "x" }), "nonexistent")
1303
+ await render(Document({ children: 'x' }), 'nonexistent')
1304
1304
  } catch (e) {
1305
- expect((e as Error).message).toContain("No renderer registered")
1306
- expect((e as Error).message).toContain("Available:")
1307
- expect((e as Error).message).toContain("html")
1305
+ expect((e as Error).message).toContain('No renderer registered')
1306
+ expect((e as Error).message).toContain('Available:')
1307
+ expect((e as Error).message).toContain('html')
1308
1308
  }
1309
1309
  })
1310
1310
  })
1311
1311
 
1312
1312
  // ─── Email Renderer — additional coverage ───────────────────────────────────
1313
1313
 
1314
- describe("email renderer — additional", () => {
1315
- it("renders image with caption", async () => {
1314
+ describe('email renderer — additional', () => {
1315
+ it('renders image with caption', async () => {
1316
1316
  const doc = Document({
1317
- children: Image({ src: "/x.png", alt: "Photo", caption: "Nice" }),
1317
+ children: Image({ src: '/x.png', alt: 'Photo', caption: 'Nice' }),
1318
1318
  })
1319
- const html = (await render(doc, "email")) as string
1320
- expect(html).toContain("Nice")
1319
+ const html = (await render(doc, 'email')) as string
1320
+ expect(html).toContain('Nice')
1321
1321
  })
1322
1322
 
1323
- it("renders image with center alignment", async () => {
1323
+ it('renders image with center alignment', async () => {
1324
1324
  const doc = Document({
1325
- children: Image({ src: "/x.png", align: "center" }),
1325
+ children: Image({ src: '/x.png', align: 'center' }),
1326
1326
  })
1327
- const html = (await render(doc, "email")) as string
1328
- expect(html).toContain("text-align:center")
1327
+ const html = (await render(doc, 'email')) as string
1328
+ expect(html).toContain('text-align:center')
1329
1329
  })
1330
1330
 
1331
- it("renders code block", async () => {
1332
- const doc = Document({ children: Code({ children: "const x = 1" }) })
1333
- const html = (await render(doc, "email")) as string
1334
- expect(html).toContain("Courier New")
1335
- expect(html).toContain("const x = 1")
1331
+ it('renders code block', async () => {
1332
+ const doc = Document({ children: Code({ children: 'const x = 1' }) })
1333
+ const html = (await render(doc, 'email')) as string
1334
+ expect(html).toContain('Courier New')
1335
+ expect(html).toContain('const x = 1')
1336
1336
  })
1337
1337
 
1338
- it("renders spacer with line-height trick", async () => {
1338
+ it('renders spacer with line-height trick', async () => {
1339
1339
  const doc = Document({ children: Spacer({ height: 20 }) })
1340
- const html = (await render(doc, "email")) as string
1341
- expect(html).toContain("height:20px")
1342
- expect(html).toContain("line-height:20px")
1340
+ const html = (await render(doc, 'email')) as string
1341
+ expect(html).toContain('height:20px')
1342
+ expect(html).toContain('line-height:20px')
1343
1343
  })
1344
1344
 
1345
- it("renders list", async () => {
1345
+ it('renders list', async () => {
1346
1346
  const doc = Document({
1347
1347
  children: List({
1348
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
1348
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
1349
1349
  }),
1350
1350
  })
1351
- const html = (await render(doc, "email")) as string
1352
- expect(html).toContain("<ul")
1353
- expect(html).toContain("<li")
1351
+ const html = (await render(doc, 'email')) as string
1352
+ expect(html).toContain('<ul')
1353
+ expect(html).toContain('<li')
1354
1354
  })
1355
1355
 
1356
- it("renders table caption", async () => {
1356
+ it('renders table caption', async () => {
1357
1357
  const doc = Document({
1358
- children: Table({ columns: ["A"], rows: [["1"]], caption: "Data" }),
1358
+ children: Table({ columns: ['A'], rows: [['1']], caption: 'Data' }),
1359
1359
  })
1360
- const html = (await render(doc, "email")) as string
1361
- expect(html).toContain("Data")
1360
+ const html = (await render(doc, 'email')) as string
1361
+ expect(html).toContain('Data')
1362
1362
  })
1363
1363
 
1364
- it("renders link with target _blank", async () => {
1364
+ it('renders link with target _blank', async () => {
1365
1365
  const doc = Document({
1366
- children: Link({ href: "https://x.com", children: "X" }),
1366
+ children: Link({ href: 'https://x.com', children: 'X' }),
1367
1367
  })
1368
- const html = (await render(doc, "email")) as string
1368
+ const html = (await render(doc, 'email')) as string
1369
1369
  expect(html).toContain('target="_blank"')
1370
1370
  })
1371
1371
 
1372
- it("renders row layout using tables", async () => {
1372
+ it('renders row layout using tables', async () => {
1373
1373
  const doc = Document({
1374
1374
  children: Row({
1375
1375
  gap: 10,
1376
- children: [Text({ children: "L" }), Text({ children: "R" })],
1376
+ children: [Text({ children: 'L' }), Text({ children: 'R' })],
1377
1377
  }),
1378
1378
  })
1379
- const html = (await render(doc, "email")) as string
1380
- expect(html).toContain("<table")
1379
+ const html = (await render(doc, 'email')) as string
1380
+ expect(html).toContain('<table')
1381
1381
  expect(html).toContain('valign="top"')
1382
1382
  })
1383
1383
  })
1384
1384
 
1385
1385
  // ─── DOCX Renderer (integration) ────────────────────────────────────────────
1386
1386
 
1387
- describe("DOCX renderer", () => {
1388
- it("renders a document with heading, text, table, list, code, divider to a valid Uint8Array", async () => {
1387
+ describe('DOCX renderer', () => {
1388
+ it('renders a document with heading, text, table, list, code, divider to a valid Uint8Array', async () => {
1389
1389
  const doc = Document({
1390
- title: "DOCX Test",
1391
- author: "Test Suite",
1390
+ title: 'DOCX Test',
1391
+ author: 'Test Suite',
1392
1392
  children: Page({
1393
- size: "A4",
1393
+ size: 'A4',
1394
1394
  margin: 40,
1395
1395
  children: [
1396
- Heading({ children: "DOCX Integration Test" }),
1397
- Text({ children: "A test paragraph.", bold: true }),
1396
+ Heading({ children: 'DOCX Integration Test' }),
1397
+ Text({ children: 'A test paragraph.', bold: true }),
1398
1398
  Table({
1399
- columns: ["Name", "Value"],
1399
+ columns: ['Name', 'Value'],
1400
1400
  rows: [
1401
- ["Alpha", "100"],
1402
- ["Beta", "200"],
1401
+ ['Alpha', '100'],
1402
+ ['Beta', '200'],
1403
1403
  ],
1404
1404
  striped: true,
1405
- headerStyle: { background: "#333333", color: "#ffffff" },
1405
+ headerStyle: { background: '#333333', color: '#ffffff' },
1406
1406
  }),
1407
1407
  List({
1408
1408
  ordered: true,
1409
- children: [ListItem({ children: "First" }), ListItem({ children: "Second" })],
1409
+ children: [ListItem({ children: 'First' }), ListItem({ children: 'Second' })],
1410
1410
  }),
1411
- Code({ children: "const x = 42" }),
1411
+ Code({ children: 'const x = 42' }),
1412
1412
  Divider(),
1413
1413
  ],
1414
1414
  }),
1415
1415
  })
1416
1416
 
1417
- const result = await render(doc, "docx")
1417
+ const result = await render(doc, 'docx')
1418
1418
  expect(result).toBeInstanceOf(Uint8Array)
1419
1419
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1420
1420
  // DOCX files are ZIP archives — first two bytes are PK (0x50, 0x4B)
@@ -1422,76 +1422,76 @@ describe("DOCX renderer", () => {
1422
1422
  expect((result as Uint8Array)[1]).toBe(0x4b)
1423
1423
  }, 15000)
1424
1424
 
1425
- it("embeds base64 images via ImageRun", async () => {
1425
+ it('embeds base64 images via ImageRun', async () => {
1426
1426
  // 1x1 red pixel PNG as base64
1427
1427
  const redPixel =
1428
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
1428
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='
1429
1429
 
1430
1430
  const doc = Document({
1431
1431
  children: Page({
1432
- children: [Image({ src: redPixel, width: 50, height: 50, caption: "Red pixel" })],
1432
+ children: [Image({ src: redPixel, width: 50, height: 50, caption: 'Red pixel' })],
1433
1433
  }),
1434
1434
  })
1435
1435
 
1436
- const result = await render(doc, "docx")
1436
+ const result = await render(doc, 'docx')
1437
1437
  expect(result).toBeInstanceOf(Uint8Array)
1438
1438
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1439
1439
  }, 15000)
1440
1440
 
1441
- it("renders external URL images as placeholders", async () => {
1441
+ it('renders external URL images as placeholders', async () => {
1442
1442
  const doc = Document({
1443
1443
  children: Image({
1444
- src: "https://example.com/logo.png",
1445
- alt: "Logo",
1446
- caption: "Company",
1444
+ src: 'https://example.com/logo.png',
1445
+ alt: 'Logo',
1446
+ caption: 'Company',
1447
1447
  }),
1448
1448
  })
1449
1449
 
1450
- const result = await render(doc, "docx")
1450
+ const result = await render(doc, 'docx')
1451
1451
  expect(result).toBeInstanceOf(Uint8Array)
1452
1452
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1453
1453
  }, 15000)
1454
1454
 
1455
- it("renders page with header and footer", async () => {
1455
+ it('renders page with header and footer', async () => {
1456
1456
  const doc = Document({
1457
1457
  children: Page({
1458
- header: Text({ children: "My Header" }),
1459
- footer: Text({ children: "Page Footer" }),
1460
- children: [Heading({ children: "Content" }), Text({ children: "Body text." })],
1458
+ header: Text({ children: 'My Header' }),
1459
+ footer: Text({ children: 'Page Footer' }),
1460
+ children: [Heading({ children: 'Content' }), Text({ children: 'Body text.' })],
1461
1461
  }),
1462
1462
  })
1463
1463
 
1464
- const result = await render(doc, "docx")
1464
+ const result = await render(doc, 'docx')
1465
1465
  expect(result).toBeInstanceOf(Uint8Array)
1466
1466
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1467
1467
  }, 15000)
1468
1468
 
1469
- it("renders table with bordered option and column widths", async () => {
1469
+ it('renders table with bordered option and column widths', async () => {
1470
1470
  const doc = Document({
1471
1471
  children: Table({
1472
1472
  columns: [
1473
- { header: "Name", width: "60%" },
1474
- { header: "Price", width: "40%", align: "right" },
1473
+ { header: 'Name', width: '60%' },
1474
+ { header: 'Price', width: '40%', align: 'right' },
1475
1475
  ],
1476
- rows: [["Widget", "$10"]],
1476
+ rows: [['Widget', '$10']],
1477
1477
  bordered: true,
1478
1478
  }),
1479
1479
  })
1480
1480
 
1481
- const result = await render(doc, "docx")
1481
+ const result = await render(doc, 'docx')
1482
1482
  expect(result).toBeInstanceOf(Uint8Array)
1483
1483
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1484
1484
  }, 15000)
1485
1485
 
1486
- it("renders nested lists", async () => {
1486
+ it('renders nested lists', async () => {
1487
1487
  const doc = Document({
1488
1488
  children: List({
1489
1489
  children: [
1490
1490
  ListItem({
1491
1491
  children: [
1492
- "Parent item",
1492
+ 'Parent item',
1493
1493
  List({
1494
- children: [ListItem({ children: "Child item" })],
1494
+ children: [ListItem({ children: 'Child item' })],
1495
1495
  }),
1496
1496
  ],
1497
1497
  }),
@@ -1499,7 +1499,7 @@ describe("DOCX renderer", () => {
1499
1499
  }),
1500
1500
  })
1501
1501
 
1502
- const result = await render(doc, "docx")
1502
+ const result = await render(doc, 'docx')
1503
1503
  expect(result).toBeInstanceOf(Uint8Array)
1504
1504
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1505
1505
  }, 15000)
@@ -1507,28 +1507,28 @@ describe("DOCX renderer", () => {
1507
1507
 
1508
1508
  // ─── XLSX Renderer (integration) ────────────────────────────────────────────
1509
1509
 
1510
- describe("XLSX renderer", () => {
1511
- it("renders a document with tables to a valid Uint8Array", async () => {
1510
+ describe('XLSX renderer', () => {
1511
+ it('renders a document with tables to a valid Uint8Array', async () => {
1512
1512
  const doc = Document({
1513
- title: "XLSX Test",
1514
- author: "Test Suite",
1513
+ title: 'XLSX Test',
1514
+ author: 'Test Suite',
1515
1515
  children: Page({
1516
1516
  children: [
1517
- Heading({ children: "Sales Data" }),
1517
+ Heading({ children: 'Sales Data' }),
1518
1518
  Table({
1519
- columns: ["Product", "Revenue", "Margin"],
1519
+ columns: ['Product', 'Revenue', 'Margin'],
1520
1520
  rows: [
1521
- ["Widget", "$1,234.56", "15%"],
1522
- ["Gadget", "$2,500.00", "22.5%"],
1521
+ ['Widget', '$1,234.56', '15%'],
1522
+ ['Gadget', '$2,500.00', '22.5%'],
1523
1523
  ],
1524
1524
  striped: true,
1525
- headerStyle: { background: "#1a1a2e", color: "#ffffff" },
1525
+ headerStyle: { background: '#1a1a2e', color: '#ffffff' },
1526
1526
  }),
1527
1527
  ],
1528
1528
  }),
1529
1529
  })
1530
1530
 
1531
- const result = await render(doc, "xlsx")
1531
+ const result = await render(doc, 'xlsx')
1532
1532
  expect(result).toBeInstanceOf(Uint8Array)
1533
1533
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1534
1534
  // XLSX files are ZIP archives — first two bytes are PK (0x50, 0x4B)
@@ -1536,75 +1536,75 @@ describe("XLSX renderer", () => {
1536
1536
  expect((result as Uint8Array)[1]).toBe(0x4b)
1537
1537
  }, 15000)
1538
1538
 
1539
- it("parses currency values as numbers", async () => {
1539
+ it('parses currency values as numbers', async () => {
1540
1540
  const doc = Document({
1541
1541
  children: Table({
1542
- columns: ["Amount"],
1543
- rows: [["$1,234.56"], ["$500"], ["-$100.50"]],
1542
+ columns: ['Amount'],
1543
+ rows: [['$1,234.56'], ['$500'], ['-$100.50']],
1544
1544
  }),
1545
1545
  })
1546
1546
 
1547
- const result = await render(doc, "xlsx")
1547
+ const result = await render(doc, 'xlsx')
1548
1548
  expect(result).toBeInstanceOf(Uint8Array)
1549
1549
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1550
1550
  }, 15000)
1551
1551
 
1552
- it("parses percentage values", async () => {
1552
+ it('parses percentage values', async () => {
1553
1553
  const doc = Document({
1554
1554
  children: Table({
1555
- columns: ["Rate"],
1556
- rows: [["45%"], ["12.5%"], ["-3%"]],
1555
+ columns: ['Rate'],
1556
+ rows: [['45%'], ['12.5%'], ['-3%']],
1557
1557
  }),
1558
1558
  })
1559
1559
 
1560
- const result = await render(doc, "xlsx")
1560
+ const result = await render(doc, 'xlsx')
1561
1561
  expect(result).toBeInstanceOf(Uint8Array)
1562
1562
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1563
1563
  }, 15000)
1564
1564
 
1565
- it("renders multiple tables on the same sheet with spacing", async () => {
1565
+ it('renders multiple tables on the same sheet with spacing', async () => {
1566
1566
  const doc = Document({
1567
1567
  children: [
1568
- Heading({ children: "Report" }),
1568
+ Heading({ children: 'Report' }),
1569
1569
  Table({
1570
- columns: ["A", "B"],
1570
+ columns: ['A', 'B'],
1571
1571
  rows: [
1572
- ["1", "2"],
1573
- ["3", "4"],
1572
+ ['1', '2'],
1573
+ ['3', '4'],
1574
1574
  ],
1575
- caption: "First Table",
1575
+ caption: 'First Table',
1576
1576
  }),
1577
1577
  Table({
1578
- columns: ["X", "Y"],
1579
- rows: [["a", "b"]],
1580
- caption: "Second Table",
1578
+ columns: ['X', 'Y'],
1579
+ rows: [['a', 'b']],
1580
+ caption: 'Second Table',
1581
1581
  }),
1582
1582
  ],
1583
1583
  })
1584
1584
 
1585
- const result = await render(doc, "xlsx")
1585
+ const result = await render(doc, 'xlsx')
1586
1586
  expect(result).toBeInstanceOf(Uint8Array)
1587
1587
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1588
1588
  }, 15000)
1589
1589
 
1590
- it("renders bordered tables", async () => {
1590
+ it('renders bordered tables', async () => {
1591
1591
  const doc = Document({
1592
1592
  children: Table({
1593
- columns: ["Name", "Value"],
1594
- rows: [["Alpha", "100"]],
1593
+ columns: ['Name', 'Value'],
1594
+ rows: [['Alpha', '100']],
1595
1595
  bordered: true,
1596
1596
  }),
1597
1597
  })
1598
1598
 
1599
- const result = await render(doc, "xlsx")
1599
+ const result = await render(doc, 'xlsx')
1600
1600
  expect(result).toBeInstanceOf(Uint8Array)
1601
1601
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1602
1602
  }, 15000)
1603
1603
 
1604
- it("renders empty document with default sheet", async () => {
1605
- const doc = Document({ children: Text({ children: "no tables" }) })
1604
+ it('renders empty document with default sheet', async () => {
1605
+ const doc = Document({ children: Text({ children: 'no tables' }) })
1606
1606
 
1607
- const result = await render(doc, "xlsx")
1607
+ const result = await render(doc, 'xlsx')
1608
1608
  expect(result).toBeInstanceOf(Uint8Array)
1609
1609
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1610
1610
  }, 15000)
@@ -1612,76 +1612,76 @@ describe("XLSX renderer", () => {
1612
1612
 
1613
1613
  // ─── PDF Renderer (integration) ─────────────────────────────────────────────
1614
1614
 
1615
- describe("PDF renderer", () => {
1616
- it("renders a document with heading, text, table, and data: image to a valid Uint8Array", async () => {
1615
+ describe('PDF renderer', () => {
1616
+ it('renders a document with heading, text, table, and data: image to a valid Uint8Array', async () => {
1617
1617
  // 1x1 red pixel PNG as base64
1618
1618
  const redPixel =
1619
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
1619
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='
1620
1620
 
1621
1621
  const doc = Document({
1622
- title: "PDF Test",
1623
- author: "Test Suite",
1622
+ title: 'PDF Test',
1623
+ author: 'Test Suite',
1624
1624
  children: Page({
1625
- size: "A4",
1625
+ size: 'A4',
1626
1626
  margin: 40,
1627
1627
  children: [
1628
- Heading({ children: "PDF Integration Test" }),
1629
- Text({ children: "This is a test paragraph.", bold: true }),
1628
+ Heading({ children: 'PDF Integration Test' }),
1629
+ Text({ children: 'This is a test paragraph.', bold: true }),
1630
1630
  Table({
1631
- columns: ["Name", "Value"],
1631
+ columns: ['Name', 'Value'],
1632
1632
  rows: [
1633
- ["Alpha", "100"],
1634
- ["Beta", "200"],
1633
+ ['Alpha', '100'],
1634
+ ['Beta', '200'],
1635
1635
  ],
1636
1636
  striped: true,
1637
- headerStyle: { background: "#333333", color: "#ffffff" },
1637
+ headerStyle: { background: '#333333', color: '#ffffff' },
1638
1638
  }),
1639
1639
  Image({ src: redPixel, width: 50, height: 50 }),
1640
1640
  List({
1641
1641
  ordered: true,
1642
- children: [ListItem({ children: "First" }), ListItem({ children: "Second" })],
1642
+ children: [ListItem({ children: 'First' }), ListItem({ children: 'Second' })],
1643
1643
  }),
1644
1644
  Divider(),
1645
- Quote({ children: "A wise quote." }),
1645
+ Quote({ children: 'A wise quote.' }),
1646
1646
  ],
1647
1647
  }),
1648
1648
  })
1649
1649
 
1650
- const result = await render(doc, "pdf")
1650
+ const result = await render(doc, 'pdf')
1651
1651
  expect(result).toBeInstanceOf(Uint8Array)
1652
1652
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1653
1653
  // PDF files start with %PDF
1654
1654
  const header = String.fromCharCode(...(result as Uint8Array).slice(0, 5))
1655
- expect(header).toBe("%PDF-")
1655
+ expect(header).toBe('%PDF-')
1656
1656
  }, 15000)
1657
1657
 
1658
- it("renders images with HTTP URLs as placeholder text", async () => {
1658
+ it('renders images with HTTP URLs as placeholder text', async () => {
1659
1659
  const doc = Document({
1660
- title: "HTTP Image Test",
1660
+ title: 'HTTP Image Test',
1661
1661
  children: Page({
1662
1662
  children: [
1663
- Heading({ children: "Test" }),
1664
- Image({ src: "https://example.com/image.png", width: 100 }),
1663
+ Heading({ children: 'Test' }),
1664
+ Image({ src: 'https://example.com/image.png', width: 100 }),
1665
1665
  ],
1666
1666
  }),
1667
1667
  })
1668
1668
 
1669
- const result = await render(doc, "pdf")
1669
+ const result = await render(doc, 'pdf')
1670
1670
  expect(result).toBeInstanceOf(Uint8Array)
1671
1671
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1672
1672
  }, 15000)
1673
1673
 
1674
- it("renders page with header and footer", async () => {
1674
+ it('renders page with header and footer', async () => {
1675
1675
  const doc = Document({
1676
- title: "Header/Footer Test",
1676
+ title: 'Header/Footer Test',
1677
1677
  children: Page({
1678
- header: Text({ children: "Page Header", bold: true }),
1679
- footer: Text({ children: "Page Footer", size: 10 }),
1680
- children: [Heading({ children: "Content" }), Text({ children: "Body text." })],
1678
+ header: Text({ children: 'Page Header', bold: true }),
1679
+ footer: Text({ children: 'Page Footer', size: 10 }),
1680
+ children: [Heading({ children: 'Content' }), Text({ children: 'Body text.' })],
1681
1681
  }),
1682
1682
  })
1683
1683
 
1684
- const result = await render(doc, "pdf")
1684
+ const result = await render(doc, 'pdf')
1685
1685
  expect(result).toBeInstanceOf(Uint8Array)
1686
1686
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1687
1687
  }, 15000)
@@ -1689,31 +1689,31 @@ describe("PDF renderer", () => {
1689
1689
 
1690
1690
  // ─── PPTX Renderer (integration) ────────────────────────────────────────────
1691
1691
 
1692
- describe("PPTX renderer", () => {
1693
- it("renders a document with pages, headings, text, and tables to a valid Uint8Array", async () => {
1692
+ describe('PPTX renderer', () => {
1693
+ it('renders a document with pages, headings, text, and tables to a valid Uint8Array', async () => {
1694
1694
  const doc = Document({
1695
- title: "PPTX Test",
1696
- author: "Test Suite",
1695
+ title: 'PPTX Test',
1696
+ author: 'Test Suite',
1697
1697
  children: [
1698
1698
  Page({
1699
1699
  children: [
1700
- Heading({ children: "Slide 1 Title" }),
1701
- Text({ children: "Introduction text.", bold: true }),
1700
+ Heading({ children: 'Slide 1 Title' }),
1701
+ Text({ children: 'Introduction text.', bold: true }),
1702
1702
  List({
1703
- children: [ListItem({ children: "Point A" }), ListItem({ children: "Point B" })],
1703
+ children: [ListItem({ children: 'Point A' }), ListItem({ children: 'Point B' })],
1704
1704
  }),
1705
1705
  ],
1706
1706
  }),
1707
1707
  Page({
1708
1708
  children: [
1709
- Heading({ level: 2, children: "Slide 2 Data" }),
1709
+ Heading({ level: 2, children: 'Slide 2 Data' }),
1710
1710
  Table({
1711
- columns: ["Metric", "Value"],
1711
+ columns: ['Metric', 'Value'],
1712
1712
  rows: [
1713
- ["Revenue", "$1M"],
1714
- ["Profit", "$300K"],
1713
+ ['Revenue', '$1M'],
1714
+ ['Profit', '$300K'],
1715
1715
  ],
1716
- headerStyle: { background: "#1a1a2e", color: "#ffffff" },
1716
+ headerStyle: { background: '#1a1a2e', color: '#ffffff' },
1717
1717
  striped: true,
1718
1718
  }),
1719
1719
  ],
@@ -1721,7 +1721,7 @@ describe("PPTX renderer", () => {
1721
1721
  ],
1722
1722
  })
1723
1723
 
1724
- const result = await render(doc, "pptx")
1724
+ const result = await render(doc, 'pptx')
1725
1725
  expect(result).toBeInstanceOf(Uint8Array)
1726
1726
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1727
1727
  // PPTX files are ZIP archives — first two bytes are PK (0x50, 0x4B)
@@ -1729,44 +1729,44 @@ describe("PPTX renderer", () => {
1729
1729
  expect((result as Uint8Array)[1]).toBe(0x4b)
1730
1730
  }, 15000)
1731
1731
 
1732
- it("renders a document without explicit pages as a single slide", async () => {
1732
+ it('renders a document without explicit pages as a single slide', async () => {
1733
1733
  const doc = Document({
1734
- title: "Single Slide",
1734
+ title: 'Single Slide',
1735
1735
  children: [
1736
- Heading({ children: "Auto Slide" }),
1737
- Text({ children: "No explicit page wrapper." }),
1736
+ Heading({ children: 'Auto Slide' }),
1737
+ Text({ children: 'No explicit page wrapper.' }),
1738
1738
  ],
1739
1739
  })
1740
1740
 
1741
- const result = await render(doc, "pptx")
1741
+ const result = await render(doc, 'pptx')
1742
1742
  expect(result).toBeInstanceOf(Uint8Array)
1743
1743
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1744
1744
  }, 15000)
1745
1745
 
1746
- it("renders all node types without errors", async () => {
1746
+ it('renders all node types without errors', async () => {
1747
1747
  // 1x1 red pixel PNG as base64
1748
1748
  const redPixel =
1749
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
1749
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='
1750
1750
 
1751
1751
  const doc = Document({
1752
1752
  children: Page({
1753
1753
  children: [
1754
- Heading({ children: "Title" }),
1755
- Text({ children: "Body", bold: true, italic: true }),
1754
+ Heading({ children: 'Title' }),
1755
+ Text({ children: 'Body', bold: true, italic: true }),
1756
1756
  Image({ src: redPixel, width: 50, height: 50 }),
1757
- Code({ children: "const x = 1" }),
1758
- Quote({ children: "A quote" }),
1759
- Link({ href: "https://example.com", children: "Link text" }),
1760
- Button({ href: "/action", background: "#4f46e5", children: "Click" }),
1757
+ Code({ children: 'const x = 1' }),
1758
+ Quote({ children: 'A quote' }),
1759
+ Link({ href: 'https://example.com', children: 'Link text' }),
1760
+ Button({ href: '/action', background: '#4f46e5', children: 'Click' }),
1761
1761
  Divider(),
1762
1762
  Spacer({ height: 20 }),
1763
- List({ ordered: true, children: [ListItem({ children: "one" })] }),
1764
- Section({ children: Text({ children: "nested" }) }),
1763
+ List({ ordered: true, children: [ListItem({ children: 'one' })] }),
1764
+ Section({ children: Text({ children: 'nested' }) }),
1765
1765
  ],
1766
1766
  }),
1767
1767
  })
1768
1768
 
1769
- const result = await render(doc, "pptx")
1769
+ const result = await render(doc, 'pptx')
1770
1770
  expect(result).toBeInstanceOf(Uint8Array)
1771
1771
  expect((result as Uint8Array).length).toBeGreaterThan(0)
1772
1772
  }, 15000)
@@ -1774,224 +1774,224 @@ describe("PPTX renderer", () => {
1774
1774
 
1775
1775
  // ─── Slack Renderer ─────────────────────────────────────────────────────────
1776
1776
 
1777
- describe("Slack renderer", () => {
1778
- it("renders heading as header block", async () => {
1779
- const doc = Document({ children: Heading({ children: "Hello" }) })
1780
- const json = (await render(doc, "slack")) as string
1777
+ describe('Slack renderer', () => {
1778
+ it('renders heading as header block', async () => {
1779
+ const doc = Document({ children: Heading({ children: 'Hello' }) })
1780
+ const json = (await render(doc, 'slack')) as string
1781
1781
  const parsed = JSON.parse(json)
1782
1782
  expect(parsed.blocks).toHaveLength(1)
1783
- expect(parsed.blocks[0].type).toBe("header")
1783
+ expect(parsed.blocks[0].type).toBe('header')
1784
1784
  })
1785
1785
 
1786
- it("renders text with bold as mrkdwn", async () => {
1786
+ it('renders text with bold as mrkdwn', async () => {
1787
1787
  const doc = Document({
1788
- children: Text({ bold: true, children: "Bold text" }),
1788
+ children: Text({ bold: true, children: 'Bold text' }),
1789
1789
  })
1790
- const json = (await render(doc, "slack")) as string
1790
+ const json = (await render(doc, 'slack')) as string
1791
1791
  const parsed = JSON.parse(json)
1792
- expect(parsed.blocks[0].text.text).toContain("*Bold text*")
1792
+ expect(parsed.blocks[0].text.text).toContain('*Bold text*')
1793
1793
  })
1794
1794
 
1795
- it("renders button as actions block", async () => {
1795
+ it('renders button as actions block', async () => {
1796
1796
  const doc = Document({
1797
- children: Button({ href: "/go", children: "Click" }),
1797
+ children: Button({ href: '/go', children: 'Click' }),
1798
1798
  })
1799
- const json = (await render(doc, "slack")) as string
1799
+ const json = (await render(doc, 'slack')) as string
1800
1800
  const parsed = JSON.parse(json)
1801
- expect(parsed.blocks[0].type).toBe("actions")
1802
- expect(parsed.blocks[0].elements[0].type).toBe("button")
1801
+ expect(parsed.blocks[0].type).toBe('actions')
1802
+ expect(parsed.blocks[0].elements[0].type).toBe('button')
1803
1803
  })
1804
1804
 
1805
- it("renders table as code block", async () => {
1805
+ it('renders table as code block', async () => {
1806
1806
  const doc = Document({
1807
- children: Table({ columns: ["A", "B"], rows: [["1", "2"]] }),
1807
+ children: Table({ columns: ['A', 'B'], rows: [['1', '2']] }),
1808
1808
  })
1809
- const json = (await render(doc, "slack")) as string
1809
+ const json = (await render(doc, 'slack')) as string
1810
1810
  const parsed = JSON.parse(json)
1811
- expect(parsed.blocks[0].text.text).toContain("*A*")
1812
- expect(parsed.blocks[0].text.text).toContain("1 | 2")
1811
+ expect(parsed.blocks[0].text.text).toContain('*A*')
1812
+ expect(parsed.blocks[0].text.text).toContain('1 | 2')
1813
1813
  })
1814
1814
 
1815
- it("renders divider", async () => {
1815
+ it('renders divider', async () => {
1816
1816
  const doc = Document({ children: Divider() })
1817
- const json = (await render(doc, "slack")) as string
1817
+ const json = (await render(doc, 'slack')) as string
1818
1818
  const parsed = JSON.parse(json)
1819
- expect(parsed.blocks[0].type).toBe("divider")
1819
+ expect(parsed.blocks[0].type).toBe('divider')
1820
1820
  })
1821
1821
 
1822
- it("renders list as bullet points", async () => {
1822
+ it('renders list as bullet points', async () => {
1823
1823
  const doc = Document({
1824
1824
  children: List({
1825
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
1825
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
1826
1826
  }),
1827
1827
  })
1828
- const json = (await render(doc, "slack")) as string
1828
+ const json = (await render(doc, 'slack')) as string
1829
1829
  const parsed = JSON.parse(json)
1830
- expect(parsed.blocks[0].text.text).toContain("• one")
1831
- expect(parsed.blocks[0].text.text).toContain("• two")
1830
+ expect(parsed.blocks[0].text.text).toContain('• one')
1831
+ expect(parsed.blocks[0].text.text).toContain('• two')
1832
1832
  })
1833
1833
 
1834
- it("renders ordered list", async () => {
1834
+ it('renders ordered list', async () => {
1835
1835
  const doc = Document({
1836
1836
  children: List({
1837
1837
  ordered: true,
1838
- children: [ListItem({ children: "a" }), ListItem({ children: "b" })],
1838
+ children: [ListItem({ children: 'a' }), ListItem({ children: 'b' })],
1839
1839
  }),
1840
1840
  })
1841
- const json = (await render(doc, "slack")) as string
1841
+ const json = (await render(doc, 'slack')) as string
1842
1842
  const parsed = JSON.parse(json)
1843
- expect(parsed.blocks[0].text.text).toContain("1. a")
1844
- expect(parsed.blocks[0].text.text).toContain("2. b")
1843
+ expect(parsed.blocks[0].text.text).toContain('1. a')
1844
+ expect(parsed.blocks[0].text.text).toContain('2. b')
1845
1845
  })
1846
1846
 
1847
- it("renders link in mrkdwn format", async () => {
1847
+ it('renders link in mrkdwn format', async () => {
1848
1848
  const doc = Document({
1849
- children: Link({ href: "https://x.com", children: "X" }),
1849
+ children: Link({ href: 'https://x.com', children: 'X' }),
1850
1850
  })
1851
- const json = (await render(doc, "slack")) as string
1851
+ const json = (await render(doc, 'slack')) as string
1852
1852
  const parsed = JSON.parse(json)
1853
- expect(parsed.blocks[0].text.text).toContain("<https://x.com|X>")
1853
+ expect(parsed.blocks[0].text.text).toContain('<https://x.com|X>')
1854
1854
  })
1855
1855
 
1856
- it("renders code block", async () => {
1856
+ it('renders code block', async () => {
1857
1857
  const doc = Document({
1858
- children: Code({ language: "js", children: "const x = 1" }),
1858
+ children: Code({ language: 'js', children: 'const x = 1' }),
1859
1859
  })
1860
- const json = (await render(doc, "slack")) as string
1860
+ const json = (await render(doc, 'slack')) as string
1861
1861
  const parsed = JSON.parse(json)
1862
- expect(parsed.blocks[0].text.text).toContain("```js")
1863
- expect(parsed.blocks[0].text.text).toContain("const x = 1")
1862
+ expect(parsed.blocks[0].text.text).toContain('```js')
1863
+ expect(parsed.blocks[0].text.text).toContain('const x = 1')
1864
1864
  })
1865
1865
 
1866
- it("renders quote with >", async () => {
1867
- const doc = Document({ children: Quote({ children: "wise" }) })
1868
- const json = (await render(doc, "slack")) as string
1866
+ it('renders quote with >', async () => {
1867
+ const doc = Document({ children: Quote({ children: 'wise' }) })
1868
+ const json = (await render(doc, 'slack')) as string
1869
1869
  const parsed = JSON.parse(json)
1870
- expect(parsed.blocks[0].text.text).toContain("> wise")
1870
+ expect(parsed.blocks[0].text.text).toContain('> wise')
1871
1871
  })
1872
1872
 
1873
- it("renders image with URL", async () => {
1873
+ it('renders image with URL', async () => {
1874
1874
  const doc = Document({
1875
- children: Image({ src: "https://x.com/img.png", alt: "Photo" }),
1875
+ children: Image({ src: 'https://x.com/img.png', alt: 'Photo' }),
1876
1876
  })
1877
- const json = (await render(doc, "slack")) as string
1877
+ const json = (await render(doc, 'slack')) as string
1878
1878
  const parsed = JSON.parse(json)
1879
- expect(parsed.blocks[0].type).toBe("image")
1880
- expect(parsed.blocks[0].image_url).toBe("https://x.com/img.png")
1879
+ expect(parsed.blocks[0].type).toBe('image')
1880
+ expect(parsed.blocks[0].image_url).toBe('https://x.com/img.png')
1881
1881
  })
1882
1882
 
1883
- it("skips non-URL images", async () => {
1883
+ it('skips non-URL images', async () => {
1884
1884
  const doc = Document({
1885
- children: Image({ src: "data:image/png;base64,abc" }),
1885
+ children: Image({ src: 'data:image/png;base64,abc' }),
1886
1886
  })
1887
- const json = (await render(doc, "slack")) as string
1887
+ const json = (await render(doc, 'slack')) as string
1888
1888
  const parsed = JSON.parse(json)
1889
1889
  expect(parsed.blocks).toHaveLength(0)
1890
1890
  })
1891
1891
 
1892
- it("renders page-break as divider", async () => {
1892
+ it('renders page-break as divider', async () => {
1893
1893
  const doc = Document({ children: PageBreak() })
1894
- const json = (await render(doc, "slack")) as string
1894
+ const json = (await render(doc, 'slack')) as string
1895
1895
  const parsed = JSON.parse(json)
1896
- expect(parsed.blocks[0].type).toBe("divider")
1896
+ expect(parsed.blocks[0].type).toBe('divider')
1897
1897
  })
1898
1898
 
1899
- it("renders text with italic and strikethrough", async () => {
1899
+ it('renders text with italic and strikethrough', async () => {
1900
1900
  const doc = Document({
1901
1901
  children: [
1902
- Text({ italic: true, children: "italic" }),
1903
- Text({ strikethrough: true, children: "struck" }),
1902
+ Text({ italic: true, children: 'italic' }),
1903
+ Text({ strikethrough: true, children: 'struck' }),
1904
1904
  ],
1905
1905
  })
1906
- const json = (await render(doc, "slack")) as string
1906
+ const json = (await render(doc, 'slack')) as string
1907
1907
  const parsed = JSON.parse(json)
1908
- expect(parsed.blocks[0].text.text).toContain("_italic_")
1909
- expect(parsed.blocks[1].text.text).toContain("~struck~")
1908
+ expect(parsed.blocks[0].text.text).toContain('_italic_')
1909
+ expect(parsed.blocks[1].text.text).toContain('~struck~')
1910
1910
  })
1911
1911
 
1912
- it("renders image with caption", async () => {
1912
+ it('renders image with caption', async () => {
1913
1913
  const doc = Document({
1914
- children: Image({ src: "https://x.com/img.png", caption: "Nice" }),
1914
+ children: Image({ src: 'https://x.com/img.png', caption: 'Nice' }),
1915
1915
  })
1916
- const json = (await render(doc, "slack")) as string
1916
+ const json = (await render(doc, 'slack')) as string
1917
1917
  const parsed = JSON.parse(json)
1918
- expect(parsed.blocks[0].title.text).toBe("Nice")
1918
+ expect(parsed.blocks[0].title.text).toBe('Nice')
1919
1919
  })
1920
1920
 
1921
- it("renders table with caption", async () => {
1921
+ it('renders table with caption', async () => {
1922
1922
  const doc = Document({
1923
- children: Table({ columns: ["X"], rows: [["1"]], caption: "My Table" }),
1923
+ children: Table({ columns: ['X'], rows: [['1']], caption: 'My Table' }),
1924
1924
  })
1925
- const json = (await render(doc, "slack")) as string
1925
+ const json = (await render(doc, 'slack')) as string
1926
1926
  const parsed = JSON.parse(json)
1927
- expect(parsed.blocks[0].text.text).toContain("_My Table_")
1927
+ expect(parsed.blocks[0].text.text).toContain('_My Table_')
1928
1928
  })
1929
1929
  })
1930
1930
 
1931
1931
  // ─── PageBreak ──────────────────────────────────────────────────────────────
1932
1932
 
1933
- describe("PageBreak", () => {
1934
- it("creates a page-break node", () => {
1933
+ describe('PageBreak', () => {
1934
+ it('creates a page-break node', () => {
1935
1935
  const pb = PageBreak()
1936
- expect(pb.type).toBe("page-break")
1936
+ expect(pb.type).toBe('page-break')
1937
1937
  expect(pb.children).toEqual([])
1938
1938
  })
1939
1939
 
1940
- it("renders as CSS page-break in HTML", async () => {
1940
+ it('renders as CSS page-break in HTML', async () => {
1941
1941
  const doc = Document({ children: PageBreak() })
1942
- const html = (await render(doc, "html")) as string
1943
- expect(html).toContain("page-break-after:always")
1942
+ const html = (await render(doc, 'html')) as string
1943
+ expect(html).toContain('page-break-after:always')
1944
1944
  })
1945
1945
 
1946
- it("renders as separator in email", async () => {
1946
+ it('renders as separator in email', async () => {
1947
1947
  const doc = Document({ children: PageBreak() })
1948
- const html = (await render(doc, "email")) as string
1949
- expect(html).toContain("border-top:2px solid")
1948
+ const html = (await render(doc, 'email')) as string
1949
+ expect(html).toContain('border-top:2px solid')
1950
1950
  })
1951
1951
 
1952
- it("renders as --- in markdown", async () => {
1952
+ it('renders as --- in markdown', async () => {
1953
1953
  const doc = Document({ children: PageBreak() })
1954
- const md = (await render(doc, "md")) as string
1955
- expect(md).toContain("---")
1954
+ const md = (await render(doc, 'md')) as string
1955
+ expect(md).toContain('---')
1956
1956
  })
1957
1957
 
1958
- it("renders as separator in text", async () => {
1958
+ it('renders as separator in text', async () => {
1959
1959
  const doc = Document({ children: PageBreak() })
1960
- const text = (await render(doc, "text")) as string
1961
- expect(text).toContain("")
1960
+ const text = (await render(doc, 'text')) as string
1961
+ expect(text).toContain('')
1962
1962
  })
1963
1963
 
1964
- it("builder pageBreak inserts page-break node", async () => {
1965
- const doc = createDocument().heading("Page 1").pageBreak().heading("Page 2")
1964
+ it('builder pageBreak inserts page-break node', async () => {
1965
+ const doc = createDocument().heading('Page 1').pageBreak().heading('Page 2')
1966
1966
  const html = await doc.toHtml()
1967
- expect(html).toContain("page-break-after:always")
1968
- expect(html).toContain("Page 1")
1969
- expect(html).toContain("Page 2")
1967
+ expect(html).toContain('page-break-after:always')
1968
+ expect(html).toContain('Page 1')
1969
+ expect(html).toContain('Page 2')
1970
1970
  })
1971
1971
  })
1972
1972
 
1973
1973
  // ─── RTL Support ────────────────────────────────────────────────────────────
1974
1974
 
1975
- describe("RTL support", () => {
1976
- it("adds dir=rtl to HTML body", async () => {
1977
- const doc = Document({ children: Text({ children: "مرحبا" }) })
1978
- const html = (await render(doc, "html", { direction: "rtl" })) as string
1975
+ describe('RTL support', () => {
1976
+ it('adds dir=rtl to HTML body', async () => {
1977
+ const doc = Document({ children: Text({ children: 'مرحبا' }) })
1978
+ const html = (await render(doc, 'html', { direction: 'rtl' })) as string
1979
1979
  expect(html).toContain('dir="rtl"')
1980
- expect(html).toContain("direction:rtl")
1980
+ expect(html).toContain('direction:rtl')
1981
1981
  })
1982
1982
 
1983
- it("does not add dir for ltr (default)", async () => {
1984
- const doc = Document({ children: Text({ children: "Hello" }) })
1985
- const html = (await render(doc, "html")) as string
1983
+ it('does not add dir for ltr (default)', async () => {
1984
+ const doc = Document({ children: Text({ children: 'Hello' }) })
1985
+ const html = (await render(doc, 'html')) as string
1986
1986
  expect(html).not.toContain('dir="rtl"')
1987
1987
  })
1988
1988
  })
1989
1989
 
1990
1990
  // ─── keepTogether ───────────────────────────────────────────────────────────
1991
1991
 
1992
- describe("keepTogether", () => {
1993
- it("table accepts keepTogether prop", () => {
1994
- const t = Table({ columns: ["A"], rows: [["1"]], keepTogether: true })
1992
+ describe('keepTogether', () => {
1993
+ it('table accepts keepTogether prop', () => {
1994
+ const t = Table({ columns: ['A'], rows: [['1']], keepTogether: true })
1995
1995
  expect(t.props.keepTogether).toBe(true)
1996
1996
  })
1997
1997
  })
@@ -2000,358 +2000,358 @@ describe("keepTogether", () => {
2000
2000
 
2001
2001
  // ─── SVG Renderer ───────────────────────────────────────────────────────────
2002
2002
 
2003
- describe("SVG renderer", () => {
2004
- it("renders a valid SVG document", async () => {
2005
- const doc = Document({ children: Heading({ children: "Title" }) })
2006
- const svg = (await render(doc, "svg")) as string
2003
+ describe('SVG renderer', () => {
2004
+ it('renders a valid SVG document', async () => {
2005
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2006
+ const svg = (await render(doc, 'svg')) as string
2007
2007
  expect(svg).toContain('<svg xmlns="http://www.w3.org/2000/svg"')
2008
- expect(svg).toContain("</svg>")
2009
- expect(svg).toContain("Title")
2008
+ expect(svg).toContain('</svg>')
2009
+ expect(svg).toContain('Title')
2010
2010
  })
2011
2011
 
2012
- it("renders heading with correct font size", async () => {
2013
- const doc = Document({ children: Heading({ level: 2, children: "Sub" }) })
2014
- const svg = (await render(doc, "svg")) as string
2012
+ it('renders heading with correct font size', async () => {
2013
+ const doc = Document({ children: Heading({ level: 2, children: 'Sub' }) })
2014
+ const svg = (await render(doc, 'svg')) as string
2015
2015
  expect(svg).toContain('font-size="24"')
2016
2016
  expect(svg).toContain('font-weight="bold"')
2017
2017
  })
2018
2018
 
2019
- it("renders text with bold and italic", async () => {
2019
+ it('renders text with bold and italic', async () => {
2020
2020
  const doc = Document({
2021
2021
  children: [
2022
- Text({ bold: true, children: "Bold" }),
2023
- Text({ italic: true, children: "Italic" }),
2022
+ Text({ bold: true, children: 'Bold' }),
2023
+ Text({ italic: true, children: 'Italic' }),
2024
2024
  ],
2025
2025
  })
2026
- const svg = (await render(doc, "svg")) as string
2026
+ const svg = (await render(doc, 'svg')) as string
2027
2027
  expect(svg).toContain('font-weight="bold"')
2028
2028
  expect(svg).toContain('font-style="italic"')
2029
2029
  })
2030
2030
 
2031
- it("renders table with header and rows", async () => {
2031
+ it('renders table with header and rows', async () => {
2032
2032
  const doc = Document({
2033
2033
  children: Table({
2034
- columns: ["Name", "Price"],
2035
- rows: [["Widget", "$10"]],
2036
- headerStyle: { background: "#000", color: "#fff" },
2034
+ columns: ['Name', 'Price'],
2035
+ rows: [['Widget', '$10']],
2036
+ headerStyle: { background: '#000', color: '#fff' },
2037
2037
  striped: true,
2038
2038
  }),
2039
2039
  })
2040
- const svg = (await render(doc, "svg")) as string
2041
- expect(svg).toContain("Name")
2042
- expect(svg).toContain("Widget")
2040
+ const svg = (await render(doc, 'svg')) as string
2041
+ expect(svg).toContain('Name')
2042
+ expect(svg).toContain('Widget')
2043
2043
  expect(svg).toContain('fill="#000"')
2044
2044
  })
2045
2045
 
2046
- it("renders image from data URL", async () => {
2046
+ it('renders image from data URL', async () => {
2047
2047
  const doc = Document({
2048
2048
  children: Image({
2049
- src: "data:image/png;base64,abc",
2049
+ src: 'data:image/png;base64,abc',
2050
2050
  width: 200,
2051
2051
  height: 100,
2052
- caption: "Photo",
2052
+ caption: 'Photo',
2053
2053
  }),
2054
2054
  })
2055
- const svg = (await render(doc, "svg")) as string
2056
- expect(svg).toContain("<image")
2057
- expect(svg).toContain("data:image/png;base64,abc")
2058
- expect(svg).toContain("Photo")
2055
+ const svg = (await render(doc, 'svg')) as string
2056
+ expect(svg).toContain('<image')
2057
+ expect(svg).toContain('data:image/png;base64,abc')
2058
+ expect(svg).toContain('Photo')
2059
2059
  })
2060
2060
 
2061
- it("renders image placeholder for local paths", async () => {
2061
+ it('renders image placeholder for local paths', async () => {
2062
2062
  const doc = Document({
2063
- children: Image({ src: "/local.png", alt: "Local" }),
2063
+ children: Image({ src: '/local.png', alt: 'Local' }),
2064
2064
  })
2065
- const svg = (await render(doc, "svg")) as string
2066
- expect(svg).toContain("Local")
2065
+ const svg = (await render(doc, 'svg')) as string
2066
+ expect(svg).toContain('Local')
2067
2067
  expect(svg).toContain('fill="#f0f0f0"')
2068
2068
  })
2069
2069
 
2070
- it("renders list", async () => {
2070
+ it('renders list', async () => {
2071
2071
  const doc = Document({
2072
2072
  children: List({
2073
2073
  ordered: true,
2074
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
2074
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
2075
2075
  }),
2076
2076
  })
2077
- const svg = (await render(doc, "svg")) as string
2078
- expect(svg).toContain("1. one")
2079
- expect(svg).toContain("2. two")
2077
+ const svg = (await render(doc, 'svg')) as string
2078
+ expect(svg).toContain('1. one')
2079
+ expect(svg).toContain('2. two')
2080
2080
  })
2081
2081
 
2082
- it("renders unordered list", async () => {
2082
+ it('renders unordered list', async () => {
2083
2083
  const doc = Document({
2084
2084
  children: List({
2085
- children: [ListItem({ children: "a" }), ListItem({ children: "b" })],
2085
+ children: [ListItem({ children: 'a' }), ListItem({ children: 'b' })],
2086
2086
  }),
2087
2087
  })
2088
- const svg = (await render(doc, "svg")) as string
2089
- expect(svg).toContain("• a")
2090
- expect(svg).toContain("• b")
2088
+ const svg = (await render(doc, 'svg')) as string
2089
+ expect(svg).toContain('• a')
2090
+ expect(svg).toContain('• b')
2091
2091
  })
2092
2092
 
2093
- it("renders code block", async () => {
2094
- const doc = Document({ children: Code({ children: "const x = 1" }) })
2095
- const svg = (await render(doc, "svg")) as string
2096
- expect(svg).toContain("const x = 1")
2093
+ it('renders code block', async () => {
2094
+ const doc = Document({ children: Code({ children: 'const x = 1' }) })
2095
+ const svg = (await render(doc, 'svg')) as string
2096
+ expect(svg).toContain('const x = 1')
2097
2097
  expect(svg).toContain('font-family="monospace"')
2098
2098
  expect(svg).toContain('fill="#f5f5f5"') // background
2099
2099
  })
2100
2100
 
2101
- it("renders divider", async () => {
2102
- const doc = Document({ children: Divider({ color: "#ccc", thickness: 2 }) })
2103
- const svg = (await render(doc, "svg")) as string
2101
+ it('renders divider', async () => {
2102
+ const doc = Document({ children: Divider({ color: '#ccc', thickness: 2 }) })
2103
+ const svg = (await render(doc, 'svg')) as string
2104
2104
  expect(svg).toContain('stroke="#ccc"')
2105
2105
  expect(svg).toContain('stroke-width="2"')
2106
2106
  })
2107
2107
 
2108
- it("renders button", async () => {
2108
+ it('renders button', async () => {
2109
2109
  const doc = Document({
2110
2110
  children: Button({
2111
- href: "/pay",
2112
- background: "#4f46e5",
2113
- children: "Pay",
2111
+ href: '/pay',
2112
+ background: '#4f46e5',
2113
+ children: 'Pay',
2114
2114
  }),
2115
2115
  })
2116
- const svg = (await render(doc, "svg")) as string
2116
+ const svg = (await render(doc, 'svg')) as string
2117
2117
  expect(svg).toContain('fill="#4f46e5"')
2118
- expect(svg).toContain("Pay")
2118
+ expect(svg).toContain('Pay')
2119
2119
  })
2120
2120
 
2121
- it("renders quote", async () => {
2122
- const doc = Document({ children: Quote({ children: "wise words" }) })
2123
- const svg = (await render(doc, "svg")) as string
2124
- expect(svg).toContain("wise words")
2121
+ it('renders quote', async () => {
2122
+ const doc = Document({ children: Quote({ children: 'wise words' }) })
2123
+ const svg = (await render(doc, 'svg')) as string
2124
+ expect(svg).toContain('wise words')
2125
2125
  expect(svg).toContain('font-style="italic"')
2126
2126
  })
2127
2127
 
2128
- it("renders link", async () => {
2128
+ it('renders link', async () => {
2129
2129
  const doc = Document({
2130
- children: Link({ href: "https://x.com", children: "X" }),
2130
+ children: Link({ href: 'https://x.com', children: 'X' }),
2131
2131
  })
2132
- const svg = (await render(doc, "svg")) as string
2132
+ const svg = (await render(doc, 'svg')) as string
2133
2133
  expect(svg).toContain('<a href="https://x.com">')
2134
- expect(svg).toContain("X")
2134
+ expect(svg).toContain('X')
2135
2135
  })
2136
2136
 
2137
- it("renders spacer", async () => {
2137
+ it('renders spacer', async () => {
2138
2138
  const doc = Document({
2139
- children: [Text({ children: "A" }), Spacer({ height: 50 }), Text({ children: "B" })],
2139
+ children: [Text({ children: 'A' }), Spacer({ height: 50 }), Text({ children: 'B' })],
2140
2140
  })
2141
- const svg = (await render(doc, "svg")) as string
2142
- expect(svg).toContain("A")
2143
- expect(svg).toContain("B")
2141
+ const svg = (await render(doc, 'svg')) as string
2142
+ expect(svg).toContain('A')
2143
+ expect(svg).toContain('B')
2144
2144
  })
2145
2145
 
2146
- it("renders page-break as dashed line", async () => {
2146
+ it('renders page-break as dashed line', async () => {
2147
2147
  const doc = Document({ children: PageBreak() })
2148
- const svg = (await render(doc, "svg")) as string
2149
- expect(svg).toContain("stroke-dasharray")
2148
+ const svg = (await render(doc, 'svg')) as string
2149
+ expect(svg).toContain('stroke-dasharray')
2150
2150
  })
2151
2151
 
2152
- it("supports RTL direction", async () => {
2153
- const doc = Document({ children: Text({ children: "مرحبا" }) })
2154
- const svg = (await render(doc, "svg", { direction: "rtl" })) as string
2152
+ it('supports RTL direction', async () => {
2153
+ const doc = Document({ children: Text({ children: 'مرحبا' }) })
2154
+ const svg = (await render(doc, 'svg', { direction: 'rtl' })) as string
2155
2155
  expect(svg).toContain('direction="rtl"')
2156
2156
  })
2157
2157
 
2158
- it("auto-calculates height from content", async () => {
2158
+ it('auto-calculates height from content', async () => {
2159
2159
  const doc = Document({
2160
- children: [Heading({ children: "A" }), Text({ children: "B" }), Text({ children: "C" })],
2160
+ children: [Heading({ children: 'A' }), Text({ children: 'B' }), Text({ children: 'C' })],
2161
2161
  })
2162
- const svg = (await render(doc, "svg")) as string
2162
+ const svg = (await render(doc, 'svg')) as string
2163
2163
  const match = svg.match(/height="(\d+)"/)
2164
2164
  expect(match).toBeTruthy()
2165
2165
  expect(Number(match![1])).toBeGreaterThan(80)
2166
2166
  })
2167
2167
 
2168
- it("renders image from HTTP URL", async () => {
2168
+ it('renders image from HTTP URL', async () => {
2169
2169
  const doc = Document({
2170
- children: Image({ src: "https://x.com/img.png", width: 300 }),
2170
+ children: Image({ src: 'https://x.com/img.png', width: 300 }),
2171
2171
  })
2172
- const svg = (await render(doc, "svg")) as string
2173
- expect(svg).toContain("<image")
2174
- expect(svg).toContain("https://x.com/img.png")
2172
+ const svg = (await render(doc, 'svg')) as string
2173
+ expect(svg).toContain('<image')
2174
+ expect(svg).toContain('https://x.com/img.png')
2175
2175
  })
2176
2176
 
2177
- it("builder toSvg works", async () => {
2178
- const svg = await createDocument().heading("Hi").text("World").toSvg()
2179
- expect(svg).toContain("<svg")
2180
- expect(svg).toContain("Hi")
2181
- expect(svg).toContain("World")
2177
+ it('builder toSvg works', async () => {
2178
+ const svg = await createDocument().heading('Hi').text('World').toSvg()
2179
+ expect(svg).toContain('<svg')
2180
+ expect(svg).toContain('Hi')
2181
+ expect(svg).toContain('World')
2182
2182
  })
2183
2183
  })
2184
2184
 
2185
2185
  // ─── Teams Renderer ─────────────────────────────────────────────────────────
2186
2186
 
2187
- describe("Teams renderer", () => {
2188
- it("renders heading as TextBlock", async () => {
2189
- const doc = Document({ children: Heading({ children: "Hello" }) })
2190
- const json = (await render(doc, "teams")) as string
2187
+ describe('Teams renderer', () => {
2188
+ it('renders heading as TextBlock', async () => {
2189
+ const doc = Document({ children: Heading({ children: 'Hello' }) })
2190
+ const json = (await render(doc, 'teams')) as string
2191
2191
  const card = JSON.parse(json)
2192
- expect(card.type).toBe("AdaptiveCard")
2193
- expect(card.body[0].type).toBe("TextBlock")
2194
- expect(card.body[0].text).toBe("Hello")
2195
- expect(card.body[0].weight).toBe("bolder")
2192
+ expect(card.type).toBe('AdaptiveCard')
2193
+ expect(card.body[0].type).toBe('TextBlock')
2194
+ expect(card.body[0].text).toBe('Hello')
2195
+ expect(card.body[0].weight).toBe('bolder')
2196
2196
  })
2197
2197
 
2198
- it("renders bold text", async () => {
2199
- const doc = Document({ children: Text({ bold: true, children: "Bold" }) })
2200
- const json = (await render(doc, "teams")) as string
2198
+ it('renders bold text', async () => {
2199
+ const doc = Document({ children: Text({ bold: true, children: 'Bold' }) })
2200
+ const json = (await render(doc, 'teams')) as string
2201
2201
  const card = JSON.parse(json)
2202
- expect(card.body[0].text).toContain("**Bold**")
2202
+ expect(card.body[0].text).toContain('**Bold**')
2203
2203
  })
2204
2204
 
2205
- it("renders button as Action.OpenUrl", async () => {
2205
+ it('renders button as Action.OpenUrl', async () => {
2206
2206
  const doc = Document({
2207
- children: Button({ href: "/go", children: "Click" }),
2207
+ children: Button({ href: '/go', children: 'Click' }),
2208
2208
  })
2209
- const json = (await render(doc, "teams")) as string
2209
+ const json = (await render(doc, 'teams')) as string
2210
2210
  const card = JSON.parse(json)
2211
- expect(card.body[0].type).toBe("ActionSet")
2212
- expect(card.body[0].actions[0].type).toBe("Action.OpenUrl")
2211
+ expect(card.body[0].type).toBe('ActionSet')
2212
+ expect(card.body[0].actions[0].type).toBe('Action.OpenUrl')
2213
2213
  })
2214
2214
 
2215
- it("renders table as ColumnSet", async () => {
2215
+ it('renders table as ColumnSet', async () => {
2216
2216
  const doc = Document({
2217
- children: Table({ columns: ["A", "B"], rows: [["1", "2"]] }),
2217
+ children: Table({ columns: ['A', 'B'], rows: [['1', '2']] }),
2218
2218
  })
2219
- const json = (await render(doc, "teams")) as string
2219
+ const json = (await render(doc, 'teams')) as string
2220
2220
  const card = JSON.parse(json)
2221
- expect(card.body[0].type).toBe("ColumnSet")
2221
+ expect(card.body[0].type).toBe('ColumnSet')
2222
2222
  })
2223
2223
 
2224
- it("renders list", async () => {
2224
+ it('renders list', async () => {
2225
2225
  const doc = Document({
2226
2226
  children: List({
2227
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
2227
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
2228
2228
  }),
2229
2229
  })
2230
- const json = (await render(doc, "teams")) as string
2230
+ const json = (await render(doc, 'teams')) as string
2231
2231
  const card = JSON.parse(json)
2232
- expect(card.body[0].text).toContain("• one")
2232
+ expect(card.body[0].text).toContain('• one')
2233
2233
  })
2234
2234
 
2235
- it("renders code as monospace", async () => {
2236
- const doc = Document({ children: Code({ children: "x = 1" }) })
2237
- const json = (await render(doc, "teams")) as string
2235
+ it('renders code as monospace', async () => {
2236
+ const doc = Document({ children: Code({ children: 'x = 1' }) })
2237
+ const json = (await render(doc, 'teams')) as string
2238
2238
  const card = JSON.parse(json)
2239
- expect(card.body[0].fontType).toBe("monospace")
2239
+ expect(card.body[0].fontType).toBe('monospace')
2240
2240
  })
2241
2241
 
2242
- it("renders divider as separator", async () => {
2242
+ it('renders divider as separator', async () => {
2243
2243
  const doc = Document({ children: Divider() })
2244
- const json = (await render(doc, "teams")) as string
2244
+ const json = (await render(doc, 'teams')) as string
2245
2245
  const card = JSON.parse(json)
2246
2246
  expect(card.body[0].separator).toBe(true)
2247
2247
  })
2248
2248
 
2249
- it("renders image with URL", async () => {
2249
+ it('renders image with URL', async () => {
2250
2250
  const doc = Document({
2251
- children: Image({ src: "https://x.com/img.png", alt: "Photo" }),
2251
+ children: Image({ src: 'https://x.com/img.png', alt: 'Photo' }),
2252
2252
  })
2253
- const json = (await render(doc, "teams")) as string
2253
+ const json = (await render(doc, 'teams')) as string
2254
2254
  const card = JSON.parse(json)
2255
- expect(card.body[0].type).toBe("Image")
2255
+ expect(card.body[0].type).toBe('Image')
2256
2256
  })
2257
2257
 
2258
- it("renders quote as Container", async () => {
2259
- const doc = Document({ children: Quote({ children: "wise" }) })
2260
- const json = (await render(doc, "teams")) as string
2258
+ it('renders quote as Container', async () => {
2259
+ const doc = Document({ children: Quote({ children: 'wise' }) })
2260
+ const json = (await render(doc, 'teams')) as string
2261
2261
  const card = JSON.parse(json)
2262
- expect(card.body[0].type).toBe("Container")
2263
- expect(card.body[0].style).toBe("emphasis")
2262
+ expect(card.body[0].type).toBe('Container')
2263
+ expect(card.body[0].style).toBe('emphasis')
2264
2264
  })
2265
2265
 
2266
- it("renders link as markdown", async () => {
2266
+ it('renders link as markdown', async () => {
2267
2267
  const doc = Document({
2268
- children: Link({ href: "https://x.com", children: "X" }),
2268
+ children: Link({ href: 'https://x.com', children: 'X' }),
2269
2269
  })
2270
- const json = (await render(doc, "teams")) as string
2270
+ const json = (await render(doc, 'teams')) as string
2271
2271
  const card = JSON.parse(json)
2272
- expect(card.body[0].text).toContain("[X](https://x.com)")
2272
+ expect(card.body[0].text).toContain('[X](https://x.com)')
2273
2273
  })
2274
2274
 
2275
- it("builder toTeams works", async () => {
2276
- const json = await createDocument().heading("Hi").toTeams()
2275
+ it('builder toTeams works', async () => {
2276
+ const json = await createDocument().heading('Hi').toTeams()
2277
2277
  const card = JSON.parse(json)
2278
- expect(card.type).toBe("AdaptiveCard")
2278
+ expect(card.type).toBe('AdaptiveCard')
2279
2279
  })
2280
2280
  })
2281
2281
 
2282
2282
  // ─── Discord Renderer ───────────────────────────────────────────────────────
2283
2283
 
2284
- describe("Discord renderer", () => {
2285
- it("renders heading as embed title", async () => {
2286
- const doc = Document({ children: Heading({ children: "Title" }) })
2287
- const json = (await render(doc, "discord")) as string
2284
+ describe('Discord renderer', () => {
2285
+ it('renders heading as embed title', async () => {
2286
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2287
+ const json = (await render(doc, 'discord')) as string
2288
2288
  const payload = JSON.parse(json)
2289
- expect(payload.embeds[0].title).toBe("Title")
2289
+ expect(payload.embeds[0].title).toBe('Title')
2290
2290
  })
2291
2291
 
2292
- it("renders text in description", async () => {
2292
+ it('renders text in description', async () => {
2293
2293
  const doc = Document({
2294
- children: [Heading({ children: "T" }), Text({ children: "Body" })],
2294
+ children: [Heading({ children: 'T' }), Text({ children: 'Body' })],
2295
2295
  })
2296
- const json = (await render(doc, "discord")) as string
2296
+ const json = (await render(doc, 'discord')) as string
2297
2297
  const payload = JSON.parse(json)
2298
- expect(payload.embeds[0].description).toContain("Body")
2298
+ expect(payload.embeds[0].description).toContain('Body')
2299
2299
  })
2300
2300
 
2301
- it("renders small table as fields", async () => {
2301
+ it('renders small table as fields', async () => {
2302
2302
  const doc = Document({
2303
2303
  children: Table({
2304
- columns: ["A", "B"],
2304
+ columns: ['A', 'B'],
2305
2305
  rows: [
2306
- ["1", "2"],
2307
- ["3", "4"],
2306
+ ['1', '2'],
2307
+ ['3', '4'],
2308
2308
  ],
2309
2309
  }),
2310
2310
  })
2311
- const json = (await render(doc, "discord")) as string
2311
+ const json = (await render(doc, 'discord')) as string
2312
2312
  const payload = JSON.parse(json)
2313
2313
  expect(payload.embeds[0].fields).toHaveLength(2)
2314
- expect(payload.embeds[0].fields[0].name).toBe("A")
2314
+ expect(payload.embeds[0].fields[0].name).toBe('A')
2315
2315
  expect(payload.embeds[0].fields[0].inline).toBe(true)
2316
2316
  })
2317
2317
 
2318
- it("renders image as embed image", async () => {
2319
- const doc = Document({ children: Image({ src: "https://x.com/img.png" }) })
2320
- const json = (await render(doc, "discord")) as string
2318
+ it('renders image as embed image', async () => {
2319
+ const doc = Document({ children: Image({ src: 'https://x.com/img.png' }) })
2320
+ const json = (await render(doc, 'discord')) as string
2321
2321
  const payload = JSON.parse(json)
2322
- expect(payload.embeds[0].image.url).toBe("https://x.com/img.png")
2322
+ expect(payload.embeds[0].image.url).toBe('https://x.com/img.png')
2323
2323
  })
2324
2324
 
2325
- it("renders quote with >", async () => {
2326
- const doc = Document({ children: Quote({ children: "wise" }) })
2327
- const json = (await render(doc, "discord")) as string
2325
+ it('renders quote with >', async () => {
2326
+ const doc = Document({ children: Quote({ children: 'wise' }) })
2327
+ const json = (await render(doc, 'discord')) as string
2328
2328
  const payload = JSON.parse(json)
2329
- expect(payload.embeds[0].description).toContain("> wise")
2329
+ expect(payload.embeds[0].description).toContain('> wise')
2330
2330
  })
2331
2331
 
2332
- it("renders code block", async () => {
2332
+ it('renders code block', async () => {
2333
2333
  const doc = Document({
2334
- children: Code({ language: "js", children: "x()" }),
2334
+ children: Code({ language: 'js', children: 'x()' }),
2335
2335
  })
2336
- const json = (await render(doc, "discord")) as string
2336
+ const json = (await render(doc, 'discord')) as string
2337
2337
  const payload = JSON.parse(json)
2338
- expect(payload.embeds[0].description).toContain("```js")
2338
+ expect(payload.embeds[0].description).toContain('```js')
2339
2339
  })
2340
2340
 
2341
- it("renders list", async () => {
2341
+ it('renders list', async () => {
2342
2342
  const doc = Document({
2343
2343
  children: List({
2344
2344
  ordered: true,
2345
- children: [ListItem({ children: "a" })],
2345
+ children: [ListItem({ children: 'a' })],
2346
2346
  }),
2347
2347
  })
2348
- const json = (await render(doc, "discord")) as string
2348
+ const json = (await render(doc, 'discord')) as string
2349
2349
  const payload = JSON.parse(json)
2350
- expect(payload.embeds[0].description).toContain("1. a")
2350
+ expect(payload.embeds[0].description).toContain('1. a')
2351
2351
  })
2352
2352
 
2353
- it("builder toDiscord works", async () => {
2354
- const json = await createDocument().heading("Hi").text("World").toDiscord()
2353
+ it('builder toDiscord works', async () => {
2354
+ const json = await createDocument().heading('Hi').text('World').toDiscord()
2355
2355
  const payload = JSON.parse(json)
2356
2356
  expect(payload.embeds).toHaveLength(1)
2357
2357
  })
@@ -2359,214 +2359,214 @@ describe("Discord renderer", () => {
2359
2359
 
2360
2360
  // ─── Telegram Renderer ──────────────────────────────────────────────────────
2361
2361
 
2362
- describe("Telegram renderer", () => {
2363
- it("renders heading as bold", async () => {
2364
- const doc = Document({ children: Heading({ children: "Title" }) })
2365
- const html = (await render(doc, "telegram")) as string
2366
- expect(html).toContain("<b>Title</b>")
2362
+ describe('Telegram renderer', () => {
2363
+ it('renders heading as bold', async () => {
2364
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2365
+ const html = (await render(doc, 'telegram')) as string
2366
+ expect(html).toContain('<b>Title</b>')
2367
2367
  })
2368
2368
 
2369
- it("renders text with formatting", async () => {
2369
+ it('renders text with formatting', async () => {
2370
2370
  const doc = Document({
2371
2371
  children: [
2372
- Text({ bold: true, children: "Bold" }),
2373
- Text({ italic: true, children: "Italic" }),
2374
- Text({ underline: true, children: "Under" }),
2375
- Text({ strikethrough: true, children: "Struck" }),
2372
+ Text({ bold: true, children: 'Bold' }),
2373
+ Text({ italic: true, children: 'Italic' }),
2374
+ Text({ underline: true, children: 'Under' }),
2375
+ Text({ strikethrough: true, children: 'Struck' }),
2376
2376
  ],
2377
2377
  })
2378
- const html = (await render(doc, "telegram")) as string
2379
- expect(html).toContain("<b>Bold</b>")
2380
- expect(html).toContain("<i>Italic</i>")
2381
- expect(html).toContain("<u>Under</u>")
2382
- expect(html).toContain("<s>Struck</s>")
2378
+ const html = (await render(doc, 'telegram')) as string
2379
+ expect(html).toContain('<b>Bold</b>')
2380
+ expect(html).toContain('<i>Italic</i>')
2381
+ expect(html).toContain('<u>Under</u>')
2382
+ expect(html).toContain('<s>Struck</s>')
2383
2383
  })
2384
2384
 
2385
- it("renders link as <a>", async () => {
2385
+ it('renders link as <a>', async () => {
2386
2386
  const doc = Document({
2387
- children: Link({ href: "https://x.com", children: "X" }),
2387
+ children: Link({ href: 'https://x.com', children: 'X' }),
2388
2388
  })
2389
- const html = (await render(doc, "telegram")) as string
2389
+ const html = (await render(doc, 'telegram')) as string
2390
2390
  expect(html).toContain('<a href="https://x.com">X</a>')
2391
2391
  })
2392
2392
 
2393
- it("renders table as pre-formatted text", async () => {
2393
+ it('renders table as pre-formatted text', async () => {
2394
2394
  const doc = Document({
2395
- children: Table({ columns: ["A", "B"], rows: [["1", "2"]] }),
2395
+ children: Table({ columns: ['A', 'B'], rows: [['1', '2']] }),
2396
2396
  })
2397
- const html = (await render(doc, "telegram")) as string
2398
- expect(html).toContain("<pre>")
2399
- expect(html).toContain("A | B")
2400
- expect(html).toContain("1 | 2")
2397
+ const html = (await render(doc, 'telegram')) as string
2398
+ expect(html).toContain('<pre>')
2399
+ expect(html).toContain('A | B')
2400
+ expect(html).toContain('1 | 2')
2401
2401
  })
2402
2402
 
2403
- it("renders code with language", async () => {
2403
+ it('renders code with language', async () => {
2404
2404
  const doc = Document({
2405
- children: Code({ language: "python", children: "x = 1" }),
2405
+ children: Code({ language: 'python', children: 'x = 1' }),
2406
2406
  })
2407
- const html = (await render(doc, "telegram")) as string
2408
- expect(html).toContain("language-python")
2409
- expect(html).toContain("x = 1")
2407
+ const html = (await render(doc, 'telegram')) as string
2408
+ expect(html).toContain('language-python')
2409
+ expect(html).toContain('x = 1')
2410
2410
  })
2411
2411
 
2412
- it("renders code without language", async () => {
2413
- const doc = Document({ children: Code({ children: "x = 1" }) })
2414
- const html = (await render(doc, "telegram")) as string
2415
- expect(html).toContain("<pre>x = 1</pre>")
2412
+ it('renders code without language', async () => {
2413
+ const doc = Document({ children: Code({ children: 'x = 1' }) })
2414
+ const html = (await render(doc, 'telegram')) as string
2415
+ expect(html).toContain('<pre>x = 1</pre>')
2416
2416
  })
2417
2417
 
2418
- it("renders quote as blockquote", async () => {
2419
- const doc = Document({ children: Quote({ children: "wise" }) })
2420
- const html = (await render(doc, "telegram")) as string
2421
- expect(html).toContain("<blockquote>wise</blockquote>")
2418
+ it('renders quote as blockquote', async () => {
2419
+ const doc = Document({ children: Quote({ children: 'wise' }) })
2420
+ const html = (await render(doc, 'telegram')) as string
2421
+ expect(html).toContain('<blockquote>wise</blockquote>')
2422
2422
  })
2423
2423
 
2424
- it("renders list", async () => {
2424
+ it('renders list', async () => {
2425
2425
  const doc = Document({
2426
2426
  children: List({
2427
- children: [ListItem({ children: "one" }), ListItem({ children: "two" })],
2427
+ children: [ListItem({ children: 'one' }), ListItem({ children: 'two' })],
2428
2428
  }),
2429
2429
  })
2430
- const html = (await render(doc, "telegram")) as string
2431
- expect(html).toContain("• one")
2432
- expect(html).toContain("• two")
2430
+ const html = (await render(doc, 'telegram')) as string
2431
+ expect(html).toContain('• one')
2432
+ expect(html).toContain('• two')
2433
2433
  })
2434
2434
 
2435
- it("renders button as link", async () => {
2435
+ it('renders button as link', async () => {
2436
2436
  const doc = Document({
2437
- children: Button({ href: "/pay", children: "Pay" }),
2437
+ children: Button({ href: '/pay', children: 'Pay' }),
2438
2438
  })
2439
- const html = (await render(doc, "telegram")) as string
2439
+ const html = (await render(doc, 'telegram')) as string
2440
2440
  expect(html).toContain('<a href="/pay">Pay</a>')
2441
2441
  })
2442
2442
 
2443
- it("skips images (sent separately in Telegram)", async () => {
2444
- const doc = Document({ children: Image({ src: "https://x.com/img.png" }) })
2445
- const html = (await render(doc, "telegram")) as string
2446
- expect(html).toBe("")
2443
+ it('skips images (sent separately in Telegram)', async () => {
2444
+ const doc = Document({ children: Image({ src: 'https://x.com/img.png' }) })
2445
+ const html = (await render(doc, 'telegram')) as string
2446
+ expect(html).toBe('')
2447
2447
  })
2448
2448
 
2449
- it("escapes HTML entities", async () => {
2449
+ it('escapes HTML entities', async () => {
2450
2450
  const doc = Document({
2451
- children: Text({ children: "<script>alert(1)</script>" }),
2451
+ children: Text({ children: '<script>alert(1)</script>' }),
2452
2452
  })
2453
- const html = (await render(doc, "telegram")) as string
2454
- expect(html).not.toContain("<script>")
2455
- expect(html).toContain("&lt;script&gt;")
2453
+ const html = (await render(doc, 'telegram')) as string
2454
+ expect(html).not.toContain('<script>')
2455
+ expect(html).toContain('&lt;script&gt;')
2456
2456
  })
2457
2457
 
2458
- it("builder toTelegram works", async () => {
2459
- const html = await createDocument().heading("Hi").text("World").toTelegram()
2460
- expect(html).toContain("<b>Hi</b>")
2461
- expect(html).toContain("World")
2458
+ it('builder toTelegram works', async () => {
2459
+ const html = await createDocument().heading('Hi').text('World').toTelegram()
2460
+ expect(html).toContain('<b>Hi</b>')
2461
+ expect(html).toContain('World')
2462
2462
  })
2463
2463
  })
2464
2464
 
2465
2465
  // ─── Notion Renderer ────────────────────────────────────────────────────────
2466
2466
 
2467
- describe("Notion renderer", () => {
2468
- it("renders heading as heading block", async () => {
2469
- const doc = Document({ children: Heading({ children: "Title" }) })
2470
- const json = (await render(doc, "notion")) as string
2467
+ describe('Notion renderer', () => {
2468
+ it('renders heading as heading block', async () => {
2469
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2470
+ const json = (await render(doc, 'notion')) as string
2471
2471
  const parsed = JSON.parse(json)
2472
- expect(parsed.children[0].type).toBe("heading_1")
2472
+ expect(parsed.children[0].type).toBe('heading_1')
2473
2473
  })
2474
2474
 
2475
- it("renders h2 as heading_2", async () => {
2476
- const doc = Document({ children: Heading({ level: 2, children: "Sub" }) })
2477
- const json = (await render(doc, "notion")) as string
2475
+ it('renders h2 as heading_2', async () => {
2476
+ const doc = Document({ children: Heading({ level: 2, children: 'Sub' }) })
2477
+ const json = (await render(doc, 'notion')) as string
2478
2478
  const parsed = JSON.parse(json)
2479
- expect(parsed.children[0].type).toBe("heading_2")
2479
+ expect(parsed.children[0].type).toBe('heading_2')
2480
2480
  })
2481
2481
 
2482
- it("renders h3+ as heading_3", async () => {
2483
- const doc = Document({ children: Heading({ level: 4, children: "Sub" }) })
2484
- const json = (await render(doc, "notion")) as string
2482
+ it('renders h3+ as heading_3', async () => {
2483
+ const doc = Document({ children: Heading({ level: 4, children: 'Sub' }) })
2484
+ const json = (await render(doc, 'notion')) as string
2485
2485
  const parsed = JSON.parse(json)
2486
- expect(parsed.children[0].type).toBe("heading_3")
2486
+ expect(parsed.children[0].type).toBe('heading_3')
2487
2487
  })
2488
2488
 
2489
- it("renders text as paragraph", async () => {
2490
- const doc = Document({ children: Text({ bold: true, children: "Bold" }) })
2491
- const json = (await render(doc, "notion")) as string
2489
+ it('renders text as paragraph', async () => {
2490
+ const doc = Document({ children: Text({ bold: true, children: 'Bold' }) })
2491
+ const json = (await render(doc, 'notion')) as string
2492
2492
  const parsed = JSON.parse(json)
2493
- expect(parsed.children[0].type).toBe("paragraph")
2493
+ expect(parsed.children[0].type).toBe('paragraph')
2494
2494
  expect(parsed.children[0].paragraph.rich_text[0].annotations.bold).toBe(true)
2495
2495
  })
2496
2496
 
2497
- it("renders table with header row", async () => {
2497
+ it('renders table with header row', async () => {
2498
2498
  const doc = Document({
2499
- children: Table({ columns: ["A", "B"], rows: [["1", "2"]] }),
2499
+ children: Table({ columns: ['A', 'B'], rows: [['1', '2']] }),
2500
2500
  })
2501
- const json = (await render(doc, "notion")) as string
2501
+ const json = (await render(doc, 'notion')) as string
2502
2502
  const parsed = JSON.parse(json)
2503
- expect(parsed.children[0].type).toBe("table")
2503
+ expect(parsed.children[0].type).toBe('table')
2504
2504
  expect(parsed.children[0].table.has_column_header).toBe(true)
2505
2505
  })
2506
2506
 
2507
- it("renders bulleted list", async () => {
2507
+ it('renders bulleted list', async () => {
2508
2508
  const doc = Document({
2509
- children: List({ children: [ListItem({ children: "a" })] }),
2509
+ children: List({ children: [ListItem({ children: 'a' })] }),
2510
2510
  })
2511
- const json = (await render(doc, "notion")) as string
2511
+ const json = (await render(doc, 'notion')) as string
2512
2512
  const parsed = JSON.parse(json)
2513
- expect(parsed.children[0].type).toBe("bulleted_list_item")
2513
+ expect(parsed.children[0].type).toBe('bulleted_list_item')
2514
2514
  })
2515
2515
 
2516
- it("renders numbered list", async () => {
2516
+ it('renders numbered list', async () => {
2517
2517
  const doc = Document({
2518
2518
  children: List({
2519
2519
  ordered: true,
2520
- children: [ListItem({ children: "a" })],
2520
+ children: [ListItem({ children: 'a' })],
2521
2521
  }),
2522
2522
  })
2523
- const json = (await render(doc, "notion")) as string
2523
+ const json = (await render(doc, 'notion')) as string
2524
2524
  const parsed = JSON.parse(json)
2525
- expect(parsed.children[0].type).toBe("numbered_list_item")
2525
+ expect(parsed.children[0].type).toBe('numbered_list_item')
2526
2526
  })
2527
2527
 
2528
- it("renders code block", async () => {
2528
+ it('renders code block', async () => {
2529
2529
  const doc = Document({
2530
- children: Code({ language: "python", children: "x = 1" }),
2530
+ children: Code({ language: 'python', children: 'x = 1' }),
2531
2531
  })
2532
- const json = (await render(doc, "notion")) as string
2532
+ const json = (await render(doc, 'notion')) as string
2533
2533
  const parsed = JSON.parse(json)
2534
- expect(parsed.children[0].type).toBe("code")
2535
- expect(parsed.children[0].code.language).toBe("python")
2534
+ expect(parsed.children[0].type).toBe('code')
2535
+ expect(parsed.children[0].code.language).toBe('python')
2536
2536
  })
2537
2537
 
2538
- it("renders quote", async () => {
2539
- const doc = Document({ children: Quote({ children: "wise" }) })
2540
- const json = (await render(doc, "notion")) as string
2538
+ it('renders quote', async () => {
2539
+ const doc = Document({ children: Quote({ children: 'wise' }) })
2540
+ const json = (await render(doc, 'notion')) as string
2541
2541
  const parsed = JSON.parse(json)
2542
- expect(parsed.children[0].type).toBe("quote")
2542
+ expect(parsed.children[0].type).toBe('quote')
2543
2543
  })
2544
2544
 
2545
- it("renders divider", async () => {
2545
+ it('renders divider', async () => {
2546
2546
  const doc = Document({ children: Divider() })
2547
- const json = (await render(doc, "notion")) as string
2547
+ const json = (await render(doc, 'notion')) as string
2548
2548
  const parsed = JSON.parse(json)
2549
- expect(parsed.children[0].type).toBe("divider")
2549
+ expect(parsed.children[0].type).toBe('divider')
2550
2550
  })
2551
2551
 
2552
- it("renders image with URL", async () => {
2553
- const doc = Document({ children: Image({ src: "https://x.com/img.png" }) })
2554
- const json = (await render(doc, "notion")) as string
2552
+ it('renders image with URL', async () => {
2553
+ const doc = Document({ children: Image({ src: 'https://x.com/img.png' }) })
2554
+ const json = (await render(doc, 'notion')) as string
2555
2555
  const parsed = JSON.parse(json)
2556
- expect(parsed.children[0].type).toBe("image")
2556
+ expect(parsed.children[0].type).toBe('image')
2557
2557
  })
2558
2558
 
2559
- it("renders link as paragraph with link", async () => {
2559
+ it('renders link as paragraph with link', async () => {
2560
2560
  const doc = Document({
2561
- children: Link({ href: "https://x.com", children: "X" }),
2561
+ children: Link({ href: 'https://x.com', children: 'X' }),
2562
2562
  })
2563
- const json = (await render(doc, "notion")) as string
2563
+ const json = (await render(doc, 'notion')) as string
2564
2564
  const parsed = JSON.parse(json)
2565
- expect(parsed.children[0].paragraph.rich_text[0].text.link.url).toBe("https://x.com")
2565
+ expect(parsed.children[0].paragraph.rich_text[0].text.link.url).toBe('https://x.com')
2566
2566
  })
2567
2567
 
2568
- it("builder toNotion works", async () => {
2569
- const json = await createDocument().heading("Hi").toNotion()
2568
+ it('builder toNotion works', async () => {
2569
+ const json = await createDocument().heading('Hi').toNotion()
2570
2570
  const parsed = JSON.parse(json)
2571
2571
  expect(parsed.children.length).toBeGreaterThan(0)
2572
2572
  })
@@ -2574,298 +2574,298 @@ describe("Notion renderer", () => {
2574
2574
 
2575
2575
  // ─── Confluence/Jira Renderer ───────────────────────────────────────────────
2576
2576
 
2577
- describe("Confluence renderer", () => {
2578
- it("renders ADF document", async () => {
2579
- const doc = Document({ children: Heading({ children: "Title" }) })
2580
- const json = (await render(doc, "confluence")) as string
2577
+ describe('Confluence renderer', () => {
2578
+ it('renders ADF document', async () => {
2579
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2580
+ const json = (await render(doc, 'confluence')) as string
2581
2581
  const adf = JSON.parse(json)
2582
2582
  expect(adf.version).toBe(1)
2583
- expect(adf.type).toBe("doc")
2584
- expect(adf.content[0].type).toBe("heading")
2583
+ expect(adf.type).toBe('doc')
2584
+ expect(adf.content[0].type).toBe('heading')
2585
2585
  })
2586
2586
 
2587
- it("renders text with marks", async () => {
2587
+ it('renders text with marks', async () => {
2588
2588
  const doc = Document({
2589
- children: Text({ bold: true, italic: true, children: "styled" }),
2589
+ children: Text({ bold: true, italic: true, children: 'styled' }),
2590
2590
  })
2591
- const json = (await render(doc, "confluence")) as string
2591
+ const json = (await render(doc, 'confluence')) as string
2592
2592
  const adf = JSON.parse(json)
2593
2593
  const marks = adf.content[0].content[0].marks
2594
- expect(marks.some((m: any) => m.type === "strong")).toBe(true)
2595
- expect(marks.some((m: any) => m.type === "em")).toBe(true)
2594
+ expect(marks.some((m: any) => m.type === 'strong')).toBe(true)
2595
+ expect(marks.some((m: any) => m.type === 'em')).toBe(true)
2596
2596
  })
2597
2597
 
2598
- it("renders table", async () => {
2598
+ it('renders table', async () => {
2599
2599
  const doc = Document({
2600
- children: Table({ columns: ["A"], rows: [["1"]] }),
2600
+ children: Table({ columns: ['A'], rows: [['1']] }),
2601
2601
  })
2602
- const json = (await render(doc, "confluence")) as string
2602
+ const json = (await render(doc, 'confluence')) as string
2603
2603
  const adf = JSON.parse(json)
2604
- expect(adf.content[0].type).toBe("table")
2605
- expect(adf.content[0].content[0].content[0].type).toBe("tableHeader")
2604
+ expect(adf.content[0].type).toBe('table')
2605
+ expect(adf.content[0].content[0].content[0].type).toBe('tableHeader')
2606
2606
  })
2607
2607
 
2608
- it("renders ordered list", async () => {
2608
+ it('renders ordered list', async () => {
2609
2609
  const doc = Document({
2610
2610
  children: List({
2611
2611
  ordered: true,
2612
- children: [ListItem({ children: "a" })],
2612
+ children: [ListItem({ children: 'a' })],
2613
2613
  }),
2614
2614
  })
2615
- const json = (await render(doc, "confluence")) as string
2615
+ const json = (await render(doc, 'confluence')) as string
2616
2616
  const adf = JSON.parse(json)
2617
- expect(adf.content[0].type).toBe("orderedList")
2617
+ expect(adf.content[0].type).toBe('orderedList')
2618
2618
  })
2619
2619
 
2620
- it("renders code block", async () => {
2620
+ it('renders code block', async () => {
2621
2621
  const doc = Document({
2622
- children: Code({ language: "java", children: "int x = 1;" }),
2622
+ children: Code({ language: 'java', children: 'int x = 1;' }),
2623
2623
  })
2624
- const json = (await render(doc, "confluence")) as string
2624
+ const json = (await render(doc, 'confluence')) as string
2625
2625
  const adf = JSON.parse(json)
2626
- expect(adf.content[0].type).toBe("codeBlock")
2627
- expect(adf.content[0].attrs.language).toBe("java")
2626
+ expect(adf.content[0].type).toBe('codeBlock')
2627
+ expect(adf.content[0].attrs.language).toBe('java')
2628
2628
  })
2629
2629
 
2630
- it("renders blockquote", async () => {
2631
- const doc = Document({ children: Quote({ children: "wise" }) })
2632
- const json = (await render(doc, "confluence")) as string
2630
+ it('renders blockquote', async () => {
2631
+ const doc = Document({ children: Quote({ children: 'wise' }) })
2632
+ const json = (await render(doc, 'confluence')) as string
2633
2633
  const adf = JSON.parse(json)
2634
- expect(adf.content[0].type).toBe("blockquote")
2634
+ expect(adf.content[0].type).toBe('blockquote')
2635
2635
  })
2636
2636
 
2637
- it("renders rule (divider)", async () => {
2637
+ it('renders rule (divider)', async () => {
2638
2638
  const doc = Document({ children: Divider() })
2639
- const json = (await render(doc, "confluence")) as string
2639
+ const json = (await render(doc, 'confluence')) as string
2640
2640
  const adf = JSON.parse(json)
2641
- expect(adf.content[0].type).toBe("rule")
2641
+ expect(adf.content[0].type).toBe('rule')
2642
2642
  })
2643
2643
 
2644
- it("renders link with href", async () => {
2644
+ it('renders link with href', async () => {
2645
2645
  const doc = Document({
2646
- children: Link({ href: "https://x.com", children: "X" }),
2646
+ children: Link({ href: 'https://x.com', children: 'X' }),
2647
2647
  })
2648
- const json = (await render(doc, "confluence")) as string
2648
+ const json = (await render(doc, 'confluence')) as string
2649
2649
  const adf = JSON.parse(json)
2650
- expect(adf.content[0].content[0].marks[0].attrs.href).toBe("https://x.com")
2650
+ expect(adf.content[0].content[0].marks[0].attrs.href).toBe('https://x.com')
2651
2651
  })
2652
2652
 
2653
- it("builder toConfluence works", async () => {
2654
- const json = await createDocument().heading("Hi").toConfluence()
2653
+ it('builder toConfluence works', async () => {
2654
+ const json = await createDocument().heading('Hi').toConfluence()
2655
2655
  const adf = JSON.parse(json)
2656
- expect(adf.type).toBe("doc")
2656
+ expect(adf.type).toBe('doc')
2657
2657
  })
2658
2658
  })
2659
2659
 
2660
2660
  // ─── WhatsApp Renderer ──────────────────────────────────────────────────────
2661
2661
 
2662
- describe("WhatsApp renderer", () => {
2663
- it("renders heading as bold", async () => {
2664
- const doc = Document({ children: Heading({ children: "Title" }) })
2665
- const text = (await render(doc, "whatsapp")) as string
2666
- expect(text).toContain("*Title*")
2662
+ describe('WhatsApp renderer', () => {
2663
+ it('renders heading as bold', async () => {
2664
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2665
+ const text = (await render(doc, 'whatsapp')) as string
2666
+ expect(text).toContain('*Title*')
2667
2667
  })
2668
2668
 
2669
- it("renders bold, italic, strikethrough", async () => {
2669
+ it('renders bold, italic, strikethrough', async () => {
2670
2670
  const doc = Document({
2671
2671
  children: [
2672
- Text({ bold: true, children: "Bold" }),
2673
- Text({ italic: true, children: "Italic" }),
2674
- Text({ strikethrough: true, children: "Struck" }),
2672
+ Text({ bold: true, children: 'Bold' }),
2673
+ Text({ italic: true, children: 'Italic' }),
2674
+ Text({ strikethrough: true, children: 'Struck' }),
2675
2675
  ],
2676
2676
  })
2677
- const text = (await render(doc, "whatsapp")) as string
2678
- expect(text).toContain("*Bold*")
2679
- expect(text).toContain("_Italic_")
2680
- expect(text).toContain("~Struck~")
2677
+ const text = (await render(doc, 'whatsapp')) as string
2678
+ expect(text).toContain('*Bold*')
2679
+ expect(text).toContain('_Italic_')
2680
+ expect(text).toContain('~Struck~')
2681
2681
  })
2682
2682
 
2683
- it("renders code as triple backticks", async () => {
2684
- const doc = Document({ children: Code({ children: "x = 1" }) })
2685
- const text = (await render(doc, "whatsapp")) as string
2686
- expect(text).toContain("```x = 1```")
2683
+ it('renders code as triple backticks', async () => {
2684
+ const doc = Document({ children: Code({ children: 'x = 1' }) })
2685
+ const text = (await render(doc, 'whatsapp')) as string
2686
+ expect(text).toContain('```x = 1```')
2687
2687
  })
2688
2688
 
2689
- it("renders quote with >", async () => {
2690
- const doc = Document({ children: Quote({ children: "wise" }) })
2691
- const text = (await render(doc, "whatsapp")) as string
2692
- expect(text).toContain("> wise")
2689
+ it('renders quote with >', async () => {
2690
+ const doc = Document({ children: Quote({ children: 'wise' }) })
2691
+ const text = (await render(doc, 'whatsapp')) as string
2692
+ expect(text).toContain('> wise')
2693
2693
  })
2694
2694
 
2695
- it("renders link as text + URL", async () => {
2695
+ it('renders link as text + URL', async () => {
2696
2696
  const doc = Document({
2697
- children: Link({ href: "https://x.com", children: "X" }),
2697
+ children: Link({ href: 'https://x.com', children: 'X' }),
2698
2698
  })
2699
- const text = (await render(doc, "whatsapp")) as string
2700
- expect(text).toContain("X: https://x.com")
2699
+ const text = (await render(doc, 'whatsapp')) as string
2700
+ expect(text).toContain('X: https://x.com')
2701
2701
  })
2702
2702
 
2703
- it("renders table", async () => {
2703
+ it('renders table', async () => {
2704
2704
  const doc = Document({
2705
- children: Table({ columns: ["A", "B"], rows: [["1", "2"]] }),
2705
+ children: Table({ columns: ['A', 'B'], rows: [['1', '2']] }),
2706
2706
  })
2707
- const text = (await render(doc, "whatsapp")) as string
2708
- expect(text).toContain("*A* | *B*")
2709
- expect(text).toContain("1 | 2")
2707
+ const text = (await render(doc, 'whatsapp')) as string
2708
+ expect(text).toContain('*A* | *B*')
2709
+ expect(text).toContain('1 | 2')
2710
2710
  })
2711
2711
 
2712
- it("renders list", async () => {
2712
+ it('renders list', async () => {
2713
2713
  const doc = Document({
2714
- children: List({ children: [ListItem({ children: "one" })] }),
2714
+ children: List({ children: [ListItem({ children: 'one' })] }),
2715
2715
  })
2716
- const text = (await render(doc, "whatsapp")) as string
2717
- expect(text).toContain("• one")
2716
+ const text = (await render(doc, 'whatsapp')) as string
2717
+ expect(text).toContain('• one')
2718
2718
  })
2719
2719
 
2720
- it("skips images", async () => {
2721
- const doc = Document({ children: Image({ src: "https://x.com/img.png" }) })
2722
- const text = (await render(doc, "whatsapp")) as string
2723
- expect(text).toBe("")
2720
+ it('skips images', async () => {
2721
+ const doc = Document({ children: Image({ src: 'https://x.com/img.png' }) })
2722
+ const text = (await render(doc, 'whatsapp')) as string
2723
+ expect(text).toBe('')
2724
2724
  })
2725
2725
 
2726
- it("builder toWhatsApp works", async () => {
2727
- const text = await createDocument().heading("Hi").text("World").toWhatsApp()
2728
- expect(text).toContain("*Hi*")
2729
- expect(text).toContain("World")
2726
+ it('builder toWhatsApp works', async () => {
2727
+ const text = await createDocument().heading('Hi').text('World').toWhatsApp()
2728
+ expect(text).toContain('*Hi*')
2729
+ expect(text).toContain('World')
2730
2730
  })
2731
2731
  })
2732
2732
 
2733
2733
  // ─── Google Chat Renderer ───────────────────────────────────────────────────
2734
2734
 
2735
- describe("Google Chat renderer", () => {
2736
- it("renders card with header", async () => {
2735
+ describe('Google Chat renderer', () => {
2736
+ it('renders card with header', async () => {
2737
2737
  const doc = Document({
2738
- title: "Report",
2739
- children: Text({ children: "Body" }),
2738
+ title: 'Report',
2739
+ children: Text({ children: 'Body' }),
2740
2740
  })
2741
- const json = (await render(doc, "google-chat")) as string
2741
+ const json = (await render(doc, 'google-chat')) as string
2742
2742
  const card = JSON.parse(json)
2743
- expect(card.cardsV2[0].card.header.title).toBe("Report")
2743
+ expect(card.cardsV2[0].card.header.title).toBe('Report')
2744
2744
  })
2745
2745
 
2746
- it("renders heading as decorated text", async () => {
2747
- const doc = Document({ children: Heading({ children: "Title" }) })
2748
- const json = (await render(doc, "google-chat")) as string
2746
+ it('renders heading as decorated text', async () => {
2747
+ const doc = Document({ children: Heading({ children: 'Title' }) })
2748
+ const json = (await render(doc, 'google-chat')) as string
2749
2749
  const card = JSON.parse(json)
2750
- expect(card.cardsV2[0].card.sections[0].widgets[0].decoratedText.text).toContain("<b>Title</b>")
2750
+ expect(card.cardsV2[0].card.sections[0].widgets[0].decoratedText.text).toContain('<b>Title</b>')
2751
2751
  })
2752
2752
 
2753
- it("renders text paragraph", async () => {
2754
- const doc = Document({ children: Text({ bold: true, children: "Bold" }) })
2755
- const json = (await render(doc, "google-chat")) as string
2753
+ it('renders text paragraph', async () => {
2754
+ const doc = Document({ children: Text({ bold: true, children: 'Bold' }) })
2755
+ const json = (await render(doc, 'google-chat')) as string
2756
2756
  const card = JSON.parse(json)
2757
- expect(card.cardsV2[0].card.sections[0].widgets[0].textParagraph.text).toContain("<b>Bold</b>")
2757
+ expect(card.cardsV2[0].card.sections[0].widgets[0].textParagraph.text).toContain('<b>Bold</b>')
2758
2758
  })
2759
2759
 
2760
- it("renders button", async () => {
2760
+ it('renders button', async () => {
2761
2761
  const doc = Document({
2762
- children: Button({ href: "/go", children: "Click" }),
2762
+ children: Button({ href: '/go', children: 'Click' }),
2763
2763
  })
2764
- const json = (await render(doc, "google-chat")) as string
2764
+ const json = (await render(doc, 'google-chat')) as string
2765
2765
  const card = JSON.parse(json)
2766
- expect(card.cardsV2[0].card.sections[0].widgets[0].buttonList.buttons[0].text).toBe("Click")
2766
+ expect(card.cardsV2[0].card.sections[0].widgets[0].buttonList.buttons[0].text).toBe('Click')
2767
2767
  })
2768
2768
 
2769
- it("renders divider", async () => {
2769
+ it('renders divider', async () => {
2770
2770
  const doc = Document({ children: Divider() })
2771
- const json = (await render(doc, "google-chat")) as string
2771
+ const json = (await render(doc, 'google-chat')) as string
2772
2772
  const card = JSON.parse(json)
2773
2773
  expect(card.cardsV2[0].card.sections[0].widgets[0].divider).toBeDefined()
2774
2774
  })
2775
2775
 
2776
- it("renders image", async () => {
2776
+ it('renders image', async () => {
2777
2777
  const doc = Document({
2778
- children: Image({ src: "https://x.com/img.png", alt: "Photo" }),
2778
+ children: Image({ src: 'https://x.com/img.png', alt: 'Photo' }),
2779
2779
  })
2780
- const json = (await render(doc, "google-chat")) as string
2780
+ const json = (await render(doc, 'google-chat')) as string
2781
2781
  const card = JSON.parse(json)
2782
- expect(card.cardsV2[0].card.sections[0].widgets[0].image.imageUrl).toBe("https://x.com/img.png")
2782
+ expect(card.cardsV2[0].card.sections[0].widgets[0].image.imageUrl).toBe('https://x.com/img.png')
2783
2783
  })
2784
2784
 
2785
- it("renders link", async () => {
2785
+ it('renders link', async () => {
2786
2786
  const doc = Document({
2787
- children: Link({ href: "https://x.com", children: "X" }),
2787
+ children: Link({ href: 'https://x.com', children: 'X' }),
2788
2788
  })
2789
- const json = (await render(doc, "google-chat")) as string
2789
+ const json = (await render(doc, 'google-chat')) as string
2790
2790
  const card = JSON.parse(json)
2791
2791
  expect(card.cardsV2[0].card.sections[0].widgets[0].textParagraph.text).toContain(
2792
2792
  'href="https://x.com"',
2793
2793
  )
2794
2794
  })
2795
2795
 
2796
- it("renders list", async () => {
2796
+ it('renders list', async () => {
2797
2797
  const doc = Document({
2798
- children: List({ children: [ListItem({ children: "one" })] }),
2798
+ children: List({ children: [ListItem({ children: 'one' })] }),
2799
2799
  })
2800
- const json = (await render(doc, "google-chat")) as string
2800
+ const json = (await render(doc, 'google-chat')) as string
2801
2801
  const card = JSON.parse(json)
2802
- expect(card.cardsV2[0].card.sections[0].widgets[0].textParagraph.text).toContain("• one")
2802
+ expect(card.cardsV2[0].card.sections[0].widgets[0].textParagraph.text).toContain('• one')
2803
2803
  })
2804
2804
 
2805
- it("uses first heading as title when no title prop", async () => {
2805
+ it('uses first heading as title when no title prop', async () => {
2806
2806
  const doc = Document({
2807
- children: [Heading({ children: "Auto Title" }), Text({ children: "body" })],
2807
+ children: [Heading({ children: 'Auto Title' }), Text({ children: 'body' })],
2808
2808
  })
2809
- const json = (await render(doc, "google-chat")) as string
2809
+ const json = (await render(doc, 'google-chat')) as string
2810
2810
  const card = JSON.parse(json)
2811
- expect(card.cardsV2[0].card.header.title).toBe("Auto Title")
2811
+ expect(card.cardsV2[0].card.header.title).toBe('Auto Title')
2812
2812
  })
2813
2813
 
2814
- it("builder toGoogleChat works", async () => {
2815
- const json = await createDocument({ title: "Hi" }).text("World").toGoogleChat()
2814
+ it('builder toGoogleChat works', async () => {
2815
+ const json = await createDocument({ title: 'Hi' }).text('World').toGoogleChat()
2816
2816
  const card = JSON.parse(json)
2817
- expect(card.cardsV2[0].card.header.title).toBe("Hi")
2817
+ expect(card.cardsV2[0].card.header.title).toBe('Hi')
2818
2818
  })
2819
2819
  })
2820
2820
 
2821
- describe("builder toSlack", () => {
2822
- it("renders to Slack JSON", async () => {
2823
- const result = await createDocument().heading("Hi").text("World").toSlack()
2821
+ describe('builder toSlack', () => {
2822
+ it('renders to Slack JSON', async () => {
2823
+ const result = await createDocument().heading('Hi').text('World').toSlack()
2824
2824
  const parsed = JSON.parse(result)
2825
2825
  expect(parsed.blocks).toHaveLength(2)
2826
- expect(parsed.blocks[0].type).toBe("header")
2827
- expect(parsed.blocks[1].type).toBe("section")
2826
+ expect(parsed.blocks[0].type).toBe('header')
2827
+ expect(parsed.blocks[1].type).toBe('section')
2828
2828
  })
2829
2829
  })
2830
2830
 
2831
2831
  // ─── Builder .add() and .section() ──────────────────────────────────────────
2832
2832
 
2833
- describe("builder .add() and .section()", () => {
2833
+ describe('builder .add() and .section()', () => {
2834
2834
  // build() wraps sections in Document({ children: [Page({ children: sections })] })
2835
2835
  // so actual content nodes are inside the page's children
2836
2836
  function getPageChildren(doc: ReturnType<typeof createDocument>) {
2837
2837
  const node = doc.build()
2838
- expect(node.type).toBe("document")
2838
+ expect(node.type).toBe('document')
2839
2839
  const page = node.children[0]!
2840
- expect(typeof page !== "string" && page.type).toBe("page")
2841
- return typeof page !== "string" ? page.children : []
2840
+ expect(typeof page !== 'string' && page.type).toBe('page')
2841
+ return typeof page !== 'string' ? page.children : []
2842
2842
  }
2843
2843
 
2844
- it("add() with a single node adds it to the document", () => {
2845
- const doc = createDocument().add(Text({ children: "hello" }))
2844
+ it('add() with a single node adds it to the document', () => {
2845
+ const doc = createDocument().add(Text({ children: 'hello' }))
2846
2846
  const children = getPageChildren(doc)
2847
2847
  expect(children).toHaveLength(1)
2848
2848
  const child = children[0]!
2849
- expect(typeof child !== "string" && child.type).toBe("text")
2849
+ expect(typeof child !== 'string' && child.type).toBe('text')
2850
2850
  })
2851
2851
 
2852
- it("add() with an array adds multiple nodes", () => {
2853
- const doc = createDocument().add([Heading({ children: "a" }), Text({ children: "b" })])
2852
+ it('add() with an array adds multiple nodes', () => {
2853
+ const doc = createDocument().add([Heading({ children: 'a' }), Text({ children: 'b' })])
2854
2854
  const children = getPageChildren(doc)
2855
2855
  expect(children).toHaveLength(2)
2856
2856
  const first = children[0]!
2857
2857
  const second = children[1]!
2858
- expect(typeof first !== "string" && first.type).toBe("heading")
2859
- expect(typeof second !== "string" && second.type).toBe("text")
2858
+ expect(typeof first !== 'string' && first.type).toBe('heading')
2859
+ expect(typeof second !== 'string' && second.type).toBe('text')
2860
2860
  })
2861
2861
 
2862
- it("section() wraps children in a Section node", () => {
2863
- const doc = createDocument().section([Text({ children: "a" }), Text({ children: "b" })])
2862
+ it('section() wraps children in a Section node', () => {
2863
+ const doc = createDocument().section([Text({ children: 'a' }), Text({ children: 'b' })])
2864
2864
  const children = getPageChildren(doc)
2865
2865
  expect(children).toHaveLength(1)
2866
2866
  const section = children[0]!
2867
- expect(typeof section !== "string" && section.type).toBe("section")
2868
- if (typeof section !== "string") {
2867
+ expect(typeof section !== 'string' && section.type).toBe('section')
2868
+ if (typeof section !== 'string') {
2869
2869
  expect(section.children).toHaveLength(2)
2870
2870
  }
2871
2871
  })