import { beforeEach, describe, expect, it, vi } from "vitest"; import type { StateStorage } from "zustand/middleware"; import { createSidebarViewStorage, migrateSidebarViewState, useSidebarViewStore, } from "./sidebar-view-store"; vi.mock("@react-native-async-storage/async-storage", () => ({ default: { getItem: vi.fn().mockResolvedValue(null), setItem: vi.fn().mockResolvedValue(undefined), removeItem: vi.fn().mockResolvedValue(undefined), }, })); interface MemoryStorage extends StateStorage> { reads: string[]; } function createMemoryStorage(entries: Record): MemoryStorage { const reads: string[] = []; return { reads, getItem: async (name) => { reads.push(name); return entries[name] ?? null; }, setItem: async (name, value) => { entries[name] = value; }, removeItem: async (name) => { entries[name] = null; }, }; } describe("sidebar view store", () => { beforeEach(() => { useSidebarViewStore.setState({ groupMode: "project", hostFilters: [], }); }); it("toggles multiple hosts into and out of the filter", () => { const store = useSidebarViewStore.getState(); store.toggleHostFilter("host-a"); store.toggleHostFilter("host-b"); expect(useSidebarViewStore.getState().hostFilters).toEqual(["host-a", "host-b"]); store.toggleHostFilter("host-a"); expect(useSidebarViewStore.getState().hostFilters).toEqual(["host-b"]); store.clearHostFilters(); expect(useSidebarViewStore.getState().hostFilters).toEqual([]); }); it("keeps host filters that still point at available hosts", () => { const store = useSidebarViewStore.getState(); store.toggleHostFilter("host-a"); store.toggleHostFilter("host-b"); store.reconcileHostFilters(["host-a", "host-b", "host-c"]); expect(useSidebarViewStore.getState().hostFilters).toEqual(["host-a", "host-b"]); }); it("drops a host filter after that host is removed", () => { const store = useSidebarViewStore.getState(); store.toggleHostFilter("host-a"); store.toggleHostFilter("removed-host"); store.reconcileHostFilters(["host-a"]); expect(useSidebarViewStore.getState().hostFilters).toEqual(["host-a"]); }); it("migrates legacy per-host group modes to the new global mode", () => { expect( migrateSidebarViewState({ groupModeByServerId: { "host-a": "project", "host-b": "status", }, }), ).toEqual({ groupMode: "status", hostFilters: [], }); }); it("migrates a pre-v2 single host filter to the multi-host list", () => { expect( migrateSidebarViewState({ groupMode: "status", hostFilter: "host-a", }), ).toEqual({ groupMode: "status", hostFilters: ["host-a"], }); }); it("keeps current persisted sidebar view state during version migration", () => { expect( migrateSidebarViewState({ groupMode: "status", hostFilters: ["host-a", "host-b"], }), ).toEqual({ groupMode: "status", hostFilters: ["host-a", "host-b"], }); }); it("falls back to the legacy storage key when the new key is empty", async () => { const storage = createMemoryStorage({ "sidebar-view": null, "sidebar-group-mode": JSON.stringify({ state: { groupModeByServerId: { "host-a": "status" } }, version: 0, }), }); const value = await createSidebarViewStorage(storage).getItem("sidebar-view"); expect(value).toBe( JSON.stringify({ state: { groupModeByServerId: { "host-a": "status" } }, version: 0, }), ); expect(storage.reads).toEqual(["sidebar-view", "sidebar-group-mode"]); }); it("uses the new storage key without reading the legacy key when current state exists", async () => { const storage = createMemoryStorage({ "sidebar-view": JSON.stringify({ state: { groupMode: "project", hostFilters: ["host-a"] }, version: 2, }), "sidebar-group-mode": JSON.stringify({ state: { groupModeByServerId: { "host-b": "status" } }, version: 0, }), }); const value = await createSidebarViewStorage(storage).getItem("sidebar-view"); expect(value).toBe( JSON.stringify({ state: { groupMode: "project", hostFilters: ["host-a"] }, version: 2, }), ); expect(storage.reads).toEqual(["sidebar-view"]); }); });