Adds real-time updates when lore syncs new GitLab data by watching the lore.db file for changes. React hook (src/hooks/useTauriEvents.ts): - useTauriEvent(): Subscribe to a single Tauri event with auto-cleanup - useTauriEvents(): Subscribe to multiple events with a handler map - Typed payloads for each event type: - global-shortcut-triggered: toggle-window | quick-capture - lore-data-changed: void (refresh trigger) - sync-status: started | completed | failed - error-notification: code + message Rust watcher (src-tauri/src/watcher.rs): - Watches lore's data directory for lore.db modifications - Uses notify crate with 2-second poll interval - Emits "lore-data-changed" event to frontend on file change - Handles atomic writes by watching parent directory - Gracefully handles missing lore.db (logs warning, skips watcher) Test coverage: - Hook subscription and cleanup behavior - Focus store test fix: clear localStorage before each test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
113 lines
2.8 KiB
TypeScript
113 lines
2.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { renderHook, act } from "@testing-library/react";
|
|
import { useTauriEvent, useTauriEvents } from "@/hooks/useTauriEvents";
|
|
|
|
// Mock the listen function
|
|
const mockUnlisten = vi.fn();
|
|
const mockListen = vi.fn().mockResolvedValue(mockUnlisten);
|
|
|
|
vi.mock("@tauri-apps/api/event", () => ({
|
|
listen: (...args: unknown[]) => mockListen(...args),
|
|
}));
|
|
|
|
describe("useTauriEvent", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("subscribes to the specified event on mount", async () => {
|
|
const handler = vi.fn();
|
|
renderHook(() => useTauriEvent("lore-data-changed", handler));
|
|
|
|
// Wait for the listen promise to resolve
|
|
await vi.waitFor(() => {
|
|
expect(mockListen).toHaveBeenCalledWith(
|
|
"lore-data-changed",
|
|
expect.any(Function)
|
|
);
|
|
});
|
|
});
|
|
|
|
it("calls the handler when event is received", async () => {
|
|
const handler = vi.fn();
|
|
renderHook(() => useTauriEvent("global-shortcut-triggered", handler));
|
|
|
|
// Get the callback that was passed to listen
|
|
await vi.waitFor(() => {
|
|
expect(mockListen).toHaveBeenCalled();
|
|
});
|
|
|
|
const eventCallback = mockListen.mock.calls[0][1];
|
|
|
|
// Simulate receiving an event
|
|
act(() => {
|
|
eventCallback({ payload: "quick-capture" });
|
|
});
|
|
|
|
expect(handler).toHaveBeenCalledWith("quick-capture");
|
|
});
|
|
|
|
it("calls unlisten on unmount", async () => {
|
|
const handler = vi.fn();
|
|
const { unmount } = renderHook(() =>
|
|
useTauriEvent("lore-data-changed", handler)
|
|
);
|
|
|
|
// Wait for subscription to be set up
|
|
await vi.waitFor(() => {
|
|
expect(mockListen).toHaveBeenCalled();
|
|
});
|
|
|
|
unmount();
|
|
|
|
expect(mockUnlisten).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("useTauriEvents", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("subscribes to multiple events", async () => {
|
|
const handlers = {
|
|
"lore-data-changed": vi.fn(),
|
|
"sync-status": vi.fn(),
|
|
};
|
|
|
|
renderHook(() => useTauriEvents(handlers));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(mockListen).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
expect(mockListen).toHaveBeenCalledWith(
|
|
"lore-data-changed",
|
|
expect.any(Function)
|
|
);
|
|
expect(mockListen).toHaveBeenCalledWith("sync-status", expect.any(Function));
|
|
});
|
|
|
|
it("cleans up all subscriptions on unmount", async () => {
|
|
const handlers = {
|
|
"lore-data-changed": vi.fn(),
|
|
"sync-status": vi.fn(),
|
|
};
|
|
|
|
const { unmount } = renderHook(() => useTauriEvents(handlers));
|
|
|
|
await vi.waitFor(() => {
|
|
expect(mockListen).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
unmount();
|
|
|
|
// Should call unlisten for each subscription
|
|
expect(mockUnlisten).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|