import { describe, it, expect } from 'vitest'; import { SponsorDashboardViewModel } from './SponsorDashboardViewModel'; import { SponsorshipViewModel } from './SponsorshipViewModel'; import { ActivityItemViewModel } from './ActivityItemViewModel'; import { RenewalAlertViewModel } from './RenewalAlertViewModel'; function makeSponsorship(overrides: Partial = {}) { return { id: 's-1', type: 'leagues', entityId: 'league-1', entityName: 'Pro League', status: 'active', startDate: '2025-01-01', endDate: '2025-12-31', price: 5_000, impressions: 50_000, ...overrides, }; } describe('SponsorDashboardViewModel', () => { const baseDto = { sponsorId: 'sp-1', sponsorName: 'Acme Corp', metrics: { totalSpend: 10_000, totalImpressions: 100_000 }, sponsorships: { leagues: [makeSponsorship()], teams: [makeSponsorship({ id: 's-2', type: 'teams', price: 2_000, impressions: 20_000 })], drivers: [], races: [], platform: [], }, recentActivity: [ { id: 'a-1', type: 'impression', timestamp: '2025-01-01T00:00:00Z', metadata: {} }, ], upcomingRenewals: [ { id: 'r-1', sponsorshipId: 's-1', leagueName: 'Pro League', daysUntilRenewal: 10 }, ], } as any; it('maps nested DTOs into view models', () => { const vm = new SponsorDashboardViewModel(baseDto); expect(vm.sponsorId).toBe(baseDto.sponsorId); expect(vm.sponsorName).toBe(baseDto.sponsorName); expect(vm.metrics).toBe(baseDto.metrics); expect(vm.sponsorships.leagues[0]).toBeInstanceOf(SponsorshipViewModel); expect(vm.sponsorships.teams[0]).toBeInstanceOf(SponsorshipViewModel); expect(vm.recentActivity[0]).toBeInstanceOf(ActivityItemViewModel); expect(vm.upcomingRenewals[0]).toBeInstanceOf(RenewalAlertViewModel); }); it('computes total, active counts and investment from sponsorship buckets', () => { const vm = new SponsorDashboardViewModel(baseDto); const all = [ ...baseDto.sponsorships.leagues, ...baseDto.sponsorships.teams, ]; expect(vm.totalSponsorships).toBe(all.length); expect(vm.activeSponsorships).toBe(all.filter(s => s.status === 'active').length); const expectedInvestment = all .filter(s => s.status === 'active') .reduce((sum, s) => sum + s.price, 0); expect(vm.totalInvestment).toBe(expectedInvestment); }); it('aggregates total impressions across all sponsorships', () => { const vm = new SponsorDashboardViewModel(baseDto); const all = [ ...baseDto.sponsorships.leagues, ...baseDto.sponsorships.teams, ]; const expectedImpressions = all.reduce((sum, s) => sum + s.impressions, 0); expect(vm.totalImpressions).toBe(expectedImpressions); }); it('derives formatted investment, active percentage, status text and CPM', () => { const vm = new SponsorDashboardViewModel(baseDto); expect(vm.formattedTotalInvestment).toBe(`$${vm.totalInvestment.toLocaleString()}`); const expectedActivePercentage = Math.round((vm.activeSponsorships / vm.totalSponsorships) * 100); expect(vm.activePercentage).toBe(expectedActivePercentage); expect(vm.hasSponsorships).toBe(true); // statusText variants const noActive = new SponsorDashboardViewModel({ ...baseDto, sponsorships: { leagues: [makeSponsorship({ status: 'expired' })], teams: [], drivers: [], races: [], platform: [], }, } as any); expect(noActive.statusText).toBe('No active sponsorships'); const allActive = new SponsorDashboardViewModel({ ...baseDto, sponsorships: { leagues: [makeSponsorship()], teams: [], drivers: [], races: [], platform: [], }, } as any); expect(allActive.statusText).toBe('All sponsorships active'); // cost per thousand views expect(vm.costPerThousandViews).toBe(`$${(vm.totalInvestment / vm.totalImpressions * 1000).toFixed(2)}`); const zeroImpressions = new SponsorDashboardViewModel({ ...baseDto, sponsorships: { leagues: [makeSponsorship({ impressions: 0 })], teams: [], drivers: [], races: [], platform: [], }, } as any); expect(zeroImpressions.costPerThousandViews).toBe('$0.00'); }); it('exposes category data per sponsorship bucket', () => { const vm = new SponsorDashboardViewModel(baseDto); expect(vm.categoryData.leagues.count).toBe(baseDto.sponsorships.leagues.length); expect(vm.categoryData.leagues.impressions).toBe( baseDto.sponsorships.leagues.reduce((sum: number, s: any) => sum + s.impressions, 0), ); expect(vm.categoryData.teams.count).toBe(baseDto.sponsorships.teams.length); expect(vm.categoryData.teams.impressions).toBe( baseDto.sponsorships.teams.reduce((sum: number, s: any) => sum + s.impressions, 0), ); }); it('handles missing sponsorship buckets gracefully', () => { const vm = new SponsorDashboardViewModel({ ...baseDto, sponsorships: undefined, recentActivity: undefined, upcomingRenewals: undefined, } as any); expect(vm.totalSponsorships).toBe(0); expect(vm.activeSponsorships).toBe(0); expect(vm.totalInvestment).toBe(0); expect(vm.totalImpressions).toBe(0); expect(vm.activePercentage).toBe(0); expect(vm.hasSponsorships).toBe(false); expect(vm.costPerThousandViews).toBe('$0.00'); expect(vm.recentActivity).toEqual([]); expect(vm.upcomingRenewals).toEqual([]); }); });