// @vitest-environment jsdom import * as React from "react" import { act, cleanup, fireEvent, render, screen, waitFor, } from "@testing-library/react" import { I18nProvider } from "@workspace/i18n" import { createOverlayScope, globalOverlayScope, OverlayScopeProvider, useOverlayScope, type OverlayScope, } from "@workspace/overlays" import { AlertHost, openAlert } from "@workspace/overlays/alert" import { OverlayHost } from "@workspace/overlays/all" import { openConfirm } from "@workspace/overlays/confirm" import { openDrawer } from "@workspace/overlays/drawer" import { openModal } from "@workspace/overlays/modal" import { OverlayNavigationGuard, type OverlayNavigationAdapter, type OverlayNavigationAttempt, } from "@workspace/overlays/navigation" import { openPrompt } from "@workspace/overlays/prompt" import { openSheet } from "@workspace/overlays/sheet" import { openToast } from "@workspace/overlays/toast" import { Button } from "@workspace/ui/components/button" import { afterEach, describe, expect, it, vi } from "vitest" import { messages as englishMessages } from "./locales/en-US" class ResizeObserverStub { disconnect() {} observe() {} unobserve() {} } vi.stubGlobal("ResizeObserver", ResizeObserverStub) Object.defineProperty(Element.prototype, "getAnimations", { configurable: true, value: () => [], }) afterEach(cleanup) function renderHosts(scope = createOverlayScope()) { const result = render( ) return { scope, ...result } } describe("atomic overlays", () => { it("uses the global scope when scope is omitted", async () => { render( ) let result: Promise | undefined act(() => { result = openAlert({ title: "Global alert" }) }) expect(globalOverlayScope.getTopmostLayer()).toBeTruthy() fireEvent.click(screen.getByRole("button", { name: "OK" })) await expect(result).resolves.toBeUndefined() }) it("resolves alert, confirm, and prompt results", async () => { const { scope } = renderHosts() let alertResult: Promise | undefined act(() => { alertResult = openAlert({ scope, title: "Export complete" }) }) fireEvent.click(screen.getByRole("button", { name: "OK" })) await expect(alertResult).resolves.toBeUndefined() let confirmation: Promise | undefined act(() => { confirmation = openConfirm({ scope, title: "Publish changes?" }) }) fireEvent.click(screen.getByRole("button", { name: "Confirm" })) await expect(confirmation).resolves.toBe(true) await waitFor(() => expect(screen.queryByText("Publish changes?")).toBeNull() ) let promptResult: Promise | undefined act(() => { promptResult = openPrompt({ scope, title: "Document name" }) }) fireEvent.change(screen.getByLabelText("Value"), { target: { value: "Release notes" }, }) fireEvent.click(screen.getByRole("button", { name: "Confirm" })) await expect(promptResult).resolves.toBe("Release notes") }) it("returns typed custom content results", async () => { const { scope } = renderHosts() let modalResult: Promise | undefined act(() => { modalResult = openModal({ content: ({ close }) => ( ), scope, title: "Choose a value", }) }) fireEvent.click(screen.getByRole("button", { name: "Choose 42" })) await expect(modalResult).resolves.toBe(42) await waitFor(() => expect(screen.queryByText("Choose a value")).toBeNull()) act(() => { void openSheet({ content:

Account details

, scope, title: "Account", }) void openDrawer({ content:

Filters

, scope, title: "Filter products", }) }) expect(screen.getByText("Account details")).toBeTruthy() expect(screen.getByText("Filter products")).toBeTruthy() }) it("renders scoped toasts", async () => { const { scope } = renderHosts() act(() => { openToast({ description: "Your changes were saved.", scope, title: "Saved", type: "success", }) }) expect(await screen.findByText("Your changes were saved.")).toBeTruthy() }) it("provides current and named local scopes", () => { const editor = createOverlayScope({ name: "editor" }) const picker = createOverlayScope({ name: "picker" }) let current: OverlayScope | undefined let named: OverlayScope | undefined function CaptureScopes() { current = useOverlayScope() named = useOverlayScope("picker") return null } render( ) expect(current).toBe(editor) expect(named).toBe(picker) }) it("queues requests for the same atomic host", async () => { const { scope } = renderHosts() let first: Promise | undefined let second: Promise | undefined act(() => { first = openConfirm({ scope, title: "First confirmation" }) second = openConfirm({ scope, title: "Second confirmation" }) }) expect(screen.getByText("First confirmation")).toBeTruthy() expect(screen.queryByText("Second confirmation")).toBeNull() fireEvent.click(screen.getByRole("button", { name: "Confirm" })) await expect(first).resolves.toBe(true) expect(await screen.findByText("Second confirmation")).toBeTruthy() fireEvent.click(screen.getByRole("button", { name: "Cancel" })) await expect(second).resolves.toBe(false) }) it("settles pending requests when their host unmounts", async () => { const { scope, unmount } = renderHosts() let result: Promise | undefined act(() => { result = openConfirm({ scope, title: "Pending action" }) }) unmount() await expect(result).resolves.toBe(false) }) it("intercepts navigation through a router-neutral adapter", async () => { const scope = createOverlayScope() let listener: ((attempt: OverlayNavigationAttempt) => void) | undefined const adapter: OverlayNavigationAdapter = { subscribe(nextListener) { listener = nextListener return () => { listener = undefined } }, } const cancel = vi.fn() const proceed = vi.fn() render( ) let result: Promise | undefined act(() => { result = openConfirm({ scope, title: "Discard changes?" }) }) act(() => listener?.({ cancel, proceed })) expect(cancel).toHaveBeenCalledOnce() expect(proceed).not.toHaveBeenCalled() await expect(result).resolves.toBe(false) await waitFor(() => expect(scope.getTopmostLayer()).toBeUndefined()) act(() => listener?.({ cancel, proceed })) expect(proceed).toHaveBeenCalledOnce() }) it("keeps non-dismissible content open while blocking navigation", () => { const scope = createOverlayScope() let listener: ((attempt: OverlayNavigationAttempt) => void) | undefined const adapter: OverlayNavigationAdapter = { subscribe(nextListener) { listener = nextListener return () => { listener = undefined } }, } const cancel = vi.fn() render( ) act(() => { void openModal({ content: ({ close }) => ( ), dismissible: false, scope, title: "Required step", }) }) act(() => listener?.({ cancel, proceed: vi.fn() })) expect(cancel).toHaveBeenCalledOnce() expect(screen.getByText("Required step")).toBeTruthy() }) })