// @vitest-environment jsdom import { describe, expect, test } from "vitest" import type { Ast } from "../ast/schemas.js" import { astToTiptapDoc } from "./mdwc-to-tiptap.js" import { tiptapDocToAst } from "./tiptap-to-mdwc.js" import { Editor } from "@tiptap/core" import { MarkdownWc } from "./markdown-wc.js" import { parseMarkdown } from "../ast/parse-markdown.js" import { serializeAst } from "../ast/serialize-ast.js" function roundtrip(ast: Ast): Ast { const pmDoc = astToTiptapDoc(ast) const out = tiptapDocToAst(pmDoc) return out } function roundtripThroughEditor(ast: Ast): Ast { const pmDoc = astToTiptapDoc(ast) const editor = new Editor({ extensions: MarkdownWc(), content: pmDoc, }) const outJSON = editor.getJSON() const result = tiptapDocToAst(outJSON) editor.destroy() return result } function roundtripMarkdownThroughEditor(markdown: string): string { const ast = parseMarkdown(markdown) const editorAst = roundtripThroughEditor(ast) return serializeAst(editorAst) } describe("root & paragraph", () => { test("simple paragraph", () => { const input: Ast = { type: "root", children: [{ type: "paragraph", children: [{ type: "text", value: "Hello world." }] }], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) }) describe("vendor data roundtrip", () => { test("data.id and arbitrary keys survive ast → pm → ast", () => { const input: Ast = { type: "root", children: [ { type: "heading", depth: 2, data: { id: "H1", foo: "bar", nested: { a: 1 } }, children: [{ type: "text", value: "Title" }], }, { type: "paragraph", data: { id: "P1", custom: { x: 42 } }, children: [{ type: "text", value: "Content" }], }, ], } const pm = astToTiptapDoc(input) const output = tiptapDocToAst(pm) // Expect full structural equality including data.* preservation expect(output).toEqual(input) }) }) describe("heading", () => { for (let level = 1 as 1 | 2 | 3 | 4 | 5 | 6; level <= 6; level++) { test(`h${level}`, () => { const input: Ast = { type: "root", children: [ { type: "heading", depth: level, children: [{ type: "text", value: "Heading" }] }, ], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) } }) describe("paragraph marks", () => { test("bold + italic + text", () => { const input: Ast = { type: "root", children: [ { type: "paragraph", children: [ { type: "text", value: "Hello " }, { type: "strong", children: [{ type: "text", value: "world" }] }, { type: "text", value: " and " }, { type: "emphasis", children: [{ type: "text", value: "friends" }] }, { type: "text", value: "." }, ], }, ], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) test("strong only", () => { const input: Ast = { type: "root", children: [ { type: "paragraph", children: [{ type: "strong", children: [{ type: "text", value: "bold" }] }], }, ], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) test("italic only", () => { const input: Ast = { type: "root", children: [ { type: "paragraph", children: [{ type: "emphasis", children: [{ type: "text", value: "italic" }] }], }, ], } as any const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) test("inline code", () => { const input: Ast = { type: "root", children: [{ type: "paragraph", children: [{ type: "inlineCode", value: "code" }] }], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) test("strikethrough", () => { const input: Ast = { type: "root", children: [ { type: "paragraph", children: [{ type: "delete", children: [{ type: "text", value: "strike" }] }], }, ], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) test("link with text and title", () => { const input: Ast = { type: "root", children: [ { type: "paragraph", children: [ { type: "link", url: "https://example.com", title: "title", children: [{ type: "text", value: "text" }], }, ], }, ], } const output = roundtrip(input) expect(output).toEqual(input) const editorOutput = roundtripThroughEditor(input) expect(editorOutput).toEqual(input) }) // is a valid markdown form test.skip("link retains literal bracket syntax when label matches url", () => { const markdown = "[https://agents.md/](https://agents.md/)" const roundtripped = roundtripMarkdownThroughEditor(markdown) expect(roundtripped).toBe(markdown) }) }) describe("unsupported blocks", () => { test("html block preserved through editor", () => { const input: Ast = { type: "root", children: [ { type: "html", value: '