Files
fusion/packages/dashboard/app/components/__tests__/PluginSlot.test.tsx
Fusion e2f71cbfb8 feat(FN-3072): fix design tokens, mobile touch targets, and plugin settings
This merge addresses FN-3072 design feedback by updating CSS design tokens and mobile touch targets in ModelOnboardingModal, PluginSlot, and SettingsModal components, while also adding tests for droid settings plugin integration states. FN-3137 refines MissionManager's back-button navigation test as

Fusion-Task-Id: FN-3072
2026-05-04 05:25:52 -07:00

189 lines
6.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render } from "@testing-library/react";
import { PluginSlot } from "../PluginSlot";
import type { PluginUiSlotEntry } from "../../api";
import { usePluginUiSlots } from "../../hooks/usePluginUiSlots";
vi.mock("../../hooks/usePluginUiSlots");
vi.mock("../DroidCliProviderCard", () => ({
DroidCliProviderCard: () => <div data-testid="droid-cli-provider-card" />,
}));
function createSlotEntry(slotId: string, pluginId = "test-plugin"): PluginUiSlotEntry {
return {
pluginId,
slot: {
slotId,
label: `Test slot ${slotId}`,
componentPath: `./components/${slotId}.js`,
},
};
}
describe("PluginSlot", () => {
beforeEach(() => {
vi.mocked(usePluginUiSlots).mockReset();
});
afterEach(() => {
vi.restoreAllMocks();
});
it("renders nothing when no matching slots (empty array)", () => {
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [],
getSlotsForId: vi.fn(() => []),
loading: false,
error: null,
});
const { container } = render(<PluginSlot slotId="task-detail-tab" />);
expect(container.firstChild).toBeNull();
});
it("renders fallback shell for single matching slot", () => {
const entry = createSlotEntry("task-detail-tab", "plugin-a");
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [entry],
getSlotsForId: vi.fn(() => [entry]),
loading: false,
error: null,
});
const { container } = render(<PluginSlot slotId="task-detail-tab" />);
const shells = container.querySelectorAll("[data-plugin-slot]");
expect(shells).toHaveLength(1);
const shell = shells[0];
expect(shell).toHaveAttribute("data-slot-id", "task-detail-tab");
expect(shell).toHaveAttribute("data-plugin-id", "plugin-a");
expect(shell).toHaveAttribute("aria-label", "Test slot task-detail-tab");
expect(shell.textContent).toContain("Test slot task-detail-tab");
expect(shell.textContent).toContain("Extension content available.");
expect(shell.textContent).not.toContain("plugin-a");
expect(shell.textContent).not.toContain("./components/task-detail-tab.js");
});
it("renders multiple fallback shells for multiple plugins registered for same slotId", () => {
const entryA = createSlotEntry("board-column-footer", "plugin-x");
const entryB = createSlotEntry("board-column-footer", "plugin-y");
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [entryA, entryB],
getSlotsForId: vi.fn(() => [entryA, entryB]),
loading: false,
error: null,
});
const { container } = render(<PluginSlot slotId="board-column-footer" />);
const shells = container.querySelectorAll("[data-plugin-slot]");
expect(shells).toHaveLength(2);
expect(shells[0]).toHaveAttribute("data-plugin-id", "plugin-x");
expect(shells[0]).toHaveAttribute("data-slot-id", "board-column-footer");
expect(shells[1]).toHaveAttribute("data-plugin-id", "plugin-y");
expect(shells[1]).toHaveAttribute("data-slot-id", "board-column-footer");
});
it("returns null when loading", () => {
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [],
getSlotsForId: vi.fn(() => []),
loading: true,
error: null,
});
const { container } = render(<PluginSlot slotId="header-action" />);
expect(container.firstChild).toBeNull();
});
it("returns null on error", () => {
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [],
getSlotsForId: vi.fn(() => []),
loading: false,
error: "fetch failed",
});
const { container } = render(<PluginSlot slotId="header-action" />);
expect(container.firstChild).toBeNull();
});
it("passes projectId to usePluginUiSlots hook", () => {
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [],
getSlotsForId: vi.fn(() => []),
loading: false,
error: null,
});
render(<PluginSlot slotId="settings-section" projectId="proj-1" />);
expect(vi.mocked(usePluginUiSlots)).toHaveBeenCalledWith("proj-1");
});
it("suppresses placeholder rendering when renderPlaceholder is false for unknown slots", () => {
const entry = createSlotEntry("settings-provider-card", "plugin-droid");
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [entry],
getSlotsForId: vi.fn(() => [entry]),
loading: false,
error: null,
});
const { container } = render(
<PluginSlot slotId="settings-provider-card" renderPlaceholder={false} />,
);
expect(container.firstChild).toBeNull();
});
it("renders known droid settings slot even when placeholders are disabled", () => {
const entry = createSlotEntry("settings-provider-card", "fusion-plugin-droid-runtime");
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [entry],
getSlotsForId: vi.fn(() => [entry]),
loading: false,
error: null,
});
const { container } = render(
<PluginSlot slotId="settings-provider-card" renderPlaceholder={false} />,
);
expect(container.querySelector('[data-testid="droid-cli-provider-card"]')).not.toBeNull();
});
it("returns null for empty string slotId", () => {
const getSlotsForId = vi.fn(() => []);
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [],
getSlotsForId,
loading: false,
error: null,
});
const { container } = render(<PluginSlot slotId="" />);
expect(container.firstChild).toBeNull();
// getSlotsForId should not be called when slotId is falsy
expect(getSlotsForId).not.toHaveBeenCalled();
});
it("filters rendered slots by pluginIds when provided", () => {
const entryA = createSlotEntry("task-detail-tab", "plugin-a");
const entryB = createSlotEntry("task-detail-tab", "plugin-b");
vi.mocked(usePluginUiSlots).mockReturnValue({
slots: [entryA, entryB],
getSlotsForId: vi.fn(() => [entryA, entryB]),
loading: false,
error: null,
});
const { container } = render(<PluginSlot slotId="task-detail-tab" pluginIds={["plugin-b"]} />);
const shells = container.querySelectorAll("[data-plugin-slot]");
expect(shells).toHaveLength(1);
expect(shells[0]).toHaveAttribute("data-plugin-id", "plugin-b");
});
});