- 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>
120 lines
3.3 KiB
TypeScript
120 lines
3.3 KiB
TypeScript
/**
|
|
* 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();
|
|
});
|
|
});
|