generated from nhcarrigan/template
352 lines
11 KiB
TypeScript
352 lines
11 KiB
TypeScript
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");
|
|
});
|
|
});
|