import { afterEach, describe, expect, test } from "bun:test"; import TestRenderer, { act } from "react-test-renderer"; import { InlineMarkdown } from "./inline-markdown"; const mountedRenderers: TestRenderer.ReactTestRenderer[] = []; async function render(content: string, className?: string) { (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; let renderer: TestRenderer.ReactTestRenderer | undefined; await act(async () => { renderer = TestRenderer.create( , ); }); mountedRenderers.push(renderer!); return renderer!; } function textFromInstance(node: TestRenderer.ReactTestInstance): string { return node.children .map((child) => (typeof child === "string" ? child : textFromInstance(child))) .join(""); } describe("InlineMarkdown", () => { afterEach(() => { act(() => { for (const renderer of mountedRenderers.splice(0)) { renderer.unmount(); } }); delete (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT; }); test("renders backtick spans as without visible backticks", async () => { const renderer = await render( "Move from `[server.integrations.github]` to `[run.integrations.github]`", ); const codes = renderer.root.findAllByType("code"); expect(codes).toHaveLength(2); expect(textFromInstance(codes[0]!)).toBe("[server.integrations.github]"); expect(textFromInstance(codes[1]!)).toBe("[run.integrations.github]"); const fullText = textFromInstance(renderer.root); expect(fullText).not.toContain("`"); }); test("renders **bold** as and _italic_ as ", async () => { const renderer = await render("**bold** and _italic_"); const strongs = renderer.root.findAllByType("strong"); expect(strongs).toHaveLength(1); expect(textFromInstance(strongs[0]!)).toBe("bold"); const ems = renderer.root.findAllByType("em"); expect(ems).toHaveLength(1); expect(textFromInstance(ems[0]!)).toBe("italic"); }); test("renders *italic* (asterisk form) as ", async () => { const renderer = await render("*italic*"); const ems = renderer.root.findAllByType("em"); expect(ems).toHaveLength(1); expect(textFromInstance(ems[0]!)).toBe("italic"); }); test("block markdown like headings, lists, blockquotes stays text", async () => { const renderer = await render("# heading - item > quote"); expect(renderer.root.findAllByType("h1")).toHaveLength(0); expect(renderer.root.findAllByType("h2")).toHaveLength(0); expect(renderer.root.findAllByType("ul")).toHaveLength(0); expect(renderer.root.findAllByType("li")).toHaveLength(0); expect(renderer.root.findAllByType("blockquote")).toHaveLength(0); expect(textFromInstance(renderer.root)).toContain("# heading"); expect(textFromInstance(renderer.root)).toContain("> quote"); }); test("link syntax renders the label as text without an ", async () => { const renderer = await render("[label](javascript:alert(1))"); expect(renderer.root.findAllByType("a")).toHaveLength(0); expect(textFromInstance(renderer.root)).toContain("label"); expect(textFromInstance(renderer.root)).not.toContain("javascript:"); }); test("image syntax renders the alt text without an ", async () => { const renderer = await render("![alt](x)"); expect(renderer.root.findAllByType("img")).toHaveLength(0); expect(textFromInstance(renderer.root)).toContain("alt"); }); test("raw HTML is shown literally as text, not interpreted", async () => { const renderer = await render(""); expect(renderer.root.findAllByType("script")).toHaveLength(0); expect(textFromInstance(renderer.root)).toContain(""); }); test("applies a className to the wrapper span", async () => { const renderer = await render("hello", "text-fg-2"); const wrapper = renderer.root.findByType("span"); expect(wrapper.props.className).toBe("text-fg-2"); }); });