import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; 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' }), })); vi.mock('@/lib/services/ServiceProvider', async (importOriginal) => { const actual = (await importOriginal()) as object; return { ...actual, useServices: () => ({ leagueService: mockLeagueService, }), }; }); 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(() => { 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' }), ]; mockLeagueService.getAdminRosterJoinRequests.mockResolvedValue(joinRequests); mockLeagueService.getAdminRosterMembers.mockResolvedValue(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 () => { mockLeagueService.getAdminRosterJoinRequests.mockResolvedValue([makeJoinRequest({ id: 'jr-1', driverName: 'Driver One' })]); mockLeagueService.getAdminRosterMembers.mockResolvedValue([makeMember({ driverId: 'driver-10', driverName: 'Member Ten' })]); mockLeagueService.approveJoinRequest.mockResolvedValue({ success: true } as any); render(); expect(await screen.findByText('Driver One')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('join-request-jr-1-approve')); await waitFor(() => { expect(mockLeagueService.approveJoinRequest).toHaveBeenCalledWith('league-1', 'jr-1'); }); await waitFor(() => { expect(screen.queryByText('Driver One')).not.toBeInTheDocument(); }); }); it('rejects a join request and removes it from the pending list', async () => { mockLeagueService.getAdminRosterJoinRequests.mockResolvedValue([makeJoinRequest({ id: 'jr-2', driverName: 'Driver Two' })]); mockLeagueService.getAdminRosterMembers.mockResolvedValue([makeMember({ driverId: 'driver-10', driverName: 'Member Ten' })]); mockLeagueService.rejectJoinRequest.mockResolvedValue({ success: true } as any); render(); expect(await screen.findByText('Driver Two')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('join-request-jr-2-reject')); await waitFor(() => { expect(mockLeagueService.rejectJoinRequest).toHaveBeenCalledWith('league-1', 'jr-2'); }); await waitFor(() => { expect(screen.queryByText('Driver Two')).not.toBeInTheDocument(); }); }); it('changes a member role via service and updates the displayed role', async () => { mockLeagueService.getAdminRosterJoinRequests.mockResolvedValue([]); mockLeagueService.getAdminRosterMembers.mockResolvedValue([ makeMember({ driverId: 'driver-11', driverName: 'Member Eleven', role: 'member' }), ]); mockLeagueService.updateMemberRole.mockResolvedValue({ success: true } as any); 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(mockLeagueService.updateMemberRole).toHaveBeenCalledWith('league-1', 'driver-11', '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 () => { mockLeagueService.getAdminRosterJoinRequests.mockResolvedValue([]); mockLeagueService.getAdminRosterMembers.mockResolvedValue([ makeMember({ driverId: 'driver-12', driverName: 'Member Twelve', role: 'member' }), ]); mockLeagueService.removeMember.mockResolvedValue({ success: true } as any); render(); expect(await screen.findByText('Member Twelve')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('member-driver-12-remove')); await waitFor(() => { expect(mockLeagueService.removeMember).toHaveBeenCalledWith('league-1', 'driver-12'); }); await waitFor(() => { expect(screen.queryByText('Member Twelve')).not.toBeInTheDocument(); }); }); });