@gtkx/vitest 0.21.0 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +137 -34
  2. package/dist/headless-display.d.ts +31 -0
  3. package/dist/headless-display.d.ts.map +1 -0
  4. package/dist/headless-display.js +335 -0
  5. package/dist/headless-display.js.map +1 -0
  6. package/dist/headless-globals.d.ts +10 -0
  7. package/dist/headless-globals.d.ts.map +1 -0
  8. package/dist/headless-globals.js +13 -0
  9. package/dist/headless-globals.js.map +1 -0
  10. package/dist/index.d.ts +19 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +54 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/install-headless-shutdown.d.ts +3 -0
  15. package/dist/install-headless-shutdown.d.ts.map +1 -0
  16. package/dist/install-headless-shutdown.js +12 -0
  17. package/dist/install-headless-shutdown.js.map +1 -0
  18. package/dist/notification-service.d.ts +3 -0
  19. package/dist/notification-service.d.ts.map +1 -0
  20. package/dist/notification-service.js +48 -0
  21. package/dist/notification-service.js.map +1 -0
  22. package/dist/resolve-executable.d.ts +3 -0
  23. package/dist/resolve-executable.d.ts.map +1 -0
  24. package/dist/resolve-executable.js +26 -0
  25. package/dist/resolve-executable.js.map +1 -0
  26. package/dist/virtual-seat.d.ts +3 -0
  27. package/dist/virtual-seat.d.ts.map +1 -0
  28. package/dist/virtual-seat.js +155 -0
  29. package/dist/virtual-seat.js.map +1 -0
  30. package/dist/worker-preload.d.ts +2 -0
  31. package/dist/worker-preload.d.ts.map +1 -0
  32. package/dist/worker-preload.js +7 -0
  33. package/dist/worker-preload.js.map +1 -0
  34. package/dist/worker-setup.d.ts +2 -0
  35. package/dist/worker-setup.d.ts.map +1 -0
  36. package/dist/worker-setup.js +3 -0
  37. package/dist/worker-setup.js.map +1 -0
  38. package/package.json +14 -7
  39. package/src/dbus-native.d.ts +17 -0
  40. package/src/headless-display.ts +507 -0
  41. package/src/headless-globals.ts +22 -0
  42. package/src/index.ts +72 -1
  43. package/src/install-headless-shutdown.ts +15 -0
  44. package/src/notification-service.ts +57 -0
  45. package/src/resolve-executable.ts +32 -0
  46. package/src/virtual-seat.ts +214 -0
  47. package/src/worker-preload.ts +8 -0
  48. package/src/worker-setup.ts +3 -0
  49. package/dist/plugin.d.ts +0 -22
  50. package/dist/plugin.d.ts.map +0 -1
  51. package/dist/plugin.js +0 -36
  52. package/dist/plugin.js.map +0 -1
  53. package/dist/setup.d.ts +0 -2
  54. package/dist/setup.d.ts.map +0 -1
  55. package/dist/setup.js +0 -44
  56. package/dist/setup.js.map +0 -1
  57. package/src/plugin.ts +0 -41
  58. package/src/setup.ts +0 -52
@@ -0,0 +1,32 @@
1
+ import { accessSync, constants } from "node:fs";
2
+ import { delimiter, isAbsolute, join } from "node:path";
3
+
4
+ const isExecutable = (path: string): boolean => {
5
+ try {
6
+ accessSync(path, constants.X_OK);
7
+
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ };
13
+
14
+ const resolveExecutable = (command: string): string => {
15
+ if (isAbsolute(command)) {
16
+ return command;
17
+ }
18
+
19
+ const searchPaths = (process.env.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0);
20
+
21
+ for (const directory of searchPaths) {
22
+ const candidate = join(directory, command);
23
+
24
+ if (isExecutable(candidate)) {
25
+ return candidate;
26
+ }
27
+ }
28
+
29
+ throw new Error(`Cannot find the "${command}" executable on PATH`);
30
+ };
31
+
32
+ export { resolveExecutable };
@@ -0,0 +1,214 @@
1
+ import { connect, type Socket } from "node:net";
2
+
3
+ type WireGlobal = { name: number; version: number };
4
+ type PendingCallback = { resolve: () => void; reject: (error: Error) => void };
5
+
6
+ type WireState = {
7
+ socket: Socket;
8
+ globals: Map<string, WireGlobal>;
9
+ pending: Map<number, PendingCallback>;
10
+ inbox: Buffer;
11
+ nextId: number;
12
+ };
13
+
14
+ const DISPLAY_ID = 1;
15
+ const REGISTRY_ID = 2;
16
+ const FIRST_CLIENT_ID = 3;
17
+ const HEADER_SIZE = 8;
18
+ const WORD_SIZE = 4;
19
+ const OPCODE_MASK = 0xFF_FF;
20
+ const DISPLAY_ERROR = 0;
21
+ const DISPLAY_SYNC = 0;
22
+ const DISPLAY_GET_REGISTRY = 1;
23
+ const REGISTRY_GLOBAL = 0;
24
+ const REGISTRY_BIND = 0;
25
+ const CREATE_DEVICE = 0;
26
+ const SEAT_INTERFACE = "wl_seat";
27
+ const KEYBOARD_MANAGER_INTERFACE = "zwp_virtual_keyboard_manager_v1";
28
+ const POINTER_MANAGER_INTERFACE = "zwlr_virtual_pointer_manager_v1";
29
+
30
+ const align4 = (size: number): number => (size + 3) & ~3;
31
+
32
+ const encodeString = (value: string): Buffer => {
33
+ const bytes = Buffer.from(`${value}\0`, "utf8");
34
+ const encoded = Buffer.alloc(WORD_SIZE + align4(bytes.length));
35
+ encoded.writeUInt32LE(bytes.length, 0);
36
+ bytes.copy(encoded, WORD_SIZE);
37
+
38
+ return encoded;
39
+ };
40
+
41
+ const encodeArgument = (argument: number | string): Buffer => {
42
+ if (typeof argument === "string") {
43
+ return encodeString(argument);
44
+ }
45
+
46
+ const encoded = Buffer.alloc(WORD_SIZE);
47
+ encoded.writeUInt32LE(argument, 0);
48
+
49
+ return encoded;
50
+ };
51
+
52
+ const sendRequest = (state: WireState, objectId: number, opcode: number, args: (number | string)[]): void => {
53
+ const body = Buffer.concat(args.map((argument) => encodeArgument(argument)));
54
+ const header = Buffer.alloc(HEADER_SIZE);
55
+ header.writeUInt32LE(objectId, 0);
56
+ header.writeUInt32LE(((body.length + HEADER_SIZE) << 16) | opcode, WORD_SIZE);
57
+ state.socket.write(Buffer.concat([header, body]));
58
+ };
59
+
60
+ const readString = (body: Buffer, offset: number): string => {
61
+ const length = body.readUInt32LE(offset);
62
+
63
+ return body.subarray(offset + WORD_SIZE, offset + WORD_SIZE + length - 1).toString("utf8");
64
+ };
65
+
66
+ const allocateId = (state: WireState): number => {
67
+ const id = state.nextId;
68
+ state.nextId += 1;
69
+
70
+ return id;
71
+ };
72
+
73
+ const storeGlobal = (state: WireState, body: Buffer): void => {
74
+ const name = body.readUInt32LE(0);
75
+ const interfaceName = readString(body, WORD_SIZE);
76
+ const versionOffset = WORD_SIZE + WORD_SIZE + align4(interfaceName.length + 1);
77
+ state.globals.set(interfaceName, { name, version: body.readUInt32LE(versionOffset) });
78
+ };
79
+
80
+ const rejectPending = (state: WireState, error: Error): void => {
81
+ for (const callback of state.pending.values()) {
82
+ callback.reject(error);
83
+ }
84
+
85
+ state.pending.clear();
86
+ };
87
+
88
+ const settlePending = (state: WireState, objectId: number): void => {
89
+ const callback = state.pending.get(objectId);
90
+
91
+ if (callback === undefined) {
92
+ return;
93
+ }
94
+
95
+ state.pending.delete(objectId);
96
+ callback.resolve();
97
+ };
98
+
99
+ const handleEvent = (state: WireState, objectId: number, opcode: number, body: Buffer): void => {
100
+ if (objectId === DISPLAY_ID && opcode === DISPLAY_ERROR) {
101
+ rejectPending(state, new Error(`virtual seat request rejected: ${readString(body, HEADER_SIZE)}`));
102
+
103
+ return;
104
+ }
105
+
106
+ if (objectId === REGISTRY_ID && opcode === REGISTRY_GLOBAL) {
107
+ storeGlobal(state, body);
108
+
109
+ return;
110
+ }
111
+
112
+ settlePending(state, objectId);
113
+ };
114
+
115
+ const consumeMessages = (state: WireState): void => {
116
+ while (state.inbox.length >= HEADER_SIZE) {
117
+ const objectId = state.inbox.readUInt32LE(0);
118
+ const word = state.inbox.readUInt32LE(WORD_SIZE);
119
+ const size = word >>> 16;
120
+
121
+ if (size < HEADER_SIZE || state.inbox.length < size) {
122
+ return;
123
+ }
124
+
125
+ handleEvent(state, objectId, word & OPCODE_MASK, state.inbox.subarray(HEADER_SIZE, size));
126
+ state.inbox = state.inbox.subarray(size);
127
+ }
128
+ };
129
+
130
+ const receive = (state: WireState, chunk: Buffer): void => {
131
+ state.inbox = Buffer.concat([state.inbox, chunk]);
132
+ consumeMessages(state);
133
+ };
134
+
135
+ const attachHandlers = (state: WireState, onOpen: () => void, onFailure: (error: Error) => void): void => {
136
+ state.socket.on("data", (chunk: Buffer) => {
137
+ receive(state, chunk);
138
+ });
139
+
140
+ state.socket.on("error", onFailure);
141
+
142
+ state.socket.on("close", () => {
143
+ onFailure(new Error("the compositor closed the virtual seat connection"));
144
+ });
145
+
146
+ state.socket.once("connect", onOpen);
147
+ };
148
+
149
+ const openConnection = (socketPath: string): Promise<WireState> =>
150
+ new Promise((resolve, reject) => {
151
+ const socket = connect(socketPath);
152
+
153
+ const state: WireState = {
154
+ socket,
155
+ globals: new Map(),
156
+ pending: new Map(),
157
+ inbox: Buffer.alloc(0),
158
+ nextId: FIRST_CLIENT_ID,
159
+ };
160
+
161
+ attachHandlers(
162
+ state,
163
+ () => {
164
+ resolve(state);
165
+ },
166
+ (error) => {
167
+ reject(error);
168
+ rejectPending(state, error);
169
+ },
170
+ );
171
+ });
172
+
173
+ const roundtrip = (state: WireState): Promise<void> =>
174
+ new Promise((resolve, reject) => {
175
+ const callbackId = allocateId(state);
176
+ state.pending.set(callbackId, { resolve, reject });
177
+ sendRequest(state, DISPLAY_ID, DISPLAY_SYNC, [callbackId]);
178
+ });
179
+
180
+ const bindGlobal = (state: WireState, interfaceName: string, version: number): number => {
181
+ const entry = state.globals.get(interfaceName);
182
+
183
+ if (entry === undefined) {
184
+ throw new Error(`the headless compositor does not implement ${interfaceName}`);
185
+ }
186
+
187
+ const id = allocateId(state);
188
+ sendRequest(state, REGISTRY_ID, REGISTRY_BIND, [entry.name, interfaceName, Math.min(version, entry.version), id]);
189
+
190
+ return id;
191
+ };
192
+
193
+ const createDevices = (state: WireState): void => {
194
+ const seat = bindGlobal(state, SEAT_INTERFACE, 1);
195
+ const keyboardManager = bindGlobal(state, KEYBOARD_MANAGER_INTERFACE, 1);
196
+ const pointerManager = bindGlobal(state, POINTER_MANAGER_INTERFACE, 1);
197
+ sendRequest(state, keyboardManager, CREATE_DEVICE, [seat, allocateId(state)]);
198
+ sendRequest(state, pointerManager, CREATE_DEVICE, [seat, allocateId(state)]);
199
+ };
200
+
201
+ const startVirtualSeat = async (socketPath: string): Promise<() => void> => {
202
+ const state = await openConnection(socketPath);
203
+ sendRequest(state, DISPLAY_ID, DISPLAY_GET_REGISTRY, [REGISTRY_ID]);
204
+ await roundtrip(state);
205
+ createDevices(state);
206
+ await roundtrip(state);
207
+ state.socket.unref();
208
+
209
+ return () => {
210
+ state.socket.destroy();
211
+ };
212
+ };
213
+
214
+ export { startVirtualSeat };
@@ -0,0 +1,8 @@
1
+ import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from "./headless-display.js";
2
+ import { setHeadlessTeardown } from "./headless-globals.js";
3
+
4
+ const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
5
+ const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
6
+
7
+ process.on("exit", teardown);
8
+ setHeadlessTeardown(teardown);
@@ -0,0 +1,3 @@
1
+ import { installHeadlessShutdown } from "./install-headless-shutdown.js";
2
+
3
+ installHeadlessShutdown();
package/dist/plugin.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import type { Plugin } from "vitest/config";
2
- /**
3
- * Creates the GTKX Vitest plugin for running GTK tests.
4
- *
5
- * Each worker spawns its own Xvfb instance on a PID-based display number.
6
- *
7
- * @returns Vitest plugin configuration
8
- *
9
- * @example
10
- * ```ts
11
- * // vitest.config.ts
12
- * import { defineConfig } from "vitest/config";
13
- * import gtkx from "@gtkx/vitest";
14
- *
15
- * export default defineConfig({
16
- * plugins: [gtkx()],
17
- * });
18
- * ```
19
- */
20
- declare const gtkx: () => Plugin;
21
- export default gtkx;
22
- //# sourceMappingURL=plugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,IAAI,QAAO,MAgBhB,CAAC;AAEF,eAAe,IAAI,CAAC"}
package/dist/plugin.js DELETED
@@ -1,36 +0,0 @@
1
- import { join } from "node:path";
2
- /**
3
- * Creates the GTKX Vitest plugin for running GTK tests.
4
- *
5
- * Each worker spawns its own Xvfb instance on a PID-based display number.
6
- *
7
- * @returns Vitest plugin configuration
8
- *
9
- * @example
10
- * ```ts
11
- * // vitest.config.ts
12
- * import { defineConfig } from "vitest/config";
13
- * import gtkx from "@gtkx/vitest";
14
- *
15
- * export default defineConfig({
16
- * plugins: [gtkx()],
17
- * });
18
- * ```
19
- */
20
- const gtkx = () => {
21
- const workerSetupPath = join(import.meta.dirname, "setup.js");
22
- return {
23
- name: "gtkx",
24
- config(config) {
25
- const setupFiles = config.test?.setupFiles ?? [];
26
- return {
27
- test: {
28
- setupFiles: [workerSetupPath, ...(Array.isArray(setupFiles) ? setupFiles : [setupFiles])],
29
- pool: "forks",
30
- },
31
- };
32
- },
33
- };
34
- };
35
- export default gtkx;
36
- //# sourceMappingURL=plugin.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,IAAI,GAAG,GAAW,EAAE;IACtB,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE9D,OAAO;QACH,IAAI,EAAE,MAAM;QACZ,MAAM,CAAC,MAAM;YACT,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC;YAEjD,OAAO;gBACH,IAAI,EAAE;oBACF,UAAU,EAAE,CAAC,eAAe,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;oBACzF,IAAI,EAAE,OAAO;iBAChB;aACJ,CAAC;QACN,CAAC;KACJ,CAAC;AACN,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC"}
package/dist/setup.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":""}
package/dist/setup.js DELETED
@@ -1,44 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { beforeAll } from "vitest";
4
- const display = 100 + (process.pid % 5000);
5
- const socketPath = `/tmp/.X11-unix/X${display}`;
6
- const xvfb = spawn("Xvfb", [`:${display}`, "-screen", "0", "1024x768x24"], {
7
- stdio: "ignore",
8
- });
9
- xvfb.unref();
10
- process.env.GDK_BACKEND = "x11";
11
- process.env.GSK_RENDERER = "cairo";
12
- process.env.LIBGL_ALWAYS_SOFTWARE = "1";
13
- process.env.DISPLAY = `:${display}`;
14
- const killXvfb = () => {
15
- if (xvfb.pid !== undefined) {
16
- try {
17
- process.kill(xvfb.pid, "SIGTERM");
18
- }
19
- catch { }
20
- }
21
- };
22
- process.on("exit", killXvfb);
23
- process.on("SIGTERM", () => {
24
- killXvfb();
25
- process.exit(143);
26
- });
27
- process.on("SIGINT", () => {
28
- killXvfb();
29
- process.exit(130);
30
- });
31
- const waitForDisplay = async (timeout = 5000) => {
32
- const start = Date.now();
33
- while (Date.now() - start < timeout) {
34
- if (existsSync(socketPath)) {
35
- return;
36
- }
37
- await new Promise((resolve) => setTimeout(resolve, 50));
38
- }
39
- throw new Error(`Xvfb display :${display} did not become available within ${timeout}ms`);
40
- };
41
- beforeAll(async () => {
42
- await waitForDisplay();
43
- });
44
- //# sourceMappingURL=setup.js.map
package/dist/setup.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEnC,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AAC3C,MAAM,UAAU,GAAG,mBAAmB,OAAO,EAAE,CAAC;AAEhD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,OAAO,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE;IACvE,KAAK,EAAE,QAAQ;CAClB,CAAC,CAAC;AAEH,IAAI,CAAC,KAAK,EAAE,CAAC;AAEb,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC;AAChC,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC;AACnC,OAAO,CAAC,GAAG,CAAC,qBAAqB,GAAG,GAAG,CAAC;AACxC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAEpC,MAAM,QAAQ,GAAG,GAAS,EAAE;IACxB,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACd,CAAC;AACL,CAAC,CAAC;AAEF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAE7B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,QAAQ,EAAE,CAAC;IACX,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,QAAQ,EAAE,CAAC;IACX,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,EAAiB,EAAE;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;QAClC,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACzB,OAAO;QACX,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,OAAO,oCAAoC,OAAO,IAAI,CAAC,CAAC;AAC7F,CAAC,CAAC;AAEF,SAAS,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,cAAc,EAAE,CAAC;AAC3B,CAAC,CAAC,CAAC"}
package/src/plugin.ts DELETED
@@ -1,41 +0,0 @@
1
- import { join } from "node:path";
2
-
3
- import type { Plugin } from "vitest/config";
4
-
5
- /**
6
- * Creates the GTKX Vitest plugin for running GTK tests.
7
- *
8
- * Each worker spawns its own Xvfb instance on a PID-based display number.
9
- *
10
- * @returns Vitest plugin configuration
11
- *
12
- * @example
13
- * ```ts
14
- * // vitest.config.ts
15
- * import { defineConfig } from "vitest/config";
16
- * import gtkx from "@gtkx/vitest";
17
- *
18
- * export default defineConfig({
19
- * plugins: [gtkx()],
20
- * });
21
- * ```
22
- */
23
- const gtkx = (): Plugin => {
24
- const workerSetupPath = join(import.meta.dirname, "setup.js");
25
-
26
- return {
27
- name: "gtkx",
28
- config(config) {
29
- const setupFiles = config.test?.setupFiles ?? [];
30
-
31
- return {
32
- test: {
33
- setupFiles: [workerSetupPath, ...(Array.isArray(setupFiles) ? setupFiles : [setupFiles])],
34
- pool: "forks",
35
- },
36
- };
37
- },
38
- };
39
- };
40
-
41
- export default gtkx;
package/src/setup.ts DELETED
@@ -1,52 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { beforeAll } from "vitest";
4
-
5
- const display = 100 + (process.pid % 5000);
6
- const socketPath = `/tmp/.X11-unix/X${display}`;
7
-
8
- const xvfb = spawn("Xvfb", [`:${display}`, "-screen", "0", "1024x768x24"], {
9
- stdio: "ignore",
10
- });
11
-
12
- xvfb.unref();
13
-
14
- process.env.GDK_BACKEND = "x11";
15
- process.env.GSK_RENDERER = "cairo";
16
- process.env.LIBGL_ALWAYS_SOFTWARE = "1";
17
- process.env.DISPLAY = `:${display}`;
18
-
19
- const killXvfb = (): void => {
20
- if (xvfb.pid !== undefined) {
21
- try {
22
- process.kill(xvfb.pid, "SIGTERM");
23
- } catch {}
24
- }
25
- };
26
-
27
- process.on("exit", killXvfb);
28
-
29
- process.on("SIGTERM", () => {
30
- killXvfb();
31
- process.exit(143);
32
- });
33
-
34
- process.on("SIGINT", () => {
35
- killXvfb();
36
- process.exit(130);
37
- });
38
-
39
- const waitForDisplay = async (timeout = 5000): Promise<void> => {
40
- const start = Date.now();
41
- while (Date.now() - start < timeout) {
42
- if (existsSync(socketPath)) {
43
- return;
44
- }
45
- await new Promise((resolve) => setTimeout(resolve, 50));
46
- }
47
- throw new Error(`Xvfb display :${display} did not become available within ${timeout}ms`);
48
- };
49
-
50
- beforeAll(async () => {
51
- await waitForDisplay();
52
- });