import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createDebouncedSaver, formatSavedAt } from "./autosave"; describe("createDebouncedSaver", () => { beforeEach(() => vi.useFakeTimers()); afterEach(() => vi.useRealTimers()); it("saves only once after the debounce window for rapid edits", () => { const save = vi.fn().mockResolvedValue(undefined); const saver = createDebouncedSaver(save, 1000); saver.schedule("a"); saver.schedule("ab"); saver.schedule("abc"); expect(save).not.toHaveBeenCalled(); vi.advanceTimersByTime(1000); expect(save).toHaveBeenCalledTimes(1); expect(save).toHaveBeenCalledWith("abc"); }); it("flush triggers the pending save immediately", () => { const save = vi.fn().mockResolvedValue(undefined); const saver = createDebouncedSaver(save, 1000); saver.schedule("hello"); saver.flush(); expect(save).toHaveBeenCalledTimes(1); expect(save).toHaveBeenCalledWith("hello"); // No double-fire when the timer would have elapsed. vi.advanceTimersByTime(1000); expect(save).toHaveBeenCalledTimes(1); }); it("cancel prevents a scheduled save", () => { const save = vi.fn().mockResolvedValue(undefined); const saver = createDebouncedSaver(save, 1000); saver.schedule("x"); saver.cancel(); vi.advanceTimersByTime(2000); expect(save).not.toHaveBeenCalled(); }); it("each new schedule resets the timer (only last value persists)", () => { const save = vi.fn().mockResolvedValue(undefined); const saver = createDebouncedSaver(save, 1000); saver.schedule("one"); vi.advanceTimersByTime(800); saver.schedule("two"); vi.advanceTimersByTime(800); expect(save).not.toHaveBeenCalled(); vi.advanceTimersByTime(200); expect(save).toHaveBeenCalledTimes(1); expect(save).toHaveBeenCalledWith("two"); }); }); describe("formatSavedAt", () => { it("formats as 保存于 hh:mm zero-padded", () => { const d = new Date(2026, 5, 18, 9, 4); expect(formatSavedAt(d)).toBe("保存于 09:04"); }); });