Files
hikari-desktop/src/lib/stores/quickActions.test.ts
T
naomi be25152eea
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 54s
CI / Lint & Test (pull_request) Failing after 5m52s
CI / Build Linux (pull_request) Has been skipped
CI / Build Windows (cross-compile) (pull_request) Has been skipped
chore: format
2026-01-25 23:35:03 -08:00

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");
});
});