import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; vi.mock("./logService", () => ({ logInfo: vi.fn(), logWarn: vi.fn(), logError: vi.fn(), })); import { logError } from "./logService"; import { notifyDataChanged, subscribeDataChanged } from "./dataEvents"; // The channel is module state: every test unsubscribes what it subscribed. const cleanups: Array<() => void> = []; function subscribe(listener: () => void): () => void { const unsubscribe = subscribeDataChanged(listener); cleanups.push(unsubscribe); return unsubscribe; } beforeEach(() => { vi.mocked(logError).mockReset(); }); afterEach(() => { cleanups.splice(0).forEach((unsubscribe) => unsubscribe()); }); describe("dataEvents (#396)", () => { it("a subscriber receives each notification", () => { const listener = vi.fn(); subscribe(listener); notifyDataChanged(); notifyDataChanged(); expect(listener).toHaveBeenCalledTimes(2); }); it("an unsubscribed listener no longer receives", () => { const listener = vi.fn(); const unsubscribe = subscribe(listener); notifyDataChanged(); unsubscribe(); notifyDataChanged(); expect(listener).toHaveBeenCalledTimes(1); }); it("unsubscribing one listener leaves the others subscribed", () => { const first = vi.fn(); const second = vi.fn(); const unsubscribeFirst = subscribe(first); subscribe(second); unsubscribeFirst(); notifyDataChanged(); expect(first).not.toHaveBeenCalled(); expect(second).toHaveBeenCalledTimes(1); }); it("a subscriber that throws does not stop the others, and notify never throws", () => { const before = vi.fn(); const after = vi.fn(); subscribe(before); subscribe(() => { throw new Error("listener bug"); }); subscribe(after); expect(() => notifyDataChanged()).not.toThrow(); expect(before).toHaveBeenCalledTimes(1); expect(after).toHaveBeenCalledTimes(1); expect(logError).toHaveBeenCalledTimes(1); expect(vi.mocked(logError).mock.calls[0]).toEqual([ "Data events: a subscriber failed", "listener bug", ]); }); it("a listener that unsubscribes while being called does not skip the next one", () => { const next = vi.fn(); const unsubscribeSelf: { fn: () => void } = { fn: () => undefined }; unsubscribeSelf.fn = subscribe(() => unsubscribeSelf.fn()); subscribe(next); notifyDataChanged(); expect(next).toHaveBeenCalledTimes(1); }); it("notifies nobody without subscribers", () => { expect(() => notifyDataChanged()).not.toThrow(); expect(logError).not.toHaveBeenCalled(); }); });