c53f699714
- go plugin backends ship as release tarballs (per-platform binaries, checksums) that kandev installs from url/upload or filesystem sideload+sync, then spawns and supervises via hashicorp/go-plugin (unix socket / loopback tcp+automtls) - strict typed grpc contract (kandev.plugin.v1): DeliverEvent/InvokeTool/HandleWebhook plus a capability-gated host service (state, secrets, emit); author sdk in pkg/pluginsdk; packaging cli in cmd/plugin-pack - native frontend bundles: window.registerKandevPlugin + registry/host loader with dynamic routes, nav items, slots and ws handlers; plugins settings page with install dialog, sync button and enable/disable/uninstall - gated behind the plugins feature flag (prod off, dev/e2e on); covered by unit and integration tests plus playwright e2e against a real packaged fixture plugin Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
import { cleanup, render, screen } from "@testing-library/react";
|
|
import { TooltipProvider } from "@kandev/ui/tooltip";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { pluginRegistry } from "@/lib/plugins/registry";
|
|
import { PluginNavItems } from "./plugin-nav-items";
|
|
|
|
let pathname = "/";
|
|
|
|
function cleanupPlugins(...pluginIds: string[]) {
|
|
pluginIds.forEach((id) => pluginRegistry.unregisterPlugin(id));
|
|
}
|
|
|
|
vi.mock("@/lib/routing/client-router", () => ({
|
|
usePathname: () => pathname,
|
|
}));
|
|
|
|
function renderNavItems(collapsed = false) {
|
|
return render(
|
|
<TooltipProvider>
|
|
<PluginNavItems collapsed={collapsed} />
|
|
</TooltipProvider>,
|
|
);
|
|
}
|
|
|
|
describe("PluginNavItems", () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
cleanupPlugins("plugin-a", "plugin-b");
|
|
pathname = "/";
|
|
window.history.pushState({}, "", "/");
|
|
});
|
|
|
|
it("renders nothing when no plugin has registered a nav item", () => {
|
|
const { container } = renderNavItems();
|
|
expect(container.innerHTML).toBe("");
|
|
});
|
|
|
|
it("renders a registered main-section nav item", () => {
|
|
pluginRegistry
|
|
.forPlugin("plugin-a")
|
|
.registerNavItem({ id: "hello", label: "Hello", path: "/plugins/hello" });
|
|
|
|
renderNavItems();
|
|
|
|
expect(screen.getByTestId("plugin-nav-item-hello")).not.toBeNull();
|
|
expect(screen.getByText("Hello")).not.toBeNull();
|
|
});
|
|
|
|
it("omits a nav item registered for a non-main section", () => {
|
|
pluginRegistry.forPlugin("plugin-a").registerNavItem({
|
|
id: "settings-item",
|
|
label: "Settings Item",
|
|
path: "/settings/plugins/plugin-a",
|
|
section: "settings",
|
|
});
|
|
|
|
renderNavItems();
|
|
|
|
expect(screen.queryByTestId("plugin-nav-item-settings-item")).toBeNull();
|
|
});
|
|
|
|
it("navigates to item.path when clicked", () => {
|
|
pluginRegistry
|
|
.forPlugin("plugin-a")
|
|
.registerNavItem({ id: "hello", label: "Hello", path: "/plugins/hello" });
|
|
|
|
renderNavItems();
|
|
screen.getByTestId("plugin-nav-item-hello").click();
|
|
|
|
expect(window.location.pathname).toBe("/plugins/hello");
|
|
});
|
|
});
|