import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; vi.mock('framer-motion', () => ({ motion: { div: ({ children, ...props }: any) =>
{children}
, button: ({ children, ...props }: any) => , }, })); vi.mock('canvas-confetti', () => ({ default: vi.fn(), })); describe('PaymentResult', () => { let PaymentResult: typeof import('../../components/PaymentResult').PaymentResult; let mockConfetti: ReturnType; beforeEach(async () => { vi.clearAllMocks(); vi.resetModules(); vi.useFakeTimers(); const confettiModule = await import('canvas-confetti'); mockConfetti = confettiModule.default as ReturnType; const module = await import('../../components/PaymentResult'); PaymentResult = module.PaymentResult; }); afterEach(() => { vi.useRealTimers(); vi.resetAllMocks(); }); describe('loading state', () => { it('shows loading spinner', () => { render(); expect(document.querySelector('.animate-spin')).toBeInTheDocument(); expect(screen.getByText(/Processing your payment/i)).toBeInTheDocument(); }); it('does not trigger confetti in loading state', () => { render(); expect(mockConfetti).not.toHaveBeenCalled(); }); }); describe('success state', () => { it('shows success message', () => { render(); expect(screen.getByText('Welcome to Pro!')).toBeInTheDocument(); expect(screen.getByText(/AI powers are now unlocked/i)).toBeInTheDocument(); }); it('shows generation count badge', () => { render(); expect(screen.getByText('250 AI generations ready to use')).toBeInTheDocument(); }); it('shows "Start Creating" button', () => { render(); expect(screen.getByText('Start Creating')).toBeInTheDocument(); }); it('calls onBack when "Start Creating" is clicked', () => { const mockOnBack = vi.fn(); render(); fireEvent.click(screen.getByText('Start Creating')); expect(mockOnBack).toHaveBeenCalled(); }); it('triggers confetti animation on mount', async () => { render(); vi.advanceTimersByTime(100); expect(mockConfetti).toHaveBeenCalled(); }); it('confetti uses correct colors', async () => { render(); vi.advanceTimersByTime(100); expect(mockConfetti).toHaveBeenCalledWith( expect.objectContaining({ colors: ['#8B5CF6', '#6366F1', '#EC4899', '#F59E0B'], }) ); }); it('only triggers confetti once', async () => { const { rerender } = render(); vi.advanceTimersByTime(3100); const callCountAfterFirst = mockConfetti.mock.calls.length; rerender(); vi.advanceTimersByTime(3100); expect(mockConfetti.mock.calls.length).toBe(callCountAfterFirst); }); }); describe('cancel state', () => { it('shows cancel message', () => { render(); expect(screen.getByText('Payment Cancelled')).toBeInTheDocument(); expect(screen.getByText(/No worries/i)).toBeInTheDocument(); }); it('shows "Go Back" button', () => { render(); expect(screen.getByText('Go Back')).toBeInTheDocument(); }); it('calls onBack when "Go Back" is clicked', () => { const mockOnBack = vi.fn(); render(); fireEvent.click(screen.getByText('Go Back')); expect(mockOnBack).toHaveBeenCalled(); }); it('does not show generation count badge', () => { render(); expect(screen.queryByText(/250 AI generations/i)).not.toBeInTheDocument(); }); it('does not trigger confetti', () => { render(); vi.advanceTimersByTime(3100); expect(mockConfetti).not.toHaveBeenCalled(); }); }); describe('UI elements', () => { it('renders PartyPopper icon for success', () => { render(); const successCard = screen.getByText('Welcome to Pro!').closest('div'); expect(successCard).toBeInTheDocument(); }); it('renders XCircle icon for cancel', () => { render(); const cancelCard = screen.getByText('Payment Cancelled').closest('div'); expect(cancelCard).toBeInTheDocument(); }); it('has green gradient background for success', () => { render(); const gradientBg = document.querySelector('.from-green-50'); expect(gradientBg).toBeInTheDocument(); }); it('does not have green gradient for cancel', () => { render(); const gradientBg = document.querySelector('.from-green-50'); expect(gradientBg).not.toBeInTheDocument(); }); }); describe('button styling', () => { it('success button has dark styling', () => { render(); const button = screen.getByText('Start Creating').closest('button'); expect(button?.className).toContain('bg-gray-900'); }); it('cancel button has theme primary styling', () => { render(); const button = screen.getByText('Go Back').closest('button'); expect(button?.className).toContain('bg-theme-primary'); }); }); describe('without onBack callback', () => { it('renders buttons without errors when onBack is undefined', () => { render(); const button = screen.getByText('Start Creating'); expect(button).toBeInTheDocument(); fireEvent.click(button); }); }); });