import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; import { CustomerPreview } from '../CustomerPreview'; import { Service, Business } from '../../../types'; // Mock dependencies vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })); // Mock Lucide icons vi.mock('lucide-react', () => ({ Clock: () => , DollarSign: () => , Image: () => , CheckCircle2: () => , AlertCircle: () => , })); // Mock Badge component vi.mock('../../ui/Badge', () => ({ default: ({ children, variant, size }: any) => {children}, })); describe('CustomerPreview', () => { const mockBusiness: Business = { id: 'business-1', name: 'Test Business', primaryColor: '#2563eb', secondaryColor: '#0ea5e9', } as Business; const mockService: Service = { id: '1', name: 'Haircut', description: 'Professional haircut service', price: 50, durationMinutes: 60, photos: [], category: { id: 'cat1', name: 'Hair Services' }, variable_pricing: false, } as Service; const mockServiceWithPhoto: Service = { ...mockService, photos: ['https://example.com/photo.jpg'], }; const mockServiceWithDeposit: Service = { ...mockService, deposit_amount: 10, }; const mockServiceVariablePricing: Service = { ...mockService, variable_pricing: true, deposit_amount: 25, }; const defaultProps = { service: mockService, business: mockBusiness, }; beforeEach(() => { vi.clearAllMocks(); }); it('renders customer preview heading', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByText('Customer Preview')).toBeInTheDocument(); }); it('shows live preview badge', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByTestId('badge-info')).toBeInTheDocument(); expect(screen.getByText('Live Preview')).toBeInTheDocument(); }); it('displays service name', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByText('Haircut')).toBeInTheDocument(); }); it('displays service description', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByText('Professional haircut service')).toBeInTheDocument(); }); it('displays service category', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByText('Hair Services')).toBeInTheDocument(); }); it('displays duration', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByText('60 mins')).toBeInTheDocument(); }); it('shows clock icon for duration', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByTestId('icon-clock')).toBeInTheDocument(); }); it('displays price', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByText('50')).toBeInTheDocument(); }); it('shows dollar sign icon for price', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByTestId('icon-dollar-sign')).toBeInTheDocument(); }); it('shows image icon when no photos', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByTestId('icon-image')).toBeInTheDocument(); }); it('displays photo when available', () => { const props = { ...defaultProps, service: mockServiceWithPhoto }; render(React.createElement(CustomerPreview, props)); const img = document.querySelector('img'); expect(img).toBeInTheDocument(); expect(img).toHaveAttribute('src', 'https://example.com/photo.jpg'); expect(img).toHaveAttribute('alt', 'Haircut'); }); it('displays deposit requirement when set', () => { const props = { ...defaultProps, service: mockServiceWithDeposit }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText(/Deposit required:/)).toBeInTheDocument(); expect(screen.getByText((content, element) => { return element?.textContent === 'Deposit required: $10'; })).toBeInTheDocument(); }); it('does not show deposit when not required', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.queryByText(/Deposit required:/)).not.toBeInTheDocument(); }); it('shows variable pricing badge', () => { const props = { ...defaultProps, service: mockServiceVariablePricing }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('Variable')).toBeInTheDocument(); }); it('shows "Price varies" text for variable pricing', () => { const props = { ...defaultProps, service: mockServiceVariablePricing }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('Price varies')).toBeInTheDocument(); }); it('shows deposit for variable pricing services', () => { const props = { ...defaultProps, service: mockServiceVariablePricing }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText(/Deposit required:/)).toBeInTheDocument(); }); it('displays info alert about preview', () => { render(React.createElement(CustomerPreview, defaultProps)); expect(screen.getByTestId('icon-alert-circle')).toBeInTheDocument(); expect(screen.getByText(/This is how your service will appear to customers/)).toBeInTheDocument(); }); it('handles null service gracefully', () => { const props = { ...defaultProps, service: null }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('New Service')).toBeInTheDocument(); expect(screen.getByText('Service description will appear here...')).toBeInTheDocument(); }); it('displays default category when not set', () => { const serviceWithoutCategory = { ...mockService, category: undefined }; const props = { ...defaultProps, service: serviceWithoutCategory }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('General')).toBeInTheDocument(); }); it('uses preview data when provided', () => { const previewData = { name: 'Custom Name', description: 'Custom Description', price: 75, durationMinutes: 90, }; const props = { ...defaultProps, previewData }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('Custom Name')).toBeInTheDocument(); expect(screen.getByText('Custom Description')).toBeInTheDocument(); expect(screen.getByText('75')).toBeInTheDocument(); expect(screen.getByText('90 mins')).toBeInTheDocument(); }); it('preview data overrides service data', () => { const previewData = { name: 'Preview Name' }; const props = { ...defaultProps, previewData }; render(React.createElement(CustomerPreview, props)); // Should show preview data instead of service data expect(screen.getByText('Preview Name')).toBeInTheDocument(); expect(screen.queryByText('Haircut')).not.toBeInTheDocument(); }); it('formats price correctly', () => { const props = { ...defaultProps, service: { ...mockService, price: 123 } }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('123')).toBeInTheDocument(); }); it('handles zero price', () => { const props = { ...defaultProps, service: { ...mockService, price: 0 } }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('0')).toBeInTheDocument(); }); it('applies business colors to gradient', () => { render(React.createElement(CustomerPreview, defaultProps)); // The gradient uses business colors in the style attribute const gradientDiv = document.querySelector('[style*="gradient"]'); expect(gradientDiv).toBeInTheDocument(); }); it('displays deposit with correct formatting', () => { const props = { ...defaultProps, service: { ...mockService, deposit_amount: 15 } }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText((content, element) => { return element?.textContent === 'Deposit required: $15'; })).toBeInTheDocument(); }); it('shows default duration when not set', () => { const serviceWithoutDuration = { ...mockService, durationMinutes: undefined }; const props = { ...defaultProps, service: serviceWithoutDuration }; render(React.createElement(CustomerPreview, props)); expect(screen.getByText('30 mins')).toBeInTheDocument(); }); it('displays border styling to indicate selected preview', () => { render(React.createElement(CustomerPreview, defaultProps)); const card = document.querySelector('.border-brand-600'); expect(card).toBeInTheDocument(); }); it('displays ring styling', () => { render(React.createElement(CustomerPreview, defaultProps)); const card = document.querySelector('.ring-brand-600'); expect(card).toBeInTheDocument(); }); it('handles preview data with partial updates', () => { const previewData = { price: 99 }; const props = { ...defaultProps, previewData }; render(React.createElement(CustomerPreview, props)); // Name should still be from service expect(screen.getByText('Haircut')).toBeInTheDocument(); // Price should be from previewData expect(screen.getByText('99')).toBeInTheDocument(); }); it('merges photos from preview data', () => { const previewData = { photos: ['https://preview.com/new.jpg'] }; const props = { ...defaultProps, previewData }; render(React.createElement(CustomerPreview, props)); const img = document.querySelector('img'); expect(img).toHaveAttribute('src', 'https://preview.com/new.jpg'); }); it('handles empty photos array', () => { const props = { ...defaultProps, service: { ...mockService, photos: [] } }; render(React.createElement(CustomerPreview, props)); expect(screen.getByTestId('icon-image')).toBeInTheDocument(); }); it('uses first photo only for cover', () => { const serviceWithMultiplePhotos = { ...mockService, photos: ['https://first.com/1.jpg', 'https://second.com/2.jpg'], }; const props = { ...defaultProps, service: serviceWithMultiplePhotos }; render(React.createElement(CustomerPreview, props)); const img = document.querySelector('img'); expect(img).toHaveAttribute('src', 'https://first.com/1.jpg'); }); it('displays horizontal card layout', () => { render(React.createElement(CustomerPreview, defaultProps)); const cardContainer = document.querySelector('.flex.h-full'); expect(cardContainer).toBeInTheDocument(); }); });