generated from nhcarrigan/template
fa906684c2
## Summary - **fix**: `show_thinking_blocks` setting now persists across sessions — it was defined on the TypeScript side but missing from the Rust `HikariConfig` struct, so serde silently dropped it on every save/load - **feat**: Tool calls are now rendered as collapsible blocks matching the Extended Thinking block aesthetic, replacing the old inline dropdown approach - **feat**: Add configurable max output tokens setting - **feat**: Use random creative names for conversation tabs - **test**: Significantly expanded frontend unit test coverage - **docs**: Require tests for all changes in CLAUDE.md - **feat**: Allow users to specify a custom terminal font (Closes #176) - **feat**: Display friendly names for memory files derived from the first heading (Closes #177) - **feat**: Add custom UI font support for the app chrome (buttons, labels, tabs) - **fix**: Apply custom UI font to the full app interface — `.app-container` was hardcoded, blocking inheritance from `body`; also renamed "Custom Font" to "Custom Terminal Font" for clarity ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #175 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
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);
|
|
});
|
|
});
|