12345678910111213import "./_setup";14import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";15import { setupSaveDetection } from "./save";16import { resetChromeMock, setMessageResponder, sentMessages, makeInput } from "./_setup";171819function bannerHost(): HTMLElement | null {20 return document.body.querySelector<HTMLElement>('div[style*="2147483647"]');21}22232425async function submitForm(fields: { user?: string; pass?: string; type?: string }): Promise<void> {26 const form = document.createElement("form");27 document.body.appendChild(form);28 if (fields.user !== undefined) {29 const u = makeInput({ type: "text", name: "username", autocomplete: "username" }, form);30 u.value = fields.user;31 }32 if (fields.pass !== undefined) {33 const p = makeInput({ type: fields.type ?? "password", name: "password" }, form);34 p.value = fields.pass;35 }36 form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));37 await new Promise((r) => setTimeout(r, 30)); 38}3940beforeAll(() => {41 setupSaveDetection(); 42});4344beforeEach(() => {45 document.body.innerHTML = "";46 resetChromeMock();47});48afterEach(() => {49 document.body.innerHTML = "";50});5152describe("T-SV01, T-SV03: banner mounts for new and changed credentials", () => {53 54 it("T-SV01: new credentials → save banner mounts", async () => {55 setMessageResponder((msg) => (msg.type === "AUTOFILL_QUERY" ? { ok: true, data: [] } : { ok: true, data: null }));56 await submitForm({ user: "alice", pass: "s3cret" });57 expect(bannerHost()).not.toBeNull();58 });5960 61 it("T-SV03: changed password for an existing entry → banner mounts (update)", async () => {62 setMessageResponder((msg) => {63 if (msg.type === "AUTOFILL_QUERY") return { ok: true, data: [{ id: "e1" }] };64 if (msg.type === "GET_ENTRY_FOR_FILL") return { ok: true, data: { id: "e1", title: "Acme", password: "old-password" } };65 return { ok: true, data: null };66 });67 await submitForm({ user: "alice", pass: "new-password" });68 expect(bannerHost()).not.toBeNull();69 });70});7172describe("T-SV02, T-SV04, T-SV05: no banner cases", () => {73 74 it("T-SV02: credentials already saved (password matches) → no banner", async () => {75 setMessageResponder((msg) => {76 if (msg.type === "AUTOFILL_QUERY") return { ok: true, data: [{ id: "e1" }] };77 if (msg.type === "GET_ENTRY_FOR_FILL") return { ok: true, data: { id: "e1", title: "Acme", password: "s3cret" } };78 return { ok: true, data: null };79 });80 await submitForm({ user: "alice", pass: "s3cret" });81 expect(bannerHost()).toBeNull();82 });8384 85 86 it("T-SV04: search form (no password) → no banner, no vault lookup", async () => {87 setMessageResponder(() => ({ ok: true, data: [] }));88 const form = document.createElement("form");89 document.body.appendChild(form);90 const q = makeInput({ type: "search", name: "q" }, form);91 q.value = "kittens";92 form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));93 await new Promise((r) => setTimeout(r, 30));94 expect(bannerHost()).toBeNull();95 expect(sentMessages.some((m) => m.type === "AUTOFILL_QUERY")).toBe(false);96 });9798 99 it("T-SV05: empty password → no banner, no vault lookup", async () => {100 setMessageResponder(() => ({ ok: true, data: [] }));101 await submitForm({ user: "alice", pass: "" });102 expect(bannerHost()).toBeNull();103 expect(sentMessages.some((m) => m.type === "AUTOFILL_QUERY")).toBe(false);104 });105});106107describe("T-SV10: domain-matched lookup (MV-1 surface)", () => {108 109 110 it("T-SV10: sends AUTOFILL_QUERY{domain} then GET_ENTRY_FOR_FILL{id}", async () => {111 setMessageResponder((msg) => {112 if (msg.type === "AUTOFILL_QUERY") return { ok: true, data: [{ id: "e1" }] };113 if (msg.type === "GET_ENTRY_FOR_FILL") return { ok: true, data: { id: "e1", title: "Acme", password: "old" } };114 return { ok: true, data: null };115 });116 await submitForm({ user: "alice", pass: "new" });117118 const query = sentMessages.find((m) => m.type === "AUTOFILL_QUERY");119 expect(query?.payload?.domain).toBeTruthy();120 const fetch = sentMessages.find((m) => m.type === "GET_ENTRY_FOR_FILL");121 expect(fetch?.payload?.id).toBe("e1");122 });123});