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);
});
});
});