1234567891011121314151617181920import "./_setup"; 21import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";22import { showAutofillSelector } from "./inject";23import { makeInput } from "./_setup";2425const ENTRIES = [26 { id: "e1", title: "Acme", username: "[email protected]" },27 { id: "e2", title: "Acme Admin", username: "[email protected]" },28];29303132function selectorHost(): HTMLElement | null {33 return document.body.querySelector<HTMLElement>('div[style*="2147483647"]');34}3536beforeEach(() => {37 document.body.innerHTML = "";38});39afterEach(() => {40 document.body.innerHTML = "";41});4243describe("T-I01: mount + dismiss (the observable contract)", () => {44 45 46 it("T-I01: showAutofillSelector mounts a host and returns a dismiss fn", () => {47 const anchor = makeInput({ type: "password" });48 const dismiss = showAutofillSelector(anchor, ENTRIES, vi.fn());49 expect(typeof dismiss).toBe("function");50 expect(selectorHost()).not.toBeNull();51 dismiss();52 });5354 55 56 57 it("T-I05/T-I06 (teardown half): the returned dismiss removes the host", async () => {58 const anchor = makeInput({ type: "password" });59 const dismiss = showAutofillSelector(anchor, ENTRIES, vi.fn());60 expect(selectorHost()).not.toBeNull();61 dismiss();62 await new Promise((r) => setTimeout(r, 180));63 expect(selectorHost()).toBeNull();64 });6566 67 it("T-I01b: locked-mode open also mounts a host", () => {68 const anchor = makeInput({ type: "password" });69 const dismiss = showAutofillSelector(anchor, [], vi.fn(), { locked: true });70 expect(selectorHost()).not.toBeNull();71 dismiss();72 });7374 75 76 77 it("T-I01c: each open creates its own host (caller owns single-instance policy)", () => {78 const anchor = makeInput({ type: "password" });79 const d1 = showAutofillSelector(anchor, ENTRIES, vi.fn());80 const d2 = showAutofillSelector(anchor, ENTRIES, vi.fn());81 expect(document.body.querySelectorAll('div[style*="2147483647"]').length).toBe(2);82 d1();83 d2();84 });85});868788899091describe("T-I02–T-I16: interior behavior (closed shadow → e2e)", () => {92 it.todo("T-I02: ArrowDown moves selection to the next entry");93 it.todo("T-I03: ArrowUp wraps from first to last entry");94 it.todo("T-I04: Enter selects the focused entry, fills, and closes");95 it.todo("T-I05: Escape closes the selector and fires the dismissal lock");96 it.todo("T-I06: click outside closes the selector and fires the dismissal lock");97 it.todo("T-I07: typing in the search box filters entries by title/domain");98 it.todo("T-I08: search lazily fires GET_ENTRY_FOR_FILL only on a user gesture (MV-2)");99 it.todo("T-I09: empty search results show the 'no matches' state");100 it.todo("T-I10: selector repositions on window scroll (stays anchored)");101 it.todo("T-I11: selector repositions on window resize");102 it.todo("T-I12: selector renders above the field when space below is insufficient");103 it.todo("T-I13: an entry with TOTP shows a live-countdown chip");104 it.todo("T-I14: an entry without TOTP shows no chip");105 it.todo("T-I15: keyboard nav is trapped — Tab cycles entries, does not escape to page");106 it.todo("T-I16: isTrusted — synthetic keyboard events do not trigger fill");107});