import { describe, it, expect } from 'vitest'; import { LeagueDetailPageViewModel, type SponsorInfo } from './LeagueDetailPageViewModel'; import type { LeagueWithCapacityDTO } from '../types/generated/LeagueWithCapacityDTO'; import type { LeagueStatsDTO } from '../types/generated/LeagueStatsDTO'; import type { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO'; import type { LeagueMembershipsDTO } from '../types/generated/LeagueMembershipsDTO'; import type { LeagueScheduleDTO } from '../types/generated/LeagueScheduleDTO'; import { RaceViewModel } from './RaceViewModel'; describe('LeagueDetailPageViewModel', () => { const league: LeagueWithCapacityDTO = { id: 'league-1', name: 'Pro Series', // extra fields used by view-model but not in generated type yet description: 'Top tier competition', ownerId: 'owner-1', createdAt: '2025-01-01T00:00:00Z', settings: { maxDrivers: 40, }, socialLinks: { discordUrl: 'https://discord.gg/example', youtubeUrl: 'https://youtube.com/example', websiteUrl: 'https://example.com', }, } as any; const owner: GetDriverOutputDTO = { id: 'owner-1', iracingId: '100', name: 'Owner Driver', country: 'US', joinedAt: '2024-01-01T00:00:00Z', }; const drivers: GetDriverOutputDTO[] = [ owner, { id: 'driver-2', iracingId: '101', name: 'Second Driver', country: 'DE', joinedAt: '2024-02-01T00:00:00Z', }, ]; const memberships: LeagueMembershipsDTO = { members: [ { driverId: 'owner-1', role: 'owner', status: 'active', joinedAt: '2024-01-01T00:00:00Z', }, { driverId: 'driver-2', role: 'admin', status: 'active', joinedAt: '2024-02-01T00:00:00Z', }, { driverId: 'driver-3', role: 'steward', status: 'inactive', joinedAt: '2024-03-01T00:00:00Z', }, ], } as any; const schedule: LeagueScheduleDTO = { races: [ { id: 'race-1', name: 'Round 1', date: '2025-01-10T20:00:00Z' }, { id: 'race-2', name: 'Round 2', date: '2025-01-17T20:00:00Z' }, ], } as any; const allRaces: RaceViewModel[] = schedule.races.map(r => new RaceViewModel({ ...r, views: 12000, status: 'completed' })); const leagueStats: LeagueStatsDTO = { totalMembers: 2, totalRaces: 10, averageRating: 3_200, // view-model also expects averageSOF and completedRaces averageSOF: 3200, completedRaces: 4, } as any; const sponsors: SponsorInfo[] = [ { id: 's1', name: 'Main Sponsor', tier: 'main', logoUrl: '/logo-main.png', tagline: 'Primary partner' }, { id: 's2', name: 'Secondary Sponsor', tier: 'secondary', logoUrl: '/logo-sec.png' }, ]; it('maps core league, owner, drivers, memberships and races', () => { const vm = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, leagueStats, sponsors, ); expect(vm.id).toBe(league.id); expect(vm.name).toBe(league.name); expect(vm.description).toBe(league.description); expect(vm.ownerId).toBe(league.ownerId); expect(vm.settings.maxDrivers).toBe((league.settings as any).maxDrivers); expect(vm.socialLinks?.discordUrl).toBe((league.socialLinks as any).discordUrl); expect(vm.owner).toEqual(owner); expect(vm.scoringConfig).toBeNull(); expect(vm.drivers).toHaveLength(drivers.length); expect(vm.memberships).toHaveLength(memberships.members.length); expect(vm.allRaces).toHaveLength(allRaces.length); expect(vm.runningRaces.every(r => r.status === 'running')).toBe(true); }); it('computes sponsor insights from member count and stats', () => { const vm = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, leagueStats, sponsors, ); const memberCount = memberships.members.length; const mainSponsorTaken = sponsors.some(s => s.tier === 'main'); const secondaryTaken = sponsors.filter(s => s.tier === 'secondary').length; expect(vm.sponsorInsights.avgViewsPerRace).toBe(5400 + memberCount * 50); expect(vm.sponsorInsights.totalImpressions).toBe(45000 + memberCount * 500); expect(vm.sponsorInsights.engagementRate).toBe((3.5 + memberCount / 50).toFixed(1)); expect(vm.sponsorInsights.estimatedReach).toBe(memberCount * 150); expect(vm.sponsorInsights.mainSponsorAvailable).toBe(!mainSponsorTaken); expect(vm.sponsorInsights.secondarySlotsAvailable).toBe(Math.max(0, 2 - secondaryTaken)); expect(vm.sponsorInsights.mainSponsorPrice).toBe(800 + Math.floor(memberCount * 10)); expect(vm.sponsorInsights.secondaryPrice).toBe(250 + Math.floor(memberCount * 3)); expect(vm.sponsorInsights.discordMembers).toBe(memberCount * 3); expect(vm.sponsorInsights.monthlyActivity).toBe(Math.min(100, 40 + (leagueStats as any).completedRaces * 2)); }); it('derives sponsor tier and trustScore from averageSOF and completedRaces', () => { const highSofStats = { ...leagueStats, averageSOF: 3500 } as any; const vmHigh = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, highSofStats, sponsors, ); expect(vmHigh.sponsorInsights.tier).toBe('premium'); const midSofStats = { ...leagueStats, averageSOF: 2500 } as any; const vmMid = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, midSofStats, sponsors, ); expect(vmMid.sponsorInsights.tier).toBe('standard'); const lowSofStats = { ...leagueStats, averageSOF: 1500 } as any; const vmLow = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, lowSofStats, sponsors, ); expect(vmLow.sponsorInsights.tier).toBe('starter'); expect(vmHigh.sponsorInsights.trustScore).toBe( Math.min(100, 60 + memberships.members.length + (leagueStats as any).completedRaces), ); }); it('builds owner, admin and steward summaries from memberships', () => { const vm = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, leagueStats, sponsors, ); expect(vm.ownerSummary?.driver.id).toBe('owner-1'); expect(vm.ownerSummary?.rating).toBeNull(); expect(vm.ownerSummary?.rank).toBeNull(); expect(vm.adminSummaries.length).toBeGreaterThanOrEqual(1); expect(vm.adminSummaries[0].driver.id).toBe('driver-2'); expect(Array.isArray(vm.stewardSummaries)).toBe(true); }); it('returns falsey sponsor/membership helpers by default', () => { const vm = new LeagueDetailPageViewModel( league, owner, null, drivers, memberships, allRaces, leagueStats, sponsors, ); expect(vm.isSponsorMode).toBe(false); expect(vm.currentUserMembership).toBeNull(); expect(vm.canEndRaces).toBe(false); }); });