123import { beforeEach, describe, expect, it } from "vitest";45import {6 detectCardForm,7 detectLoginFields,8 getPageDomain,9 hasPaymentProcessorIframe,10 isLoginForm,11 isSignupForm,12 isTOTPField,13} from "./detect";1415describe("content detect", () => {16 beforeEach(() => {17 document.body.innerHTML = "";18 });1920 function visibleInput(21 attributes: Record<string, string> = {},22 tagName: "input" = "input"23 ): HTMLInputElement {24 const input = document.createElement(tagName);25 Object.entries(attributes).forEach(([key, value]) => input.setAttribute(key, value));26 Object.defineProperty(input, "getBoundingClientRect", {27 configurable: true,28 value: () => ({29 width: 100,30 height: 20,31 top: 0,32 left: 0,33 right: 100,34 bottom: 20,35 x: 0,36 y: 0,37 toJSON: () => ({}),38 }),39 });40 document.body.appendChild(input);41 return input as HTMLInputElement;42 }4344 it("isLoginForm returns true for a username/password pair", () => {45 const username = visibleInput({ type: "email", name: "email" });46 const password = visibleInput({ type: "password", autocomplete: "current-password" });4748 expect(49 isLoginForm([50 { type: "username", el: username },51 { type: "password", el: password },52 ])53 ).toBe(true);54 });5556 it("isLoginForm returns false for fields without a password", () => {57 const username = visibleInput({ type: "text", name: "username" });5859 expect(isLoginForm([{ type: "username", el: username }])).toBe(false);60 });6162 it("isSignupForm returns true for confirm-password flows", () => {63 const password = visibleInput({ type: "password", name: "password" });64 const confirm = visibleInput({ type: "password", name: "confirmPassword" });6566 expect(67 isSignupForm([68 { type: "password", el: password },69 { type: "password", el: confirm },70 ])71 ).toBe(true);72 });7374 it("isSignupForm returns false for a single current-password field", () => {75 const password = visibleInput({ type: "password", autocomplete: "current-password" });7677 expect(isSignupForm([{ type: "password", el: password }])).toBe(false);78 });7980 it("detectLoginFields matches email and username-style inputs but not tag-name fields", () => {81 const email = visibleInput({ type: "email", name: "email" });82 const username = visibleInput({ type: "text", name: "login-id" });83 const tagName = visibleInput({ type: "text", name: "tag-name" });84 const password = visibleInput({ type: "password", autocomplete: "current-password" });85 const hidden = visibleInput({ type: "hidden", name: "username" });8687 const fields = detectLoginFields(document);8889 expect(fields.some((field) => field.el === email && field.type === "username")).toBe(true);90 expect(fields.some((field) => field.el === username && field.type === "username")).toBe(true);91 expect(fields.some((field) => field.el === password && field.type === "password")).toBe(true);92 expect(fields.some((field) => field.el === tagName)).toBe(false);93 expect(fields.some((field) => field.el === hidden)).toBe(false);94 });9596 it("getPageDomain falls back to the current hostname when no registrable domain exists", () => {97 expect(getPageDomain()).toBe(window.location.hostname);98 });99100 it("isTOTPField detects one-time-code fields without matching generic code words", () => {101 const oneTimeCode = visibleInput({ autocomplete: "one-time-code" });102 const otpName = visibleInput({ name: "otp" });103 const verification = visibleInput({ placeholder: "Verification code" });104 const zipCode = visibleInput({ name: "zipcode" });105106 expect(isTOTPField(oneTimeCode)).toBe(true);107 expect(isTOTPField(otpName)).toBe(true);108 expect(isTOTPField(verification)).toBe(true);109 expect(isTOTPField(zipCode)).toBe(false);110 });111112 it("isTOTPField detects an adjacent 2FA label", () => {113 const label = document.createElement("label");114 label.textContent = "2FA Code";115 document.body.appendChild(label);116 const input = visibleInput({ placeholder: "If enabled" });117118 expect(isTOTPField(input)).toBe(true);119 });120});121122describe("card form detection", () => {123 beforeEach(() => {124 document.body.innerHTML = "";125 });126127 it("detectCardForm — finds number field by autocomplete attribute", () => {128 document.body.innerHTML = '<input autocomplete="cc-number" /><input autocomplete="cc-exp-month" /><input autocomplete="cc-exp-year" /><input autocomplete="cc-csc" />';129 const result = detectCardForm(document);130 expect(result).not.toBeNull();131 expect(result!.number).toBeTruthy();132 expect(result!.expMonth).toBeTruthy();133 });134135 it("detectCardForm — finds fields by name/id heuristics when no autocomplete", () => {136 document.body.innerHTML = '<input name="cardnum" /><input name="expiry" /><input name="cvv" />';137 const result = detectCardForm(document);138 expect(result).not.toBeNull();139 expect(result!.number).toBeTruthy();140 });141142 it("detectCardForm — returns null when no card number field found", () => {143 document.body.innerHTML = '<input type="text" name="username" />';144 expect(detectCardForm(document)).toBeNull();145 });146147 it("hasPaymentProcessorIframe — returns true when Stripe iframe present", () => {148 document.body.innerHTML = '<iframe src="https://js.stripe.com/v3/"></iframe>';149 expect(hasPaymentProcessorIframe()).toBe(true);150 });151152 it("hasPaymentProcessorIframe — returns false on standard checkout page", () => {153 document.body.innerHTML = '<input name="cardnum" />';154 expect(hasPaymentProcessorIframe()).toBe(false);155 });156});