Skip to content
Open
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
328 changes: 324 additions & 4 deletions src/features/messages/components/Markdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,19 @@ describe("Markdown file-like href behavior", () => {
expect(onOpenFileLink).toHaveBeenCalledWith("/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("overview").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/reviews/overview");
const link = screen.getByText("reviews").closest("a");
expect(link?.getAttribute("href")).toBe("/workspace/reviews");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
Expand Down Expand Up @@ -252,6 +252,45 @@ describe("Markdown file-like href behavior", () => {
expect(onOpenFileLink).toHaveBeenCalledWith("./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);
expect(onOpenFileLink).toHaveBeenCalledWith(
"I:\\gpt-projects\\CodexMonitor\\src\\features\\settings\\components\\sections\\SettingsDisplaySection.tsx:422",
);

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

it("prevents unsupported route fragments without treating them as file links", () => {
const onOpenFileLink = vi.fn();
render(
Expand All @@ -274,6 +313,78 @@ describe("Markdown file-like href behavior", () => {
expect(onOpenFileLink).not.toHaveBeenCalled();
});

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="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("/workspace/settings#L12");
});

it("does not turn workspace review #L anchors in inline code into file links", () => {
const { container } = render(
<Markdown
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("does not turn natural-language slash phrases into file links", () => {
const { container } = render(
<Markdown
Expand Down Expand Up @@ -312,4 +423,213 @@ describe("Markdown file-like href behavior", () => {
expect(fileLinks[0]?.textContent).toContain("setup.md");
expect(fileLinks[1]?.textContent).toContain("index.ts");
});

it("turns Windows absolute paths in plain text into file links", () => {
const { container } = render(
<Markdown
value="Open I:\\gpt-projects\\CodexMonitor\\src\\App.tsx:12 for details."
className="markdown"
/>,
);

const fileLinks = [...container.querySelectorAll(".message-file-link")];
expect(fileLinks).toHaveLength(1);
expect(fileLinks[0]?.textContent).toContain("App.tsx");
expect(fileLinks[0]?.getAttribute("title")).toBe(
"I:\\gpt-projects\\CodexMonitor\\src\\App.tsx:12",
);
});

it("normalizes plain-text Windows #L anchors before opening file links", () => {
const onOpenFileLink = vi.fn();
const { container } = render(
<Markdown
value="Open I:\\gpt-projects\\CodexMonitor\\src\\App.tsx#L12 for details."
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const fileLinks = [...container.querySelectorAll(".message-file-link")];
expect(fileLinks).toHaveLength(1);
expect(fileLinks[0]?.getAttribute("title")).toBe(
"I:\\gpt-projects\\CodexMonitor\\src\\App.tsx:12",
);

const clickEvent = createEvent.click(fileLinks[0] as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(fileLinks[0] as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith(
"I:\\gpt-projects\\CodexMonitor\\src\\App.tsx:12",
);
});

it("does not linkify Windows paths embedded inside file URLs", () => {
const { container } = render(
<Markdown
value="Download file:///C:/repo/src/App.tsx instead of opening a local file link."
className="markdown"
/>,
);

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

it("ignores non-line file URL fragments when opening file hrefs", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [report](file:///tmp/report.md#overview)"
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("report").closest("a");
expect(link?.getAttribute("href")).toBe("file:///tmp/report.md#overview");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/tmp/report.md");
});

it("keeps line anchors when opening file URLs", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [report](file:///tmp/report.md#L12)"
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("report").closest("a");
expect(link?.getAttribute("href")).toBe("file:///tmp/report.md#L12");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/tmp/report.md:12");
});

it("preserves Windows drive paths when file URL decoding encounters an unescaped percent", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [report](file:///C:/repo/100%.tsx#L12)"
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("report").closest("a");
expect(link?.getAttribute("href")).toBe("file:///C:/repo/100%25.tsx#L12");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("C:/repo/100%.tsx:12");
});

it("preserves UNC host paths when file URL decoding encounters an unescaped percent", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [report](file://server/share/100%.tsx#L12)"
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("report").closest("a");
expect(link?.getAttribute("href")).toBe("file://server/share/100%25.tsx#L12");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("//server/share/100%.tsx:12");
});

it("keeps encoded #L-like filenames intact when opening file URLs", () => {
const onOpenFileLink = vi.fn();
render(
<Markdown
value="See [report](file:///tmp/report%23L12.md)"
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = screen.getByText("report").closest("a");
expect(link?.getAttribute("href")).toBe("file:///tmp/report%23L12.md");

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/tmp/report#L12.md");
});

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 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);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspace/settings/src/App.tsx");
});

it("linkifies mounted file paths when the nested workspace basename is reviews", () => {
const onOpenFileLink = vi.fn();
const { container } = render(
<Markdown
value="See /workspaces/team/reviews/src/App.tsx for details."
className="markdown"
onOpenFileLink={onOpenFileLink}
/>,
);

const link = container.querySelector('a[href^="codex-file:"]');
expect(link).not.toBeNull();

const clickEvent = createEvent.click(link as Element, {
bubbles: true,
cancelable: true,
});
fireEvent(link as Element, clickEvent);
expect(clickEvent.defaultPrevented).toBe(true);
expect(onOpenFileLink).toHaveBeenCalledWith("/workspaces/team/reviews/src/App.tsx");
});
});
Loading
Loading