892 lines · 32.0 KB · TypeScript
RawDownload
import "../index.css"; import { page } from "vite-plus/test/browser";import { afterEach, describe, expect, it, vi } from "vite-plus/test";import { render } from "vitest-browser-react"; const {  contextMenuShowMock,  openFileInPreviewMock,  openInPreferredEditorMock,  openUrlInPreviewMock,  readLocalApiMock,} = vi.hoisted(() => ({  contextMenuShowMock: vi.fn(),  openFileInPreviewMock: vi.fn(async () => undefined),  openInPreferredEditorMock: vi.fn(async () => "vscode"),  openUrlInPreviewMock: vi.fn(async () => undefined),  readLocalApiMock: vi.fn(() => ({    contextMenu: { show: contextMenuShowMock },    server: { getConfig: vi.fn(async () => ({ availableEditors: ["vscode"] })) },    shell: {      openExternal: vi.fn(async () => undefined),      openInEditor: vi.fn(async () => undefined),    },  })),})); vi.mock("../editorPreferences", () => ({  openInPreferredEditor: openInPreferredEditorMock,})); vi.mock("../localApi", () => ({  ensureLocalApi: vi.fn(() => {    throw new Error("ensureLocalApi not implemented in browser test");  }),  readLocalApi: readLocalApiMock,})); vi.mock("../previewStateStore", async (importOriginal) => ({  ...(await importOriginal<typeof import("../previewStateStore")>()),  isPreviewSupportedInRuntime: () => true,})); vi.mock("../browser/openFileInPreview", async (importOriginal) => ({  ...(await importOriginal<typeof import("../browser/openFileInPreview")>()),  openFileInPreview: openFileInPreviewMock,  openUrlInPreview: openUrlInPreviewMock,})); import ChatMarkdown from "./ChatMarkdown";import { serializeTableElementToCsv, serializeTableElementToMarkdown } from "../markdown-clipboard";import { EnvironmentId, ThreadId } from "@t3tools/contracts";import { selectThreadRightPanelState, useRightPanelStore } from "../rightPanelStore"; const threadRef = {  environmentId: EnvironmentId.make("environment-test"),  threadId: ThreadId.make("thread-test"),}; describe("ChatMarkdown", () => {  afterEach(() => {    openInPreferredEditorMock.mockClear();    openFileInPreviewMock.mockClear();    openUrlInPreviewMock.mockClear();    contextMenuShowMock.mockReset();    readLocalApiMock.mockClear();    useRightPanelStore.setState({ byThreadKey: {} });    localStorage.clear();    document.body.innerHTML = "";  });   it("makes task-list checkboxes interactive when a change handler is provided", async () => {    const onTaskListChange = vi.fn();    const screen = await render(      <ChatMarkdown        text={"- [ ] Ship it"}        cwd="/repo/project"        onTaskListChange={onTaskListChange}      />,    );     try {      const checkbox = page.getByRole("checkbox", { name: "Toggle task" });      await expect.element(checkbox).not.toBeDisabled();      await checkbox.click();      expect(onTaskListChange).toHaveBeenCalledWith({ markerOffset: 2, checked: true });    } finally {      await screen.unmount();    }  });   it("rewrites file uri hrefs into direct paths before rendering", async () => {    const filePath =      "/Users/yashsingh/p/sco/claude-code-extract/src/utils/permissions/PermissionRule.ts";    const screen = await render(      <ChatMarkdown text={`[PermissionRule.ts](file://${filePath})`} cwd="/repo/project" />,    );     try {      const link = page.getByRole("link", { name: "PermissionRule.ts" });      await expect.element(link).toBeInTheDocument();      await expect.element(link).toHaveAttribute("href", filePath);       await link.click();       await vi.waitFor(() => {        expect(openInPreferredEditorMock).toHaveBeenCalledWith(expect.anything(), filePath);      });    } finally {      await screen.unmount();    }  });   it("keeps line anchors working after rewriting file uri hrefs", async () => {    const filePath =      "/Users/yashsingh/p/sco/claude-code-extract/src/utils/permissions/PermissionRule.ts";    const screen = await render(      <ChatMarkdown text={`[PermissionRule.ts:1](file://${filePath}#L1)`} cwd="/repo/project" />,    );     try {      const link = page.getByRole("link", { name: "PermissionRule.ts · L1" });      await expect.element(link).toBeInTheDocument();      await expect.element(link).toHaveAttribute("href", `${filePath}:1`);       await link.click();       await vi.waitFor(() => {        expect(openInPreferredEditorMock).toHaveBeenCalledWith(expect.anything(), `${filePath}:1`);      });    } finally {      await screen.unmount();    }  });   it("shows column information inline when present", async () => {    const filePath =      "/Users/yashsingh/p/sco/claude-code-extract/src/utils/permissions/PermissionRule.ts";    const screen = await render(      <ChatMarkdown text={`[PermissionRule.ts](file://${filePath}#L1C7)`} cwd="/repo/project" />,    );     try {      const link = page.getByRole("link", { name: "PermissionRule.ts · L1:C7" });      await expect.element(link).toBeInTheDocument();      await expect.element(link).toHaveAttribute("href", `${filePath}:1:7`);       await link.click();       await vi.waitFor(() => {        expect(openInPreferredEditorMock).toHaveBeenCalledWith(          expect.anything(),          `${filePath}:1:7`,        );      });    } finally {      await screen.unmount();    }  });   it("disambiguates duplicate file basenames inline", async () => {    const firstPath = "/Users/yashsingh/p/t3code/apps/web/src/components/chat/MessagesTimeline.tsx";    const secondPath = "/Users/yashsingh/p/t3code/apps/web/src/components/MessagesTimeline.tsx";    const screen = await render(      <ChatMarkdown        text={`See [MessagesTimeline.tsx](file://${firstPath}) and [MessagesTimeline.tsx](file://${secondPath}).`}        cwd="/repo/project"      />,    );     try {      await expect        .element(page.getByRole("link", { name: "MessagesTimeline.tsx · components/chat" }))        .toBeInTheDocument();      await expect        .element(page.getByRole("link", { name: "MessagesTimeline.tsx · src/components" }))        .toBeInTheDocument();    } finally {      await screen.unmount();    }  });   it("keeps normal web links unchanged", async () => {    const screen = await render(      <ChatMarkdown text="[OpenAI](https://openai.com/docs)" cwd="/repo/project" />,    );     try {      const link = page.getByRole("link", { name: "OpenAI" });      await expect.element(link).toBeInTheDocument();      await expect.element(link).toHaveAttribute("href", "https://openai.com/docs");      await expect.element(link).toHaveAttribute("target", "_blank");      const favicon = link.element().querySelector<HTMLElement>(".chat-markdown-link-favicon");      const leading = link.element().querySelector<HTMLElement>(".chat-markdown-link-leading");      expect(favicon).not.toBeNull();      expect(leading).not.toBeNull();      expect(leading?.contains(favicon)).toBe(true);      expect(getComputedStyle(leading!).display).toBe("inline");      expect(getComputedStyle(leading!).whiteSpace).toBe("nowrap");      expect(getComputedStyle(favicon!).verticalAlign).not.toBe("baseline");      expect(leading?.textContent).toBe("O");      expect(link.element().textContent).toBe("OpenAI");      expect(getComputedStyle(link.element()).textDecorationLine).toBe("none");      expect(link.element().querySelector("img, svg")?.getBoundingClientRect().width).toBe(14);      await link.hover();      expect(getComputedStyle(link.element()).backgroundImage).not.toBe("none");      await expect.element(page.getByText("https://openai.com/docs")).toBeVisible();    } finally {      await screen.unmount();    }  });   it("opens web links in the integrated browser from the context menu", async () => {    contextMenuShowMock.mockResolvedValue("open-in-browser");    const screen = await render(      <ChatMarkdown        text="[OpenAI](https://openai.com/docs)"        cwd="/repo/project"        threadRef={threadRef}      />,    );     try {      const link = page.getByRole("link", { name: "OpenAI" }).element();      link.dispatchEvent(        new MouseEvent("contextmenu", {          bubbles: true,          cancelable: true,          clientX: 12,          clientY: 24,        }),      );       await vi.waitFor(() => {        expect(contextMenuShowMock).toHaveBeenCalled();        expect(openUrlInPreviewMock).toHaveBeenCalledWith(threadRef, "https://openai.com/docs");      });    } finally {      await screen.unmount();    }  });   it("offers integrated browser opening for HTML file links", async () => {    contextMenuShowMock.mockResolvedValue("open-in-browser");    const filePath = "/repo/project/report.html";    const screen = await render(      <ChatMarkdown text="[report.html](report.html)" cwd="/repo/project" threadRef={threadRef} />,    );     try {      const link = page.getByRole("link", { name: "report.html" }).element();      link.dispatchEvent(        new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: 4, clientY: 8 }),      );       await vi.waitFor(() => {        expect(contextMenuShowMock).toHaveBeenCalledWith(          expect.arrayContaining([            expect.objectContaining({              id: "open-in-browser",              label: "Open in integrated browser",            }),          ]),          { x: 4, y: 8 },        );        expect(openFileInPreviewMock).toHaveBeenCalledWith(threadRef, filePath);      });    } finally {      await screen.unmount();    }  });   it("opens code file links in the right-panel file preview", async () => {    const screen = await render(      <ChatMarkdown        text="[ChatMarkdown.tsx](apps/web/src/components/ChatMarkdown.tsx#L978)"        cwd="/repo/project"        threadRef={threadRef}      />,    );     try {      await page.getByRole("link", { name: "ChatMarkdown.tsx · L978" }).click();       await vi.waitFor(() => {        expect(          selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, threadRef),        ).toMatchObject({          isOpen: true,          activeSurfaceId: "file:apps/web/src/components/ChatMarkdown.tsx",          surfaces: [            expect.objectContaining({              relativePath: "apps/web/src/components/ChatMarkdown.tsx",              revealLine: 978,              revealRequestId: 1,            }),          ],        });        expect(openInPreferredEditorMock).not.toHaveBeenCalled();        expect(openFileInPreviewMock).not.toHaveBeenCalled();      });    } finally {      await screen.unmount();    }  });   it("opens HTML and PDF file links in the integrated browser preview", async () => {    const screen = await render(      <ChatMarkdown        text="[report.html](report.html) [report.pdf](report.pdf)"        cwd="/repo/project"        threadRef={threadRef}      />,    );     try {      await page.getByRole("link", { name: "report.html" }).click();      await page.getByRole("link", { name: "report.pdf" }).click();       await vi.waitFor(() => {        expect(openFileInPreviewMock).toHaveBeenNthCalledWith(          1,          threadRef,          "/repo/project/report.html",        );        expect(openFileInPreviewMock).toHaveBeenNthCalledWith(          2,          threadRef,          "/repo/project/report.pdf",        );        expect(openInPreferredEditorMock).not.toHaveBeenCalled();      });    } finally {      await screen.unmount();    }  });   it("keeps opening file links in the editor from the context menu", async () => {    contextMenuShowMock.mockResolvedValue("open");    const filePath = "/repo/project/src/index.ts";    const screen = await render(      <ChatMarkdown text="[index.ts](src/index.ts)" cwd="/repo/project" threadRef={threadRef} />,    );     try {      page        .getByRole("link", { name: "index.ts" })        .element()        .dispatchEvent(          new MouseEvent("contextmenu", {            bubbles: true,            cancelable: true,            clientX: 4,            clientY: 8,          }),        );       await vi.waitFor(() => {        expect(openInPreferredEditorMock).toHaveBeenCalledWith(expect.anything(), filePath);      });    } finally {      await screen.unmount();    }  });   it("keeps a favicon with the leading segment of a wrapping URL", async () => {    const url = "https://github.com/pingdotgg/t3code/pull/3017/changes";    const screen = await render(      <div style={{ width: 180 }}>        <ChatMarkdown text={`[${url}](${url})`} cwd="/repo/project" />      </div>,    );     try {      const link = page.getByRole("link", { name: url });      const leading = link.element().querySelector<HTMLElement>(".chat-markdown-link-leading");      const favicon = link.element().querySelector<HTMLElement>(".chat-markdown-link-favicon");      expect(leading).not.toBeNull();      expect(favicon).not.toBeNull();      expect(leading?.contains(favicon)).toBe(true);      expect(leading?.textContent).toBe("https://");      expect(getComputedStyle(leading!).display).toBe("inline");      expect(getComputedStyle(leading!).whiteSpace).toBe("nowrap");      expect(getComputedStyle(favicon!).verticalAlign).not.toBe("baseline");      expect(link.element().textContent).toBe(url);      expect(link.element().querySelectorAll("wbr").length).toBeGreaterThan(0);      const markdownRoot = link.element().closest<HTMLElement>(".chat-markdown");      expect(markdownRoot).not.toBeNull();      expect(markdownRoot!.scrollWidth).toBeLessThanOrEqual(markdownRoot!.clientWidth);    } finally {      await screen.unmount();    }  });   it("renders file links with the shared file tag chip treatment", async () => {    const screen = await render(      <ChatMarkdown text="[package.json](path/to/package.json)" cwd="/repo/project" />,    );     try {      const link = page.getByRole("link", { name: "package.json" });      await expect.element(link).toHaveClass(/chat-markdown-file-link/);      const element = document.querySelector<HTMLElement>(".chat-markdown-file-link");      expect(element?.querySelector("img, svg")).not.toBeNull();      expect(getComputedStyle(element!).display).toBe("inline-flex");      expect(getComputedStyle(element!).textDecorationLine).toBe("none");      expect(getComputedStyle(element!).borderStyle).toBe("solid");      expect(getComputedStyle(element!).userSelect).not.toBe("none");    } finally {      await screen.unmount();    }  });   it("renders sanitized details with the design-system collapsible", async () => {    const source = [      "<details open>",      "<summary>Expandable details section</summary>",      "",      "This content includes **formatted text**.",      "",      '<span title="native tooltip should be stripped">Safe inline HTML</span>',      "<script>window.__unsafeMarkdownScript = true</script>",      "</details>",    ].join("\n");    const screen = await render(<ChatMarkdown text={source} cwd="/repo/project" />);     try {      const details = document.querySelector<HTMLElement>("[data-markdown-details]");      const trigger = page.getByRole("button", { name: "Expandable details section" });      expect(details).not.toBeNull();      expect(details?.tagName).toBe("DIV");      await expect.element(trigger).toHaveAttribute("aria-expanded", "true");      expect(details?.querySelector("strong")?.textContent).toBe("formatted text");      expect(details?.querySelector("script")).toBeNull();      expect(details?.querySelector("[title]")).toBeNull();       await trigger.click();      await expect.element(trigger).toHaveAttribute("aria-expanded", "false");      await trigger.click();      await expect.element(trigger).toHaveAttribute("aria-expanded", "true");    } finally {      await screen.unmount();    }  });   it("renders footnotes as same-document references", async () => {    const source = [      "A claim with supporting context.[^context]",      "",      "[^context]: Supporting **footnote text**.",    ].join("\n");    const screen = await render(<ChatMarkdown text={source} cwd="/repo/project" />);     try {      const reference = document.querySelector<HTMLAnchorElement>(        '.chat-markdown a[data-footnote-ref=""]',      );      const footnotes = document.querySelector<HTMLElement>(        ".chat-markdown section[data-footnotes]",      );      expect(reference).not.toBeNull();      expect(reference?.getAttribute("href")).toMatch(/^#user-content-fn-/);      expect(reference?.hasAttribute("target")).toBe(false);      expect(footnotes).not.toBeNull();      expect(footnotes?.querySelector("strong")?.textContent).toBe("footnote text");      expect(footnotes?.querySelector<HTMLAnchorElement>("a[data-footnote-backref]")?.target).toBe(        "",      );    } finally {      await screen.unmount();    }  });   it("navigates hash links within the clicked markdown message", async () => {    const source = [      "A claim with supporting context.[^context]",      "",      "[^context]: Supporting footnote text.",    ].join("\n");    const originalUrl = window.location.href;    const scrollIntoView = vi      .spyOn(HTMLElement.prototype, "scrollIntoView")      .mockImplementation(() => undefined);    const screen = await render(      <div>        <ChatMarkdown text={source} cwd="/repo/project" />        <ChatMarkdown text={source} cwd="/repo/project" />      </div>,    );     try {      const markdownRoots = document.querySelectorAll<HTMLElement>(".chat-markdown");      const secondRoot = markdownRoots[1];      const secondReference =        secondRoot?.querySelector<HTMLAnchorElement>('a[data-footnote-ref=""]');      const secondFootnote = secondRoot?.querySelector<HTMLElement>(        "section[data-footnotes] li[id]",      );      expect(secondReference).not.toBeNull();      expect(secondFootnote).not.toBeNull();

Lines 1–500 of 892.