diff --git a/ui/src/components/onboarding/OnboardingSummaryStep.test.tsx b/ui/src/components/onboarding/OnboardingSummaryStep.test.tsx new file mode 100644 index 00000000..487bf1cb --- /dev/null +++ b/ui/src/components/onboarding/OnboardingSummaryStep.test.tsx @@ -0,0 +1,99 @@ +// @vitest-environment jsdom +// [nexus] Unit tests for OnboardingSummaryStep component +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { OnboardingSummaryStep } from "./OnboardingSummaryStep"; +import type { HardwareInfo } from "@/api/hardware"; + +afterEach(() => { + cleanup(); +}); + +const mockHardwareInfo: HardwareInfo = { + totalGb: 16, + freeGb: 8, + usableGb: 12, + platform: "darwin", + gpuName: null, + gpuVramGb: null, + unifiedMemory: true, + hardwareTier: "apple_silicon", + cpuModel: "Apple M2", +}; + +const defaultProps = { + hardwareInfo: mockHardwareInfo, + selectedMode: "both" as const, + providerLabel: "Puter (free, zero-config)", + rootDir: "~/projects/test", + loading: false, + error: null, + onStartChat: vi.fn(), + onBack: vi.fn(), +}; + +describe("OnboardingSummaryStep", () => { + it("renders all summary rows with provided data", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("Hardware"); + expect(html).toContain("Apple Silicon"); + expect(html).toContain("Mode"); + expect(html).toContain("Both (recommended)"); + expect(html).toContain("Provider"); + expect(html).toContain("Puter (free, zero-config)"); + expect(html).toContain("Root directory"); + expect(html).toContain("~/projects/test"); + }); + + it("hides root directory row when rootDir is empty string", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).not.toContain("Root directory"); + }); + + it("shows error message when error prop is non-null", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("Setup failed. Please try again."); + }); + + it("calls onStartChat when 'Start chatting' button is clicked", () => { + const onStartChat = vi.fn(); + render(); + + const buttons = screen.getAllByRole("button"); + const startBtn = buttons.find((b) => b.textContent?.includes("Start chatting")); + expect(startBtn).toBeTruthy(); + fireEvent.click(startBtn!); + + expect(onStartChat).toHaveBeenCalledOnce(); + }); + + it("disables 'Start chatting' button when loading is true", () => { + render(); + + const buttons = screen.getAllByRole("button"); + const startBtn = buttons.find((b) => b.textContent?.includes("Setting up")); + expect(startBtn).toBeTruthy(); + expect(startBtn).toHaveProperty("disabled", true); + }); + + it("shows 'Setting up...' text when loading is true", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("Setting up..."); + }); +}); diff --git a/ui/src/components/onboarding/OnboardingSummaryStep.tsx b/ui/src/components/onboarding/OnboardingSummaryStep.tsx new file mode 100644 index 00000000..44ce4b25 --- /dev/null +++ b/ui/src/components/onboarding/OnboardingSummaryStep.tsx @@ -0,0 +1,129 @@ +// [nexus] Summary screen for onboarding wizard step 5 — read-only review before starting +import type { HardwareInfo, HardwareTier, NexusMode } from "@/api/hardware"; +import { Button } from "@/components/ui/button"; + +interface OnboardingSummaryStepProps { + hardwareInfo: HardwareInfo | undefined; + selectedMode: NexusMode; + providerLabel: string; + rootDir: string; + loading: boolean; + error: string | null; + onStartChat: () => void; + onBack: () => void; +} + +interface SummaryRowProps { + label: string; + value: string; + mono?: boolean; +} + +function SummaryRow({ label, value, mono }: SummaryRowProps) { + return ( +
+ {label} + {value} +
+ ); +} + +const HARDWARE_TIER_LABELS: Record = { + gpu: "GPU", + apple_silicon: "Apple Silicon", + cpu_only: "CPU Only", +}; + +const MODE_LABELS: Record = { + personal_ai: "Personal AI Assistant", + project_builder: "Project Builder", + both: "Both (recommended)", +}; + +export function OnboardingSummaryStep({ + hardwareInfo, + selectedMode, + providerLabel, + rootDir, + loading, + error, + onStartChat, + onBack, +}: OnboardingSummaryStepProps) { + const hardwareLabel = hardwareInfo + ? (HARDWARE_TIER_LABELS[hardwareInfo.hardwareTier] ?? "Unknown") + : "Unknown"; + + const modeLabel = MODE_LABELS[selectedMode]; + + return ( +
+
+

Ready to go

+

Review your setup before starting.

+
+ + {/* Summary card */} +
+ + + + {rootDir && } +
+ + {/* Error message */} + {error && ( +

+ {error} +

+ )} + + {/* Actions */} +
+ + +
+
+ ); +}