166 lines
5.5 KiB
TypeScript
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([]);
|
|
});
|
|
});
|