749 lines · 26.7 KB · TypeScript
RawDownload
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<typeof import("../hooks/useSettings")>();  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<typeof import("./ui/tooltip").TooltipTrigger>) {      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<typeof import("../state/session")>()),  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<typeof Button>;} 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) => <ChatMarkdown cwd="/tmp/project" text={`[Link](${url})`} />;    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(          <ChatMarkdown cwd="/tmp/project" text={"```text\ninitial\n```"} isStreaming />,        );      });      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<NonNullable<typeof wrap.onClick>>[0]);      });      expect(mounted.root.findAllByProps({ className: "chat-markdown-shiki" })).toHaveLength(0);       fail = false;      await act(async () => {        mounted.update(          <ChatMarkdown cwd="/tmp/project" text={"```text\nrecovered\n```"} isStreaming />,        );      });      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",      "```",      "",      "<details><summary>More</summary>",      "",      "Details content",      "",      "</details>",      "",      "Streaming reply",    ].join("\n");     try {      await act(async () => {        renderer = create(<ChatMarkdown cwd="/tmp/project" text={text} isStreaming />);      });      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<NonNullable<typeof wrap.onClick>>[0]);        copy.onClick?.({} as Parameters<NonNullable<typeof copy.onClick>>[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(<ChatMarkdown cwd="/tmp/project" text={`${text} ${index}`} isStreaming />);        });      }       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(          <ChatMarkdown            cwd="/tmp/project"            text={text.replace("First code block", "Updated code block")}            isStreaming          />,        );      });      const copyUpdated = codeButton(mounted, "Copied");      await act(async () => {        copyUpdated.onClick?.({} as Parameters<NonNullable<typeof copyUpdated.onClick>>[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) => (      <ChatMarkdown        cwd="/tmp/project"        text={text}        onTaskListChange={({ markerOffset, checked }) => {          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<typeof onChange>[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(<ChatMarkdown cwd="/tmp/project" text={text} />);      });      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(          <ChatMarkdown            cwd="/tmp/project"            text={text}            skills={[              { name: "2spec", displayName: "2Spec" },              { name: "20k", displayName: "MoneySkill" },            ]}          />,        );      });      expect(labels("2Spec")).toHaveLength(1);      expect(labels("MoneySkill")).toHaveLength(0);       await act(async () => {        mounted.update(<ChatMarkdown cwd="/tmp/project" text={text} skills={[]} />);      });      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(      <ChatMarkdown cwd="/tmp/project" text="[Source](/tmp/project/src/main.ts)" />,    );     expect(html).toContain("<button");    expect(html).toContain('aria-haspopup="menu"');    expect(html).toContain("select-text");  });   it.each([true, false])(    "renders Codex file citations as file chips with parseRawHtml=%s",    (parseRawHtml) => {      const html = renderToStaticMarkup(        <ChatMarkdown          cwd="/tmp/project"          text={            'Created :codex-file-citation{path="/tmp/project/outputs/report.xlsx" purpose="output"}.'          }          lineBreaks={!parseRawHtml}          parseRawHtml={parseRawHtml}        />,      );       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(      <ChatMarkdown        cwd="/tmp/project"        text={'Created :codex-file-citation{path="/tmp/project/outputs/report.xlsx"'}        isStreaming      />,    );     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(<ChatMarkdown cwd="/tmp/project" text={text} />);       expect(html).toContain(text.replaceAll('"', "&quot;"));      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(      <ChatMarkdown        cwd="/tmp/project"        text={`Example: \`${directive}\`\n\n\`\`\`text\n${directive}\n\`\`\``}      />,    );     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(      <ChatMarkdown        cwd="/tmp/project"        text={'Example: \\:codex-file-citation{path="/tmp/project/outputs/report.xlsx"}'}      />,    );     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(      <ChatMarkdown cwd="/tmp/project" text={`[See ${directive}](https://example.com)`} />,    );    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(      <ChatMarkdown        cwd="/tmp/project"        text={'-       Created :codex-file-citation{path="/tmp/project/outputs/report.xlsx"}'}      />,    );     expect(html).not.toContain("<pre>");    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(      <ChatMarkdown        cwd="/tmp/project"        text={          'Changed :codex-file-citation{path="/tmp/project/src/index.ts"} and :codex-file-citation{path="/tmp/project/test/index.ts"}.'

Lines 1–500 of 749.