feat: add Tauri state persistence and BvCli trait
- Add Tauri storage adapter for Zustand (tauri-storage.ts) - Add read_state, write_state, clear_state Tauri commands - Wire focus-store and nav-store to use Tauri persistence - Add BvCli trait for bv CLI mocking with response types - Add BvError and McError conversion for bv errors - Add cleanup_tmp_files tests for bridge - Fix linter-introduced tauri_specta::command issues Closes bd-2x6, bd-gil, bd-3px Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
119
tests/stores/tauri-storage.test.ts
Normal file
119
tests/stores/tauri-storage.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Tests for Tauri storage adapter for Zustand.
|
||||
*
|
||||
* Verifies that the store persists state to Tauri backend
|
||||
* instead of browser localStorage.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
// Mock Tauri's invoke
|
||||
const mockInvoke = vi.fn();
|
||||
vi.mock("@tauri-apps/api/core", () => ({
|
||||
invoke: (...args: unknown[]) => mockInvoke(...args),
|
||||
}));
|
||||
|
||||
// Import after mocking
|
||||
import { createTauriStorage, initializeStorage } from "@/lib/tauri-storage";
|
||||
|
||||
describe("Tauri Storage Adapter", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("getItem", () => {
|
||||
it("calls read_state Tauri command", async () => {
|
||||
const savedState = { focusId: "br-123", queueOrder: ["a", "b"] };
|
||||
mockInvoke.mockResolvedValue(savedState);
|
||||
|
||||
const storage = createTauriStorage();
|
||||
const result = await storage.getItem("mc-state");
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("read_state");
|
||||
expect(result).toBe(JSON.stringify(savedState));
|
||||
});
|
||||
|
||||
it("returns null when no state exists", async () => {
|
||||
mockInvoke.mockResolvedValue(null);
|
||||
|
||||
const storage = createTauriStorage();
|
||||
const result = await storage.getItem("mc-state");
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null on Tauri error (graceful fallback)", async () => {
|
||||
mockInvoke.mockRejectedValue(new Error("Tauri not available"));
|
||||
|
||||
const storage = createTauriStorage();
|
||||
const result = await storage.getItem("mc-state");
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setItem", () => {
|
||||
it("calls write_state Tauri command with parsed JSON", async () => {
|
||||
mockInvoke.mockResolvedValue(undefined);
|
||||
|
||||
const storage = createTauriStorage();
|
||||
const state = { focusId: "br-456", activeView: "queue" };
|
||||
await storage.setItem("mc-state", JSON.stringify(state));
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("write_state", { state });
|
||||
});
|
||||
|
||||
it("handles Tauri error gracefully (does not throw)", async () => {
|
||||
mockInvoke.mockRejectedValue(new Error("Write failed"));
|
||||
|
||||
const storage = createTauriStorage();
|
||||
|
||||
// Should not throw
|
||||
await expect(
|
||||
storage.setItem("mc-state", JSON.stringify({ focusId: null }))
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeItem", () => {
|
||||
it("calls clear_state Tauri command", async () => {
|
||||
mockInvoke.mockResolvedValue(undefined);
|
||||
|
||||
const storage = createTauriStorage();
|
||||
await storage.removeItem("mc-state");
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("clear_state");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("initializeStorage", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns Tauri storage when in Tauri context", async () => {
|
||||
// Tauri context detection: window.__TAURI__ exists
|
||||
vi.stubGlobal("__TAURI__", {});
|
||||
|
||||
const storage = await initializeStorage();
|
||||
|
||||
// Should be our custom storage, not localStorage
|
||||
expect(storage.getItem).toBeDefined();
|
||||
expect(storage.setItem).toBeDefined();
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("falls back to localStorage in browser context", async () => {
|
||||
// Not in Tauri
|
||||
vi.stubGlobal("__TAURI__", undefined);
|
||||
|
||||
const storage = await initializeStorage();
|
||||
|
||||
// Should fall back to localStorage wrapper
|
||||
expect(storage).toBeDefined();
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user