1import { DEFAULT_CLIENT_SETTINGS, type DesktopSnapShotState } from "@t3tools/contracts";
2import { expect, it } from "vite-plus/test";
3import {
4 captureSetupAccessReady,
5 captureSetupBackend,
6 captureSetupCheckMessage,
7 captureSetupDesktopName,
8 captureSetupInitialStep,
9 captureSetupMacPermissionsReady,
10 captureSetupShortcutReady,
11 captureSetupShouldDisableOnClose,
12} from "./SnapShotSetupDialog.logic";
13
14const gnome: DesktopSnapShotState = {
15 mode: "portal",
16 linuxBackend: "gnome-extension",
17 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
18 shortcutRegistered: true,
19 shortcutMessage: "Requested",
20 shortcutVerified: false,
21 message: null,
22 gnomeExtension: { status: "enabled", message: "Running" },
23};
24
25it("derives Hyprland setup from its own helper and registered action, not old GNOME approval", () => {
26 const state: DesktopSnapShotState = {
27 ...gnome,
28 linuxBackend: "hyprland",
29 linuxDesktop: "hyprland",
30 shortcutRegistered: false,
31 hyprlandHelper: { status: "not-installed", message: "Install" },
32 };
33 expect(captureSetupBackend(state)).toBe("hyprland");
34 expect(captureSetupDesktopName(state)).toBe("Hyprland");
35 expect(captureSetupAccessReady(state)).toBe(false);
36 expect(captureSetupInitialStep(state)).toBe("access");
37 const installed = { ...state, hyprlandHelper: { status: "ready" as const, message: "Ready" } };
38 expect(captureSetupInitialStep(installed)).toBe("shortcut");
39 expect(captureSetupShortcutReady(installed, false)).toBe(false);
40 expect(captureSetupShortcutReady({ ...installed, shortcutPending: true }, false)).toBe(false);
41 expect(captureSetupShortcutReady({ ...installed, shortcutActionRegistered: true }, false)).toBe(
42 true,
43 );
44 expect(captureSetupAccessReady({ ...installed, message: "Check access" })).toBe(false);
45});
46
47it.each([
48 "not-installed",
49 "disabled",
50 "restart-required",
51 "update-required",
52 "extensions-disabled",
53 "error",
54] as const)(
55 "keeps %s GNOME setup on the access step, even with a previously verified shortcut",
56 (status) => {
57 const state = {
58 ...gnome,
59 shortcutVerified: true,
60 gnomeExtension: { status, message: "Needs setup" },
61 };
62 expect(captureSetupInitialStep(state)).toBe("access");
63 expect(captureSetupInitialStep(state, "shortcut")).toBe("access");
64 expect(captureSetupAccessReady(state)).toBe(false);
65 expect(captureSetupShortcutReady(state, false)).toBe(false);
66 },
67);
68
69it("does not declare access ready before the enabled extension's capture endpoint is available", () => {
70 expect(captureSetupAccessReady({ ...gnome, linuxBackend: "picker" })).toBe(false);
71 expect(captureSetupAccessReady(gnome)).toBe(true);
72});
73
74it("resumes after login from real extension state without an onboarding-completed flag", () => {
75 expect(
76 captureSetupInitialStep({
77 ...gnome,
78 gnomeExtension: { status: "disabled", message: "Enable it" },
79 }),
80 ).toBe("access");
81 expect(captureSetupInitialStep({ ...gnome, shortcutRegistered: false })).toBe("shortcut");
82 expect(captureSetupInitialStep(gnome)).toBe("shortcut");
83});
84
85it("finishes setup with a saved shortcut without requiring a separate delivery test", () => {
86 expect(captureSetupInitialStep({ ...gnome, shortcutVerified: false })).toBe("shortcut");
87 expect(captureSetupShortcutReady(gnome, false)).toBe(true);
88});
89
90it("still allows revisiting capture access and editing a saved shortcut", () => {
91 expect(captureSetupInitialStep(gnome, "access")).toBe("access");
92 expect(captureSetupInitialStep(gnome, "shortcut")).toBe("shortcut");
93});
94
95it("does not skip native permission setup when capture has not been enabled", () => {
96 expect(
97 captureSetupInitialStep({
98 ...gnome,
99 mode: "direct",
100 linuxBackend: undefined,
101 gnomeExtension: undefined,
102 shortcutRegistered: false,
103 }),
104 ).toBe("access");
105});
106
107it("keeps the picker explanation when automatic capture is unavailable", () => {
108 expect(
109 captureSetupInitialStep({ ...gnome, linuxBackend: "picker", gnomeExtension: undefined }),
110 ).toBe("access");
111});
112
113it("offers manual capture on an unsupported GNOME version instead of trapping setup", () => {
114 const state = {
115 ...gnome,
116 linuxBackend: "picker" as const,
117 gnomeExtension: { status: "unsupported" as const, message: "New GNOME version" },
118 };
119 expect(captureSetupBackend(state)).toBe("picker");
120 expect(captureSetupAccessReady(state)).toBe(true);
121 expect(captureSetupInitialStep(state)).toBe("access");
122 expect(captureSetupCheckMessage(state)).toContain("choose a window each time");
123});
124
125it.each(["not-installed", "update-required", "error"] as const)(
126 "requires KDE helper access for %s even with a saved shortcut",
127 (status) => {
128 const state = {
129 ...gnome,
130 linuxBackend: "kde" as const,
131 gnomeExtension: undefined,
132 kdeHelper: { status, message: "Needs setup" },
133 shortcutVerified: true,
134 };
135 expect(captureSetupBackend(state)).toBe("kde");
136 expect(captureSetupAccessReady(state)).toBe(false);
137 expect(captureSetupInitialStep(state, "shortcut")).toBe("access");
138 expect(captureSetupShortcutReady(state, false)).toBe(false);
139 },
140);
141
142it("resumes KDE setup after the helper passes its real permission check", () => {
143 const state = {
144 ...gnome,
145 linuxBackend: "kde" as const,
146 gnomeExtension: undefined,
147 kdeHelper: { status: "ready" as const, message: "Ready" },
148 };
149 expect(captureSetupAccessReady(state)).toBe(true);
150 expect(captureSetupInitialStep(state)).toBe("shortcut");
151 expect(captureSetupInitialStep({ ...state, shortcutRegistered: false })).toBe("shortcut");
152});
153
154it("uses the current desktop's access requirements while preserving setup for the others", () => {
155 const kde: DesktopSnapShotState = {
156 ...gnome,
157 linuxDesktop: "kde",
158 linuxBackend: "kde",
159 kdeHelper: { status: "not-installed", message: "Install the helper" },
160 shortcutVerified: false,
161 };
162 expect(captureSetupDesktopName(kde)).toBe("KDE Plasma");
163 expect(captureSetupInitialStep(kde)).toBe("access");
164 expect(
165 captureSetupInitialStep({
166 ...kde,
167 kdeHelper: { status: "ready", message: "Ready" },
168 shortcutRegistered: false,
169 }),
170 ).toBe("shortcut");
171
172 const niri: DesktopSnapShotState = {
173 ...gnome,
174 linuxDesktop: "niri",
175 linuxBackend: "niri",
176 shortcutRegistered: false,
177 };
178 expect(captureSetupDesktopName(niri)).toBe("Niri");
179 expect(captureSetupInitialStep(niri)).toBe("shortcut");
180
181 expect(captureSetupDesktopName(gnome)).toBe("GNOME");
182 expect(captureSetupInitialStep(gnome)).toBe("shortcut");
183 expect(captureSetupShortcutReady(gnome, false)).toBe(true);
184});
185
186it("does not infer a Linux desktop for native capture or an unidentified portal", () => {
187 expect(captureSetupDesktopName({ ...gnome, mode: "direct" })).toBeUndefined();
188 expect(
189 captureSetupDesktopName({
190 ...gnome,
191 linuxBackend: "screenshot-portal",
192 gnomeExtension: undefined,
193 }),
194 ).toBeUndefined();
195});
196
197it("acknowledges an unchanged recheck while GNOME still needs a sign-out", () => {
198 const state = {
199 ...gnome,
200 gnomeExtension: { status: "restart-required" as const, message: "Sign out" },
201 };
202 expect(captureSetupCheckMessage(state)).toBe("Still waiting for you to sign out and back in.");
203 expect(captureSetupAccessReady(state)).toBe(false);
204});
205
206it("only confirms capture access when the rechecked extension is running and reachable", () => {
207 expect(captureSetupCheckMessage(gnome)).toBe("Ready. Continue to choose your shortcut.");
208 expect(captureSetupCheckMessage({ ...gnome, linuxBackend: "picker" })).toBe(
209 "Not ready yet. Finish the step above.",
210 );
211 expect(
212 captureSetupCheckMessage({
213 ...gnome,
214 gnomeExtension: { status: "disabled", message: "Enable it" },
215 }),
216 ).toBe("Not ready yet. Finish the step above.");
217});
218
219it("does not report a successful check when capture support could not be read", () => {
220 expect(captureSetupCheckMessage({ ...gnome, message: "Desktop disconnected" })).toContain(
221 "Still unable to check access",
222 );
223 expect(
224 captureSetupCheckMessage({
225 ...gnome,
226 gnomeExtension: { status: "error", message: "Could not read extension state" },
227 }),
228 ).toContain("Still unable to check access");
229});
230
231it("uses a capable portal without requiring the optional GNOME extension", () => {
232 const state = {
233 ...gnome,
234 linuxBackend: "screenshot-portal" as const,
235 gnomeExtension: { status: "not-installed" as const, message: "Not installed" },
236 };
237 expect(captureSetupBackend(state)).toBe("portal");
238 expect(captureSetupAccessReady(state)).toBe(true);
239 expect(captureSetupInitialStep(state)).toBe("shortcut");
240 expect(
241 captureSetupCheckMessage({
242 ...state,
243 gnomeExtension: { status: "error", message: "Optional extension failed" },
244 }),
245 ).toBe("Ready. Continue to choose your shortcut.");
246});
247
248it("lets Niri setup finish with configuration instructions without claiming the binding was verified", () => {
249 const niri = {
250 ...gnome,
251 linuxBackend: "niri" as const,
252 gnomeExtension: undefined,
253 shortcutRegistered: false,
254 };
255 expect(captureSetupBackend(niri)).toBe("niri");
256 expect(captureSetupAccessReady(niri)).toBe(true);
257 expect(captureSetupShortcutReady(niri, false)).toBe(false);
258 const withEndpoint = { ...niri, shortcutBinding: "Ctrl+Shift+2 { spawn ...; }" };
259 expect(captureSetupInitialStep(withEndpoint)).toBe("shortcut");
260 expect(captureSetupShortcutReady(withEndpoint, false)).toBe(true);
261 expect(withEndpoint.shortcutRegistered).toBe(false);
262 expect(withEndpoint.shortcutVerified).toBe(false);
263});
264
265it("requires saving a changed chord before finishing setup", () => {
266 expect(captureSetupShortcutReady(gnome, true)).toBe(false);
267 expect(captureSetupShortcutReady(gnome, false)).toBe(true);
268 expect(captureSetupShortcutReady({ ...gnome, shortcutRegistered: false }, false)).toBe(false);
269});
270
271it("allows leaving setup while the desktop prompt is pending but never after an explicit denial", () => {
272 const pending = { ...gnome, shortcutRegistered: false, shortcutPending: true };
273 expect(captureSetupShortcutReady(pending, false)).toBe(true);
274 expect(captureSetupShortcutReady(pending, true)).toBe(false);
275 expect(captureSetupShortcutReady({ ...pending, shortcutPending: false }, false)).toBe(false);
276});
277
278it.each([false, true])(
279 "does not require a previously observed shortcut activation (%s)",
280 (shortcutVerified) => {
281 const state = { ...gnome, shortcutVerified };
282 expect(captureSetupShortcutReady(state, false)).toBe(true);
283 expect(captureSetupInitialStep(state)).toBe("shortcut");
284 },
285);
286
287it("blocks finishing if desktop access is lost during the wizard", () => {
288 expect(
289 captureSetupShortcutReady(
290 {
291 ...gnome,
292 shortcutVerified: true,
293 message: "Desktop disconnected",
294 },
295 false,
296 ),
297 ).toBe(false);
298 expect(captureSetupAccessReady({ ...gnome, mode: "unavailable" })).toBe(false);
299});
300
301it.each([
302 [false, false, true],
303 [false, true, false],
304 [true, false, false],
305 [true, true, false],
306] as const)(
307 "closing setup (previously enabled=%s, completed=%s) disables only an unfinished first opt-in",
308 (wasEnabled, completed, disable) => {
309 expect(captureSetupShouldDisableOnClose(wasEnabled, completed)).toBe(disable);
310 },
311);
312
313it("gates Continue on macOS permissions, requiring accessibility only when app text is on", () => {
314 const mac: DesktopSnapShotState = {
315 ...gnome,
316 mode: "direct",
317 linuxBackend: undefined,
318 gnomeExtension: undefined,
319 macPermissions: { screenRecording: true, accessibility: false },
320 };
321 expect(captureSetupMacPermissionsReady(mac, true)).toBe(false);
322 expect(captureSetupMacPermissionsReady(mac, false)).toBe(true);
323 expect(
324 captureSetupMacPermissionsReady(
325 { ...mac, macPermissions: { screenRecording: false, accessibility: true } },
326 false,
327 ),
328 ).toBe(false);
329 expect(captureSetupMacPermissionsReady({ ...mac, macPermissions: undefined }, true)).toBe(true);
330});
331
332it.each(["kde", "hyprland"] as const)("ignores errors from inactive helpers on %s", (backend) => {
333 const state: DesktopSnapShotState = {
334 ...gnome,
335 linuxBackend: backend,
336 kdeHelper: { status: backend === "kde" ? "ready" : "error", message: "KDE" },
337 hyprlandHelper: { status: backend === "hyprland" ? "ready" : "error", message: "Hyprland" },
338 };
339 expect(captureSetupAccessReady(state)).toBe(true);
340 expect(captureSetupCheckMessage(state)).toBe("Ready. Continue to choose your shortcut.");
341});