Files
resolutionflow/frontend/src/components/dashboard/__tests__/NextStepCard.test.tsx
Michael Chihlas f1be3abcc5
Some checks failed
CI / e2e (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / backend (push) Has been cancelled
Mirror to GitHub / mirror (push) Has been cancelled
feat: self-serve signup Phase 2 (frontend cutover) (#162)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com>
Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
2026-05-07 18:42:20 +00:00

149 lines
4.8 KiB
TypeScript

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<typeof vi.fn>
function makeStatus(overrides: Partial<OnboardingStatus> = {}): 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(<BrowserRouter>{ui}</BrowserRouter>)
}
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(<NextStepCard />)
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(<NextStepCard />)
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(<NextStepCard />)
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(<NextStepCard />)
// 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(<NextStepCard />)
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')
})
})