geo/frontend/__tests__/stores/notification-store.test.ts

233 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);
});
});
});