@pyreon/document-primitives 0.12.13 → 0.12.15
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.
- package/lib/index.d.ts +13 -13
- package/package.json +18 -16
- package/src/__tests__/document-primitives.browser.test.ts +253 -0
package/lib/index.d.ts
CHANGED
|
@@ -137,7 +137,7 @@ declare const DocCode: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
137
137
|
readonly multi: true;
|
|
138
138
|
readonly transform: true;
|
|
139
139
|
};
|
|
140
|
-
},
|
|
140
|
+
}, false, {}>;
|
|
141
141
|
//#endregion
|
|
142
142
|
//#region src/primitives/DocColumn.d.ts
|
|
143
143
|
declare const DocColumn: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -188,7 +188,7 @@ declare const DocColumn: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
188
188
|
readonly multi: true;
|
|
189
189
|
readonly transform: true;
|
|
190
190
|
};
|
|
191
|
-
},
|
|
191
|
+
}, false, {}>;
|
|
192
192
|
//#endregion
|
|
193
193
|
//#region src/primitives/DocDivider.d.ts
|
|
194
194
|
declare const DocDivider: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -243,7 +243,7 @@ declare const DocDivider: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
243
243
|
readonly multi: true;
|
|
244
244
|
readonly transform: true;
|
|
245
245
|
};
|
|
246
|
-
},
|
|
246
|
+
}, false, {}>;
|
|
247
247
|
//#endregion
|
|
248
248
|
//#region src/primitives/DocDocument.d.ts
|
|
249
249
|
/**
|
|
@@ -342,7 +342,7 @@ declare const DocDocument: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
342
342
|
readonly multi: true;
|
|
343
343
|
readonly transform: true;
|
|
344
344
|
};
|
|
345
|
-
},
|
|
345
|
+
}, false, {}>;
|
|
346
346
|
//#endregion
|
|
347
347
|
//#region src/primitives/DocHeading.d.ts
|
|
348
348
|
declare const DocHeading: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -443,7 +443,7 @@ declare const DocImage: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
443
443
|
readonly multi: true;
|
|
444
444
|
readonly transform: true;
|
|
445
445
|
};
|
|
446
|
-
},
|
|
446
|
+
}, false, {}>;
|
|
447
447
|
//#endregion
|
|
448
448
|
//#region src/primitives/DocLink.d.ts
|
|
449
449
|
declare const DocLink: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -472,7 +472,7 @@ declare const DocLink: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
472
472
|
readonly multi: true;
|
|
473
473
|
readonly transform: true;
|
|
474
474
|
};
|
|
475
|
-
},
|
|
475
|
+
}, false, {}>;
|
|
476
476
|
//#endregion
|
|
477
477
|
//#region src/primitives/DocList.d.ts
|
|
478
478
|
declare const DocList: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -526,7 +526,7 @@ declare const DocList: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
526
526
|
readonly multi: true;
|
|
527
527
|
readonly transform: true;
|
|
528
528
|
};
|
|
529
|
-
},
|
|
529
|
+
}, false, {}>;
|
|
530
530
|
//#endregion
|
|
531
531
|
//#region src/primitives/DocListItem.d.ts
|
|
532
532
|
declare const DocListItem: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -553,7 +553,7 @@ declare const DocListItem: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
553
553
|
readonly multi: true;
|
|
554
554
|
readonly transform: true;
|
|
555
555
|
};
|
|
556
|
-
},
|
|
556
|
+
}, false, {}>;
|
|
557
557
|
//#endregion
|
|
558
558
|
//#region src/primitives/DocPage.d.ts
|
|
559
559
|
declare const DocPage: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -608,7 +608,7 @@ declare const DocPage: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
608
608
|
readonly multi: true;
|
|
609
609
|
readonly transform: true;
|
|
610
610
|
};
|
|
611
|
-
},
|
|
611
|
+
}, false, {}>;
|
|
612
612
|
//#endregion
|
|
613
613
|
//#region src/primitives/DocPageBreak.d.ts
|
|
614
614
|
declare const DocPageBreak: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -657,7 +657,7 @@ declare const DocPageBreak: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
657
657
|
readonly multi: true;
|
|
658
658
|
readonly transform: true;
|
|
659
659
|
};
|
|
660
|
-
},
|
|
660
|
+
}, false, {}>;
|
|
661
661
|
//#endregion
|
|
662
662
|
//#region src/primitives/DocQuote.d.ts
|
|
663
663
|
declare const DocQuote: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -713,7 +713,7 @@ declare const DocQuote: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
713
713
|
readonly multi: true;
|
|
714
714
|
readonly transform: true;
|
|
715
715
|
};
|
|
716
|
-
},
|
|
716
|
+
}, false, {}>;
|
|
717
717
|
//#endregion
|
|
718
718
|
//#region src/primitives/DocRow.d.ts
|
|
719
719
|
/**
|
|
@@ -768,7 +768,7 @@ declare const DocRow: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
768
768
|
readonly multi: true;
|
|
769
769
|
readonly transform: true;
|
|
770
770
|
};
|
|
771
|
-
},
|
|
771
|
+
}, false, {}>;
|
|
772
772
|
//#endregion
|
|
773
773
|
//#region src/primitives/DocSection.d.ts
|
|
774
774
|
declare const DocSection: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
@@ -868,7 +868,7 @@ declare const DocSpacer: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
868
868
|
readonly multi: true;
|
|
869
869
|
readonly transform: true;
|
|
870
870
|
};
|
|
871
|
-
},
|
|
871
|
+
}, false, {}>;
|
|
872
872
|
//#endregion
|
|
873
873
|
//#region src/primitives/DocTable.d.ts
|
|
874
874
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/document-primitives",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.15",
|
|
4
4
|
"description": "Rocketstyle document components — render in browser, export to 18 formats",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,32 +35,34 @@
|
|
|
35
35
|
"build:watch": "bun run vl_rolldown_build-watch",
|
|
36
36
|
"lint": "oxlint .",
|
|
37
37
|
"test": "vitest run",
|
|
38
|
+
"test:browser": "vitest run --config ./vitest.browser.config.ts",
|
|
38
39
|
"test:coverage": "vitest run --coverage",
|
|
39
40
|
"test:watch": "vitest",
|
|
40
41
|
"typecheck": "tsc --noEmit"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
|
-
"@pyreon/connector-document": "^0.12.
|
|
44
|
+
"@pyreon/connector-document": "^0.12.15"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
|
-
"@pyreon/core": "^0.12.
|
|
47
|
-
"@pyreon/elements": "^0.12.
|
|
48
|
-
"@pyreon/reactivity": "^0.12.
|
|
49
|
-
"@pyreon/rocketstyle": "^0.12.
|
|
50
|
-
"@pyreon/runtime-dom": "^0.12.
|
|
51
|
-
"@pyreon/styler": "^0.12.
|
|
47
|
+
"@pyreon/core": "^0.12.15",
|
|
48
|
+
"@pyreon/elements": "^0.12.15",
|
|
49
|
+
"@pyreon/reactivity": "^0.12.15",
|
|
50
|
+
"@pyreon/rocketstyle": "^0.12.15",
|
|
51
|
+
"@pyreon/runtime-dom": "^0.12.15",
|
|
52
|
+
"@pyreon/styler": "^0.12.15",
|
|
52
53
|
"@pyreon/test-utils": "^0.12.10",
|
|
53
|
-
"@pyreon/typescript": "^0.12.
|
|
54
|
-
"@pyreon/ui-core": "^0.12.
|
|
54
|
+
"@pyreon/typescript": "^0.12.15",
|
|
55
|
+
"@pyreon/ui-core": "^0.12.15",
|
|
56
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
55
57
|
"@vitus-labs/tools-rolldown": "^1.15.4"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
58
|
-
"@pyreon/core": "^0.12.
|
|
59
|
-
"@pyreon/document": "^0.12.
|
|
60
|
-
"@pyreon/elements": "^0.12.
|
|
61
|
-
"@pyreon/rocketstyle": "^0.12.
|
|
62
|
-
"@pyreon/styler": "^0.12.
|
|
63
|
-
"@pyreon/ui-core": "^0.12.
|
|
60
|
+
"@pyreon/core": "^0.12.15",
|
|
61
|
+
"@pyreon/document": "^0.12.15",
|
|
62
|
+
"@pyreon/elements": "^0.12.15",
|
|
63
|
+
"@pyreon/rocketstyle": "^0.12.15",
|
|
64
|
+
"@pyreon/styler": "^0.12.15",
|
|
65
|
+
"@pyreon/ui-core": "^0.12.15"
|
|
64
66
|
},
|
|
65
67
|
"engines": {
|
|
66
68
|
"node": ">= 22"
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { h } from '@pyreon/core'
|
|
2
|
+
import { extractDocumentTree } from '@pyreon/connector-document'
|
|
3
|
+
import { signal } from '@pyreon/reactivity'
|
|
4
|
+
import { mountInBrowser } from '@pyreon/test-utils/browser'
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
6
|
+
import DocCode from '../primitives/DocCode'
|
|
7
|
+
import DocDocument from '../primitives/DocDocument'
|
|
8
|
+
import DocHeading from '../primitives/DocHeading'
|
|
9
|
+
import DocImage from '../primitives/DocImage'
|
|
10
|
+
import DocLink from '../primitives/DocLink'
|
|
11
|
+
import DocList from '../primitives/DocList'
|
|
12
|
+
import DocListItem from '../primitives/DocListItem'
|
|
13
|
+
import DocSection from '../primitives/DocSection'
|
|
14
|
+
import DocTable from '../primitives/DocTable'
|
|
15
|
+
import DocText from '../primitives/DocText'
|
|
16
|
+
|
|
17
|
+
// Real-browser smoke suite for @pyreon/document-primitives.
|
|
18
|
+
//
|
|
19
|
+
// The contract under test here is the one PR #197 fixed: when you pass a real
|
|
20
|
+
// rocketstyle-wrapped primitive (not a hand-constructed mock vnode) through
|
|
21
|
+
// `extractDocumentTree`, the extractor must invoke the component to reach the
|
|
22
|
+
// post-attrs vnode where `_documentProps` actually lives. Before PR #197,
|
|
23
|
+
// every real primitive silently dropped its metadata during export.
|
|
24
|
+
//
|
|
25
|
+
// The existing unit tests in `connector-document/src/__tests__/` use a
|
|
26
|
+
// hand-constructed `DocDocLike` function. This suite closes the gap by
|
|
27
|
+
// using the ACTUAL `DocDocument` primitive with real rocketstyle runtime,
|
|
28
|
+
// in a real browser.
|
|
29
|
+
|
|
30
|
+
describe('document-primitives in real browser', () => {
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
// Each test cleans up its own mount; extract-only tests have nothing to do.
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('extracts title + author from a real DocDocument vnode (PR #197 regression)', () => {
|
|
36
|
+
const vnode = h(DocDocument, { title: 'Resume', author: 'Alice' })
|
|
37
|
+
const tree = extractDocumentTree(vnode)
|
|
38
|
+
|
|
39
|
+
expect(tree.type).toBe('document')
|
|
40
|
+
expect(tree.props.title).toBe('Resume')
|
|
41
|
+
expect(tree.props.author).toBe('Alice')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('resolves reactive accessor props at extraction time (live signal reads)', () => {
|
|
45
|
+
const name = signal('Alice')
|
|
46
|
+
const vnode = h(DocDocument, {
|
|
47
|
+
title: () => `${name()} — Resume`,
|
|
48
|
+
author: () => name(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const first = extractDocumentTree(vnode)
|
|
52
|
+
expect(first.props.title).toBe('Alice — Resume')
|
|
53
|
+
expect(first.props.author).toBe('Alice')
|
|
54
|
+
|
|
55
|
+
name.set('Bob')
|
|
56
|
+
const second = extractDocumentTree(vnode)
|
|
57
|
+
expect(second.props.title).toBe('Bob — Resume')
|
|
58
|
+
expect(second.props.author).toBe('Bob')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('renders a nested DocDocument tree to real DOM and extracts the same tree', () => {
|
|
62
|
+
const vnode = h(
|
|
63
|
+
DocDocument,
|
|
64
|
+
{ title: 'Report' },
|
|
65
|
+
h(DocHeading, { level: 'h1' }, 'Q1 Results'),
|
|
66
|
+
h(DocText, null, 'Revenue grew 12%.'),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const { container, unmount } = mountInBrowser(vnode)
|
|
70
|
+
// Real browser renders the rocketstyle-wrapped elements.
|
|
71
|
+
expect(container.textContent).toContain('Q1 Results')
|
|
72
|
+
expect(container.textContent).toContain('Revenue grew 12%.')
|
|
73
|
+
|
|
74
|
+
// Same vnode drives the export path.
|
|
75
|
+
const tree = extractDocumentTree(vnode)
|
|
76
|
+
expect(tree.type).toBe('document')
|
|
77
|
+
expect(tree.props.title).toBe('Report')
|
|
78
|
+
expect(tree.children.length).toBeGreaterThanOrEqual(2)
|
|
79
|
+
const nodeChildren = tree.children.filter(
|
|
80
|
+
(c): c is Exclude<typeof c, string> => typeof c !== 'string',
|
|
81
|
+
)
|
|
82
|
+
expect(nodeChildren.some((c) => c.type === 'heading')).toBe(true)
|
|
83
|
+
expect(nodeChildren.some((c) => c.type === 'text')).toBe(true)
|
|
84
|
+
|
|
85
|
+
unmount()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('extracts heading level from real DocHeading primitive', () => {
|
|
89
|
+
const tree = extractDocumentTree(
|
|
90
|
+
h(DocDocument, { title: 't' }, h(DocHeading, { level: 'h2' }, 'Section')),
|
|
91
|
+
)
|
|
92
|
+
const heading = (tree.children.find((c) => typeof c !== 'string' && c.type === 'heading') ??
|
|
93
|
+
null) as { type: string; props: { level?: number } } | null
|
|
94
|
+
expect(heading).not.toBeNull()
|
|
95
|
+
// DocHeading converts the 'h2' level prop into a numeric `level: 2`.
|
|
96
|
+
expect(heading?.props.level).toBe(2)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('extracts href from real DocLink primitive', () => {
|
|
100
|
+
const tree = extractDocumentTree(
|
|
101
|
+
h(
|
|
102
|
+
DocDocument,
|
|
103
|
+
{ title: 't' },
|
|
104
|
+
h(DocLink, { href: 'https://example.com' }, 'click me'),
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
const link = (tree.children.find((c) => typeof c !== 'string' && c.type === 'link') ??
|
|
108
|
+
null) as { type: string; props: { href?: string } } | null
|
|
109
|
+
expect(link).not.toBeNull()
|
|
110
|
+
expect(link?.props.href).toBe('https://example.com')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('extracts ordered flag from real DocList primitive', () => {
|
|
114
|
+
const tree = extractDocumentTree(
|
|
115
|
+
h(
|
|
116
|
+
DocDocument,
|
|
117
|
+
{ title: 't' },
|
|
118
|
+
h(
|
|
119
|
+
DocList,
|
|
120
|
+
{ ordered: true },
|
|
121
|
+
h(DocListItem, null, 'one'),
|
|
122
|
+
h(DocListItem, null, 'two'),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
const list = (tree.children.find((c) => typeof c !== 'string' && c.type === 'list') ??
|
|
127
|
+
null) as { type: string; props: { ordered?: boolean }; children: unknown[] } | null
|
|
128
|
+
expect(list).not.toBeNull()
|
|
129
|
+
expect(list?.props.ordered).toBe(true)
|
|
130
|
+
expect(list?.children.length).toBe(2)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('extracts language from real DocCode primitive', () => {
|
|
134
|
+
const tree = extractDocumentTree(
|
|
135
|
+
h(
|
|
136
|
+
DocDocument,
|
|
137
|
+
{ title: 't' },
|
|
138
|
+
h(DocCode, { language: 'typescript' }, 'const x = 1'),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
const code = (tree.children.find((c) => typeof c !== 'string' && c.type === 'code') ??
|
|
142
|
+
null) as { type: string; props: { language?: string } } | null
|
|
143
|
+
expect(code).not.toBeNull()
|
|
144
|
+
expect(code?.props.language).toBe('typescript')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('extracts src + alt from real DocImage primitive (no DOM forwarding crash)', () => {
|
|
148
|
+
// Image element has read-only `naturalWidth`/`naturalHeight` properties —
|
|
149
|
+
// a clean test that DocImage doesn't try to set them via property assignment.
|
|
150
|
+
const tree = extractDocumentTree(
|
|
151
|
+
h(
|
|
152
|
+
DocDocument,
|
|
153
|
+
{ title: 't' },
|
|
154
|
+
h(DocImage, { src: 'https://example.com/x.png', alt: 'logo' }),
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
const img = (tree.children.find((c) => typeof c !== 'string' && c.type === 'image') ??
|
|
158
|
+
null) as { type: string; props: { src?: string; alt?: string } } | null
|
|
159
|
+
expect(img).not.toBeNull()
|
|
160
|
+
expect(img?.props.src).toBe('https://example.com/x.png')
|
|
161
|
+
expect(img?.props.alt).toBe('logo')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('extracts table rows + columns without read-only-property crash', () => {
|
|
165
|
+
// DocTable uses `.attrs(callback, { filter: ['rows', 'columns', ...] })`
|
|
166
|
+
// to strip export-only props before they reach the DOM, because
|
|
167
|
+
// HTMLTableElement.rows is a read-only HTMLCollection getter — assigning
|
|
168
|
+
// to it would crash. This test exercises that filter path.
|
|
169
|
+
const tree = extractDocumentTree(
|
|
170
|
+
h(
|
|
171
|
+
DocDocument,
|
|
172
|
+
{ title: 't' },
|
|
173
|
+
h(DocTable, {
|
|
174
|
+
rows: [
|
|
175
|
+
['a1', 'b1'],
|
|
176
|
+
['a2', 'b2'],
|
|
177
|
+
],
|
|
178
|
+
columns: ['col-a', 'col-b'],
|
|
179
|
+
}),
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
const table = (tree.children.find((c) => typeof c !== 'string' && c.type === 'table') ??
|
|
183
|
+
null) as
|
|
184
|
+
| {
|
|
185
|
+
type: string
|
|
186
|
+
props: {
|
|
187
|
+
rows?: unknown[]
|
|
188
|
+
columns?: unknown[]
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
| null
|
|
192
|
+
expect(table).not.toBeNull()
|
|
193
|
+
expect(Array.isArray(table?.props.rows)).toBe(true)
|
|
194
|
+
expect(table?.props.rows).toHaveLength(2)
|
|
195
|
+
expect(table?.props.columns).toEqual(['col-a', 'col-b'])
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('extracts deeply nested tree (DocSection wrapping multiple primitives)', () => {
|
|
199
|
+
const tree = extractDocumentTree(
|
|
200
|
+
h(
|
|
201
|
+
DocDocument,
|
|
202
|
+
{ title: 'Report' },
|
|
203
|
+
h(
|
|
204
|
+
DocSection,
|
|
205
|
+
null,
|
|
206
|
+
h(DocHeading, { level: 'h1' }, 'Intro'),
|
|
207
|
+
h(DocText, null, 'paragraph'),
|
|
208
|
+
h(DocSection, null, h(DocText, null, 'nested-paragraph')),
|
|
209
|
+
),
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
expect(tree.type).toBe('document')
|
|
213
|
+
|
|
214
|
+
// The traversal into nested sections should preserve depth.
|
|
215
|
+
const findByType = (node: unknown, type: string): unknown => {
|
|
216
|
+
if (typeof node !== 'object' || !node) return null
|
|
217
|
+
const n = node as { type?: string; children?: unknown[] }
|
|
218
|
+
if (n.type === type) return n
|
|
219
|
+
for (const c of n.children ?? []) {
|
|
220
|
+
const found = findByType(c, type)
|
|
221
|
+
if (found) return found
|
|
222
|
+
}
|
|
223
|
+
return null
|
|
224
|
+
}
|
|
225
|
+
const heading = findByType(tree, 'heading')
|
|
226
|
+
expect(heading).not.toBeNull()
|
|
227
|
+
// 'nested-paragraph' should appear as a text node anywhere in the tree.
|
|
228
|
+
const treeStr = JSON.stringify(tree)
|
|
229
|
+
expect(treeStr).toContain('nested-paragraph')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('handles primitives that have NO _documentProps cleanly (DocText)', () => {
|
|
233
|
+
// DocText has `_documentProps: {}` — empty object. Confirms the
|
|
234
|
+
// extractor doesn't choke on absent metadata.
|
|
235
|
+
const tree = extractDocumentTree(
|
|
236
|
+
h(DocDocument, { title: 't' }, h(DocText, null, 'just text')),
|
|
237
|
+
)
|
|
238
|
+
const text = (tree.children.find((c) => typeof c !== 'string' && c.type === 'text') ??
|
|
239
|
+
null) as { type: string; children?: unknown[] } | null
|
|
240
|
+
expect(text).not.toBeNull()
|
|
241
|
+
expect(JSON.stringify(text)).toContain('just text')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('handles undefined / nullish optional metadata (omits, does not emit `key: undefined`)', () => {
|
|
245
|
+
// DocDocument explicitly only sets keys when non-null. Verify by
|
|
246
|
+
// omitting all optional props.
|
|
247
|
+
const tree = extractDocumentTree(h(DocDocument, {}))
|
|
248
|
+
expect(tree.type).toBe('document')
|
|
249
|
+
expect(tree.props).not.toHaveProperty('title')
|
|
250
|
+
expect(tree.props).not.toHaveProperty('author')
|
|
251
|
+
expect(tree.props).not.toHaveProperty('subject')
|
|
252
|
+
})
|
|
253
|
+
})
|