1import { expect, it } from "vite-plus/test";
2import { CaptureService, CLIENT_NAMES, isWaylandSession } from "./captureService.js";
3
4it("supports GNOME 50 without the removed X11 API, rejecting older X11 sessions", () => {
5 expect(isWaylandSession({})).toBe(true);
6 expect(isWaylandSession({ is_wayland_compositor: () => true })).toBe(true);
7 expect(isWaylandSession({ is_wayland_compositor: () => false })).toBe(false);
8});
9
10function fixture(overrides = {}) {
11 let snapshots = 0;
12 const options = {
13 getNameOwner: async (name) => (name === CLIENT_NAMES[0] ? ":1.23" : null),
14 isAvailable: () => true,
15 takeSnapshot: async () => {
16 snapshots++;
17 return "pixels";
18 },
19 ...overrides,
20 };
21 return { service: new CaptureService(options), snapshots: () => snapshots };
22}
23
24it("rejects other clients before looking at the active window", async () => {
25 const { service, snapshots } = fixture();
26 await expect(service.capture(":1.99")).rejects.toThrow("Only The Knife");
27 expect(snapshots()).toBe(0);
28 expect(await service.capture(":1.23")).toBe("pixels");
29});
30
31it.each(CLIENT_NAMES)("accepts the current owner of %s", async (client) => {
32 const { service } = fixture({ getNameOwner: async (name) => (name === client ? ":1.23" : null) });
33 expect(await service.capture(":1.23")).toBe("pixels");
34});
35
36it("rechecks the name owner on each capture", async () => {
37 let owner = ":1.23";
38 const { service } = fixture({ getNameOwner: async () => owner });
39 expect(await service.capture(owner)).toBe("pixels");
40 owner = ":1.24";
41 await expect(service.capture(":1.23")).rejects.toThrow("Only The Knife");
42});
43
44it("rejects locked and non-Wayland sessions without taking a screenshot", async () => {
45 const { service, snapshots } = fixture({ isAvailable: () => false });
46 await expect(service.capture(":1.23")).rejects.toThrow("unavailable");
47 expect(snapshots()).toBe(0);
48});
49
50it("does not start a screenshot if disabled during authorization", async () => {
51 let authorize;
52 const { service, snapshots } = fixture({
53 getNameOwner: () =>
54 new Promise((resolve) => {
55 authorize = resolve;
56 }),
57 });
58 const result = service.capture(":1.23");
59 service.disable();
60 authorize(":1.23");
61 await expect(result).rejects.toThrow("unavailable");
62 expect(snapshots()).toBe(0);
63});
64
65it.each(["lock", "disable"])(
66 "does not return pixels after %s during capture",
67 async (operation) => {
68 let available = true;
69 const { service } = fixture({
70 isAvailable: () => available,
71 takeSnapshot: async () => {
72 if (operation === "disable") service.disable();
73 else available = false;
74 return "private pixels";
75 },
76 });
77 await expect(service.capture(":1.23")).rejects.toThrow("unavailable");
78 },
79);
80
81it("rejects concurrent requests and clears busy after a failed capture", async () => {
82 let finish;
83 const started = Promise.withResolvers();
84 const { service } = fixture({
85 takeSnapshot: () => {
86 started.resolve();
87 return new Promise((_, reject) => {
88 finish = reject;
89 });
90 },
91 });
92 const first = service.capture(":1.23");
93 await started.promise;
94 await expect(service.capture(":1.23")).rejects.toThrow("already in progress");
95 finish(new Error("gone"));
96 await expect(first).rejects.toThrow("gone");
97 await expect(service.capture(":1.99")).rejects.toThrow("Only The Knife");
98});
99
100it("prepares focus/effects only after pixels and identity have been captured", async () => {
101 const events = [];
102 const snapshot = { png: "pixels", metadata: "captured-window" };
103 const { service } = fixture({
104 getProcessId: async () => {
105 events.push("caller-pid");
106 return 42;
107 },
108 takeSnapshot: async () => {
109 events.push("snapshot");
110 return snapshot;
111 },
112 beginFeedback: (sender, pid, captured, options) => {
113 expect([sender, pid, captured, options]).toEqual([
114 ":1.23",
115 42,
116 snapshot,
117 { flash: true, animate: true },
118 ]);
119 events.push("feedback");
120 return true;
121 },
122 });
123 expect(await service.capture(":1.23", { flash: true, animate: true })).toEqual({
124 ...snapshot,
125 animationStarted: true,
126 });
127 expect(events).toEqual(["caller-pid", "snapshot", "feedback"]);
128});
129
130it("does not create overlays or activate after the session locks during capture", async () => {
131 let available = true;
132 let feedback = 0;
133 const { service } = fixture({
134 getProcessId: async () => 42,
135 isAvailable: () => available,
136 takeSnapshot: async () => {
137 available = false;
138 return {};
139 },
140 beginFeedback: () => {
141 feedback++;
142 },
143 });
144 await expect(service.capture(":1.23", { flash: true, animate: true })).rejects.toThrow(
145 "unavailable",
146 );
147 expect(feedback).toBe(0);
148});