import { describe, it, expect, beforeEach, vi } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { SelectPlanPage } from '../SelectPlanPage' import { useBillingStore } from '@/store/billingStore' vi.mock('@/api/billing', () => ({ billingApi: { createCheckoutSession: vi.fn(), }, })) vi.mock('@/api/plans', () => ({ plansApi: { getPublic: vi.fn(), }, })) vi.mock('@/lib/toast', () => ({ toast: { error: vi.fn(), success: vi.fn(), }, })) import { billingApi } from '@/api/billing' import { plansApi } from '@/api/plans' const createCheckoutSession = billingApi.createCheckoutSession as unknown as ReturnType const getPublic = plansApi.getPublic as unknown as ReturnType const PLAN_FIXTURE = [ { plan: 'starter', display_name: 'Starter', description: 'For solo techs.', monthly_price_cents: 1900, annual_price_cents: 19000, max_seats: 1, sort_order: 1, is_public: true, }, { plan: 'pro', display_name: 'Pro', description: 'For growing teams.', monthly_price_cents: 4900, annual_price_cents: 49000, max_seats: 5, sort_order: 2, is_public: true, }, { plan: 'enterprise', display_name: 'Enterprise', description: 'Custom.', monthly_price_cents: null, annual_price_cents: null, max_seats: null, sort_order: 3, is_public: true, }, ] function renderPage() { return render( , ) } describe('SelectPlanPage', () => { // Stub window.location.href setter so we can assert without a real navigation. let assignedHref: string | null = null const originalLocation = window.location beforeEach(() => { getPublic.mockReset() createCheckoutSession.mockReset() assignedHref = null Object.defineProperty(window, 'location', { configurable: true, value: { ...originalLocation, get href() { return assignedHref ?? originalLocation.href }, set href(v: string) { assignedHref = v }, }, }) useBillingStore.setState({ subscription: null, planBilling: null, planLimits: {}, enabledFeatures: {}, isLoading: false, error: null, }) }) it('renders plan cards from plansApi', async () => { getPublic.mockResolvedValueOnce(PLAN_FIXTURE) renderPage() await waitFor(() => { expect(screen.getByTestId('plan-card-starter')).toBeInTheDocument() }) expect(screen.getByTestId('plan-card-pro')).toBeInTheDocument() expect(screen.getByTestId('plan-card-enterprise')).toBeInTheDocument() }) it('Continue to checkout calls createCheckoutSession and redirects', async () => { getPublic.mockResolvedValueOnce(PLAN_FIXTURE) createCheckoutSession.mockResolvedValueOnce({ url: 'https://checkout.stripe.com/abc' }) renderPage() await waitFor(() => { expect(screen.getByTestId('plan-card-pro')).toBeInTheDocument() }) // Bump seats and switch to annual. fireEvent.change(screen.getByTestId('seats-input'), { target: { value: '3' } }) fireEvent.click(screen.getByTestId('interval-annual')) fireEvent.click(screen.getByTestId('plan-cta-pro')) await waitFor(() => { expect(createCheckoutSession).toHaveBeenCalledWith({ plan: 'pro', seats: 3, billing_interval: 'annual', }) }) await waitFor(() => { expect(assignedHref).toBe('https://checkout.stripe.com/abc') }) }) it('Talk to sales links to /contact-sales for enterprise', async () => { getPublic.mockResolvedValueOnce(PLAN_FIXTURE) renderPage() await waitFor(() => { expect(screen.getByTestId('plan-cta-enterprise')).toBeInTheDocument() }) const cta = screen.getByTestId('plan-cta-enterprise') as HTMLAnchorElement expect(cta.getAttribute('href')).toBe('/contact-sales') }) it('marks the active current plan as Current plan and disables its CTA', async () => { getPublic.mockResolvedValueOnce(PLAN_FIXTURE) useBillingStore.setState({ subscription: { status: 'active', plan: 'pro', current_period_start: '2026-04-01T00:00:00Z', current_period_end: '2026-05-01T00:00:00Z', cancel_at_period_end: false, seat_limit: 5, has_pro_entitlement: true, is_paid: true, }, planBilling: null, planLimits: {}, enabledFeatures: {}, isLoading: false, error: null, }) renderPage() await waitFor(() => { expect(screen.getByTestId('plan-current-pro')).toBeInTheDocument() }) const cta = screen.getByTestId('plan-cta-pro') as HTMLButtonElement expect(cta).toBeDisabled() }) })