// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The sign-out control ends every enabled module's browser context (joint contract ยง12, J1). Sign-out // is a full navigation, so no provider unmounts: without the lifecycle run, the guides progress and // unsent events of the person who signed out stay in the tab's `sessionStorage`. import { afterEach, describe, expect, it, vi } from "vitest" import { composedClientModules, endClientModules } from "../apps/web/src/ui/client-module-list" /** A `Storage` over a Map, as much of it as the modules' handlers use. */ function memoryStorage(seed: Record): Storage { const values = new Map(Object.entries(seed)) const storage = { get length() { return values.size }, key: (index: number) => [...values.keys()][index] ?? null, getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => void values.set(key, value), removeItem: (key: string) => void values.delete(key), clear: () => values.clear(), } // `Object.keys(storage)` lists the stored keys, as it does for the browser's own. return new Proxy(storage, { ownKeys: () => [...values.keys()], getOwnPropertyDescriptor: (_target, key) => typeof key === "string" && values.has(key) ? { enumerable: true, configurable: true, value: values.get(key) } : undefined, }) as unknown as Storage } afterEach(() => { vi.unstubAllGlobals() }) describe("signing out ends the client modules' context", () => { it("clears what each enabled module keeps in the tab and leaves the app's own keys", async () => { const own = "app.preference" const keys = [own] keys.push("conusai.guides.acme.u-7.queue", "conusai.guides.beta.u-7.active") // kit:guides keys.push("conusai.support.session.v1:spk_example") // kit:support const storage = memoryStorage(Object.fromEntries(keys.map((key) => [key, "{}"]))) vi.stubGlobal("window", { sessionStorage: storage }) const report = await endClientModules("sign-out") expect(report.ok).toBe(true) expect(report.outcomes.map((outcome) => outcome.module).sort()).toEqual([...composedClientModules.modules].sort()) expect(Object.keys(storage)).toEqual([own]) }) it("issues a new context instance, so a late answer of the old one is discarded", async () => { vi.stubGlobal("window", { sessionStorage: memoryStorage({}) }) const before = composedClientModules.current() await endClientModules("sign-out") expect(before.signal.aborted).toBe(true) expect(composedClientModules.isCurrent(before.instance)).toBe(false) }) })