import { cleanup, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, expect, it, vi } from "vitest"; import { toast } from "sonner"; import { dismissMapNotice, showMapNotice } from "./notice-actions"; import type { MapNoticeTone } from "./notice-types"; vi.mock("sonner", () => ({ toast: { custom: vi.fn(), dismiss: vi.fn() } })); afterEach(() => { cleanup(); vi.clearAllMocks(); }); it.each(["info", "success", "warning", "error", "loading"])( "keeps actionable %s notices until manual dismissal even with an explicit timeout", async (tone) => { const onAction = vi.fn(); const id = showMapNotice({ tone, title: "报告已更新", message: "查看分析结果", duration: 10_000, dismissible: false, actionLabel: "查看报告", onAction }); await Promise.resolve(); const [content, options] = vi.mocked(toast.custom).mock.calls[0]; expect(options).toMatchObject({ duration: Infinity, dismissible: true }); render(content(id)); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "查看报告" })); expect(onAction).toHaveBeenCalledOnce(); expect(toast.dismiss).not.toHaveBeenCalled(); expect(options?.cancel).toMatchObject({ label: expect.anything(), onClick: expect.any(Function) }); } ); it("preserves transient feedback and the explicit lifecycle of non-actionable notices", async () => { showMapNotice({ tone: "success", message: "已保存" }); await Promise.resolve(); expect(vi.mocked(toast.custom).mock.lastCall?.[1]).toMatchObject({ duration: 3200 }); showMapNotice({ tone: "warning", message: "稍后重试", duration: 8000, actionLabel: "缺少回调" }); await Promise.resolve(); expect(vi.mocked(toast.custom).mock.lastCall?.[1]).toMatchObject({ duration: 8000 }); showMapNotice({ tone: "loading", message: "正在加载" }); await Promise.resolve(); const [content, options] = vi.mocked(toast.custom).mock.lastCall!; expect(options).toMatchObject({ duration: Infinity, dismissible: false }); render(content("loading")); expect(screen.queryByRole("button", { name: "关闭通知" })).not.toBeInTheDocument(); }); it("publishes after effects and cancels notices dismissed before publication", async () => { showMapNotice({ id: "source", tone: "loading", message: "加载中" }); showMapNotice({ id: "source", tone: "success", message: "已完成" }); const cancelled = showMapNotice({ tone: "info", message: "已取消" }); dismissMapNotice(cancelled); expect(toast.custom).not.toHaveBeenCalled(); await Promise.resolve(); expect(toast.custom).toHaveBeenCalledOnce(); expect(vi.mocked(toast.custom).mock.lastCall?.[1]).toMatchObject({ id: "source", duration: 3200 }); });