1import { assert, beforeEach, it, vi } from "vite-plus/test";
2
3const { execFileMock, loadWindowsForegroundApiMock } = vi.hoisted(() => ({
4 execFileMock: vi.fn(),
5 loadWindowsForegroundApiMock: vi.fn(),
6}));
7
8vi.mock("node:child_process", () => ({ execFile: execFileMock }));
9vi.mock("../electron/WindowsForeground.ts", () => ({
10 loadWindowsForegroundApi: loadWindowsForegroundApiMock,
11}));
12
13import { activeWindow } from "./ActiveWindow.ts";
14
15beforeEach(() => {
16 execFileMock.mockReset();
17 loadWindowsForegroundApiMock.mockReset();
18});
19
20function stubMacLookup(stdout: string) {
21 execFileMock.mockImplementation(
22 (
23 _file: string,
24 _args: ReadonlyArray<string>,
25 _options: unknown,
26 callback: (error: Error | null, stdout: string) => void,
27 ) => callback(null, stdout),
28 );
29}
30
31it("parses the frontmost macOS window from the osascript lookup", async () => {
32 stubMacLookup(
33 JSON.stringify({
34 id: 42,
35 title: "main.ts",
36 bounds: { x: 10, y: 20, width: 800, height: 600 },
37 owner: {
38 name: "Editor",
39 processId: 123,
40 path: "/Applications/Editor.app",
41 bundleId: "com.example.editor",
42 },
43 }) + "\n",
44 );
45
46 const window = await activeWindow("darwin");
47
48 assert.deepEqual(window, {
49 platform: "macos",
50 id: 42,
51 title: "main.ts",
52 bounds: { x: 10, y: 20, width: 800, height: 600 },
53 owner: {
54 name: "Editor",
55 processId: 123,
56 path: "/Applications/Editor.app",
57 bundleId: "com.example.editor",
58 },
59 });
60 const [file, args] = execFileMock.mock.calls[0]!;
61 assert.strictEqual(file, "/usr/bin/osascript");
62 assert.deepEqual(args.slice(0, 3), ["-l", "JavaScript", "-e"]);
63 assert.lengthOf(loadWindowsForegroundApiMock.mock.calls, 0);
64});
65
66it("omits an empty macOS bundle identifier", async () => {
67 stubMacLookup(
68 JSON.stringify({
69 id: 7,
70 title: "",
71 bounds: { x: 0, y: 0, width: 1, height: 1 },
72 owner: { name: "cli", processId: 9, path: "", bundleId: "" },
73 }),
74 );
75
76 const window = await activeWindow("darwin");
77
78 assert.deepEqual(window?.owner, { name: "cli", processId: 9, path: "" });
79});
80
81it("resolves undefined when macOS has no frontmost window", async () => {
82 stubMacLookup("\n");
83
84 assert.isUndefined(await activeWindow("darwin"));
85});
86
87it("composes the Windows foreground window from Win32 calls", async () => {
88 const api = {
89 getForegroundWindow: vi.fn(() => 0x1_f123_4567n),
90 getWindowRect: vi.fn(() => ({ x: 10, y: 20, width: 800, height: 600 })),
91 getWindowThreadAndProcessId: vi.fn(() => ({ threadId: 5, processId: 123 })),
92 getProcessImagePath: vi.fn(() => "C:\\Program Files\\Editor\\editor.exe"),
93 getWindowText: vi.fn(() => "main.ts - Editor"),
94 };
95 loadWindowsForegroundApiMock.mockResolvedValue(api);
96
97 const window = await activeWindow("win32");
98
99 assert.deepEqual(window, {
100 platform: "windows",
101 id: 0x1_f123_4567,
102 title: "main.ts - Editor",
103 bounds: { x: 10, y: 20, width: 800, height: 600 },
104 owner: { name: "editor.exe", processId: 123, path: "C:\\Program Files\\Editor\\editor.exe" },
105 });
106 assert.deepEqual(api.getWindowRect.mock.calls, [[0x1_f123_4567n]]);
107 assert.deepEqual(api.getProcessImagePath.mock.calls, [[123]]);
108 assert.lengthOf(execFileMock.mock.calls, 0);
109});
110
111it("resolves undefined when Windows reports no foreground window", async () => {
112 const api = {
113 getForegroundWindow: vi.fn(() => 0n),
114 getWindowRect: vi.fn(),
115 getWindowThreadAndProcessId: vi.fn(),
116 getProcessImagePath: vi.fn(),
117 getWindowText: vi.fn(),
118 };
119 loadWindowsForegroundApiMock.mockResolvedValue(api);
120
121 assert.isUndefined(await activeWindow("win32"));
122 assert.lengthOf(api.getWindowRect.mock.calls, 0);
123});
124
125it("resolves undefined on other platforms without querying the OS", async () => {
126 assert.isUndefined(await activeWindow("linux"));
127 assert.lengthOf(execFileMock.mock.calls, 0);
128 assert.lengthOf(loadWindowsForegroundApiMock.mock.calls, 0);
129});