mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import { EventEmitter } from "node:events";
|
|
import { readFileSync } from "node:fs";
|
|
import { PassThrough } from "node:stream";
|
|
import { fileURLToPath } from "node:url";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { setWindowsCaptureOutputBuffer, setWindowsCaptureTargetPath } from "../state";
|
|
import { waitForWindowsCaptureStop } from "./windows";
|
|
|
|
const windowsCaptureSource = readFileSync(
|
|
fileURLToPath(new URL("../../native/wgc-capture/src/wgc_session.cpp", import.meta.url)),
|
|
"utf8",
|
|
);
|
|
|
|
vi.mock("electron", () => ({
|
|
app: {
|
|
getPath: () => "C:\\RecordlyTest",
|
|
},
|
|
BrowserWindow: {
|
|
getAllWindows: () => [],
|
|
},
|
|
}));
|
|
|
|
class FakeCaptureProcess extends EventEmitter {
|
|
stdout = new PassThrough();
|
|
stderr = new PassThrough();
|
|
stdin = new PassThrough();
|
|
killed = false;
|
|
|
|
kill = vi.fn(() => {
|
|
this.killed = true;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
describe("waitForWindowsCaptureStop", () => {
|
|
beforeEach(() => {
|
|
setWindowsCaptureOutputBuffer("");
|
|
setWindowsCaptureTargetPath(null);
|
|
});
|
|
|
|
it("resolves the helper output path when the process closes cleanly", async () => {
|
|
const proc = new FakeCaptureProcess();
|
|
setWindowsCaptureOutputBuffer("Recording stopped. Output path: C:\\Recordly\\capture.mp4");
|
|
|
|
const stopped = waitForWindowsCaptureStop(
|
|
proc as unknown as Parameters<typeof waitForWindowsCaptureStop>[0],
|
|
1000,
|
|
);
|
|
proc.emit("close", 0);
|
|
|
|
await expect(stopped).resolves.toBe("C:\\Recordly\\capture.mp4");
|
|
expect(proc.kill).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("resolves the fallback target path when the helper closes cleanly without output path", async () => {
|
|
const proc = new FakeCaptureProcess();
|
|
setWindowsCaptureOutputBuffer("Recording stopped without output path");
|
|
setWindowsCaptureTargetPath("C:\\Recordly\\fallback.mp4");
|
|
|
|
const stopped = waitForWindowsCaptureStop(
|
|
proc as unknown as Parameters<typeof waitForWindowsCaptureStop>[0],
|
|
1000,
|
|
);
|
|
proc.emit("close", 0);
|
|
|
|
await expect(stopped).resolves.toBe("C:\\Recordly\\fallback.mp4");
|
|
expect(proc.kill).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects with helper output when the helper exits with a non-zero code", async () => {
|
|
const proc = new FakeCaptureProcess();
|
|
setWindowsCaptureOutputBuffer("Encoder error: insufficient memory");
|
|
|
|
const stopped = waitForWindowsCaptureStop(
|
|
proc as unknown as Parameters<typeof waitForWindowsCaptureStop>[0],
|
|
1000,
|
|
);
|
|
proc.emit("close", 1);
|
|
|
|
await expect(stopped).rejects.toThrow("Encoder error: insufficient memory");
|
|
expect(proc.kill).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects when the helper emits an error", async () => {
|
|
const proc = new FakeCaptureProcess();
|
|
const error = new Error("spawn failed");
|
|
|
|
const stopped = waitForWindowsCaptureStop(
|
|
proc as unknown as Parameters<typeof waitForWindowsCaptureStop>[0],
|
|
1000,
|
|
);
|
|
proc.emit("error", error);
|
|
|
|
await expect(stopped).rejects.toBe(error);
|
|
expect(proc.kill).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("kills the helper and rejects when stop never completes", async () => {
|
|
const proc = new FakeCaptureProcess();
|
|
|
|
await expect(
|
|
waitForWindowsCaptureStop(
|
|
proc as unknown as Parameters<typeof waitForWindowsCaptureStop>[0],
|
|
5,
|
|
),
|
|
).rejects.toThrow("Timed out waiting for native Windows capture to stop");
|
|
expect(proc.kill).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("native Windows window capture", () => {
|
|
it("crops monitor frames to the selected window bounds", () => {
|
|
expect(windowsCaptureSource).not.toContain("CreateForWindow(");
|
|
expect(windowsCaptureSource).toContain("DwmGetWindowAttribute(");
|
|
expect(windowsCaptureSource).toContain("CopySubresourceRegion(cropTexture_");
|
|
});
|
|
|
|
it("maps desktop bounds into WGC texture coordinates and keeps the encoder size fixed", () => {
|
|
expect(windowsCaptureSource).toContain("normalized * framePoolWidth_");
|
|
expect(windowsCaptureSource).toContain(
|
|
"d3dDevice_->CreateTexture2D(&desc, nullptr, &resizedTexture)",
|
|
);
|
|
expect(windowsCaptureSource).not.toContain("nextWidth != captureWidth_");
|
|
});
|
|
|
|
it("does not expose desktop pixels when the selected window shrinks", () => {
|
|
expect(windowsCaptureSource).toContain("(std::min)(mappedWidth");
|
|
expect(windowsCaptureSource).toContain("ClearRenderTargetView");
|
|
});
|
|
});
|