115 lines
4.2 KiB
TypeScript
115 lines
4.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|||
|
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||
|
|
import { useAppState } from "../../store/appState";
|
||
|
|
import type { AppSettings, CatalogItem, MarketplaceSnapshot } from "../../lib/types";
|
||
|
|
import type { MarketplaceApi } from "../../hooks/useMarketplace";
|
||
|
|
|
||
|
|
vi.mock("./InstallControls", () => ({ default: () => <div>install controls</div> }));
|
||
|
|
vi.mock("./AddMarketplaceModal", () => ({ default: () => <div>add modal</div> }));
|
||
|
|
|
||
|
|
import BrowsePane from "./BrowsePane";
|
||
|
|
|
||
|
|
const it_ = (kind: CatalogItem["kind"], key: string, patch: Partial<CatalogItem> = {}): CatalogItem => ({
|
||
|
|
kind,
|
||
|
|
key,
|
||
|
|
name: key,
|
||
|
|
description: `${key} description`,
|
||
|
|
path: key,
|
||
|
|
invalid: null,
|
||
|
|
hook_commands: [],
|
||
|
|
preview: `${key} preview body`,
|
||
|
|
...patch,
|
||
|
|
});
|
||
|
|
|
||
|
|
const snapshot: MarketplaceSnapshot = {
|
||
|
|
marketplace_id: "m1",
|
||
|
|
head_commit: "a".repeat(40),
|
||
|
|
fetched_at: "2026-09-27T12:00:00Z",
|
||
|
|
fetch_error: "network unreachable",
|
||
|
|
items: [it_("agent", "code-reviewer"), it_("hook", "notify-on-stop"), it_("skill", "broken", { invalid: "SKILL.md missing" })],
|
||
|
|
};
|
||
|
|
|
||
|
|
function api(patch: Partial<MarketplaceApi> = {}): MarketplaceApi {
|
||
|
|
return {
|
||
|
|
snapshots: [snapshot],
|
||
|
|
updates: [],
|
||
|
|
loading: false,
|
||
|
|
refreshing: [],
|
||
|
|
load: vi.fn(),
|
||
|
|
refresh: vi.fn(),
|
||
|
|
reloadState: vi.fn(),
|
||
|
|
install: vi.fn(),
|
||
|
|
uninstall: vi.fn(),
|
||
|
|
setDisabled: vi.fn(),
|
||
|
|
update: vi.fn(),
|
||
|
|
forget: vi.fn(),
|
||
|
|
remove: vi.fn(async () => true),
|
||
|
|
...patch,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("BrowsePane", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
useAppState.setState({
|
||
|
|
appSettings: {
|
||
|
|
marketplaces: [{ id: "m1", name: "Starter", url: "https://github.com/s/m.git", branch: null, account_id: null }],
|
||
|
|
marketplace_accounts: [],
|
||
|
|
global_marketplace_installs: [],
|
||
|
|
} as unknown as AppSettings,
|
||
|
|
projects: [],
|
||
|
|
marketplaceFilterProjectId: null,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("lists items, filters by kind and search, and shows detail", () => {
|
||
|
|
render(<BrowsePane mp={api()} />);
|
||
|
|
expect(screen.getByText("network unreachable")).toBeInTheDocument();
|
||
|
|
expect(screen.getByRole("button", { name: /code-reviewer/ })).toBeInTheDocument();
|
||
|
|
expect(screen.getByRole("button", { name: /notify-on-stop/ })).toBeInTheDocument();
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("radio", { name: "Hooks" }));
|
||
|
|
expect(screen.queryByRole("button", { name: /code-reviewer/ })).not.toBeInTheDocument();
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("radio", { name: "All" }));
|
||
|
|
fireEvent.change(screen.getByLabelText("Search items"), { target: { value: "review" } });
|
||
|
|
expect(screen.queryByRole("button", { name: /notify-on-stop/ })).not.toBeInTheDocument();
|
||
|
|
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: /code-reviewer/ }));
|
||
|
|
expect(screen.getByText("code-reviewer preview body")).toBeInTheDocument();
|
||
|
|
expect(screen.getByText("install controls")).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("shows why an item is invalid", () => {
|
||
|
|
render(<BrowsePane mp={api()} />);
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: /broken/ }));
|
||
|
|
expect(screen.getByText("SKILL.md missing")).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("refreshes one marketplace", () => {
|
||
|
|
const mp = api();
|
||
|
|
render(<BrowsePane mp={mp} />);
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "Refresh Starter" }));
|
||
|
|
expect(mp.refresh).toHaveBeenCalledWith("m1");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("offers Add when there are no marketplaces", () => {
|
||
|
|
useAppState.setState({
|
||
|
|
appSettings: { marketplaces: [], marketplace_accounts: [], global_marketplace_installs: [] } as unknown as AppSettings,
|
||
|
|
});
|
||
|
|
render(<BrowsePane mp={api({ snapshots: [] })} />);
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "Add marketplace" }));
|
||
|
|
expect(screen.getByText("add modal")).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("confirms before removing a marketplace (F6)", () => {
|
||
|
|
const mp = api();
|
||
|
|
render(<BrowsePane mp={mp} />);
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "Remove Starter" }));
|
||
|
|
expect(screen.getByText(/Source removed/)).toBeInTheDocument();
|
||
|
|
expect(screen.getByText(/Forget/)).toBeInTheDocument();
|
||
|
|
expect(mp.remove).not.toHaveBeenCalled();
|
||
|
|
fireEvent.click(screen.getByRole("button", { name: "Remove marketplace" }));
|
||
|
|
expect(mp.remove).toHaveBeenCalledWith("m1");
|
||
|
|
});
|
||
|
|
});
|