detect.test.ts

5.3 KB · raw

1// @vitest-environment jsdom23import { 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  });111});112113describe("card form detection", () => {114  beforeEach(() => {115    document.body.innerHTML = "";116  });117118  it("detectCardForm — finds number field by autocomplete attribute", () => {119    document.body.innerHTML = '<input autocomplete="cc-number" /><input autocomplete="cc-exp-month" /><input autocomplete="cc-exp-year" /><input autocomplete="cc-csc" />';120    const result = detectCardForm(document);121    expect(result).not.toBeNull();122    expect(result!.number).toBeTruthy();123    expect(result!.expMonth).toBeTruthy();124  });125126  it("detectCardForm — finds fields by name/id heuristics when no autocomplete", () => {127    document.body.innerHTML = '<input name="cardnum" /><input name="expiry" /><input name="cvv" />';128    const result = detectCardForm(document);129    expect(result).not.toBeNull();130    expect(result!.number).toBeTruthy();131  });132133  it("detectCardForm — returns null when no card number field found", () => {134    document.body.innerHTML = '<input type="text" name="username" />';135    expect(detectCardForm(document)).toBeNull();136  });137138  it("hasPaymentProcessorIframe — returns true when Stripe iframe present", () => {139    document.body.innerHTML = '<iframe src="https://js.stripe.com/v3/"></iframe>';140    expect(hasPaymentProcessorIframe()).toBe(true);141  });142143  it("hasPaymentProcessorIframe — returns false on standard checkout page", () => {144    document.body.innerHTML = '<input name="cardnum" />';145    expect(hasPaymentProcessorIframe()).toBe(false);146  });147});