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 { useResources, useResource, useCreateResource, useUpdateResource, useDeleteResource, } from '../useResources'; 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('useResources hooks', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('useResources', () => { it('fetches resources and transforms data', async () => { const mockResources = [ { id: 1, name: 'Room 1', type: 'ROOM', max_concurrent_events: 2 }, { id: 2, name: 'Staff 1', type: 'STAFF', user_id: 10 }, ]; 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).toHaveLength(2); expect(result.current.data?.[0]).toEqual({ id: '1', name: 'Room 1', type: 'ROOM', userId: undefined, maxConcurrentEvents: 2, savedLaneCount: undefined, userCanEditSchedule: false, }); }); it('applies type filter', async () => { vi.mocked(apiClient.get).mockResolvedValue({ data: [] }); renderHook(() => useResources({ type: 'STAFF' }), { wrapper: createWrapper(), }); await waitFor(() => { expect(apiClient.get).toHaveBeenCalledWith('/resources/?type=STAFF'); }); }); }); describe('useResource', () => { it('fetches single resource by id', async () => { const mockResource = { id: 1, name: 'Room 1', type: 'ROOM', max_concurrent_events: 1, }; vi.mocked(apiClient.get).mockResolvedValue({ data: mockResource }); const { result } = renderHook(() => useResource('1'), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(apiClient.get).toHaveBeenCalledWith('/resources/1/'); expect(result.current.data?.name).toBe('Room 1'); }); it('does not fetch when id is empty', async () => { const { result } = renderHook(() => useResource(''), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); expect(apiClient.get).not.toHaveBeenCalled(); }); }); describe('useCreateResource', () => { it('creates resource with backend field mapping', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1 } }); const { result } = renderHook(() => useCreateResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ name: 'New Room', type: 'ROOM', maxConcurrentEvents: 3, }); }); expect(apiClient.post).toHaveBeenCalledWith('/resources/', { name: 'New Room', type: 'ROOM', user: null, timezone: 'UTC', max_concurrent_events: 3, }); }); it('converts userId to user integer', async () => { vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1 } }); const { result } = renderHook(() => useCreateResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ name: 'Staff', type: 'STAFF', userId: '42', }); }); expect(apiClient.post).toHaveBeenCalledWith('/resources/', expect.objectContaining({ user: 42, })); }); }); describe('useUpdateResource', () => { it('updates resource with mapped fields', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1 } }); const { result } = renderHook(() => useUpdateResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ id: '1', updates: { name: 'Updated Room', maxConcurrentEvents: 5 }, }); }); expect(apiClient.patch).toHaveBeenCalledWith('/resources/1/', { name: 'Updated Room', max_concurrent_events: 5, }); }); it('handles userId update', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ id: '1', updates: { userId: '10' }, }); }); expect(apiClient.patch).toHaveBeenCalledWith('/resources/1/', { user: 10, }); }); it('sets user to null when userId is empty', async () => { vi.mocked(apiClient.patch).mockResolvedValue({ data: {} }); const { result } = renderHook(() => useUpdateResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync({ id: '1', updates: { userId: '' }, }); }); expect(apiClient.patch).toHaveBeenCalledWith('/resources/1/', { user: null, }); }); }); describe('useDeleteResource', () => { it('deletes resource by id', async () => { vi.mocked(apiClient.delete).mockResolvedValue({}); const { result } = renderHook(() => useDeleteResource(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync('5'); }); expect(apiClient.delete).toHaveBeenCalledWith('/resources/5/'); }); }); });