// @vitest-environment jsdom /** * useWorkspace — the [workspace] route's workspace resolver. * * Regression (domain deploy, post-login): the magic alias `default` is a * routing placeholder (the middleware + post-login redirect target), NOT a * workspace id/slug. The hook used to fire `GET /workspaces/default` → a * guaranteed 404 (no workspace has that id/slug) → the [workspace] layout's * error gate (`isError` → `return null`) blanked the whole shell (content + * menu) before the index page could resolve the alias, and no page layout * ever mounted to open the realtime socket (status chip stuck "Offline"). * * The alias must NOT be fetched here — the [workspace] index page owns * resolving it (list → first workspace → redirect). A disabled query * reports neither data nor error, so `isError` stays false. */ // React 18 act() needs the global flag in a non-React-test-library env. (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; 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"; vi.mock("@/lib/api", () => ({ workspaceApi: { get: vi.fn(async (idOrSlug: string) => ({ id: idOrSlug, name: `WS ${idOrSlug}` })), }, })); import { workspaceApi } from "@/lib/api"; import { useWorkspace } from "../use-workspace"; import type { UseQueryResult } from "@tanstack/react-query"; import type { Workspace } from "@/lib/types"; const getMock = vi.mocked(workspaceApi.get); let container: HTMLDivElement; let root: Root; let client: QueryClient; let last: UseQueryResult | undefined; function renderHook(slugOrId: string) { function Probe() { last = useWorkspace(slugOrId); return null; } root.render( ); } async function flush(ms = 50) { await act(async () => { await new Promise((r) => setTimeout(r, ms)); }); } /** Poll until `predicate(last)` holds (or the budget runs out). */ async function until(predicate: () => boolean, budgetMs = 2000) { const started = Date.now(); while (!predicate()) { if (Date.now() - started > budgetMs) return; await flush(10); } } beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); root = createRoot(container); client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); // restoreMocks + mockReset wipes the implementation, so re-arm it. getMock.mockReset(); getMock.mockImplementation(async (idOrSlug: string) => ({ id: idOrSlug, name: `WS ${idOrSlug}`, slug: idOrSlug, plan: "free", role: "owner", memberCount: 1, createdAt: "2026-01-01T00:00:00Z" })); last = undefined; }); afterEach(() => { act(() => root.unmount()); container.remove(); }); describe("useWorkspace", () => { it("does NOT fetch the `default` magic alias (the guaranteed-404 regression)", async () => { renderHook("default"); await flush(); expect(getMock).not.toHaveBeenCalled(); // No error, no data — the layout's error gate must stay closed while // the index page resolves the alias. expect(last).toMatchObject({ isError: false, data: undefined, isLoading: false }); }); it("fetches a real slug (or id) as before", async () => { renderHook("demo"); await until(() => last?.data !== undefined); expect(getMock).toHaveBeenCalledWith("demo"); expect(last?.data).toMatchObject({ id: "demo" }); expect(last?.isError).toBe(false); }); it("does NOT fetch the empty route param (pre-existing guard preserved)", async () => { renderHook(""); await flush(); expect(getMock).not.toHaveBeenCalled(); expect(last?.isError).toBe(false); }); });