import { describe, it, expect, vi, beforeEach } from "vitest"; import { get } from "svelte/store"; import { invoke } from "@tauri-apps/api/core"; import { setMockInvokeResult } from "../../../vitest.setup"; import { draftsStore, type Draft } from "./drafts"; const makeDraft = (id: string, content: string, saved_at: string): Draft => ({ id, content, saved_at, }); describe("Draft interface", () => { it("defines all required fields", () => { const draft: Draft = { id: "draft-123", content: "Hello world", saved_at: "2026-01-01T00:00:00+00:00", }; expect(draft.id).toBe("draft-123"); expect(draft.content).toBe("Hello world"); expect(draft.saved_at).toBe("2026-01-01T00:00:00+00:00"); }); it("supports multiline content", () => { const draft: Draft = { id: "multi", content: "Line 1\nLine 2\nLine 3", saved_at: "2026-01-01T00:00:00+00:00", }; expect(draft.content.includes("\n")).toBe(true); expect(draft.content.split("\n")).toHaveLength(3); }); }); describe("draftsStore", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("store structure", () => { it("has all expected methods", () => { expect(typeof draftsStore.loadDrafts).toBe("function"); expect(typeof draftsStore.saveDraft).toBe("function"); expect(typeof draftsStore.deleteDraft).toBe("function"); expect(typeof draftsStore.deleteAllDrafts).toBe("function"); expect(typeof draftsStore.formatTimestamp).toBe("function"); }); it("has subscribable stores", () => { expect(typeof draftsStore.drafts.subscribe).toBe("function"); expect(typeof draftsStore.isLoading.subscribe).toBe("function"); }); }); describe("loadDrafts", () => { it("loads drafts from backend", async () => { const mockDrafts: Draft[] = [ makeDraft("draft-1", "Hello world", "2026-01-01T00:00:00+00:00"), ]; setMockInvokeResult("list_drafts", mockDrafts); await draftsStore.loadDrafts(); expect(invoke).toHaveBeenCalledWith("list_drafts"); expect(get(draftsStore.drafts)).toEqual(mockDrafts); }); it("handles load errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("list_drafts", new Error("Failed to load")); await draftsStore.loadDrafts(); expect(consoleSpy).toHaveBeenCalledWith("Failed to load drafts:", expect.any(Error)); consoleSpy.mockRestore(); }); it("sets isLoading during load", async () => { const loadingStates: boolean[] = []; const unsubscribe = draftsStore.isLoading.subscribe((val) => loadingStates.push(val)); setMockInvokeResult("list_drafts", []); await draftsStore.loadDrafts(); unsubscribe(); expect(loadingStates).toContain(true); expect(loadingStates.at(-1)).toBe(false); }); }); describe("saveDraft", () => { it("saves draft and reloads list", async () => { const mockDraft = makeDraft("new-id", "My draft content", "2026-01-01T00:00:00+00:00"); setMockInvokeResult("save_draft", mockDraft); setMockInvokeResult("list_drafts", [mockDraft]); const result = await draftsStore.saveDraft("My draft content"); expect(result).toEqual(mockDraft); expect(invoke).toHaveBeenCalledWith("save_draft", { content: "My draft content" }); }); it("returns null on error", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("save_draft", new Error("Save failed")); const result = await draftsStore.saveDraft("content"); expect(result).toBeNull(); expect(consoleSpy).toHaveBeenCalledWith("Failed to save draft:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("deleteDraft", () => { it("deletes draft by ID and reloads", async () => { setMockInvokeResult("delete_draft", undefined); setMockInvokeResult("list_drafts", []); const result = await draftsStore.deleteDraft("draft-123"); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith("delete_draft", { draftId: "draft-123" }); }); it("handles delete errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("delete_draft", new Error("Delete failed")); const result = await draftsStore.deleteDraft("draft-123"); expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith("Failed to delete draft:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("deleteAllDrafts", () => { it("deletes all drafts and clears store", async () => { // First populate the store setMockInvokeResult("list_drafts", [makeDraft("d1", "Draft 1", "2026-01-01T00:00:00+00:00")]); await draftsStore.loadDrafts(); expect(get(draftsStore.drafts)).toHaveLength(1); setMockInvokeResult("delete_all_drafts", undefined); const result = await draftsStore.deleteAllDrafts(); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith("delete_all_drafts"); expect(get(draftsStore.drafts)).toHaveLength(0); }); it("handles delete-all errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("delete_all_drafts", new Error("Delete all failed")); const result = await draftsStore.deleteAllDrafts(); expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith("Failed to delete all drafts:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("formatTimestamp", () => { it("formats a valid ISO timestamp", () => { const result = draftsStore.formatTimestamp("2026-01-15T14:30:00+00:00"); // Should produce a human-readable string (locale-dependent) expect(typeof result).toBe("string"); expect(result.length).toBeGreaterThan(0); }); it("falls back to raw string on invalid timestamp", () => { const invalid = "not-a-date"; const result = draftsStore.formatTimestamp(invalid); expect(result).toBe(invalid); }); it("falls back to raw string when toLocaleString throws", () => { const spy = vi.spyOn(Date.prototype, "toLocaleString").mockImplementation(() => { throw new Error("Locale not supported"); }); const ts = "2026-01-15T14:30:00.000Z"; const result = draftsStore.formatTimestamp(ts); expect(result).toBe(ts); spy.mockRestore(); }); }); }); describe("draft content handling", () => { it("supports content with special characters", () => { const draft = makeDraft( "special", "echo \"Hello\" && echo 'World'", "2026-01-01T00:00:00+00:00" ); expect(draft.content).toContain('"'); expect(draft.content).toContain("'"); expect(draft.content).toContain("&&"); }); it("supports very long content", () => { const longContent = "a".repeat(1000); const draft = makeDraft("long", longContent, "2026-01-01T00:00:00+00:00"); expect(draft.content).toHaveLength(1000); }); });