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>
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
import { get } from "svelte/store";
|
|
import { characterState, characterInfo } from "./character";
|
|
|
|
describe("characterState store", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
characterState.reset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe("initial state", () => {
|
|
it("starts in idle state", () => {
|
|
expect(get(characterState)).toBe("idle");
|
|
});
|
|
});
|
|
|
|
describe("setState", () => {
|
|
it("sets the character state", () => {
|
|
characterState.setState("thinking");
|
|
expect(get(characterState)).toBe("thinking");
|
|
});
|
|
|
|
it("can set any valid state", () => {
|
|
const states = [
|
|
"idle",
|
|
"thinking",
|
|
"typing",
|
|
"coding",
|
|
"searching",
|
|
"mcp",
|
|
"permission",
|
|
"success",
|
|
"error",
|
|
] as const;
|
|
for (const state of states) {
|
|
characterState.setState(state);
|
|
expect(get(characterState)).toBe(state);
|
|
}
|
|
});
|
|
|
|
it("cancels any active temporary state timer", () => {
|
|
characterState.setTemporaryState("success", 5000);
|
|
characterState.setState("thinking");
|
|
|
|
// Advance past the temporary state duration — should stay as thinking
|
|
vi.advanceTimersByTime(6000);
|
|
expect(get(characterState)).toBe("thinking");
|
|
});
|
|
});
|
|
|
|
describe("setTemporaryState", () => {
|
|
it("sets the character state immediately", () => {
|
|
characterState.setTemporaryState("success", 2000);
|
|
expect(get(characterState)).toBe("success");
|
|
});
|
|
|
|
it("reverts to idle after the specified duration", () => {
|
|
characterState.setTemporaryState("success", 2000);
|
|
vi.advanceTimersByTime(2000);
|
|
expect(get(characterState)).toBe("idle");
|
|
});
|
|
|
|
it("uses 2000ms as the default duration", () => {
|
|
characterState.setTemporaryState("error");
|
|
vi.advanceTimersByTime(1999);
|
|
expect(get(characterState)).toBe("error");
|
|
vi.advanceTimersByTime(1);
|
|
expect(get(characterState)).toBe("idle");
|
|
});
|
|
|
|
it("cancels a previous temporary state timer when a new one is set", () => {
|
|
characterState.setTemporaryState("success", 5000);
|
|
characterState.setTemporaryState("error", 1000);
|
|
|
|
// First timer would have fired at 5000ms but was cancelled
|
|
vi.advanceTimersByTime(1000);
|
|
expect(get(characterState)).toBe("idle");
|
|
});
|
|
});
|
|
|
|
describe("reset", () => {
|
|
it("resets the state to idle", () => {
|
|
characterState.setState("thinking");
|
|
characterState.reset();
|
|
expect(get(characterState)).toBe("idle");
|
|
});
|
|
|
|
it("cancels any pending temporary state timer", () => {
|
|
characterState.setTemporaryState("success", 5000);
|
|
characterState.reset();
|
|
|
|
// Should now be idle and should NOT revert again after timer fires
|
|
expect(get(characterState)).toBe("idle");
|
|
vi.advanceTimersByTime(5000);
|
|
expect(get(characterState)).toBe("idle");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("characterInfo derived store", () => {
|
|
beforeEach(() => {
|
|
characterState.reset();
|
|
});
|
|
|
|
it("returns the info object for the current state", () => {
|
|
const info = get(characterInfo);
|
|
expect(info).toBeDefined();
|
|
expect(typeof info.label).toBe("string");
|
|
});
|
|
|
|
it("updates when the character state changes", () => {
|
|
characterState.setState("thinking");
|
|
const thinkingInfo = get(characterInfo);
|
|
|
|
characterState.setState("idle");
|
|
const idleInfo = get(characterInfo);
|
|
|
|
expect(thinkingInfo.label).not.toBe(idleInfo.label);
|
|
});
|
|
});
|