refactor driver module (wip)
This commit is contained in:
@@ -128,7 +128,7 @@ export const DriverProviders: Provider[] = [
|
||||
driverStatsService: IDriverStatsService,
|
||||
imageService: IImageServicePort,
|
||||
logger: Logger,
|
||||
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, imageService, logger),
|
||||
) => new GetDriversLeaderboardUseCase(driverRepo, rankingService, driverStatsService, (driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)), logger),
|
||||
inject: [DRIVER_REPOSITORY_TOKEN, RANKING_SERVICE_TOKEN, DRIVER_STATS_SERVICE_TOKEN, IMAGE_SERVICE_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
@@ -169,7 +169,7 @@ export const DriverProviders: Provider[] = [
|
||||
teamRepository,
|
||||
teamMembershipRepository,
|
||||
socialRepository,
|
||||
(driverId: string) => Promise.resolve(imageService.getDriverAvatar(driverId)),
|
||||
imageService,
|
||||
driverExtendedProfileProvider,
|
||||
(driverId: string) => {
|
||||
const stats = driverStatsService.getDriverStats(driverId);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export enum DriverProfileAchievementRarity {
|
||||
COMMON = 'common',
|
||||
RARE = 'rare',
|
||||
EPIC = 'epic',
|
||||
LEGENDARY = 'legendary',
|
||||
}
|
||||
|
||||
export class DriverProfileAchievementDTO {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
title!: string;
|
||||
|
||||
@ApiProperty()
|
||||
description!: string;
|
||||
|
||||
@ApiProperty({ enum: ['trophy', 'medal', 'star', 'crown', 'target', 'zap'] })
|
||||
icon!: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
||||
|
||||
@ApiProperty({ enum: DriverProfileAchievementRarity })
|
||||
rarity!: DriverProfileAchievementRarity;
|
||||
|
||||
@ApiProperty()
|
||||
earnedAt!: string;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DriverProfileDriverSummaryDTO {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
country!: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
iracingId!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
rating!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
globalRank!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
consistency!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
bio!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
totalDrivers!: number | null;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { DriverProfileSocialHandleDTO } from './DriverProfileSocialHandleDTO';
|
||||
|
||||
import { DriverProfileAchievementDTO } from './DriverProfileAchievementDTO';
|
||||
|
||||
export class DriverProfileExtendedProfileDTO {
|
||||
@ApiProperty({ type: [DriverProfileSocialHandleDTO] })
|
||||
socialHandles!: DriverProfileSocialHandleDTO[];
|
||||
|
||||
@ApiProperty({ type: [DriverProfileAchievementDTO] })
|
||||
achievements!: DriverProfileAchievementDTO[];
|
||||
|
||||
@ApiProperty()
|
||||
racingStyle!: string;
|
||||
|
||||
@ApiProperty()
|
||||
favoriteTrack!: string;
|
||||
|
||||
@ApiProperty()
|
||||
favoriteCar!: string;
|
||||
|
||||
@ApiProperty()
|
||||
timezone!: string;
|
||||
|
||||
@ApiProperty()
|
||||
availableHours!: string;
|
||||
|
||||
@ApiProperty()
|
||||
lookingForTeam!: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
openToRequests!: boolean;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DriverProfileFinishDistributionDTO {
|
||||
@ApiProperty()
|
||||
totalRaces!: number;
|
||||
|
||||
@ApiProperty()
|
||||
wins!: number;
|
||||
|
||||
@ApiProperty()
|
||||
podiums!: number;
|
||||
|
||||
@ApiProperty()
|
||||
topTen!: number;
|
||||
|
||||
@ApiProperty()
|
||||
dnfs!: number;
|
||||
|
||||
@ApiProperty()
|
||||
other!: number;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DriverProfileSocialFriendSummaryDTO {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
country!: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl!: string;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord';
|
||||
|
||||
export class DriverProfileSocialHandleDTO {
|
||||
@ApiProperty({ enum: ['twitter', 'youtube', 'twitch', 'discord'] })
|
||||
platform!: DriverProfileSocialPlatform;
|
||||
|
||||
@ApiProperty()
|
||||
handle!: string;
|
||||
|
||||
@ApiProperty()
|
||||
url!: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
import { DriverProfileSocialFriendSummaryDTO } from './DriverProfileSocialFriendSummaryDTO';
|
||||
|
||||
export class DriverProfileSocialSummaryDTO {
|
||||
@ApiProperty()
|
||||
friendsCount!: number;
|
||||
|
||||
@ApiProperty({ type: [DriverProfileSocialFriendSummaryDTO] })
|
||||
friends!: DriverProfileSocialFriendSummaryDTO[];
|
||||
}
|
||||
45
apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts
Normal file
45
apps/api/src/domain/driver/dtos/DriverProfileStatsDTO.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DriverProfileStatsDTO {
|
||||
@ApiProperty()
|
||||
totalRaces!: number;
|
||||
|
||||
@ApiProperty()
|
||||
wins!: number;
|
||||
|
||||
@ApiProperty()
|
||||
podiums!: number;
|
||||
|
||||
@ApiProperty()
|
||||
dnfs!: number;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
avgFinish!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
bestFinish!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
worstFinish!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
finishRate!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
winRate!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
podiumRate!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
percentile!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
rating!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
consistency!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
overallRank!: number | null;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DriverProfileTeamMembershipDTO {
|
||||
@ApiProperty()
|
||||
teamId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
teamName!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
teamTag!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
role!: string;
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
isCurrent!: boolean;
|
||||
}
|
||||
@@ -1,231 +1,28 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DriverProfileDriverSummaryDTO {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
name!: string;
|
||||
|
||||
@ApiProperty()
|
||||
country!: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
iracingId!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
rating!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
globalRank!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
consistency!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
bio!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
totalDrivers!: number | null;
|
||||
}
|
||||
|
||||
export class DriverProfileStatsDTO {
|
||||
@ApiProperty()
|
||||
totalRaces!: number;
|
||||
|
||||
@ApiProperty()
|
||||
wins!: number;
|
||||
|
||||
@ApiProperty()
|
||||
podiums!: number;
|
||||
|
||||
@ApiProperty()
|
||||
dnfs!: number;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
avgFinish!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
bestFinish!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
worstFinish!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
finishRate!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
winRate!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
podiumRate!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
percentile!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
rating!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
consistency!: number | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
overallRank!: number | null;
|
||||
}
|
||||
|
||||
export class DriverProfileFinishDistributionDTO {
|
||||
@ApiProperty()
|
||||
totalRaces: number;
|
||||
|
||||
@ApiProperty()
|
||||
wins: number;
|
||||
|
||||
@ApiProperty()
|
||||
podiums: number;
|
||||
|
||||
@ApiProperty()
|
||||
topTen: number;
|
||||
|
||||
@ApiProperty()
|
||||
dnfs: number;
|
||||
|
||||
@ApiProperty()
|
||||
other: number;
|
||||
}
|
||||
|
||||
export class DriverProfileTeamMembershipDTO {
|
||||
@ApiProperty()
|
||||
teamId: string;
|
||||
|
||||
@ApiProperty()
|
||||
teamName: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
teamTag: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
role: string;
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt: string;
|
||||
|
||||
@ApiProperty()
|
||||
isCurrent: boolean;
|
||||
}
|
||||
|
||||
export class DriverProfileSocialFriendSummaryDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
country: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export class DriverProfileSocialSummaryDTO {
|
||||
@ApiProperty()
|
||||
friendsCount: number;
|
||||
|
||||
@ApiProperty({ type: [DriverProfileSocialFriendSummaryDTO] })
|
||||
friends: DriverProfileSocialFriendSummaryDTO[];
|
||||
}
|
||||
|
||||
export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord';
|
||||
|
||||
export enum DriverProfileAchievementRarity {
|
||||
COMMON = 'common',
|
||||
RARE = 'rare',
|
||||
EPIC = 'epic',
|
||||
LEGENDARY = 'legendary',
|
||||
}
|
||||
|
||||
export class DriverProfileAchievementDTO {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
title: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ enum: ['trophy', 'medal', 'star', 'crown', 'target', 'zap'] })
|
||||
icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
||||
|
||||
@ApiProperty({ enum: DriverProfileAchievementRarity })
|
||||
rarity: DriverProfileAchievementRarity;
|
||||
|
||||
@ApiProperty()
|
||||
earnedAt: string;
|
||||
}
|
||||
|
||||
export class DriverProfileSocialHandleDTO {
|
||||
@ApiProperty({ enum: DriverProfileSocialPlatform })
|
||||
platform: DriverProfileSocialPlatform;
|
||||
|
||||
@ApiProperty()
|
||||
handle: string;
|
||||
|
||||
@ApiProperty()
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class DriverProfileExtendedProfileDTO {
|
||||
@ApiProperty({ type: [DriverProfileSocialHandleDTO] })
|
||||
socialHandles: DriverProfileSocialHandleDTO[];
|
||||
|
||||
@ApiProperty({ type: [DriverProfileAchievementDTO] })
|
||||
achievements: DriverProfileAchievementDTO[];
|
||||
|
||||
@ApiProperty()
|
||||
racingStyle: string;
|
||||
|
||||
@ApiProperty()
|
||||
favoriteTrack: string;
|
||||
|
||||
@ApiProperty()
|
||||
favoriteCar: string;
|
||||
|
||||
@ApiProperty()
|
||||
timezone: string;
|
||||
|
||||
@ApiProperty()
|
||||
availableHours: string;
|
||||
|
||||
@ApiProperty()
|
||||
lookingForTeam: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
openToRequests: boolean;
|
||||
}
|
||||
import { DriverProfileDriverSummaryDTO } from './DriverProfileDriverSummaryDTO';
|
||||
import { DriverProfileStatsDTO } from './DriverProfileStatsDTO';
|
||||
import { DriverProfileFinishDistributionDTO } from './DriverProfileFinishDistributionDTO';
|
||||
import { DriverProfileTeamMembershipDTO } from './DriverProfileTeamMembershipDTO';
|
||||
import { DriverProfileSocialSummaryDTO } from './DriverProfileSocialSummaryDTO';
|
||||
import { DriverProfileExtendedProfileDTO } from './DriverProfileExtendedProfileDTO';
|
||||
|
||||
export class GetDriverProfileOutputDTO {
|
||||
@ApiProperty({ type: DriverProfileDriverSummaryDTO, nullable: true })
|
||||
currentDriver: DriverProfileDriverSummaryDTO | null;
|
||||
currentDriver!: DriverProfileDriverSummaryDTO | null;
|
||||
|
||||
@ApiProperty({ type: DriverProfileStatsDTO, nullable: true })
|
||||
stats: DriverProfileStatsDTO | null;
|
||||
stats!: DriverProfileStatsDTO | null;
|
||||
|
||||
@ApiProperty({ type: DriverProfileFinishDistributionDTO, nullable: true })
|
||||
finishDistribution: DriverProfileFinishDistributionDTO | null;
|
||||
finishDistribution!: DriverProfileFinishDistributionDTO | null;
|
||||
|
||||
@ApiProperty({ type: [DriverProfileTeamMembershipDTO] })
|
||||
teamMemberships: DriverProfileTeamMembershipDTO[];
|
||||
teamMemberships!: DriverProfileTeamMembershipDTO[];
|
||||
|
||||
@ApiProperty({ type: DriverProfileSocialSummaryDTO })
|
||||
socialSummary: DriverProfileSocialSummaryDTO;
|
||||
socialSummary!: DriverProfileSocialSummaryDTO;
|
||||
|
||||
@ApiProperty({ type: DriverProfileExtendedProfileDTO, nullable: true })
|
||||
extendedProfile: DriverProfileExtendedProfileDTO | null;
|
||||
extendedProfile!: DriverProfileExtendedProfileDTO | null;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {
|
||||
GetProfileOverviewResult,
|
||||
} from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
|
||||
import type { GetDriverProfileOutputDTO, DriverProfileExtendedProfileDTO } from '../dtos/GetDriverProfileOutputDTO';
|
||||
import type { GetDriverProfileOutputDTO } from '../dtos/GetDriverProfileOutputDTO';
|
||||
import type { DriverProfileExtendedProfileDTO } from '../dtos/DriverProfileExtendedProfileDTO';
|
||||
|
||||
export class DriverProfilePresenter
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import { DriverStatsPresenter } from './DriverStatsPresenter';
|
||||
import type { GetTotalDriversResult } from '../../../../../core/racing/application/use-cases/GetTotalDriversUseCase';
|
||||
import type { GetTotalDriversResult } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
|
||||
|
||||
describe('DriverStatsPresenter', () => {
|
||||
let presenter: DriverStatsPresenter;
|
||||
@@ -16,31 +15,13 @@ describe('DriverStatsPresenter', () => {
|
||||
totalDrivers: 42,
|
||||
};
|
||||
|
||||
const result = Result.ok<GetTotalDriversResult, never>(output);
|
||||
presenter.present(output);
|
||||
|
||||
presenter.present(result);
|
||||
|
||||
const response = presenter.responseModel;
|
||||
const response = presenter.getResponseModel();
|
||||
|
||||
expect(response).toEqual({
|
||||
totalDrivers: 42,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result', () => {
|
||||
const output: GetTotalDriversResult = {
|
||||
totalDrivers: 10,
|
||||
};
|
||||
|
||||
const result = Result.ok<GetTotalDriversResult, never>(output);
|
||||
|
||||
presenter.present(result);
|
||||
expect(presenter.responseModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.responseModel).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,8 @@ import { GetDriversLeaderboardResult } from '@core/racing/application/use-cases/
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { DriversLeaderboardPresenter } from './DriversLeaderboardPresenter';
|
||||
import type { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import type { SkillLevel } from '@core/racing/domain/services/SkillLevelService';
|
||||
|
||||
// TODO fix eslint issues
|
||||
|
||||
@@ -19,11 +21,11 @@ describe('DriversLeaderboardPresenter', () => {
|
||||
{
|
||||
driver: {
|
||||
id: 'driver-1',
|
||||
name: 'Driver One' as unknown,
|
||||
country: 'US' as unknown,
|
||||
} as unknown,
|
||||
name: 'Driver One',
|
||||
country: 'US',
|
||||
} as unknown as Driver,
|
||||
rating: 2500,
|
||||
skillLevel: 'advanced' as unknown,
|
||||
skillLevel: 'advanced' as unknown as SkillLevel,
|
||||
racesCompleted: 50,
|
||||
wins: 10,
|
||||
podiums: 20,
|
||||
@@ -34,11 +36,11 @@ describe('DriversLeaderboardPresenter', () => {
|
||||
{
|
||||
driver: {
|
||||
id: 'driver-2',
|
||||
name: 'Driver Two' as unknown,
|
||||
country: 'DE' as unknown,
|
||||
} as unknown,
|
||||
name: 'Driver Two',
|
||||
country: 'DE',
|
||||
} as unknown as Driver,
|
||||
rating: 2400,
|
||||
skillLevel: 'intermediate' as unknown,
|
||||
skillLevel: 'intermediate' as unknown as SkillLevel,
|
||||
racesCompleted: 40,
|
||||
wins: 5,
|
||||
podiums: 15,
|
||||
@@ -56,9 +58,10 @@ describe('DriversLeaderboardPresenter', () => {
|
||||
|
||||
presenter.present(result);
|
||||
|
||||
const output = presenter.getResponseModel();
|
||||
|
||||
expect(result.drivers).toHaveLength(2);
|
||||
expect(result.drivers[0]).toEqual({
|
||||
expect(output.drivers).toHaveLength(2);
|
||||
expect(output.drivers[0]).toEqual({
|
||||
id: 'driver-1',
|
||||
name: 'Driver One',
|
||||
rating: 2500,
|
||||
@@ -71,7 +74,7 @@ describe('DriversLeaderboardPresenter', () => {
|
||||
rank: 1,
|
||||
avatarUrl: 'https://example.com/avatar1.png',
|
||||
});
|
||||
expect(result.drivers[1]).toEqual({
|
||||
expect(output.drivers[1]).toEqual({
|
||||
id: 'driver-2',
|
||||
name: 'Driver Two',
|
||||
rating: 2400,
|
||||
@@ -84,126 +87,10 @@ describe('DriversLeaderboardPresenter', () => {
|
||||
rank: 2,
|
||||
avatarUrl: 'https://example.com/avatar2.png',
|
||||
});
|
||||
expect(result.totalRaces).toBe(90);
|
||||
expect(result.totalWins).toBe(15);
|
||||
expect(result.activeCount).toBe(2);
|
||||
expect(output.totalRaces).toBe(90);
|
||||
expect(output.totalWins).toBe(15);
|
||||
expect(output.activeCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should sort drivers by rating descending', () => {
|
||||
const dto: DriversLeaderboardResultDTO = {
|
||||
drivers: [
|
||||
{
|
||||
id: 'driver-1',
|
||||
name: 'Driver One',
|
||||
country: 'US',
|
||||
iracingId: '12345',
|
||||
joinedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 'driver-2',
|
||||
name: 'Driver Two',
|
||||
country: 'DE',
|
||||
iracingId: '67890',
|
||||
joinedAt: new Date(),
|
||||
},
|
||||
],
|
||||
rankings: [
|
||||
{ driverId: 'driver-1', rating: 2400, overallRank: 2 },
|
||||
{ driverId: 'driver-2', rating: 2500, overallRank: 1 },
|
||||
],
|
||||
stats: {},
|
||||
avatarUrls: {},
|
||||
};
|
||||
|
||||
presenter.present(dto);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
expect(result.drivers[0].id).toBe('driver-2'); // Higher rating first
|
||||
expect(result.drivers[1].id).toBe('driver-1');
|
||||
});
|
||||
|
||||
it('should handle missing stats gracefully', () => {
|
||||
const dto: DriversLeaderboardResultDTO = {
|
||||
drivers: [
|
||||
{
|
||||
id: 'driver-1',
|
||||
name: 'Driver One',
|
||||
country: 'US',
|
||||
iracingId: '12345',
|
||||
joinedAt: new Date(),
|
||||
},
|
||||
],
|
||||
rankings: [
|
||||
{ driverId: 'driver-1', rating: 2500, overallRank: 1 },
|
||||
],
|
||||
stats: {}, // No stats
|
||||
avatarUrls: {},
|
||||
};
|
||||
|
||||
presenter.present(dto);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
expect(result.drivers[0].racesCompleted).toBe(0);
|
||||
expect(result.drivers[0].wins).toBe(0);
|
||||
expect(result.drivers[0].podiums).toBe(0);
|
||||
expect(result.drivers[0].isActive).toBe(false);
|
||||
});
|
||||
|
||||
it('should derive skill level from rating bands', () => {
|
||||
const dto: DriversLeaderboardResultDTO = {
|
||||
drivers: [
|
||||
{ id: 'd1', name: 'Beginner', country: 'US', iracingId: '1', joinedAt: new Date() },
|
||||
{ id: 'd2', name: 'Intermediate', country: 'US', iracingId: '2', joinedAt: new Date() },
|
||||
{ id: 'd3', name: 'Advanced', country: 'US', iracingId: '3', joinedAt: new Date() },
|
||||
{ id: 'd4', name: 'Pro', country: 'US', iracingId: '4', joinedAt: new Date() },
|
||||
],
|
||||
rankings: [
|
||||
{ driverId: 'd1', rating: 1700, overallRank: 4 },
|
||||
{ driverId: 'd2', rating: 2000, overallRank: 3 },
|
||||
{ driverId: 'd3', rating: 2600, overallRank: 2 },
|
||||
{ driverId: 'd4', rating: 3100, overallRank: 1 },
|
||||
],
|
||||
stats: {
|
||||
d1: { racesCompleted: 5, wins: 0, podiums: 0 },
|
||||
d2: { racesCompleted: 5, wins: 0, podiums: 0 },
|
||||
d3: { racesCompleted: 5, wins: 0, podiums: 0 },
|
||||
d4: { racesCompleted: 5, wins: 0, podiums: 0 },
|
||||
},
|
||||
avatarUrls: {
|
||||
d1: 'avatar-1',
|
||||
d2: 'avatar-2',
|
||||
d3: 'avatar-3',
|
||||
d4: 'avatar-4',
|
||||
},
|
||||
};
|
||||
|
||||
presenter.present(dto);
|
||||
const result = presenter.viewModel;
|
||||
|
||||
const levels = result.drivers
|
||||
.sort((a, b) => a.rating - b.rating)
|
||||
.map(d => d.skillLevel);
|
||||
|
||||
expect(levels).toEqual(['beginner', 'intermediate', 'advanced', 'pro']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset the result', () => {
|
||||
const dto: DriversLeaderboardResultDTO = {
|
||||
drivers: [],
|
||||
rankings: [],
|
||||
stats: {},
|
||||
avatarUrls: {},
|
||||
};
|
||||
|
||||
presenter.present(dto);
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user