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
This commit is contained in:
Fusion
2026-05-04 05:25:52 -07:00
committed by gsxdsm
parent 743cc38c0f
commit 6ea1384631
9 changed files with 187 additions and 46 deletions

View File

@@ -1028,7 +1028,11 @@ describe("MissionManager", () => {
await waitFor(() => {
expect(screen.getByText("Mission event 50")).toBeDefined();
expect(screen.getByText("50 of 65")).toBeDefined();
expect(
screen.getByText("50 of 65", {
selector: ".mission-detail__activity-count",
}),
).toBeDefined();
expect(screen.getByTestId("mission-activity-load-more")).toBeDefined();
});
@@ -1036,7 +1040,9 @@ describe("MissionManager", () => {
await waitFor(() => {
expect(
screen.getByText((_, element) => element?.textContent?.includes("65 of 65") ?? false),
screen.getByText("65 of 65", {
selector: ".mission-detail__activity-count",
}),
).toBeDefined();
expect(screen.queryByTestId("mission-activity-load-more")).toBeNull();
});

View File

@@ -5,6 +5,9 @@ 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 {
@@ -119,7 +122,7 @@ describe("PluginSlot", () => {
expect(vi.mocked(usePluginUiSlots)).toHaveBeenCalledWith("proj-1");
});
it("suppresses placeholder rendering when renderPlaceholder is false", () => {
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],
@@ -135,6 +138,22 @@ describe("PluginSlot", () => {
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({

View File

@@ -49,6 +49,9 @@ const mockGenerateShortLivedRemoteToken = vi.fn();
const mockFetchRemoteQr = vi.fn();
const mockFetchRemoteUrl = vi.fn();
const mockTriggerMemoryDreams = vi.fn();
const mockFetchPluginUiSlots = vi.fn();
const mockFetchDroidCliStatus = vi.fn();
const mockSetDroidCliEnabled = vi.fn();
const mockUseWorkspaceFileBrowser = vi.fn();
vi.mock("../../api", async (importOriginal) => {
@@ -98,6 +101,9 @@ vi.mock("../../api", async (importOriginal) => {
fetchRemoteQr: (...args: unknown[]) => mockFetchRemoteQr(...args),
fetchRemoteUrl: (...args: unknown[]) => mockFetchRemoteUrl(...args),
triggerMemoryDreams: (...args: unknown[]) => mockTriggerMemoryDreams(...args),
fetchPluginUiSlots: (...args: unknown[]) => mockFetchPluginUiSlots(...args),
fetchDroidCliStatus: (...args: unknown[]) => mockFetchDroidCliStatus(...args),
setDroidCliEnabled: (...args: unknown[]) => mockSetDroidCliEnabled(...args),
});
});
@@ -136,9 +142,6 @@ vi.mock("../PiExtensionsManager", () => ({
PiExtensionsManager: () => <div data-testid="pi-extensions-manager">Pi extensions content</div>,
}));
vi.mock("../PluginSlot", () => ({
PluginSlot: () => <div data-testid="plugin-slot">Plugin slot content</div>,
}));
vi.mock("../../hooks/useWorkspaceFileBrowser", () => ({
useWorkspaceFileBrowser: (...args: unknown[]) => mockUseWorkspaceFileBrowser(...args),
@@ -361,6 +364,14 @@ describe("SettingsModal", () => {
mockFetchRemoteQr.mockResolvedValue({ url: "https://remote.example.com", tokenType: "persistent", expiresAt: null, format: "image/svg", data: "<svg></svg>" });
mockFetchRemoteUrl.mockResolvedValue({ url: "https://remote.example.com", tokenType: "persistent", expiresAt: null });
mockTriggerMemoryDreams.mockResolvedValue({ success: true, summary: "done" });
mockFetchPluginUiSlots.mockResolvedValue([]);
mockFetchDroidCliStatus.mockResolvedValue({
binary: { available: true, version: "1.2.3", binaryPath: "/usr/local/bin/droid", probeDurationMs: 9 },
enabled: false,
extension: { status: "ok" },
ready: false,
});
mockSetDroidCliEnabled.mockResolvedValue({ enabled: true, restartRequired: true });
mockUseWorkspaceFileBrowser.mockReturnValue({
entries: [],
currentPath: ".",
@@ -1056,6 +1067,78 @@ describe("SettingsModal", () => {
});
});
describe("Droid plugin Settings integration", () => {
it.each([
{
name: "unavailable/not enabled",
status: {
binary: { available: false, reason: "`droid` not found on PATH", probeDurationMs: 9 },
enabled: false,
extension: { status: "ok" },
ready: false,
},
expectedText: "not found on PATH",
},
{
name: "enabled but not ready",
status: {
binary: { available: true, version: "1.2.3", binaryPath: "/usr/local/bin/droid", probeDurationMs: 9 },
enabled: true,
extension: { status: "ok" },
ready: false,
},
expectedText: "Enabled. Validating…",
},
{
name: "connected and ready",
status: {
binary: { available: true, version: "1.2.3", binaryPath: "/usr/local/bin/droid", probeDurationMs: 9 },
enabled: true,
extension: { status: "ok" },
ready: true,
},
expectedText: "✓ Connected — 1.2.3",
},
])("renders plugin-driven droid card state: $name", async ({ status, expectedText }) => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [{ id: "droid-cli", name: "Factory AI (via Droid CLI)", authenticated: false, type: "cli" }],
});
mockFetchPluginUiSlots.mockResolvedValueOnce([
{
pluginId: "fusion-plugin-droid-runtime",
slot: {
slotId: "settings-provider-card",
label: "Droid CLI Provider",
componentPath: "./components/settings-provider-card.js",
},
},
]);
mockFetchDroidCliStatus.mockResolvedValueOnce(status);
renderModal();
await waitForSettingsModalReady();
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument();
expect(await screen.findByTestId("droid-cli-provider-card")).toBeInTheDocument();
expect(screen.getAllByTestId("droid-cli-provider-card")).toHaveLength(1);
expect(screen.getByText(new RegExp(expectedText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))).toBeInTheDocument();
});
it("renders legacy droid auth card only when plugin slot is not present", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [{ id: "droid-cli", name: "Factory AI (via Droid CLI)", authenticated: false, type: "cli" }],
});
mockFetchPluginUiSlots.mockResolvedValueOnce([]);
renderModal();
await waitForSettingsModalReady();
expect(await screen.findByTestId("droid-cli-provider-card")).toBeInTheDocument();
expect(screen.getAllByTestId("droid-cli-provider-card")).toHaveLength(1);
});
});
describe("Plugins section navigation", () => {
it("does not render a standalone Pi Extensions sidebar item", async () => {
renderModal();
@@ -1090,7 +1173,6 @@ describe("SettingsModal", () => {
await userEvent.click(await screen.findByRole("button", { name: /Plugins$/ }));
expect(await screen.findByTestId("plugin-manager")).toBeInTheDocument();
expect(screen.getByTestId("plugin-slot")).toBeInTheDocument();
expect(screen.queryByTestId("pi-extensions-manager")).not.toBeInTheDocument();
await userEvent.click(screen.getByRole("tab", { name: "Pi Extensions" }));
@@ -1099,7 +1181,6 @@ describe("SettingsModal", () => {
expect(screen.getByTestId("pi-extensions-manager")).toBeInTheDocument();
});
expect(screen.queryByTestId("plugin-manager")).not.toBeInTheDocument();
expect(screen.queryByTestId("plugin-slot")).not.toBeInTheDocument();
expect(screen.getByRole("tabpanel", { name: "Pi Extensions" })).toBeVisible();
expect(document.getElementById("plugins-panel-fusion-plugins")).toHaveAttribute("hidden");
});