generated from nhcarrigan/template
test: add coverage for terminalNotifier, testNotifications, and claude store
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { NotificationType } from "./types";
|
||||||
|
import { claudeStore } from "$lib/stores/claude";
|
||||||
|
import { sendTerminalNotification } from "./terminalNotifier";
|
||||||
|
|
||||||
|
vi.mock("$lib/stores/claude", () => ({
|
||||||
|
claudeStore: {
|
||||||
|
addLine: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("sendTerminalNotification", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a system line for success type with sparkle emoji", () => {
|
||||||
|
sendTerminalNotification(NotificationType.SUCCESS);
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith("system", expect.stringContaining("✨"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a system line for error type with cross emoji", () => {
|
||||||
|
sendTerminalNotification(NotificationType.ERROR);
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith("system", expect.stringContaining("❌"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a system line for permission type with lock emoji", () => {
|
||||||
|
sendTerminalNotification(NotificationType.PERMISSION);
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith("system", expect.stringContaining("🔐"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a system line for connection type with link emoji", () => {
|
||||||
|
sendTerminalNotification(NotificationType.CONNECTION);
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith("system", expect.stringContaining("🔗"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a system line for task_start type with rocket emoji", () => {
|
||||||
|
sendTerminalNotification(NotificationType.TASK_START);
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith("system", expect.stringContaining("🚀"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes the optional message in the notification", () => {
|
||||||
|
sendTerminalNotification(NotificationType.SUCCESS, "Custom message text");
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith(
|
||||||
|
"system",
|
||||||
|
expect.stringContaining("Custom message text")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes the sound phrase as the notification title", () => {
|
||||||
|
sendTerminalNotification(NotificationType.SUCCESS);
|
||||||
|
|
||||||
|
expect(claudeStore.addLine).toHaveBeenCalledWith(
|
||||||
|
"system",
|
||||||
|
expect.stringContaining("I'm done!")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { testAllNotifications } from "./testNotifications";
|
||||||
|
|
||||||
|
vi.mock("./notificationManager", () => ({
|
||||||
|
notificationManager: {
|
||||||
|
notifySuccess: vi.fn().mockResolvedValue(undefined),
|
||||||
|
notifyError: vi.fn().mockResolvedValue(undefined),
|
||||||
|
notifyPermission: vi.fn().mockResolvedValue(undefined),
|
||||||
|
notifyConnection: vi.fn().mockResolvedValue(undefined),
|
||||||
|
notifyTaskStart: vi.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("testNotifications", () => {
|
||||||
|
describe("window assignment", () => {
|
||||||
|
it("assigns testAllNotifications to window.testNotifications", async () => {
|
||||||
|
// The module-level if block runs on import — reimport to ensure it ran
|
||||||
|
await import("./testNotifications");
|
||||||
|
|
||||||
|
expect((window as unknown as { testNotifications: unknown }).testNotifications).toBe(
|
||||||
|
testAllNotifications
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("testAllNotifications", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is an async function", () => {
|
||||||
|
expect(typeof testAllNotifications).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("schedules all five notification type calls", async () => {
|
||||||
|
const { notificationManager } = await import("./notificationManager");
|
||||||
|
|
||||||
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||||
|
|
||||||
|
await testAllNotifications();
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
|
||||||
|
expect(notificationManager.notifySuccess).toHaveBeenCalledWith("Test task completed!");
|
||||||
|
expect(notificationManager.notifyError).toHaveBeenCalledWith("Test error occurred!");
|
||||||
|
expect(notificationManager.notifyPermission).toHaveBeenCalledWith("Test permission request!");
|
||||||
|
expect(notificationManager.notifyConnection).toHaveBeenCalledWith(
|
||||||
|
"Test connection established!"
|
||||||
|
);
|
||||||
|
expect(notificationManager.notifyTaskStart).toHaveBeenCalledWith("Test task starting!");
|
||||||
|
|
||||||
|
consoleLogSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { describe, it, expect, afterEach } from "vitest";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import {
|
||||||
|
claudeStore,
|
||||||
|
hasPermissionPending,
|
||||||
|
hasQuestionPending,
|
||||||
|
isClaudeProcessing,
|
||||||
|
} from "./claude";
|
||||||
|
import { conversationsStore } from "./conversations";
|
||||||
|
import { characterState } from "$lib/stores/character";
|
||||||
|
import type { PermissionRequest, UserQuestionEvent } from "$lib/types/messages";
|
||||||
|
|
||||||
|
describe("claudeStore (compatibility wrapper)", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
conversationsStore.revokeAllTools();
|
||||||
|
conversationsStore.clearPermission();
|
||||||
|
conversationsStore.clearQuestion();
|
||||||
|
conversationsStore.setConnectionStatus("disconnected");
|
||||||
|
characterState.setState("idle");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getGrantedTools", () => {
|
||||||
|
it("returns an empty array when no tools are granted", () => {
|
||||||
|
expect(claudeStore.getGrantedTools()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns granted tools as an array", () => {
|
||||||
|
conversationsStore.grantTool("Read");
|
||||||
|
conversationsStore.grantTool("Write");
|
||||||
|
|
||||||
|
const tools = claudeStore.getGrantedTools();
|
||||||
|
|
||||||
|
expect(tools).toContain("Read");
|
||||||
|
expect(tools).toContain("Write");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("reset", () => {
|
||||||
|
it("clears terminal lines and resets processing state", () => {
|
||||||
|
conversationsStore.setProcessing(true);
|
||||||
|
conversationsStore.grantTool("Edit");
|
||||||
|
|
||||||
|
claudeStore.reset();
|
||||||
|
|
||||||
|
expect(get(conversationsStore.isProcessing)).toBe(false);
|
||||||
|
expect(claudeStore.getGrantedTools()).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hasPermissionPending derived store", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
conversationsStore.clearPermission();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is false when there are no pending permissions", () => {
|
||||||
|
expect(get(hasPermissionPending)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is true when there is a pending permission request", () => {
|
||||||
|
const request: PermissionRequest = {
|
||||||
|
id: "perm-1",
|
||||||
|
tool: "Bash",
|
||||||
|
description: "Run a shell command",
|
||||||
|
input: { command: "ls" },
|
||||||
|
};
|
||||||
|
|
||||||
|
conversationsStore.requestPermission(request);
|
||||||
|
|
||||||
|
expect(get(hasPermissionPending)).toBe(true);
|
||||||
|
|
||||||
|
conversationsStore.clearPermission();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hasQuestionPending derived store", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
conversationsStore.clearQuestion();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is false when there is no pending question", () => {
|
||||||
|
expect(get(hasQuestionPending)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is true when there is a pending question", () => {
|
||||||
|
const question: UserQuestionEvent = {
|
||||||
|
id: "q-1",
|
||||||
|
question: "Which approach do you prefer?",
|
||||||
|
options: [{ label: "A" }, { label: "B" }],
|
||||||
|
multi_select: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
conversationsStore.requestQuestion(question);
|
||||||
|
|
||||||
|
expect(get(hasQuestionPending)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isClaudeProcessing derived store", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
conversationsStore.setConnectionStatus("disconnected");
|
||||||
|
characterState.setState("idle");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is false when disconnected regardless of character state", () => {
|
||||||
|
conversationsStore.setConnectionStatus("disconnected");
|
||||||
|
characterState.setState("thinking");
|
||||||
|
|
||||||
|
expect(get(isClaudeProcessing)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is false when connected but in idle state", () => {
|
||||||
|
conversationsStore.setConnectionStatus("connected");
|
||||||
|
characterState.setState("idle");
|
||||||
|
|
||||||
|
expect(get(isClaudeProcessing)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is true when connected and in thinking state", () => {
|
||||||
|
conversationsStore.setConnectionStatus("connected");
|
||||||
|
characterState.setState("thinking");
|
||||||
|
|
||||||
|
expect(get(isClaudeProcessing)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is true when connected and in coding state", () => {
|
||||||
|
conversationsStore.setConnectionStatus("connected");
|
||||||
|
characterState.setState("coding");
|
||||||
|
|
||||||
|
expect(get(isClaudeProcessing)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is true when connected and in searching state", () => {
|
||||||
|
conversationsStore.setConnectionStatus("connected");
|
||||||
|
characterState.setState("searching");
|
||||||
|
|
||||||
|
expect(get(isClaudeProcessing)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user