3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Publish what each call writes for chosen corpus examples, and fail the gate where the page is stale

This commit is contained in:
2026-11-05 11:14:27 +01:00
committed by Lillem4n
parent f8157d3ad8
commit 1f86f24870
16 changed files with 876 additions and 46 deletions
+16
View File
@@ -0,0 +1,16 @@
import { readFileSync, writeFileSync } from 'node:fs'
import * as library from '@larvit/adf-codec'
import { renderFeatureTable } from './render.mjs'
const pagePath = new URL('../docs/feature-table.md', import.meta.url)
const readJson = (url) => JSON.parse(readFileSync(url, 'utf8'))
const examples = readJson(new URL('examples.json', import.meta.url)).map(({ id, name }) => ({ doc: readJson(new URL(`../corpus/round-trip/${id}.json`, import.meta.url)), name }))
const page = renderFeatureTable(examples, library)
if (!process.argv.includes('--check')) writeFileSync(pagePath, page)
else if (readFileSync(pagePath, 'utf8') !== page) {
console.error('docs/feature-table.md differs from what the examples and the build give: run ./feature-table/write.sh')
process.exitCode = 1
}
+17
View File
@@ -0,0 +1,17 @@
[
{ "id": "combinations/bold-command", "name": "Bold instruction with a command in it" },
{ "id": "commonmark-subset/em-strong-strike", "name": "Emphasis and strikethrough" },
{ "id": "commonmark-subset/heading", "name": "Headings" },
{ "id": "commonmark-subset/bullet-list", "name": "Bullet list" },
{ "id": "commonmark-subset/code-block", "name": "Code block" },
{ "id": "commonmark-subset/blockquote", "name": "Quote" },
{ "id": "commonmark-subset/link", "name": "Link" },
{ "id": "block-nodes/table-plain", "name": "Table" },
{ "id": "block-nodes/panel", "name": "Panels" },
{ "id": "block-nodes/task-list", "name": "Task list" },
{ "id": "block-nodes/image", "name": "Image" },
{ "id": "inline-nodes/mention", "name": "Mention" },
{ "id": "inline-nodes/status", "name": "Status" },
{ "id": "inline-nodes/date", "name": "Date" },
{ "id": "opaque-carry/unknown-block", "name": "Node types this version does not know" }
]
+41
View File
@@ -0,0 +1,41 @@
import { isDeepStrictEqual } from 'node:util'
const writers = [
{ column: 'Lossless', fence: 'markdown', reader: 'losslessMarkdownToAdf', writer: 'adfToLosslessMarkdown' },
{ column: 'Portable', fence: 'markdown', reader: 'portableMarkdownToAdf', writer: 'adfToPortableMarkdown' },
{ column: 'HTML', fence: 'html', reader: 'htmlToAdf', writer: 'adfToHtml' },
]
const intro = `# What each call writes
\`feature-table/build.mjs\` writes this page from the corpus and the built package, and \`./ci.sh\` fails where the page differs.
Each output is read back by its format's reader. \`holds\` means that gives the same document, \`loses\` a different one, and \`refused\` an error whose code the example names.
`
// `library` holds the package's calls, so a test can hand in what a real document cannot make them do.
export function renderFeatureTable(examples, library) {
const written = examples.map(({ doc, name }) => ({ doc, name, outcomes: writers.map((writer) => outcome(doc, writer, library)) }))
const rows = written.map(({ name, outcomes }) => `| [${name}](#${name.toLowerCase().replaceAll(' ', '-')}) | ${outcomes.map(({ result }) => result).join(' | ')} |`)
const head = `| Example | ${writers.map(({ column }) => column).join(' | ')} |\n| --- | --- | --- | --- |`
return [intro, [head, ...rows].join('\n') + '\n', ...written.map(section)].join('\n')
}
function outcome(doc, { fence, reader, writer }, library) {
const output = library[writer](doc)
if (!output.ok) return { label: `\`${writer}\` refuses with \`${output.error.code}\`.\n`, result: 'refused' }
const read = library[reader](output.value)
if (!read.ok) return { label: `\`${writer}\`, round-trip refused with \`${read.error.code}\`:\n\n${fenced(fence, output.value)}`, result: 'refused' }
const result = isDeepStrictEqual(read.value, doc) ? 'holds' : 'loses'
return { label: `\`${writer}\`, round-trip ${result}:\n\n${fenced(fence, output.value)}`, result }
}
function section({ doc, name, outcomes }) {
return [`## ${name}\n`, `ADF:\n\n${fenced('json', `${JSON.stringify(doc)}\n`)}`, ...outcomes.map(({ label }) => label)].join('\n')
}
function fenced(language, text) {
const longest = Math.max(2, ...[...text.matchAll(/`+/g)].map(([run]) => run.length))
const fence = '`'.repeat(longest + 1)
return `${fence}${language}\n${text}${fence}\n`
}
+43
View File
@@ -0,0 +1,43 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { renderFeatureTable } from './render.mjs'
const doc = { content: [], type: 'doc', version: 1 }
const held = { ok: true, value: doc }
const refused = { error: { code: 'unsupported-node-shape' }, ok: false }
function library(overrides) {
const writes = (value) => () => ({ ok: true, value })
return { adfToHtml: writes('<p>a</p>\n'), adfToLosslessMarkdown: writes('a\n'), adfToPortableMarkdown: writes('a\n'), htmlToAdf: () => held, losslessMarkdownToAdf: () => held, portableMarkdownToAdf: () => held, ...overrides }
}
test('leads with a row per example linking its section, then the section labels each output', () => {
const page = renderFeatureTable([{ doc, name: 'Some example' }], library())
assert.ok(page.includes('| [Some example](#some-example) | holds | holds | holds |\n'))
assert.ok(page.includes(`## Some example\n\nADF:\n\n\`\`\`json\n${JSON.stringify(doc)}\n\`\`\`\n\n\`adfToLosslessMarkdown\`, round-trip holds:\n\n\`\`\`markdown\na\n\`\`\`\n`))
assert.ok(page.includes('`adfToHtml`, round-trip holds:\n\n```html\n<p>a</p>\n```\n'))
})
test('says the round-trip loses where the output reads back to another document', () => {
const page = renderFeatureTable([{ doc, name: 'Some example' }], library({ portableMarkdownToAdf: () => ({ ok: true, value: { ...doc, content: [{ type: 'rule' }] } }) }))
assert.ok(page.includes('| holds | loses | holds |'))
assert.ok(page.includes('`adfToPortableMarkdown`, round-trip loses:'))
})
test('compares documents whatever their key order', () => {
const page = renderFeatureTable([{ doc, name: 'Some example' }], library({ htmlToAdf: () => ({ ok: true, value: { version: 1, type: 'doc', content: [] } }) }))
assert.ok(page.includes('| holds | holds | holds |'))
})
test('names the code where a writer or a reader refuses', () => {
const page = renderFeatureTable([{ doc, name: 'Some example' }], library({ adfToHtml: () => refused, losslessMarkdownToAdf: () => refused }))
assert.ok(page.includes('| refused | holds | refused |'))
assert.ok(page.includes('`adfToLosslessMarkdown`, round-trip refused with `unsupported-node-shape`:\n\n```markdown\na\n```\n'))
assert.ok(page.includes('`adfToHtml` refuses with `unsupported-node-shape`.\n'))
})
test('fences an output in more backticks than it holds', () => {
const page = renderFeatureTable([{ doc, name: 'Some example' }], library({ adfToLosslessMarkdown: () => ({ ok: true, value: '````js\nx\n````\n' }) }))
assert.ok(page.includes('`````markdown\n````js\nx\n````\n`````\n'))
})
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
source ./docker-runner.sh
in_image_memory=4g
leg "install ($node_image)" in_image "$node_image" npm ci
leg "build ($node_image)" in_image "$node_image" npm run build
leg "write the feature table ($node_image)" in_image "$node_image" node feature-table/build.mjs