@oxide/react-asciidoc 1.2.0 → 1.3.0-canary.5535dcb
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/README.md +361 -74
- package/dist/react-asciidoc.js +3142 -1077
- package/dist/react-asciidoc.umd.cjs +21 -23
- package/dist/types/RenderInline.d.ts +93 -0
- package/dist/types/index.d.ts +7 -2
- package/dist/types/inline/index.d.ts +4 -0
- package/dist/types/inline/parser.d.ts +90 -0
- package/dist/types/inline/quotes.d.ts +5 -0
- package/dist/types/inline/renderer.d.ts +3 -0
- package/dist/types/inline/rx.d.ts +28 -0
- package/dist/types/inline/types.d.ts +105 -0
- package/dist/types/templates/FloatingTitle.d.ts +5 -1
- package/dist/types/templates/Stem.d.ts +5 -0
- package/dist/types/templates/index.d.ts +2 -1
- package/dist/types/utils/prepareDocument.d.ts +32 -5
- package/package.json +10 -18
package/README.md
CHANGED
|
@@ -1,98 +1,385 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@oxide/react-asciidoc`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A React renderer for [AsciiDoc](https://asciidoc.org/), built on top of
|
|
4
|
+
[Asciidoctor.js](https://github.com/asciidoctor/asciidoctor.js/). It parses a document with
|
|
5
|
+
stock Asciidoctor, walks the resulting AST into a plain serialisable tree, and renders that
|
|
6
|
+
tree with React components. The goal is to reproduce the HTML of Asciidoctor's built-in
|
|
7
|
+
HTML5 converter while letting you swap individual block and inline templates for your own
|
|
8
|
+
React components.
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
in output of the built-in HTML5 converter. Will allow for custom components to be passed in
|
|
8
|
-
similar to
|
|
9
|
-
[Custom converters in Asciidoctor.js](https://docs.asciidoctor.org/asciidoctor.js/latest/extend/converter/custom-converter/)
|
|
10
|
-
and custom block types.
|
|
10
|
+
It's aimed at rendering AsciiDoc documents for the web — a document at a time — when you
|
|
11
|
+
want real React components in the output instead of a string of HTML.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
Built and maintained at [Oxide](https://oxide.computer).
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Status
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Output is checked against stock Asciidoctor with a diff suite: each document is converted
|
|
18
|
+
both ways and the HTML compared. Current results:
|
|
19
|
+
|
|
20
|
+
- **Examples:** 78 / 79 hand-written fixtures match (including the full Asciidoctor writer's
|
|
21
|
+
guide). The lone miss is a table using `{set:cellbgcolor}`.
|
|
22
|
+
- **Corpus:** 1894 / 2001 real-world documents match (~95%) — a mix of Oxide RFDs and
|
|
23
|
+
Asciidoctor's own test fixtures.
|
|
24
|
+
|
|
25
|
+
Every block and inline type the HTML5 converter emits is implemented. The remaining
|
|
26
|
+
mismatches are a small set of edge cases — see [Known differences](#known-differences). If
|
|
27
|
+
your content steers clear of those, the output matches Asciidoctor.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
17
30
|
|
|
18
31
|
```
|
|
19
|
-
npm install
|
|
32
|
+
npm install @oxide/react-asciidoc @asciidoctor/core
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`@asciidoctor/core`, `react`, and `react-dom` are peer dependencies (React 18 or 19).
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
The flow is always: load the source with Asciidoctor → `prepareDocument(doc)` → render
|
|
40
|
+
`<Asciidoc>`.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import asciidoctor from '@asciidoctor/core'
|
|
44
|
+
import { Asciidoc, prepareDocument } from '@oxide/react-asciidoc'
|
|
45
|
+
|
|
46
|
+
const ad = asciidoctor()
|
|
47
|
+
|
|
48
|
+
function Page({ source }: { source: string }) {
|
|
49
|
+
const doc = ad.load(source, {
|
|
50
|
+
standalone: true,
|
|
51
|
+
attributes: { sectlinks: 'true', icons: 'font' },
|
|
52
|
+
})
|
|
53
|
+
const prepared = prepareDocument(doc)
|
|
54
|
+
return <Asciidoc document={prepared} />
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`prepareDocument` returns a plain, JSON-serialisable `DocumentBlock`. You can run
|
|
59
|
+
`ad.load` + `prepareDocument` on a server (or at build time), serialise the result, and
|
|
60
|
+
hydrate `<Asciidoc>` on the client without shipping Asciidoctor.js to the browser.
|
|
61
|
+
|
|
62
|
+
> Asciidoctor's default stylesheet is not injected. Pull in
|
|
63
|
+
> [`asciidoctor.css`](https://github.com/asciidoctor/asciidoctor/blob/main/data/stylesheets/asciidoctor.css)
|
|
64
|
+
> (or your own) to style the output.
|
|
65
|
+
|
|
66
|
+
### Block-level overrides
|
|
67
|
+
|
|
68
|
+
Replace the component used for any block type via `options.overrides`. Your component
|
|
69
|
+
receives the prepared `node`; render `<Content blocks={node.blocks} />` to recurse into
|
|
70
|
+
children.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Asciidoc, type Block, Content, type DocumentBlock } from '@oxide/react-asciidoc'
|
|
74
|
+
|
|
75
|
+
function Callout({ node }: { node: Block }) {
|
|
76
|
+
return (
|
|
77
|
+
<aside className={`callout callout-${node.attributes?.name}`}>
|
|
78
|
+
<Content blocks={node.blocks} />
|
|
79
|
+
</aside>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function Doc({ document }: { document: DocumentBlock }) {
|
|
84
|
+
return <Asciidoc document={document} options={{ overrides: { admonition: Callout } }} />
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Override keys are the block type names:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
admonition · audio · colist · dlist · example · floating_title · image · listing ·
|
|
92
|
+
literal · olist · open · page_break · paragraph · pass · preamble · quote · section ·
|
|
93
|
+
sidebar · stem · table · toc · thematic_break · ulist · verse · video
|
|
20
94
|
```
|
|
21
95
|
|
|
22
|
-
|
|
96
|
+
### Inline-level overrides
|
|
97
|
+
|
|
98
|
+
Inline content (emphasis, links, footnotes, etc.) is parsed into its own AST. By default
|
|
99
|
+
it's rendered to an HTML string, which is the form that matches Asciidoctor most precisely.
|
|
100
|
+
To render individual inline nodes as React components instead, pass
|
|
101
|
+
`options.inlineOverrides`:
|
|
23
102
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[here](https://github.com/oxidecomputer/react-asciidoc/tree/main/src/examples) for the
|
|
27
|
-
example names. Use `renderer=react` for the React renderer and `renderer=html` for the HTML
|
|
28
|
-
one.
|
|
103
|
+
```tsx
|
|
104
|
+
import { Asciidoc, type DocumentBlock, type InlineOverrides } from '@oxide/react-asciidoc'
|
|
29
105
|
|
|
30
|
-
|
|
31
|
-
|
|
106
|
+
const inlineOverrides: InlineOverrides = {
|
|
107
|
+
// node.subtype is 'link' | 'xref' | 'ref' | 'bibref'
|
|
108
|
+
anchor: ({ node, children }) =>
|
|
109
|
+
node.subtype === 'xref' ? (
|
|
110
|
+
<a className="xref" href={node.target}>
|
|
111
|
+
{children}
|
|
112
|
+
</a>
|
|
113
|
+
) : (
|
|
114
|
+
<a href={node.target}>{children}</a>
|
|
115
|
+
),
|
|
116
|
+
}
|
|
32
117
|
|
|
33
|
-
|
|
118
|
+
function Doc({ document }: { document: DocumentBlock }) {
|
|
119
|
+
return <Asciidoc document={document} options={{ inlineOverrides }} />
|
|
120
|
+
}
|
|
121
|
+
```
|
|
34
122
|
|
|
35
|
-
|
|
36
|
-
running a visual diff on the original renderer and the React one. You must therefore run the
|
|
37
|
-
following command first:
|
|
123
|
+
Override keys (with the node passed to each):
|
|
38
124
|
|
|
39
125
|
```
|
|
40
|
-
|
|
126
|
+
quoted · anchor · image · footnote · indexterm · callout · break · button · kbd · menu
|
|
41
127
|
```
|
|
42
128
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
129
|
+
`quoted` covers
|
|
130
|
+
`emphasis | strong | monospaced | mark | superscript | subscript | double | single | unquoted`
|
|
131
|
+
(distinguished by `node.subtype`).
|
|
132
|
+
|
|
133
|
+
#### Bibliography cross-references as direct links
|
|
134
|
+
|
|
135
|
+
When an `<<id>>` cross-reference resolves to a `[bibliography]` entry whose citation carries
|
|
136
|
+
a URL, `prepareDocument` records that URL on the anchor node as `node.externalHref`. The
|
|
137
|
+
default output is unchanged (it still links to the in-page `#id` anchor, matching
|
|
138
|
+
Asciidoctor), but an `anchor` override can read `externalHref` to link straight to the
|
|
139
|
+
external resource instead:
|
|
140
|
+
|
|
141
|
+
```asciidoc
|
|
142
|
+
See <<pegjs>> for the parser generator.
|
|
143
|
+
|
|
144
|
+
[bibliography]
|
|
145
|
+
== References
|
|
146
|
+
|
|
147
|
+
- [[[pegjs]]] PEG.js https://pegjs.org
|
|
148
|
+
```
|
|
46
149
|
|
|
150
|
+
```tsx
|
|
151
|
+
const inlineOverrides: InlineOverrides = {
|
|
152
|
+
anchor: ({ node, children }) =>
|
|
153
|
+
node.subtype === 'xref' && node.externalHref ? (
|
|
154
|
+
<a href={node.externalHref} target="_blank" rel="noopener">
|
|
155
|
+
{children}
|
|
156
|
+
</a>
|
|
157
|
+
) : (
|
|
158
|
+
<a href={node.target.startsWith('#') ? node.target : `#${node.target}`}>{children}</a>
|
|
159
|
+
),
|
|
160
|
+
}
|
|
47
161
|
```
|
|
48
|
-
|
|
162
|
+
|
|
163
|
+
`externalHref` is only set when the reference resolves to that entry under stock rules; an
|
|
164
|
+
unresolved citation is left as the normal fallback.
|
|
165
|
+
|
|
166
|
+
Alongside `externalHref`, `prepareDocument` also records the entry's citation body on the
|
|
167
|
+
anchor as `node.referenceInlines` — the entry's inline AST with the leading `[[[id]]]`
|
|
168
|
+
bibref label stripped (the free text and links that follow it). It is set for every xref
|
|
169
|
+
that resolves to a `[bibliography]` entry, whether or not the citation carries a URL. The
|
|
170
|
+
default renderer ignores it (preserving stock parity); an `anchor` override can render it
|
|
171
|
+
through `<RenderInline>` to show the reference's content in a hover tooltip:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import { RenderInline } from '@oxide/react-asciidoc'
|
|
175
|
+
|
|
176
|
+
const inlineOverrides: InlineOverrides = {
|
|
177
|
+
anchor: ({ node, children }) =>
|
|
178
|
+
node.subtype === 'xref' && node.referenceInlines ? (
|
|
179
|
+
<Tooltip content={<RenderInline nodes={node.referenceInlines} />}>
|
|
180
|
+
<a href={`#${node.target}`}>{children}</a>
|
|
181
|
+
</Tooltip>
|
|
182
|
+
) : (
|
|
183
|
+
<a href={node.target.startsWith('#') ? node.target : `#${node.target}`}>{children}</a>
|
|
184
|
+
),
|
|
185
|
+
}
|
|
49
186
|
```
|
|
50
187
|
|
|
51
|
-
|
|
188
|
+
> **Inline overrides change the inline output path.** Without them, inline content is
|
|
189
|
+
> emitted as an HTML string and matches Asciidoctor exactly. Supplying any `inlineOverrides`
|
|
190
|
+
> switches to a React element tree, which entity-decodes text (e.g. `’` renders as a
|
|
191
|
+
> literal `'`). The visible result is the same, but the HTML is no longer
|
|
192
|
+
> character-for-character identical to Asciidoctor — leave `inlineOverrides` unset if you
|
|
193
|
+
> need an exact match.
|
|
194
|
+
|
|
195
|
+
### Custom document shell
|
|
196
|
+
|
|
197
|
+
`options.customDocument` replaces the outer document component (header, TOC, footnotes
|
|
198
|
+
section) while still using the built-in block templates for the body. See `src/App.tsx` and
|
|
199
|
+
`tests/helpers.tsx` for a worked example.
|
|
200
|
+
|
|
201
|
+
### Other exports
|
|
202
|
+
|
|
203
|
+
| Export | Purpose |
|
|
204
|
+
| ----------------------- | ------------------------------------------------------------------------------------- |
|
|
205
|
+
| `Content` | Renders an array of blocks; use inside block overrides to recurse. |
|
|
206
|
+
| `RenderInline` | Renders an inline AST (`node.inlines`) as a React tree (honours `inlineOverrides`). |
|
|
207
|
+
| `inlineHtml` | Renders an inline AST to an HTML string (`{ __html }`) for `dangerouslySetInnerHTML`. |
|
|
208
|
+
| `Inline` | The standalone inline parser/renderer (`parseInline`, `renderInline`). |
|
|
209
|
+
| `prepareDocument` | Asciidoctor AST → serialisable `DocumentBlock`. |
|
|
210
|
+
| `processDocument` | Walk/transform the prepared tree (e.g. to post-process listing blocks). |
|
|
211
|
+
| Block templates & types | `Paragraph`, `Listing`, …, `ParagraphBlock`, `ListingBlock`, … re-exported. |
|
|
212
|
+
|
|
213
|
+
## How it works
|
|
214
|
+
|
|
215
|
+
1. You call `ad.load(...)` to get an Asciidoctor AST.
|
|
216
|
+
2. `prepareDocument()` walks that tree into plain `Block` objects. For each leaf block it
|
|
217
|
+
feeds the raw source through a vendored TypeScript port of Asciidoctor's inline
|
|
218
|
+
substitution pipeline (`src/asciidoc/inline/`), producing an `InlineNode[]` AST.
|
|
219
|
+
3. `<Asciidoc>` puts options into context and renders `<Document>` (or your
|
|
220
|
+
`customDocument`).
|
|
221
|
+
4. `<Content>` dispatches each block to its template, preferring `options.overrides`.
|
|
222
|
+
5. Inline content renders via `inlineHtml` + `dangerouslySetInnerHTML` (the default, closest
|
|
223
|
+
to Asciidoctor) or, when `inlineOverrides` are set, via `<RenderInline>` (a React tree).
|
|
52
224
|
|
|
53
225
|
## Limitations
|
|
54
226
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
227
|
+
- **Inline parsing is reimplemented, not delegated.** To expose inline nodes to React we
|
|
228
|
+
vendored Asciidoctor's substitution pipeline rather than calling its converter. It mirrors
|
|
229
|
+
`apply_normal_subs` closely, but a few constructs that rely on Asciidoctor's sequential
|
|
230
|
+
`gsub` model don't round-trip perfectly — see [Known differences](#known-differences).
|
|
231
|
+
- **The default stylesheet is not bundled.**
|
|
232
|
+
- **Inline overrides change the inline output path** (see above).
|
|
233
|
+
- **No file-system features.** `include::` directives are resolved by Asciidoctor at
|
|
234
|
+
`ad.load` time; this library never touches the file system itself. An unresolved
|
|
235
|
+
`include::` becomes a link, exactly as in stock Asciidoctor.
|
|
236
|
+
- **Syntax highlighting is left to you.** Source blocks emit
|
|
237
|
+
`<pre class="highlight"><code class="language-…">`; run a client-side highlighter (e.g.
|
|
238
|
+
highlight.js, Shiki) over that. Server-side `source-highlighter` backends
|
|
239
|
+
(Rouge/CodeRay/Pygments) are not reproduced.
|
|
240
|
+
|
|
241
|
+
## Known differences
|
|
242
|
+
|
|
243
|
+
The ~107 corpus mismatches fall into the buckets below. Each shows a minimal source, what
|
|
244
|
+
**Asciidoctor** emits, and what this renderer emits.
|
|
245
|
+
|
|
246
|
+
### More likely to surface
|
|
247
|
+
|
|
248
|
+
**1. Escaped `\#` in inline monospace** — authors escape `#` (the highlight delimiter) so a
|
|
249
|
+
literal `#` survives in code, e.g. when documenting Rust attributes.
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
`\#[endpoint]` is the macro.
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Asciidoctor strips the backslash in some block contexts and keeps it in others, so the
|
|
256
|
+
expected output depends on context. We always keep it literal:
|
|
257
|
+
|
|
258
|
+
```html
|
|
259
|
+
<!-- asciidoctor (in a paragraph after other text): -->
|
|
260
|
+
<code>#[endpoint]</code>
|
|
261
|
+
<!-- this renderer: -->
|
|
262
|
+
<code>\#[endpoint]</code>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Where you can, writing the literal `#` unescaped avoids the difference.
|
|
266
|
+
|
|
267
|
+
**2. Highlight (`#…#`) spanning multiple inline code spans.** Two `#` characters across
|
|
268
|
+
separate code spans on one line are treated as a `<mark>` pair:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
use `#[a]` ... `#[b]` here
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<!-- asciidoctor (the mark tags interleave across the code spans): -->
|
|
276
|
+
use <code><mark>[a]</code>…<code></mark>[b]</code>here
|
|
277
|
+
<!-- this renderer (no mark applied): -->
|
|
278
|
+
use <code>#[a]</code>…<code>#[b]</code>here
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Comes up when a line of prose has two pieces of inline code that each contain a `#` (again,
|
|
282
|
+
Rust attributes are a common case). The two outputs differ.
|
|
283
|
+
|
|
284
|
+
**3. Sentinel / placeholder leak (bug).** In rare combinations — e.g. a bare URL whose
|
|
285
|
+
fragment looks like a callout — an internal placeholder can leak into the output:
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<!-- asciidoctor: -->
|
|
289
|
+
…slack#verifying-requests…
|
|
290
|
+
<!-- this renderer: -->
|
|
291
|
+
…slack<0>verifying-requests…
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Rare (seen once in ~700 documents), but it produces visibly wrong output when hit. This is a
|
|
295
|
+
bug on our side, not a feature gap.
|
|
296
|
+
|
|
297
|
+
**4. Footnote edge cases.** Footnotes defined in section titles are numbered out of
|
|
298
|
+
sequence, and a footnote defined inside an AsciiDoc table cell can be mis-shared with the
|
|
299
|
+
main document. Can surface in long documents that footnote heavily.
|
|
300
|
+
|
|
301
|
+
### Rarely an issue
|
|
302
|
+
|
|
303
|
+
| Difference | Example | Asciidoctor | This renderer | Notes |
|
|
304
|
+
| ------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ----------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
305
|
+
| Inline formatting straddling a span boundary | `` `a __b` c__ `` | `<code>a <em>b</code>c</em>` | `<code>a __b</code>c__` | Rare in practice. |
|
|
306
|
+
| `manpage` doctype | `:doctype: manpage` + NAME section | special NAME header block | rendered as ordinary sections | Only relevant if you render man pages (~12 failures). |
|
|
307
|
+
| `source-highlighter` with a `>` in the language token | `[source,console?prompt=$>]` | `data-lang="…$>"` (raw) | `data-lang="…$>"` | Needs an unusual language token. |
|
|
308
|
+
| Server-side highlighter markup (Rouge/CodeRay) | `:source-highlighter: rouge` | per-token `<span>` markup | plain `<code class="language-…">` | Highlight on the client instead. |
|
|
309
|
+
| `xref` nested inside another link's text | `link:…[… <<id>> …]` | resolved `<a>` | literal `<<id>>` | Uncommon nesting. |
|
|
310
|
+
| `{set:cellbgcolor}`, nested-document table cells, compat-mode toggling, `pass:` inside a body-level attribute value | — | — | — | Rare/legacy features. |
|
|
311
|
+
| Audio/video `controls`/`autoplay`/`loop` attribute casing | `audio::…[] controls` | `controls` (bare) | `controls=""` via React | Default templates use raw HTML to match Asciidoctor; override `options.overrides.audio` / `video` to use React elements instead. |
|
|
312
|
+
| `inlineOverrides` silently bypassed for blocks with straddling passthrough HTML | `pass:q[<u>x *y*</u>]` | `<u>x <strong>y</strong></u>` | same (string path, no override) | Any `<`/`>` in the inline AST triggers the HTML-string fallback, bypassing registered `inlineOverrides` for that block. |
|
|
313
|
+
|
|
314
|
+
For ordinary technical prose — code blocks, tables, admonitions, lists, links, images,
|
|
315
|
+
xrefs, and footnotes — output should match Asciidoctor. The things most worth watching are
|
|
316
|
+
the two `#`-related cases above (inline code containing `#`) and the placeholder-leak bug.
|
|
317
|
+
|
|
318
|
+
## Development
|
|
319
|
+
|
|
320
|
+
### Previewing
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
npm install
|
|
324
|
+
npm run dev
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Visit `http://localhost:8000/?example=<EXAMPLE>&renderer=<react|html>`, where `<EXAMPLE>` is
|
|
328
|
+
a file in `src/examples/` (case-sensitive, no extension). `renderer=react` uses this
|
|
329
|
+
renderer; `renderer=html` uses stock Asciidoctor, for side-by-side comparison.
|
|
330
|
+
|
|
331
|
+
### Running tests
|
|
332
|
+
|
|
333
|
+
The suite is the diff-against-Asciidoctor check described in [Status](#status):
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
npm test # everything
|
|
337
|
+
npx vitest run tests/renderer.test.tsx # just the hand-written examples
|
|
338
|
+
npx vitest run tests/corpus.*.test.tsx # just the corpus (sharded across the worker pool)
|
|
339
|
+
npx vitest run -t "rfds/0042" # a single document, whichever shard it's in
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The corpus is split into eight shard files so vitest can parallelise across cores (it
|
|
343
|
+
parallelises across files, not within one).
|
|
344
|
+
|
|
345
|
+
For triaging failures without the test framework:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
npx vite-node tests/triage.ts 2>/dev/null # pass/fail + first divergence per doc
|
|
349
|
+
npx vite-node tests/triage.ts -- --group --summary # counts grouped by category
|
|
350
|
+
npx vite-node tests/triage.ts -- --filter "tables_test" # restrict to matching docs
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Fetching the corpus
|
|
354
|
+
|
|
355
|
+
The corpus lives under `tests/corpus/` and is **git-ignored** — you fetch it locally. It has
|
|
356
|
+
two parts:
|
|
357
|
+
|
|
358
|
+
- `tests/corpus/rfds/` — Oxide RFDs, pulled with the
|
|
359
|
+
[`rfd-cli`](https://github.com/oxidecomputer/rfd-api). **~80 RFDs are currently public**;
|
|
360
|
+
internal access yields more.
|
|
361
|
+
- `tests/corpus/asciidoctor-extracted/` — fixtures lifted from Asciidoctor's own test suite
|
|
362
|
+
(optional; used to exercise feature edge cases).
|
|
363
|
+
|
|
364
|
+
To pull the RFDs, install `rfd-cli` from the
|
|
365
|
+
[rfd-api repo](https://github.com/oxidecomputer/rfd-api), point it at the public host, and
|
|
366
|
+
run the fetch script:
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
rfd-cli config set host https://rfd-api.shared.oxide.computer
|
|
370
|
+
|
|
371
|
+
# RFD_CLI defaults to ~/Development/rfd-cli; override if yours lives elsewhere.
|
|
372
|
+
RFD_CLI=$(command -v rfd-cli) ./tests/fetch-corpus.sh
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The script lists every AsciiDoc-format RFD (`rfd-cli list`), writes each to
|
|
376
|
+
`tests/corpus/rfds/NNNN.adoc` (`rfd-cli view --number N`), and skips empty bodies. Re-run
|
|
377
|
+
any time to refresh. With no corpus present, the corpus shard tests simply find no
|
|
378
|
+
documents; the example suite (`renderer.test.tsx`) always runs.
|
|
379
|
+
|
|
380
|
+
## Conventions
|
|
381
|
+
|
|
382
|
+
- Prettier: 92-column width, no semicolons, single quotes, trailing commas.
|
|
383
|
+
- ESLint ignores `.js` files (the examples are intentionally plain JS).
|
|
384
|
+
- Releases are triggered manually via the GitHub Actions `workflow_dispatch` event with an
|
|
385
|
+
explicit version number. Release notes are generated automatically in GitHub Releases.
|