import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { defineComponent, h } from "vue"; import { mount, flushPromises } from "@vue/test-utils"; import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query"; import { useManifest } from "../useManifest"; import { useDiscovery } from "../useDiscovery"; import { useProject } from "../useProject"; /** Mount a component that runs `fn()` in setup and capture its return. */ function withQuery(fn: () => T): { result: T; unmount: () => void } { let result!: T; const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const wrapper = mount( defineComponent({ setup() { result = fn(); return () => h("div"); }, }), { global: { plugins: [[VueQueryPlugin, { queryClient: client }]] } }, ); return { result, unmount: () => wrapper.unmount() }; } const MANIFEST = { version: 3, model: { nodes: [{ id: "action:a", kind: "action", name: "a" }], edges: [], }, }; beforeEach(() => { localStorage.clear(); localStorage.setItem("nwire.activeProject", "/proj"); localStorage.setItem( "nwire.projects", JSON.stringify({ "/proj": { cwd: "/proj", name: "proj", lastVisited: "2026-01-01" } }), ); }); afterEach(() => { vi.unstubAllGlobals(); localStorage.clear(); }); describe("useManifest", () => { it("fetches the manifest and exposes a native view", async () => { vi.stubGlobal( "fetch", vi.fn(async () => ({ ok: true, json: async () => MANIFEST }) as unknown as Response), ); const { result, unmount } = withQuery(() => useManifest()); await flushPromises(); expect(result.view.value?.byKind("action").map((n) => n.name)).toEqual(["a"]); expect(result.error.value).toBeNull(); unmount(); }); it("surfaces a friendly error when the fetch fails", async () => { vi.stubGlobal( "fetch", vi.fn(async () => ({ ok: false, status: 404 }) as unknown as Response), ); const { result, unmount } = withQuery(() => useManifest()); await flushPromises(); expect(result.error.value).toMatch(/Manifest fetch failed \(404\)/); unmount(); }); }); describe("useDiscovery", () => { it("merges the catalog with live status + marks the active project", async () => { vi.stubGlobal( "fetch", vi.fn( async () => ({ ok: true, json: async () => ({ "/proj": { running: true, port: 3000 } }), }) as unknown as Response, ), ); const { result, unmount } = withQuery(() => useDiscovery({ intervalMs: 999_999 })); await flushPromises(); expect(result.projects.value).toHaveLength(1); expect(result.projects.value[0]!.active).toBe(true); expect(result.projects.value[0]!.status?.running).toBe(true); expect(result.runningCount.value).toBe(1); unmount(); }); }); describe("useProject", () => { it("reads the active project and switches without reload", async () => { vi.stubGlobal( "fetch", vi.fn(async () => ({ ok: true, json: async () => ({}) }) as unknown as Response), ); const { result, unmount } = withQuery(() => useProject()); expect(result.activeCwd.value).toBe("/proj"); expect(result.activeName.value).toBe("proj"); result.switchTo("/other"); expect(result.activeCwd.value).toBe("/other"); expect(localStorage.getItem("nwire.activeProject")).toBe("/other"); unmount(); }); });