import { describe, it, expect, beforeEach, vi } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' import { NextStepCard, pickNextStep } from '../NextStepCard' import { useBillingStore } from '@/store/billingStore' import type { OnboardingStatus } from '@/api/onboarding' vi.mock('@/api/onboarding', () => { const mockGet = vi.fn() const mockDismiss = vi.fn() return { getOnboardingStatus: mockGet, dismissOnboarding: mockDismiss, } }) import { getOnboardingStatus as _getOnboardingStatus, } from '@/api/onboarding' const getOnboardingStatus = _getOnboardingStatus as unknown as ReturnType function makeStatus(overrides: Partial = {}): OnboardingStatus { return { created_flow: false, ran_session: false, exported_session: false, tried_ai_assistant: false, invited_teammate: false, connected_psa: false, is_team_user: false, dismissed: false, email_verified: false, shop_setup_done: false, ...overrides, } } function renderWithRouter(ui: React.ReactElement) { return render({ui}) } function setBillingComplimentary() { // 'complimentary' status -> stage 'complimentary' (not in plan-gate set), so the // "Pick a plan" item stays hidden — perfect default for unrelated tests. useBillingStore.setState({ subscription: { status: 'complimentary', plan: 'pro', current_period_start: '2026-05-01T00:00:00Z', current_period_end: null, cancel_at_period_end: false, seat_limit: null, has_pro_entitlement: true, is_paid: true, }, planBilling: null, planLimits: {}, enabledFeatures: {}, isLoading: false, error: null, }) } describe('NextStepCard', () => { beforeEach(() => { getOnboardingStatus.mockReset() setBillingComplimentary() }) it('renders Verify your email when email unverified', async () => { getOnboardingStatus.mockResolvedValue(makeStatus({ email_verified: false })) renderWithRouter() await waitFor(() => { expect(screen.getByTestId('next-step-card')).toBeInTheDocument() }) expect(screen.getByRole('heading', { name: /Verify your email/i })).toBeInTheDocument() }) it('renders Set up your shop after email verified', async () => { getOnboardingStatus.mockResolvedValue( makeStatus({ email_verified: true, shop_setup_done: false }), ) renderWithRouter() await waitFor(() => { expect(screen.getByRole('heading', { name: /Set up your shop/i })).toBeInTheDocument() }) }) it('renders Run your first FlowPilot session after shop setup', async () => { getOnboardingStatus.mockResolvedValue( makeStatus({ email_verified: true, shop_setup_done: true, ran_session: false, }), ) renderWithRouter() await waitFor(() => { expect( screen.getByRole('heading', { name: /Run your first FlowPilot session/i }), ).toBeInTheDocument() }) }) it('hidden when all items done', async () => { getOnboardingStatus.mockResolvedValue( makeStatus({ email_verified: true, shop_setup_done: true, ran_session: true, connected_psa: true, invited_teammate: true, }), ) const { container } = renderWithRouter() // Resolve the awaited promise. await waitFor(() => expect(getOnboardingStatus).toHaveBeenCalled()) expect(container.querySelector('[data-testid="next-step-card"]')).toBeNull() }) it('hidden when onboarding_dismissed', async () => { getOnboardingStatus.mockResolvedValue(makeStatus({ dismissed: true })) const { container } = renderWithRouter() await waitFor(() => expect(getOnboardingStatus).toHaveBeenCalled()) expect(container.querySelector('[data-testid="next-step-card"]')).toBeNull() }) it('Pick a plan item appears when trial stage is warning or later', () => { // Direct unit-test on the pure picker — easier than coordinating both the // billing store + the network mock + a fake clock for stage='warning'. const allDoneExceptPlan = makeStatus({ email_verified: true, shop_setup_done: true, ran_session: true, connected_psa: true, invited_teammate: true, }) expect(pickNextStep(allDoneExceptPlan, 'pristine')).toBeNull() expect(pickNextStep(allDoneExceptPlan, 'paid')).toBeNull() expect(pickNextStep(allDoneExceptPlan, 'complimentary')).toBeNull() expect(pickNextStep(allDoneExceptPlan, 'warning')?.key).toBe('pick_plan') expect(pickNextStep(allDoneExceptPlan, 'urgent')?.key).toBe('pick_plan') expect(pickNextStep(allDoneExceptPlan, 'expired')?.key).toBe('pick_plan') }) })