123import { beforeEach, describe, expect, it, vi } from "vitest";45import { fillFields, fillCardForm, formatCardNumber } from "./fill";67describe("content fill", () => {8 beforeEach(() => {9 document.body.innerHTML = "";10 });1112 function makeVisibleInput(type: string): HTMLInputElement {13 const input = document.createElement("input");14 input.type = type;15 Object.defineProperty(input, "getBoundingClientRect", {16 configurable: true,17 value: () => ({18 width: 100,19 height: 20,20 top: 0,21 left: 0,22 right: 100,23 bottom: 20,24 x: 0,25 y: 0,26 toJSON: () => ({}),27 }),28 });29 document.body.appendChild(input);30 return input;31 }3233 it("fillFields writes username and password into matching inputs", () => {34 const username = makeVisibleInput("text");35 const password = makeVisibleInput("password");3637 fillFields(username, password, "june", "hunter2");3839 expect(username.value).toBe("june");40 expect(password.value).toBe("hunter2");41 });4243 it("fillFields refuses to fill when the form action domain does not match", () => {44 const form = document.createElement("form");45 form.action = "https://evil.com/login";46 const username = makeVisibleInput("text");47 const password = makeVisibleInput("password");48 form.append(username, password);49 document.body.appendChild(form);5051 fillFields(username, password, "june", "hunter2", form, "github.com");5253 expect(username.value).toBe("");54 expect(password.value).toBe("");55 });5657 58 59 it("fillFields refuses a plain-http form action (PB-26-008)", () => {60 const form = document.createElement("form");61 form.action = "http://github.com/login";62 const username = makeVisibleInput("text");63 const password = makeVisibleInput("password");64 form.append(username, password);65 document.body.appendChild(form);6667 fillFields(username, password, "june", "hunter2", form, "github.com");6869 expect(username.value).toBe("");70 expect(password.value).toBe("");71 });7273 it("fillFields allows an https form action on a subdomain of the expected domain (PB-26-008)", () => {74 const form = document.createElement("form");75 form.action = "https://auth.github.com/session";76 const username = makeVisibleInput("text");77 const password = makeVisibleInput("password");78 form.append(username, password);79 document.body.appendChild(form);8081 fillFields(username, password, "june", "hunter2", form, "github.com");8283 expect(username.value).toBe("june");84 expect(password.value).toBe("hunter2");85 });8687 it("fillFields still fills the username when the username field is hidden", () => {88 const username = makeVisibleInput("text");89 const password = makeVisibleInput("password");90 Object.defineProperty(username, "getBoundingClientRect", {91 value: () => ({92 width: 0,93 height: 0,94 top: 0,95 left: 0,96 right: 0,97 bottom: 0,98 x: 0,99 y: 0,100 toJSON: () => ({}),101 }),102 });103104 fillFields(username, password, "june", "hunter2");105106 expect(username.value).toBe("june");107 expect(password.value).toBe("hunter2");108 });109110 it("fillFields dispatches standard input/change events rather than custom password events", () => {111 const username = makeVisibleInput("text");112 const password = makeVisibleInput("password");113 const usernameDispatch = vi.spyOn(username, "dispatchEvent");114 const passwordDispatch = vi.spyOn(password, "dispatchEvent");115116 fillFields(username, password, "june", "hunter2");117118 expect(usernameDispatch.mock.calls.map(([event]) => event.type)).toEqual(["input", "change"]);119 expect(passwordDispatch.mock.calls.map(([event]) => event.type)).toEqual(["input", "change"]);120 expect(passwordDispatch.mock.calls.every(([event]) => !(event instanceof CustomEvent))).toBe(true);121 });122});123124describe("card fill", () => {125 beforeEach(() => {126 document.body.innerHTML = "";127 });128129 function makeInput(attrs: Record<string, string> = {}): HTMLInputElement {130 const input = document.createElement("input");131 for (const [k, v] of Object.entries(attrs)) input.setAttribute(k, v);132 133 134 Object.defineProperty(input, "getBoundingClientRect", {135 configurable: true,136 value: () => ({137 width: 100,138 height: 20,139 top: 0,140 left: 0,141 right: 100,142 bottom: 20,143 x: 0,144 y: 0,145 toJSON: () => ({}),146 }),147 });148 document.body.appendChild(input);149 return input;150 }151152 function makeHiddenInput(attrs: Record<string, string> = {}): HTMLInputElement {153 const input = makeInput(attrs);154 Object.defineProperty(input, "getBoundingClientRect", {155 configurable: true,156 value: () => ({157 width: 0,158 height: 0,159 top: 0,160 left: 0,161 right: 0,162 bottom: 0,163 x: 0,164 y: 0,165 toJSON: () => ({}),166 }),167 });168 return input;169 }170171 172 173 174 it("fillCardForm — skips hidden fields (PB-26-007)", () => {175 const number = makeHiddenInput({ type: "text" });176 const cvc = makeHiddenInput({ type: "text" });177 const name = makeInput({ type: "text" });178179 fillCardForm(180 { number, expMonth: null, expYear: null, expiryCombo: null, cvc, name },181 { cardNumber: "4111111111111111", expMonth: "05", expYear: "2028", cvc: "123", cardholderName: "Jane Smith" }182 );183184 expect(number.value).toBe("");185 expect(cvc.value).toBe("");186 expect(name.value).toBe("Jane Smith");187 });188189 it("fillCardForm — fills number, expiry, CVC, and name fields", () => {190 const number = makeInput({ type: "text" });191 const expMonth = makeInput({ type: "text" });192 const expYear = makeInput({ type: "text" });193 const cvc = makeInput({ type: "text" });194 const name = makeInput({ type: "text" });195196 fillCardForm(197 { number, expMonth, expYear, expiryCombo: null, cvc, name },198 { cardNumber: "4111111111111111", expMonth: "05", expYear: "2028", cvc: "123", cardholderName: "Jane Smith" }199 );200201 expect(number.value).toBe("4111 1111 1111 1111");202 expect(expMonth.value).toBe("05");203 expect(expYear.value).toBe("2028");204 expect(cvc.value).toBe("123");205 expect(name.value).toBe("Jane Smith");206 });207208 it("fillCardForm — handles combined expiry field (MM/YY)", () => {209 const number = makeInput({ type: "text" });210 const expiryCombo = makeInput({ type: "text" });211 const cvc = makeInput({ type: "text" });212213 fillCardForm(214 { number, expMonth: null, expYear: null, expiryCombo, cvc, name: null },215 { cardNumber: "5555555555554444", expMonth: "12", expYear: "2026", cvc: "456", cardholderName: "John Doe" }216 );217218 expect(expiryCombo.value).toBe("12/26");219 });220221 it("fillCardForm — dispatches input and change events for React compatibility", () => {222 const number = makeInput({ type: "text" });223 const numberDispatch = vi.spyOn(number, "dispatchEvent");224225 fillCardForm(226 { number, expMonth: null, expYear: null, expiryCombo: null, cvc: null, name: null },227 { cardNumber: "4111111111111111", expMonth: "05", expYear: "2028", cvc: "123", cardholderName: "Jane" }228 );229230 expect(numberDispatch.mock.calls.map(([event]) => event.type)).toEqual(["input", "change"]);231 });232233 it("formatCardNumber — formats Visa as 4-4-4-4", () => {234 expect(formatCardNumber("4111111111111111")).toBe("4111 1111 1111 1111");235 });236237 it("formatCardNumber — formats Amex as 4-6-5", () => {238 expect(formatCardNumber("378282246310005")).toBe("3782 822463 10005");239 });240});