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 apiClient vi.mock('../../api/client', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), }, })); import { useServices, useService, useCreateService, useUpdateService, useDeleteService, useReorderServices, } from '../useServices'; import apiClient from '../../api/client'; // 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('useServices hooks', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('useServices', () => { it('fetches services and transforms data', async () => { const mockServices = [ { id: 1, name: 'Haircut', duration: 30, price: '25.00', description: 'Basic haircut' }, { id: 2, name: 'Color', duration_minutes: 60, price: '75.00', variable_pricing: true }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockServices }); const { result } = renderHook(() => useServices(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(apiClient.get).toHaveBeenCalledWith('/services/'); expect(result.current.data).toHaveLength(2); expect(result.current.data?.[0]).toEqual(expect.objectContaining({ id: '1', name: 'Haircut', durationMinutes: 30, price: 25, description: 'Basic haircut', })); expect(result.current.data?.[1].variable_pricing).toBe(true); }); it('handles missing fields with defaults', async () => { const mockServices = [ { id: 1, name: 'Service', price: '10.00', duration: 15 }, ]; vi.mocked(apiClient.get).mockResolvedValue({ data: mockServices }); const { result } = renderHook(() => useServices(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(result.current.data?.[0].description).toBe(''); expect(result.current.data?.[0].displayOrder).toBe(0); expect(result.current.data?.[0].photos).toEqual([]); }); }); describe('useService', () => { it('fetches single service by id', async () => { const mockService = { id: 1, name: 'Premium Cut', duration: 45, price: '50.00', description: 'Premium service', photos: ['photo1.jpg'], }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockService }); const { result } = renderHook(() => useService('1'), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(apiClient.get).toHaveBeenCalledWith('/services/1/'); expect(result.current.data?.name).toBe('Premium Cut'); }); it('does not fetch when id is empty', async () => { const { result } = renderHook(() => useService(''), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); expect(apiClient.get).not.toHaveBeenCalled(); }); }); describe('useCreateService', () => { it('creates service with correct field mapping', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1 } }); const { result } = renderHook(() => useCreateService(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ name: 'New Service', durationMinutes: 45, price: 35.99, description: 'Test description', photos: ['photo.jpg'], }); }); expect(apiClient.post).toHaveBeenCalledWith('/services/', { name: 'New Service', duration: 45, price: '35.99', description: 'Test description', photos: ['photo.jpg'], }); }); it('includes pricing fields when provided', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1 } }); const { result } = renderHook(() => useCreateService(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ name: 'Priced Service', durationMinutes: 30, price: 100, variable_pricing: true, deposit_amount: 25, deposit_percent: 25, }); }); expect(apiClient.post).toHaveBeenCalledWith('/services/', expect.objectContaining({ variable_pricing: true, deposit_amount: 25, deposit_percent: 25, })); }); }); describe('useUpdateService', () => { it('updates service with mapped fields', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateService(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ id: '1', updates: { name: 'Updated Name', price: 50 }, }); }); expect(apiClient.patch).toHaveBeenCalledWith('/services/1/', { name: 'Updated Name', price: '50', }); }); }); describe('useDeleteService', () => { it('deletes service by id', async () => { vi.mocked(apiClient.delete).mockResolvedValue({}); const { result } = renderHook(() => useDeleteService(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync('3'); }); expect(apiClient.delete).toHaveBeenCalledWith('/services/3/'); }); }); describe('useReorderServices', () => { it('sends reorder request with converted ids', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useReorderServices(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync(['3', '1', '2']); }); expect(apiClient.post).toHaveBeenCalledWith('/services/reorder/', { order: [3, 1, 2], }); }); }); });