resolve todos in website

This commit is contained in:
2025-12-20 12:22:48 +01:00
parent a87cf27fb9
commit 20588e1c0b
39 changed files with 1238 additions and 359 deletions

View File

@@ -2,7 +2,7 @@
import Image from 'next/image';
import Link from 'next/link';
import type { DriverDTO } from '@core/racing/application/dto/DriverDTO';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import DriverRating from '@/components/profile/DriverRatingPill';
export interface DriverSummaryPillProps {

View File

@@ -0,0 +1,120 @@
import React from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import UserPill from './UserPill';
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
// Mock useAuth to control session state
vi.mock('@/lib/auth/AuthContext', () => {
return {
useAuth: () => mockedAuthValue,
};
});
// Mock effective driver id hook
vi.mock('@/hooks/useEffectiveDriverId', () => {
return {
useEffectiveDriverId: () => mockedDriverId,
};
});
// Mock services hook to inject stub driverService/mediaService
const mockFindById = vi.fn<[], Promise<DriverDTO | null>>();
const mockGetDriverAvatar = vi.fn<(driverId: string) => string>();
vi.mock('@/lib/services/ServiceProvider', () => {
return {
useServices: () => ({
driverService: {
findById: mockFindById,
},
mediaService: {
getDriverAvatar: mockGetDriverAvatar,
},
}),
};
});
interface MockSessionUser {
id: string;
}
interface MockSession {
user: MockSessionUser | null;
}
let mockedAuthValue: { session: MockSession | null } = { session: null };
let mockedDriverId: string | null = null;
// Provide global stats helpers used by UserPill's rating/rank computation
// They are UI-level helpers, so a minimal stub is sufficient for these tests.
(globalThis as any).getDriverStats = (driverId: string) => ({
driverId,
rating: 2000,
overallRank: 10,
wins: 5,
});
(globalThis as any).getAllDriverRankings = () => [
{ driverId: 'driver-1', rating: 2100 },
{ driverId: 'driver-2', rating: 2000 },
];
describe('UserPill', () => {
beforeEach(() => {
mockedAuthValue = { session: null };
mockedDriverId = null;
mockFindById.mockReset();
mockGetDriverAvatar.mockReset();
});
it('renders auth links when there is no session', () => {
mockedAuthValue = { session: null };
const { container } = render(<UserPill />);
expect(screen.getByText('Sign In')).toBeInTheDocument();
expect(screen.getByText('Get Started')).toBeInTheDocument();
expect(mockFindById).not.toHaveBeenCalled();
expect(container).toMatchSnapshot();
});
it('does not load driver when there is no primary driver id', async () => {
mockedAuthValue = { session: { user: { id: 'user-1' } } };
mockedDriverId = null;
const { container } = render(<UserPill />);
await waitFor(() => {
// component should render nothing in this state
expect(container.firstChild).toBeNull();
});
expect(mockFindById).not.toHaveBeenCalled();
});
it('loads driver via driverService and uses mediaService avatar', async () => {
const driver: DriverDTO = {
id: 'driver-1',
iracingId: 'ir-123',
name: 'Test Driver',
country: 'DE',
};
mockedAuthValue = { session: { user: { id: 'user-1' } } };
mockedDriverId = driver.id;
mockFindById.mockResolvedValue(driver);
mockGetDriverAvatar.mockImplementation((driverId: string) => `/api/media/avatar/${driverId}`);
render(<UserPill />);
await waitFor(() => {
expect(screen.getByText('Test Driver')).toBeInTheDocument();
});
expect(mockFindById).toHaveBeenCalledWith('driver-1');
expect(mockGetDriverAvatar).toHaveBeenCalledWith('driver-1');
});
});

View File

@@ -8,10 +8,8 @@ import { useEffect, useMemo, useState } from 'react';
import DriverSummaryPill from '@/components/profile/DriverSummaryPill';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import type { DriverDTO } from '@core/racing/application/dto/DriverDTO';
import { EntityMappers } from '@core/racing/application/mappers/EntityMappers';
// TODO EntityMapper is legacy. Must use ´useServices` hook.
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
import { useServices } from '@/lib/services/ServiceProvider';
// Hook to detect sponsor mode
function useSponsorMode(): boolean {
@@ -84,6 +82,7 @@ function SponsorSummaryPill({
export default function UserPill() {
const { session } = useAuth();
const { driverService, mediaService } = useServices();
const [driver, setDriver] = useState<DriverDTO | null>(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const isSponsorMode = useSponsorMode();
@@ -103,19 +102,18 @@ export default function UserPill() {
return;
}
const repo = getDriverRepository();
const entity = await repo.findById(primaryDriverId);
const dto = await driverService.findById(primaryDriverId);
if (!cancelled) {
setDriver(EntityMappers.toDriverDTO(entity));
setDriver(dto);
}
}
loadDriver();
void loadDriver();
return () => {
cancelled = true;
};
}, [primaryDriverId]);
}, [primaryDriverId, driverService]);
const data = useMemo(() => {
if (!session?.user || !primaryDriverId || !driver) {
@@ -153,7 +151,7 @@ export default function UserPill() {
}
}
const avatarSrc = getImageService().getDriverAvatar(primaryDriverId);
const avatarSrc = mediaService.getDriverAvatar(primaryDriverId);
return {
driver,
@@ -161,7 +159,7 @@ export default function UserPill() {
rating,
rank,
};
}, [session, driver, primaryDriverId]);
}, [session, driver, primaryDriverId, mediaService]);
// Close menu when clicking outside
useEffect(() => {