import { describe, it, expect, vi } from "vitest"; import { slashCommands, parseSlashCommand, getMatchingCommands, isSlashCommand, type SlashCommand, } from "./slashCommands"; // Mock all external dependencies vi.mock("svelte/store", () => ({ get: vi.fn(), })); vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn(), })); vi.mock("$lib/stores/claude", () => ({ claudeStore: { addLine: vi.fn(), clearTerminal: vi.fn(), activeConversationId: { subscribe: vi.fn() }, currentWorkingDirectory: { subscribe: vi.fn() }, setWorkingDirectory: vi.fn(), getConversationHistory: vi.fn(), }, })); vi.mock("$lib/stores/character", () => ({ characterState: { setState: vi.fn(), setTemporaryState: vi.fn(), }, })); vi.mock("$lib/tauri", () => ({ setSkipNextGreeting: vi.fn(), })); vi.mock("$lib/stores/search", () => ({ searchState: { setQuery: vi.fn(), clear: vi.fn(), }, })); describe("slashCommands", () => { describe("slashCommands array", () => { it("contains expected commands", () => { const commandNames = slashCommands.map((cmd) => cmd.name); expect(commandNames).toContain("cd"); expect(commandNames).toContain("clear"); expect(commandNames).toContain("new"); expect(commandNames).toContain("help"); expect(commandNames).toContain("search"); expect(commandNames).toContain("summarise"); expect(commandNames).toContain("skill"); }); it("has 7 commands total", () => { expect(slashCommands.length).toBe(7); }); it("each command has required properties", () => { slashCommands.forEach((cmd) => { expect(cmd.name).toBeDefined(); expect(typeof cmd.name).toBe("string"); expect(cmd.name.length).toBeGreaterThan(0); expect(cmd.description).toBeDefined(); expect(typeof cmd.description).toBe("string"); expect(cmd.description.length).toBeGreaterThan(0); expect(cmd.usage).toBeDefined(); expect(typeof cmd.usage).toBe("string"); expect(cmd.usage.startsWith("/")).toBe(true); expect(cmd.execute).toBeDefined(); expect(typeof cmd.execute).toBe("function"); }); }); it("cd command has correct metadata", () => { const cdCmd = slashCommands.find((cmd) => cmd.name === "cd"); expect(cdCmd).toBeDefined(); expect(cdCmd!.description).toBe("Change the working directory"); expect(cdCmd!.usage).toBe("/cd "); }); it("clear command has correct metadata", () => { const clearCmd = slashCommands.find((cmd) => cmd.name === "clear"); expect(clearCmd).toBeDefined(); expect(clearCmd!.description).toBe("Clear the terminal display (keeps conversation context)"); expect(clearCmd!.usage).toBe("/clear"); }); it("new command has correct metadata", () => { const newCmd = slashCommands.find((cmd) => cmd.name === "new"); expect(newCmd).toBeDefined(); expect(newCmd!.description).toBe("Start a fresh conversation (resets context)"); expect(newCmd!.usage).toBe("/new"); }); it("help command has correct metadata", () => { const helpCmd = slashCommands.find((cmd) => cmd.name === "help"); expect(helpCmd).toBeDefined(); expect(helpCmd!.description).toBe("Show available slash commands"); expect(helpCmd!.usage).toBe("/help"); }); it("search command has correct metadata", () => { const searchCmd = slashCommands.find((cmd) => cmd.name === "search"); expect(searchCmd).toBeDefined(); expect(searchCmd!.description).toBe("Search within the conversation (use /search to clear)"); expect(searchCmd!.usage).toBe("/search [query]"); }); it("summarise command has correct metadata", () => { const summariseCmd = slashCommands.find((cmd) => cmd.name === "summarise"); expect(summariseCmd).toBeDefined(); expect(summariseCmd!.description).toBe("Get a summary of the entire conversation"); expect(summariseCmd!.usage).toBe("/summarise"); }); it("skill command has correct metadata", () => { const skillCmd = slashCommands.find((cmd) => cmd.name === "skill"); expect(skillCmd).toBeDefined(); expect(skillCmd!.description).toBe("Invoke a Claude Code skill from ~/.claude/skills/"); expect(skillCmd!.usage).toBe("/skill [name] [data]"); }); }); describe("parseSlashCommand", () => { it("returns null for non-slash input", () => { const result = parseSlashCommand("hello world"); expect(result.command).toBeNull(); expect(result.args).toBe(""); }); it("returns null for empty string", () => { const result = parseSlashCommand(""); expect(result.command).toBeNull(); expect(result.args).toBe(""); }); it("returns null for whitespace only", () => { const result = parseSlashCommand(" "); expect(result.command).toBeNull(); expect(result.args).toBe(""); }); it("parses /cd command without args", () => { const result = parseSlashCommand("/cd"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("cd"); expect(result.args).toBe(""); }); it("parses /cd command with path argument", () => { const result = parseSlashCommand("/cd /home/naomi/code"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("cd"); expect(result.args).toBe("/home/naomi/code"); }); it("parses /clear command", () => { const result = parseSlashCommand("/clear"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("clear"); expect(result.args).toBe(""); }); it("parses /new command", () => { const result = parseSlashCommand("/new"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("new"); expect(result.args).toBe(""); }); it("parses /help command", () => { const result = parseSlashCommand("/help"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("help"); expect(result.args).toBe(""); }); it("parses /search command with query", () => { const result = parseSlashCommand("/search hello world"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("search"); expect(result.args).toBe("hello world"); }); it("parses /search command without query", () => { const result = parseSlashCommand("/search"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("search"); expect(result.args).toBe(""); }); it("parses /summarise command", () => { const result = parseSlashCommand("/summarise"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("summarise"); expect(result.args).toBe(""); }); it("parses /skill command with name and data", () => { const result = parseSlashCommand("/skill onboard-mentee john@example.com"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("skill"); expect(result.args).toBe("onboard-mentee john@example.com"); }); it("parses /skill command with name only", () => { const result = parseSlashCommand("/skill onboard-mentee"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("skill"); expect(result.args).toBe("onboard-mentee"); }); it("parses /skill command without arguments", () => { const result = parseSlashCommand("/skill"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("skill"); expect(result.args).toBe(""); }); it("returns null for unknown command", () => { const result = parseSlashCommand("/unknown"); expect(result.command).toBeNull(); expect(result.args).toBe(""); }); it("is case insensitive for command names", () => { const result1 = parseSlashCommand("/CD /path"); expect(result1.command).not.toBeNull(); expect(result1.command!.name).toBe("cd"); const result2 = parseSlashCommand("/CLEAR"); expect(result2.command).not.toBeNull(); expect(result2.command!.name).toBe("clear"); const result3 = parseSlashCommand("/Help"); expect(result3.command).not.toBeNull(); expect(result3.command!.name).toBe("help"); }); it("handles leading whitespace", () => { const result = parseSlashCommand(" /cd /path"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("cd"); expect(result.args).toBe("/path"); }); it("handles trailing whitespace", () => { const result = parseSlashCommand("/cd /path "); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("cd"); expect(result.args).toBe("/path"); }); it("handles multiple spaces between args", () => { const result = parseSlashCommand("/search hello world"); expect(result.command).not.toBeNull(); expect(result.command!.name).toBe("search"); expect(result.args).toBe("hello world"); }); }); describe("getMatchingCommands", () => { it("returns empty array for non-slash input", () => { const result = getMatchingCommands("hello"); expect(result).toEqual([]); }); it("returns empty array for empty string", () => { const result = getMatchingCommands(""); expect(result).toEqual([]); }); it("returns all commands for just slash", () => { const result = getMatchingCommands("/"); expect(result.length).toBe(slashCommands.length); }); it("returns matching commands for partial input", () => { const result = getMatchingCommands("/c"); const names = result.map((cmd) => cmd.name); expect(names).toContain("cd"); expect(names).toContain("clear"); expect(names).not.toContain("help"); }); it("returns single command for exact match", () => { const result = getMatchingCommands("/cd"); expect(result.length).toBe(1); expect(result[0].name).toBe("cd"); }); it("returns single command for partial unique match", () => { const result = getMatchingCommands("/cl"); expect(result.length).toBe(1); expect(result[0].name).toBe("clear"); }); it("returns matching commands for /s prefix", () => { const result = getMatchingCommands("/s"); const names = result.map((cmd) => cmd.name); expect(names).toContain("search"); expect(names).toContain("summarise"); expect(names).toContain("skill"); }); it("is case insensitive", () => { const result1 = getMatchingCommands("/C"); const result2 = getMatchingCommands("/c"); expect(result1.length).toBe(result2.length); }); it("returns empty array for no matches", () => { const result = getMatchingCommands("/xyz"); expect(result).toEqual([]); }); it("handles whitespace correctly", () => { const result = getMatchingCommands(" /c"); const names = result.map((cmd) => cmd.name); expect(names).toContain("cd"); expect(names).toContain("clear"); }); it("returns command for full command name", () => { const result = getMatchingCommands("/help"); expect(result.length).toBe(1); expect(result[0].name).toBe("help"); }); it("returns command for /new", () => { const result = getMatchingCommands("/n"); expect(result.length).toBe(1); expect(result[0].name).toBe("new"); }); }); describe("isSlashCommand", () => { it("returns true for input starting with slash", () => { expect(isSlashCommand("/cd")).toBe(true); expect(isSlashCommand("/")).toBe(true); expect(isSlashCommand("/help")).toBe(true); expect(isSlashCommand("/unknown")).toBe(true); }); it("returns false for non-slash input", () => { expect(isSlashCommand("hello")).toBe(false); expect(isSlashCommand("")).toBe(false); expect(isSlashCommand("cd")).toBe(false); }); it("handles whitespace correctly", () => { expect(isSlashCommand(" /cd")).toBe(true); expect(isSlashCommand(" hello")).toBe(false); }); it("returns false for slash in middle of string", () => { expect(isSlashCommand("hello/world")).toBe(false); }); }); describe("SlashCommand interface", () => { it("can create a valid slash command object", () => { const testCommand: SlashCommand = { name: "test", description: "A test command", usage: "/test [arg]", execute: vi.fn(), }; expect(testCommand.name).toBe("test"); expect(testCommand.description).toBe("A test command"); expect(testCommand.usage).toBe("/test [arg]"); expect(typeof testCommand.execute).toBe("function"); }); it("execute can be async function", () => { const asyncCommand: SlashCommand = { name: "async", description: "An async command", usage: "/async", execute: async () => { await Promise.resolve(); }, }; expect(asyncCommand.execute("")).toBeInstanceOf(Promise); }); it("execute can be sync function", () => { const syncCommand: SlashCommand = { name: "sync", description: "A sync command", usage: "/sync", execute: () => { // Synchronous execution }, }; const result = syncCommand.execute(""); // Sync function returns undefined, not a Promise expect(result).toBeUndefined(); }); }); });