import { EnvironmentId } from "@t3tools/contracts"; import { act, type ComponentProps, type ReactNode } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { create, type ReactTestRenderer } from "react-test-renderer"; import { describe, expect, it, vi } from "vite-plus/test"; import { getSyntaxHighlighterPromise } from "../lib/syntaxHighlighting"; import { GitHubIcon } from "./Icons"; import { Button } from "./ui/button"; import { setMarkdownTaskChecked } from "./files/filePreviewMode"; vi.mock("@effect/atom-react", () => ({ useAtomValue: () => null })); vi.mock("../hooks/useTheme", () => ({ useTheme: () => ({ resolvedTheme: "dark" }) })); vi.mock("../hooks/useSettings", async (importOriginal) => { const actual = await importOriginal(); const settings = actual.getClientSettings(); return { ...actual, useClientSettings: (select?: (value: typeof settings) => unknown) => select ? select(settings) : settings, }; }); vi.mock("./ui/tooltip", async () => { const { cloneElement, isValidElement } = await import("react"); return { Tooltip: ({ children }: { children: ReactNode }) => <>{children}, TooltipTrigger({ render, children, }: ComponentProps) { if (!isValidElement(render)) return <>{children}; return children === undefined ? render : cloneElement(render, undefined, children); }, TooltipPopup: () => null, }; }); vi.mock("../state/use-atom-query-runner", () => ({ useAtomQueryRunner: () => vi.fn() })); vi.mock("../state/use-atom-command", () => ({ useAtomCommand: () => vi.fn() })); vi.mock("../state/session", async (importOriginal) => ({ ...(await importOriginal()), usePreparedConnection: () => ({ _tag: "Loading" }), })); vi.mock("../state/entities", () => ({ readThreadShell: () => null, useProjects: () => [], useServerConfigs: () => new Map(), })); vi.mock("../remoteOpen", () => ({ useRemoteOpenResolution: () => ({ state: { mode: "local-exec" }, isResolved: true }), })); vi.mock("../editorPreferences", () => ({ useOpenInPreferredEditor: () => vi.fn(), usePreferredEditor: () => [null, vi.fn()], })); vi.mock("~/lib/openPullRequestLink", () => ({ findProjectOnChangeRequestHost: () => undefined, parseChangeRequestUrl: () => null, useOpenChangeRequestLink: () => vi.fn(), })); import ChatMarkdown, { canUseMarkdownFileShellActions, hasMarkdownFilePrimaryAction, shouldUseMarkdownFileBrowserPrimaryAction, } from "./ChatMarkdown"; function codeButton(renderer: ReactTestRenderer, label: string) { const button = renderer.root .findAllByType(Button) .find((instance) => instance.props["aria-label"] === label); if (!button) throw new Error(`Missing code button: ${label}`); return button.props as ComponentProps; } describe("ChatMarkdown favicon privacy", () => { it("suppresses private link images while preserving public links across updates", async () => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); let renderer: ReactTestRenderer | undefined; const markdown = (url: string) => ; try { await act(async () => { renderer = create(markdown("https://example.com")); }); expect(renderer!.root.findAllByType("img").map((image) => image.props.src)).toEqual([ "https://www.google.com/s2/favicons?domain=example.com&sz=32", ]); for (const url of ["http://192.168.1.10:8080", "http://localhost:3000", "http://home.arpa"]) { await act(async () => { renderer!.update(markdown(url)); }); expect(renderer!.root.findAllByType("img")).toHaveLength(0); } await act(async () => { renderer!.update(markdown("https://example.com")); }); expect(renderer!.root.findAllByType("img")).toHaveLength(1); // GitHub links draw the brand mark in currentColor instead of fetching a favicon. await act(async () => { renderer!.update(markdown("https://github.com/pingdotgg/t3code/pull/1")); }); expect(renderer!.root.findAllByType("img")).toHaveLength(0); expect(renderer!.root.findAllByType(GitHubIcon)).toHaveLength(1); } finally { await act(async () => { renderer?.unmount(); }); vi.unstubAllGlobals(); } }); }); describe("ChatMarkdown streaming", () => { it("recovers highlighting after a failed fence changes without resetting its controls", async () => { const highlighter = await getSyntaxHighlighterPromise("text"); const codeToHtml = highlighter.codeToHtml.bind(highlighter); let fail = true; vi.spyOn(highlighter, "codeToHtml").mockImplementation((...args) => { if (fail) throw new Error("Temporary highlighter failure"); return codeToHtml(...args); }); vi.spyOn(console, "error").mockImplementation(() => {}); vi.spyOn(console, "warn").mockImplementation(() => {}); vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); let renderer: ReactTestRenderer | undefined; try { await act(async () => { renderer = create( , ); }); const mounted = renderer!; const codeBlock = mounted.root.findByProps({ "data-language": "text" }); const initialWrap = codeBlock.props["data-wrap"] === "true"; const wrap = codeButton(mounted, initialWrap ? "Disable line wrap" : "Wrap lines"); await act(async () => { wrap.onClick?.({} as Parameters>[0]); }); expect(mounted.root.findAllByProps({ className: "chat-markdown-shiki" })).toHaveLength(0); fail = false; await act(async () => { mounted.update( , ); }); expect(mounted.root.findAllByProps({ className: "chat-markdown-shiki" })).toHaveLength(1); expect(mounted.root.findByProps({ "data-language": "text" })).toBe(codeBlock); expect(codeBlock.props["data-wrap"]).toBe(String(!initialWrap)); } finally { await act(async () => renderer?.unmount()); vi.unstubAllGlobals(); vi.restoreAllMocks(); } }); it("preserves code controls and details without highlighting an unchanged fence again", async () => { const highlighter = await getSyntaxHighlighterPromise("text"); const highlight = vi.spyOn(highlighter, "codeToHtml"); const writeText = vi.fn(async (_text: string) => {}); vi.stubGlobal("navigator", { clipboard: { writeText } }); vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); let renderer: ReactTestRenderer | undefined; const text = [ "```text", "First code block", "```", "", "
More", "", "Details content", "", "
", "", "Streaming reply", ].join("\n"); try { await act(async () => { renderer = create(); }); const mounted = renderer!; const codeBlock = mounted.root.findByProps({ "data-language": "text" }); const initialWrap = codeBlock.props["data-wrap"] === "true"; const wrap = codeButton(mounted, initialWrap ? "Disable line wrap" : "Wrap lines"); const copy = codeButton(mounted, "Copy code"); await act(async () => { wrap.onClick?.({} as Parameters>[0]); copy.onClick?.({} as Parameters>[0]); }); const detailsButton = mounted.root.find( (instance) => instance.type === "button" && instance.props["data-markdown-details-summary"] === "", ); await act(async () => { detailsButton.props.onClick({ nativeEvent: new Event("click") }); }); const details = mounted.root.findByProps({ "data-markdown-details": "" }); expect(details.props["data-markdown-details-open"]).toBe("true"); expect(writeText).toHaveBeenCalledWith("First code block\n"); expect(highlight).toHaveBeenCalledTimes(1); for (let index = 0; index < 10; index += 1) { await act(async () => { mounted.update(); }); } expect(highlight).toHaveBeenCalledTimes(1); expect(mounted.root.findByProps({ "data-language": "text" })).toBe(codeBlock); expect(codeBlock.props["data-wrap"]).toBe(String(!initialWrap)); expect(mounted.root.findByProps({ "data-markdown-details": "" })).toBe(details); expect(details.props["data-markdown-details-open"]).toBe("true"); await act(async () => { mounted.update( , ); }); const copyUpdated = codeButton(mounted, "Copied"); await act(async () => { copyUpdated.onClick?.({} as Parameters>[0]); }); expect(writeText).toHaveBeenLastCalledWith("Updated code block\n"); expect(highlight).toHaveBeenCalledTimes(2); } finally { await act(async () => renderer?.unmount()); vi.useRealTimers(); vi.unstubAllGlobals(); vi.restoreAllMocks(); } }); it("edits the current task text and marker after reusing a renderer", async () => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); let renderer: ReactTestRenderer | undefined; let editedText: string | undefined; const message = (text: string) => ( { editedText = setMarkdownTaskChecked(text, markerOffset, checked); renderer!.update(message(editedText)); }} /> ); try { await act(async () => { renderer = create(message("- [ ] First\n- [ ] Second")); }); const mounted = renderer!; const originalInput = mounted.root.findAllByType("input")[1]!; await act(async () => { mounted.update(message("- [ ] A longer first task\n- [ ] Second")); }); const input = mounted.root.findAllByType("input")[1]!; const listItem = mounted.root.findAllByType("li")[1]!; const { onChange } = input.props as ComponentProps<"input">; if (!onChange) throw new Error("Task checkbox has no edit handler"); await act(async () => { onChange({ currentTarget: { checked: true, closest: () => ({ dataset: { taskMarkerOffset: String(listItem.props["data-task-marker-offset"]) }, }), }, } as unknown as Parameters[0]); }); expect(input).toBe(originalInput); expect(editedText).toBe("- [ ] A longer first task\n- [x] Second"); expect(mounted.root.findAllByType("input")[1]!.props.checked).toBe(true); } finally { await act(async () => renderer?.unmount()); vi.unstubAllGlobals(); } }); }); describe("canUseMarkdownFileShellActions", () => { const environmentId = EnvironmentId.make("environment-1"); it("allows editor and file manager actions for local environments", () => { expect(canUseMarkdownFileShellActions(environmentId, "local-exec", true)).toBe(true); }); it("hides shell actions until the environment mode is resolved", () => { expect(canUseMarkdownFileShellActions(environmentId, "local-exec", false)).toBe(false); }); it("hides editor and file manager actions for remote environments", () => { expect(canUseMarkdownFileShellActions(environmentId, "remote-links", true)).toBe(false); expect(canUseMarkdownFileShellActions(environmentId, "remote-unavailable", true)).toBe(false); }); it("hides shell actions when no environment owns the markdown", () => { expect(canUseMarkdownFileShellActions(null, "local-exec", true)).toBe(false); }); }); describe("hasMarkdownFilePrimaryAction", () => { it("keeps the chip interactive when an editor, browser, or panel can open it", () => { expect( hasMarkdownFilePrimaryAction({ canOpenInEditor: true, canOpenInBrowser: false, canOpenInPanel: false, }), ).toBe(true); expect( hasMarkdownFilePrimaryAction({ canOpenInEditor: false, canOpenInBrowser: true, canOpenInPanel: false, }), ).toBe(true); expect( hasMarkdownFilePrimaryAction({ canOpenInEditor: false, canOpenInBrowser: false, canOpenInPanel: true, }), ).toBe(true); }); it("removes the link affordance when no primary action can open the file", () => { expect( hasMarkdownFilePrimaryAction({ canOpenInEditor: false, canOpenInBrowser: false, canOpenInPanel: false, }), ).toBe(false); }); }); describe("ChatMarkdown skill chips", () => { it("updates digit-leading skill labels when discovered skills change", async () => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); let renderer: ReactTestRenderer | undefined; const text = "Use $2spec with a $20k budget."; try { await act(async () => { renderer = create(); }); const mounted = renderer!; const labels = (label: string) => mounted.root.findAllByType("span").filter((node) => node.children.includes(label)); expect(labels("2Spec")).toHaveLength(0); await act(async () => { mounted.update( , ); }); expect(labels("2Spec")).toHaveLength(1); expect(labels("MoneySkill")).toHaveLength(0); await act(async () => { mounted.update(); }); expect(labels("2Spec")).toHaveLength(0); } finally { await act(async () => renderer?.unmount()); vi.unstubAllGlobals(); } }); }); describe("ChatMarkdown file option chips", () => { it("keeps the fallback button text selectable", () => { const html = renderToStaticMarkup( , ); expect(html).toContain(" { const html = renderToStaticMarkup( , ); expect(html).not.toContain("codex-file-citation"); expect(html).toContain("chat-markdown-file-link"); expect(html).toContain( 'data-markdown-copy="[report.xlsx](/tmp/project/outputs/report.xlsx)"', ); expect(html).toContain("report.xlsx"); }, ); it("leaves an unfinished streaming citation visible until it is complete", () => { const html = renderToStaticMarkup( , ); expect(html).toContain(":codex-file-citation"); expect(html).not.toContain("chat-markdown-file-link"); }); it("leaves malformed and similarly named file directives literal", () => { for (const text of [ ':codex-file-citation{purpose="output"}', ':codex-file-citation-extra{path="/tmp/project/outputs/report.xlsx"}', ]) { const html = renderToStaticMarkup(); expect(html).toContain(text.replaceAll('"', """)); expect(html).not.toContain("chat-markdown-file-link"); } }); it("preserves Codex file citation examples inside code", () => { const directive = ':codex-file-citation{path="/tmp/project/outputs/report.xlsx"}'; const html = renderToStaticMarkup( , ); expect(html.match(/:codex-file-citation/g)).toHaveLength(2); expect(html).not.toContain("chat-markdown-file-link"); }); it("preserves escaped Codex file citations as literal text", () => { const html = renderToStaticMarkup( , ); expect(html).toContain(":codex-file-citation"); expect(html).not.toContain("chat-markdown-file-link"); }); it("does not create a nested link for citations inside link text", () => { const directive = ':codex-file-citation{path="/tmp/project/outputs/report.xlsx"}'; const html = renderToStaticMarkup( , ); const renderedText = html.replace(/<[^>]+>/g, ""); expect(renderedText).toContain("codex-file-citation"); expect(html).not.toContain("chat-markdown-file-link"); }); it("renders file citations created by over-indented list recovery", () => { const html = renderToStaticMarkup( , ); expect(html).not.toContain("
");
    expect(html).toContain("Created ");
    expect(html).toContain("chat-markdown-file-link");
    expect(html).toContain("report.xlsx");
  });

  it("disambiguates Codex citations with the same basename", () => {
    const html = renderToStaticMarkup(
      ,
    );

    expect(html).toContain("index.ts · project/src");
    expect(html).toContain("index.ts · project/test");
  });

  it("preserves rejected citations created by over-indented list recovery", () => {
    const malformedHtml = renderToStaticMarkup(
      ,
    );
    const nestedLinkHtml = renderToStaticMarkup(
      ,
    );
    const nestedLinkText = nestedLinkHtml.replace(/<[^>]+>/g, "");

    expect(malformedHtml).toContain(
      "
  • Bad :codex-file-citation{purpose="output"}
  • ", ); expect(nestedLinkText).toContain( "Bad :codex-file-citation{path="/tmp/project/report.xlsx"}", ); }); }); const ARTIFACT_TEMPLATE_DIRECTIVE = '::artifact-template{skill_name="artifact-template-hello-world" skill_directory="/Users/test/.codex/skills/artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}'; describe("ChatMarkdown artifact-template cards", () => { it.each([true, false])("renders the Codex result card with parseRawHtml=%s", (parseRawHtml) => { const html = renderToStaticMarkup( undefined} />, ); expect(html).not.toContain("::artifact-template"); expect(html).toContain("chat-markdown-artifact-template"); expect(html).toContain('data-artifact-kind="document"'); expect(html).toContain('data-markdown-copy="Hello World (Document template)\n\n"'); expect(html).toContain('data-skill-name="artifact-template-hello-world"'); expect(html).toContain("Hello World"); expect(html).toContain("Document template"); expect(html).toContain("Use template"); expect(html).not.toContain("

    { const html = renderToStaticMarkup( , ); expect(html).toContain("chat-markdown-artifact-template"); expect(html).not.toContain("Use template"); }); it("leaves malformed and unfinished artifact-template directives literal", () => { const malformed = '::artifact-template{skill_name="artifact-template-hello-world" display_name="Hello World" artifact_kind="document"}'; const unfinished = ARTIFACT_TEMPLATE_DIRECTIVE.slice(0, -1); for (const text of [malformed, unfinished]) { const html = renderToStaticMarkup(); expect(html).toContain("::artifact-template"); expect(html).not.toContain("chat-markdown-artifact-template"); } }); it("leaves escaped and similarly named artifact-template directives literal", () => { for (const text of [ `\\${ARTIFACT_TEMPLATE_DIRECTIVE}`, ARTIFACT_TEMPLATE_DIRECTIVE.replace("::artifact-template", "::artifact-template-extra"), ]) { const html = renderToStaticMarkup(); expect(html).toContain("::artifact-template"); expect(html).not.toContain("chat-markdown-artifact-template"); } }); it("preserves artifact-template examples inside code", () => { const html = renderToStaticMarkup( , ); expect(html.match(/::artifact-template/g)).toHaveLength(2); expect(html).not.toContain("chat-markdown-artifact-template"); }); }); describe("shouldUseMarkdownFileBrowserPrimaryAction", () => { it("uses the browser when it is the only available primary action", () => { expect( shouldUseMarkdownFileBrowserPrimaryAction({ iconPath: "/tmp/report.html", canOpenInEditor: false, canOpenInBrowser: true, canOpenInPanel: false, }), ).toBe(true); }); it("preserves the normal editor and panel defaults for HTML files", () => { expect( shouldUseMarkdownFileBrowserPrimaryAction({ iconPath: "/tmp/report.html", canOpenInEditor: true, canOpenInBrowser: true, canOpenInPanel: false, }), ).toBe(false); expect( shouldUseMarkdownFileBrowserPrimaryAction({ iconPath: "/tmp/report.html", canOpenInEditor: false, canOpenInBrowser: true, canOpenInPanel: true, }), ).toBe(false); }); it("continues to open PDF files in the browser by default", () => { expect( shouldUseMarkdownFileBrowserPrimaryAction({ iconPath: "/tmp/report.pdf", canOpenInEditor: true, canOpenInBrowser: true, canOpenInPanel: true, }), ).toBe(true); }); }); describe("ChatMarkdown Windows file links", () => { const environmentId = EnvironmentId.make("env-windows"); it.each([true, false])("preserves drive paths with parseRawHtml=%s", (parseRawHtml) => { const html = renderToStaticMarkup( , ); expect(html).toContain('href="C:/Users/shawn/project/src/main.ts"'); expect(html).toContain("chat-markdown-file-link"); }); it.each([true, false])("normalizes backslashes with parseRawHtml=%s", (parseRawHtml) => { const html = renderToStaticMarkup( , ); expect(html).toContain('href="C:/Users/shawn/project/src/main.ts"'); expect(html).toContain("chat-markdown-file-link"); }); it.each([true, false])( "distinguishes same-named backslash paths with parseRawHtml=%s", (parseRawHtml) => { const html = renderToStaticMarkup( , ); expect(html).toContain("index.ts · project/src"); expect(html).toContain("index.ts · project/test"); }, ); it.each([true, false])( "does not disambiguate the same file in links and inline code with parseRawHtml=%s", (parseRawHtml) => { const path = String.raw`C:\Users\shawn\project\src\main.ts`; const html = renderToStaticMarkup( , ); expect(html.match(/chat-markdown-file-link/g)).toHaveLength(2); expect(html).not.toContain("main.ts ·"); }, ); it.each([true, false])("preserves reference links with parseRawHtml=%s", (parseRawHtml) => { const html = renderToStaticMarkup( , ); expect(html).toContain('href="C:/Users/shawn/project/src/main.ts"'); expect(html).toContain("chat-markdown-file-link"); }); it.each([true, false])("still rejects unsafe schemes with parseRawHtml=%s", (parseRawHtml) => { const html = renderToStaticMarkup( , ); expect(html).not.toContain("javascript:"); expect(html).not.toContain("d:alert"); expect(html).not.toContain("chat-markdown-file-link"); }); });