import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import type { Mocked } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { LeagueAdminRosterJoinRequestViewModel } from '@/lib/view-models/LeagueAdminRosterJoinRequestViewModel'; import type { LeagueAdminRosterMemberViewModel } from '@/lib/view-models/LeagueAdminRosterMemberViewModel'; import { RosterAdminPage } from './RosterAdminPage'; type RosterAdminLeagueService = { getAdminRosterJoinRequests(leagueId: string): Promise; getAdminRosterMembers(leagueId: string): Promise; approveJoinRequest(leagueId: string, joinRequestId: string): Promise<{ success: boolean }>; rejectJoinRequest(leagueId: string, joinRequestId: string): Promise<{ success: boolean }>; updateMemberRole(leagueId: string, driverId: string, role: string): Promise<{ success: boolean }>; removeMember(leagueId: string, driverId: string): Promise<{ success: boolean }>; }; let mockLeagueService: Mocked; vi.mock('next/navigation', () => ({ useParams: () => ({ id: 'league-1' }), })); // Mock data storage let mockJoinRequests: any[] = []; let mockMembers: any[] = []; // Mock the hooks directly vi.mock('@/lib/hooks/league/useLeagueRosterAdmin', () => ({ useLeagueJoinRequests: (leagueId: string) => ({ data: [...mockJoinRequests], isLoading: false, isError: false, isSuccess: true, refetch: vi.fn(), }), useLeagueRosterAdmin: (leagueId: string) => ({ data: [...mockMembers], isLoading: false, isError: false, isSuccess: true, refetch: vi.fn(), }), useApproveJoinRequest: (options?: any) => ({ mutate: (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.requestId); if (options?.onSuccess) options.onSuccess(); }, mutateAsync: async (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.requestId); if (options?.onSuccess) options.onSuccess(); return { success: true }; }, isPending: false, }), useRejectJoinRequest: (options?: any) => ({ mutate: (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.requestId); if (options?.onSuccess) options.onSuccess(); }, mutateAsync: async (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.requestId); if (options?.onSuccess) options.onSuccess(); return { success: true }; }, isPending: false, }), useUpdateMemberRole: (options?: any) => ({ mutate: (params: any) => { const member = mockMembers.find(m => m.driverId === params.driverId); if (member) member.role = params.newRole; if (options?.onError) options.onError(); }, mutateAsync: async (params: any) => { const member = mockMembers.find(m => m.driverId === params.driverId); if (member) member.role = params.newRole; if (options?.onError) options.onError(); return { success: true }; }, isPending: false, }), useRemoveMember: (options?: any) => ({ mutate: (params: any) => { mockMembers = mockMembers.filter(m => m.driverId !== params.driverId); if (options?.onSuccess) options.onSuccess(); }, mutateAsync: async (params: any) => { mockMembers = mockMembers.filter(m => m.driverId !== params.driverId); if (options?.onSuccess) options.onSuccess(); return { success: true }; }, isPending: false, }), })); function makeJoinRequest(overrides: Partial = {}): LeagueAdminRosterJoinRequestViewModel { return { id: 'jr-1', leagueId: 'league-1', driver: { id: 'driver-1', name: 'Driver One', }, requestedAt: '2025-01-01T00:00:00.000Z', message: 'Please let me in', ...overrides, }; } function makeMember(overrides: Partial = {}): LeagueAdminRosterMemberViewModel { return { driverId: 'driver-10', driver: { id: 'driver-10', name: 'Member Ten', }, role: 'member', joinedAt: '2025-01-01T00:00:00.000Z', ...overrides, }; } describe('RosterAdminPage', () => { let queryClient: QueryClient; beforeEach(() => { // Reset mock data mockJoinRequests = []; mockMembers = []; mockLeagueService = { getAdminRosterJoinRequests: vi.fn(), getAdminRosterMembers: vi.fn(), approveJoinRequest: vi.fn(), rejectJoinRequest: vi.fn(), updateMemberRole: vi.fn(), removeMember: vi.fn(), } as any; // Create a new QueryClient for each test queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, mutations: { retry: false, }, }, }); }); const renderWithProviders = (component: React.ReactNode) => { return render( {component} ); }; it('renders join requests + members from service ViewModels', async () => { const joinRequests: LeagueAdminRosterJoinRequestViewModel[] = [ makeJoinRequest({ id: 'jr-1', driver: { id: 'driver-1', name: 'Driver One' } }), makeJoinRequest({ id: 'jr-2', driver: { id: 'driver-2', name: 'Driver Two' } }), ]; const members: LeagueAdminRosterMemberViewModel[] = [ makeMember({ driverId: 'driver-10', driver: { id: 'driver-10', name: 'Member Ten' }, role: 'member' }), makeMember({ driverId: 'driver-11', driver: { id: 'driver-11', name: 'Member Eleven' }, role: 'admin' }), ]; // Set mock data for hooks mockJoinRequests = joinRequests; mockMembers = members; renderWithProviders(); expect(await screen.findByText('Roster Admin')).toBeInTheDocument(); expect(await screen.findByText('Driver One')).toBeInTheDocument(); expect(screen.getByText('Driver Two')).toBeInTheDocument(); expect(await screen.findByText('Member Ten')).toBeInTheDocument(); expect(screen.getByText('Member Eleven')).toBeInTheDocument(); }); it('approves a join request and removes it from the pending list', async () => { mockJoinRequests = [makeJoinRequest({ id: 'jr-1', driver: { id: 'driver-1', name: 'Driver One' } })]; mockMembers = [makeMember({ driverId: 'driver-10', driver: { id: 'driver-10', name: 'Member Ten' } })]; renderWithProviders(); expect(await screen.findByText('Driver One')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('join-request-jr-1-approve')); await waitFor(() => { expect(screen.queryByText('Driver One')).not.toBeInTheDocument(); }); }); it('rejects a join request and removes it from the pending list', async () => { mockJoinRequests = [makeJoinRequest({ id: 'jr-2', driver: { id: 'driver-2', name: 'Driver Two' } })]; mockMembers = [makeMember({ driverId: 'driver-10', driver: { id: 'driver-10', name: 'Member Ten' } })]; renderWithProviders(); expect(await screen.findByText('Driver Two')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('join-request-jr-2-reject')); await waitFor(() => { expect(screen.queryByText('Driver Two')).not.toBeInTheDocument(); }); }); it('changes a member role via service and updates the displayed role', async () => { mockJoinRequests = []; mockMembers = [makeMember({ driverId: 'driver-11', driver: { id: 'driver-11', name: 'Member Eleven' }, role: 'member' })]; renderWithProviders(); expect(await screen.findByText('Member Eleven')).toBeInTheDocument(); const roleSelect = screen.getByLabelText('Role for Member Eleven') as HTMLSelectElement; expect(roleSelect.value).toBe('member'); fireEvent.change(roleSelect, { target: { value: 'admin' } }); await waitFor(() => { expect((screen.getByLabelText('Role for Member Eleven') as HTMLSelectElement).value).toBe('admin'); }); }); it('removes a member via service and removes them from the list', async () => { mockJoinRequests = []; mockMembers = [makeMember({ driverId: 'driver-12', driver: { id: 'driver-12', name: 'Member Twelve' }, role: 'member' })]; renderWithProviders(); expect(await screen.findByText('Member Twelve')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('member-driver-12-remove')); await waitFor(() => { expect(screen.queryByText('Member Twelve')).not.toBeInTheDocument(); }); }); });