123456789import { vi } from "vitest";1011const { injectIcon } = vi.hoisted(() => ({12 injectIcon: vi.fn<(field: HTMLInputElement) => () => void>(() => vi.fn()),13}));1415vi.mock("./inject", () => ({16 injectFieldIcon: injectIcon,17 showAutofillSelector: vi.fn(() => vi.fn()),18 showTOTPSelector: vi.fn(() => vi.fn()),19 showCardSelector: vi.fn(() => vi.fn()),20 showRememberSiteToast: vi.fn(),21 showPasskeyAvailable: vi.fn(),22 showPasskeyProGate: vi.fn(),23 showPasskeyRegistrationOffer: vi.fn(),24}));25vi.mock("./content-ml-detector", () => ({26 contentMLDetector: { predictBatch: vi.fn(() => Promise.resolve([])) },27}));2829import { afterEach, beforeEach, describe, expect, it } from "vitest";30import "./_setup";31import { resetChromeMock, makeInput } from "./_setup";32import { scanTOTPFields } from "./index";3334beforeEach(() => {35 document.body.innerHTML = "";36 resetChromeMock();37 injectIcon.mockClear();38});3940afterEach(async () => {41 42 43 await new Promise((r) => setTimeout(r, 0));44});4546describe("split-OTP group icon injection (no maxlength)", () => {47 48 49 50 51 it("six one-time-code boxes (no maxlength) → one icon on box[0]", () => {52 const f = document.createElement("form");53 document.body.appendChild(f);54 const boxes: HTMLInputElement[] = [];55 for (let i = 0; i < 6; i++) {56 boxes.push(57 makeInput({ type: "text", autocomplete: "one-time-code", name: "otp" }, f)58 );59 }6061 scanTOTPFields();6263 expect(injectIcon).toHaveBeenCalledTimes(1);64 expect(injectIcon.mock.calls[0]![0]).toBe(boxes[0]);65 });6667 68 69 it("rescan is idempotent — still one icon", () => {70 const f = document.createElement("form");71 document.body.appendChild(f);72 const boxes: HTMLInputElement[] = [];73 for (let i = 0; i < 6; i++) {74 boxes.push(75 makeInput({ type: "text", autocomplete: "one-time-code", name: "otp" }, f)76 );77 }7879 scanTOTPFields();80 scanTOTPFields();8182 expect(injectIcon).toHaveBeenCalledTimes(1);83 expect(injectIcon.mock.calls[0]![0]).toBe(boxes[0]);84 });8586 87 88 it("a lone one-time-code field → one icon on itself", () => {89 const f = document.createElement("form");90 document.body.appendChild(f);91 const lone = makeInput(92 { type: "text", autocomplete: "one-time-code", name: "code" },93 f94 );9596 scanTOTPFields();9798 expect(injectIcon).toHaveBeenCalledTimes(1);99 expect(injectIcon.mock.calls[0]![0]).toBe(lone);100 });101});