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>
188 lines
5.0 KiB
TypeScript
188 lines
5.0 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { get } from "svelte/store";
|
|
import { searchState, isSearchActive, searchQuery } from "./search";
|
|
|
|
describe("searchState store", () => {
|
|
beforeEach(() => {
|
|
searchState.clear();
|
|
});
|
|
|
|
describe("initial state", () => {
|
|
it("starts with empty query", () => {
|
|
const state = get(searchState);
|
|
expect(state.query).toBe("");
|
|
});
|
|
|
|
it("starts inactive", () => {
|
|
const state = get(searchState);
|
|
expect(state.isActive).toBe(false);
|
|
});
|
|
|
|
it("starts with zero match count", () => {
|
|
const state = get(searchState);
|
|
expect(state.matchCount).toBe(0);
|
|
});
|
|
|
|
it("starts with zero current match index", () => {
|
|
const state = get(searchState);
|
|
expect(state.currentMatchIndex).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("setQuery", () => {
|
|
it("sets the query string", () => {
|
|
searchState.setQuery("hello");
|
|
expect(get(searchState).query).toBe("hello");
|
|
});
|
|
|
|
it("activates the store when query is non-empty", () => {
|
|
searchState.setQuery("hello");
|
|
expect(get(searchState).isActive).toBe(true);
|
|
});
|
|
|
|
it("deactivates the store when query is empty", () => {
|
|
searchState.setQuery("hello");
|
|
searchState.setQuery("");
|
|
expect(get(searchState).isActive).toBe(false);
|
|
});
|
|
|
|
it("resets currentMatchIndex to 0 on each query change", () => {
|
|
searchState.setQuery("hello");
|
|
searchState.setMatchCount(5);
|
|
searchState.nextMatch();
|
|
searchState.nextMatch();
|
|
searchState.setQuery("world");
|
|
expect(get(searchState).currentMatchIndex).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("setMatchCount", () => {
|
|
it("updates the match count", () => {
|
|
searchState.setMatchCount(7);
|
|
expect(get(searchState).matchCount).toBe(7);
|
|
});
|
|
|
|
it("does not reset currentMatchIndex", () => {
|
|
searchState.setQuery("test");
|
|
searchState.setMatchCount(5);
|
|
searchState.nextMatch();
|
|
searchState.setMatchCount(10);
|
|
expect(get(searchState).currentMatchIndex).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("nextMatch", () => {
|
|
it("advances to the next match index", () => {
|
|
searchState.setMatchCount(5);
|
|
searchState.nextMatch();
|
|
expect(get(searchState).currentMatchIndex).toBe(1);
|
|
});
|
|
|
|
it("wraps around to 0 after the last match", () => {
|
|
searchState.setMatchCount(3);
|
|
searchState.nextMatch();
|
|
searchState.nextMatch();
|
|
searchState.nextMatch();
|
|
expect(get(searchState).currentMatchIndex).toBe(0);
|
|
});
|
|
|
|
it("stays at 0 when match count is 0", () => {
|
|
searchState.setMatchCount(0);
|
|
searchState.nextMatch();
|
|
expect(get(searchState).currentMatchIndex).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("previousMatch", () => {
|
|
it("goes to the previous match index", () => {
|
|
searchState.setMatchCount(5);
|
|
searchState.nextMatch();
|
|
searchState.nextMatch();
|
|
searchState.previousMatch();
|
|
expect(get(searchState).currentMatchIndex).toBe(1);
|
|
});
|
|
|
|
it("wraps around to last match when at index 0", () => {
|
|
searchState.setMatchCount(5);
|
|
searchState.previousMatch();
|
|
expect(get(searchState).currentMatchIndex).toBe(4);
|
|
});
|
|
|
|
it("stays at 0 when match count is 0", () => {
|
|
searchState.setMatchCount(0);
|
|
searchState.previousMatch();
|
|
expect(get(searchState).currentMatchIndex).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("clear", () => {
|
|
it("resets query to empty string", () => {
|
|
searchState.setQuery("hello");
|
|
searchState.clear();
|
|
expect(get(searchState).query).toBe("");
|
|
});
|
|
|
|
it("resets isActive to false", () => {
|
|
searchState.setQuery("hello");
|
|
searchState.clear();
|
|
expect(get(searchState).isActive).toBe(false);
|
|
});
|
|
|
|
it("resets matchCount to 0", () => {
|
|
searchState.setMatchCount(10);
|
|
searchState.clear();
|
|
expect(get(searchState).matchCount).toBe(0);
|
|
});
|
|
|
|
it("resets currentMatchIndex to 0", () => {
|
|
searchState.setMatchCount(5);
|
|
searchState.nextMatch();
|
|
searchState.nextMatch();
|
|
searchState.clear();
|
|
expect(get(searchState).currentMatchIndex).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("isSearchActive derived store", () => {
|
|
beforeEach(() => {
|
|
searchState.clear();
|
|
});
|
|
|
|
it("is false initially", () => {
|
|
expect(get(isSearchActive)).toBe(false);
|
|
});
|
|
|
|
it("is true when query is set", () => {
|
|
searchState.setQuery("test");
|
|
expect(get(isSearchActive)).toBe(true);
|
|
});
|
|
|
|
it("is false after clearing", () => {
|
|
searchState.setQuery("test");
|
|
searchState.clear();
|
|
expect(get(isSearchActive)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("searchQuery derived store", () => {
|
|
beforeEach(() => {
|
|
searchState.clear();
|
|
});
|
|
|
|
it("is empty string initially", () => {
|
|
expect(get(searchQuery)).toBe("");
|
|
});
|
|
|
|
it("reflects the current query", () => {
|
|
searchState.setQuery("my search");
|
|
expect(get(searchQuery)).toBe("my search");
|
|
});
|
|
|
|
it("is empty after clearing", () => {
|
|
searchState.setQuery("test");
|
|
searchState.clear();
|
|
expect(get(searchQuery)).toBe("");
|
|
});
|
|
});
|