refactor
This commit is contained in:
@@ -1,120 +1,137 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { DashboardOverviewPresenter } from './DashboardOverviewPresenter';
|
||||
import type { DashboardOverviewOutputPort } from '@core/racing/application/ports/output/DashboardOverviewOutputPort';
|
||||
import type { DashboardOverviewResult } from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||
import { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import { Race } from '@core/racing/domain/entities/Race';
|
||||
import { League } from '@core/racing/domain/entities/League';
|
||||
import { Standing } from '@core/racing/domain/entities/Standing';
|
||||
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
|
||||
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
||||
|
||||
const createOutput = (): DashboardOverviewOutputPort => ({
|
||||
currentDriver: {
|
||||
id: 'driver-1',
|
||||
name: 'Test Driver',
|
||||
country: 'DE',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
rating: 2500,
|
||||
globalRank: 42,
|
||||
totalRaces: 10,
|
||||
wins: 3,
|
||||
podiums: 5,
|
||||
consistency: 90,
|
||||
},
|
||||
myUpcomingRaces: [
|
||||
{
|
||||
id: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'League 1',
|
||||
track: 'Spa',
|
||||
car: 'GT3',
|
||||
scheduledAt: '2025-01-01T10:00:00Z',
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
],
|
||||
otherUpcomingRaces: [
|
||||
{
|
||||
id: 'race-2',
|
||||
leagueId: 'league-2',
|
||||
leagueName: 'League 2',
|
||||
track: 'Monza',
|
||||
car: 'GT3',
|
||||
scheduledAt: '2025-01-02T10:00:00Z',
|
||||
status: 'scheduled',
|
||||
isMyLeague: false,
|
||||
},
|
||||
],
|
||||
upcomingRaces: [
|
||||
{
|
||||
id: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'League 1',
|
||||
track: 'Spa',
|
||||
car: 'GT3',
|
||||
scheduledAt: '2025-01-01T10:00:00Z',
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
{
|
||||
id: 'race-2',
|
||||
leagueId: 'league-2',
|
||||
leagueName: 'League 2',
|
||||
track: 'Monza',
|
||||
car: 'GT3',
|
||||
scheduledAt: '2025-01-02T10:00:00Z',
|
||||
status: 'scheduled',
|
||||
isMyLeague: false,
|
||||
},
|
||||
],
|
||||
activeLeaguesCount: 2,
|
||||
nextRace: {
|
||||
const createOutput = (): DashboardOverviewResult => {
|
||||
const driver = Driver.create({ id: 'driver-1', iracingId: '12345', name: 'Test Driver', country: 'DE' });
|
||||
const league1 = League.create({ id: 'league-1', name: 'League 1', description: 'First league', ownerId: 'owner-1' });
|
||||
const league2 = League.create({ id: 'league-2', name: 'League 2', description: 'Second league', ownerId: 'owner-2' });
|
||||
const league3 = League.create({ id: 'league-3', name: 'League 3', description: 'Third league', ownerId: 'owner-3' });
|
||||
|
||||
const race1 = Race.create({
|
||||
id: 'race-1',
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'League 1',
|
||||
track: 'Spa',
|
||||
car: 'GT3',
|
||||
scheduledAt: '2025-01-01T10:00:00Z',
|
||||
scheduledAt: new Date('2025-01-01T10:00:00Z'),
|
||||
status: 'scheduled',
|
||||
isMyLeague: true,
|
||||
},
|
||||
recentResults: [
|
||||
{
|
||||
raceId: 'race-3',
|
||||
raceName: 'Nürburgring',
|
||||
leagueId: 'league-3',
|
||||
leagueName: 'League 3',
|
||||
finishedAt: '2024-12-01T10:00:00Z',
|
||||
position: 1,
|
||||
incidents: 0,
|
||||
});
|
||||
const race2 = Race.create({
|
||||
id: 'race-2',
|
||||
leagueId: 'league-2',
|
||||
track: 'Monza',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date('2025-01-02T10:00:00Z'),
|
||||
status: 'scheduled',
|
||||
});
|
||||
const race3 = Race.create({
|
||||
id: 'race-3',
|
||||
leagueId: 'league-3',
|
||||
track: 'Nürburgring',
|
||||
car: 'GT3',
|
||||
scheduledAt: new Date('2024-12-01T10:00:00Z'),
|
||||
status: 'completed',
|
||||
});
|
||||
|
||||
const standing1 = Standing.create({ leagueId: 'league-1', driverId: 'driver-1', position: 1, points: 150 });
|
||||
|
||||
const result1 = RaceResult.create({
|
||||
id: 'result-1',
|
||||
raceId: 'race-3',
|
||||
driverId: 'driver-1',
|
||||
position: 1,
|
||||
fastestLap: 120,
|
||||
incidents: 0,
|
||||
startPosition: 1,
|
||||
});
|
||||
|
||||
const feedItem: FeedItem = {
|
||||
id: 'feed-1',
|
||||
type: 'friend-joined-league',
|
||||
headline: 'You won a race',
|
||||
body: 'Congrats!',
|
||||
timestamp: new Date('2024-12-02T10:00:00Z'),
|
||||
ctaLabel: 'View',
|
||||
ctaHref: '/races/race-3',
|
||||
};
|
||||
|
||||
const friend = Driver.create({ id: 'friend-1', iracingId: '67890', name: 'Friend One', country: 'US' });
|
||||
|
||||
return {
|
||||
currentDriver: {
|
||||
driver,
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
rating: 2500,
|
||||
globalRank: 42,
|
||||
totalRaces: 10,
|
||||
wins: 3,
|
||||
podiums: 5,
|
||||
consistency: 90,
|
||||
},
|
||||
],
|
||||
leagueStandingsSummaries: [
|
||||
{
|
||||
leagueId: 'league-1',
|
||||
leagueName: 'League 1',
|
||||
position: 1,
|
||||
totalDrivers: 20,
|
||||
points: 150,
|
||||
},
|
||||
],
|
||||
feedSummary: {
|
||||
notificationCount: 3,
|
||||
items: [
|
||||
myUpcomingRaces: [
|
||||
{
|
||||
id: 'feed-1',
|
||||
type: 'race_result' as any,
|
||||
headline: 'You won a race',
|
||||
body: 'Congrats!',
|
||||
timestamp: '2024-12-02T10:00:00Z',
|
||||
ctaLabel: 'View',
|
||||
ctaHref: '/races/race-3',
|
||||
race: race1,
|
||||
league: league1,
|
||||
isMyLeague: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
friends: [
|
||||
{
|
||||
id: 'friend-1',
|
||||
name: 'Friend One',
|
||||
country: 'US',
|
||||
avatarUrl: 'https://example.com/friend.jpg',
|
||||
otherUpcomingRaces: [
|
||||
{
|
||||
race: race2,
|
||||
league: league2,
|
||||
isMyLeague: false,
|
||||
},
|
||||
],
|
||||
upcomingRaces: [
|
||||
{
|
||||
race: race1,
|
||||
league: league1,
|
||||
isMyLeague: true,
|
||||
},
|
||||
{
|
||||
race: race2,
|
||||
league: league2,
|
||||
isMyLeague: false,
|
||||
},
|
||||
],
|
||||
activeLeaguesCount: 2,
|
||||
nextRace: {
|
||||
race: race1,
|
||||
league: league1,
|
||||
isMyLeague: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
recentResults: [
|
||||
{
|
||||
race: race3,
|
||||
league: league3,
|
||||
result: result1,
|
||||
},
|
||||
],
|
||||
leagueStandingsSummaries: [
|
||||
{
|
||||
league: league1,
|
||||
standing: standing1,
|
||||
totalDrivers: 20,
|
||||
},
|
||||
],
|
||||
feedSummary: {
|
||||
notificationCount: 3,
|
||||
items: [feedItem],
|
||||
},
|
||||
friends: [
|
||||
{
|
||||
driver: friend,
|
||||
avatarUrl: 'https://example.com/friend.jpg',
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
describe('DashboardOverviewPresenter', () => {
|
||||
let presenter: DashboardOverviewPresenter;
|
||||
@@ -123,44 +140,23 @@ describe('DashboardOverviewPresenter', () => {
|
||||
presenter = new DashboardOverviewPresenter();
|
||||
});
|
||||
|
||||
it('maps DashboardOverviewOutputPort to DashboardOverviewDTO correctly', () => {
|
||||
it('maps DashboardOverviewResult to DashboardOverviewDTO correctly', () => {
|
||||
const output = createOutput();
|
||||
|
||||
presenter.present(output);
|
||||
const dto = presenter.getResponseModel();
|
||||
|
||||
const viewModel = presenter.viewModel;
|
||||
|
||||
expect(viewModel.activeLeaguesCount).toBe(2);
|
||||
expect(viewModel.currentDriver?.id).toBe('driver-1');
|
||||
expect(viewModel.myUpcomingRaces[0].id).toBe('race-1');
|
||||
expect(viewModel.otherUpcomingRaces[0].id).toBe('race-2');
|
||||
expect(viewModel.upcomingRaces).toHaveLength(2);
|
||||
expect(viewModel.nextRace?.id).toBe('race-1');
|
||||
expect(viewModel.recentResults[0].raceId).toBe('race-3');
|
||||
expect(viewModel.leagueStandingsSummaries[0].leagueId).toBe('league-1');
|
||||
expect(viewModel.feedSummary.notificationCount).toBe(3);
|
||||
expect(viewModel.feedSummary.items[0].id).toBe('feed-1');
|
||||
expect(viewModel.friends[0].id).toBe('friend-1');
|
||||
expect(dto.activeLeaguesCount).toBe(2);
|
||||
expect(dto.currentDriver?.id).toBe('driver-1');
|
||||
expect(dto.myUpcomingRaces[0].id).toBe('race-1');
|
||||
expect(dto.otherUpcomingRaces[0].id).toBe('race-2');
|
||||
expect(dto.upcomingRaces).toHaveLength(2);
|
||||
expect(dto.nextRace?.id).toBe('race-1');
|
||||
expect(dto.recentResults[0].raceId).toBe('race-3');
|
||||
expect(dto.leagueStandingsSummaries[0].leagueId).toBe('league-1');
|
||||
expect(dto.feedSummary.notificationCount).toBe(3);
|
||||
expect(dto.feedSummary.items[0].id).toBe('feed-1');
|
||||
expect(dto.friends[0].id).toBe('friend-1');
|
||||
});
|
||||
|
||||
it('reset clears state and causes viewModel to throw', () => {
|
||||
const output = createOutput();
|
||||
presenter.present(output);
|
||||
expect(presenter.viewModel).toBeDefined();
|
||||
|
||||
presenter.reset();
|
||||
|
||||
expect(() => presenter.viewModel).toThrow('Presenter not presented');
|
||||
});
|
||||
|
||||
it('getViewModel returns null when not presented', () => {
|
||||
expect(presenter.getViewModel()).toBeNull();
|
||||
});
|
||||
|
||||
it('getViewModel returns same DTO after present', () => {
|
||||
const output = createOutput();
|
||||
presenter.present(output);
|
||||
|
||||
expect(presenter.getViewModel()).toEqual(presenter.viewModel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { DashboardOverviewOutputPort } from '@core/racing/application/ports/output/DashboardOverviewOutputPort';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type {
|
||||
DashboardOverviewResult,
|
||||
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
||||
import {
|
||||
DashboardOverviewDTO,
|
||||
DashboardDriverSummaryDTO,
|
||||
@@ -10,93 +13,89 @@ import {
|
||||
DashboardFriendSummaryDTO,
|
||||
} from '../dtos/DashboardOverviewDTO';
|
||||
|
||||
export class DashboardOverviewPresenter {
|
||||
private result: DashboardOverviewDTO | null = null;
|
||||
export class DashboardOverviewPresenter implements UseCaseOutputPort<DashboardOverviewResult> {
|
||||
private responseModel: DashboardOverviewDTO | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(output: DashboardOverviewOutputPort): void {
|
||||
const currentDriver: DashboardDriverSummaryDTO | null = output.currentDriver
|
||||
present(result: DashboardOverviewResult): void {
|
||||
const currentDriver: DashboardDriverSummaryDTO | null = result.currentDriver
|
||||
? {
|
||||
id: output.currentDriver.id,
|
||||
name: output.currentDriver.name,
|
||||
country: output.currentDriver.country,
|
||||
avatarUrl: output.currentDriver.avatarUrl,
|
||||
rating: output.currentDriver.rating,
|
||||
globalRank: output.currentDriver.globalRank,
|
||||
totalRaces: output.currentDriver.totalRaces,
|
||||
wins: output.currentDriver.wins,
|
||||
podiums: output.currentDriver.podiums,
|
||||
consistency: output.currentDriver.consistency,
|
||||
id: result.currentDriver.driver.id,
|
||||
name: String(result.currentDriver.driver.name),
|
||||
country: String(result.currentDriver.driver.country),
|
||||
avatarUrl: result.currentDriver.avatarUrl,
|
||||
rating: result.currentDriver.rating,
|
||||
globalRank: result.currentDriver.globalRank,
|
||||
totalRaces: result.currentDriver.totalRaces,
|
||||
wins: result.currentDriver.wins,
|
||||
podiums: result.currentDriver.podiums,
|
||||
consistency: result.currentDriver.consistency,
|
||||
}
|
||||
: null;
|
||||
|
||||
const mapRace = (race: typeof output.myUpcomingRaces[number]): DashboardRaceSummaryDTO => ({
|
||||
id: race.id,
|
||||
leagueId: race.leagueId,
|
||||
leagueName: race.leagueName,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
status: race.status,
|
||||
isMyLeague: race.isMyLeague,
|
||||
const mapRace = (raceSummary: DashboardOverviewResult['myUpcomingRaces'][number]): DashboardRaceSummaryDTO => ({
|
||||
id: raceSummary.race.id,
|
||||
leagueId: raceSummary.league?.id ? String(raceSummary.league.id) : null,
|
||||
leagueName: raceSummary.league?.name ? String(raceSummary.league.name) : null,
|
||||
track: String(raceSummary.race.track),
|
||||
car: String(raceSummary.race.car),
|
||||
scheduledAt: raceSummary.race.scheduledAt.toISOString(),
|
||||
status: raceSummary.race.status as 'scheduled' | 'running' | 'completed' | 'cancelled',
|
||||
isMyLeague: raceSummary.isMyLeague,
|
||||
});
|
||||
|
||||
const myUpcomingRaces: DashboardRaceSummaryDTO[] = output.myUpcomingRaces.map(mapRace);
|
||||
const otherUpcomingRaces: DashboardRaceSummaryDTO[] = output.otherUpcomingRaces.map(mapRace);
|
||||
const upcomingRaces: DashboardRaceSummaryDTO[] = output.upcomingRaces.map(mapRace);
|
||||
const myUpcomingRaces: DashboardRaceSummaryDTO[] = result.myUpcomingRaces.map(mapRace);
|
||||
const otherUpcomingRaces: DashboardRaceSummaryDTO[] = result.otherUpcomingRaces.map(mapRace);
|
||||
const upcomingRaces: DashboardRaceSummaryDTO[] = result.upcomingRaces.map(mapRace);
|
||||
|
||||
const nextRace: DashboardRaceSummaryDTO | null = output.nextRace ? mapRace(output.nextRace) : null;
|
||||
const nextRace: DashboardRaceSummaryDTO | null = result.nextRace ? mapRace(result.nextRace) : null;
|
||||
|
||||
const recentResults: DashboardRecentResultDTO[] = output.recentResults.map(result => ({
|
||||
raceId: result.raceId,
|
||||
raceName: result.raceName,
|
||||
leagueId: result.leagueId,
|
||||
leagueName: result.leagueName,
|
||||
finishedAt: result.finishedAt,
|
||||
position: result.position,
|
||||
incidents: result.incidents,
|
||||
const recentResults: DashboardRecentResultDTO[] = result.recentResults.map(resultSummary => ({
|
||||
raceId: resultSummary.race.id,
|
||||
raceName: String(resultSummary.race.track),
|
||||
leagueId: resultSummary.league?.id ? String(resultSummary.league.id) : null,
|
||||
leagueName: resultSummary.league?.name ? String(resultSummary.league.name) : null,
|
||||
finishedAt: resultSummary.race.scheduledAt.toISOString(),
|
||||
position: Number(resultSummary.result.position),
|
||||
incidents: Number(resultSummary.result.incidents),
|
||||
}));
|
||||
|
||||
const leagueStandingsSummaries: DashboardLeagueStandingSummaryDTO[] =
|
||||
output.leagueStandingsSummaries.map(standing => ({
|
||||
leagueId: standing.leagueId,
|
||||
leagueName: standing.leagueName,
|
||||
position: standing.position,
|
||||
result.leagueStandingsSummaries.map(standing => ({
|
||||
leagueId: String(standing.league.id),
|
||||
leagueName: String(standing.league.name),
|
||||
position: standing.standing?.position ? Number(standing.standing.position) : null,
|
||||
totalDrivers: standing.totalDrivers,
|
||||
points: standing.points,
|
||||
points: standing.standing?.points ? Number(standing.standing.points) : null,
|
||||
}));
|
||||
|
||||
const feedItems: DashboardFeedItemSummaryDTO[] = output.feedSummary.items.map(item => ({
|
||||
const feedItems: DashboardFeedItemSummaryDTO[] = result.feedSummary.items.map(item => ({
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
type: String(item.type),
|
||||
headline: item.headline,
|
||||
body: item.body,
|
||||
timestamp: item.timestamp,
|
||||
ctaLabel: item.ctaLabel,
|
||||
ctaHref: item.ctaHref,
|
||||
body: item.body ?? '',
|
||||
timestamp: item.timestamp.toISOString(),
|
||||
ctaLabel: item.ctaLabel ?? '',
|
||||
ctaHref: item.ctaHref ?? '',
|
||||
}));
|
||||
|
||||
const feedSummary: DashboardFeedSummaryDTO = {
|
||||
notificationCount: output.feedSummary.notificationCount,
|
||||
notificationCount: result.feedSummary.notificationCount,
|
||||
items: feedItems,
|
||||
};
|
||||
|
||||
const friends: DashboardFriendSummaryDTO[] = output.friends.map(friend => ({
|
||||
id: friend.id,
|
||||
name: friend.name,
|
||||
country: friend.country,
|
||||
const friends: DashboardFriendSummaryDTO[] = result.friends.map(friend => ({
|
||||
id: friend.driver.id,
|
||||
name: String(friend.driver.name),
|
||||
country: String(friend.driver.country),
|
||||
avatarUrl: friend.avatarUrl,
|
||||
}));
|
||||
|
||||
this.result = {
|
||||
this.responseModel = {
|
||||
currentDriver,
|
||||
myUpcomingRaces,
|
||||
otherUpcomingRaces,
|
||||
upcomingRaces,
|
||||
activeLeaguesCount: output.activeLeaguesCount,
|
||||
activeLeaguesCount: result.activeLeaguesCount,
|
||||
nextRace,
|
||||
recentResults,
|
||||
leagueStandingsSummaries,
|
||||
@@ -105,12 +104,8 @@ export class DashboardOverviewPresenter {
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): DashboardOverviewDTO {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
|
||||
getViewModel(): DashboardOverviewDTO | null {
|
||||
return this.result;
|
||||
getResponseModel(): DashboardOverviewDTO {
|
||||
if (!this.responseModel) throw new Error('Presenter not presented');
|
||||
return this.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user