import { describe, it, expect, vi, beforeEach } from 'vitest'; import { renderHook, waitFor, act } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; // Mock dependencies vi.mock('../../api/client', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), }, })); vi.mock('../../utils/cookies', () => ({ getCookie: vi.fn(), })); import { useCurrentBusiness, useUpdateBusiness, useBusinessUsers, useResources, useCreateResource } from '../useBusiness'; import apiClient from '../../api/client'; import { getCookie } from '../../utils/cookies'; // Create wrapper const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); return function Wrapper({ children }: { children: React.ReactNode }) { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; }; describe('useBusiness hooks', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('useCurrentBusiness', () => { it('returns null when no token exists', async () => { vi.mocked(getCookie).mockReturnValue(null); const { result } = renderHook(() => useCurrentBusiness(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); expect(result.current.data).toBeNull(); expect(apiClient.get).not.toHaveBeenCalled(); }); it('fetches business and transforms data', async () => { vi.mocked(getCookie).mockReturnValue('valid-token'); const mockBusiness = { id: 1, name: 'Test Business', subdomain: 'test', primary_color: '#FF0000', secondary_color: '#00FF00', logo_url: 'https://example.com/logo.png', timezone: 'America/Denver', timezone_display_mode: 'business', tier: 'professional', status: 'active', created_at: '2024-01-01T00:00:00Z', payments_enabled: true, plan_permissions: { sms_reminders: true, api_access: true, }, }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockBusiness }); const { result } = renderHook(() => useCurrentBusiness(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(apiClient.get).toHaveBeenCalledWith('/business/current/'); expect(result.current.data).toEqual(expect.objectContaining({ id: '1', name: 'Test Business', subdomain: 'test', primaryColor: '#FF0000', secondaryColor: '#00FF00', logoUrl: 'https://example.com/logo.png', timezone: 'America/Denver', plan: 'professional', paymentsEnabled: true, })); }); it('uses default values for missing fields', async () => { vi.mocked(getCookie).mockReturnValue('valid-token'); const mockBusiness = { id: 1, name: 'Minimal Business', subdomain: 'min', }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockBusiness }); const { result } = renderHook(() => useCurrentBusiness(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(result.current.data?.primaryColor).toBe('#3B82F6'); expect(result.current.data?.secondaryColor).toBe('#1E40AF'); expect(result.current.data?.logoDisplayMode).toBe('text-only'); expect(result.current.data?.timezone).toBe('America/New_York'); expect(result.current.data?.paymentsEnabled).toBe(false); }); }); describe('useUpdateBusiness', () => { it('maps frontend fields to backend fields', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateBusiness(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ name: 'Updated Name', primaryColor: '#123456', secondaryColor: '#654321', timezone: 'America/Los_Angeles', }); }); expect(apiClient.patch).toHaveBeenCalledWith('/business/current/update/', { name: 'Updated Name', primary_color: '#123456', secondary_color: '#654321', timezone: 'America/Los_Angeles', }); }); it('handles logo fields', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateBusiness(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ logoUrl: 'https://new-logo.com/logo.png', emailLogoUrl: 'https://new-logo.com/email.png', logoDisplayMode: 'logo-only', }); }); expect(apiClient.patch).toHaveBeenCalledWith('/business/current/update/', { logo_url: 'https://new-logo.com/logo.png', email_logo_url: 'https://new-logo.com/email.png', logo_display_mode: 'logo-only', }); }); it('handles booking-related settings', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateBusiness(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ resourcesCanReschedule: true, requirePaymentMethodToBook: true, cancellationWindowHours: 24, lateCancellationFeePercent: 50, }); }); expect(apiClient.patch).toHaveBeenCalledWith('/business/current/update/', { resources_can_reschedule: true, require_payment_method_to_book: true, cancellation_window_hours: 24, late_cancellation_fee_percent: 50, }); }); it('handles website and dashboard content', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateBusiness(), { wrapper: createWrapper(), }); const websitePages = { home: { title: 'Welcome' } }; const dashboardContent = [{ type: 'text', content: 'Hello' }]; await act(async () => { await result.current.mutateAsync({ websitePages, customerDashboardContent: dashboardContent, initialSetupComplete: true, }); }); expect(apiClient.patch).toHaveBeenCalledWith('/business/current/update/', { website_pages: websitePages, customer_dashboard_content: dashboardContent, initial_setup_complete: true, }); }); }); describe('useBusinessUsers', () => { it('fetches staff users', async () => { const mockUsers = [ { id: 1, name: 'Staff 1' }, { id: 2, name: 'Staff 2' }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockUsers }); const { result } = renderHook(() => useBusinessUsers(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(apiClient.get).toHaveBeenCalledWith('/staff/'); expect(result.current.data).toEqual(mockUsers); }); }); describe('useResources', () => { it('fetches resources', async () => { const mockResources = [ { id: 1, name: 'Resource 1', type: 'equipment' }, { id: 2, name: 'Resource 2', type: 'room' }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockResources }); const { result } = renderHook(() => useResources(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(apiClient.get).toHaveBeenCalledWith('/resources/'); expect(result.current.data).toEqual(mockResources); }); it('handles empty resources list', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: [] }); const { result } = renderHook(() => useResources(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(result.current.data).toEqual([]); }); it('handles fetch error', async () => { vi.mocked(apiClient.get).mockRejectedValue(new Error('Network error')); const { result } = renderHook(() => useResources(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isError).toBe(true); }); expect(result.current.error).toBeDefined(); }); }); describe('useCreateResource', () => { it('creates a resource', async () => { const mockResource = { id: 3, name: 'New Resource', type: 'equipment' }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResource }); const { result } = renderHook(() => useCreateResource(), { wrapper: createWrapper(), }); await act(async () => { const data = await result.current.mutateAsync({ name: 'New Resource', type: 'equipment' }); expect(data).toEqual(mockResource); }); expect(apiClient.post).toHaveBeenCalledWith('/resources/', { name: 'New Resource', type: 'equipment', }); }); it('creates a resource with user_id', async () => { const mockResource = { id: 4, name: 'Staff Resource', type: 'staff', user_id: 'user-123' }; vi.mocked(apiClient.post).mockResolvedValue({ data: mockResource }); const { result } = renderHook(() => useCreateResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ name: 'Staff Resource', type: 'staff', user_id: 'user-123' }); }); expect(apiClient.post).toHaveBeenCalledWith('/resources/', { name: 'Staff Resource', type: 'staff', user_id: 'user-123', }); }); it('handles creation error', async () => { vi.mocked(apiClient.post).mockRejectedValue(new Error('Validation failed')); const { result } = renderHook(() => useCreateResource(), { wrapper: createWrapper(), }); await expect( act(async () => { await result.current.mutateAsync({ name: '', type: 'equipment' }); }) ).rejects.toThrow('Validation failed'); }); }); });