123import { afterEach, describe, expect, it, vi } from "vitest";45import { flushView, renderView } from "./view-test-utils";6import { PRO_FEATURES, PRO_SECURITY_FEATURES } from "../pro-features";78const snoozeMock = vi.fn(async () => {});9const silenceMock = vi.fn(async () => {});1011vi.mock("../../shared/pro-nudge", () => ({12 snoozeProNudge: () => snoozeMock(),13 silenceProNudge: () => silenceMock(),14}));1516const { ProNudgeView } = await import("./ProNudge");1718function button(container: HTMLElement, label: string): HTMLButtonElement {19 const match = [...container.querySelectorAll("button")].find((b) => b.textContent?.trim() === label);20 if (!match) throw new Error(`no button "${label}"`);21 return match;22}2324describe("ProNudgeView", () => {25 let dispose: (() => void) | undefined;2627 afterEach(() => {28 dispose?.();29 vi.clearAllMocks();30 });3132 it("snoozes for 90 days, then dismisses", async () => {33 const onDismiss = vi.fn();34 const view = renderView(() => <ProNudgeView onSeePro={() => {}} onDismiss={onDismiss} />);35 dispose = view.dispose;3637 button(view.container, "Remind me in 90 days").click();38 await flushView();3940 expect(snoozeMock).toHaveBeenCalledOnce();41 expect(silenceMock).not.toHaveBeenCalled();42 expect(onDismiss).toHaveBeenCalledOnce();43 });4445 it("silences permanently, then dismisses", async () => {46 const onDismiss = vi.fn();47 const view = renderView(() => <ProNudgeView onSeePro={() => {}} onDismiss={onDismiss} />);48 dispose = view.dispose;4950 button(view.container, "Don't show again").click();51 await flushView();5253 expect(silenceMock).toHaveBeenCalledOnce();54 expect(onDismiss).toHaveBeenCalledOnce();55 });5657 it("still dismisses when storage fails", async () => {58 silenceMock.mockRejectedValueOnce(new Error("quota"));59 const onDismiss = vi.fn();60 const view = renderView(() => <ProNudgeView onSeePro={() => {}} onDismiss={onDismiss} />);61 dispose = view.dispose;6263 button(view.container, "Don't show again").click();64 await flushView();6566 expect(onDismiss).toHaveBeenCalledOnce();67 });6869 it("lists every Pro feature from the shared catalog", () => {70 const view = renderView(() => <ProNudgeView onSeePro={() => {}} onDismiss={() => {}} />);71 dispose = view.dispose;7273 const names = [...view.container.querySelectorAll(".peach-pro-grid-name")].map((n) => n.textContent);74 expect(names).toEqual([...PRO_FEATURES, ...PRO_SECURITY_FEATURES].map((f) => f.name));75 });7677 it("opens Pro without recording a dismissal", () => {78 const onSeePro = vi.fn();79 const view = renderView(() => <ProNudgeView onSeePro={onSeePro} onDismiss={() => {}} />);80 dispose = view.dispose;8182 button(view.container, "See Peach Pro").click();8384 expect(onSeePro).toHaveBeenCalledOnce();85 expect(snoozeMock).not.toHaveBeenCalled();86 expect(silenceMock).not.toHaveBeenCalled();87 });88});