1import { assert, expect, it } from "vite-plus/test";
2import { DEFAULT_CLIENT_SETTINGS, type DesktopSnapShotState } from "@t3tools/contracts";
3
4import {
5 createRecordingRequestTracker,
6 snapShotStatus,
7 snapShotShortcutStatus,
8 snapShotUnavailableMessage,
9 snapShotSoundPatch,
10 snapShotFeedbackUnavailableMessage,
11 snapShotSetupSummary,
12 snapShotSetupButtonLabel,
13 snapShotSetupComplete,
14 snapShotDescription,
15 snapShotAccessibilityUnavailableMessage,
16} from "./SnapShotSettings.logic";
17
18it.each([
19 ["off", { snapShotPlaySound: false }],
20 ["soft-pop", { snapShotPlaySound: true, snapShotSound: "soft-pop" }],
21 ["camera-shutter", { snapShotPlaySound: true, snapShotSound: "camera-shutter" }],
22] as const)("maps %s to compatible capture settings", (sound, patch) => {
23 expect(snapShotSoundPatch(sound)).toEqual(patch);
24});
25
26it("offers effects only with a capable GNOME extension, explaining how to upgrade v1", () => {
27 const state: DesktopSnapShotState = {
28 mode: "portal",
29 linuxBackend: "gnome-extension",
30 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
31 shortcutRegistered: true,
32 shortcutMessage: null,
33 message: null,
34 };
35 expect(snapShotFeedbackUnavailableMessage(state)).toContain("Update");
36 expect(
37 snapShotFeedbackUnavailableMessage({ ...state, linuxFeedbackAvailable: true }),
38 ).toBeUndefined();
39 expect(snapShotFeedbackUnavailableMessage({ ...state, linuxBackend: "picker" })).toContain(
40 "aren't available",
41 );
42 expect(snapShotFeedbackUnavailableMessage({ ...state, mode: "direct" })).toBeUndefined();
43});
44
45it("ignores a stale request after a newer request starts", () => {
46 const requests = createRecordingRequestTracker();
47 const firstRequest = requests.tryBegin();
48 assert(firstRequest);
49
50 requests.clear();
51 const secondRequest = requests.tryBegin();
52 assert(secondRequest);
53
54 expect(requests.owns(firstRequest)).toBe(false);
55 expect(requests.owns(secondRequest)).toBe(true);
56 expect(requests.tryBegin()).toBeNull();
57});
58
59it("reports unavailable capture support without browser globals", () => {
60 expect(snapShotUnavailableMessage(false)).toBe("Only available in the desktop app.");
61});
62
63it("describes Niri setup without claiming a global shortcut is registered", () => {
64 const state: DesktopSnapShotState = {
65 mode: "portal",
66 linuxBackend: "niri",
67 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
68 shortcutRegistered: false,
69 shortcutMessage: "Managed by Niri",
70 message: null,
71 };
72 expect(snapShotStatus(state, true)).toBe("Finish shortcut setup");
73 expect(snapShotStatus(state, true)).not.toContain("could not be registered");
74 expect(snapShotFeedbackUnavailableMessage(state)).toContain("aren't available on Niri");
75});
76
77it("distinguishes Hyprland helper setup, action registration, and verified shortcut delivery", () => {
78 const state: DesktopSnapShotState = {
79 mode: "portal",
80 linuxBackend: "hyprland",
81 linuxDesktop: "hyprland",
82 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
83 shortcutRegistered: false,
84 shortcutMessage: "Connecting to Hyprland shortcuts…",
85 message: null,
86 hyprlandHelper: { status: "not-installed", message: "Install helper" },
87 };
88 expect(snapShotSetupButtonLabel(state)).toBe("Set up Hyprland capture");
89 expect(snapShotStatus(state, false)).toBe("Turn this on to set up snapshots.");
90 expect(snapShotStatus(state, true)).toContain("Install the capture helper");
91 expect(snapShotFeedbackUnavailableMessage(state)).toContain("Install or update");
92 const ready = {
93 ...state,
94 hyprlandHelper: { status: "ready" as const, message: "Ready" },
95 linuxFeedbackAvailable: true,
96 };
97 expect(snapShotSetupButtonLabel(ready)).toBe("Manage capture");
98 expect(snapShotShortcutStatus({ ...ready, shortcutPending: true })).not.toContain("permission");
99 expect(snapShotStatus({ ...ready, shortcutActionRegistered: true }, true)).toBe(
100 "Use your shortcut from another app",
101 );
102 expect(snapShotStatus({ ...ready, shortcutVerified: true }, true)).toBe("Ready to capture");
103 expect(snapShotFeedbackUnavailableMessage(ready)).toBeUndefined();
104 expect(snapShotAccessibilityUnavailableMessage(ready)).toBeUndefined();
105});
106
107it("keeps unavailable capture distinct from the opt-in setup prompt", () => {
108 const state: DesktopSnapShotState = {
109 mode: "unavailable",
110 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
111 shortcutRegistered: false,
112 shortcutMessage: null,
113 message: "Wayland is required.",
114 };
115
116 expect(snapShotStatus(state, false)).toBe("Wayland is required.");
117});
118
119it.each(["gnome-extension", "niri", "screenshot-portal", "picker"] as const)(
120 "waits for opt-in before presenting %s setup requirements",
121 (linuxBackend) => {
122 const state: DesktopSnapShotState = {
123 mode: "portal",
124 linuxBackend,
125 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
126 shortcutRegistered: false,
127 shortcutMessage: "Shortcut permission needed",
128 message: "Capture needs attention",
129 gnomeExtension: { status: "not-installed", message: "Install the extension" },
130 };
131
132 expect(DEFAULT_CLIENT_SETTINGS.snapShotEnabled).toBe(false);
133 expect(snapShotStatus(state, false)).toBe("Turn this on to set up snapshots.");
134 expect(snapShotStatus(state, true)).toBe("Capture needs attention");
135 },
136);
137
138it("distinguishes saved shortcuts from observed delivery without making users repeat setup", () => {
139 const state: DesktopSnapShotState = {
140 mode: "portal",
141 linuxBackend: "gnome-extension",
142 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
143 shortcutRegistered: true,
144 shortcutMessage: "Requested",
145 message: null,
146 gnomeExtension: { status: "enabled", message: "Running" },
147 };
148 expect(snapShotSetupSummary(state, true)).toBe("Shortcut saved");
149 expect(snapShotSetupButtonLabel(state)).toBe("Manage capture");
150 expect(snapShotSetupSummary({ ...state, shortcutVerified: true }, true)).toBe("Ready to capture");
151 expect(snapShotSetupButtonLabel({ ...state, shortcutVerified: true })).toBe("Manage capture");
152 expect(snapShotSetupButtonLabel({ ...state, shortcutRegistered: false })).toBe("Manage capture");
153 expect(
154 snapShotSetupButtonLabel({
155 ...state,
156 gnomeExtension: { status: "disabled", message: "Enable the extension" },
157 }),
158 ).toBe("Set up GNOME capture");
159 expect(snapShotSetupSummary({ ...state, shortcutVerified: true }, false)).toContain(
160 "Enable capture",
161 );
162 expect(
163 snapShotSetupSummary(
164 {
165 ...state,
166 gnomeExtension: { status: "restart-required", message: "Sign out" },
167 shortcutVerified: true,
168 },
169 true,
170 ),
171 ).toBe("Set up active-window snapshots");
172 expect(
173 snapShotSetupSummary(
174 { ...state, linuxBackend: "niri", gnomeExtension: undefined, shortcutRegistered: false },
175 true,
176 ),
177 ).toBe("Finish shortcut setup");
178 expect(
179 snapShotSetupSummary(
180 { ...state, linuxBackend: "picker", gnomeExtension: undefined, shortcutVerified: true },
181 true,
182 ),
183 ).toBe("Manual capture only — you'll choose a window each time");
184 expect(
185 snapShotSetupSummary(
186 {
187 ...state,
188 linuxBackend: "screenshot-portal",
189 gnomeExtension: { status: "not-installed", message: "Optional extension" },
190 shortcutVerified: true,
191 },
192 true,
193 ),
194 ).toBe("Ready to capture");
195});
196
197it.each([
198 ["gnome", "GNOME"],
199 ["kde", "KDE Plasma"],
200 ["niri", "Niri"],
201] as const)(
202 "names %s when setup cannot yet determine the capture backend",
203 (linuxDesktop, name) => {
204 const state: DesktopSnapShotState = {
205 mode: "portal",
206 linuxDesktop,
207 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
208 shortcutRegistered: false,
209 shortcutMessage: null,
210 message: "Capability check failed",
211 };
212 expect(snapShotSetupButtonLabel(state)).toBe(`Set up ${name} capture`);
213 },
214);
215
216it("keeps picker limitations visible after shortcut verification without recommending a GNOME extension", () => {
217 const state: DesktopSnapShotState = {
218 mode: "portal",
219 linuxBackend: "picker",
220 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
221 shortcutRegistered: true,
222 shortcutMessage: null,
223 message: null,
224 shortcutVerified: true,
225 };
226 expect(snapShotStatus(state, true)).toContain("Manual capture only");
227 expect(snapShotDescription(state)).toContain("Automatic capture isn't available");
228 expect(snapShotFeedbackUnavailableMessage(state)).not.toContain("GNOME");
229 expect(snapShotAccessibilityUnavailableMessage(state)).toContain("only provides a screenshot");
230 expect(
231 snapShotAccessibilityUnavailableMessage({ ...state, linuxBackend: "screenshot-portal" }),
232 ).toContain("only provides a screenshot");
233 expect(
234 snapShotAccessibilityUnavailableMessage({ ...state, linuxBackend: "kde" }),
235 ).toBeUndefined();
236 expect(snapShotFeedbackUnavailableMessage({ ...state, linuxBackend: "kde" })).toContain(
237 "capture helper",
238 );
239 expect(
240 snapShotFeedbackUnavailableMessage({
241 ...state,
242 linuxBackend: "kde",
243 linuxFeedbackAvailable: true,
244 kdeHelper: { status: "ready", message: "Ready", feedbackAvailable: true },
245 }),
246 ).toBeUndefined();
247 expect(
248 snapShotStatus(
249 { ...state, linuxBackend: "kde", kdeHelper: { status: "not-installed", message: "Install" } },
250 true,
251 ),
252 ).toContain("Install the capture helper");
253});
254
255it("does not ask Niri users to repeat setup when its capture endpoint is available", () => {
256 const state: DesktopSnapShotState = {
257 mode: "portal",
258 linuxBackend: "niri",
259 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
260 shortcutRegistered: false,
261 shortcutBinding: "Ctrl+Shift+2 { spawn ...; }",
262 shortcutMessage: null,
263 message: null,
264 };
265 expect(snapShotStatus(state, true)).toBe("Use your shortcut from another app");
266 expect(snapShotSetupButtonLabel(state)).toBe("Manage capture");
267});
268
269it("reports pending, denied, and assigned shortcuts without inferring consent from saved keys", () => {
270 const state: DesktopSnapShotState = {
271 mode: "portal",
272 linuxBackend: "screenshot-portal",
273 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
274 shortcutRegistered: false,
275 shortcutPending: true,
276 shortcutMessage: null,
277 message: null,
278 };
279 expect(snapShotStatus(state, true)).toContain("Waiting for shortcut permission");
280 expect(snapShotShortcutStatus(state)).toContain("Approve the shortcut permission prompt");
281 const denied = { ...state, shortcutPending: false, shortcutMessage: "Permission wasn't granted" };
282 expect(snapShotShortcutStatus(denied)).toBe("Permission wasn't granted");
283 const approved = {
284 ...denied,
285 shortcutRegistered: true,
286 shortcutLabel: "Press <Shift><Control>2",
287 shortcutMessage: "Desktop shortcut: Press <Shift><Control>2",
288 };
289 expect(snapShotStatus(approved, true)).toBe("Ready to capture");
290 expect(snapShotShortcutStatus(approved)).toBeNull();
291 expect(snapShotShortcutStatus({ ...approved, shortcutPending: true })).toContain(
292 "Approve the shortcut permission prompt",
293 );
294 expect(
295 snapShotShortcutStatus({
296 ...approved,
297 shortcutRegistered: false,
298 shortcutMessage: "Permission wasn't granted",
299 }),
300 ).toBe("Permission wasn't granted");
301});
302
303it("hides macOS setup only while permissions and the shortcut are all in place", () => {
304 const ready: DesktopSnapShotState = {
305 mode: "direct",
306 shortcut: DEFAULT_CLIENT_SETTINGS.snapShotShortcut,
307 shortcutRegistered: true,
308 shortcutMessage: null,
309 message: null,
310 macPermissions: { screenRecording: true, accessibility: true },
311 };
312 expect(snapShotSetupComplete(ready, true)).toBe(true);
313 expect(snapShotSetupComplete({ ...ready, macPermissions: undefined }, true)).toBe(false);
314 expect(snapShotSetupComplete({ ...ready, shortcutRegistered: false }, true)).toBe(false);
315 const revoked = {
316 ...ready,
317 macPermissions: { screenRecording: true, accessibility: false },
318 message: "Allow Accessibility in System Settings, then restart T3 Code.",
319 };
320 expect(snapShotSetupComplete(revoked, true)).toBe(false);
321 expect(snapShotStatus(revoked, true)).toBe("Capture needs attention");
322 expect(snapShotSetupButtonLabel(revoked)).toBe("Continue setup");
323 expect(snapShotSetupComplete({ ...revoked, message: null }, false)).toBe(true);
324 expect(
325 snapShotSetupComplete(
326 { ...ready, windows: true, macPermissions: undefined, shortcutRegistered: false },
327 true,
328 ),
329 ).toBe(true);
330});