123456789101112import { beforeEach, describe, expect, it } from "vitest";1314import type { DetectedField } from "./detect";15import { resolveAutofillInteractionTargets } from "./interaction";16import { makeInput } from "./_setup";1718beforeEach(() => {19 document.body.innerHTML = "";20});2122function field(type: DetectedField["type"], el: HTMLInputElement): DetectedField {23 return { type, el };24}2526describe("resolveAutofillInteractionTargets", () => {27 28 29 it("T-IT01: standard form → {usernameEl, passwordEl} and shouldOpen", () => {30 const u = makeInput({ type: "text" });31 const p = makeInput({ type: "password" });32 const targets = resolveAutofillInteractionTargets(p, [field("username", u), field("password", p)]);33 expect(targets.usernameEl).toBe(u);34 expect(targets.passwordEl).toBe(p);35 expect(targets.emailEl).toBeNull();36 expect(targets.shouldOpen).toBe(true);37 });3839 40 41 42 43 it("T-IT02: email-first form → email becomes usernameEl, passwordEl null", () => {44 const e = makeInput({ type: "email" });45 const targets = resolveAutofillInteractionTargets(e, [field("email", e)]);46 expect(targets.usernameEl).toBe(e);47 expect(targets.passwordEl).toBeNull();48 expect(targets.emailEl).toBeNull();49 50 51 52 });5354 55 it("T-IT02b: distinct username + email → emailEl is tracked alongside usernameEl", () => {56 const u = makeInput({ type: "text", autocomplete: "username" });57 const e = makeInput({ type: "email", autocomplete: "email" });58 const p = makeInput({ type: "password" });59 const targets = resolveAutofillInteractionTargets(60 u,61 [field("username", u), field("email", e), field("password", p)]62 );63 expect(targets.usernameEl).toBe(u);64 expect(targets.emailEl).toBe(e);65 expect(targets.passwordEl).toBe(p);66 });6768 69 70 it("T-IT03: password-only form → passwordEl set, usernameEl null, shouldOpen", () => {71 const p = makeInput({ type: "password", autocomplete: "current-password" });72 const targets = resolveAutofillInteractionTargets(p, [field("password", p)]);73 expect(targets.passwordEl).toBe(p);74 expect(targets.usernameEl).toBeNull();75 expect(targets.shouldOpen).toBe(true);76 });7778 79 it("T-IT04: empty field set + passive → shouldOpen false", () => {80 const anchor = makeInput({ type: "text" });81 const targets = resolveAutofillInteractionTargets(anchor, []);82 expect(targets.shouldOpen).toBe(false);83 expect(targets.usernameEl).toBeNull();84 expect(targets.passwordEl).toBeNull();85 });8687 88 89 90 91 it("T-IT05: explicit summon on a bare password anchor falls back to the anchor", () => {92 const anchor = makeInput({ type: "password" });93 const targets = resolveAutofillInteractionTargets(anchor, [], true);94 expect(targets.shouldOpen).toBe(true);95 expect(targets.passwordEl).toBe(anchor);96 });9798 it("T-IT05b: explicit summon on a bare username anchor falls back to the anchor", () => {99 const anchor = makeInput({ type: "text" });100 const targets = resolveAutofillInteractionTargets(anchor, [], true);101 expect(targets.shouldOpen).toBe(true);102 expect(targets.usernameEl).toBe(anchor);103 });104105 106 107 it("T-IT06: detached anchor + empty fields → shouldOpen false, no throw", () => {108 const detached = document.createElement("input");109 detached.type = "text"; 110 let targets!: ReturnType<typeof resolveAutofillInteractionTargets>;111 expect(() => { targets = resolveAutofillInteractionTargets(detached, []); }).not.toThrow();112 expect(targets.shouldOpen).toBe(false);113 });114});