// @vitest-environment jsdom
import type { ReactNode } from "react";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ISSUE_HISTORY_HOVER_PREFETCH_DELAY_MS } from "@/hooks/useIssueNavigationPrefetch";
import { RecentTaskNavItem } from "./RecentTaskNavItem";
const prefetches = vi.hoisted(() => [] as Array<{ ref: string; history: boolean }>);
const setSidebarOpen = vi.hoisted(() => vi.fn());
vi.mock("@/lib/issueDetailCache", () => ({
prefetchIssueDetailForNavigation: (_client: unknown, ref: string, options?: { history?: boolean }) => {
prefetches.push({ ref, history: options?.history === true });
return Promise.resolve();
},
}));
vi.mock("@/lib/router", () => ({
NavLink: ({ children, to, className, end: _end, state: _state, ...props }: {
children: ReactNode; to: string; end?: boolean; state?: unknown;
className?: string | ((state: { isActive: boolean }) => string);
}) => (
{children}
),
}));
vi.mock("@/context/SidebarContext", () => ({
useSidebar: () => ({ collapsed: false, peeking: false, isMobile: true, setSidebarOpen }),
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
let container: HTMLDivElement;
let root: Root;
const queryClient = new QueryClient();
function renderItem(task: { id: string; identifier: string | null }, to?: string) {
prefetches.length = 0;
act(() => {
root.render();
});
return container.querySelector("a") as HTMLAnchorElement;
}
const hover = (link: HTMLAnchorElement, type: "mouseover" | "mouseout") =>
act(() => { link.dispatchEvent(new MouseEvent(type, { bubbles: true, relatedTarget: document.body })); });
const press = (link: HTMLAnchorElement, pointerType: string) =>
act(() => { link.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, button: 0, pointerType })); });
beforeEach(() => {
vi.useFakeTimers();
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
vi.useRealTimers();
setSidebarOpen.mockClear();
});
describe("RecentTaskNavItem", () => {
it("opens and warms the identifier route, falling back to the id", () => {
expect(renderItem({ id: "id-1", identifier: "PAP-1" }).getAttribute("href")).toBe("/issues/PAP-1");
expect(renderItem({ id: "id-2", identifier: null }).getAttribute("href")).toBe("/issues/id-2");
expect(renderItem({ id: "id-1", identifier: "PAP-1" }, "/PAP/issues/PAP-1").getAttribute("href")).toBe("/PAP/issues/PAP-1");
});
it("warms only the light set when a pointer passes over, and the chat's first frame when it rests", () => {
const link = renderItem({ id: "id-1", identifier: "PAP-1" });
hover(link, "mouseover");
act(() => { vi.advanceTimersByTime(ISSUE_HISTORY_HOVER_PREFETCH_DELAY_MS - 1); });
hover(link, "mouseout");
act(() => { vi.advanceTimersByTime(1_000); });
expect(prefetches).toEqual([{ ref: "PAP-1", history: false }]);
hover(link, "mouseover");
act(() => { vi.advanceTimersByTime(ISSUE_HISTORY_HOVER_PREFETCH_DELAY_MS); });
expect(prefetches.at(-1)).toEqual({ ref: "PAP-1", history: true });
});
it("warms the first frame on a mouse press, keyboard focus that rests, and a click", () => {
const link = renderItem({ id: "id-1", identifier: "PAP-1" });
press(link, "mouse");
expect(prefetches).toEqual([{ ref: "PAP-1", history: true }]);
prefetches.length = 0;
act(() => { link.focus(); });
act(() => { vi.advanceTimersByTime(ISSUE_HISTORY_HOVER_PREFETCH_DELAY_MS); });
expect(prefetches).toEqual([{ ref: "PAP-1", history: false }, { ref: "PAP-1", history: true }]);
prefetches.length = 0;
act(() => { link.click(); });
expect(prefetches).toEqual([{ ref: "PAP-1", history: true }]);
// The sidebar's own click handling still runs.
expect(setSidebarOpen).toHaveBeenCalledWith(false);
});
it("keeps a touch that may start a scroll to the light set", () => {
const link = renderItem({ id: "id-1", identifier: "PAP-1" });
press(link, "touch");
act(() => { link.dispatchEvent(new TouchEvent("touchstart", { bubbles: true })); });
act(() => { vi.advanceTimersByTime(1_000); });
expect(prefetches.length).toBeGreaterThan(0);
expect(prefetches.every((prefetch) => !prefetch.history)).toBe(true);
});
});