import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { DestructiveConfirmPanel, confirmTextMatches } from "./destructive-confirm"; /** Sets a value the way a keystroke would and runs React's change handler. */ async function typeInto(input: HTMLInputElement, value: string) { await act(async () => { Object.getOwnPropertyDescriptor(Object.getPrototypeOf(input), "value")?.set?.call(input, value); const key = Object.keys(input).find((each) => each.startsWith("__reactProps$")); const props = key ? ( input as unknown as Record< string, { onChange?: ((event: { target: HTMLInputElement }) => void) | undefined } > )[key] : undefined; props?.onChange?.({ target: input }); await Promise.resolve(); }); } describe("confirmTextMatches", () => { test("matches the exact name, ignoring surrounding spaces", () => { expect(confirmTextMatches("Design preview", "Design preview")).toBe(true); expect(confirmTextMatches(" Design preview ", "Design preview")).toBe(true); expect(confirmTextMatches("design preview", "Design preview")).toBe(false); expect(confirmTextMatches("Design prev", "Design preview")).toBe(false); expect(confirmTextMatches("", "")).toBe(false); }); }); describe("DestructiveConfirmPanel", () => { let container: HTMLDivElement; let root: Root; beforeAll(() => { GlobalRegistrator.register(); ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; }); beforeEach(() => { container = document.createElement("div"); document.body.append(container); root = createRoot(container); }); afterEach(async () => { await act(async () => root.unmount()); container.remove(); }); afterAll(() => GlobalRegistrator.unregister()); const button = (label: string) => [...container.querySelectorAll("button")].find((each) => each.textContent === label); test("type to confirm starts empty and enables the primary only on an exact match", async () => { await act(async () => root.render( , ), ); const input = container.querySelector("input")!; expect(input.value).toBe(""); expect(input.placeholder).toBe("Workspace name"); expect(button("Delete workspace")!.disabled).toBe(true); await typeInto(input, "Design previe"); expect(button("Delete workspace")!.disabled).toBe(true); await typeInto(input, "Design preview"); expect(button("Delete workspace")!.disabled).toBe(false); }); test("blocked lists what uses it with links and offers no destructive button", async () => { const close = mock(); await act(async () => root.render( , ), ); expect(button("Delete variable set")).toBeUndefined(); expect([...container.querySelectorAll("button")].map((each) => each.textContent)).toEqual([ "Close", ]); const link = container.querySelector("a")!; expect(link.getAttribute("href")).toBe("/schedules?schedule=sched-aws-cost"); expect(link.textContent).toContain("Check AWS cost anomalies"); expect(link.textContent).toContain("Schedule · Every weekday at 08:00 · Oslo"); await act(async () => button("Close")!.click()); expect(close).toHaveBeenCalledTimes(1); }); test("a dependency link opens in the app when the caller routes it", async () => { const open = mock(); await act(async () => root.render( , ), ); const link = container.querySelector("a")!; const click = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 }); await act(async () => link.dispatchEvent(click)); expect(click.defaultPrevented).toBe(true); expect(open).toHaveBeenCalledTimes(1); expect(open.mock.calls[0]?.[0]).toMatchObject({ href: "/schedules?schedule=sched-aws-cost" }); // A modified click keeps the browser default (new tab). const newTab = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0, metaKey: true, }); await act(async () => link.dispatchEvent(newTab)); expect(newTab.defaultPrevented).toBe(false); expect(open).toHaveBeenCalledTimes(1); }); test("consequences render as a list and the primary is destructive", async () => { await act(async () => root.render( , ), ); expect([...container.querySelectorAll("li")].map((item) => item.textContent)).toEqual([ "Its 3 variables are deleted.", "This can't be undone.", ]); expect(button("Delete variable set")!.getAttribute("data-variant")).toBe("destructive"); expect(button("Cancel")!.hasAttribute("data-autofocus")).toBe(true); }); });