di usage in website
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -21,15 +22,70 @@ 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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
// 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> = {}): LeagueAdminRosterJoinRequestViewModel {
|
||||
return {
|
||||
@@ -55,6 +111,10 @@ function makeMember(overrides: Partial<LeagueAdminRosterMemberViewModel> = {}):
|
||||
|
||||
describe('RosterAdminPage', () => {
|
||||
beforeEach(() => {
|
||||
// Reset mock data
|
||||
mockJoinRequests = [];
|
||||
mockMembers = [];
|
||||
|
||||
mockLeagueService = {
|
||||
getAdminRosterJoinRequests: vi.fn(),
|
||||
getAdminRosterMembers: vi.fn(),
|
||||
@@ -76,8 +136,9 @@ describe('RosterAdminPage', () => {
|
||||
makeMember({ driverId: 'driver-11', driverName: 'Member Eleven', role: 'admin' }),
|
||||
];
|
||||
|
||||
mockLeagueService.getAdminRosterJoinRequests.mockResolvedValue(joinRequests);
|
||||
mockLeagueService.getAdminRosterMembers.mockResolvedValue(members);
|
||||
// Set mock data for hooks
|
||||
mockJoinRequests = joinRequests;
|
||||
mockMembers = members;
|
||||
|
||||
render(<RosterAdminPage />);
|
||||
|
||||
@@ -91,9 +152,8 @@ describe('RosterAdminPage', () => {
|
||||
});
|
||||
|
||||
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);
|
||||
mockJoinRequests = [makeJoinRequest({ id: 'jr-1', driverName: 'Driver One' })];
|
||||
mockMembers = [makeMember({ driverId: 'driver-10', driverName: 'Member Ten' })];
|
||||
|
||||
render(<RosterAdminPage />);
|
||||
|
||||
@@ -101,19 +161,14 @@ describe('RosterAdminPage', () => {
|
||||
|
||||
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);
|
||||
mockJoinRequests = [makeJoinRequest({ id: 'jr-2', driverName: 'Driver Two' })];
|
||||
mockMembers = [makeMember({ driverId: 'driver-10', driverName: 'Member Ten' })];
|
||||
|
||||
render(<RosterAdminPage />);
|
||||
|
||||
@@ -121,21 +176,14 @@ describe('RosterAdminPage', () => {
|
||||
|
||||
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);
|
||||
mockJoinRequests = [];
|
||||
mockMembers = [makeMember({ driverId: 'driver-11', driverName: 'Member Eleven', role: 'member' })];
|
||||
|
||||
render(<RosterAdminPage />);
|
||||
|
||||
@@ -146,21 +194,14 @@ describe('RosterAdminPage', () => {
|
||||
|
||||
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);
|
||||
mockJoinRequests = [];
|
||||
mockMembers = [makeMember({ driverId: 'driver-12', driverName: 'Member Twelve', role: 'member' })];
|
||||
|
||||
render(<RosterAdminPage />);
|
||||
|
||||
@@ -168,10 +209,6 @@ describe('RosterAdminPage', () => {
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import Card from '@/components/ui/Card';
|
||||
import type { LeagueAdminRosterJoinRequestViewModel } from '@/lib/view-models/LeagueAdminRosterJoinRequestViewModel';
|
||||
import type { LeagueAdminRosterMemberViewModel } from '@/lib/view-models/LeagueAdminRosterMemberViewModel';
|
||||
import type { MembershipRole } from '@/lib/types/MembershipRole';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
useLeagueRosterJoinRequests,
|
||||
useLeagueRosterMembers,
|
||||
useApproveJoinRequest,
|
||||
useRejectJoinRequest,
|
||||
useUpdateMemberRole,
|
||||
useRemoveMember,
|
||||
} from '@/hooks/league/useLeagueRosterAdmin';
|
||||
|
||||
const ROLE_OPTIONS: MembershipRole[] = ['owner', 'admin', 'steward', 'member'];
|
||||
|
||||
@@ -14,56 +19,56 @@ export function RosterAdminPage() {
|
||||
const params = useParams();
|
||||
const leagueId = params.id as string;
|
||||
|
||||
const { leagueService } = useServices();
|
||||
// Fetch data using React-Query + DI
|
||||
const {
|
||||
data: joinRequests = [],
|
||||
isLoading: loadingJoinRequests,
|
||||
refetch: refetchJoinRequests,
|
||||
} = useLeagueRosterJoinRequests(leagueId);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [joinRequests, setJoinRequests] = useState<LeagueAdminRosterJoinRequestViewModel[]>([]);
|
||||
const [members, setMembers] = useState<LeagueAdminRosterMemberViewModel[]>([]);
|
||||
const {
|
||||
data: members = [],
|
||||
isLoading: loadingMembers,
|
||||
refetch: refetchMembers,
|
||||
} = useLeagueRosterMembers(leagueId);
|
||||
|
||||
const loadRoster = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [requestsVm, membersVm] = await Promise.all([
|
||||
leagueService.getAdminRosterJoinRequests(leagueId),
|
||||
leagueService.getAdminRosterMembers(leagueId),
|
||||
]);
|
||||
setJoinRequests(requestsVm);
|
||||
setMembers(membersVm);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const loading = loadingJoinRequests || loadingMembers;
|
||||
|
||||
useEffect(() => {
|
||||
void loadRoster();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [leagueId]);
|
||||
// Mutations
|
||||
const approveMutation = useApproveJoinRequest({
|
||||
onSuccess: () => refetchJoinRequests(),
|
||||
});
|
||||
|
||||
const rejectMutation = useRejectJoinRequest({
|
||||
onSuccess: () => refetchJoinRequests(),
|
||||
});
|
||||
|
||||
const updateRoleMutation = useUpdateMemberRole({
|
||||
onError: () => refetchMembers(), // Refetch on error to restore state
|
||||
});
|
||||
|
||||
const removeMemberMutation = useRemoveMember({
|
||||
onSuccess: () => refetchMembers(),
|
||||
});
|
||||
|
||||
const pendingCountLabel = useMemo(() => {
|
||||
return joinRequests.length === 1 ? '1 request' : `${joinRequests.length} requests`;
|
||||
}, [joinRequests.length]);
|
||||
|
||||
const handleApprove = async (joinRequestId: string) => {
|
||||
await leagueService.approveJoinRequest(leagueId, joinRequestId);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== joinRequestId));
|
||||
await approveMutation.mutateAsync({ leagueId, joinRequestId });
|
||||
};
|
||||
|
||||
const handleReject = async (joinRequestId: string) => {
|
||||
await leagueService.rejectJoinRequest(leagueId, joinRequestId);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== joinRequestId));
|
||||
await rejectMutation.mutateAsync({ leagueId, joinRequestId });
|
||||
};
|
||||
|
||||
const handleRoleChange = async (driverId: string, newRole: MembershipRole) => {
|
||||
setMembers((prev) => prev.map((m) => (m.driverId === driverId ? { ...m, role: newRole } : m)));
|
||||
const result = await leagueService.updateMemberRole(leagueId, driverId, newRole);
|
||||
if (!result.success) {
|
||||
await loadRoster();
|
||||
}
|
||||
await updateRoleMutation.mutateAsync({ leagueId, driverId, role: newRole });
|
||||
};
|
||||
|
||||
const handleRemove = async (driverId: string) => {
|
||||
await leagueService.removeMember(leagueId, driverId);
|
||||
setMembers((prev) => prev.filter((m) => m.driverId !== driverId));
|
||||
await removeMemberMutation.mutateAsync({ leagueId, driverId });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user