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("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", }); 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(); secondReference?.click(); expect(scrollIntoView).toHaveBeenCalledTimes(1); expect(scrollIntoView.mock.instances[0]).toBe(secondFootnote); expect(window.location.hash).toBe(secondReference?.hash); const secondBackref = secondRoot?.querySelector<HTMLAnchorElement>( "a[data-footnote-backref]", ); expect(secondBackref).not.toBeNull(); secondBackref?.click(); const secondReferenceTarget = secondReference?.closest<HTMLElement>("[id]"); expect(scrollIntoView).toHaveBeenCalledTimes(2); expect(scrollIntoView.mock.instances[1]).toBe(secondReferenceTarget); } finally { scrollIntoView.mockRestore(); window.history.replaceState(window.history.state, "", originalUrl); await screen.unmount(); } }); describe("code block chrome", () => { it("shows icon-only language titles, text fallbacks, and filename overrides", async () => { const source = [ "```ts",Lines 1–500 of 865. Load more
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("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", }); 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(); secondReference?.click(); expect(scrollIntoView).toHaveBeenCalledTimes(1); expect(scrollIntoView.mock.instances[0]).toBe(secondFootnote); expect(window.location.hash).toBe(secondReference?.hash); const secondBackref = secondRoot?.querySelector<HTMLAnchorElement>( "a[data-footnote-backref]", ); expect(secondBackref).not.toBeNull(); secondBackref?.click(); const secondReferenceTarget = secondReference?.closest<HTMLElement>("[id]"); expect(scrollIntoView).toHaveBeenCalledTimes(2); expect(scrollIntoView.mock.instances[1]).toBe(secondReferenceTarget); } finally { scrollIntoView.mockRestore(); window.history.replaceState(window.history.state, "", originalUrl); await screen.unmount(); } }); describe("code block chrome", () => { it("shows icon-only language titles, text fallbacks, and filename overrides", async () => { const source = [ "```ts",