Files
paseo/packages/desktop/src/features/browser-capture.test.ts
T
paseo-bot[bot] 5de45e2086 fix(desktop): capture the annotated element when the app is zoomed (#5703)
The browser pane measures the selected element in page CSS pixels, but
capturePage takes device-independent pixels. Paseo's zoom carries into the
in-app browser, so at any zoom other than 100% the screenshot attached to
an annotation was offset and undersized. Scale the region by the guest's
zoom factor before capturing.

Closes #5690

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-09-29 20:08:05 +02:00

88 lines
2.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
createBrowserCaptureService,
type BrowserCaptureGuest,
type BrowserCaptureImage,
} from "./browser-capture.js";
function image(dataUrl = "data:image/png;base64,capture"): BrowserCaptureImage {
return { isEmpty: () => false, toDataURL: () => dataUrl };
}
function harness(guest: BrowserCaptureGuest | null = null) {
const clipboard = { write: vi.fn(async () => undefined) };
const decodeImage = vi.fn(() => image("data:image/png;base64,clipboard"));
const warn = vi.fn();
return {
clipboard,
decodeImage,
warn,
service: createBrowserCaptureService({
findGuest: () => guest,
decodeImage,
clipboard,
warn,
}),
};
}
describe("browser capture service", () => {
it("validates and rounds guest-relative bounds before capture", async () => {
const capturePage = vi.fn(async () => image());
const { service } = harness({ isDestroyed: () => false, getZoomFactor: () => 1, capturePage });
await expect(
service.capture({
browserId: "browser-1",
hostWebContentsId: 42,
rect: { x: -2.4, y: 8.6, width: 20.2, height: 10.8 },
}),
).resolves.toBe("data:image/png;base64,capture");
expect(capturePage).toHaveBeenCalledWith({ x: 0, y: 9, width: 20, height: 11 });
});
it("captures the selected element when the page is zoomed", async () => {
const capturePage = vi.fn(async () => image());
const { service } = harness({
isDestroyed: () => false,
getZoomFactor: () => 1.2,
capturePage,
});
await service.capture({
browserId: "browser-1",
hostWebContentsId: 42,
rect: { x: 300, y: 200, width: 120, height: 60 },
});
expect(capturePage).toHaveBeenCalledWith({ x: 360, y: 240, width: 144, height: 72 });
});
it("rejects invalid or unavailable captures without touching the guest", async () => {
const capturePage = vi.fn(async () => image());
const { service } = harness({ isDestroyed: () => false, getZoomFactor: () => 1, capturePage });
await expect(
service.capture({ browserId: "browser-1", hostWebContentsId: 42, rect: { width: 0 } }),
).resolves.toBeNull();
expect(capturePage).not.toHaveBeenCalled();
});
it("writes text and a decoded image to the clipboard atomically", async () => {
const { service, clipboard } = harness();
await expect(
service.copy({ text: "button", imageDataUrl: "data:image/png;base64,value" }),
).resolves.toBe(true);
expect(clipboard.write).toHaveBeenCalledWith({
text: "button",
image: expect.any(Object),
});
});
it("keeps text-only clipboard writes", async () => {
const { service, clipboard } = harness();
await expect(service.copy({ text: "button" })).resolves.toBe(true);
expect(clipboard.write).toHaveBeenCalledWith({ text: "button", image: null });
});
});