121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
import { useAppState } from "../store/appState";
|
|
import type { AppSettings, MarketplaceSnapshot } from "../lib/types";
|
|
|
|
const listMarketplaceSnapshots = vi.fn();
|
|
const refreshMarketplaces = vi.fn();
|
|
const listMarketplaceUpdates = vi.fn();
|
|
const getSettings = vi.fn();
|
|
const listProjects = vi.fn();
|
|
const installMarketplaceItem = vi.fn();
|
|
|
|
vi.mock("../lib/tauri-commands", () => ({
|
|
listMarketplaceSnapshots: () => listMarketplaceSnapshots(),
|
|
refreshMarketplaces: (id?: string) => refreshMarketplaces(id),
|
|
listMarketplaceUpdates: () => listMarketplaceUpdates(),
|
|
getSettings: () => getSettings(),
|
|
listProjects: () => listProjects(),
|
|
installMarketplaceItem: (...a: unknown[]) => installMarketplaceItem(...a),
|
|
}));
|
|
|
|
let syncHandler: ((e: { payload: unknown }) => void) | null = null;
|
|
vi.mock("@tauri-apps/api/event", () => ({
|
|
listen: vi.fn(async (_name: string, cb: (e: { payload: unknown }) => void) => {
|
|
syncHandler = cb;
|
|
return vi.fn();
|
|
}),
|
|
}));
|
|
|
|
import { useMarketplace, useMarketplaceSyncToasts } from "./useMarketplace";
|
|
|
|
const snap = (id: string, fetched_at: string | null): MarketplaceSnapshot => ({
|
|
marketplace_id: id,
|
|
head_commit: null,
|
|
fetched_at,
|
|
fetch_error: null,
|
|
items: [],
|
|
});
|
|
|
|
describe("useMarketplace", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
useAppState.setState({ toasts: [], appSettings: { marketplaces: [] } as unknown as AppSettings });
|
|
listMarketplaceUpdates.mockResolvedValue([]);
|
|
getSettings.mockResolvedValue({ marketplaces: [] });
|
|
listProjects.mockResolvedValue([]);
|
|
});
|
|
|
|
it("loads snapshots and refreshes only stale ones", async () => {
|
|
const fresh = snap("m1", new Date().toISOString());
|
|
const stale = snap("m2", null);
|
|
listMarketplaceSnapshots.mockResolvedValue([fresh, stale]);
|
|
refreshMarketplaces.mockResolvedValue([{ ...stale, fetched_at: new Date().toISOString() }]);
|
|
|
|
const { result } = renderHook(() => useMarketplace());
|
|
await act(() => result.current.load({ refreshStale: true }));
|
|
|
|
expect(refreshMarketplaces).toHaveBeenCalledTimes(1);
|
|
expect(refreshMarketplaces).toHaveBeenCalledWith("m2");
|
|
expect(result.current.snapshots.map((s) => s.marketplace_id)).toEqual(["m1", "m2"]);
|
|
expect(result.current.snapshots[1].fetched_at).not.toBeNull();
|
|
});
|
|
|
|
it("toasts and reloads after a failed mutation", async () => {
|
|
listMarketplaceSnapshots.mockResolvedValue([]);
|
|
installMarketplaceItem.mockRejectedValue("boom");
|
|
const { result } = renderHook(() => useMarketplace());
|
|
const ok = await act(() =>
|
|
result.current.install({ marketplace_id: "m1", kind: "agent", key: "a" }, { type: "global" }),
|
|
);
|
|
expect(ok).toBe(false);
|
|
expect(useAppState.getState().toasts[0]).toMatchObject({ kind: "error", detail: "boom" });
|
|
});
|
|
});
|
|
|
|
describe("useMarketplaceSyncToasts", () => {
|
|
beforeEach(() => {
|
|
syncHandler = null;
|
|
useAppState.setState({
|
|
toasts: [],
|
|
projects: [{ id: "p1", name: "api" }] as never,
|
|
});
|
|
});
|
|
|
|
it("toasts a sync with errors and stays quiet on a clean one", async () => {
|
|
renderHook(() => useMarketplaceSyncToasts());
|
|
await waitFor(() => expect(syncHandler).not.toBeNull());
|
|
|
|
act(() =>
|
|
syncHandler!({
|
|
payload: {
|
|
project_id: "p1",
|
|
report: { installed: ["agent:a"], updated: [], removed: [], skipped: [], errors: [], finished_at: "" },
|
|
},
|
|
}),
|
|
);
|
|
expect(useAppState.getState().toasts).toHaveLength(0);
|
|
|
|
act(() =>
|
|
syncHandler!({
|
|
payload: {
|
|
project_id: "p1",
|
|
report: {
|
|
installed: [],
|
|
updated: [],
|
|
removed: [],
|
|
skipped: [{ item: "agent:a", reason: "a file you created has the same name" }],
|
|
errors: ["claude plugin install failed"],
|
|
finished_at: "",
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const toast = useAppState.getState().toasts[0];
|
|
expect(toast.kind).toBe("error");
|
|
expect(toast.message).toContain("api");
|
|
expect(toast.detail).toContain("claude plugin install failed");
|
|
expect(toast.detail).toContain("agent:a");
|
|
});
|
|
});
|