import { cleanup, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { pluginRegistry } from "@/lib/plugins/registry"; import { PluginSlot } from "./plugin-slot"; const SLOT = "task-sidebar"; function cleanupPlugins(...pluginIds: string[]) { pluginIds.forEach((id) => pluginRegistry.unregisterPlugin(id)); } describe("PluginSlot", () => { afterEach(() => { cleanup(); cleanupPlugins("plugin-a", "plugin-b"); }); it("renders nothing when no plugin has registered a component for the slot", () => { const { container } = render(); expect(container.innerHTML).toBe(""); }); it("renders every component registered for the named slot", () => { pluginRegistry .forPlugin("plugin-a") .registerComponent(SLOT, () =>
A
); pluginRegistry .forPlugin("plugin-b") .registerComponent(SLOT, () =>
B
); render(); expect(screen.getByTestId("slot-a")).not.toBeNull(); expect(screen.getByTestId("slot-b")).not.toBeNull(); }); it("does not render a component registered for a different slot", () => { pluginRegistry .forPlugin("plugin-a") .registerComponent("settings-nav", () =>
A
); render(); expect(screen.queryByTestId("slot-a")).toBeNull(); }); it("passes slotProps through to each registered component", () => { pluginRegistry .forPlugin("plugin-a") .registerComponent(SLOT, ({ slotProps }) => (
{String((slotProps as { label: string })?.label)}
)); render(); expect(screen.getByTestId("slot-a").textContent).toBe("hello"); }); it("isolates a throwing slot component so a sibling still renders", () => { // eslint-disable-next-line no-console -- expected error boundary log, assert + silence it const consoleError = vi.spyOn(console, "error").mockImplementation(() => {}); pluginRegistry.forPlugin("plugin-a").registerComponent(SLOT, () => { throw new Error("boom"); }); pluginRegistry .forPlugin("plugin-b") .registerComponent(SLOT, () =>
B
); render(); expect(screen.getByTestId("slot-b")).not.toBeNull(); consoleError.mockRestore(); }); });