File size: 3,792 Bytes
fd2c364 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "test-utils";
import PluginsService, { type MarketplacePlugin } from "#/api/plugins-service";
import type { PluginSpec } from "#/api/conversation-service/agent-server-conversation-service.types";
import { PluginPicker } from "#/components/features/plugins/plugin-picker";
// The catalog hook's service constructs a typescript-client `PluginsClient` at
// module load; stub it so importing the service never touches the real client.
// Every test replaces `getPluginsMarketplace` itself, so the client is unused.
vi.mock("@openhands/typescript-client/clients", () => ({
PluginsClient: vi.fn(),
}));
const alpha: MarketplacePlugin = {
name: "alpha",
description: "first plugin",
source: "github:o/a",
ref: null,
repo_path: "plugins/alpha",
installed: false,
};
const beta: MarketplacePlugin = {
name: "beta",
description: "second plugin",
source: "github:o/b",
ref: null,
repo_path: null,
installed: true,
};
const alphaSpec: PluginSpec = {
source: "github:o/a",
ref: null,
repo_path: "plugins/alpha",
};
function renderPicker(selected: PluginSpec[] = []) {
const onChange = vi.fn();
renderWithProviders(<PluginPicker selected={selected} onChange={onChange} />);
return { onChange };
}
describe("PluginPicker", () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it("renders a selectable card for each catalog plugin", async () => {
vi.spyOn(PluginsService, "getPluginsMarketplace").mockResolvedValue([
alpha,
beta,
]);
renderPicker();
expect(
await screen.findByTestId("plugin-picker-card-alpha"),
).toBeInTheDocument();
expect(screen.getByTestId("plugin-picker-card-beta")).toBeInTheDocument();
});
it("reports the mapped PluginSpec when an unselected plugin is toggled on", async () => {
vi.spyOn(PluginsService, "getPluginsMarketplace").mockResolvedValue([
alpha,
]);
const { onChange } = renderPicker([]);
await userEvent.click(
await screen.findByTestId("plugin-picker-toggle-alpha"),
);
expect(onChange).toHaveBeenCalledWith([alphaSpec]);
});
it("removes a plugin from the selection when toggled off", async () => {
vi.spyOn(PluginsService, "getPluginsMarketplace").mockResolvedValue([
alpha,
]);
const { onChange } = renderPicker([alphaSpec]);
await userEvent.click(
await screen.findByTestId("plugin-picker-toggle-alpha"),
);
expect(onChange).toHaveBeenCalledWith([]);
});
it("shows the empty state when the catalog is empty", async () => {
vi.spyOn(PluginsService, "getPluginsMarketplace").mockResolvedValue([]);
renderPicker();
expect(
await screen.findByTestId("plugin-picker-empty"),
).toBeInTheDocument();
});
it("shows the error state when the catalog fails to load", async () => {
vi.spyOn(PluginsService, "getPluginsMarketplace").mockRejectedValue(
new Error("unreachable"),
);
renderPicker();
expect(
await screen.findByTestId("plugin-picker-error"),
).toBeInTheDocument();
});
it("filters the catalog by the search query", async () => {
vi.spyOn(PluginsService, "getPluginsMarketplace").mockResolvedValue([
alpha,
beta,
]);
renderPicker();
await screen.findByTestId("plugin-picker-card-alpha");
await userEvent.type(
screen.getByTestId("plugin-picker-search-input"),
"alpha",
);
expect(screen.getByTestId("plugin-picker-card-alpha")).toBeInTheDocument();
expect(
screen.queryByTestId("plugin-picker-card-beta"),
).not.toBeInTheDocument();
});
});
|