1import { beforeEach, describe, expect, it } from "vite-plus/test";
2
3import {
4 completeConfirmDialogClose,
5 isConfirmDialogActive,
6 readConfirmDialogState,
7 registerConfirmDialogHost,
8 requestConfirmDialog,
9 resetConfirmDialogForTests,
10 respondToConfirmDialog,
11} from "./confirmDialog";
12
13function requireConfirmation(confirmation: Promise<boolean> | undefined): Promise<boolean> {
14 if (!confirmation) {
15 throw new Error("Expected a registered confirmation host.");
16 }
17 return confirmation;
18}
19
20describe("confirm dialog coordinator", () => {
21 beforeEach(() => {
22 resetConfirmDialogForTests();
23 });
24
25 it("returns undefined until a themed host is mounted", () => {
26 expect(requestConfirmDialog("Confirm this action?")).toBeUndefined();
27 expect(readConfirmDialogState()).toEqual({ status: "idle" });
28 });
29
30 it("resolves a displayed confirmation and waits for its close transition", async () => {
31 const unregister = registerConfirmDialogHost();
32 const confirmation = requireConfirmation(
33 requestConfirmDialog("Delete this thread?", { variant: "destructive" }),
34 );
35
36 expect(readConfirmDialogState()).toEqual({
37 status: "confirming",
38 message: "Delete this thread?",
39 variant: "destructive",
40 });
41
42 respondToConfirmDialog(true);
43 await expect(confirmation).resolves.toBe(true);
44 // Still on screen while it closes, so nothing may cover it yet.
45 expect(isConfirmDialogActive()).toBe(true);
46 expect(readConfirmDialogState()).toEqual({
47 status: "closing",
48 message: "Delete this thread?",
49 variant: "destructive",
50 });
51
52 completeConfirmDialogClose();
53 expect(readConfirmDialogState()).toEqual({ status: "idle" });
54 expect(isConfirmDialogActive()).toBe(false);
55 unregister();
56 });
57
58 it("serializes concurrent confirmations", async () => {
59 const unregister = registerConfirmDialogHost();
60 const first = requireConfirmation(requestConfirmDialog("Delete the project?"));
61 const second = requireConfirmation(requestConfirmDialog("Delete the worktree too?"));
62
63 respondToConfirmDialog(false);
64 await expect(first).resolves.toBe(false);
65 expect(readConfirmDialogState()).toEqual({
66 status: "closing",
67 message: "Delete the project?",
68 variant: "default",
69 });
70
71 completeConfirmDialogClose();
72 expect(readConfirmDialogState()).toEqual({
73 status: "confirming",
74 message: "Delete the worktree too?",
75 variant: "default",
76 });
77
78 respondToConfirmDialog(true);
79 await expect(second).resolves.toBe(true);
80 completeConfirmDialogClose();
81 expect(readConfirmDialogState()).toEqual({ status: "idle" });
82 unregister();
83 });
84
85 it("cancels active and queued confirmations if the last host unmounts", async () => {
86 const unregister = registerConfirmDialogHost();
87 const active = requireConfirmation(requestConfirmDialog("Delete the thread?"));
88 const queued = requireConfirmation(requestConfirmDialog("Delete the worktree too?"));
89
90 unregister();
91
92 await expect(Promise.all([active, queued])).resolves.toEqual([false, false]);
93 expect(readConfirmDialogState()).toEqual({ status: "idle" });
94 });
95
96 it("ignores responses after the active dialog has been closed", () => {
97 const unregister = registerConfirmDialogHost();
98 const confirmation = requireConfirmation(requestConfirmDialog("Continue?"));
99
100 respondToConfirmDialog(true);
101 respondToConfirmDialog(false);
102 completeConfirmDialogClose();
103
104 expect(readConfirmDialogState()).toEqual({ status: "idle" });
105 unregister();
106 return expect(confirmation).resolves.toBe(true);
107 });
108});