mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-08 09:37:47 +03:00
166 lines
6.5 KiB
TypeScript
166 lines
6.5 KiB
TypeScript
import { readFileSync } from "node:fs"
|
|
import { resolve } from "node:path"
|
|
import { describe, expect, test } from "bun:test"
|
|
import { tests as examples } from "commonmark-spec"
|
|
import { createMarkdownStream, renderBlocksToHtml, type BlockPatch, type MarkdownBlock } from "../src/index"
|
|
import { rng } from "./fuzz"
|
|
|
|
interface SpecExample {
|
|
markdown: string
|
|
html: string
|
|
example: number
|
|
start_line?: number
|
|
end_line?: number
|
|
section: string
|
|
}
|
|
|
|
const gfmExamples: SpecExample[] = JSON.parse(readFileSync(resolve(import.meta.dir, "gfm-spec.json"), "utf8"))
|
|
|
|
const CORPUS_TRANSCRIPTS = [
|
|
"# Architecture Review\n\nHere is the summary with **bold**, *italic*, `src/stream.ts`, and [CommonMark][cm].\n\n1. First item:\n\n ```ts\n export const answer = 42\n ```\n\n Follow-up paragraph inside item 1.\n\n2. Second item with a nested blockquote:\n > Quoted line one\n lazy continuation line two\n\n| Phase | Cost | Share |\n| :--- | ---: | :---: |\n| Split | 6 us | 5% |\n| Render | 185 us | 10% |\n\n$$\n\\int_0^1 x^2\\,dx = \\frac{1}{3}\n$$\n\n[cm]: https://spec.commonmark.org/\n",
|
|
"<details>\n<summary>Click to expand</summary>\n\n- [x] Completed task\n- [ ] Pending task with ~~old~~ new text and https://opencode.ai.\n\n```mermaid\ngraph TD\n A --> B\n```\n</details>\n\nSetext Heading\n---\n\nTrailing paragraph with $E = mc^2$ and $5 price.\n",
|
|
]
|
|
|
|
function applyPatches(replica: MarkdownBlock[], patches: ReadonlyArray<BlockPatch>) {
|
|
for (const patch of patches) {
|
|
if (patch.op === "append") replica.push(patch.block)
|
|
else if (patch.op === "replace" || patch.op === "finalize") replica[patch.index] = patch.block
|
|
else if (patch.op === "truncate") replica.length = patch.keepCount
|
|
}
|
|
}
|
|
|
|
function assertStreamingInvariance(md: string, chunks: string[], gfm: boolean, math: boolean, wantHtml?: string) {
|
|
const oneShot = createMarkdownStream({ gfm, math, heal: false, holdPendingMarkers: false, renderHtml: true })
|
|
const wantBlocks = oneShot.update(md, { streaming: false }).blocks
|
|
const expectedHtml = wantHtml ?? renderBlocksToHtml(wantBlocks, { gfm, math })
|
|
|
|
const stream = createMarkdownStream({ gfm, math, heal: true, holdPendingMarkers: true, renderHtml: true })
|
|
const replica: MarkdownBlock[] = []
|
|
const settledSnapshot = new Map<number, MarkdownBlock>()
|
|
|
|
for (const delta of chunks) {
|
|
const frame = stream.append(delta)
|
|
applyPatches(replica, frame.patches)
|
|
expect(replica.length).toBe(frame.blocks.length)
|
|
|
|
for (let k = 0; k < frame.blocks.length; k++) {
|
|
expect(replica[k]).toBe(frame.blocks[k])
|
|
const block = frame.blocks[k]!
|
|
const prev = settledSnapshot.get(block.id)
|
|
|
|
if (prev) {
|
|
const refUpdated = frame.patches.some((p) => p.op === "replace" && p.id === block.id)
|
|
|
|
if (!refUpdated) {
|
|
expect(block).toBe(prev)
|
|
}
|
|
}
|
|
|
|
if (block.status === "settled") {
|
|
settledSnapshot.set(block.id, block)
|
|
}
|
|
}
|
|
}
|
|
|
|
const finalFrame = stream.finalize()
|
|
applyPatches(replica, finalFrame.patches)
|
|
expect(replica.length).toBe(finalFrame.blocks.length)
|
|
|
|
for (let k = 0; k < finalFrame.blocks.length; k++) {
|
|
expect(replica[k]).toBe(finalFrame.blocks[k])
|
|
expect(finalFrame.blocks[k]!.status).toBe("settled")
|
|
}
|
|
|
|
expect(renderBlocksToHtml(finalFrame.blocks, { gfm, math })).toBe(expectedHtml)
|
|
}
|
|
|
|
function tokenChunks(md: string, r: () => number): string[] {
|
|
const chunks: string[] = []
|
|
let pos = 0
|
|
|
|
while (pos < md.length) {
|
|
const roll = r()
|
|
const step = roll < 0.2 ? 1 : roll < 0.75 ? 2 + Math.floor(r() * 5) : 7 + Math.floor(r() * 14)
|
|
chunks.push(md.slice(pos, pos + step))
|
|
pos += step
|
|
}
|
|
|
|
return chunks
|
|
}
|
|
|
|
describe("CommonMark 0.31.2 + GFM spec compliance", () => {
|
|
test("passes all 652 CommonMark 0.31.2 spec examples (100%)", () => {
|
|
expect(examples.length).toBe(652)
|
|
const failures: Array<{ example: number; section: string; markdown: string; got: string; want: string }> = []
|
|
|
|
for (const ex of examples) {
|
|
const md = ex.markdown.replace(/→/g, "\t")
|
|
const want = ex.html.replace(/→/g, "\t")
|
|
const stream = createMarkdownStream({ gfm: false, math: false, heal: false, holdPendingMarkers: false, renderHtml: true })
|
|
const frame = stream.update(md, { streaming: false })
|
|
const got = renderBlocksToHtml(frame.blocks, { gfm: false, math: false })
|
|
|
|
if (got !== want) {
|
|
failures.push({ example: ex.example, section: ex.section, markdown: md, got, want })
|
|
}
|
|
}
|
|
|
|
expect(failures).toEqual([])
|
|
})
|
|
|
|
test("passes all 24 GFM spec examples (tables, task lists, strikethrough, autolinks)", () => {
|
|
expect(gfmExamples.length).toBe(24)
|
|
const failures: Array<{ example: number; section: string; markdown: string; got: string; want: string }> = []
|
|
|
|
for (const ex of gfmExamples) {
|
|
const stream = createMarkdownStream({ gfm: true, math: false, heal: false, holdPendingMarkers: false, renderHtml: true })
|
|
const frame = stream.update(ex.markdown, { streaming: false })
|
|
const got = renderBlocksToHtml(frame.blocks, { gfm: true, math: false })
|
|
|
|
if (got !== ex.html) {
|
|
failures.push({ example: ex.example, section: ex.section, markdown: ex.markdown, got, want: ex.html })
|
|
}
|
|
}
|
|
|
|
expect(failures).toEqual([])
|
|
})
|
|
|
|
test("streaming invariance: 1-char, seeded random, and token chunking across all 652 CommonMark + 24 GFM + transcripts", () => {
|
|
const allSpec = [
|
|
...examples.map((ex) => ({
|
|
...ex,
|
|
markdown: ex.markdown.replace(/→/g, "\t"),
|
|
html: ex.html.replace(/→/g, "\t"),
|
|
gfm: false,
|
|
})),
|
|
...gfmExamples.map((ex) => ({ ...ex, gfm: true })),
|
|
]
|
|
const rand = rng(0x5eed1234)
|
|
|
|
for (const ex of allSpec) {
|
|
// 1-char streaming on every single spec example (652 CommonMark + 24 GFM)
|
|
assertStreamingInvariance(ex.markdown, [...ex.markdown], ex.gfm, false, ex.html)
|
|
|
|
// Seeded random chunking (1..19 chars)
|
|
const randomChunks: string[] = []
|
|
let pos = 0
|
|
|
|
while (pos < ex.markdown.length) {
|
|
const len = 1 + Math.floor(rand() * 19)
|
|
randomChunks.push(ex.markdown.slice(pos, pos + len))
|
|
pos += len
|
|
}
|
|
|
|
assertStreamingInvariance(ex.markdown, randomChunks, ex.gfm, false, ex.html)
|
|
|
|
// Token chunking
|
|
assertStreamingInvariance(ex.markdown, tokenChunks(ex.markdown, rand), ex.gfm, false, ex.html)
|
|
}
|
|
|
|
for (const md of CORPUS_TRANSCRIPTS) {
|
|
assertStreamingInvariance(md, [...md], true, true)
|
|
assertStreamingInvariance(md, tokenChunks(md, rand), true, true)
|
|
}
|
|
})
|
|
})
|