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>
105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { writable } from "svelte/store";
|
|
|
|
const mockSetEnabled = vi.fn();
|
|
const mockSetGlobalVolume = vi.fn();
|
|
|
|
vi.mock("$lib/notifications", () => ({
|
|
soundPlayer: {
|
|
setEnabled: mockSetEnabled,
|
|
setGlobalVolume: mockSetGlobalVolume,
|
|
},
|
|
}));
|
|
|
|
// We need to control the config store's emitted values
|
|
const configWritable = writable({
|
|
notifications_enabled: true,
|
|
notification_volume: 0.7,
|
|
});
|
|
|
|
vi.mock("./config", () => ({
|
|
configStore: {
|
|
config: { subscribe: configWritable.subscribe },
|
|
},
|
|
}));
|
|
|
|
describe("notifications sync store", () => {
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
mockSetEnabled.mockReset();
|
|
mockSetGlobalVolume.mockReset();
|
|
configWritable.set({ notifications_enabled: true, notification_volume: 0.7 });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Re-import to clean up any lingering subscriptions
|
|
const { cleanupNotificationSync } = await import("./notifications");
|
|
cleanupNotificationSync();
|
|
});
|
|
|
|
describe("initNotificationSync", () => {
|
|
it("syncs soundPlayer enabled state from config on init", async () => {
|
|
const { initNotificationSync } = await import("./notifications");
|
|
initNotificationSync();
|
|
expect(mockSetEnabled).toHaveBeenCalledWith(true);
|
|
});
|
|
|
|
it("syncs soundPlayer volume from config on init", async () => {
|
|
const { initNotificationSync } = await import("./notifications");
|
|
initNotificationSync();
|
|
expect(mockSetGlobalVolume).toHaveBeenCalledWith(0.7);
|
|
});
|
|
|
|
it("updates soundPlayer when config changes", async () => {
|
|
const { initNotificationSync } = await import("./notifications");
|
|
initNotificationSync();
|
|
|
|
mockSetEnabled.mockReset();
|
|
mockSetGlobalVolume.mockReset();
|
|
|
|
configWritable.set({ notifications_enabled: false, notification_volume: 0.3 });
|
|
|
|
expect(mockSetEnabled).toHaveBeenCalledWith(false);
|
|
expect(mockSetGlobalVolume).toHaveBeenCalledWith(0.3);
|
|
});
|
|
|
|
it("does not register a duplicate subscription when called twice", async () => {
|
|
const { initNotificationSync } = await import("./notifications");
|
|
initNotificationSync();
|
|
initNotificationSync();
|
|
|
|
// Both calls should only produce one subscription (one initial sync)
|
|
expect(mockSetEnabled).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("cleanupNotificationSync", () => {
|
|
it("stops reacting to config changes after cleanup", async () => {
|
|
const { initNotificationSync, cleanupNotificationSync } = await import("./notifications");
|
|
initNotificationSync();
|
|
cleanupNotificationSync();
|
|
|
|
mockSetEnabled.mockReset();
|
|
configWritable.set({ notifications_enabled: false, notification_volume: 0.5 });
|
|
|
|
expect(mockSetEnabled).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("is safe to call when not initialised", async () => {
|
|
const { cleanupNotificationSync } = await import("./notifications");
|
|
expect(() => cleanupNotificationSync()).not.toThrow();
|
|
});
|
|
|
|
it("allows re-initialisation after cleanup", async () => {
|
|
const { initNotificationSync, cleanupNotificationSync } = await import("./notifications");
|
|
initNotificationSync();
|
|
cleanupNotificationSync();
|
|
|
|
mockSetEnabled.mockReset();
|
|
initNotificationSync();
|
|
|
|
expect(mockSetEnabled).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|