Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

96 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { type DocNode, docToMarkdown, escapeText, markdownToDoc } from "./chat-compose.ts";
5
6const text = (value: string, ...marks: (string | { type: string; attrs?: Record<string, unknown> })[]): DocNode => ({
7 type: "text",
8 text: value,
9 ...(marks.length ? { marks: marks.map((mark) => (typeof mark === "string" ? { type: mark } : mark)) } : {}),
10});
11const p = (...content: DocNode[]): DocNode => (content.length ? { type: "paragraph", content } : { type: "paragraph" });
12const doc = (...content: DocNode[]): DocNode => ({ type: "doc", content });
13
14test("formatting is written as Markdown", () => {
15 assert.equal(
16 docToMarkdown(doc(p(text("bold", "bold"), text(" "), text("it", "italic"), text(" "), text("gone", "strike"), text(" "), text("a*b", "code")))),
17 "**bold** _it_ ~~gone~~ `a*b`",
18 );
19 // Marks that overlap open and close once.
20 assert.equal(docToMarkdown(doc(p(text("a", "bold"), text("b", "bold", "italic"), text("c", "bold")))), "**a_b_c**");
21 // Spaces at a mark's edges sit outside it.
22 assert.equal(docToMarkdown(doc(p(text("say "), text(" hi ", "bold"), text("there")))), "say **hi** there");
23 assert.equal(docToMarkdown(doc(p(text("tick ` in", "code")))), "`` tick ` in ``");
24});
25
26test("links: with their text, or bare when the text is the address", () => {
27 const link = (href: string) => ({ type: "link", attrs: { href } });
28 assert.equal(docToMarkdown(doc(p(text("the docs", link("https://g1t.sh/docs"))))), "[the docs](https://g1t.sh/docs)");
29 assert.equal(docToMarkdown(doc(p(text("https://g1t.sh", link("https://g1t.sh"))))), "https://g1t.sh");
30 assert.equal(docToMarkdown(doc(p(text("x", link("https://a.example/a b"))))), "[x](<https://a.example/a%20b>)");
31});
32
33test("each paragraph is a line; an empty one is a blank line", () => {
34 assert.equal(docToMarkdown(doc(p(text("one")), p(text("two")), p(), p(text("three")))), "one\ntwo\n\nthree");
35 assert.equal(docToMarkdown(doc(p(), p(text("x")), p())), "x");
36});
37
38test("text that only looks like Markdown is escaped, and mentions stay mentions", () => {
39 assert.equal(escapeText("2*3 and snake_case and _edge_ and [x] and `t` and ~5"), "2\\*3 and snake_case and \\_edge\\_ and \\[x\\] and \\`t\\` and \\~5");
40 assert.equal(
41 docToMarkdown(doc(p(text("- not a list")), p(text("1. not either")), p(text("# nor this")), p(text("> or this")))),
42 "\\- not a list\n1\\. not either\n\\# nor this\n\\> or this",
43 );
44 assert.equal(docToMarkdown(doc(p(text("ask @ana in #web about #12")))), "ask @ana in #web about #12");
45});
46
47test("lists, quotes and code blocks", () => {
48 const item = (...content: DocNode[]): DocNode => ({ type: "listItem", content });
49 assert.equal(
50 docToMarkdown(
51 doc(
52 p(text("Plan:")),
53 {
54 type: "bulletList",
55 content: [item(p(text("one"))), item(p(text("two")), { type: "orderedList", attrs: { start: 1 }, content: [item(p(text("deep")))] })],
56 },
57 p(text("after")),
58 ),
59 ),
60 "Plan:\n\n- one\n- two\n 1. deep\n\nafter",
61 );
62 assert.equal(docToMarkdown(doc({ type: "orderedList", attrs: { start: 3 }, content: [item(p(text("c"))), item(p(text("d")))] })), "3. c\n4. d");
63 assert.equal(docToMarkdown(doc({ type: "blockquote", content: [p(text("quoted")), p(), p(text("more"))] })), "> quoted\n>\n> more");
64 assert.equal(
65 docToMarkdown(doc({ type: "codeBlock", attrs: { language: "ts" }, content: [{ type: "text", text: "const a = 1;\n\n\nconst b = `x`;" }] })),
66 "```ts\nconst a = 1;\n\n\nconst b = `x`;\n```",
67 );
68 assert.equal(
69 docToMarkdown(doc({ type: "codeBlock", attrs: { language: null }, content: [{ type: "text", text: "```\nfence\n```" }] })),
70 "````\n```\nfence\n```\n````",
71 );
72});
73
74test("Markdown comes back into the composer as it was written", () => {
75 const samples = [
76 "**bold** _it_ ~~gone~~ `code`",
77 "one\ntwo\n\nthree",
78 "Plan:\n\n- one\n- two\n 1. deep\n\nafter",
79 "3. c\n4. d",
80 "> quoted\n>\n> more",
81 "```ts\nconst a = 1;\n\nconst b = 2;\n```",
82 "[the docs](https://g1t.sh/docs) and https://g1t.sh",
83 "ask @ana in #web about g1t#12",
84 "\\- not a list and 2\\*3 and snake_case",
85 "**a _b_ c**",
86 "| Session | Length |\n| --- | --: |\n| **Welcome** | `45` min |",
87 ];
88 for (const sample of samples) assert.equal(docToMarkdown(markdownToDoc(sample)), sample, sample);
89});
90
91test("Markdown the composer cannot write still comes in", () => {
92 // A heading is a bold line; a rule is left out; an image is a link.
93 assert.equal(docToMarkdown(markdownToDoc("## Notes\n---\ntext")), "**Notes**\ntext");
94 assert.equal(docToMarkdown(markdownToDoc("*star em* and __strong__ and ~one~")), "_star em_ and **strong** and ~~one~~");
95 assert.deepEqual(markdownToDoc(""), { type: "doc", content: [{ type: "paragraph" }] });
96});