12345678910111213141516171819import { vi } from "vitest";202122232425export type MessageResponder = (msg: any) => any;2627let messageResponder: MessageResponder = (msg) => {28 29 30 switch (msg?.type) {31 case "GET_SETTINGS":32 return { ok: true, data: { accentColor: "#ff9776", theme: "dark" } };33 case "GET_VAULT_STATUS":34 return { ok: true, data: { unlocked: false } };35 default:36 return { ok: true, data: null };37 }38};394041export const sentMessages: any[] = [];4243export const messageListeners: Array<44 (msg: any, sender: any, sendResponse: (r?: any) => void) => unknown45> = [];464748export function setMessageResponder(fn: MessageResponder): void {49 messageResponder = fn;50}515253export function dispatchRuntimeMessage(54 msg: any,55 sender: any = { id: "peach-test-extension-id" }56): Array<unknown> {57 const responses: unknown[] = [];58 for (const listener of messageListeners) {59 responses.push(listener(msg, sender, (r?: any) => responses.push(r)));60 }61 return responses;62}6364const sendMessage = vi.fn((msg: any, maybeCb?: (resp: any) => void) => {65 sentMessages.push(msg);66 const result = messageResponder(msg);67 const resolved = Promise.resolve(result);68 if (typeof maybeCb === "function") {69 resolved.then((r) => maybeCb(r));70 return undefined as unknown as Promise<any>;71 }72 return resolved;73});7475const sessionStore = new Map<string, unknown>();7677const chromeMock = {78 runtime: {79 id: "peach-test-extension-id",80 sendMessage,81 getURL: (path: string) => `chrome-extension://peach-test/${path}`,82 onMessage: {83 addListener: (fn: any) => messageListeners.push(fn),84 removeListener: (fn: any) => {85 const i = messageListeners.indexOf(fn);86 if (i >= 0) messageListeners.splice(i, 1);87 },88 },89 lastError: undefined as undefined | { message: string },90 },91 storage: {92 session: {93 get: (key: string) =>94 Promise.resolve(sessionStore.has(key) ? { [key]: sessionStore.get(key) } : {}),95 set: (obj: Record<string, unknown>) => {96 for (const [k, v] of Object.entries(obj)) sessionStore.set(k, v);97 return Promise.resolve();98 },99 },100 local: {101 get: () => Promise.resolve({}),102 set: () => Promise.resolve(),103 },104 },105};106107108109(globalThis as any).chrome = chromeMock;110111112113export const clipboardWrite = vi.fn(() => Promise.resolve());114if (!("clipboard" in navigator)) {115 Object.defineProperty(navigator, "clipboard", {116 configurable: true,117 value: { writeText: clipboardWrite },118 });119} else {120 Object.defineProperty(navigator.clipboard, "writeText", {121 configurable: true,122 value: clipboardWrite,123 });124}125126127128export function resetChromeMock(): void {129 sentMessages.length = 0;130 sessionStore.clear();131 sendMessage.mockClear();132 clipboardWrite.mockClear();133 messageResponder = (msg) => {134 switch (msg?.type) {135 case "GET_SETTINGS":136 return { ok: true, data: { accentColor: "#ff9776", theme: "dark" } };137 case "GET_VAULT_STATUS":138 return { ok: true, data: { unlocked: false } };139 default:140 return { ok: true, data: null };141 }142 };143}144145146147const VISIBLE_RECT = {148 width: 160,149 height: 32,150 top: 10,151 left: 10,152 right: 170,153 bottom: 42,154 x: 10,155 y: 10,156 toJSON: () => ({}),157} as const;158159160161162export function makeFieldVisible(el: HTMLElement): void {163 Object.defineProperty(el, "getBoundingClientRect", {164 configurable: true,165 value: () => ({ ...VISIBLE_RECT }),166 });167}168169170export function makeInput(171 attrs: Record<string, string> = {},172 parent: HTMLElement = document.body173): HTMLInputElement {174 const input = document.createElement("input");175 for (const [k, v] of Object.entries(attrs)) input.setAttribute(k, v);176 parent.appendChild(input);177 makeFieldVisible(input);178 return input;179}180181182export function createLoginForm(): {183 form: HTMLFormElement;184 username: HTMLInputElement;185 password: HTMLInputElement;186} {187 const form = document.createElement("form");188 document.body.appendChild(form);189 const username = makeInput(190 { type: "text", name: "username", autocomplete: "username" },191 form192 );193 const password = makeInput(194 { type: "password", name: "password", autocomplete: "current-password" },195 form196 );197 const button = document.createElement("button");198 button.type = "submit";199 button.textContent = "Log in";200 form.appendChild(button);201 return { form, username, password };202}203204205export function createEmailFirstForm(): {206 form: HTMLFormElement;207 email: HTMLInputElement;208} {209 const form = document.createElement("form");210 document.body.appendChild(form);211 const email = makeInput(212 { type: "email", name: "email", autocomplete: "email" },213 form214 );215 const button = document.createElement("button");216 button.type = "submit";217 button.textContent = "Next";218 form.appendChild(button);219 return { form, email };220}221222223export function createSignupForm(): {224 form: HTMLFormElement;225 username: HTMLInputElement;226 password: HTMLInputElement;227 confirm: HTMLInputElement;228} {229 const form = document.createElement("form");230 document.body.appendChild(form);231 const username = makeInput({ type: "text", name: "new-username" }, form);232 const password = makeInput(233 { type: "password", name: "new-password", autocomplete: "new-password" },234 form235 );236 const confirm = makeInput(237 { type: "password", name: "confirm-password", autocomplete: "new-password" },238 form239 );240 const button = document.createElement("button");241 button.type = "submit";242 button.textContent = "Create account";243 form.appendChild(button);244 return { form, username, password, confirm };245}246247248export function createSplitOtpForm(count = 6): {249 form: HTMLFormElement;250 boxes: HTMLInputElement[];251} {252 const form = document.createElement("form");253 document.body.appendChild(form);254 const boxes: HTMLInputElement[] = [];255 for (let i = 0; i < count; i++) {256 boxes.push(257 makeInput({ type: "text", maxlength: "1", inputmode: "numeric" }, form)258 );259 }260 return { form, boxes };261}