test(views): fix app-sidebar useWorkspacePaths mock for the new chat nav (MUL-4171)

The AppSidebar personal nav gained a `chat` item, so it calls
`useWorkspacePaths().chat()` at render. The app-sidebar.test.tsx mock hadn't
been updated, so `p.chat` was undefined and every render threw
`TypeError: p[item.key] is not a function`, failing @multica/views#test in CI.

- Add `chat: () => "/acme/chat"` to the mocked useWorkspacePaths.
- Route the chat-sessions query key through a mutable `chatSessions` fixture.
- Add coverage for the Chat nav: renders the link, badges the summed
  unread_count, and hides the badge when all sessions are read — so this drift
  is caught next time.

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Lambda
2026-07-08 18:42:52 +08:00
parent 7796f2d8e5
commit 9ca3e45857

View File

@@ -3,7 +3,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { ApiError } from "@multica/core/api";
import { AppSidebar } from "./app-sidebar";
const { detail, deletePin, navigation, pins, summary, workspaces } = vi.hoisted(() => ({
const { chatSessions, detail, deletePin, navigation, pins, summary, workspaces } = vi.hoisted(() => ({
chatSessions: { current: [] as { unread_count?: number }[] },
detail: { current: { isPending: false, isError: false, data: null as unknown, error: null as unknown } },
deletePin: vi.fn(),
navigation: { current: { pathname: "/acme/issues" } },
@@ -102,6 +103,7 @@ vi.mock("@multica/core/paths", () => ({
useCurrentWorkspace: () => ({ id: "ws-1", name: "Acme", slug: "acme" }),
useWorkspacePaths: () => ({
inbox: () => "/acme/inbox",
chat: () => "/acme/chat",
myIssues: () => "/acme/my-issues",
issues: () => "/acme/issues",
projects: () => "/acme/projects",
@@ -161,6 +163,7 @@ vi.mock("@tanstack/react-query", async (importOriginal) => ({
if (queryKey[0] === "issue") return detail.current;
if (queryKey[0] === "inbox" && queryKey[1] === "unread-summary") return { data: summary.current };
if (queryKey[0] === "workspaces") return { data: workspaces.current };
if (queryKey[0] === "chat" && queryKey[2] === "sessions") return { data: chatSessions.current };
return { data: [] };
},
useQueryClient: () => ({ fetchQuery: vi.fn(), invalidateQueries: vi.fn() }),
@@ -279,3 +282,32 @@ describe("workspace-switcher dropdown per-workspace dot", () => {
expect(rowDots(container)).toHaveLength(0);
});
});
describe("personal nav — Chat", () => {
beforeEach(() => {
chatSessions.current = [];
navigation.current = { pathname: "/acme/issues" };
});
// The mocked SidebarMenuButton exposes the AppLink target as `data-href`
// and renders the label + badge as its children.
const chatNav = (container: HTMLElement) =>
container.querySelector<HTMLElement>('button[data-href="/acme/chat"]');
it("renders a Chat nav link to the workspace chat route", () => {
const { container } = render(<AppSidebar />);
expect(chatNav(container)).not.toBeNull();
});
it("badges the Chat nav with the summed unread_count of chat sessions", () => {
chatSessions.current = [{ unread_count: 3 }, { unread_count: 2 }, { unread_count: 0 }];
const { container } = render(<AppSidebar />);
expect(chatNav(container)?.textContent).toContain("5");
});
it("shows no Chat unread badge when every session is read", () => {
chatSessions.current = [{ unread_count: 0 }, {}];
const { container } = render(<AppSidebar />);
expect(chatNav(container)?.textContent ?? "").not.toMatch(/\d/);
});
});