Files
mission-control/tests/stores/tauri-storage.test.ts
teernisse 087b588d71 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>
2026-02-26 10:06:57 -05:00

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();
});
});