233 lines
9.0 KiB
TypeScript
233 lines
9.0 KiB
TypeScript
/**
|
||
* Notification Store 单元测试
|
||
*
|
||
* 覆盖:addNotification / removeNotification / clearAll / 自动过期清除
|
||
*/
|
||
|
||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||
import { useNotificationStore } from "@/lib/stores/notification-store";
|
||
|
||
describe("useNotificationStore", () => {
|
||
beforeEach(() => {
|
||
// 重置 store 到初始状态
|
||
useNotificationStore.setState({ notifications: [] });
|
||
vi.useFakeTimers();
|
||
});
|
||
|
||
afterEach(() => {
|
||
vi.useRealTimers();
|
||
});
|
||
|
||
// ── addNotification ────────────────────────────────────────────────────
|
||
|
||
describe("addNotification", () => {
|
||
it("应添加一条通知到队列", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
const id = addNotification({ type: "success", message: "操作成功" });
|
||
|
||
expect(id).toBeTruthy();
|
||
expect(id).toMatch(/^notif-/);
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(1);
|
||
expect(notifications[0].type).toBe("success");
|
||
expect(notifications[0].message).toBe("操作成功");
|
||
});
|
||
|
||
it("应支持不同类型的通知", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
|
||
addNotification({ type: "success", message: "成功" });
|
||
addNotification({ type: "error", message: "错误" });
|
||
addNotification({ type: "warning", message: "警告" });
|
||
addNotification({ type: "info", message: "信息" });
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(4);
|
||
expect(notifications[0].type).toBe("success");
|
||
expect(notifications[1].type).toBe("error");
|
||
expect(notifications[2].type).toBe("warning");
|
||
expect(notifications[3].type).toBe("info");
|
||
});
|
||
|
||
it("应支持可选标题", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "info", message: "消息", title: "标题" });
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications[0].title).toBe("标题");
|
||
});
|
||
|
||
it("默认过期时间应按类型自动设置", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
|
||
addNotification({ type: "success", message: "成功" });
|
||
addNotification({ type: "error", message: "错误" });
|
||
addNotification({ type: "warning", message: "警告" });
|
||
addNotification({ type: "info", message: "信息" });
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications[0].duration).toBe(3000); // success
|
||
expect(notifications[1].duration).toBe(5000); // error
|
||
expect(notifications[2].duration).toBe(4000); // warning
|
||
expect(notifications[3].duration).toBe(3000); // info
|
||
});
|
||
|
||
it("应支持自定义过期时间", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "success", message: "自定义", duration: 10000 });
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications[0].duration).toBe(10000);
|
||
});
|
||
|
||
it("duration 为 undefined 时应使用默认过期时间", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
// 不传 duration,使用 error 类型的默认值 5000
|
||
addNotification({ type: "error", message: "使用默认" });
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications[0].duration).toBe(5000);
|
||
});
|
||
|
||
it("每条通知应有唯一 ID", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
const id1 = addNotification({ type: "info", message: "第一条" });
|
||
const id2 = addNotification({ type: "info", message: "第二条" });
|
||
|
||
expect(id1).not.toBe(id2);
|
||
});
|
||
});
|
||
|
||
// ── removeNotification ─────────────────────────────────────────────────
|
||
|
||
describe("removeNotification", () => {
|
||
it("应移除指定 ID 的通知", () => {
|
||
const { addNotification, removeNotification } =
|
||
useNotificationStore.getState();
|
||
|
||
const id1 = addNotification({ type: "success", message: "保留" });
|
||
const id2 = addNotification({ type: "error", message: "移除" });
|
||
|
||
removeNotification(id2);
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(1);
|
||
expect(notifications[0].id).toBe(id1);
|
||
});
|
||
|
||
it("移除不存在的 ID 不应报错", () => {
|
||
const { addNotification, removeNotification } =
|
||
useNotificationStore.getState();
|
||
|
||
addNotification({ type: "info", message: "测试" });
|
||
expect(() => removeNotification("non-existent")).not.toThrow();
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(1);
|
||
});
|
||
|
||
it("移除通知时应清除其定时器", () => {
|
||
const { addNotification, removeNotification } =
|
||
useNotificationStore.getState();
|
||
|
||
const id = addNotification({ type: "success", message: "提前移除" });
|
||
removeNotification(id);
|
||
|
||
// 快进超过默认过期时间,不应再触发移除(避免对空列表操作)
|
||
vi.advanceTimersByTime(5000);
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(0);
|
||
});
|
||
});
|
||
|
||
// ── 自动过期清除 ───────────────────────────────────────────────────────
|
||
|
||
describe("自动过期清除", () => {
|
||
it("到达过期时间后应自动移除通知", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "success", message: "3秒后过期" });
|
||
|
||
// 快进 3 秒
|
||
vi.advanceTimersByTime(3000);
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(0);
|
||
});
|
||
|
||
it("不同类型通知在不同时间过期", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "success", message: "3秒" });
|
||
addNotification({ type: "error", message: "5秒" });
|
||
|
||
// 快进 3 秒,success 应被清除
|
||
vi.advanceTimersByTime(3000);
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(1);
|
||
expect(useNotificationStore.getState().notifications[0].type).toBe(
|
||
"error"
|
||
);
|
||
|
||
// 再快进 2 秒(共 5 秒),error 也应被清除
|
||
vi.advanceTimersByTime(2000);
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(0);
|
||
});
|
||
|
||
it("自定义 duration=0 的通知不会自动过期", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
// duration=0 时 setTimeout(cb, 0) 会在下一个事件循环触发
|
||
// 但 effectiveDuration !== null 为 true,所以会设置定时器
|
||
// 这里测试 duration 传入 0 的行为
|
||
addNotification({ type: "error", message: "0 毫秒过期", duration: 0 });
|
||
|
||
// 0 毫秒定时器应立即触发
|
||
vi.advanceTimersByTime(1);
|
||
|
||
const { notifications } = useNotificationStore.getState();
|
||
expect(notifications).toHaveLength(0);
|
||
});
|
||
|
||
it("自定义 duration 应在指定时间后过期", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "info", message: "1秒过期", duration: 1000 });
|
||
|
||
vi.advanceTimersByTime(999);
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(1);
|
||
|
||
vi.advanceTimersByTime(1);
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(0);
|
||
});
|
||
});
|
||
|
||
// ── clearAll ────────────────────────────────────────────────────────────
|
||
|
||
describe("clearAll", () => {
|
||
it("应清空所有通知", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "success", message: "A" });
|
||
addNotification({ type: "error", message: "B" });
|
||
addNotification({ type: "warning", message: "C" });
|
||
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(3);
|
||
|
||
const { clearAll } = useNotificationStore.getState();
|
||
clearAll();
|
||
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(0);
|
||
});
|
||
|
||
it("clearAll 后定时器不应再触发", () => {
|
||
const { addNotification } = useNotificationStore.getState();
|
||
addNotification({ type: "success", message: "3秒后过期" });
|
||
|
||
const { clearAll } = useNotificationStore.getState();
|
||
clearAll();
|
||
|
||
// 快进超过过期时间
|
||
vi.advanceTimersByTime(5000);
|
||
|
||
expect(useNotificationStore.getState().notifications).toHaveLength(0);
|
||
});
|
||
});
|
||
});
|