Files
Recordly/electron/ipc/captureSelection.test.ts
T

130 lines
4.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
read: vi.fn(),
write: vi.fn(),
windows: vi.fn(),
sources: vi.fn(),
current: null as unknown,
displays: [{ id: 1, bounds: { x: 0, y: 0, width: 1440, height: 1000 } }],
}));
vi.mock("../appSettingsStore", () => ({
readAppSetting: mocks.read,
writeAppSetting: mocks.write,
}));
vi.mock("./capturePickerSources", () => ({ listCapturePickerWindows: mocks.windows }));
vi.mock("./register/sources", () => ({ getDesktopSources: mocks.sources }));
vi.mock("./state", () => ({
get selectedSource() {
return mocks.current;
},
setSelectedSource: (value: unknown) => {
mocks.current = value;
},
}));
vi.mock("./utils", () => ({ getScreen: () => ({ getAllDisplays: () => mocks.displays }) }));
import { getSessionCaptureSource, validateCaptureSource } from "./captureSelection";
const platform = process.platform;
const source = { id: "window:101:0", name: "Document", windowTitle: "Document", appName: "Notes" };
beforeEach(() => {
vi.clearAllMocks();
Object.defineProperty(process, "platform", { value: "darwin" });
mocks.current = null;
mocks.read.mockReturnValue({ platform: "darwin", source });
mocks.windows.mockResolvedValue([
{ id: source.id, appName: "Notes", title: "Document", x: 0, y: 0, width: 100, height: 100 },
]);
});
afterEach(() => {
Object.defineProperty(process, "platform", { value: platform });
vi.unstubAllEnvs();
});
describe("session capture selection", () => {
it("starts without a capture source even when the previous window is still available", async () => {
expect(await getSessionCaptureSource()).toBeNull();
expect(mocks.current).toBeNull();
});
it("ignores persisted selection without enumerating capture sources on startup", () => {
expect(getSessionCaptureSource()).toBeNull();
expect(mocks.read).not.toHaveBeenCalled();
expect(mocks.windows).not.toHaveBeenCalled();
expect(mocks.sources).not.toHaveBeenCalled();
});
it("rejects a recycled window ID with a different title", async () => {
mocks.windows.mockResolvedValue([
{ id: source.id, title: "Another document", appName: "Notes" },
]);
expect(await validateCaptureSource(source)).toBeNull();
});
it("rejects disconnected screens and areas that would be resized", async () => {
expect(
await validateCaptureSource({ id: "screen:2:0", name: "Gone", display_id: "2" }),
).toBeNull();
expect(
await validateCaptureSource({
id: "area:1",
name: "Area",
display_id: "1",
area: { x: 1300, y: 0, width: 400, height: 300 },
}),
).toBeNull();
});
it("does not broaden window capture when detection fails", async () => {
mocks.windows.mockRejectedValue(new Error("permission denied"));
await expect(validateCaptureSource(source)).rejects.toThrow("permission denied");
expect(mocks.sources).not.toHaveBeenCalled();
});
it("keeps a selection only within the current app session", () => {
mocks.current = source;
expect(getSessionCaptureSource()).toEqual(source);
// A fresh main process initializes its in-memory selection to null.
mocks.current = null;
expect(getSessionCaptureSource()).toBeNull();
expect(mocks.read).not.toHaveBeenCalled();
expect(mocks.write).not.toHaveBeenCalled();
});
});
describe("Linux selection", () => {
beforeEach(() => {
Object.defineProperty(process, "platform", { value: "linux" });
vi.stubEnv("XDG_SESSION_TYPE", "x11");
mocks.sources.mockResolvedValue([{ ...source, display_id: "1" }]);
});
it("validates X11 windows using live sources without geometry helpers", async () => {
expect(await validateCaptureSource(source)).toMatchObject({
id: source.id,
name: source.name,
windowTitle: source.windowTitle,
display_id: "1",
sourceType: "window",
});
expect(mocks.windows).not.toHaveBeenCalled();
expect(mocks.sources).toHaveBeenCalledWith(
{ types: ["window"], thumbnailSize: { width: 0, height: 0 } },
{ strict: true },
);
});
it("rejects missing and recycled X11 windows without substitution", async () => {
mocks.sources.mockResolvedValue([{ id: source.id, name: "Different document" }]);
expect(await validateCaptureSource(source)).toBeNull();
mocks.sources.mockResolvedValue([{ ...source, id: "window:102:0" }]);
expect(await validateCaptureSource(source)).toBeNull();
});
it("starts Wayland unselected without opening the portal", () => {
vi.stubEnv("XDG_SESSION_TYPE", "wayland");
expect(getSessionCaptureSource()).toBeNull();
expect(mocks.read).not.toHaveBeenCalled();
expect(mocks.sources).not.toHaveBeenCalled();
expect(mocks.windows).not.toHaveBeenCalled();
});
it("rejects old X11 IDs on Wayland without enumerating portal sources", async () => {
vi.stubEnv("XDG_SESSION_TYPE", "wayland");
expect(await validateCaptureSource(source)).toBeNull();
expect(
await validateCaptureSource({ id: "screen:1:0", name: "Screen", display_id: "1" }),
).toBeNull();
expect(mocks.sources).not.toHaveBeenCalled();
expect(mocks.windows).not.toHaveBeenCalled();
});
});