Files
gridpilot.gg/apps/website/lib/view-models/SponsorDashboardViewModel.test.ts
2025-12-20 00:31:31 +01:00

166 lines
5.5 KiB
TypeScript

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<any> = {}) {
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([]);
});
});