ProNudge.test.tsx

2.9 KB · raw

1// @vitest-environment jsdom23import { 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});