import { describe, it, expect, beforeEach } from 'vitest'; import { LeagueDetailViewDataBuilder } from '../../../apps/website/lib/builders/view-data/LeagueDetailViewDataBuilder'; import type { LeagueWithCapacityAndScoringDTO } from '../../../apps/website/lib/types/generated/LeagueWithCapacityAndScoringDTO'; import type { LeagueMembershipsDTO } from '../../../apps/website/lib/types/generated/LeagueMembershipsDTO'; import type { RaceDTO } from '../../../apps/website/lib/types/generated/RaceDTO'; import type { GetDriverOutputDTO } from '../../../apps/website/lib/types/generated/GetDriverOutputDTO'; import type { LeagueScoringConfigDTO } from '../../../apps/website/lib/types/generated/LeagueScoringConfigDTO'; describe('LeagueDetailViewDataBuilder', () => { const mockLeague: LeagueWithCapacityAndScoringDTO = { id: 'league-123', name: 'Test League', description: 'A test league description', ownerId: 'owner-456', createdAt: '2024-01-01T00:00:00Z', settings: { maxDrivers: 32, qualifyingFormat: 'Solo', }, usedSlots: 15, category: 'Road', socialLinks: { discordUrl: 'https://discord.gg/test', youtubeUrl: 'https://youtube.com/test', websiteUrl: 'https://test.com', }, scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'Solo', scoringPresetId: 'preset-1', scoringPresetName: 'Standard', dropPolicySummary: 'Drop 2 worst races', scoringPatternSummary: 'Points based on finish position', }, timingSummary: 'Every Sunday at 8 PM', logoUrl: 'https://logo.com/test.png', pendingJoinRequestsCount: 3, pendingProtestsCount: 1, walletBalance: 1000, }; const mockOwner: GetDriverOutputDTO = { id: 'owner-456', iracingId: '12345', name: 'John Doe', country: 'US', joinedAt: '2024-01-01T00:00:00Z', avatarUrl: 'https://avatar.com/john.png', rating: 850, }; const mockScoringConfig: LeagueScoringConfigDTO = { leagueId: 'league-123', seasonId: 'season-1', gameId: 'game-1', gameName: 'Test Game', scoringPresetId: 'preset-1', scoringPresetName: 'Standard', dropPolicySummary: 'Drop 2 worst races', championships: [], }; const mockMemberships: LeagueMembershipsDTO = { members: [ { driverId: 'owner-456', driver: { id: 'owner-456', name: 'John Doe', iracingId: '12345', country: 'US', joinedAt: '2024-01-01T00:00:00Z', }, role: 'owner', joinedAt: '2024-01-01T00:00:00Z', }, { driverId: 'admin-789', driver: { id: 'admin-789', name: 'Jane Smith', iracingId: '67890', country: 'UK', joinedAt: '2024-01-02T00:00:00Z', }, role: 'admin', joinedAt: '2024-01-02T00:00:00Z', }, { driverId: 'steward-101', driver: { id: 'steward-101', name: 'Bob Wilson', iracingId: '11111', country: 'CA', joinedAt: '2024-01-03T00:00:00Z', }, role: 'steward', joinedAt: '2024-01-03T00:00:00Z', }, { driverId: 'member-202', driver: { id: 'member-202', name: 'Alice Brown', iracingId: '22222', country: 'AU', joinedAt: '2024-01-04T00:00:00Z', }, role: 'member', joinedAt: '2024-01-04T00:00:00Z', }, ], }; const mockSponsors = [ { id: 'sponsor-1', name: 'Test Sponsor', tier: 'main' as const, logoUrl: 'https://sponsor.com/logo.png', websiteUrl: 'https://sponsor.com', tagline: 'Best sponsor ever', }, ]; describe('build()', () => { it('should transform all input data correctly', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-2', name: 'Race 2', date: '2024-02-08T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.leagueId).toBe('league-123'); expect(result.name).toBe('Test League'); expect(result.description).toBe('A test league description'); expect(result.logoUrl).toBe('https://logo.com/test.png'); expect(result.walletBalance).toBe(1000); expect(result.pendingProtestsCount).toBe(1); expect(result.pendingJoinRequestsCount).toBe(3); // Check info data expect(result.info.name).toBe('Test League'); expect(result.info.description).toBe('A test league description'); expect(result.info.membersCount).toBe(4); expect(result.info.racesCount).toBe(2); expect(result.info.avgSOF).toBeNull(); expect(result.info.structure).toBe('Solo • 32 max'); expect(result.info.scoring).toBe('preset-1'); expect(result.info.createdAt).toBe('2024-01-01T00:00:00Z'); expect(result.info.discordUrl).toBe('https://discord.gg/test'); expect(result.info.youtubeUrl).toBe('https://youtube.com/test'); expect(result.info.websiteUrl).toBe('https://test.com'); // Check owner summary expect(result.ownerSummary).not.toBeNull(); expect(result.ownerSummary?.driverId).toBe('owner-456'); expect(result.ownerSummary?.driverName).toBe('John Doe'); expect(result.ownerSummary?.avatarUrl).toBe('https://avatar.com/john.png'); expect(result.ownerSummary?.roleBadgeText).toBe('Owner'); expect(result.ownerSummary?.profileUrl).toBe('/drivers/owner-456'); // Check admin summaries expect(result.adminSummaries).toHaveLength(1); expect(result.adminSummaries[0].driverId).toBe('admin-789'); expect(result.adminSummaries[0].roleBadgeText).toBe('Admin'); // Check steward summaries expect(result.stewardSummaries).toHaveLength(1); expect(result.stewardSummaries[0].driverId).toBe('steward-101'); expect(result.stewardSummaries[0].roleBadgeText).toBe('Steward'); // Check member summaries expect(result.memberSummaries).toHaveLength(1); expect(result.memberSummaries[0].driverId).toBe('member-202'); expect(result.memberSummaries[0].roleBadgeText).toBe('Member'); // Check sponsors expect(result.sponsors).toHaveLength(1); expect(result.sponsors[0].id).toBe('sponsor-1'); expect(result.sponsors[0].name).toBe('Test Sponsor'); expect(result.sponsors[0].tier).toBe('main'); // Check running races (empty in this case) expect(result.runningRaces).toEqual([]); }); it('should calculate next race correctly', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 86400000).toISOString(); // 1 day from now const pastDate = new Date(now.getTime() - 86400000).toISOString(); // 1 day ago const races: RaceDTO[] = [ { id: 'race-past', name: 'Past Race', date: pastDate, leagueName: 'Test League', }, { id: 'race-future-1', name: 'Future Race 1', date: futureDate, leagueName: 'Test League', }, { id: 'race-future-2', name: 'Future Race 2', date: new Date(now.getTime() + 172800000).toISOString(), // 2 days from now leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.nextRace).toBeDefined(); expect(result.nextRace?.id).toBe('race-future-1'); expect(result.nextRace?.name).toBe('Future Race 1'); expect(result.nextRace?.date).toBe(futureDate); }); it('should handle no upcoming races', () => { const pastDate = new Date(Date.now() - 86400000).toISOString(); const races: RaceDTO[] = [ { id: 'race-past-1', name: 'Past Race 1', date: pastDate, leagueName: 'Test League', }, { id: 'race-past-2', name: 'Past Race 2', date: new Date(Date.now() - 172800000).toISOString(), leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.nextRace).toBeUndefined(); }); it('should calculate season progress correctly', () => { const now = new Date(); const pastDate = new Date(now.getTime() - 86400000).toISOString(); const futureDate = new Date(now.getTime() + 86400000).toISOString(); const races: RaceDTO[] = [ { id: 'race-past-1', name: 'Past Race 1', date: pastDate, leagueName: 'Test League', }, { id: 'race-past-2', name: 'Past Race 2', date: new Date(now.getTime() - 172800000).toISOString(), leagueName: 'Test League', }, { id: 'race-future-1', name: 'Future Race 1', date: futureDate, leagueName: 'Test League', }, { id: 'race-future-2', name: 'Future Race 2', date: new Date(now.getTime() + 172800000).toISOString(), leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.seasonProgress).toBeDefined(); expect(result.seasonProgress?.completedRaces).toBe(2); expect(result.seasonProgress?.totalRaces).toBe(4); expect(result.seasonProgress?.percentage).toBe(50); }); it('should handle no races for season progress', () => { const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races: [], sponsors: mockSponsors, }); expect(result.seasonProgress).toBeDefined(); expect(result.seasonProgress?.completedRaces).toBe(0); expect(result.seasonProgress?.totalRaces).toBe(0); expect(result.seasonProgress?.percentage).toBe(0); }); it('should extract recent results from last completed race', () => { const now = new Date(); const pastDate = new Date(now.getTime() - 86400000).toISOString(); const futureDate = new Date(now.getTime() + 86400000).toISOString(); const races: RaceDTO[] = [ { id: 'race-past-1', name: 'Past Race 1', date: pastDate, leagueName: 'Test League', }, { id: 'race-past-2', name: 'Past Race 2', date: new Date(now.getTime() - 172800000).toISOString(), leagueName: 'Test League', }, { id: 'race-future-1', name: 'Future Race 1', date: futureDate, leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.recentResults).toBeDefined(); expect(result.recentResults?.length).toBe(2); expect(result.recentResults?.[0].raceId).toBe('race-past-1'); expect(result.recentResults?.[0].raceName).toBe('Past Race 1'); expect(result.recentResults?.[1].raceId).toBe('race-past-2'); }); it('should handle no completed races for recent results', () => { const futureDate = new Date(Date.now() + 86400000).toISOString(); const races: RaceDTO[] = [ { id: 'race-future-1', name: 'Future Race 1', date: futureDate, leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.recentResults).toBeDefined(); expect(result.recentResults?.length).toBe(0); }); it('should handle null owner', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: null, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.ownerSummary).toBeNull(); }); it('should handle null scoring config', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: null, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.info.scoring).toBe('Standard'); }); it('should handle empty memberships', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: { members: [] }, races, sponsors: mockSponsors, }); expect(result.info.membersCount).toBe(0); expect(result.adminSummaries).toHaveLength(0); expect(result.stewardSummaries).toHaveLength(0); expect(result.memberSummaries).toHaveLength(0); }); it('should calculate avgSOF from races with strengthOfField', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-2', name: 'Race 2', date: '2024-02-08T18:00:00Z', leagueName: 'Test League', }, ]; // Add strengthOfField to races (races[0] as any).strengthOfField = 1500; (races[1] as any).strengthOfField = 1800; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.info.avgSOF).toBe(1650); }); it('should ignore races with zero or null strengthOfField', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-2', name: 'Race 2', date: '2024-02-08T18:00:00Z', leagueName: 'Test League', }, ]; // Add strengthOfField to races (races[0] as any).strengthOfField = 0; (races[1] as any).strengthOfField = null; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.info.avgSOF).toBeNull(); }); it('should handle empty races array', () => { const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races: [], sponsors: mockSponsors, }); expect(result.info.racesCount).toBe(0); expect(result.info.avgSOF).toBeNull(); expect(result.nextRace).toBeUndefined(); expect(result.seasonProgress?.completedRaces).toBe(0); expect(result.seasonProgress?.totalRaces).toBe(0); expect(result.recentResults?.length).toBe(0); }); it('should handle empty sponsors array', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: [], }); expect(result.sponsors).toHaveLength(0); }); it('should handle missing social links', () => { const leagueWithoutSocialLinks: LeagueWithCapacityAndScoringDTO = { ...mockLeague, socialLinks: undefined, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: leagueWithoutSocialLinks, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.info.discordUrl).toBeUndefined(); expect(result.info.youtubeUrl).toBeUndefined(); expect(result.info.websiteUrl).toBeUndefined(); }); it('should handle missing category', () => { const leagueWithoutCategory: LeagueWithCapacityAndScoringDTO = { ...mockLeague, category: undefined, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: leagueWithoutCategory, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.info).toBeDefined(); }); it('should handle missing description', () => { const leagueWithoutDescription: LeagueWithCapacityAndScoringDTO = { ...mockLeague, description: '', }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: leagueWithoutDescription, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.description).toBe(''); expect(result.info.description).toBe(''); }); it('should handle missing logoUrl', () => { const leagueWithoutLogo: LeagueWithCapacityAndScoringDTO = { ...mockLeague, logoUrl: undefined, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: leagueWithoutLogo, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.logoUrl).toBeUndefined(); }); it('should handle missing admin fields', () => { const leagueWithoutAdminFields: LeagueWithCapacityAndScoringDTO = { ...mockLeague, pendingJoinRequestsCount: undefined, pendingProtestsCount: undefined, walletBalance: undefined, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: leagueWithoutAdminFields, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.walletBalance).toBeUndefined(); expect(result.pendingProtestsCount).toBeUndefined(); expect(result.pendingJoinRequestsCount).toBeUndefined(); }); it('should extract running races correctly', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Running Race 1', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-2', name: 'Past Race', date: '2024-01-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-3', name: 'Running Race 2', date: '2024-02-08T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.runningRaces).toHaveLength(2); expect(result.runningRaces[0].id).toBe('race-1'); expect(result.runningRaces[0].name).toBe('Running Race 1'); expect(result.runningRaces[0].date).toBe('2024-02-01T18:00:00Z'); expect(result.runningRaces[1].id).toBe('race-3'); expect(result.runningRaces[1].name).toBe('Running Race 2'); }); it('should handle no running races', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Past Race 1', date: '2024-01-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-2', name: 'Past Race 2', date: '2024-01-08T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.runningRaces).toEqual([]); }); it('should handle races with "Running" in different positions', () => { const races: RaceDTO[] = [ { id: 'race-1', name: 'Race Running', date: '2024-02-01T18:00:00Z', leagueName: 'Test League', }, { id: 'race-2', name: 'Running', date: '2024-02-08T18:00:00Z', leagueName: 'Test League', }, { id: 'race-3', name: 'Completed Race', date: '2024-02-15T18:00:00Z', leagueName: 'Test League', }, ]; const result = LeagueDetailViewDataBuilder.build({ league: mockLeague, owner: mockOwner, scoringConfig: mockScoringConfig, memberships: mockMemberships, races, sponsors: mockSponsors, }); expect(result.runningRaces).toHaveLength(2); expect(result.runningRaces[0].id).toBe('race-1'); expect(result.runningRaces[1].id).toBe('race-2'); }); }); });