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 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 new DI hooks vi.mock('@/hooks/league/useLeagueRosterAdmin', () => ({ useLeagueRosterJoinRequests: (leagueId: string) => ({ data: [...mockJoinRequests], isLoading: false, isError: false, isSuccess: true, refetch: vi.fn(), }), useLeagueRosterMembers: (leagueId: string) => ({ data: [...mockMembers], isLoading: false, isError: false, isSuccess: true, refetch: vi.fn(), }), useApproveJoinRequest: () => ({ mutate: (params: any) => { // Remove from join requests mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.joinRequestId); }, mutateAsync: async (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.joinRequestId); return { success: true }; }, isPending: false, }), useRejectJoinRequest: () => ({ mutate: (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.joinRequestId); }, mutateAsync: async (params: any) => { mockJoinRequests = mockJoinRequests.filter(req => req.id !== params.joinRequestId); return { success: true }; }, isPending: false, }), useUpdateMemberRole: () => ({ mutate: (params: any) => { const member = mockMembers.find(m => m.driverId === params.driverId); if (member) member.role = params.role; }, mutateAsync: async (params: any) => { const member = mockMembers.find(m => m.driverId === params.driverId); if (member) member.role = params.role; return { success: true }; }, isPending: false, }), useRemoveMember: () => ({ mutate: (params: any) => { mockMembers = mockMembers.filter(m => m.driverId !== params.driverId); }, mutateAsync: async (params: any) => { mockMembers = mockMembers.filter(m => m.driverId !== params.driverId); return { success: true }; }, isPending: false, }), })); function makeJoinRequest(overrides: Partial = {}): LeagueAdminRosterJoinRequestViewModel { return { id: 'jr-1', leagueId: 'league-1', driverId: 'driver-1', driverName: 'Driver One', requestedAtIso: '2025-01-01T00:00:00.000Z', message: 'Please let me in', ...overrides, }; } function makeMember(overrides: Partial = {}): LeagueAdminRosterMemberViewModel { return { driverId: 'driver-10', driverName: 'Member Ten', role: 'member', joinedAtIso: '2025-01-01T00:00:00.000Z', ...overrides, }; } describe('RosterAdminPage', () => { 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; }); it('renders join requests + members from service ViewModels', async () => { const joinRequests: LeagueAdminRosterJoinRequestViewModel[] = [ makeJoinRequest({ id: 'jr-1', driverName: 'Driver One' }), makeJoinRequest({ id: 'jr-2', driverName: 'Driver Two', driverId: 'driver-2' }), ]; const members: LeagueAdminRosterMemberViewModel[] = [ makeMember({ driverId: 'driver-10', driverName: 'Member Ten', role: 'member' }), makeMember({ driverId: 'driver-11', driverName: 'Member Eleven', role: 'admin' }), ]; // Set mock data for hooks mockJoinRequests = joinRequests; mockMembers = members; render(); 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', driverName: 'Driver One' })]; mockMembers = [makeMember({ driverId: 'driver-10', driverName: 'Member Ten' })]; render(); 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', driverName: 'Driver Two' })]; mockMembers = [makeMember({ driverId: 'driver-10', driverName: 'Member Ten' })]; render(); 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', driverName: 'Member Eleven', role: 'member' })]; render(); 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', driverName: 'Member Twelve', role: 'member' })]; render(); expect(await screen.findByText('Member Twelve')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('member-driver-12-remove')); await waitFor(() => { expect(screen.queryByText('Member Twelve')).not.toBeInTheDocument(); }); }); });