import { describe, it, expect } from 'vitest'; import { DashboardViewDataBuilder } from './DashboardViewDataBuilder'; import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO'; describe('DashboardViewDataBuilder', () => { describe('happy paths', () => { it('should transform DashboardOverviewDTO to DashboardViewData correctly', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'John Doe', country: 'USA', avatarUrl: 'https://example.com/avatar.jpg', rating: 1234.56, globalRank: 42, totalRaces: 150, wins: 25, podiums: 60, consistency: 85, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 3, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 5, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.currentDriver).toEqual({ name: 'John Doe', avatarUrl: 'https://example.com/avatar.jpg', country: 'USA', rating: '1,235', rank: '42', totalRaces: '150', wins: '25', podiums: '60', consistency: '85%', }); expect(result.nextRace).toBeNull(); expect(result.upcomingRaces).toEqual([]); expect(result.leagueStandings).toEqual([]); expect(result.feedItems).toEqual([]); expect(result.friends).toEqual([]); expect(result.activeLeaguesCount).toBe('3'); expect(result.friendCount).toBe('0'); expect(result.hasUpcomingRaces).toBe(false); expect(result.hasLeagueStandings).toBe(false); expect(result.hasFeedItems).toBe(false); expect(result.hasFriends).toBe(false); }); it('should handle missing currentDriver gracefully', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 0, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.currentDriver).toEqual({ name: '', avatarUrl: '', country: '', rating: '0.0', rank: '0', totalRaces: '0', wins: '0', podiums: '0', consistency: '0%', }); }); it('should handle null/undefined driver fields', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'Jane Smith', country: 'Canada', rating: null, globalRank: null, totalRaces: 0, wins: 0, podiums: 0, consistency: null, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 0, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.currentDriver.rating).toBe('0'); expect(result.currentDriver.rank).toBe('0'); expect(result.currentDriver.consistency).toBe('0%'); }); it('should handle nextRace with all fields', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours from now const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: { id: 'race-456', leagueId: 'league-789', leagueName: 'Pro League', track: 'Monza', car: 'Ferrari 488 GT3', scheduledAt: futureDate.toISOString(), status: 'scheduled', isMyLeague: true, }, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.nextRace).not.toBeNull(); expect(result.nextRace?.id).toBe('race-456'); expect(result.nextRace?.track).toBe('Monza'); expect(result.nextRace?.car).toBe('Ferrari 488 GT3'); expect(result.nextRace?.scheduledAt).toBe(futureDate.toISOString()); expect(result.nextRace?.isMyLeague).toBe(true); expect(result.nextRace?.formattedDate).toBeDefined(); expect(result.nextRace?.formattedTime).toBeDefined(); expect(result.nextRace?.timeUntil).toBeDefined(); }); it('should handle upcomingRaces with multiple races', () => { const now = new Date(); const race1Date = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000); // 2 days from now const race2Date = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000); // 5 days from now const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [ { id: 'race-1', track: 'Spa', car: 'Porsche 911 GT3', scheduledAt: race1Date.toISOString(), status: 'scheduled', isMyLeague: true, }, { id: 'race-2', track: 'Nürburgring', car: 'Audi R8 LMS', scheduledAt: race2Date.toISOString(), status: 'scheduled', isMyLeague: false, }, ], activeLeaguesCount: 2, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.upcomingRaces).toHaveLength(2); expect(result.upcomingRaces[0].id).toBe('race-1'); expect(result.upcomingRaces[0].track).toBe('Spa'); expect(result.upcomingRaces[0].isMyLeague).toBe(true); expect(result.upcomingRaces[1].id).toBe('race-2'); expect(result.upcomingRaces[1].track).toBe('Nürburgring'); expect(result.upcomingRaces[1].isMyLeague).toBe(false); expect(result.hasUpcomingRaces).toBe(true); }); it('should handle leagueStandings with multiple leagues', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 2, nextRace: null, recentResults: [], leagueStandingsSummaries: [ { leagueId: 'league-1', leagueName: 'Rookie League', position: 5, totalDrivers: 50, points: 1250, }, { leagueId: 'league-2', leagueName: 'Pro League', position: 12, totalDrivers: 100, points: 890, }, ], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.leagueStandings).toHaveLength(2); expect(result.leagueStandings[0].leagueId).toBe('league-1'); expect(result.leagueStandings[0].leagueName).toBe('Rookie League'); expect(result.leagueStandings[0].position).toBe('#5'); expect(result.leagueStandings[0].points).toBe('1250'); expect(result.leagueStandings[0].totalDrivers).toBe('50'); expect(result.leagueStandings[1].leagueId).toBe('league-2'); expect(result.leagueStandings[1].leagueName).toBe('Pro League'); expect(result.leagueStandings[1].position).toBe('#12'); expect(result.leagueStandings[1].points).toBe('890'); expect(result.leagueStandings[1].totalDrivers).toBe('100'); expect(result.hasLeagueStandings).toBe(true); }); it('should handle feedItems with all fields', () => { const now = new Date(); const timestamp = new Date(now.getTime() - 30 * 60 * 1000); // 30 minutes ago const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 3, items: [ { id: 'feed-1', type: 'race_result', headline: 'Race completed', body: 'You finished 3rd in the Pro League race', timestamp: timestamp.toISOString(), ctaLabel: 'View Results', ctaHref: '/races/123', }, { id: 'feed-2', type: 'league_update', headline: 'League standings updated', body: 'You moved up 2 positions', timestamp: timestamp.toISOString(), }, ], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.feedItems).toHaveLength(2); expect(result.feedItems[0].id).toBe('feed-1'); expect(result.feedItems[0].type).toBe('race_result'); expect(result.feedItems[0].headline).toBe('Race completed'); expect(result.feedItems[0].body).toBe('You finished 3rd in the Pro League race'); expect(result.feedItems[0].timestamp).toBe(timestamp.toISOString()); expect(result.feedItems[0].formattedTime).toBe('Past'); expect(result.feedItems[0].ctaLabel).toBe('View Results'); expect(result.feedItems[0].ctaHref).toBe('/races/123'); expect(result.feedItems[1].id).toBe('feed-2'); expect(result.feedItems[1].type).toBe('league_update'); expect(result.feedItems[1].headline).toBe('League standings updated'); expect(result.feedItems[1].body).toBe('You moved up 2 positions'); expect(result.feedItems[1].ctaLabel).toBeUndefined(); expect(result.feedItems[1].ctaHref).toBeUndefined(); expect(result.hasFeedItems).toBe(true); }); it('should handle friends with avatar URLs', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [ { id: 'friend-1', name: 'Alice', country: 'UK', avatarUrl: 'https://example.com/alice.jpg', }, { id: 'friend-2', name: 'Bob', country: 'Germany', avatarUrl: undefined, }, ], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.friends).toHaveLength(2); expect(result.friends[0].id).toBe('friend-1'); expect(result.friends[0].name).toBe('Alice'); expect(result.friends[0].country).toBe('UK'); expect(result.friends[0].avatarUrl).toBe('https://example.com/alice.jpg'); expect(result.friends[1].id).toBe('friend-2'); expect(result.friends[1].name).toBe('Bob'); expect(result.friends[1].country).toBe('Germany'); expect(result.friends[1].avatarUrl).toBe(''); expect(result.friendCount).toBe('2'); expect(result.hasFriends).toBe(true); }); it('should handle empty arrays and zero counts', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 0, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.upcomingRaces).toEqual([]); expect(result.leagueStandings).toEqual([]); expect(result.feedItems).toEqual([]); expect(result.friends).toEqual([]); expect(result.activeLeaguesCount).toBe('0'); expect(result.friendCount).toBe('0'); expect(result.hasUpcomingRaces).toBe(false); expect(result.hasLeagueStandings).toBe(false); expect(result.hasFeedItems).toBe(false); expect(result.hasFriends).toBe(false); }); }); describe('data transformation', () => { it('should preserve all DTO fields in the output', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'John Doe', country: 'USA', avatarUrl: 'https://example.com/avatar.jpg', rating: 1234.56, globalRank: 42, totalRaces: 150, wins: 25, podiums: 60, consistency: 85, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 3, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 5, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.currentDriver.name).toBe(dashboardDTO.currentDriver?.name); expect(result.currentDriver.country).toBe(dashboardDTO.currentDriver?.country); expect(result.currentDriver.avatarUrl).toBe(dashboardDTO.currentDriver?.avatarUrl); expect(result.activeLeaguesCount).toBe(dashboardDTO.activeLeaguesCount.toString()); }); it('should not modify the input DTO', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'John Doe', country: 'USA', rating: 1234.56, globalRank: 42, totalRaces: 150, wins: 25, podiums: 60, consistency: 85, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 3, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 5, items: [], }, friends: [], }; const originalDTO = JSON.parse(JSON.stringify(dashboardDTO)); DashboardViewDataBuilder.build(dashboardDTO); expect(dashboardDTO).toEqual(originalDTO); }); it('should transform all numeric fields to formatted strings', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'John Doe', country: 'USA', rating: 1234.56, globalRank: 42, totalRaces: 150, wins: 25, podiums: 60, consistency: 85, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 3, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 5, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(typeof result.currentDriver.rating).toBe('string'); expect(typeof result.currentDriver.rank).toBe('string'); expect(typeof result.currentDriver.totalRaces).toBe('string'); expect(typeof result.currentDriver.wins).toBe('string'); expect(typeof result.currentDriver.podiums).toBe('string'); expect(typeof result.currentDriver.consistency).toBe('string'); expect(typeof result.activeLeaguesCount).toBe('string'); expect(typeof result.friendCount).toBe('string'); }); it('should handle large numbers correctly', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'John Doe', country: 'USA', rating: 999999.99, globalRank: 1, totalRaces: 10000, wins: 2500, podiums: 5000, consistency: 99.9, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 100, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.currentDriver.rating).toBe('1,000,000'); expect(result.currentDriver.totalRaces).toBe('10000'); expect(result.currentDriver.wins).toBe('2500'); expect(result.currentDriver.podiums).toBe('5000'); expect(result.currentDriver.consistency).toBe('99.9%'); expect(result.activeLeaguesCount).toBe('100'); }); }); describe('edge cases', () => { it('should handle missing optional fields in driver', () => { const dashboardDTO: DashboardOverviewDTO = { currentDriver: { id: 'driver-123', name: 'John Doe', country: 'USA', totalRaces: 100, wins: 20, podiums: 40, }, myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.currentDriver.avatarUrl).toBe(''); expect(result.currentDriver.rating).toBe('0'); expect(result.currentDriver.rank).toBe('0'); expect(result.currentDriver.consistency).toBe('0%'); }); it('should handle race with missing optional fields', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: { id: 'race-456', track: 'Monza', car: 'Ferrari 488 GT3', scheduledAt: futureDate.toISOString(), status: 'scheduled', isMyLeague: true, }, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.nextRace).not.toBeNull(); expect(result.nextRace?.leagueId).toBeUndefined(); expect(result.nextRace?.leagueName).toBeUndefined(); }); it('should handle feed item with missing optional fields', () => { const now = new Date(); const timestamp = new Date(now.getTime() - 60 * 60 * 1000); // 1 hour ago const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 1, items: [ { id: 'feed-1', type: 'notification', headline: 'New notification', timestamp: timestamp.toISOString(), }, ], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.feedItems[0].body).toBeUndefined(); expect(result.feedItems[0].ctaLabel).toBeUndefined(); expect(result.feedItems[0].ctaHref).toBeUndefined(); }); it('should handle friend with missing avatarUrl', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [ { id: 'friend-1', name: 'Alice', country: 'UK', }, ], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.friends[0].avatarUrl).toBe(''); }); it('should handle league standing with null position', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [ { leagueId: 'league-1', leagueName: 'Test League', position: null as any, totalDrivers: 50, points: 1000, }, ], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.leagueStandings[0].position).toBe('-'); }); it('should handle race with empty track and car', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: { id: 'race-456', track: '', car: '', scheduledAt: futureDate.toISOString(), status: 'scheduled', isMyLeague: true, }, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.nextRace?.track).toBe(''); expect(result.nextRace?.car).toBe(''); }); }); describe('derived fields', () => { it('should correctly calculate hasUpcomingRaces', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [ { id: 'race-1', track: 'Spa', car: 'Porsche', scheduledAt: new Date().toISOString(), status: 'scheduled', isMyLeague: true, }, ], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.hasUpcomingRaces).toBe(true); }); it('should correctly calculate hasLeagueStandings', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [ { leagueId: 'league-1', leagueName: 'Test League', position: 5, totalDrivers: 50, points: 1000, }, ], feedSummary: { notificationCount: 0, items: [], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.hasLeagueStandings).toBe(true); }); it('should correctly calculate hasFeedItems', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 1, items: [ { id: 'feed-1', type: 'notification', headline: 'Test', timestamp: new Date().toISOString(), }, ], }, friends: [], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.hasFeedItems).toBe(true); }); it('should correctly calculate hasFriends', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [ { id: 'friend-1', name: 'Alice', country: 'UK', }, ], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.hasFriends).toBe(true); }); it('should correctly calculate friendCount', () => { const dashboardDTO: DashboardOverviewDTO = { myUpcomingRaces: [], otherUpcomingRaces: [], upcomingRaces: [], activeLeaguesCount: 1, nextRace: null, recentResults: [], leagueStandingsSummaries: [], feedSummary: { notificationCount: 0, items: [], }, friends: [ { id: 'friend-1', name: 'Alice', country: 'UK' }, { id: 'friend-2', name: 'Bob', country: 'Germany' }, { id: 'friend-3', name: 'Charlie', country: 'France' }, ], }; const result = DashboardViewDataBuilder.build(dashboardDTO); expect(result.friendCount).toBe('3'); }); }); });