import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { renderHook } from '@testing-library/react' import { useTrialBanner } from './useTrialBanner' import { useBillingStore } from '@/store/billingStore' import type { SubscriptionState } from '@/types/billing' const FROZEN_NOW = new Date('2026-05-06T00:00:00Z') function makeSub(overrides: Partial): SubscriptionState { return { status: 'trialing', plan: 'starter', current_period_start: '2026-05-01T00:00:00Z', current_period_end: null, cancel_at_period_end: false, seat_limit: null, has_pro_entitlement: false, is_paid: false, ...overrides, } } function setSubscription(overrides: Partial) { useBillingStore.setState({ subscription: makeSub(overrides) }) } describe('useTrialBanner', () => { beforeEach(() => { vi.useFakeTimers() vi.setSystemTime(FROZEN_NOW) useBillingStore.setState({ subscription: null, planBilling: null, planLimits: {}, enabledFeatures: {}, isLoading: false, error: null, }) }) afterEach(() => { vi.useRealTimers() }) describe('stage matches subscription state matrix', () => { it('returns null when subscription is null (no flicker on initial load)', () => { const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe(null) expect(result.current.daysRemaining).toBe(null) }) it('complimentary status -> complimentary stage', () => { setSubscription({ status: 'complimentary' }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('complimentary') }) it('active status -> paid stage', () => { setSubscription({ status: 'active' }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('paid') }) it('past_due status -> past_due stage', () => { setSubscription({ status: 'past_due' }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('past_due') }) it('canceled status -> canceled stage', () => { setSubscription({ status: 'canceled' }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('canceled') }) it('trialing >3 days remaining -> pristine', () => { // 7 days from frozen now. setSubscription({ status: 'trialing', current_period_end: '2026-05-13T00:00:00Z', }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('pristine') expect(result.current.daysRemaining).toBe(7) }) it('trialing 1-3 days remaining -> warning', () => { // 2 days from frozen now. setSubscription({ status: 'trialing', current_period_end: '2026-05-08T00:00:00Z', }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('warning') expect(result.current.daysRemaining).toBe(2) }) it('trialing exactly 24 hours remaining -> warning (boundary, not urgent)', () => { // Exactly 1.0 fractional day from frozen now — must sit on the warning // side per spec (1–3 days inclusive of 1). setSubscription({ status: 'trialing', current_period_end: '2026-05-07T00:00:00Z', }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('warning') expect(result.current.daysRemaining).toBe(1) }) it('trialing <1 day remaining -> urgent', () => { // 12 hours from frozen now -> Math.ceil(0.5) = 1 day. setSubscription({ status: 'trialing', current_period_end: '2026-05-06T12:00:00Z', }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('urgent') expect(result.current.daysRemaining).toBe(1) }) it('trialing past period_end -> expired', () => { setSubscription({ status: 'trialing', current_period_end: '2026-05-01T00:00:00Z', }) const { result } = renderHook(() => useTrialBanner()) expect(result.current.stage).toBe('expired') expect(result.current.daysRemaining).toBe(0) }) }) })