generated from nhcarrigan/template
feat: multiple UI improvements, font settings, and memory file display names (#175)
## 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>
This commit was merged in pull request #175.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user