Files
fusion/packages/dashboard/app/components/__tests__/OnboardingResumeCard.test.tsx
Fusion 738ff223aa feat(FN-1861): restore completed steps in onboarding resume flow
- Update getOnboardingResumeStep to include completedSteps array
- Allow backward navigation to previously completed steps in onboarding modal
- Show completed step count in the resume card UI
- Restore completed steps when reopening the onboarding modal
- Add comprehensive tests for completedSteps functionality and live auth reopen scenarios
2026-04-15 23:00:26 -07:00

192 lines
6.5 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { OnboardingResumeCard } from "../OnboardingResumeCard";
// Mock the model-onboarding-state module
vi.mock("../model-onboarding-state", () => ({
getOnboardingResumeStep: vi.fn(),
}));
import { getOnboardingResumeStep } from "../model-onboarding-state";
describe("OnboardingResumeCard", () => {
const mockGetOnboardingResumeStep = getOnboardingResumeStep as ReturnType<typeof vi.fn>;
beforeEach(() => {
mockGetOnboardingResumeStep.mockReset();
mockGetOnboardingResumeStep.mockReturnValue(null);
});
afterEach(() => {
mockGetOnboardingResumeStep.mockReset();
});
describe("rendering", () => {
it("renders nothing when no resumable state exists", () => {
mockGetOnboardingResumeStep.mockReturnValue(null);
const { container } = render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(container.firstChild).toBeNull();
});
it("renders the resume card when resumable state exists", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByRole("region", { name: "Resume onboarding" })).toBeInTheDocument();
});
it("displays the step label", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "github",
label: "GitHub",
completedSteps: ["ai-setup"],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByText("GitHub")).toBeInTheDocument();
});
it("displays the title", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "first-task",
label: "First Task",
completedSteps: ["ai-setup", "github"],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByText("Continue Setup")).toBeInTheDocument();
});
it("displays the continue button", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByText("Continue onboarding")).toBeInTheDocument();
});
it("has accessible button with proper role", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
const button = screen.getByRole("button", { name: "Continue onboarding" });
expect(button).toBeInTheDocument();
});
});
describe("interaction", () => {
it("calls onResume when button is clicked", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
const onResume = vi.fn();
render(<OnboardingResumeCard onResume={onResume} />);
const button = screen.getByRole("button", { name: "Continue onboarding" });
fireEvent.click(button);
expect(onResume).toHaveBeenCalledTimes(1);
});
it("button is keyboard accessible", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
const onResume = vi.fn();
render(<OnboardingResumeCard onResume={onResume} />);
const button = screen.getByRole("button", { name: "Continue onboarding" });
// Click simulates both mouse and keyboard activation
fireEvent.click(button);
expect(onResume).toHaveBeenCalledTimes(1);
});
});
describe("step context", () => {
it("shows correct message for ai-setup step", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByText(/AI Setup/)).toBeInTheDocument();
});
it("shows correct message for github step", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "github",
label: "GitHub",
completedSteps: ["ai-setup"],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByText(/GitHub/)).toBeInTheDocument();
});
it("shows correct message for first-task step", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "first-task",
label: "First Task",
completedSteps: ["ai-setup", "github"],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(screen.getByText(/First Task/)).toBeInTheDocument();
});
});
describe("completed step count", () => {
it("shows progress text without count when 0 steps completed", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "ai-setup",
label: "AI Setup",
completedSteps: [],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
// When 0 steps completed, text is just "You're on the <label> step"
expect(screen.getByText(/^You're on the/)).toBeInTheDocument();
expect(screen.getByText(/AI Setup/)).toBeInTheDocument();
});
it("shows completed step count text with 1 completed step (singular)", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "github",
label: "GitHub",
completedSteps: ["ai-setup"],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
// Uses singular "step" for 1 completed
expect(screen.getByText(/1 of 3 step complete/)).toBeInTheDocument();
});
it("shows completed step count text with 2 completed steps (plural)", () => {
mockGetOnboardingResumeStep.mockReturnValue({
currentStep: "first-task",
label: "First Task",
completedSteps: ["ai-setup", "github"],
});
render(<OnboardingResumeCard onResume={vi.fn()} />);
// Uses plural "steps" for 2 completed
expect(screen.getByText(/2 of 3 steps complete/)).toBeInTheDocument();
});
});
describe("hidden state", () => {
it("does not render when currentStep is null", () => {
mockGetOnboardingResumeStep.mockReturnValue(null);
const { container } = render(<OnboardingResumeCard onResume={vi.fn()} />);
expect(container.firstChild).toBeNull();
});
});
});