import { afterEach, describe, expect, it, vi } from "vitest"; import { scanQrFromCamera } from "../../src/qr/camera"; describe("opt-in camera QR scanning", () => { afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); }); it("requests only video and stops every track after a match", async () => { const stop = vi.fn(); const getUserMedia = vi.fn().mockResolvedValue({ getTracks: () => [{ stop }], }); Object.defineProperty(navigator, "mediaDevices", { configurable: true, value: { getUserMedia }, }); vi.stubGlobal("isSecureContext", true); vi.stubGlobal( "BarcodeDetector", class { detect = vi .fn() .mockResolvedValue([{ rawValue: "otpauth://totp/Test" }]); }, ); const video = document.createElement("video"); vi.spyOn(video, "play").mockResolvedValue(); vi.spyOn(video, "pause").mockImplementation(() => undefined); Object.defineProperty(video, "readyState", { configurable: true, value: HTMLMediaElement.HAVE_CURRENT_DATA, }); await expect( scanQrFromCamera(video, new AbortController().signal), ).resolves.toBe("otpauth://totp/Test"); expect(getUserMedia).toHaveBeenCalledWith( expect.objectContaining({ audio: false, video: expect.any(Object) }), ); expect(stop).toHaveBeenCalledOnce(); expect(video.srcObject).toBeNull(); }); it("does not request permission after prior cancellation", async () => { const getUserMedia = vi.fn(); Object.defineProperty(navigator, "mediaDevices", { configurable: true, value: { getUserMedia }, }); vi.stubGlobal("isSecureContext", true); const controller = new AbortController(); controller.abort(); await expect( scanQrFromCamera(document.createElement("video"), controller.signal), ).rejects.toMatchObject({ name: "AbortError" }); expect(getUserMedia).not.toHaveBeenCalled(); }); });