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();
});
});