12345678910111213141516171819import { beforeEach, describe, expect, it, vi } from "vitest";2021type StorageShape = Record<string, unknown>;2223describe("T-037: clipboard.ts alreadyWritten and double-failure paths", () => {24 let storage: StorageShape;25 let sessionStorage: StorageShape;2627 beforeEach(async () => {28 vi.resetModules();29 storage = {};30 sessionStorage = {};3132 const alarmListeners: Array<(alarm: { name: string }) => void> = [];33 const createdAlarms: Array<{ name: string; opts: Record<string, unknown> }> = [];3435 const chromeMock = {36 storage: {37 local: {38 get: vi.fn(async (key?: string | string[]) => {39 if (!key) return { ...storage };40 if (typeof key === "string") return { [key]: storage[key] };41 return (Array.isArray(key) ? key : [key]).reduce<StorageShape>(42 (acc, k) => { acc[k] = storage[k]; return acc; },43 {},44 );45 }),46 set: vi.fn(async (next: StorageShape) => { Object.assign(storage, next); }),47 remove: vi.fn(async () => {}),48 },49 session: {50 get: vi.fn(async (key?: string | string[]) => {51 if (!key) return { ...sessionStorage };52 if (typeof key === "string") return { [key]: sessionStorage[key] };53 return (Array.isArray(key) ? key : [key]).reduce<StorageShape>(54 (acc, k) => { acc[k] = sessionStorage[k]; return acc; },55 {},56 );57 }),58 set: vi.fn(async (next: StorageShape) => { Object.assign(sessionStorage, next); }),59 remove: vi.fn(async () => {}),60 },61 },62 alarms: {63 clear: vi.fn(async () => true),64 create: vi.fn(async (name: string, opts: Record<string, unknown>) => {65 createdAlarms.push({ name, opts });66 }),67 onAlarm: {68 addListener: vi.fn((cb: (alarm: { name: string }) => void) => {69 alarmListeners.push(cb);70 }),71 },72 },73 tabs: {74 query: vi.fn(async () => []),75 },76 scripting: {77 executeScript: vi.fn(async () => { throw new Error("executeScript failed"); }),78 },79 } as unknown as typeof chrome;8081 (globalThis as Record<string, unknown>).chrome = chromeMock;82 (globalThis as Record<string, unknown>)._createdAlarms = createdAlarms;8384 85 Object.defineProperty(globalThis, "navigator", {86 value: { clipboard: { writeText: vi.fn() } },87 configurable: true,88 });89 });9091 92 93 94 95 96 97 98 it("T-037a: alreadyWritten=true → navigator.clipboard.writeText NOT called", async () => {99 const { copyToClipboard } = await import("../../background/clipboard");100101 const writeTextMock = vi.fn(async () => {});102 Object.defineProperty(globalThis.navigator, "clipboard", {103 value: { writeText: writeTextMock },104 configurable: true,105 });106107 await copyToClipboard("secret-value", "Password", true );108109 110 expect(writeTextMock).not.toHaveBeenCalled();111 });112113 114 115 it("T-037b: alreadyWritten=true → alarm is still created for auto-clear", async () => {116 const { copyToClipboard } = await import("../../background/clipboard");117 const alarmsMock = chrome.alarms as { create: ReturnType<typeof vi.fn> };118119 await copyToClipboard("secret-value", "Password", true);120121 122 expect(alarmsMock.create).toHaveBeenCalled();123 const alarmName = (alarmsMock.create.mock.calls[0] as [string, unknown])[0];124 expect(alarmName).toBe("peach-clipboard-clear-current");125 });126127 128 129 130 131 132 133 134 135 it("T-037c: both write paths fail → copyToClipboard does not throw (alarm still scheduled)", async () => {136 137 Object.defineProperty(globalThis.navigator, "clipboard", {138 value: { writeText: vi.fn(async () => { throw new Error("clipboard API unavailable"); }) },139 configurable: true,140 });141 142143 const { copyToClipboard } = await import("../../background/clipboard");144 const alarmsMock = chrome.alarms as { create: ReturnType<typeof vi.fn> };145146 147 await expect(copyToClipboard("secret", "Password", false)).resolves.toBeUndefined();148149 150 expect(alarmsMock.create).toHaveBeenCalled();151 });152153 154 155 it("T-037d: alreadyWritten=false (default) → clipboard write is attempted", async () => {156 const writeTextMock = vi.fn(async () => {});157 Object.defineProperty(globalThis.navigator, "clipboard", {158 value: { writeText: writeTextMock },159 configurable: true,160 });161162 const { copyToClipboard } = await import("../../background/clipboard");163164 await copyToClipboard("secret", "Password", false);165166 167 168 expect(writeTextMock).toHaveBeenCalledWith("secret");169 });170});