import { afterAll, beforeAll, expect, mock, test } from "bun:test"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; import { act } from "react"; import { createRoot } from "react-dom/client"; import type { Session, SessionListResponse } from "@opengeni/sdk"; mock.module("../../app-context", () => ({ useAppContext: () => ({}) })); mock.module("../../lib/navigation", () => ({ useNavigate: () => () => undefined })); const success = mock( (_message: string, _options?: { action?: { onClick: () => void } | undefined }) => undefined, ); const failure = mock(() => undefined); mock.module("sonner", () => ({ toast: { success, error: failure } })); let module: typeof import("./site-conversations"); let ownsDom = false; beforeAll(async () => { ownsDom = !GlobalRegistrator.isRegistered; if (ownsDom) GlobalRegistrator.register({ url: "https://example.test" }); Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); module = await import("./site-conversations"); }); afterAll(async () => { if (ownsDom) await GlobalRegistrator.unregister(); }); const chat = (id: string, owned = true) => ({ id, title: `Chat ${id}`, titleSource: "user", status: "idle", effectiveControl: { state: "running", override: null }, metadata: {}, tenancy: { ownedByCurrentUser: owned }, initialMessage: "", }) as unknown as Session; const page = (sessions: Session[]): SessionListResponse => ({ pinned: [], sessions, nextCursor: null, }); const flush = () => act(async () => { await new Promise((resolve) => setTimeout(resolve, 10)); }); const click = async (element: Element | undefined) => { expect(element).toBeDefined(); await act(async () => { element!.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flush(); }; test("owner can choose an existing chat, then revoke access without changing the chat", async () => { const container = document.createElement("div"); document.body.append(container); const root = createRoot(container); let allowed: string[] = []; const changes: Array<[string, boolean]> = []; const client = { listSessionPage: async ( _ws: string, options: { siteAccessId?: string; includePinned?: boolean }, ) => ({ ...page(options.siteAccessId ? [] : [chat("someone-else", false)]), pinned: options.includePinned === false ? [] : (options.siteAccessId ? allowed : ["mine"]).map((id) => ({ ...chat(id), parentSessionId: "parent", })), }), listSiteSessionAccess: async () => ({ sessionIds: [...allowed] }), setSiteSessionAccess: async ( _ws: string, siteId: string, sessionId: string, allow: boolean, ) => { changes.push([sessionId, allow]); allowed = allow ? [sessionId] : []; return { siteId, sessionId, allowed: allow }; }, }; const button = (text: string) => [...container.querySelectorAll("button")].find((node) => node.textContent === text); try { await act(async () => root.render( , ), ); await flush(); await click(button("Allow an existing chat")); expect(container.textContent).toContain("send messages as you"); expect(container.textContent).toContain("Chat mine"); expect(container.textContent).not.toContain("Chat someone-else"); await click(button("Allow chat")); expect(changes).toEqual([["mine", true]]); expect(button("Allowed")?.disabled).toBe(true); await click(button("Back to Site conversations")); await click(button("Remove access")); expect(changes).toEqual([ ["mine", true], ["mine", false], ]); expect(container.textContent).toContain("No conversations yet"); expect(success).toHaveBeenCalled(); const undo = success.mock.calls.at(-1)?.[1]?.action?.onClick; expect(undo).toBeDefined(); await act(async () => undo!()); await flush(); expect(changes.at(-1)).toEqual(["mine", true]); expect(container.textContent).toContain("Chat mine"); } finally { await act(async () => root.unmount()); container.remove(); } }); test("non-owner UI has no grant control and failed mutations retain the selectable chat", async () => { const container = document.createElement("div"); document.body.append(container); const root = createRoot(container); const client = { listSessionPage: async () => page([chat("mine")]), listSiteSessionAccess: async () => ({ sessionIds: [] }), setSiteSessionAccess: async () => { throw new Error("Connection lost"); }, }; try { await act(async () => root.render( , ), ); await flush(); expect(container.textContent).not.toContain("Allow an existing chat"); await act(async () => root.render( , ), ); await flush(); await click( [...container.querySelectorAll("button")].find( (node) => node.textContent === "Allow an existing chat", ), ); await click( [...container.querySelectorAll("button")].find((node) => node.textContent === "Allow chat"), ); expect(failure).toHaveBeenCalled(); expect(container.textContent).toContain("Chat mine"); } finally { await act(async () => root.unmount()); container.remove(); } });