Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 228 additions & 22 deletions src/features/messages/components/Markdown.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @vitest-environment jsdom
import { cleanup, createEvent, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { expectOpenedFileTarget } from "../test/fileLinkAssertions";
import { Markdown } from "./Markdown";

describe("Markdown file-like href behavior", () => {
Expand Down Expand Up @@ -46,7 +47,7 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("./docs/setup.md");
expectOpenedFileTarget(onOpenFileLink, "./docs/setup.md");
});

it("prevents bare relative link navigation without treating it as a file", () => {
Expand Down Expand Up @@ -89,7 +90,7 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspace/src/example.ts");
expectOpenedFileTarget(onOpenFileLink, "/workspace/src/example.ts");
});

it("still intercepts dotless workspace file hrefs when a file opener is provided", () => {
Expand All @@ -112,7 +113,7 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspace/CodexMonitor/LICENSE");
expectOpenedFileTarget(onOpenFileLink, "/workspace/CodexMonitor/LICENSE");
});

it("intercepts mounted workspace links outside the old root allowlist", () => {
Expand All @@ -135,7 +136,7 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspace/.github/workflows");
expectOpenedFileTarget(onOpenFileLink, "/workspace/.github/workflows");
});

it("intercepts mounted workspace directory links that resolve relative to the workspace", () => {
Expand All @@ -158,20 +159,43 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspace/dist/assets");
expectOpenedFileTarget(onOpenFileLink, "/workspace/dist/assets");
});

it("keeps generic workspace routes as normal markdown links", () => {
it("keeps exact workspace routes as normal markdown links", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [overview](/workspace/reviews/overview)"
value="See [reviews](/workspace/reviews)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("reviews").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/reviews");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).not.toHaveBeenCalled();
});

it("keeps nested workspace reviews routes local even when the workspace basename matches", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [overview](/workspace/reviews/overview)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/reviews"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("overview").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/reviews/overview");

Expand Down Expand Up @@ -207,6 +231,29 @@ describe("Markdown file-like href behavior", () => {
expect(onOpenFileLink).not.toHaveBeenCalled();
});

it("keeps nested reviews routes local even when the workspace basename matches the route segment", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [overview](/workspaces/team/reviews/overview)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/reviews"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("overview").closest("a");
expect(link?.getAttribute("href")).toBe("/workspaces/team/reviews/overview");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).not.toHaveBeenCalled();
});

it("still intercepts nested workspace file hrefs when a file opener is provided", () => {
const onOpenFileLink = vi.fn();
render(
Expand All @@ -227,7 +274,30 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspaces/team/CodexMonitor/src");
expectOpenedFileTarget(onOpenFileLink, "/workspaces/team/CodexMonitor/src");
});

it("treats extensionless paths under /workspace/settings as files", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [license](/workspace/settings/LICENSE)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/settings"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("license").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/settings/LICENSE");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expectOpenedFileTarget(onOpenFileLink, "/workspace/settings/LICENSE");
});

it("intercepts file hrefs that use #L line anchors", () => {
Expand All @@ -249,7 +319,52 @@ describe("Markdown file-like href behavior", () => {
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("./docs/setup.md:12");
expectOpenedFileTarget(onOpenFileLink, "./docs/setup.md", 12);
});

it("intercepts Windows absolute file hrefs with #L anchors and preserves the tooltip", () => {
const onOpenFileLink = vi.fn();
const onOpenFileLinkMenu = vi.fn();
const linkedPath =
"I:\\gpt-projects\\CodexMonitor\\src\\features\\settings\\components\\sections\\SettingsDisplaySection.tsx#L422";
render(
<Markdown
value={`See [SettingsDisplaySection.tsx](${linkedPath})`}
className="markdown"
onOpenFileLink={onOpenFileLink}
onOpenFileLinkMenu={onOpenFileLinkMenu}
/>,
);

const link = screen.getByText("SettingsDisplaySection.tsx").closest("a");
expect(link?.getAttribute("href")).toBe(
"I:%5Cgpt-projects%5CCodexMonitor%5Csrc%5Cfeatures%5Csettings%5Ccomponents%5Csections%5CSettingsDisplaySection.tsx#L422",
);
expect(link?.getAttribute("title")).toBe(
"I:\\gpt-projects\\CodexMonitor\\src\\features\\settings\\components\\sections\\SettingsDisplaySection.tsx:422",
);

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expectOpenedFileTarget(
onOpenFileLink,
"I:\\gpt-projects\\CodexMonitor\\src\\features\\settings\\components\\sections\\SettingsDisplaySection.tsx",
422,
);

fireEvent.contextMenu(link as Element);
expect(onOpenFileLinkMenu).toHaveBeenCalledWith(
expect.anything(),
{
path: "I:\\gpt-projects\\CodexMonitor\\src\\features\\settings\\components\\sections\\SettingsDisplaySection.tsx",
line: 422,
column: null,
},
);
});

it("prevents unsupported route fragments without treating them as file links", () => {
Expand All @@ -274,42 +389,133 @@ describe("Markdown file-like href behavior", () => {
expect(onOpenFileLink).not.toHaveBeenCalled();
});

it("does not turn natural-language slash phrases into file links", () => {
it("keeps workspace settings #L anchors as local routes", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [settings](/workspace/settings#L12)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("settings").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/settings#L12");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).not.toHaveBeenCalled();
});

it("keeps workspace reviews #L anchors as local routes", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [reviews](/workspace/reviews#L9)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("reviews").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/reviews#L9");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).not.toHaveBeenCalled();
});

it("does not linkify workspace settings #L anchors in plain text", () => {
const { container } = render(
<Markdown
value="Keep the current app/daemon behavior and the existing Git/Plan experience."
value="See /workspace/settings#L12 for app settings."
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
/>,
);

expect(container.querySelector(".message-file-link")).toBeNull();
expect(container.textContent).toContain("app/daemon");
expect(container.textContent).toContain("Git/Plan");
expect(container.textContent).toContain("/workspace/settings#L12");
});

it("does not turn longer slash phrases into file links", () => {
it("does not linkify Windows file paths embedded in custom URIs", () => {
const { container } = render(
<Markdown
value="This keeps Spec/Verification/Evidence in the note without turning it into a file link."
value="Open vscode://file/C:/repo/src/App.tsx:12 in VS Code."
className="markdown"
/>,
);

expect(container.querySelector(".message-file-link")).toBeNull();
expect(container.textContent).toContain("Spec/Verification/Evidence");
expect(container.textContent).toContain("vscode://file/C:/repo/src/App.tsx:12");
});

it("still turns clear file paths in plain text into file links", () => {
it("does not turn workspace review #L anchors in inline code into file links", () => {
const { container } = render(
<Markdown
value="See docs/setup.md and /Users/example/project/src/index.ts for details."
value="Use `/workspace/reviews#L9` to reference the reviews route."
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
/>,
);

expect(container.querySelector(".message-file-link")).toBeNull();
expect(container.querySelector("code")?.textContent).toBe("/workspace/reviews#L9");
});

it("still opens mounted file links when the workspace basename is settings", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [app](/workspace/settings/src/App.tsx)"
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const fileLinks = [...container.querySelectorAll(".message-file-link")];
expect(fileLinks).toHaveLength(2);
expect(fileLinks[0]?.textContent).toContain("setup.md");
expect(fileLinks[1]?.textContent).toContain("index.ts");
const link = screen.getByText("app").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/settings/src/App.tsx");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expectOpenedFileTarget(onOpenFileLink, "/workspace/settings/src/App.tsx");
});

it("keeps nested settings routes local when the workspace basename is settings", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [profile](/workspace/settings/profile)"
className="markdown"
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/settings"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("profile").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/settings/profile");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).not.toHaveBeenCalled();
});

});
Loading
Loading