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:
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user