/** * View Data Layer Tests - Leagues Functionality * * This test file covers the view data layer for leagues 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: * - League list data transformation and sorting * - Individual league profile view models * - League roster data formatting and member management * - League schedule and standings view models * - League stewarding and protest handling data transformation * - League wallet and sponsorship data formatting * - League creation and migration data transformation * - Derived league fields (member counts, status, permissions, etc.) * - Default values and fallbacks for league views * - League-specific formatting (dates, points, positions, race formats, etc.) * - Data grouping and categorization for league components * - League search and filtering view models * - Real-time league data updates and state management */ import { LeaguesViewDataBuilder } from '@/lib/builders/view-data/LeaguesViewDataBuilder'; import { LeagueDetailViewDataBuilder } from '@/lib/builders/view-data/LeagueDetailViewDataBuilder'; import { LeagueRosterAdminViewDataBuilder } from '@/lib/builders/view-data/LeagueRosterAdminViewDataBuilder'; import { LeagueScheduleViewDataBuilder } from '@/lib/builders/view-data/LeagueScheduleViewDataBuilder'; import { LeagueStandingsViewDataBuilder } from '@/lib/builders/view-data/LeagueStandingsViewDataBuilder'; import type { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/generated/AllLeaguesWithCapacityAndScoringDTO'; import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO'; import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO'; import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO'; import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO'; import type { LeagueStandingDTO } from '@/lib/types/generated/LeagueStandingDTO'; import type { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO'; import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO'; import type { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO'; import type { RaceDTO } from '@/lib/types/generated/RaceDTO'; describe('LeaguesViewDataBuilder', () => { describe('happy paths', () => { it('should transform AllLeaguesWithCapacityAndScoringDTO to LeaguesViewData correctly', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Pro League', description: 'A competitive league for experienced drivers', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, qualifyingFormat: 'Solo • 32 max', }, usedSlots: 25, category: 'competitive', scoring: { gameId: 'game-1', gameName: 'iRacing', primaryChampionshipType: 'Single Championship', scoringPresetId: 'preset-1', scoringPresetName: 'Standard', dropPolicySummary: 'Drop 2 worst races', scoringPatternSummary: 'Points based on finish position', }, timingSummary: 'Weekly races on Sundays', logoUrl: 'https://example.com/logo.png', pendingJoinRequestsCount: 3, pendingProtestsCount: 1, walletBalance: 1000, }, { id: 'league-2', name: 'Rookie League', description: null, ownerId: 'owner-2', createdAt: '2024-02-01T00:00:00.000Z', settings: { maxDrivers: 16, qualifyingFormat: 'Solo • 16 max', }, usedSlots: 10, category: 'rookie', scoring: { gameId: 'game-1', gameName: 'iRacing', primaryChampionshipType: 'Single Championship', scoringPresetId: 'preset-2', scoringPresetName: 'Rookie', dropPolicySummary: 'No drops', scoringPatternSummary: 'Points based on finish position', }, timingSummary: 'Bi-weekly races', logoUrl: null, pendingJoinRequestsCount: 0, pendingProtestsCount: 0, walletBalance: 0, }, ], totalCount: 2, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues).toHaveLength(2); expect(result.leagues[0]).toEqual({ id: 'league-1', name: 'Pro League', description: 'A competitive league for experienced drivers', logoUrl: 'https://example.com/logo.png', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', maxDrivers: 32, usedDriverSlots: 25, activeDriversCount: undefined, nextRaceAt: undefined, maxTeams: undefined, usedTeamSlots: undefined, structureSummary: 'Solo • 32 max', timingSummary: 'Weekly races on Sundays', category: 'competitive', scoring: { gameId: 'game-1', gameName: 'iRacing', primaryChampionshipType: 'Single Championship', scoringPresetId: 'preset-1', scoringPresetName: 'Standard', dropPolicySummary: 'Drop 2 worst races', scoringPatternSummary: 'Points based on finish position', }, }); expect(result.leagues[1]).toEqual({ id: 'league-2', name: 'Rookie League', description: null, logoUrl: null, ownerId: 'owner-2', createdAt: '2024-02-01T00:00:00.000Z', maxDrivers: 16, usedDriverSlots: 10, activeDriversCount: undefined, nextRaceAt: undefined, maxTeams: undefined, usedTeamSlots: undefined, structureSummary: 'Solo • 16 max', timingSummary: 'Bi-weekly races', category: 'rookie', scoring: { gameId: 'game-1', gameName: 'iRacing', primaryChampionshipType: 'Single Championship', scoringPresetId: 'preset-2', scoringPresetName: 'Rookie', dropPolicySummary: 'No drops', scoringPatternSummary: 'Points based on finish position', }, }); }); it('should handle empty leagues list', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [], totalCount: 0, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues).toHaveLength(0); }); it('should handle leagues with missing optional fields', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Minimal League', description: '', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 20, }, usedSlots: 5, }, ], totalCount: 1, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues[0].description).toBe(null); expect(result.leagues[0].logoUrl).toBe(null); expect(result.leagues[0].category).toBe(null); expect(result.leagues[0].scoring).toBeUndefined(); expect(result.leagues[0].timingSummary).toBe(''); }); }); describe('data transformation', () => { it('should preserve all DTO fields in the output', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, qualifyingFormat: 'Solo • 32 max', }, usedSlots: 20, category: 'test', scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'Test Type', scoringPresetId: 'preset-1', scoringPresetName: 'Test Preset', dropPolicySummary: 'Test drop policy', scoringPatternSummary: 'Test pattern', }, timingSummary: 'Test timing', logoUrl: 'https://example.com/test.png', pendingJoinRequestsCount: 5, pendingProtestsCount: 2, walletBalance: 500, }, ], totalCount: 1, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues[0].id).toBe(leaguesDTO.leagues[0].id); expect(result.leagues[0].name).toBe(leaguesDTO.leagues[0].name); expect(result.leagues[0].description).toBe(leaguesDTO.leagues[0].description); expect(result.leagues[0].logoUrl).toBe(leaguesDTO.leagues[0].logoUrl); expect(result.leagues[0].ownerId).toBe(leaguesDTO.leagues[0].ownerId); expect(result.leagues[0].createdAt).toBe(leaguesDTO.leagues[0].createdAt); expect(result.leagues[0].maxDrivers).toBe(leaguesDTO.leagues[0].settings.maxDrivers); expect(result.leagues[0].usedDriverSlots).toBe(leaguesDTO.leagues[0].usedSlots); expect(result.leagues[0].structureSummary).toBe(leaguesDTO.leagues[0].settings.qualifyingFormat); expect(result.leagues[0].timingSummary).toBe(leaguesDTO.leagues[0].timingSummary); expect(result.leagues[0].category).toBe(leaguesDTO.leagues[0].category); expect(result.leagues[0].scoring).toEqual(leaguesDTO.leagues[0].scoring); }); it('should not modify the input DTO', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, qualifyingFormat: 'Solo • 32 max', }, usedSlots: 20, category: 'test', scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'Test Type', scoringPresetId: 'preset-1', scoringPresetName: 'Test Preset', dropPolicySummary: 'Test drop policy', scoringPatternSummary: 'Test pattern', }, timingSummary: 'Test timing', logoUrl: 'https://example.com/test.png', pendingJoinRequestsCount: 5, pendingProtestsCount: 2, walletBalance: 500, }, ], totalCount: 1, }; const originalDTO = JSON.parse(JSON.stringify(leaguesDTO)); LeaguesViewDataBuilder.build(leaguesDTO); expect(leaguesDTO).toEqual(originalDTO); }); }); describe('edge cases', () => { it('should handle leagues with very long descriptions', () => { const longDescription = 'A'.repeat(1000); const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Test League', description: longDescription, ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 20, }, ], totalCount: 1, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues[0].description).toBe(longDescription); }); it('should handle leagues with special characters in name', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'League & Co. (2024)', description: 'Test league', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 20, }, ], totalCount: 1, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues[0].name).toBe('League & Co. (2024)'); }); it('should handle leagues with zero used slots', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Empty League', description: 'No members yet', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 0, }, ], totalCount: 1, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues[0].usedDriverSlots).toBe(0); }); it('should handle leagues with maximum capacity', () => { const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = { leagues: [ { id: 'league-1', name: 'Full League', description: 'At maximum capacity', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 32, }, ], totalCount: 1, }; const result = LeaguesViewDataBuilder.build(leaguesDTO); expect(result.leagues[0].usedDriverSlots).toBe(32); expect(result.leagues[0].maxDrivers).toBe(32); }); }); }); describe('LeagueDetailViewDataBuilder', () => { describe('happy paths', () => { it('should transform league DTOs to LeagueDetailViewData correctly', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Pro League', description: 'A competitive league for experienced drivers', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, qualifyingFormat: 'Solo • 32 max', }, usedSlots: 25, category: 'competitive', scoring: { gameId: 'game-1', gameName: 'iRacing', primaryChampionshipType: 'Single Championship', scoringPresetId: 'preset-1', scoringPresetName: 'Standard', dropPolicySummary: 'Drop 2 worst races', scoringPatternSummary: 'Points based on finish position', }, timingSummary: 'Weekly races on Sundays', logoUrl: 'https://example.com/logo.png', pendingJoinRequestsCount: 3, pendingProtestsCount: 1, walletBalance: 1000, }; const owner: GetDriverOutputDTO = { id: 'owner-1', name: 'John Doe', iracingId: '12345', country: 'USA', bio: 'Experienced driver', joinedAt: '2023-01-01T00:00:00.000Z', avatarUrl: 'https://example.com/avatar.jpg', }; const scoringConfig: LeagueScoringConfigDTO = { id: 'config-1', leagueId: 'league-1', gameId: 'game-1', gameName: 'iRacing', primaryChampionshipType: 'Single Championship', scoringPresetId: 'preset-1', scoringPresetName: 'Standard', dropPolicySummary: 'Drop 2 worst races', scoringPatternSummary: 'Points based on finish position', dropRaces: 2, pointsPerRace: 100, pointsForWin: 25, pointsForPodium: [20, 15, 10], }; const memberships: LeagueMembershipsDTO = { members: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'admin', joinedAt: '2023-06-01T00:00:00.000Z', }, { driverId: 'driver-2', driver: { id: 'driver-2', name: 'Bob', iracingId: '22222', country: 'Germany', joinedAt: '2023-07-01T00:00:00.000Z', }, role: 'steward', joinedAt: '2023-07-01T00:00:00.000Z', }, { driverId: 'driver-3', driver: { id: 'driver-3', name: 'Charlie', iracingId: '33333', country: 'France', joinedAt: '2023-08-01T00:00:00.000Z', }, role: 'member', joinedAt: '2023-08-01T00:00:00.000Z', }, ], }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-01-15T14:00:00.000Z', track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', strengthOfField: 1500, }, { id: 'race-2', name: 'Race 2', date: '2024-01-22T14:00:00.000Z', track: 'Monza', car: 'Ferrari 488 GT3', sessionType: 'race', strengthOfField: 1600, }, ]; const sponsors: any[] = [ { id: 'sponsor-1', name: 'Sponsor A', tier: 'main', logoUrl: 'https://example.com/sponsor-a.png', websiteUrl: 'https://sponsor-a.com', tagline: 'Premium racing gear', }, ]; const result = LeagueDetailViewDataBuilder.build({ league, owner, scoringConfig, memberships, races, sponsors, }); expect(result.leagueId).toBe('league-1'); expect(result.name).toBe('Pro League'); expect(result.description).toBe('A competitive league for experienced drivers'); expect(result.logoUrl).toBe('https://example.com/logo.png'); expect(result.info.name).toBe('Pro League'); expect(result.info.description).toBe('A competitive league for experienced drivers'); expect(result.info.membersCount).toBe(3); expect(result.info.racesCount).toBe(2); expect(result.info.avgSOF).toBe(1550); expect(result.info.structure).toBe('Solo • 32 max'); expect(result.info.scoring).toBe('preset-1'); expect(result.info.createdAt).toBe('2024-01-01T00:00:00.000Z'); expect(result.info.discordUrl).toBeUndefined(); expect(result.info.youtubeUrl).toBeUndefined(); expect(result.info.websiteUrl).toBeUndefined(); expect(result.ownerSummary).not.toBeNull(); expect(result.ownerSummary?.driverId).toBe('owner-1'); expect(result.ownerSummary?.driverName).toBe('John Doe'); expect(result.ownerSummary?.avatarUrl).toBe('https://example.com/avatar.jpg'); expect(result.ownerSummary?.roleBadgeText).toBe('Owner'); expect(result.adminSummaries).toHaveLength(1); expect(result.adminSummaries[0].driverId).toBe('driver-1'); expect(result.adminSummaries[0].driverName).toBe('Alice'); expect(result.adminSummaries[0].roleBadgeText).toBe('Admin'); expect(result.stewardSummaries).toHaveLength(1); expect(result.stewardSummaries[0].driverId).toBe('driver-2'); expect(result.stewardSummaries[0].driverName).toBe('Bob'); expect(result.stewardSummaries[0].roleBadgeText).toBe('Steward'); expect(result.memberSummaries).toHaveLength(1); expect(result.memberSummaries[0].driverId).toBe('driver-3'); expect(result.memberSummaries[0].driverName).toBe('Charlie'); expect(result.memberSummaries[0].roleBadgeText).toBe('Member'); expect(result.sponsors).toHaveLength(1); expect(result.sponsors[0].id).toBe('sponsor-1'); expect(result.sponsors[0].name).toBe('Sponsor A'); expect(result.sponsors[0].tier).toBe('main'); expect(result.walletBalance).toBe(1000); expect(result.pendingProtestsCount).toBe(1); expect(result.pendingJoinRequestsCount).toBe(3); }); it('should handle league with no owner', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races: [], sponsors: [], }); expect(result.ownerSummary).toBeNull(); }); it('should handle league with no scoring config', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races: [], sponsors: [], }); expect(result.info.scoring).toBe('Standard'); }); it('should handle league with no races', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races: [], sponsors: [], }); expect(result.info.racesCount).toBe(0); expect(result.info.avgSOF).toBeNull(); expect(result.runningRaces).toEqual([]); expect(result.nextRace).toBeUndefined(); expect(result.seasonProgress).toEqual({ completedRaces: 0, totalRaces: 0, percentage: 0, }); expect(result.recentResults).toEqual([]); }); }); describe('data transformation', () => { it('should preserve all DTO fields in the output', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, qualifyingFormat: 'Solo • 32 max', }, usedSlots: 20, category: 'test', scoring: { gameId: 'game-1', gameName: 'Test Game', primaryChampionshipType: 'Test Type', scoringPresetId: 'preset-1', scoringPresetName: 'Test Preset', dropPolicySummary: 'Test drop policy', scoringPatternSummary: 'Test pattern', }, timingSummary: 'Test timing', logoUrl: 'https://example.com/test.png', pendingJoinRequestsCount: 5, pendingProtestsCount: 2, walletBalance: 500, }; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races: [], sponsors: [], }); expect(result.leagueId).toBe(league.id); expect(result.name).toBe(league.name); expect(result.description).toBe(league.description); expect(result.logoUrl).toBe(league.logoUrl); expect(result.walletBalance).toBe(league.walletBalance); expect(result.pendingProtestsCount).toBe(league.pendingProtestsCount); expect(result.pendingJoinRequestsCount).toBe(league.pendingJoinRequestsCount); }); it('should not modify the input DTOs', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 20, }; const originalLeague = JSON.parse(JSON.stringify(league)); LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races: [], sponsors: [], }); expect(league).toEqual(originalLeague); }); }); describe('edge cases', () => { it('should handle league with missing optional fields', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Minimal League', description: '', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races: [], sponsors: [], }); expect(result.description).toBe(''); expect(result.logoUrl).toBeUndefined(); expect(result.info.description).toBe(''); expect(result.info.discordUrl).toBeUndefined(); expect(result.info.youtubeUrl).toBeUndefined(); expect(result.info.websiteUrl).toBeUndefined(); }); it('should handle races with missing strengthOfField', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-01-15T14:00:00.000Z', track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, ]; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races, sponsors: [], }); expect(result.info.avgSOF).toBeNull(); }); it('should handle races with zero strengthOfField', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Race 1', date: '2024-01-15T14:00:00.000Z', track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', strengthOfField: 0, }, ]; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races, sponsors: [], }); expect(result.info.avgSOF).toBeNull(); }); it('should handle races with different dates for next race calculation', () => { const now = new Date(); const pastDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 1 day from now const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const races: RaceDTO[] = [ { id: 'race-1', name: 'Past Race', date: pastDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, { id: 'race-2', name: 'Future Race', date: futureDate.toISOString(), track: 'Monza', car: 'Ferrari 488 GT3', sessionType: 'race', }, ]; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships: { members: [] }, races, sponsors: [], }); expect(result.nextRace).toBeDefined(); expect(result.nextRace?.id).toBe('race-2'); expect(result.nextRace?.name).toBe('Future Race'); expect(result.seasonProgress.completedRaces).toBe(1); expect(result.seasonProgress.totalRaces).toBe(2); expect(result.seasonProgress.percentage).toBe(50); expect(result.recentResults).toHaveLength(1); expect(result.recentResults[0].raceId).toBe('race-1'); }); it('should handle members with different roles', () => { const league: LeagueWithCapacityAndScoringDTO = { id: 'league-1', name: 'Test League', description: 'Test description', ownerId: 'owner-1', createdAt: '2024-01-01T00:00:00.000Z', settings: { maxDrivers: 32, }, usedSlots: 10, }; const memberships: LeagueMembershipsDTO = { members: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Admin', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'admin', joinedAt: '2023-06-01T00:00:00.000Z', }, { driverId: 'driver-2', driver: { id: 'driver-2', name: 'Steward', iracingId: '22222', country: 'Germany', joinedAt: '2023-07-01T00:00:00.000Z', }, role: 'steward', joinedAt: '2023-07-01T00:00:00.000Z', }, { driverId: 'driver-3', driver: { id: 'driver-3', name: 'Member', iracingId: '33333', country: 'France', joinedAt: '2023-08-01T00:00:00.000Z', }, role: 'member', joinedAt: '2023-08-01T00:00:00.000Z', }, ], }; const result = LeagueDetailViewDataBuilder.build({ league, owner: null, scoringConfig: null, memberships, races: [], sponsors: [], }); expect(result.adminSummaries).toHaveLength(1); expect(result.stewardSummaries).toHaveLength(1); expect(result.memberSummaries).toHaveLength(1); expect(result.info.membersCount).toBe(3); }); }); }); describe('LeagueRosterAdminViewDataBuilder', () => { describe('happy paths', () => { it('should transform roster DTOs to LeagueRosterAdminViewData correctly', () => { const members: LeagueRosterMemberDTO[] = [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'admin', joinedAt: '2023-06-01T00:00:00.000Z', }, { driverId: 'driver-2', driver: { id: 'driver-2', name: 'Bob', iracingId: '22222', country: 'Germany', joinedAt: '2023-07-01T00:00:00.000Z', }, role: 'member', joinedAt: '2023-07-01T00:00:00.000Z', }, ]; const joinRequests: LeagueRosterJoinRequestDTO[] = [ { id: 'request-1', leagueId: 'league-1', driverId: 'driver-3', requestedAt: '2024-01-15T10:00:00.000Z', message: 'I would like to join this league', driver: {}, }, ]; const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members, joinRequests, }); expect(result.leagueId).toBe('league-1'); expect(result.members).toHaveLength(2); expect(result.members[0].driverId).toBe('driver-1'); expect(result.members[0].driver.id).toBe('driver-1'); expect(result.members[0].driver.name).toBe('Alice'); expect(result.members[0].role).toBe('admin'); expect(result.members[0].joinedAt).toBe('2023-06-01T00:00:00.000Z'); expect(result.members[0].formattedJoinedAt).toBeDefined(); expect(result.members[1].driverId).toBe('driver-2'); expect(result.members[1].driver.id).toBe('driver-2'); expect(result.members[1].driver.name).toBe('Bob'); expect(result.members[1].role).toBe('member'); expect(result.members[1].joinedAt).toBe('2023-07-01T00:00:00.000Z'); expect(result.members[1].formattedJoinedAt).toBeDefined(); expect(result.joinRequests).toHaveLength(1); expect(result.joinRequests[0].id).toBe('request-1'); expect(result.joinRequests[0].driver.id).toBe('driver-3'); expect(result.joinRequests[0].driver.name).toBe('Unknown Driver'); expect(result.joinRequests[0].requestedAt).toBe('2024-01-15T10:00:00.000Z'); expect(result.joinRequests[0].formattedRequestedAt).toBeDefined(); expect(result.joinRequests[0].message).toBe('I would like to join this league'); }); it('should handle empty members and join requests', () => { const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members: [], joinRequests: [], }); expect(result.leagueId).toBe('league-1'); expect(result.members).toHaveLength(0); expect(result.joinRequests).toHaveLength(0); }); it('should handle members without driver details', () => { const members: LeagueRosterMemberDTO[] = [ { driverId: 'driver-1', driver: undefined as any, role: 'member', joinedAt: '2023-06-01T00:00:00.000Z', }, ]; const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members, joinRequests: [], }); expect(result.members[0].driver.name).toBe('Unknown Driver'); }); }); describe('data transformation', () => { it('should preserve all DTO fields in the output', () => { const members: LeagueRosterMemberDTO[] = [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'admin', joinedAt: '2023-06-01T00:00:00.000Z', }, ]; const joinRequests: LeagueRosterJoinRequestDTO[] = [ { id: 'request-1', leagueId: 'league-1', driverId: 'driver-3', requestedAt: '2024-01-15T10:00:00.000Z', message: 'I would like to join this league', driver: {}, }, ]; const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members, joinRequests, }); expect(result.leagueId).toBe('league-1'); expect(result.members[0].driverId).toBe(members[0].driverId); expect(result.members[0].driver.id).toBe(members[0].driver.id); expect(result.members[0].driver.name).toBe(members[0].driver.name); expect(result.members[0].role).toBe(members[0].role); expect(result.members[0].joinedAt).toBe(members[0].joinedAt); expect(result.joinRequests[0].id).toBe(joinRequests[0].id); expect(result.joinRequests[0].requestedAt).toBe(joinRequests[0].requestedAt); expect(result.joinRequests[0].message).toBe(joinRequests[0].message); }); it('should not modify the input DTOs', () => { const members: LeagueRosterMemberDTO[] = [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'admin', joinedAt: '2023-06-01T00:00:00.000Z', }, ]; const joinRequests: LeagueRosterJoinRequestDTO[] = [ { id: 'request-1', leagueId: 'league-1', driverId: 'driver-3', requestedAt: '2024-01-15T10:00:00.000Z', message: 'I would like to join this league', driver: {}, }, ]; const originalMembers = JSON.parse(JSON.stringify(members)); const originalRequests = JSON.parse(JSON.stringify(joinRequests)); LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members, joinRequests, }); expect(members).toEqual(originalMembers); expect(joinRequests).toEqual(originalRequests); }); }); describe('edge cases', () => { it('should handle members with missing driver field', () => { const members: LeagueRosterMemberDTO[] = [ { driverId: 'driver-1', driver: undefined as any, role: 'member', joinedAt: '2023-06-01T00:00:00.000Z', }, ]; const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members, joinRequests: [], }); expect(result.members[0].driver.name).toBe('Unknown Driver'); }); it('should handle join requests with missing driver field', () => { const joinRequests: LeagueRosterJoinRequestDTO[] = [ { id: 'request-1', leagueId: 'league-1', driverId: 'driver-3', requestedAt: '2024-01-15T10:00:00.000Z', message: 'I would like to join this league', driver: undefined, }, ]; const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members: [], joinRequests, }); expect(result.joinRequests[0].driver.name).toBe('Unknown Driver'); }); it('should handle join requests without message', () => { const joinRequests: LeagueRosterJoinRequestDTO[] = [ { id: 'request-1', leagueId: 'league-1', driverId: 'driver-3', requestedAt: '2024-01-15T10:00:00.000Z', driver: {}, }, ]; const result = LeagueRosterAdminViewDataBuilder.build({ leagueId: 'league-1', members: [], joinRequests, }); expect(result.joinRequests[0].message).toBeUndefined(); }); }); }); describe('LeagueScheduleViewDataBuilder', () => { describe('happy paths', () => { it('should transform schedule DTO to LeagueScheduleViewData correctly', () => { const now = new Date(); const pastDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 1 day from now const apiDto = { leagueId: 'league-1', races: [ { id: 'race-1', name: 'Past Race', date: pastDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, { id: 'race-2', name: 'Future Race', date: futureDate.toISOString(), track: 'Monza', car: 'Ferrari 488 GT3', sessionType: 'race', }, ], }; const result = LeagueScheduleViewDataBuilder.build(apiDto, 'driver-1', true); expect(result.leagueId).toBe('league-1'); expect(result.races).toHaveLength(2); expect(result.races[0].id).toBe('race-1'); expect(result.races[0].name).toBe('Past Race'); expect(result.races[0].scheduledAt).toBe(pastDate.toISOString()); expect(result.races[0].track).toBe('Spa'); expect(result.races[0].car).toBe('Porsche 911 GT3'); expect(result.races[0].sessionType).toBe('race'); expect(result.races[0].isPast).toBe(true); expect(result.races[0].isUpcoming).toBe(false); expect(result.races[0].status).toBe('completed'); expect(result.races[0].isUserRegistered).toBe(false); expect(result.races[0].canRegister).toBe(false); expect(result.races[0].canEdit).toBe(true); expect(result.races[0].canReschedule).toBe(true); expect(result.races[1].id).toBe('race-2'); expect(result.races[1].name).toBe('Future Race'); expect(result.races[1].scheduledAt).toBe(futureDate.toISOString()); expect(result.races[1].track).toBe('Monza'); expect(result.races[1].car).toBe('Ferrari 488 GT3'); expect(result.races[1].sessionType).toBe('race'); expect(result.races[1].isPast).toBe(false); expect(result.races[1].isUpcoming).toBe(true); expect(result.races[1].status).toBe('scheduled'); expect(result.races[1].isUserRegistered).toBe(false); expect(result.races[1].canRegister).toBe(true); expect(result.races[1].canEdit).toBe(true); expect(result.races[1].canReschedule).toBe(true); expect(result.currentDriverId).toBe('driver-1'); expect(result.isAdmin).toBe(true); }); it('should handle empty races list', () => { const apiDto = { leagueId: 'league-1', races: [], }; const result = LeagueScheduleViewDataBuilder.build(apiDto); expect(result.leagueId).toBe('league-1'); expect(result.races).toHaveLength(0); }); it('should handle non-admin user', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); const apiDto = { leagueId: 'league-1', races: [ { id: 'race-1', name: 'Future Race', date: futureDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, ], }; const result = LeagueScheduleViewDataBuilder.build(apiDto, 'driver-1', false); expect(result.races[0].canEdit).toBe(false); expect(result.races[0].canReschedule).toBe(false); }); }); describe('data transformation', () => { it('should preserve all DTO fields in the output', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); const apiDto = { leagueId: 'league-1', races: [ { id: 'race-1', name: 'Test Race', date: futureDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, ], }; const result = LeagueScheduleViewDataBuilder.build(apiDto); expect(result.leagueId).toBe(apiDto.leagueId); expect(result.races[0].id).toBe(apiDto.races[0].id); expect(result.races[0].name).toBe(apiDto.races[0].name); expect(result.races[0].scheduledAt).toBe(apiDto.races[0].date); expect(result.races[0].track).toBe(apiDto.races[0].track); expect(result.races[0].car).toBe(apiDto.races[0].car); expect(result.races[0].sessionType).toBe(apiDto.races[0].sessionType); }); it('should not modify the input DTO', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); const apiDto = { leagueId: 'league-1', races: [ { id: 'race-1', name: 'Test Race', date: futureDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, ], }; const originalDto = JSON.parse(JSON.stringify(apiDto)); LeagueScheduleViewDataBuilder.build(apiDto); expect(apiDto).toEqual(originalDto); }); }); describe('edge cases', () => { it('should handle races with missing optional fields', () => { const now = new Date(); const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); const apiDto = { leagueId: 'league-1', races: [ { id: 'race-1', name: 'Test Race', date: futureDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, ], }; const result = LeagueScheduleViewDataBuilder.build(apiDto); expect(result.races[0].track).toBe('Spa'); expect(result.races[0].car).toBe('Porsche 911 GT3'); expect(result.races[0].sessionType).toBe('race'); }); it('should handle races at exactly the current time', () => { const now = new Date(); const currentRaceDate = new Date(now.getTime()); const apiDto = { leagueId: 'league-1', races: [ { id: 'race-1', name: 'Current Race', date: currentRaceDate.toISOString(), track: 'Spa', car: 'Porsche 911 GT3', sessionType: 'race', }, ], }; const result = LeagueScheduleViewDataBuilder.build(apiDto); // Race at current time should be considered past expect(result.races[0].isPast).toBe(true); expect(result.races[0].isUpcoming).toBe(false); expect(result.races[0].status).toBe('completed'); }); }); }); describe('LeagueStandingsViewDataBuilder', () => { describe('happy paths', () => { it('should transform standings DTOs to LeagueStandingsViewData correctly', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: ['race-1', 'race-2'], }, { driverId: 'driver-2', driver: { id: 'driver-2', name: 'Bob', iracingId: '22222', country: 'Germany', }, points: 1100, position: 2, wins: 3, podiums: 8, races: 15, positionChange: -1, lastRacePoints: 15, droppedRaceIds: [], }, ], }; const membershipsDto = { members: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'member', joinedAt: '2023-06-01T00:00:00.000Z', }, { driverId: 'driver-2', driver: { id: 'driver-2', name: 'Bob', iracingId: '22222', country: 'Germany', joinedAt: '2023-07-01T00:00:00.000Z', }, role: 'member', joinedAt: '2023-07-01T00:00:00.000Z', }, ], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(result.leagueId).toBe('league-1'); expect(result.isTeamChampionship).toBe(false); expect(result.currentDriverId).toBeNull(); expect(result.isAdmin).toBe(false); expect(result.standings).toHaveLength(2); expect(result.standings[0].driverId).toBe('driver-1'); expect(result.standings[0].position).toBe(1); expect(result.standings[0].totalPoints).toBe(1250); expect(result.standings[0].racesFinished).toBe(15); expect(result.standings[0].racesStarted).toBe(15); expect(result.standings[0].avgFinish).toBeNull(); expect(result.standings[0].penaltyPoints).toBe(0); expect(result.standings[0].bonusPoints).toBe(0); expect(result.standings[0].positionChange).toBe(2); expect(result.standings[0].lastRacePoints).toBe(25); expect(result.standings[0].droppedRaceIds).toEqual(['race-1', 'race-2']); expect(result.standings[0].wins).toBe(5); expect(result.standings[0].podiums).toBe(10); expect(result.standings[1].driverId).toBe('driver-2'); expect(result.standings[1].position).toBe(2); expect(result.standings[1].totalPoints).toBe(1100); expect(result.standings[1].racesFinished).toBe(15); expect(result.standings[1].racesStarted).toBe(15); expect(result.standings[1].avgFinish).toBeNull(); expect(result.standings[1].penaltyPoints).toBe(0); expect(result.standings[1].bonusPoints).toBe(0); expect(result.standings[1].positionChange).toBe(-1); expect(result.standings[1].lastRacePoints).toBe(15); expect(result.standings[1].droppedRaceIds).toEqual([]); expect(result.standings[1].wins).toBe(3); expect(result.standings[1].podiums).toBe(8); expect(result.drivers).toHaveLength(2); expect(result.drivers[0].id).toBe('driver-1'); expect(result.drivers[0].name).toBe('Alice'); expect(result.drivers[0].iracingId).toBe('11111'); expect(result.drivers[0].country).toBe('UK'); expect(result.drivers[0].avatarUrl).toBeNull(); expect(result.drivers[1].id).toBe('driver-2'); expect(result.drivers[1].name).toBe('Bob'); expect(result.drivers[1].iracingId).toBe('22222'); expect(result.drivers[1].country).toBe('Germany'); expect(result.drivers[1].avatarUrl).toBeNull(); expect(result.memberships).toHaveLength(2); expect(result.memberships[0].driverId).toBe('driver-1'); expect(result.memberships[0].leagueId).toBe('league-1'); expect(result.memberships[0].role).toBe('member'); expect(result.memberships[0].joinedAt).toBe('2023-06-01T00:00:00.000Z'); expect(result.memberships[0].status).toBe('active'); expect(result.memberships[1].driverId).toBe('driver-2'); expect(result.memberships[1].leagueId).toBe('league-1'); expect(result.memberships[1].role).toBe('member'); expect(result.memberships[1].joinedAt).toBe('2023-07-01T00:00:00.000Z'); expect(result.memberships[1].status).toBe('active'); }); it('should handle empty standings and memberships', () => { const standingsDto = { standings: [], }; const membershipsDto = { members: [], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(result.standings).toHaveLength(0); expect(result.drivers).toHaveLength(0); expect(result.memberships).toHaveLength(0); }); it('should handle team championship mode', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: [], }, ], }; const membershipsDto = { members: [], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', true ); expect(result.isTeamChampionship).toBe(true); }); }); describe('data transformation', () => { it('should preserve all DTO fields in the output', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: ['race-1'], }, ], }; const membershipsDto = { members: [], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(result.standings[0].driverId).toBe(standingsDto.standings[0].driverId); expect(result.standings[0].position).toBe(standingsDto.standings[0].position); expect(result.standings[0].totalPoints).toBe(standingsDto.standings[0].points); expect(result.standings[0].racesFinished).toBe(standingsDto.standings[0].races); expect(result.standings[0].racesStarted).toBe(standingsDto.standings[0].races); expect(result.standings[0].positionChange).toBe(standingsDto.standings[0].positionChange); expect(result.standings[0].lastRacePoints).toBe(standingsDto.standings[0].lastRacePoints); expect(result.standings[0].droppedRaceIds).toEqual(standingsDto.standings[0].droppedRaceIds); expect(result.standings[0].wins).toBe(standingsDto.standings[0].wins); expect(result.standings[0].podiums).toBe(standingsDto.standings[0].podiums); expect(result.drivers[0].id).toBe(standingsDto.standings[0].driver.id); expect(result.drivers[0].name).toBe(standingsDto.standings[0].driver.name); expect(result.drivers[0].iracingId).toBe(standingsDto.standings[0].driver.iracingId); expect(result.drivers[0].country).toBe(standingsDto.standings[0].driver.country); }); it('should not modify the input DTOs', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: ['race-1'], }, ], }; const membershipsDto = { members: [], }; const originalStandings = JSON.parse(JSON.stringify(standingsDto)); const originalMemberships = JSON.parse(JSON.stringify(membershipsDto)); LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(standingsDto).toEqual(originalStandings); expect(membershipsDto).toEqual(originalMemberships); }); }); describe('edge cases', () => { it('should handle standings with missing optional fields', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, }, ], }; const membershipsDto = { members: [], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(result.standings[0].positionChange).toBe(0); expect(result.standings[0].lastRacePoints).toBe(0); expect(result.standings[0].droppedRaceIds).toEqual([]); }); it('should handle standings with missing driver field', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: undefined as any, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: [], }, ], }; const membershipsDto = { members: [], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(result.drivers).toHaveLength(0); }); it('should handle duplicate drivers in standings', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: [], }, { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1100, position: 2, wins: 3, podiums: 8, races: 15, positionChange: -1, lastRacePoints: 15, droppedRaceIds: [], }, ], }; const membershipsDto = { members: [], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); // Should only have one driver entry expect(result.drivers).toHaveLength(1); expect(result.drivers[0].id).toBe('driver-1'); }); it('should handle members with different roles', () => { const standingsDto = { standings: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', }, points: 1250, position: 1, wins: 5, podiums: 10, races: 15, positionChange: 2, lastRacePoints: 25, droppedRaceIds: [], }, ], }; const membershipsDto = { members: [ { driverId: 'driver-1', driver: { id: 'driver-1', name: 'Alice', iracingId: '11111', country: 'UK', joinedAt: '2023-06-01T00:00:00.000Z', }, role: 'admin', joinedAt: '2023-06-01T00:00:00.000Z', }, ], }; const result = LeagueStandingsViewDataBuilder.build( standingsDto, membershipsDto, 'league-1', false ); expect(result.memberships[0].role).toBe('admin'); }); }); });