view models
This commit is contained in:
165
apps/website/lib/view-models/SponsorDashboardViewModel.test.ts
Normal file
165
apps/website/lib/view-models/SponsorDashboardViewModel.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user