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;110111112113114if (typeof (globalThis as any).ResizeObserver === "undefined") {115 (globalThis as any).ResizeObserver = class {116 observe(): void {}117 unobserve(): void {}118 disconnect(): void {}119 };120}121122123124export const clipboardWrite = vi.fn(() => Promise.resolve());125if (!("clipboard" in navigator)) {126 Object.defineProperty(navigator, "clipboard", {127 configurable: true,128 value: { writeText: clipboardWrite },129 });130} else {131 Object.defineProperty(navigator.clipboard, "writeText", {132 configurable: true,133 value: clipboardWrite,134 });135}136137138139export function resetChromeMock(): void {140 sentMessages.length = 0;141 sessionStore.clear();142 sendMessage.mockClear();143 clipboardWrite.mockClear();144 messageResponder = (msg) => {145 switch (msg?.type) {146 case "GET_SETTINGS":147 return { ok: true, data: { accentColor: "#ff9776", theme: "dark" } };148 case "GET_VAULT_STATUS":149 return { ok: true, data: { unlocked: false } };150 default:151 return { ok: true, data: null };152 }153 };154}155156157158const VISIBLE_RECT = {159 width: 160,160 height: 32,161 top: 10,162 left: 10,163 right: 170,164 bottom: 42,165 x: 10,166 y: 10,167 toJSON: () => ({}),168} as const;169170171172173export function makeFieldVisible(el: HTMLElement): void {174 Object.defineProperty(el, "getBoundingClientRect", {175 configurable: true,176 value: () => ({ ...VISIBLE_RECT }),177 });178}179180181export function makeInput(182 attrs: Record<string, string> = {},183 parent: HTMLElement = document.body184): HTMLInputElement {185 const input = document.createElement("input");186 for (const [k, v] of Object.entries(attrs)) input.setAttribute(k, v);187 parent.appendChild(input);188 makeFieldVisible(input);189 return input;190}191192193export function createLoginForm(): {194 form: HTMLFormElement;195 username: HTMLInputElement;196 password: HTMLInputElement;197} {198 const form = document.createElement("form");199 document.body.appendChild(form);200 const username = makeInput(201 { type: "text", name: "username", autocomplete: "username" },202 form203 );204 const password = makeInput(205 { type: "password", name: "password", autocomplete: "current-password" },206 form207 );208 const button = document.createElement("button");209 button.type = "submit";210 button.textContent = "Log in";211 form.appendChild(button);212 return { form, username, password };213}214215216export function createEmailFirstForm(): {217 form: HTMLFormElement;218 email: HTMLInputElement;219} {220 const form = document.createElement("form");221 document.body.appendChild(form);222 const email = makeInput(223 { type: "email", name: "email", autocomplete: "email" },224 form225 );226 const button = document.createElement("button");227 button.type = "submit";228 button.textContent = "Next";229 form.appendChild(button);230 return { form, email };231}232233234export function createSignupForm(): {235 form: HTMLFormElement;236 username: HTMLInputElement;237 password: HTMLInputElement;238 confirm: HTMLInputElement;239} {240 const form = document.createElement("form");241 document.body.appendChild(form);242 const username = makeInput({ type: "text", name: "new-username" }, form);243 const password = makeInput(244 { type: "password", name: "new-password", autocomplete: "new-password" },245 form246 );247 const confirm = makeInput(248 { type: "password", name: "confirm-password", autocomplete: "new-password" },249 form250 );251 const button = document.createElement("button");252 button.type = "submit";253 button.textContent = "Create account";254 form.appendChild(button);255 return { form, username, password, confirm };256}257258259export function createSplitOtpForm(count = 6): {260 form: HTMLFormElement;261 boxes: HTMLInputElement[];262} {263 const form = document.createElement("form");264 document.body.appendChild(form);265 const boxes: HTMLInputElement[] = [];266 for (let i = 0; i < count; i++) {267 boxes.push(268 makeInput({ type: "text", maxlength: "1", inputmode: "numeric" }, form)269 );270 }271 return { form, boxes };272}