1// @vitest-environment jsdom
2
3import { act, createRef, useState } from "react";
4import { createRoot, type Root } from "react-dom/client";
5import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
6
7import {
8 AlertDialog,
9 AlertDialogClose,
10 AlertDialogFooter,
11 AlertDialogPopup,
12 AlertDialogTitle,
13} from "./alert-dialog";
14import { Button } from "./button";
15
16let root: Root;
17let container: HTMLDivElement;
18const cancel = createRef<HTMLButtonElement>();
19
20beforeEach(() => {
21 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
22 container = document.createElement("div");
23 document.body.append(container);
24 root = createRoot(container);
25});
26
27afterEach(async () => {
28 await act(async () => root.unmount());
29 container.remove();
30 vi.unstubAllGlobals();
31});
32
33function Confirmation({ disabled = false, explicitFocus = false }) {
34 const [open, setOpen] = useState(false);
35 return (
36 <>
37 <button onClick={() => setOpen(true)}>Open danger dialog</button>
38 <AlertDialog open={open} onOpenChange={setOpen}>
39 <AlertDialogPopup {...(explicitFocus ? { initialFocus: cancel } : {})}>
40 <AlertDialogTitle>Remove fixture?</AlertDialogTitle>
41 <AlertDialogFooter>
42 <AlertDialogClose ref={cancel} render={<Button variant="outline" />}>
43 Cancel
44 </AlertDialogClose>
45 <Button variant="destructive" disabled={disabled} onClick={() => setOpen(false)}>
46 Remove
47 </Button>
48 </AlertDialogFooter>
49 </AlertDialogPopup>
50 </AlertDialog>
51 </>
52 );
53}
54
55async function open() {
56 const trigger = container.querySelector("button")!;
57 await act(async () => {
58 trigger.focus();
59 trigger.click();
60 });
61}
62
63it("focuses confirmation on every opening and lets Escape cancel", async () => {
64 await act(async () => root.render(<Confirmation />));
65 await open();
66 await vi.waitFor(() => expect(document.activeElement?.textContent).toBe("Remove"));
67 await act(async () => {
68 document.activeElement?.dispatchEvent(
69 new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
70 );
71 });
72 await vi.waitFor(() => expect(document.querySelector('[role="alertdialog"]')).toBeNull());
73 expect(document.activeElement?.textContent).toBe("Open danger dialog");
74 await open();
75 await vi.waitFor(() => expect(document.activeElement?.textContent).toBe("Remove"));
76});
77
78it("keeps disabled confirmation out of the default keyboard action", async () => {
79 await act(async () => root.render(<Confirmation disabled />));
80 await open();
81 await vi.waitFor(() => expect(document.activeElement?.textContent).toBe("Cancel"));
82});
83
84it("preserves an explicit initial focus target", async () => {
85 await act(async () => root.render(<Confirmation explicitFocus />));
86 await open();
87 await vi.waitFor(() => expect(document.activeElement?.textContent).toBe("Cancel"));
88});