2054 lines
63 KiB
TypeScript
2054 lines
63 KiB
TypeScript
/**
|
|
* View Data Layer Tests - Leaderboards Functionality
|
|
*
|
|
* This test file covers the view data layer for leaderboards functionality.
|
|
*
|
|
* The view data layer is responsible for:
|
|
* - DTO → UI model mapping
|
|
* - Formatting, sorting, and grouping
|
|
* - Derived fields and defaults
|
|
* - UI-specific semantics
|
|
*
|
|
* This layer isolates the UI from API churn by providing a stable interface
|
|
* between the API layer and the presentation layer.
|
|
*
|
|
* Test coverage includes:
|
|
* - Leaderboard data transformation and ranking calculations
|
|
* - Driver leaderboard view models (overall, per-race, per-season)
|
|
* - Team leaderboard view models (constructor standings, team performance)
|
|
* - Leaderboard statistics and metrics formatting
|
|
* - Derived leaderboard fields (points, positions, gaps, intervals, etc.)
|
|
* - Default values and fallbacks for leaderboard views
|
|
* - Leaderboard-specific formatting (lap times, gaps, points, positions, etc.)
|
|
* - Data grouping and categorization for leaderboard components
|
|
* - Leaderboard sorting and filtering view models
|
|
* - Real-time leaderboard updates and state management
|
|
* - Historical leaderboard data transformation
|
|
* - Leaderboard comparison and trend analysis view models
|
|
*/
|
|
|
|
import { LeaderboardsViewDataBuilder } from '@/lib/builders/view-data/LeaderboardsViewDataBuilder';
|
|
import { DriverRankingsViewDataBuilder } from '@/lib/builders/view-data/DriverRankingsViewDataBuilder';
|
|
import { TeamRankingsViewDataBuilder } from '@/lib/builders/view-data/TeamRankingsViewDataBuilder';
|
|
import { WinRateDisplay } from '@/lib/display-objects/WinRateDisplay';
|
|
import { MedalDisplay } from '@/lib/display-objects/MedalDisplay';
|
|
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
|
import type { GetTeamsLeaderboardOutputDTO } from '@/lib/types/generated/GetTeamsLeaderboardOutputDTO';
|
|
import type { TeamLeaderboardItemDTO } from '@/lib/types/generated/TeamLeaderboardItemDTO';
|
|
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
|
|
|
|
describe('LeaderboardsViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform Leaderboards DTO to LeaderboardsViewData correctly', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar1.jpg',
|
|
},
|
|
{
|
|
id: 'driver-2',
|
|
name: 'Jane Smith',
|
|
rating: 1100.0,
|
|
skillLevel: 'advanced',
|
|
nationality: 'Canada',
|
|
racesCompleted: 100,
|
|
wins: 15,
|
|
podiums: 40,
|
|
isActive: true,
|
|
rank: 2,
|
|
avatarUrl: 'https://example.com/avatar2.jpg',
|
|
},
|
|
],
|
|
totalRaces: 250,
|
|
totalWins: 40,
|
|
activeCount: 2,
|
|
},
|
|
teams: {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'pro,advanced,intermediate',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
// Verify drivers
|
|
expect(result.drivers).toHaveLength(2);
|
|
expect(result.drivers[0].id).toBe('driver-1');
|
|
expect(result.drivers[0].name).toBe('John Doe');
|
|
expect(result.drivers[0].rating).toBe(1234.56);
|
|
expect(result.drivers[0].skillLevel).toBe('pro');
|
|
expect(result.drivers[0].nationality).toBe('USA');
|
|
expect(result.drivers[0].wins).toBe(25);
|
|
expect(result.drivers[0].podiums).toBe(60);
|
|
expect(result.drivers[0].racesCompleted).toBe(150);
|
|
expect(result.drivers[0].rank).toBe(1);
|
|
expect(result.drivers[0].avatarUrl).toBe('https://example.com/avatar1.jpg');
|
|
expect(result.drivers[0].position).toBe(1);
|
|
|
|
// Verify teams
|
|
expect(result.teams).toHaveLength(2);
|
|
expect(result.teams[0].id).toBe('team-1');
|
|
expect(result.teams[0].name).toBe('Racing Team Alpha');
|
|
expect(result.teams[0].tag).toBe('RTA');
|
|
expect(result.teams[0].memberCount).toBe(15);
|
|
expect(result.teams[0].totalWins).toBe(50);
|
|
expect(result.teams[0].totalRaces).toBe(200);
|
|
expect(result.teams[0].logoUrl).toBe('https://example.com/logo1.jpg');
|
|
expect(result.teams[0].position).toBe(1);
|
|
expect(result.teams[0].isRecruiting).toBe(false);
|
|
expect(result.teams[0].performanceLevel).toBe('elite');
|
|
expect(result.teams[0].rating).toBe(1500);
|
|
expect(result.teams[0].category).toBeUndefined();
|
|
});
|
|
|
|
it('should handle empty driver and team arrays', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers).toEqual([]);
|
|
expect(result.teams).toEqual([]);
|
|
});
|
|
|
|
it('should handle missing avatar URLs with empty string fallback', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers[0].avatarUrl).toBe('');
|
|
expect(result.teams[0].logoUrl).toBe('');
|
|
});
|
|
|
|
it('should handle missing optional team fields with defaults', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.teams[0].rating).toBe(0);
|
|
expect(result.teams[0].logoUrl).toBe('');
|
|
});
|
|
|
|
it('should calculate position based on index', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{ id: 'driver-1', name: 'Driver 1', rating: 1000, skillLevel: 'pro', nationality: 'USA', racesCompleted: 100, wins: 10, podiums: 30, isActive: true, rank: 1 },
|
|
{ id: 'driver-2', name: 'Driver 2', rating: 900, skillLevel: 'advanced', nationality: 'Canada', racesCompleted: 80, wins: 8, podiums: 25, isActive: true, rank: 2 },
|
|
{ id: 'driver-3', name: 'Driver 3', rating: 800, skillLevel: 'intermediate', nationality: 'UK', racesCompleted: 60, wins: 5, podiums: 15, isActive: true, rank: 3 },
|
|
],
|
|
totalRaces: 240,
|
|
totalWins: 23,
|
|
activeCount: 3,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 1,
|
|
groupsBySkillLevel: 'elite,advanced,intermediate',
|
|
topTeams: [
|
|
{ id: 'team-1', name: 'Team 1', tag: 'T1', memberCount: 10, totalWins: 30, totalRaces: 150, performanceLevel: 'elite', isRecruiting: false, createdAt: '2023-01-01' },
|
|
{ id: 'team-2', name: 'Team 2', tag: 'T2', memberCount: 8, totalWins: 20, totalRaces: 120, performanceLevel: 'advanced', isRecruiting: true, createdAt: '2023-02-01' },
|
|
{ id: 'team-3', name: 'Team 3', tag: 'T3', memberCount: 6, totalWins: 10, totalRaces: 80, performanceLevel: 'intermediate', isRecruiting: false, createdAt: '2023-03-01' },
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers[0].position).toBe(1);
|
|
expect(result.drivers[1].position).toBe(2);
|
|
expect(result.drivers[2].position).toBe(3);
|
|
|
|
expect(result.teams[0].position).toBe(1);
|
|
expect(result.teams[1].position).toBe(2);
|
|
expect(result.teams[2].position).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all DTO fields in the output', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-123',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'pro,advanced',
|
|
topTeams: [
|
|
{
|
|
id: 'team-123',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers[0].name).toBe(leaderboardsDTO.drivers.drivers[0].name);
|
|
expect(result.drivers[0].nationality).toBe(leaderboardsDTO.drivers.drivers[0].nationality);
|
|
expect(result.drivers[0].avatarUrl).toBe(leaderboardsDTO.drivers.drivers[0].avatarUrl);
|
|
expect(result.teams[0].name).toBe(leaderboardsDTO.teams.topTeams[0].name);
|
|
expect(result.teams[0].tag).toBe(leaderboardsDTO.teams.topTeams[0].tag);
|
|
expect(result.teams[0].logoUrl).toBe(leaderboardsDTO.teams.topTeams[0].logoUrl);
|
|
});
|
|
|
|
it('should not modify the input DTO', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-123',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'pro,advanced',
|
|
topTeams: [
|
|
{
|
|
id: 'team-123',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const originalDTO = JSON.parse(JSON.stringify(leaderboardsDTO));
|
|
LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(leaderboardsDTO).toEqual(originalDTO);
|
|
});
|
|
|
|
it('should handle large numbers correctly', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 999999.99,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 10000,
|
|
wins: 2500,
|
|
podiums: 5000,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
],
|
|
totalRaces: 10000,
|
|
totalWins: 2500,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 100,
|
|
rating: 999999,
|
|
totalWins: 5000,
|
|
totalRaces: 10000,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers[0].rating).toBe(999999.99);
|
|
expect(result.drivers[0].wins).toBe(2500);
|
|
expect(result.drivers[0].podiums).toBe(5000);
|
|
expect(result.drivers[0].racesCompleted).toBe(10000);
|
|
expect(result.teams[0].rating).toBe(999999);
|
|
expect(result.teams[0].totalWins).toBe(5000);
|
|
expect(result.teams[0].totalRaces).toBe(10000);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null/undefined avatar URLs', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: null as any,
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: undefined as any,
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers[0].avatarUrl).toBe('');
|
|
expect(result.teams[0].logoUrl).toBe('');
|
|
});
|
|
|
|
it('should handle null/undefined rating', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: null as any,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
rating: null as any,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.drivers[0].rating).toBeNull();
|
|
expect(result.teams[0].rating).toBe(0);
|
|
});
|
|
|
|
it('should handle null/undefined totalWins and totalRaces', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: null as any,
|
|
totalRaces: null as any,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.teams[0].totalWins).toBe(0);
|
|
expect(result.teams[0].totalRaces).toBe(0);
|
|
});
|
|
|
|
it('should handle empty performance level', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: '',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.teams[0].performanceLevel).toBe('N/A');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('DriverRankingsViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform DriverLeaderboardItemDTO array to DriverRankingsViewData correctly', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar1.jpg',
|
|
},
|
|
{
|
|
id: 'driver-2',
|
|
name: 'Jane Smith',
|
|
rating: 1100.0,
|
|
skillLevel: 'advanced',
|
|
nationality: 'Canada',
|
|
racesCompleted: 100,
|
|
wins: 15,
|
|
podiums: 40,
|
|
isActive: true,
|
|
rank: 2,
|
|
avatarUrl: 'https://example.com/avatar2.jpg',
|
|
},
|
|
{
|
|
id: 'driver-3',
|
|
name: 'Bob Johnson',
|
|
rating: 950.0,
|
|
skillLevel: 'intermediate',
|
|
nationality: 'UK',
|
|
racesCompleted: 80,
|
|
wins: 10,
|
|
podiums: 30,
|
|
isActive: true,
|
|
rank: 3,
|
|
avatarUrl: 'https://example.com/avatar3.jpg',
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
// Verify drivers
|
|
expect(result.drivers).toHaveLength(3);
|
|
expect(result.drivers[0].id).toBe('driver-1');
|
|
expect(result.drivers[0].name).toBe('John Doe');
|
|
expect(result.drivers[0].rating).toBe(1234.56);
|
|
expect(result.drivers[0].skillLevel).toBe('pro');
|
|
expect(result.drivers[0].nationality).toBe('USA');
|
|
expect(result.drivers[0].racesCompleted).toBe(150);
|
|
expect(result.drivers[0].wins).toBe(25);
|
|
expect(result.drivers[0].podiums).toBe(60);
|
|
expect(result.drivers[0].rank).toBe(1);
|
|
expect(result.drivers[0].avatarUrl).toBe('https://example.com/avatar1.jpg');
|
|
expect(result.drivers[0].winRate).toBe('16.7');
|
|
expect(result.drivers[0].medalBg).toBe('bg-warning-amber');
|
|
expect(result.drivers[0].medalColor).toBe('text-warning-amber');
|
|
|
|
// Verify podium (top 3 with special ordering: 2nd, 1st, 3rd)
|
|
expect(result.podium).toHaveLength(3);
|
|
expect(result.podium[0].id).toBe('driver-1');
|
|
expect(result.podium[0].name).toBe('John Doe');
|
|
expect(result.podium[0].rating).toBe(1234.56);
|
|
expect(result.podium[0].wins).toBe(25);
|
|
expect(result.podium[0].podiums).toBe(60);
|
|
expect(result.podium[0].avatarUrl).toBe('https://example.com/avatar1.jpg');
|
|
expect(result.podium[0].position).toBe(2); // 2nd place
|
|
|
|
expect(result.podium[1].id).toBe('driver-2');
|
|
expect(result.podium[1].position).toBe(1); // 1st place
|
|
|
|
expect(result.podium[2].id).toBe('driver-3');
|
|
expect(result.podium[2].position).toBe(3); // 3rd place
|
|
|
|
// Verify default values
|
|
expect(result.searchQuery).toBe('');
|
|
expect(result.selectedSkill).toBe('all');
|
|
expect(result.sortBy).toBe('rank');
|
|
expect(result.showFilters).toBe(false);
|
|
});
|
|
|
|
it('should handle empty driver array', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers).toEqual([]);
|
|
expect(result.podium).toEqual([]);
|
|
expect(result.searchQuery).toBe('');
|
|
expect(result.selectedSkill).toBe('all');
|
|
expect(result.sortBy).toBe('rank');
|
|
expect(result.showFilters).toBe(false);
|
|
});
|
|
|
|
it('should handle less than 3 drivers for podium', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar1.jpg',
|
|
},
|
|
{
|
|
id: 'driver-2',
|
|
name: 'Jane Smith',
|
|
rating: 1100.0,
|
|
skillLevel: 'advanced',
|
|
nationality: 'Canada',
|
|
racesCompleted: 100,
|
|
wins: 15,
|
|
podiums: 40,
|
|
isActive: true,
|
|
rank: 2,
|
|
avatarUrl: 'https://example.com/avatar2.jpg',
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers).toHaveLength(2);
|
|
expect(result.podium).toHaveLength(2);
|
|
expect(result.podium[0].position).toBe(2); // 2nd place
|
|
expect(result.podium[1].position).toBe(1); // 1st place
|
|
});
|
|
|
|
it('should handle missing avatar URLs with empty string fallback', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].avatarUrl).toBe('');
|
|
expect(result.podium[0].avatarUrl).toBe('');
|
|
});
|
|
|
|
it('should calculate win rate correctly', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 100,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
{
|
|
id: 'driver-2',
|
|
name: 'Jane Smith',
|
|
rating: 1100.0,
|
|
skillLevel: 'advanced',
|
|
nationality: 'Canada',
|
|
racesCompleted: 50,
|
|
wins: 10,
|
|
podiums: 25,
|
|
isActive: true,
|
|
rank: 2,
|
|
},
|
|
{
|
|
id: 'driver-3',
|
|
name: 'Bob Johnson',
|
|
rating: 950.0,
|
|
skillLevel: 'intermediate',
|
|
nationality: 'UK',
|
|
racesCompleted: 0,
|
|
wins: 0,
|
|
podiums: 0,
|
|
isActive: true,
|
|
rank: 3,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].winRate).toBe('25.0');
|
|
expect(result.drivers[1].winRate).toBe('20.0');
|
|
expect(result.drivers[2].winRate).toBe('0.0');
|
|
});
|
|
|
|
it('should assign correct medal colors based on position', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
{
|
|
id: 'driver-2',
|
|
name: 'Jane Smith',
|
|
rating: 1100.0,
|
|
skillLevel: 'advanced',
|
|
nationality: 'Canada',
|
|
racesCompleted: 100,
|
|
wins: 15,
|
|
podiums: 40,
|
|
isActive: true,
|
|
rank: 2,
|
|
},
|
|
{
|
|
id: 'driver-3',
|
|
name: 'Bob Johnson',
|
|
rating: 950.0,
|
|
skillLevel: 'intermediate',
|
|
nationality: 'UK',
|
|
racesCompleted: 80,
|
|
wins: 10,
|
|
podiums: 30,
|
|
isActive: true,
|
|
rank: 3,
|
|
},
|
|
{
|
|
id: 'driver-4',
|
|
name: 'Alice Brown',
|
|
rating: 800.0,
|
|
skillLevel: 'beginner',
|
|
nationality: 'Germany',
|
|
racesCompleted: 60,
|
|
wins: 5,
|
|
podiums: 15,
|
|
isActive: true,
|
|
rank: 4,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].medalBg).toBe('bg-warning-amber');
|
|
expect(result.drivers[0].medalColor).toBe('text-warning-amber');
|
|
expect(result.drivers[1].medalBg).toBe('bg-gray-300');
|
|
expect(result.drivers[1].medalColor).toBe('text-gray-300');
|
|
expect(result.drivers[2].medalBg).toBe('bg-orange-700');
|
|
expect(result.drivers[2].medalColor).toBe('text-orange-700');
|
|
expect(result.drivers[3].medalBg).toBe('bg-gray-800');
|
|
expect(result.drivers[3].medalColor).toBe('text-gray-400');
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all DTO fields in the output', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-123',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].name).toBe(driverDTOs[0].name);
|
|
expect(result.drivers[0].nationality).toBe(driverDTOs[0].nationality);
|
|
expect(result.drivers[0].avatarUrl).toBe(driverDTOs[0].avatarUrl);
|
|
expect(result.drivers[0].skillLevel).toBe(driverDTOs[0].skillLevel);
|
|
});
|
|
|
|
it('should not modify the input DTO', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-123',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
];
|
|
|
|
const originalDTO = JSON.parse(JSON.stringify(driverDTOs));
|
|
DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(driverDTOs).toEqual(originalDTO);
|
|
});
|
|
|
|
it('should handle large numbers correctly', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 999999.99,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 10000,
|
|
wins: 2500,
|
|
podiums: 5000,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].rating).toBe(999999.99);
|
|
expect(result.drivers[0].wins).toBe(2500);
|
|
expect(result.drivers[0].podiums).toBe(5000);
|
|
expect(result.drivers[0].racesCompleted).toBe(10000);
|
|
expect(result.drivers[0].winRate).toBe('25.0');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null/undefined avatar URLs', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: null as any,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].avatarUrl).toBe('');
|
|
expect(result.podium[0].avatarUrl).toBe('');
|
|
});
|
|
|
|
it('should handle null/undefined rating', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: null as any,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].rating).toBeNull();
|
|
expect(result.podium[0].rating).toBeNull();
|
|
});
|
|
|
|
it('should handle zero races completed for win rate calculation', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 0,
|
|
wins: 0,
|
|
podiums: 0,
|
|
isActive: true,
|
|
rank: 1,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].winRate).toBe('0.0');
|
|
});
|
|
|
|
it('should handle rank 0', () => {
|
|
const driverDTOs: DriverLeaderboardItemDTO[] = [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 0,
|
|
},
|
|
];
|
|
|
|
const result = DriverRankingsViewDataBuilder.build(driverDTOs);
|
|
|
|
expect(result.drivers[0].rank).toBe(0);
|
|
expect(result.drivers[0].medalBg).toBe('bg-gray-800');
|
|
expect(result.drivers[0].medalColor).toBe('text-gray-400');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('TeamRankingsViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform GetTeamsLeaderboardOutputDTO to TeamRankingsViewData correctly', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
{
|
|
id: 'team-3',
|
|
name: 'Rookie Racers',
|
|
tag: 'RR',
|
|
logoUrl: 'https://example.com/logo3.jpg',
|
|
memberCount: 5,
|
|
rating: 800,
|
|
totalWins: 5,
|
|
totalRaces: 50,
|
|
performanceLevel: 'intermediate',
|
|
isRecruiting: false,
|
|
createdAt: '2023-09-01',
|
|
},
|
|
],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'elite,advanced,intermediate',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
// Verify teams
|
|
expect(result.teams).toHaveLength(3);
|
|
expect(result.teams[0].id).toBe('team-1');
|
|
expect(result.teams[0].name).toBe('Racing Team Alpha');
|
|
expect(result.teams[0].tag).toBe('RTA');
|
|
expect(result.teams[0].memberCount).toBe(15);
|
|
expect(result.teams[0].totalWins).toBe(50);
|
|
expect(result.teams[0].totalRaces).toBe(200);
|
|
expect(result.teams[0].logoUrl).toBe('https://example.com/logo1.jpg');
|
|
expect(result.teams[0].position).toBe(1);
|
|
expect(result.teams[0].isRecruiting).toBe(false);
|
|
expect(result.teams[0].performanceLevel).toBe('elite');
|
|
expect(result.teams[0].rating).toBe(1500);
|
|
expect(result.teams[0].category).toBeUndefined();
|
|
|
|
// Verify podium (top 3)
|
|
expect(result.podium).toHaveLength(3);
|
|
expect(result.podium[0].id).toBe('team-1');
|
|
expect(result.podium[0].position).toBe(1);
|
|
expect(result.podium[1].id).toBe('team-2');
|
|
expect(result.podium[1].position).toBe(2);
|
|
expect(result.podium[2].id).toBe('team-3');
|
|
expect(result.podium[2].position).toBe(3);
|
|
|
|
// Verify recruiting count
|
|
expect(result.recruitingCount).toBe(5);
|
|
});
|
|
|
|
it('should handle empty team array', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams).toEqual([]);
|
|
expect(result.podium).toEqual([]);
|
|
expect(result.recruitingCount).toBe(0);
|
|
});
|
|
|
|
it('should handle less than 3 teams for podium', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
],
|
|
recruitingCount: 2,
|
|
groupsBySkillLevel: 'elite,advanced',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams).toHaveLength(2);
|
|
expect(result.podium).toHaveLength(2);
|
|
expect(result.podium[0].position).toBe(1);
|
|
expect(result.podium[1].position).toBe(2);
|
|
});
|
|
|
|
it('should handle missing avatar URLs with empty string fallback', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].logoUrl).toBe('');
|
|
});
|
|
|
|
it('should calculate position based on index', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{ id: 'team-1', name: 'Team 1', tag: 'T1', memberCount: 10, totalWins: 30, totalRaces: 150, performanceLevel: 'elite', isRecruiting: false, createdAt: '2023-01-01' },
|
|
{ id: 'team-2', name: 'Team 2', tag: 'T2', memberCount: 8, totalWins: 20, totalRaces: 120, performanceLevel: 'advanced', isRecruiting: true, createdAt: '2023-02-01' },
|
|
{ id: 'team-3', name: 'Team 3', tag: 'T3', memberCount: 6, totalWins: 10, totalRaces: 80, performanceLevel: 'intermediate', isRecruiting: false, createdAt: '2023-03-01' },
|
|
{ id: 'team-4', name: 'Team 4', tag: 'T4', memberCount: 4, totalWins: 5, totalRaces: 40, performanceLevel: 'beginner', isRecruiting: true, createdAt: '2023-04-01' },
|
|
],
|
|
recruitingCount: 2,
|
|
groupsBySkillLevel: 'elite,advanced,intermediate,beginner',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].position).toBe(1);
|
|
expect(result.teams[1].position).toBe(2);
|
|
expect(result.teams[2].position).toBe(3);
|
|
expect(result.teams[3].position).toBe(4);
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all DTO fields in the output', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-123',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'elite,advanced',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].name).toBe(teamDTO.teams[0].name);
|
|
expect(result.teams[0].tag).toBe(teamDTO.teams[0].tag);
|
|
expect(result.teams[0].logoUrl).toBe(teamDTO.teams[0].logoUrl);
|
|
expect(result.teams[0].memberCount).toBe(teamDTO.teams[0].memberCount);
|
|
expect(result.teams[0].rating).toBe(teamDTO.teams[0].rating);
|
|
expect(result.teams[0].totalWins).toBe(teamDTO.teams[0].totalWins);
|
|
expect(result.teams[0].totalRaces).toBe(teamDTO.teams[0].totalRaces);
|
|
expect(result.teams[0].performanceLevel).toBe(teamDTO.teams[0].performanceLevel);
|
|
expect(result.teams[0].isRecruiting).toBe(teamDTO.teams[0].isRecruiting);
|
|
});
|
|
|
|
it('should not modify the input DTO', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-123',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'elite,advanced',
|
|
topTeams: [],
|
|
};
|
|
|
|
const originalDTO = JSON.parse(JSON.stringify(teamDTO));
|
|
TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(teamDTO).toEqual(originalDTO);
|
|
});
|
|
|
|
it('should handle large numbers correctly', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 100,
|
|
rating: 999999,
|
|
totalWins: 5000,
|
|
totalRaces: 10000,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].rating).toBe(999999);
|
|
expect(result.teams[0].totalWins).toBe(5000);
|
|
expect(result.teams[0].totalRaces).toBe(10000);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null/undefined logo URLs', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: null as any,
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].logoUrl).toBe('');
|
|
});
|
|
|
|
it('should handle null/undefined rating', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
rating: null as any,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].rating).toBe(0);
|
|
});
|
|
|
|
it('should handle null/undefined totalWins and totalRaces', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: null as any,
|
|
totalRaces: null as any,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].totalWins).toBe(0);
|
|
expect(result.teams[0].totalRaces).toBe(0);
|
|
});
|
|
|
|
it('should handle empty performance level', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
memberCount: 15,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: '',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].performanceLevel).toBe('N/A');
|
|
});
|
|
|
|
it('should handle position 0', () => {
|
|
const teamDTO: GetTeamsLeaderboardOutputDTO = {
|
|
teams: [
|
|
{ id: 'team-1', name: 'Team 1', tag: 'T1', memberCount: 10, totalWins: 30, totalRaces: 150, performanceLevel: 'elite', isRecruiting: false, createdAt: '2023-01-01' },
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
};
|
|
|
|
const result = TeamRankingsViewDataBuilder.build(teamDTO);
|
|
|
|
expect(result.teams[0].position).toBe(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('WinRateDisplay', () => {
|
|
describe('happy paths', () => {
|
|
it('should calculate win rate correctly', () => {
|
|
expect(WinRateDisplay.calculate(100, 25)).toBe('25.0');
|
|
expect(WinRateDisplay.calculate(50, 10)).toBe('20.0');
|
|
expect(WinRateDisplay.calculate(200, 50)).toBe('25.0');
|
|
});
|
|
|
|
it('should handle zero races completed', () => {
|
|
expect(WinRateDisplay.calculate(0, 0)).toBe('0.0');
|
|
expect(WinRateDisplay.calculate(0, 10)).toBe('0.0');
|
|
});
|
|
|
|
it('should handle zero wins', () => {
|
|
expect(WinRateDisplay.calculate(100, 0)).toBe('0.0');
|
|
});
|
|
|
|
it('should format rate correctly', () => {
|
|
expect(WinRateDisplay.format(25.0)).toBe('25.0%');
|
|
expect(WinRateDisplay.format(0)).toBe('0.0%');
|
|
expect(WinRateDisplay.format(100)).toBe('100.0%');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null rate in format', () => {
|
|
expect(WinRateDisplay.format(null)).toBe('0.0%');
|
|
});
|
|
|
|
it('should handle undefined rate in format', () => {
|
|
expect(WinRateDisplay.format(undefined)).toBe('0.0%');
|
|
});
|
|
|
|
it('should handle decimal win rates', () => {
|
|
expect(WinRateDisplay.calculate(100, 25)).toBe('25.0');
|
|
expect(WinRateDisplay.calculate(100, 33)).toBe('33.0');
|
|
expect(WinRateDisplay.calculate(100, 66)).toBe('66.0');
|
|
});
|
|
|
|
it('should handle large numbers', () => {
|
|
expect(WinRateDisplay.calculate(10000, 2500)).toBe('25.0');
|
|
expect(WinRateDisplay.calculate(10000, 5000)).toBe('50.0');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('MedalDisplay', () => {
|
|
describe('happy paths', () => {
|
|
it('should return correct variant for positions', () => {
|
|
expect(MedalDisplay.getVariant(1)).toBe('warning');
|
|
expect(MedalDisplay.getVariant(2)).toBe('high');
|
|
expect(MedalDisplay.getVariant(3)).toBe('warning');
|
|
expect(MedalDisplay.getVariant(4)).toBe('low');
|
|
expect(MedalDisplay.getVariant(10)).toBe('low');
|
|
});
|
|
|
|
it('should return correct medal icon for top 3 positions', () => {
|
|
expect(MedalDisplay.getMedalIcon(1)).toBe('🏆');
|
|
expect(MedalDisplay.getMedalIcon(2)).toBe('🏆');
|
|
expect(MedalDisplay.getMedalIcon(3)).toBe('🏆');
|
|
});
|
|
|
|
it('should return null for positions outside top 3', () => {
|
|
expect(MedalDisplay.getMedalIcon(4)).toBeNull();
|
|
expect(MedalDisplay.getMedalIcon(10)).toBeNull();
|
|
expect(MedalDisplay.getMedalIcon(100)).toBeNull();
|
|
});
|
|
|
|
it('should return correct background color for positions', () => {
|
|
expect(MedalDisplay.getBg(1)).toBe('bg-warning-amber');
|
|
expect(MedalDisplay.getBg(2)).toBe('bg-gray-300');
|
|
expect(MedalDisplay.getBg(3)).toBe('bg-orange-700');
|
|
expect(MedalDisplay.getBg(4)).toBe('bg-gray-800');
|
|
expect(MedalDisplay.getBg(10)).toBe('bg-gray-800');
|
|
});
|
|
|
|
it('should return correct text color for positions', () => {
|
|
expect(MedalDisplay.getColor(1)).toBe('text-warning-amber');
|
|
expect(MedalDisplay.getColor(2)).toBe('text-gray-300');
|
|
expect(MedalDisplay.getColor(3)).toBe('text-orange-700');
|
|
expect(MedalDisplay.getColor(4)).toBe('text-gray-400');
|
|
expect(MedalDisplay.getColor(10)).toBe('text-gray-400');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle position 0', () => {
|
|
expect(MedalDisplay.getVariant(0)).toBe('low');
|
|
expect(MedalDisplay.getMedalIcon(0)).toBe('🏆');
|
|
expect(MedalDisplay.getBg(0)).toBe('bg-gray-800');
|
|
expect(MedalDisplay.getColor(0)).toBe('text-gray-400');
|
|
});
|
|
|
|
it('should handle large positions', () => {
|
|
expect(MedalDisplay.getVariant(999)).toBe('low');
|
|
expect(MedalDisplay.getMedalIcon(999)).toBeNull();
|
|
expect(MedalDisplay.getBg(999)).toBe('bg-gray-800');
|
|
expect(MedalDisplay.getColor(999)).toBe('text-gray-400');
|
|
});
|
|
|
|
it('should handle negative positions', () => {
|
|
expect(MedalDisplay.getVariant(-1)).toBe('low');
|
|
expect(MedalDisplay.getMedalIcon(-1)).toBe('🏆');
|
|
expect(MedalDisplay.getBg(-1)).toBe('bg-gray-800');
|
|
expect(MedalDisplay.getColor(-1)).toBe('text-gray-400');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Leaderboards View Data - Cross-Component Consistency', () => {
|
|
describe('common patterns', () => {
|
|
it('should all use consistent formatting for numeric values', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'elite,advanced',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
// All numeric values should be preserved as numbers (not formatted as strings)
|
|
expect(typeof result.drivers[0].rating).toBe('number');
|
|
expect(typeof result.drivers[0].wins).toBe('number');
|
|
expect(typeof result.drivers[0].podiums).toBe('number');
|
|
expect(typeof result.drivers[0].racesCompleted).toBe('number');
|
|
expect(typeof result.drivers[0].rank).toBe('number');
|
|
expect(typeof result.teams[0].rating).toBe('number');
|
|
expect(typeof result.teams[0].totalWins).toBe('number');
|
|
expect(typeof result.teams[0].totalRaces).toBe('number');
|
|
});
|
|
|
|
it('should all handle missing data gracefully', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
// All fields should have safe defaults
|
|
expect(result.drivers).toEqual([]);
|
|
expect(result.teams).toEqual([]);
|
|
});
|
|
|
|
it('should all preserve ISO timestamps for serialization', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01T00:00:00Z',
|
|
},
|
|
],
|
|
recruitingCount: 0,
|
|
groupsBySkillLevel: '',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01T00:00:00Z',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
// Verify that the view data model is correctly built
|
|
expect(result.teams).toHaveLength(1);
|
|
expect(result.teams[0].id).toBe('team-1');
|
|
expect(result.teams[0].name).toBe('Racing Team Alpha');
|
|
expect(result.teams[0].tag).toBe('RTA');
|
|
expect(result.teams[0].logoUrl).toBe('https://example.com/logo.jpg');
|
|
expect(result.teams[0].memberCount).toBe(15);
|
|
expect(result.teams[0].rating).toBe(1500);
|
|
expect(result.teams[0].totalWins).toBe(50);
|
|
expect(result.teams[0].totalRaces).toBe(200);
|
|
expect(result.teams[0].performanceLevel).toBe('elite');
|
|
expect(result.teams[0].isRecruiting).toBe(false);
|
|
expect(result.teams[0].position).toBe(1);
|
|
});
|
|
|
|
it('should all handle boolean flags correctly', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [],
|
|
totalRaces: 0,
|
|
totalWins: 0,
|
|
activeCount: 0,
|
|
},
|
|
teams: {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: true,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: false,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
],
|
|
recruitingCount: 1,
|
|
groupsBySkillLevel: 'elite,advanced',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: true,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: false,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
expect(result.teams[0].isRecruiting).toBe(true);
|
|
expect(result.teams[1].isRecruiting).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('data integrity', () => {
|
|
it('should maintain data consistency across transformations', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 1234.56,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 150,
|
|
wins: 25,
|
|
podiums: 60,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar.jpg',
|
|
},
|
|
],
|
|
totalRaces: 150,
|
|
totalWins: 25,
|
|
activeCount: 1,
|
|
},
|
|
teams: {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
recruitingCount: 5,
|
|
groupsBySkillLevel: 'elite,advanced',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
// Verify derived fields match their source data
|
|
expect(result.drivers[0].position).toBe(result.drivers[0].rank);
|
|
expect(result.teams[0].position).toBe(1);
|
|
});
|
|
|
|
it('should handle complex real-world scenarios', () => {
|
|
const leaderboardsDTO = {
|
|
drivers: {
|
|
drivers: [
|
|
{
|
|
id: 'driver-1',
|
|
name: 'John Doe',
|
|
rating: 2456.78,
|
|
skillLevel: 'pro',
|
|
nationality: 'USA',
|
|
racesCompleted: 250,
|
|
wins: 45,
|
|
podiums: 120,
|
|
isActive: true,
|
|
rank: 1,
|
|
avatarUrl: 'https://example.com/avatar1.jpg',
|
|
},
|
|
{
|
|
id: 'driver-2',
|
|
name: 'Jane Smith',
|
|
rating: 2100.0,
|
|
skillLevel: 'pro',
|
|
nationality: 'Canada',
|
|
racesCompleted: 200,
|
|
wins: 35,
|
|
podiums: 100,
|
|
isActive: true,
|
|
rank: 2,
|
|
avatarUrl: 'https://example.com/avatar2.jpg',
|
|
},
|
|
{
|
|
id: 'driver-3',
|
|
name: 'Bob Johnson',
|
|
rating: 1800.0,
|
|
skillLevel: 'advanced',
|
|
nationality: 'UK',
|
|
racesCompleted: 180,
|
|
wins: 25,
|
|
podiums: 80,
|
|
isActive: true,
|
|
rank: 3,
|
|
avatarUrl: 'https://example.com/avatar3.jpg',
|
|
},
|
|
],
|
|
totalRaces: 630,
|
|
totalWins: 105,
|
|
activeCount: 3,
|
|
},
|
|
teams: {
|
|
teams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
{
|
|
id: 'team-3',
|
|
name: 'Rookie Racers',
|
|
tag: 'RR',
|
|
logoUrl: 'https://example.com/logo3.jpg',
|
|
memberCount: 5,
|
|
rating: 800,
|
|
totalWins: 5,
|
|
totalRaces: 50,
|
|
performanceLevel: 'intermediate',
|
|
isRecruiting: false,
|
|
createdAt: '2023-09-01',
|
|
},
|
|
],
|
|
recruitingCount: 1,
|
|
groupsBySkillLevel: 'elite,advanced,intermediate',
|
|
topTeams: [
|
|
{
|
|
id: 'team-1',
|
|
name: 'Racing Team Alpha',
|
|
tag: 'RTA',
|
|
logoUrl: 'https://example.com/logo1.jpg',
|
|
memberCount: 15,
|
|
rating: 1500,
|
|
totalWins: 50,
|
|
totalRaces: 200,
|
|
performanceLevel: 'elite',
|
|
isRecruiting: false,
|
|
createdAt: '2023-01-01',
|
|
},
|
|
{
|
|
id: 'team-2',
|
|
name: 'Speed Demons',
|
|
tag: 'SD',
|
|
logoUrl: 'https://example.com/logo2.jpg',
|
|
memberCount: 8,
|
|
rating: 1200,
|
|
totalWins: 20,
|
|
totalRaces: 150,
|
|
performanceLevel: 'advanced',
|
|
isRecruiting: true,
|
|
createdAt: '2023-06-01',
|
|
},
|
|
{
|
|
id: 'team-3',
|
|
name: 'Rookie Racers',
|
|
tag: 'RR',
|
|
logoUrl: 'https://example.com/logo3.jpg',
|
|
memberCount: 5,
|
|
rating: 800,
|
|
totalWins: 5,
|
|
totalRaces: 50,
|
|
performanceLevel: 'intermediate',
|
|
isRecruiting: false,
|
|
createdAt: '2023-09-01',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const result = LeaderboardsViewDataBuilder.build(leaderboardsDTO);
|
|
|
|
// Verify all transformations
|
|
expect(result.drivers).toHaveLength(3);
|
|
expect(result.drivers[0].name).toBe('John Doe');
|
|
expect(result.drivers[0].rating).toBe(2456.78);
|
|
expect(result.drivers[0].rank).toBe(1);
|
|
expect(result.drivers[0].position).toBe(1);
|
|
|
|
expect(result.teams).toHaveLength(3);
|
|
expect(result.teams[0].name).toBe('Racing Team Alpha');
|
|
expect(result.teams[0].rating).toBe(1500);
|
|
expect(result.teams[0].position).toBe(1);
|
|
expect(result.teams[0].isRecruiting).toBe(false);
|
|
|
|
expect(result.teams[1].isRecruiting).toBe(true);
|
|
expect(result.teams[2].isRecruiting).toBe(false);
|
|
});
|
|
});
|
|
});
|