alarms.test.ts

5.3 KB · raw

1import { beforeEach, describe, expect, it, vi } from "vitest";23type StorageShape = Record<string, unknown>;45describe("background alarms", () => {6  let storage: StorageShape;7  let alarmListener: ((alarm: { name: string }) => void | Promise<void>) | undefined;8  let idleListener: ((state: string) => void | Promise<void>) | undefined;9  let sentRuntimeMessages: Array<unknown>;10  let sentTabMessages: Array<{ tabId: number; message: unknown }>;1112  beforeEach(() => {13    vi.resetModules();14    storage = {};15    sentRuntimeMessages = [];16    sentTabMessages = [];1718    const chromeMock = {19      storage: {20        local: {21          get: vi.fn(async (key?: string | string[]) => {22            if (!key) return storage;23            if (typeof key === "string") {24              return { [key]: storage[key] };25            }2627            return key.reduce<Record<string, unknown>>((result, entry) => {28              result[entry] = storage[entry];29              return result;30            }, {});31          }),32          set: vi.fn(async (next: Record<string, unknown>) => {33            Object.assign(storage, next);34          }),35          remove: vi.fn(async (keys: string | string[]) => {36            const list = Array.isArray(keys) ? keys : [keys];37            for (const key of list) {38              delete storage[key];39            }40          }),41        },42      },43      alarms: {44        clear: vi.fn(),45        create: vi.fn(),46        onAlarm: {47          addListener: vi.fn((fn: (alarm: { name: string }) => void | Promise<void>) => {48            alarmListener = fn;49          }),50        },51      },52      idle: {53        setDetectionInterval: vi.fn(),54        onStateChanged: {55          addListener: vi.fn((fn: (state: string) => void | Promise<void>) => {56            idleListener = fn;57          }),58        },59      },60      runtime: {61        sendMessage: vi.fn(async (message: unknown) => {62          sentRuntimeMessages.push(message);63          return { ok: true };64        }),65      },66      tabs: {67        query: vi.fn((_query: unknown, callback: (tabs: Array<{ id?: number }>) => void) => {68          callback([{ id: 1 }, { id: 2 }]);69        }),70        sendMessage: vi.fn(async (tabId: number, message: unknown) => {71          sentTabMessages.push({ tabId, message });72          return { ok: true };73        }),74      },75    } as unknown as typeof chrome;7677    (globalThis as { chrome?: typeof chrome }).chrome = chromeMock;78    vi.stubGlobal("fetch", vi.fn(async () => {79      throw new Error("network disabled in tests");80    }));81  });8283  it("touchAutoLock creates an alarm using autoLockMinutes from settings", async () => {84    const vault = await import("./vault");85    const alarms = await import("./alarms");8687    await vault.createVault("correct-pass-12345");88    await vault.updateSettings({ autoLockMinutes: 5 });8990    alarms.touchAutoLock();9192    expect(chrome.alarms.clear).toHaveBeenCalledWith("autolock");93    expect(chrome.alarms.create).toHaveBeenLastCalledWith("autolock", {94      delayInMinutes: 5,95    });96  });9798  it("touchAutoLock does not create an alarm when autoLockMinutes is 0", async () => {99    const vault = await import("./vault");100    const alarms = await import("./alarms");101102    await vault.createVault("correct-pass-12345");103    await vault.updateSettings({ autoLockMinutes: 0 });104105    (chrome.alarms.create as unknown as ReturnType<typeof vi.fn>).mockClear();106    alarms.touchAutoLock();107108    expect(chrome.alarms.create).not.toHaveBeenCalled();109  });110111  it("setupAutoLock configures idle detection using the settings period", async () => {112    const vault = await import("./vault");113    const alarms = await import("./alarms");114115    await vault.createVault("correct-pass-12345");116    await vault.updateSettings({ autoLockMinutes: 2 });117118    await alarms.setupAutoLock();119120    expect(chrome.idle.setDetectionInterval).toHaveBeenLastCalledWith(120);121  });122123  it("setupAutoLock reads the persisted setting before applying idle detection", async () => {124    storage.peach_settings = { autoLockMinutes: 30 };125    const alarms = await import("./alarms");126127    await alarms.setupAutoLock();128129    expect(chrome.idle.setDetectionInterval).toHaveBeenLastCalledWith(1800);130  });131132  it("alarm firing locks the vault and broadcasts VAULT_LOCKED", async () => {133    const vault = await import("./vault");134    await import("./alarms");135136    await vault.createVault("correct-pass-12345");137    await vault.addEntry({138      title: "GitHub",139      username: "june",140      password: "secret123",141      url: "https://github.com",142    });143144    await alarmListener?.({ name: "autolock" });145146    expect(vault.isUnlocked()).toBe(false);147    expect(sentRuntimeMessages).toContainEqual({ type: "VAULT_LOCKED" });148    expect(sentTabMessages).toEqual([149      { tabId: 1, message: { type: "VAULT_LOCKED" } },150      { tabId: 2, message: { type: "VAULT_LOCKED" } },151    ]);152  });153154  it("idle state change to idle locks the vault and active does not", async () => {155    const vault = await import("./vault");156    await import("./alarms");157158    await vault.createVault("correct-pass-12345");159    await vault.updateSettings({ autoLockMinutes: 5 });160161    await idleListener?.("active");162    expect(vault.isUnlocked()).toBe(true);163164    await idleListener?.("idle");165    expect(vault.isUnlocked()).toBe(false);166    expect(sentRuntimeMessages).toContainEqual({ type: "VAULT_LOCKED" });167  });168});