Files
auth-tools/tests/qr/camera.test.ts
T

60 lines
1.9 KiB
TypeScript

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();
});
});