60 lines
1.9 KiB
TypeScript
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();
|
|
});
|
|
});
|