import { describe, it, expect, vi, beforeEach } from "vitest"; import { invoke } from "@tauri-apps/api/core"; import { setMockInvokeResult } from "../../../vitest.setup"; import { quickActionsStore, type QuickAction } from "./quickActions"; describe("QuickAction interface", () => { it("defines all required fields", () => { const action: QuickAction = { id: "action-123", name: "Run Tests", prompt: "Please run the tests", icon: "play", is_default: false, created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", }; expect(action.id).toBe("action-123"); expect(action.name).toBe("Run Tests"); expect(action.prompt).toBe("Please run the tests"); expect(action.icon).toBe("play"); expect(action.is_default).toBe(false); expect(action.created_at).toBe("2024-01-01T00:00:00Z"); expect(action.updated_at).toBe("2024-01-01T00:00:00Z"); }); it("supports default actions", () => { const defaultAction: QuickAction = { id: "default-review-pr", name: "Review PR", prompt: "Please review this pull request", icon: "git-pull-request", is_default: true, created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", }; expect(defaultAction.is_default).toBe(true); expect(defaultAction.id).toContain("default-"); }); }); describe("quickActionsStore", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("store structure", () => { it("has all expected methods", () => { expect(typeof quickActionsStore.loadQuickActions).toBe("function"); expect(typeof quickActionsStore.saveQuickAction).toBe("function"); expect(typeof quickActionsStore.createQuickAction).toBe("function"); expect(typeof quickActionsStore.updateQuickAction).toBe("function"); expect(typeof quickActionsStore.deleteQuickAction).toBe("function"); expect(typeof quickActionsStore.resetDefaults).toBe("function"); }); it("has subscribable stores", () => { expect(typeof quickActionsStore.actions.subscribe).toBe("function"); expect(typeof quickActionsStore.isLoading.subscribe).toBe("function"); }); }); describe("loadQuickActions", () => { it("loads quick actions from backend", async () => { const mockActions: QuickAction[] = [ { id: "action-1", name: "Action 1", prompt: "prompt 1", icon: "star", is_default: false, created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", }, ]; setMockInvokeResult("list_quick_actions", mockActions); await quickActionsStore.loadQuickActions(); expect(invoke).toHaveBeenCalledWith("list_quick_actions"); }); it("handles load errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("list_quick_actions", new Error("Failed to load")); await quickActionsStore.loadQuickActions(); expect(consoleSpy).toHaveBeenCalledWith("Failed to load quick actions:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("saveQuickAction", () => { it("saves action and reloads list", async () => { const action: QuickAction = { id: "action-123", name: "Test", prompt: "test prompt", icon: "star", is_default: false, created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", }; setMockInvokeResult("save_quick_action", undefined); setMockInvokeResult("list_quick_actions", [action]); const result = await quickActionsStore.saveQuickAction(action); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith("save_quick_action", { action }); }); it("handles save errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("save_quick_action", new Error("Failed to save")); const action: QuickAction = { id: "action-123", name: "Test", prompt: "test prompt", icon: "star", is_default: false, created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", }; const result = await quickActionsStore.saveQuickAction(action); expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith("Failed to save quick action:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("createQuickAction", () => { it("creates new action with generated ID and timestamps", async () => { setMockInvokeResult("save_quick_action", undefined); setMockInvokeResult("list_quick_actions", []); const result = await quickActionsStore.createQuickAction("My Action", "Do something", "star"); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith( "save_quick_action", expect.objectContaining({ action: expect.objectContaining({ name: "My Action", prompt: "Do something", icon: "star", is_default: false, }), }) ); }); }); describe("updateQuickAction", () => { it("updates existing action preserving created_at", async () => { const existingAction: QuickAction = { id: "action-123", name: "Old Name", prompt: "old prompt", icon: "old-icon", is_default: false, created_at: "2024-01-01T00:00:00Z", updated_at: "2024-01-01T00:00:00Z", }; setMockInvokeResult("list_quick_actions", [existingAction]); setMockInvokeResult("save_quick_action", undefined); const result = await quickActionsStore.updateQuickAction( "action-123", "New Name", "new prompt", "new-icon" ); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith( "save_quick_action", expect.objectContaining({ action: expect.objectContaining({ id: "action-123", name: "New Name", prompt: "new prompt", icon: "new-icon", created_at: "2024-01-01T00:00:00Z", }), }) ); }); it("returns false for non-existent action", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("list_quick_actions", []); const result = await quickActionsStore.updateQuickAction( "non-existent", "Name", "prompt", "icon" ); expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith("Quick action not found for update"); consoleSpy.mockRestore(); }); }); describe("deleteQuickAction", () => { it("deletes action by ID", async () => { setMockInvokeResult("delete_quick_action", undefined); setMockInvokeResult("list_quick_actions", []); const result = await quickActionsStore.deleteQuickAction("action-123"); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith("delete_quick_action", { actionId: "action-123" }); }); it("handles delete errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("delete_quick_action", new Error("Cannot delete default action")); const result = await quickActionsStore.deleteQuickAction("default-1"); expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith("Failed to delete quick action:", expect.any(Error)); consoleSpy.mockRestore(); }); }); describe("resetDefaults", () => { it("resets default actions", async () => { setMockInvokeResult("reset_default_quick_actions", undefined); setMockInvokeResult("list_quick_actions", []); const result = await quickActionsStore.resetDefaults(); expect(result).toBe(true); expect(invoke).toHaveBeenCalledWith("reset_default_quick_actions"); }); it("handles reset errors gracefully", async () => { const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("reset_default_quick_actions", new Error("Reset failed")); const result = await quickActionsStore.resetDefaults(); expect(result).toBe(false); expect(consoleSpy).toHaveBeenCalledWith( "Failed to reset default quick actions:", expect.any(Error) ); consoleSpy.mockRestore(); }); }); }); describe("quick action ID generation", () => { it("generates unique custom action IDs", () => { const generateId = () => `custom-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; const id1 = generateId(); const id2 = generateId(); expect(id1).toMatch(/^custom-\d+-[a-z0-9]+$/); expect(id2).toMatch(/^custom-\d+-[a-z0-9]+$/); expect(id1).not.toBe(id2); }); }); describe("action validation", () => { it("requires non-empty name", () => { const action = { name: "" }; const isValid = action.name.trim().length > 0; expect(isValid).toBe(false); }); it("requires non-empty prompt", () => { const action = { prompt: " " }; const isValid = action.prompt.trim().length > 0; expect(isValid).toBe(false); }); it("requires non-empty icon", () => { const action = { icon: "star" }; const isValid = action.icon.trim().length > 0; expect(isValid).toBe(true); }); }); describe("action icons", () => { it("supports various icon names", () => { const validIcons = [ "git-pull-request", "play", "file-text", "alert-circle", "check-square", "refresh-cw", "star", "code", "terminal", ]; validIcons.forEach((icon) => { expect(typeof icon).toBe("string"); expect(icon.length).toBeGreaterThan(0); }); }); }); describe("action sorting", () => { it("sorts default actions before custom actions", () => { const actions = [ { id: "custom-1", name: "Custom", is_default: false }, { id: "default-1", name: "Default", is_default: true }, ]; const sorted = [...actions].sort((a, b) => { const defaultCmp = (b.is_default ? 1 : 0) - (a.is_default ? 1 : 0); if (defaultCmp !== 0) return defaultCmp; return a.name.localeCompare(b.name); }); expect(sorted[0].is_default).toBe(true); expect(sorted[1].is_default).toBe(false); }); it("sorts alphabetically within same default status", () => { const actions = [ { id: "default-2", name: "Zebra", is_default: true }, { id: "default-1", name: "Apple", is_default: true }, ]; const sorted = [...actions].sort((a, b) => { const defaultCmp = (b.is_default ? 1 : 0) - (a.is_default ? 1 : 0); if (defaultCmp !== 0) return defaultCmp; return a.name.localeCompare(b.name); }); expect(sorted[0].name).toBe("Apple"); expect(sorted[1].name).toBe("Zebra"); }); });