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>
112 lines
3.8 KiB
TypeScript
112 lines
3.8 KiB
TypeScript
/**
|
|
* ConversationTabs Component Tests
|
|
*
|
|
* Tests the connection status colour mapping and unread message detection
|
|
* logic used by the ConversationTabs component.
|
|
*
|
|
* What this component does:
|
|
* - Displays one tab per conversation
|
|
* - Each tab shows a coloured dot for its connection state
|
|
* - Inactive tabs with new messages show an animated blue dot badge
|
|
* - Tabs can be renamed by double-clicking
|
|
* - Tabs can be reordered by drag-and-drop
|
|
* - New tabs created with Ctrl+T, closed with Ctrl+W
|
|
*
|
|
* Manual testing checklist:
|
|
* - [ ] Connected tabs show a green dot
|
|
* - [ ] Connecting tabs show a yellow dot
|
|
* - [ ] Disconnected tabs show a red dot
|
|
* - [ ] Active tab never shows the unread badge
|
|
* - [ ] Inactive tab shows blue pulsing dot when it receives new messages
|
|
* - [ ] Switching to a tab clears the unread indicator
|
|
* - [ ] Double-clicking a tab name enables inline editing
|
|
* - [ ] Tabs can be dragged to reorder
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
type ConnectionStatus = "connected" | "connecting" | "disconnected";
|
|
|
|
function getConnectionStatusColor(status: ConnectionStatus | string): string {
|
|
switch (status) {
|
|
case "connected":
|
|
return "bg-green-500";
|
|
case "connecting":
|
|
return "bg-yellow-500";
|
|
case "disconnected":
|
|
return "bg-red-500";
|
|
default:
|
|
return "bg-gray-500";
|
|
}
|
|
}
|
|
|
|
function hasUnreadMessages(
|
|
id: string,
|
|
conversationLineCount: number,
|
|
activeConversationId: string | null,
|
|
lastSeenMessageCount: Map<string, number>
|
|
): boolean {
|
|
if (id === activeConversationId) return false;
|
|
const lastSeen = lastSeenMessageCount.get(id) ?? 0;
|
|
return conversationLineCount > lastSeen;
|
|
}
|
|
|
|
// ---
|
|
|
|
describe("getConnectionStatusColor", () => {
|
|
it("returns green for connected status", () => {
|
|
expect(getConnectionStatusColor("connected")).toBe("bg-green-500");
|
|
});
|
|
|
|
it("returns yellow for connecting status", () => {
|
|
expect(getConnectionStatusColor("connecting")).toBe("bg-yellow-500");
|
|
});
|
|
|
|
it("returns red for disconnected status", () => {
|
|
expect(getConnectionStatusColor("disconnected")).toBe("bg-red-500");
|
|
});
|
|
|
|
it("returns grey for unknown status (fallback)", () => {
|
|
expect(getConnectionStatusColor("error")).toBe("bg-gray-500");
|
|
expect(getConnectionStatusColor("")).toBe("bg-gray-500");
|
|
});
|
|
});
|
|
|
|
describe("hasUnreadMessages", () => {
|
|
it("returns false for the active conversation regardless of message count", () => {
|
|
const lastSeen = new Map([["tab-1", 0]]);
|
|
expect(hasUnreadMessages("tab-1", 10, "tab-1", lastSeen)).toBe(false);
|
|
});
|
|
|
|
it("returns true when an inactive tab has more messages than last seen", () => {
|
|
const lastSeen = new Map([["tab-1", 5]]);
|
|
expect(hasUnreadMessages("tab-1", 10, "tab-2", lastSeen)).toBe(true);
|
|
});
|
|
|
|
it("returns false when an inactive tab has no new messages", () => {
|
|
const lastSeen = new Map([["tab-1", 10]]);
|
|
expect(hasUnreadMessages("tab-1", 10, "tab-2", lastSeen)).toBe(false);
|
|
});
|
|
|
|
it("returns false when an inactive tab has fewer messages than last seen", () => {
|
|
const lastSeen = new Map([["tab-1", 15]]);
|
|
expect(hasUnreadMessages("tab-1", 10, "tab-2", lastSeen)).toBe(false);
|
|
});
|
|
|
|
it("treats a tab with no last-seen record as having 0 messages seen", () => {
|
|
const lastSeen = new Map<string, number>();
|
|
// Tab has 1 message but no entry in lastSeen → treated as 0 seen → unread
|
|
expect(hasUnreadMessages("tab-1", 1, "tab-2", lastSeen)).toBe(true);
|
|
});
|
|
|
|
it("returns false for an untracked tab with 0 messages", () => {
|
|
const lastSeen = new Map<string, number>();
|
|
expect(hasUnreadMessages("tab-1", 0, "tab-2", lastSeen)).toBe(false);
|
|
});
|
|
|
|
it("handles null activeConversationId (no active tab)", () => {
|
|
const lastSeen = new Map([["tab-1", 3]]);
|
|
expect(hasUnreadMessages("tab-1", 10, null, lastSeen)).toBe(true);
|
|
});
|
|
});
|