This commit is contained in:
2026-01-08 17:36:08 +01:00
parent 66ec6fe727
commit 05cf3bafd2
23 changed files with 3004 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
import { DashboardStatsPresenter } from './DashboardStatsPresenter';
describe('DashboardStatsPresenter', () => {
let presenter: DashboardStatsPresenter;
beforeEach(() => {
presenter = new DashboardStatsPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result = {
totalUsers: 100,
activeUsers: 80,
suspendedUsers: 10,
deletedUsers: 10,
systemAdmins: 5,
recentLogins: 20,
newUsersToday: 5,
userGrowth: [
{ label: 'Day 1', value: 10, color: '#000' },
{ label: 'Day 2', value: 15, color: '#000' },
],
roleDistribution: [
{ label: 'Admin', value: 5, color: '#000' },
{ label: 'User', value: 95, color: '#000' },
],
statusDistribution: {
active: 80,
suspended: 10,
deleted: 10,
},
activityTimeline: [
{ date: '2024-01-01', newUsers: 5, logins: 20 },
],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
totalUsers: 100,
activeUsers: 80,
suspendedUsers: 10,
deletedUsers: 10,
systemAdmins: 5,
recentLogins: 20,
newUsersToday: 5,
userGrowth: [
{ label: 'Day 1', value: 10, color: '#000' },
{ label: 'Day 2', value: 15, color: '#000' },
],
roleDistribution: [
{ label: 'Admin', value: 5, color: '#000' },
{ label: 'User', value: 95, color: '#000' },
],
statusDistribution: {
active: 80,
suspended: 10,
deleted: 10,
},
activityTimeline: [
{ date: '2024-01-01', newUsers: 5, logins: 20 },
],
});
});
it('should handle empty arrays', () => {
const result = {
totalUsers: 0,
activeUsers: 0,
suspendedUsers: 0,
deletedUsers: 0,
systemAdmins: 0,
recentLogins: 0,
newUsersToday: 0,
userGrowth: [],
roleDistribution: [],
statusDistribution: {
active: 0,
suspended: 0,
deleted: 0,
},
activityTimeline: [],
};
presenter.present(result);
expect(presenter.responseModel.userGrowth).toEqual([]);
expect(presenter.responseModel.roleDistribution).toEqual([]);
expect(presenter.responseModel.activityTimeline).toEqual([]);
});
});
describe('reset', () => {
it('should clear the response model', () => {
const result = {
totalUsers: 100,
activeUsers: 80,
suspendedUsers: 10,
deletedUsers: 10,
systemAdmins: 5,
recentLogins: 20,
newUsersToday: 5,
userGrowth: [],
roleDistribution: [],
statusDistribution: {
active: 80,
suspended: 10,
deleted: 10,
},
activityTimeline: [],
};
presenter.present(result);
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('No response model available. Call present() first.');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('No response model available. Call present() first.');
});
});
});

View File

@@ -0,0 +1,43 @@
import { ForgotPasswordPresenter } from './ForgotPasswordPresenter';
describe('ForgotPasswordPresenter', () => {
let presenter: ForgotPasswordPresenter;
beforeEach(() => {
presenter = new ForgotPasswordPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result = { message: 'Password reset link generated successfully', magicLink: 'http://example.com/reset?token=abc123' };
presenter.present(result);
expect(presenter.responseModel).toEqual({ message: 'Password reset link generated successfully', magicLink: 'http://example.com/reset?token=abc123' });
});
it('should handle result without magicLink', () => {
const result = { message: 'If an account exists with this email, a password reset link will be sent', magicLink: null };
presenter.present(result);
expect(presenter.responseModel).toEqual({ message: 'If an account exists with this email, a password reset link will be sent', magicLink: null });
});
});
describe('reset', () => {
it('should clear the response model', () => {
presenter.present({ message: 'Password reset link generated successfully', magicLink: 'http://example.com' });
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('ForgotPasswordPresenter: No response model available');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('ForgotPasswordPresenter: No response model available');
});
});
});

View File

@@ -0,0 +1,43 @@
import { ResetPasswordPresenter } from './ResetPasswordPresenter';
describe('ResetPasswordPresenter', () => {
let presenter: ResetPasswordPresenter;
beforeEach(() => {
presenter = new ResetPasswordPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result = { message: 'Password reset successfully. You can now log in with your new password.' };
presenter.present(result);
expect(presenter.responseModel).toEqual({ message: 'Password reset successfully. You can now log in with your new password.' });
});
it('should handle different message', () => {
const result = { message: 'Password updated' };
presenter.present(result);
expect(presenter.responseModel).toEqual({ message: 'Password updated' });
});
});
describe('reset', () => {
it('should clear the response model', () => {
presenter.present({ message: 'Password reset successfully' });
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('ResetPasswordPresenter: No response model available');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('ResetPasswordPresenter: No response model available');
});
});
});

View File

@@ -0,0 +1,55 @@
import { CompleteOnboardingPresenter } from './CompleteOnboardingPresenter';
describe('CompleteOnboardingPresenter', () => {
let presenter: CompleteOnboardingPresenter;
beforeEach(() => {
presenter = new CompleteOnboardingPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result = {
driver: {
id: 'driver-123',
} as any,
};
presenter.present(result);
expect(presenter.getResponseModel()).toEqual({
success: true,
driverId: 'driver-123',
});
});
it('should handle different driver IDs', () => {
const result = {
driver: {
id: 'driver-456',
} as any,
};
presenter.present(result);
expect(presenter.getResponseModel()).toEqual({
success: true,
driverId: 'driver-456',
});
});
});
describe('getResponseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.getResponseModel()).toThrow('Presenter not presented');
});
it('should return model after present()', () => {
presenter.present({ driver: { id: 'driver-123' } } as any);
expect(presenter.getResponseModel()).toEqual({
success: true,
driverId: 'driver-123',
});
});
});
});

View File

@@ -0,0 +1,487 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { AllLeaguesWithCapacityAndScoringPresenter } from './AllLeaguesWithCapacityAndScoringPresenter';
import { League } from '@core/racing/domain/entities/League';
import { Season } from '@core/racing/domain/entities/season/Season';
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
import { Game } from '@core/racing/domain/entities/Game';
import { MediaReference } from '@core/domain/media/MediaReference';
import type { LeagueScoringPreset } from '@core/racing/domain/types/LeagueScoringPreset';
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
describe('AllLeaguesWithCapacityAndScoringPresenter', () => {
let presenter: AllLeaguesWithCapacityAndScoringPresenter;
beforeEach(() => {
presenter = new AllLeaguesWithCapacityAndScoringPresenter();
});
describe('present', () => {
it('should map empty leagues array to DTO', async () => {
await presenter.present({ leagues: [] });
const vm = presenter.getViewModel();
expect(vm).toEqual({
leagues: [],
totalCount: 0,
});
});
it('should map leagues with basic information', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: {
pointsSystem: 'f1-2024',
maxDrivers: 20,
},
createdAt: new Date('2024-01-01T00:00:00Z'),
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 20,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues).toHaveLength(1);
expect(vm.leagues[0]!.id).toBe('league-1');
expect(vm.leagues[0]!.name).toBe('Test League');
expect(vm.leagues[0]!.description).toBe('A test league');
expect(vm.leagues[0]!.ownerId).toBe('owner-1');
expect(vm.leagues[0]!.createdAt).toBe('2024-01-01T00:00:00.000Z');
expect(vm.leagues[0]!.settings.maxDrivers).toBe(20);
expect(vm.leagues[0]!.usedSlots).toBe(5);
expect(vm.totalCount).toBe(1);
});
it('should map leagues with social links', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
socialLinks: {
discordUrl: 'https://discord.gg/test',
youtubeUrl: 'https://youtube.com/test',
websiteUrl: 'https://test.com',
},
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 3,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.socialLinks).toEqual({
discordUrl: 'https://discord.gg/test',
youtubeUrl: 'https://youtube.com/test',
websiteUrl: 'https://test.com',
});
});
it('should map leagues with category', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
category: 'Formula',
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 2,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.category).toBe('Formula');
});
it('should map leagues with session duration and qualifying format', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: {
pointsSystem: 'f1-2024',
sessionDuration: 90,
qualifyingFormat: 'single-lap',
},
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 4,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.settings.sessionDuration).toBe(90);
expect(vm.leagues[0]!.settings.qualifyingFormat).toBe('single-lap');
});
it('should map leagues with timing summary for hours', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
gameId: 'iracing',
name: 'Season 1',
status: 'active',
schedule: {
startDate: new Date('2024-01-01'),
timeOfDay: { hour: 20, minute: 0 },
} as any,
} as any);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
seasonId: 'season-1',
scoringPresetId: 'preset-1',
championships: [
{
id: 'champ-1',
name: 'Drivers Championship',
type: 'driver',
sessionTypes: ['main'],
pointsTableBySessionType: {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1 }),
timeTrial: new PointsTable({}),
},
dropScorePolicy: { strategy: 'bestNResults', count: 3 },
},
],
});
const game = Game.create({
id: 'iracing',
name: 'iRacing',
});
const preset: LeagueScoringPreset = {
id: 'preset-1',
name: 'Test Preset',
description: 'Test preset description',
primaryChampionshipType: 'driver',
dropPolicySummary: 'Best 8 of 12',
sessionSummary: 'Qualifying + Race',
bonusSummary: 'Bonus points for top 3',
defaultTimings: {
mainRaceMinutes: 120,
practiceMinutes: 30,
qualifyingMinutes: 20,
sprintRaceMinutes: 0,
sessionCount: 1
},
};
await presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 32,
season,
scoringConfig,
game,
preset,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.timingSummary).toBe('2h Race');
expect(vm.leagues[0]!.scoring).toEqual({
gameId: 'iracing',
gameName: 'iRacing',
primaryChampionshipType: 'driver',
scoringPresetId: 'preset-1',
scoringPresetName: 'Test Preset',
dropPolicySummary: 'Best 8 of 12',
scoringPatternSummary: 'Qualifying + Race',
});
});
it('should map leagues with timing summary for minutes', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
gameId: 'iracing',
name: 'Season 1',
status: 'active',
schedule: {
startDate: new Date('2024-01-01'),
timeOfDay: { hour: 20, minute: 0 },
} as any,
} as any);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
seasonId: 'season-1',
scoringPresetId: 'preset-1',
championships: [
{
id: 'champ-1',
name: 'Drivers Championship',
type: 'driver',
sessionTypes: ['main'],
pointsTableBySessionType: {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1 }),
timeTrial: new PointsTable({}),
},
dropScorePolicy: { strategy: 'bestNResults', count: 3 },
},
],
});
const game = Game.create({
id: 'iracing',
name: 'iRacing',
});
const preset: LeagueScoringPreset = {
id: 'preset-1',
name: 'Test Preset',
description: 'Test preset description',
primaryChampionshipType: 'driver',
dropPolicySummary: 'Best 8 of 12',
sessionSummary: 'Qualifying + Race',
bonusSummary: 'Bonus points for top 3',
defaultTimings: {
mainRaceMinutes: 45,
practiceMinutes: 15,
qualifyingMinutes: 10,
sprintRaceMinutes: 0,
sessionCount: 1
},
};
await presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 32,
season,
scoringConfig,
game,
preset,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.timingSummary).toBe('45m Race');
});
it('should handle leagues without scoring information', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.scoring).toBeUndefined();
expect(vm.leagues[0]!.timingSummary).toBeUndefined();
});
it('should handle leagues with system default logoRef', async () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
// Should be null when it's system default (as per presenter logic)
expect(vm.leagues[0]!.logoUrl).toBeNull();
});
it('should handle multiple leagues with different configurations', async () => {
const league1 = League.create({
id: 'league-1',
name: 'League One',
description: 'First league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024', maxDrivers: 20 },
category: 'Formula',
});
const league2 = League.create({
id: 'league-2',
name: 'League Two',
description: 'Second league',
ownerId: 'owner-2',
settings: { pointsSystem: 'indycar', maxDrivers: 40 },
socialLinks: { discordUrl: 'https://discord.gg/league2' },
});
await presenter.present({
leagues: [
{
league: league1,
currentDrivers: 10,
maxDrivers: 20,
},
{
league: league2,
currentDrivers: 25,
maxDrivers: 40,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues).toHaveLength(2);
expect(vm.totalCount).toBe(2);
expect(vm.leagues[0]!.id).toBe('league-1');
expect(vm.leagues[0]!.category).toBe('Formula');
expect(vm.leagues[0]!.settings.maxDrivers).toBe(20);
expect(vm.leagues[1]!.id).toBe('league-2');
expect(vm.leagues[1]!.socialLinks?.discordUrl).toBe('https://discord.gg/league2');
expect(vm.leagues[1]!.settings.maxDrivers).toBe(40);
});
});
describe('getViewModel', () => {
it('should throw error when not presented', () => {
expect(() => presenter.getViewModel()).toThrow('Presenter not presented');
});
it('should return the model when presented', async () => {
await presenter.present({ leagues: [] });
const vm = presenter.getViewModel();
expect(vm).toEqual({
leagues: [],
totalCount: 0,
});
});
});
describe('setMediaResolver', () => {
it('should use media resolver to resolve logo URLs', async () => {
const mockResolver = {
resolve: async () => 'https://cdn.example.com/league-logo.png',
};
presenter.setMediaResolver(mockResolver);
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
// Override logoRef to uploaded type
(league as any).logoRef = MediaReference.fromJSON({
type: 'uploaded',
mediaId: 'media-123',
});
await presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.logoUrl).toBe('https://cdn.example.com/league-logo.png');
});
});
});

View File

@@ -0,0 +1,228 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { AllLeaguesWithCapacityPresenter } from './AllLeaguesWithCapacityPresenter';
import { League } from '@core/racing/domain/entities/League';
describe('AllLeaguesWithCapacityPresenter', () => {
let presenter: AllLeaguesWithCapacityPresenter;
beforeEach(() => {
presenter = new AllLeaguesWithCapacityPresenter();
});
describe('present', () => {
it('should map empty leagues array to DTO', () => {
presenter.present({ leagues: [] });
const vm = presenter.getViewModel();
expect(vm).toEqual({
leagues: [],
totalCount: 0,
});
});
it('should map leagues with basic information', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: {
pointsSystem: 'f1-2024',
maxDrivers: 20,
},
createdAt: new Date('2024-01-01T00:00:00Z'),
});
presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 20,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues).toHaveLength(1);
expect(vm.leagues[0]!.id).toBe('league-1');
expect(vm.leagues[0]!.name).toBe('Test League');
expect(vm.leagues[0]!.description).toBe('A test league');
expect(vm.leagues[0]!.ownerId).toBe('owner-1');
expect(vm.leagues[0]!.createdAt).toBe('2024-01-01T00:00:00.000Z');
expect(vm.leagues[0]!.settings.maxDrivers).toBe(20);
expect(vm.leagues[0]!.usedSlots).toBe(5);
expect(vm.totalCount).toBe(1);
});
it('should map leagues with social links', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
socialLinks: {
discordUrl: 'https://discord.gg/test',
youtubeUrl: 'https://youtube.com/test',
websiteUrl: 'https://test.com',
},
});
presenter.present({
leagues: [
{
league,
currentDrivers: 3,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.socialLinks).toEqual({
discordUrl: 'https://discord.gg/test',
youtubeUrl: 'https://youtube.com/test',
websiteUrl: 'https://test.com',
});
});
it('should handle leagues without social links', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
presenter.present({
leagues: [
{
league,
currentDrivers: 2,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.socialLinks).toEqual({});
});
it('should handle multiple leagues with different configurations', () => {
const league1 = League.create({
id: 'league-1',
name: 'League One',
description: 'First league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024', maxDrivers: 20 },
});
const league2 = League.create({
id: 'league-2',
name: 'League Two',
description: 'Second league',
ownerId: 'owner-2',
settings: { pointsSystem: 'indycar', maxDrivers: 40 },
socialLinks: { discordUrl: 'https://discord.gg/league2' },
});
presenter.present({
leagues: [
{
league: league1,
currentDrivers: 10,
maxDrivers: 20,
},
{
league: league2,
currentDrivers: 25,
maxDrivers: 40,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues).toHaveLength(2);
expect(vm.totalCount).toBe(2);
expect(vm.leagues[0]!.id).toBe('league-1');
expect(vm.leagues[0]!.settings.maxDrivers).toBe(20);
expect(vm.leagues[0]!.usedSlots).toBe(10);
expect(vm.leagues[1]!.id).toBe('league-2');
expect(vm.leagues[1]!.socialLinks?.discordUrl).toBe('https://discord.gg/league2');
expect(vm.leagues[1]!.settings.maxDrivers).toBe(40);
expect(vm.leagues[1]!.usedSlots).toBe(25);
});
it('should handle leagues with zero drivers', () => {
const league = League.create({
id: 'league-1',
name: 'Empty League',
description: 'No drivers yet',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024', maxDrivers: 32 },
});
presenter.present({
leagues: [
{
league,
currentDrivers: 0,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.usedSlots).toBe(0);
});
it('should handle leagues with description containing special characters', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league with "quotes" and <tags> & symbols!',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
presenter.present({
leagues: [
{
league,
currentDrivers: 5,
maxDrivers: 32,
},
],
});
const vm = presenter.getViewModel();
expect(vm.leagues[0]!.description).toBe('A test league with "quotes" and <tags> & symbols!');
});
});
describe('getViewModel', () => {
it('should throw error when not presented', () => {
expect(() => presenter.getViewModel()).toThrow('Presenter not presented');
});
it('should return the model when presented', () => {
presenter.present({ leagues: [] });
const vm = presenter.getViewModel();
expect(vm).toEqual({
leagues: [],
totalCount: 0,
});
});
});
});

View File

@@ -0,0 +1,86 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { ApproveLeagueJoinRequestPresenter } from './ApproveLeagueJoinRequestPresenter';
describe('ApproveLeagueJoinRequestPresenter', () => {
let presenter: ApproveLeagueJoinRequestPresenter;
beforeEach(() => {
presenter = new ApproveLeagueJoinRequestPresenter();
});
describe('present', () => {
it('should present successful approval result', () => {
const result = {
success: true,
message: 'Join request approved successfully',
};
presenter.present(result);
const vm = presenter.getViewModel();
expect(vm).toEqual({
success: true,
message: 'Join request approved successfully',
});
});
it('should present failed approval result', () => {
const result = {
success: false,
message: 'Failed to approve join request',
};
presenter.present(result);
const vm = presenter.getViewModel();
expect(vm).toEqual({
success: false,
message: 'Failed to approve join request',
});
});
it('should handle empty message', () => {
const result = {
success: true,
message: '',
};
presenter.present(result);
const vm = presenter.getViewModel();
expect(vm).toEqual({
success: true,
message: '',
});
});
});
describe('getViewModel', () => {
it('should return null before presentation', () => {
expect(presenter.getViewModel()).toBeNull();
});
it('should return the model after presentation', () => {
presenter.present({ success: true, message: 'Test' });
expect(presenter.getViewModel()).toEqual({
success: true,
message: 'Test',
});
});
});
describe('reset', () => {
it('should reset the model to null', () => {
presenter.present({ success: true, message: 'Test' });
expect(presenter.getViewModel()).not.toBeNull();
presenter.reset();
expect(presenter.getViewModel()).toBeNull();
});
});
});

View File

@@ -0,0 +1,228 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { CreateLeaguePresenter } from './CreateLeaguePresenter';
import { League } from '@core/racing/domain/entities/League';
import { Season } from '@core/racing/domain/entities/season/Season';
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
describe('CreateLeaguePresenter', () => {
let presenter: CreateLeaguePresenter;
beforeEach(() => {
presenter = new CreateLeaguePresenter();
});
describe('present', () => {
it('should present successful league creation', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
gameId: 'iracing',
name: 'Test League Season 1',
status: 'active',
} as any);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
seasonId: 'season-1',
scoringPresetId: 'preset-1',
championships: [
{
id: 'champ-1',
name: 'Driver Championship',
type: 'driver',
sessionTypes: ['main'],
pointsTableBySessionType: {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18, 3: 15 }),
timeTrial: new PointsTable({}),
},
dropScorePolicy: { strategy: 'none' },
},
],
});
presenter.present({ league, season, scoringConfig });
const vm = presenter.getViewModel();
expect(vm).toEqual({
leagueId: 'league-1',
success: true,
});
});
it('should present league with different ID', () => {
const league = League.create({
id: 'league-abc-123',
name: 'Another League',
description: 'Another test league',
ownerId: 'owner-2',
settings: { pointsSystem: 'indycar' },
});
const season = Season.create({
id: 'season-2',
leagueId: 'league-abc-123',
gameId: 'iracing',
name: 'Another League Season 1',
status: 'active',
} as any);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-2',
seasonId: 'season-2',
scoringPresetId: 'preset-2',
championships: [
{
id: 'champ-2',
name: 'Driver Championship',
type: 'driver',
sessionTypes: ['main'],
pointsTableBySessionType: {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18, 3: 15 }),
timeTrial: new PointsTable({}),
},
dropScorePolicy: { strategy: 'none' },
},
],
});
presenter.present({ league, season, scoringConfig });
const vm = presenter.getViewModel();
expect(vm).toEqual({
leagueId: 'league-abc-123',
success: true,
});
});
});
describe('getViewModel', () => {
it('should throw error when not presented', () => {
expect(() => presenter.getViewModel()).toThrow('Presenter not presented');
});
it('should return the model when presented', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
gameId: 'iracing',
name: 'Test League Season 1',
status: 'active',
} as any);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
seasonId: 'season-1',
scoringPresetId: 'preset-1',
championships: [
{
id: 'champ-1',
name: 'Driver Championship',
type: 'driver',
sessionTypes: ['main'],
pointsTableBySessionType: {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18, 3: 15 }),
timeTrial: new PointsTable({}),
},
dropScorePolicy: { strategy: 'none' },
},
],
});
presenter.present({ league, season, scoringConfig });
expect(presenter.getViewModel()).toEqual({
leagueId: 'league-1',
success: true,
});
});
});
describe('reset', () => {
it('should reset the model and cause getViewModel to throw', () => {
const league = League.create({
id: 'league-1',
name: 'Test League',
description: 'A test league',
ownerId: 'owner-1',
settings: { pointsSystem: 'f1-2024' },
});
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
gameId: 'iracing',
name: 'Test League Season 1',
status: 'active',
} as any);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
seasonId: 'season-1',
scoringPresetId: 'preset-1',
championships: [
{
id: 'champ-1',
name: 'Driver Championship',
type: 'driver',
sessionTypes: ['main'],
pointsTableBySessionType: {
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
sprint: new PointsTable({}),
main: new PointsTable({ 1: 25, 2: 18, 3: 15 }),
timeTrial: new PointsTable({}),
},
dropScorePolicy: { strategy: 'none' },
},
],
});
presenter.present({ league, season, scoringConfig });
expect(presenter.getViewModel()).not.toBeNull();
presenter.reset();
expect(() => presenter.getViewModel()).toThrow('Presenter not presented');
});
});
});

View File

@@ -0,0 +1,70 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { RejectLeagueJoinRequestPresenter } from './RejectLeagueJoinRequestPresenter';
describe('RejectLeagueJoinRequestPresenter', () => {
let presenter: RejectLeagueJoinRequestPresenter;
beforeEach(() => {
presenter = new RejectLeagueJoinRequestPresenter();
});
describe('present', () => {
it('should present successful rejection result', () => {
const result = {
success: true,
message: 'Join request rejected successfully',
};
presenter.present(result);
const vm = presenter.getViewModel();
expect(vm).toEqual({
success: true,
message: 'Join request rejected successfully',
});
});
it('should present failed rejection result', () => {
const result = {
success: false,
message: 'Failed to reject join request',
};
presenter.present(result);
const vm = presenter.getViewModel();
expect(vm).toEqual({
success: false,
message: 'Failed to reject join request',
});
});
});
describe('getViewModel', () => {
it('should return null before presentation', () => {
expect(presenter.getViewModel()).toBeNull();
});
it('should return the model after presentation', () => {
presenter.present({ success: true, message: 'Test' });
expect(presenter.getViewModel()).toEqual({
success: true,
message: 'Test',
});
});
});
describe('reset', () => {
it('should reset the model to null', () => {
presenter.present({ success: true, message: 'Test' });
expect(presenter.getViewModel()).not.toBeNull();
presenter.reset();
expect(presenter.getViewModel()).toBeNull();
});
});
});

View File

@@ -0,0 +1,67 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TotalLeaguesPresenter } from './TotalLeaguesPresenter';
describe('TotalLeaguesPresenter', () => {
let presenter: TotalLeaguesPresenter;
beforeEach(() => {
presenter = new TotalLeaguesPresenter();
});
describe('present', () => {
it('should present total leagues count', () => {
presenter.present({ totalLeagues: 42 });
const vm = presenter.getResponseModel();
expect(vm).toEqual({
totalLeagues: 42,
});
});
it('should present zero total leagues', () => {
presenter.present({ totalLeagues: 0 });
const vm = presenter.getResponseModel();
expect(vm).toEqual({
totalLeagues: 0,
});
});
it('should present large total leagues count', () => {
presenter.present({ totalLeagues: 999999 });
const vm = presenter.getResponseModel();
expect(vm).toEqual({
totalLeagues: 999999,
});
});
});
describe('getResponseModel', () => {
it('should return null before presentation', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return the model after presentation', () => {
presenter.present({ totalLeagues: 10 });
expect(presenter.getResponseModel()).toEqual({
totalLeagues: 10,
});
});
});
describe('reset', () => {
it('should reset the model to null', () => {
presenter.present({ totalLeagues: 25 });
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
});

View File

@@ -0,0 +1,61 @@
import { DeleteMediaPresenter } from './DeleteMediaPresenter';
describe('DeleteMediaPresenter', () => {
let presenter: DeleteMediaPresenter;
beforeEach(() => {
presenter = new DeleteMediaPresenter();
});
describe('transform', () => {
it('should map successful result to response model', () => {
const result = { mediaId: 'media-123', deleted: true };
const response = presenter.transform(result);
expect(response).toEqual({ success: true });
expect(presenter.getResponseModel()).toEqual({ success: true });
});
it('should handle failed deletion', () => {
const result = { mediaId: 'media-123', deleted: false };
const response = presenter.transform(result);
expect(response).toEqual({ success: false });
expect(presenter.getResponseModel()).toEqual({ success: false });
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.transform({ mediaId: 'media-123', deleted: true });
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('responseModel', () => {
it('should throw error when accessed before transform()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
it('should return model after transform()', () => {
presenter.transform({ mediaId: 'media-123', deleted: true });
expect(presenter.responseModel).toEqual({ success: true });
});
});
describe('getResponseModel', () => {
it('should return null before transform()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({ mediaId: 'media-123', deleted: true });
expect(presenter.getResponseModel()).toEqual({ success: true });
});
});
});

View File

@@ -0,0 +1,95 @@
import { GetAvatarPresenter } from './GetAvatarPresenter';
describe('GetAvatarPresenter', () => {
let presenter: GetAvatarPresenter;
beforeEach(() => {
presenter = new GetAvatarPresenter();
});
describe('transform', () => {
it('should map result to response model', () => {
const result = {
avatar: {
id: 'avatar-123',
driverId: 'driver-456',
mediaUrl: 'https://example.com/avatar.png',
selectedAt: new Date('2024-01-01'),
},
};
const response = presenter.transform(result);
expect(response).toEqual({ avatarUrl: 'https://example.com/avatar.png' });
expect(presenter.getResponseModel()).toEqual({ avatarUrl: 'https://example.com/avatar.png' });
});
it('should handle different avatar URLs', () => {
const result = {
avatar: {
id: 'avatar-789',
driverId: 'driver-456',
mediaUrl: 'https://cdn.example.com/avatars/test.jpg',
selectedAt: new Date('2024-01-02'),
},
};
const response = presenter.transform(result);
expect(response).toEqual({ avatarUrl: 'https://cdn.example.com/avatars/test.jpg' });
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.transform({
avatar: {
id: 'avatar-123',
driverId: 'driver-456',
mediaUrl: 'https://example.com/avatar.png',
selectedAt: new Date('2024-01-01'),
},
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('responseModel', () => {
it('should return null when model is null', () => {
expect(presenter.responseModel).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({
avatar: {
id: 'avatar-123',
driverId: 'driver-456',
mediaUrl: 'https://example.com/avatar.png',
selectedAt: new Date('2024-01-01'),
},
});
expect(presenter.responseModel).toEqual({ avatarUrl: 'https://example.com/avatar.png' });
});
});
describe('getResponseModel', () => {
it('should return null before transform()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({
avatar: {
id: 'avatar-123',
driverId: 'driver-456',
mediaUrl: 'https://example.com/avatar.png',
selectedAt: new Date('2024-01-01'),
},
});
expect(presenter.getResponseModel()).toEqual({ avatarUrl: 'https://example.com/avatar.png' });
});
});
});

View File

@@ -0,0 +1,165 @@
import { GetMediaPresenter } from './GetMediaPresenter';
describe('GetMediaPresenter', () => {
let presenter: GetMediaPresenter;
beforeEach(() => {
presenter = new GetMediaPresenter();
});
describe('transform', () => {
it('should map result to response model', () => {
const result = {
media: {
id: 'media-123',
filename: 'avatar.png',
originalName: 'my-avatar.png',
mimeType: 'image/png',
size: 12345,
url: 'https://example.com/avatar.png',
type: 'image',
uploadedBy: 'user-456',
uploadedAt: new Date('2024-01-01'),
},
};
const response = presenter.transform(result);
expect(response).toEqual({
id: 'media-123',
url: 'https://example.com/avatar.png',
type: 'image',
uploadedAt: new Date('2024-01-01'),
size: 12345,
});
});
it('should handle metadata', () => {
const result = {
media: {
id: 'media-123',
filename: 'avatar.png',
originalName: 'my-avatar.png',
mimeType: 'image/png',
size: 12345,
url: 'https://example.com/avatar.png',
type: 'image',
uploadedBy: 'user-456',
uploadedAt: new Date('2024-01-01'),
metadata: { category: 'profile', width: 100, height: 100 },
},
};
const response = presenter.transform(result);
expect(response).toEqual({
id: 'media-123',
url: 'https://example.com/avatar.png',
type: 'image',
uploadedAt: new Date('2024-01-01'),
size: 12345,
category: 'profile',
});
});
it('should handle result without metadata', () => {
const result = {
media: {
id: 'media-123',
filename: 'avatar.png',
originalName: 'my-avatar.png',
mimeType: 'image/png',
size: 12345,
url: 'https://example.com/avatar.png',
type: 'image',
uploadedBy: 'user-456',
uploadedAt: new Date('2024-01-01'),
},
};
const response = presenter.transform(result);
expect(response?.category).toBeUndefined();
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.transform({
media: {
id: 'media-123',
filename: 'avatar.png',
originalName: 'my-avatar.png',
mimeType: 'image/png',
size: 12345,
url: 'https://example.com/avatar.png',
type: 'image',
uploadedBy: 'user-456',
uploadedAt: new Date('2024-01-01'),
},
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('responseModel', () => {
it('should return null when model is null', () => {
expect(presenter.responseModel).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({
media: {
id: 'media-123',
filename: 'avatar.png',
originalName: 'my-avatar.png',
mimeType: 'image/png',
size: 12345,
url: 'https://example.com/avatar.png',
type: 'image',
uploadedBy: 'user-456',
uploadedAt: new Date('2024-01-01'),
},
});
expect(presenter.responseModel).toEqual({
id: 'media-123',
url: 'https://example.com/avatar.png',
type: 'image',
uploadedAt: new Date('2024-01-01'),
size: 12345,
});
});
});
describe('getResponseModel', () => {
it('should return null before transform()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({
media: {
id: 'media-123',
filename: 'avatar.png',
originalName: 'my-avatar.png',
mimeType: 'image/png',
size: 12345,
url: 'https://example.com/avatar.png',
type: 'image',
uploadedBy: 'user-456',
uploadedAt: new Date('2024-01-01'),
},
});
expect(presenter.getResponseModel()).toEqual({
id: 'media-123',
url: 'https://example.com/avatar.png',
type: 'image',
uploadedAt: new Date('2024-01-01'),
size: 12345,
});
});
});
});

View File

@@ -0,0 +1,126 @@
import { RequestAvatarGenerationPresenter } from './RequestAvatarGenerationPresenter';
describe('RequestAvatarGenerationPresenter', () => {
let presenter: RequestAvatarGenerationPresenter;
beforeEach(() => {
presenter = new RequestAvatarGenerationPresenter();
});
describe('transform', () => {
it('should map completed result to response model', () => {
const result = {
requestId: 'req-123',
status: 'completed' as const,
avatarUrls: ['https://example.com/avatar1.png', 'https://example.com/avatar2.png'],
};
const response = presenter.transform(result);
expect(response).toEqual({
success: true,
requestId: 'req-123',
avatarUrls: ['https://example.com/avatar1.png', 'https://example.com/avatar2.png'],
});
});
it('should handle empty avatarUrls', () => {
const result = {
requestId: 'req-123',
status: 'completed' as const,
avatarUrls: [],
};
const response = presenter.transform(result);
expect(response).toEqual({
success: true,
requestId: 'req-123',
avatarUrls: [],
});
});
it('should handle undefined avatarUrls', () => {
const result = {
requestId: 'req-123',
status: 'completed' as const,
};
const response = presenter.transform(result);
expect(response).toEqual({
success: true,
requestId: 'req-123',
avatarUrls: [],
});
});
it('should handle non-completed status', () => {
const result = {
requestId: 'req-123',
status: 'generating' as const,
avatarUrls: [],
};
const response = presenter.transform(result);
expect(response).toEqual({
success: false,
requestId: 'req-123',
avatarUrls: [],
});
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.transform({
requestId: 'req-123',
status: 'completed' as const,
avatarUrls: ['https://example.com/avatar1.png'],
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('responseModel', () => {
it('should throw error when accessed before transform()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
it('should return model after transform()', () => {
presenter.transform({
requestId: 'req-123',
status: 'completed' as const,
avatarUrls: ['https://example.com/avatar1.png'],
});
expect(presenter.responseModel).toEqual({
success: true,
requestId: 'req-123',
avatarUrls: ['https://example.com/avatar1.png'],
});
});
});
describe('getResponseModel', () => {
it('should return null before transform()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({
requestId: 'req-123',
status: 'completed' as const,
avatarUrls: ['https://example.com/avatar1.png'],
});
expect(presenter.getResponseModel()).toEqual({
success: true,
requestId: 'req-123',
avatarUrls: ['https://example.com/avatar1.png'],
});
});
});
});

View File

@@ -0,0 +1,66 @@
import { UpdateAvatarPresenter } from './UpdateAvatarPresenter';
describe('UpdateAvatarPresenter', () => {
let presenter: UpdateAvatarPresenter;
beforeEach(() => {
presenter = new UpdateAvatarPresenter();
});
describe('transform', () => {
it('should map result to response model', () => {
const result = {
avatarId: 'avatar-123',
driverId: 'driver-456',
};
const response = presenter.transform(result);
expect(response).toEqual({ success: true });
expect(presenter.getResponseModel()).toEqual({ success: true });
});
it('should always return success true', () => {
const result = {
avatarId: 'avatar-789',
driverId: 'driver-999',
};
const response = presenter.transform(result);
expect(response.success).toBe(true);
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.transform({ avatarId: 'avatar-123', driverId: 'driver-456' });
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('responseModel', () => {
it('should throw error when accessed before transform()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
it('should return model after transform()', () => {
presenter.transform({ avatarId: 'avatar-123', driverId: 'driver-456' });
expect(presenter.responseModel).toEqual({ success: true });
});
});
describe('getResponseModel', () => {
it('should return null before transform()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({ avatarId: 'avatar-123', driverId: 'driver-456' });
expect(presenter.getResponseModel()).toEqual({ success: true });
});
});
});

View File

@@ -0,0 +1,92 @@
import { UploadMediaPresenter } from './UploadMediaPresenter';
describe('UploadMediaPresenter', () => {
let presenter: UploadMediaPresenter;
beforeEach(() => {
presenter = new UploadMediaPresenter();
});
describe('transform', () => {
it('should map result with URL to response model', () => {
const result = {
mediaId: 'media-123',
url: 'https://example.com/uploaded-file.png',
};
const response = presenter.transform(result);
expect(response).toEqual({
success: true,
mediaId: 'media-123',
url: 'https://example.com/uploaded-file.png',
});
});
it('should map result without URL to response model', () => {
const result = {
mediaId: 'media-123',
url: undefined,
};
const response = presenter.transform(result);
expect(response).toEqual({
success: true,
mediaId: 'media-123',
});
expect(response.url).toBeUndefined();
});
it('should always set success to true', () => {
const result = {
mediaId: 'media-456',
url: 'https://example.com/test.jpg',
};
const response = presenter.transform(result);
expect(response.success).toBe(true);
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.transform({ mediaId: 'media-123', url: 'https://example.com/file.png' });
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('responseModel', () => {
it('should throw error when accessed before transform()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
it('should return model after transform()', () => {
presenter.transform({ mediaId: 'media-123', url: 'https://example.com/file.png' });
expect(presenter.responseModel).toEqual({
success: true,
mediaId: 'media-123',
url: 'https://example.com/file.png',
});
});
});
describe('getResponseModel', () => {
it('should return null before transform()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after transform()', () => {
presenter.transform({ mediaId: 'media-123', url: 'https://example.com/file.png' });
expect(presenter.getResponseModel()).toEqual({
success: true,
mediaId: 'media-123',
url: 'https://example.com/file.png',
});
});
});
});

View File

@@ -0,0 +1,134 @@
import { AllRacesPageDataPresenter } from './AllRacesPageDataPresenter';
import type { GetAllRacesPageDataResult } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
describe('AllRacesPageDataPresenter', () => {
let presenter: AllRacesPageDataPresenter;
beforeEach(() => {
presenter = new AllRacesPageDataPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result: GetAllRacesPageDataResult = {
races: [
{
id: 'race-1',
track: 'Track A',
car: 'Car A',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
leagueId: 'league-1',
leagueName: 'League A',
strengthOfField: 1500,
},
{
id: 'race-2',
track: 'Track B',
car: 'Car B',
scheduledAt: '2024-01-02T10:00:00Z',
status: 'completed',
leagueId: 'league-2',
leagueName: 'League B',
strengthOfField: null,
},
],
filters: {
statuses: [
{ value: 'all', label: 'All Statuses' },
{ value: 'scheduled', label: 'Scheduled' },
],
leagues: [
{ id: 'league-1', name: 'League A' },
{ id: 'league-2', name: 'League B' },
],
},
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
races: [
{
id: 'race-1',
track: 'Track A',
car: 'Car A',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
leagueId: 'league-1',
leagueName: 'League A',
strengthOfField: 1500,
},
{
id: 'race-2',
track: 'Track B',
car: 'Car B',
scheduledAt: '2024-01-02T10:00:00Z',
status: 'completed',
leagueId: 'league-2',
leagueName: 'League B',
strengthOfField: null,
},
],
filters: {
statuses: [
{ value: 'all', label: 'All Statuses' },
{ value: 'scheduled', label: 'Scheduled' },
],
leagues: [
{ id: 'league-1', name: 'League A' },
{ id: 'league-2', name: 'League B' },
],
},
});
});
it('should handle empty races', () => {
const result: GetAllRacesPageDataResult = {
races: [],
filters: {
statuses: [{ value: 'all', label: 'All Statuses' }],
leagues: [],
},
};
presenter.present(result);
expect(presenter.responseModel.races).toEqual([]);
expect(presenter.responseModel.filters.leagues).toEqual([]);
});
});
describe('reset', () => {
it('should clear the model', () => {
const result: GetAllRacesPageDataResult = {
races: [{ id: 'race-1', track: 'Track A', car: 'Car A', scheduledAt: '2024-01-01', status: 'scheduled', leagueId: 'league-1', leagueName: 'League A', strengthOfField: null }],
filters: { statuses: [], leagues: [] },
};
presenter.present(result);
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetAllRacesPageDataResult = {
races: [{ id: 'race-1', track: 'Track A', car: 'Car A', scheduledAt: '2024-01-01', status: 'scheduled', leagueId: 'league-1', leagueName: 'League A', strengthOfField: null }],
filters: { statuses: [], leagues: [] },
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,62 @@
import { GetTotalRacesPresenter } from './GetTotalRacesPresenter';
import type { GetTotalRacesResult } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
describe('GetTotalRacesPresenter', () => {
let presenter: GetTotalRacesPresenter;
beforeEach(() => {
presenter = new GetTotalRacesPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result: GetTotalRacesResult = { totalRaces: 42 };
presenter.present(result);
expect(presenter.responseModel).toEqual({ totalRaces: 42 });
});
it('should handle zero races', () => {
const result: GetTotalRacesResult = { totalRaces: 0 };
presenter.present(result);
expect(presenter.responseModel).toEqual({ totalRaces: 0 });
});
it('should handle large numbers', () => {
const result: GetTotalRacesResult = { totalRaces: 999999 };
presenter.present(result);
expect(presenter.responseModel).toEqual({ totalRaces: 999999 });
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({ totalRaces: 42 });
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetTotalRacesResult = { totalRaces: 42 };
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,115 @@
import { ImportRaceResultsApiPresenter } from './ImportRaceResultsApiPresenter';
import type { ImportRaceResultsApiResult } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
describe('ImportRaceResultsApiPresenter', () => {
let presenter: ImportRaceResultsApiPresenter;
beforeEach(() => {
presenter = new ImportRaceResultsApiPresenter();
});
describe('present', () => {
it('should map successful result to response model', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
success: true,
raceId: 'race-123',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
});
});
it('should handle result with errors', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 8,
errors: ['Driver not found: 12345', 'Driver not found: 67890'],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
success: true,
raceId: 'race-123',
driversProcessed: 10,
resultsRecorded: 8,
errors: ['Driver not found: 12345', 'Driver not found: 67890'],
});
});
it('should handle zero drivers processed', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 0,
resultsRecorded: 0,
errors: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
success: true,
raceId: 'race-123',
driversProcessed: 0,
resultsRecorded: 0,
errors: [],
});
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
});
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: ImportRaceResultsApiResult = {
success: true,
raceId: 'race-123',
leagueId: 'league-456',
driversProcessed: 10,
resultsRecorded: 10,
errors: [],
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,159 @@
import { RacePenaltiesPresenter } from './RacePenaltiesPresenter';
import type { GetRacePenaltiesResult } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
describe('RacePenaltiesPresenter', () => {
let presenter: RacePenaltiesPresenter;
beforeEach(() => {
presenter = new RacePenaltiesPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const mockPenalty = {
id: 'penalty-123',
driverId: 'driver-456',
type: 'time_penalty',
value: 10,
reason: 'Track limits violation',
issuedBy: 'steward-789',
issuedAt: new Date('2024-01-01T10:00:00Z'),
notes: 'Multiple violations',
};
const mockDriver = {
id: 'driver-456',
name: { toString: () => 'John Doe' } as any,
};
const result: GetRacePenaltiesResult = {
penalties: [mockPenalty as any],
drivers: [mockDriver as any],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
penalties: [
{
id: 'penalty-123',
driverId: 'driver-456',
type: 'time_penalty',
value: 10,
reason: 'Track limits violation',
issuedBy: 'steward-789',
issuedAt: '2024-01-01T10:00:00.000Z',
notes: 'Multiple violations',
},
],
driverMap: {
'driver-456': 'John Doe',
},
});
});
it('should handle multiple penalties and drivers', () => {
const result: GetRacePenaltiesResult = {
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'time_penalty',
value: 5,
reason: 'Reason 1',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
{
id: 'penalty-2',
driverId: 'driver-2',
type: 'drive_through',
value: 0,
reason: 'Reason 2',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:05:00Z'),
} as any,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
],
};
presenter.present(result);
expect(presenter.responseModel.penalties).toHaveLength(2);
expect(presenter.responseModel.driverMap).toEqual({
'driver-1': 'Driver One',
'driver-2': 'Driver Two',
});
});
it('should handle empty penalties', () => {
const result: GetRacePenaltiesResult = {
penalties: [],
drivers: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
penalties: [],
driverMap: {},
});
});
it('should handle penalties with undefined value', () => {
const result: GetRacePenaltiesResult = {
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'disqualification',
value: undefined,
reason: 'Reason',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
],
drivers: [{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any],
};
presenter.present(result);
expect(presenter.responseModel.penalties[0]?.value).toBe(0);
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any],
});
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetRacePenaltiesResult = {
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any],
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,198 @@
import { RaceProtestsPresenter } from './RaceProtestsPresenter';
import type { GetRaceProtestsResult } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
describe('RaceProtestsPresenter', () => {
let presenter: RaceProtestsPresenter;
beforeEach(() => {
presenter = new RaceProtestsPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const mockProtest = {
id: 'protest-123',
protestingDriverId: 'driver-456',
accusedDriverId: 'driver-789',
incident: {
lap: { toNumber: () => 5 } as any,
description: { toString: () => 'Contact at turn 3' } as any,
},
status: { toString: () => 'pending' } as any,
filedAt: new Date('2024-01-01T10:30:00Z'),
};
const mockDriver1 = {
id: 'driver-456',
name: { toString: () => 'John Doe' } as any,
};
const mockDriver2 = {
id: 'driver-789',
name: { toString: () => 'Jane Smith' } as any,
};
const result: GetRaceProtestsResult = {
protests: [mockProtest as any],
drivers: [mockDriver1 as any, mockDriver2 as any],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
protests: [
{
id: 'protest-123',
protestingDriverId: 'driver-456',
accusedDriverId: 'driver-789',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
status: 'pending',
filedAt: '2024-01-01T10:30:00.000Z',
},
],
driverMap: {
'driver-456': 'John Doe',
'driver-789': 'Jane Smith',
},
});
});
it('should handle multiple protests and drivers', () => {
const result: GetRaceProtestsResult = {
protests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: { toNumber: () => 3 } as any,
description: { toString: () => 'Incident 1' } as any
},
status: { toString: () => 'pending' } as any,
filedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-1',
incident: {
lap: { toNumber: () => 7 } as any,
description: { toString: () => 'Incident 2' } as any
},
status: { toString: () => 'reviewed' } as any,
filedAt: new Date('2024-01-01T10:10:00Z'),
} as any,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
{ id: 'driver-3', name: { toString: () => 'Driver Three' } } as any,
],
};
presenter.present(result);
expect(presenter.responseModel.protests).toHaveLength(2);
expect(presenter.responseModel.driverMap).toEqual({
'driver-1': 'Driver One',
'driver-2': 'Driver Two',
'driver-3': 'Driver Three',
});
});
it('should handle empty protests', () => {
const result: GetRaceProtestsResult = {
protests: [],
drivers: [],
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
protests: [],
driverMap: {},
});
});
it('should handle protests with reviewed status', () => {
const result: GetRaceProtestsResult = {
protests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: { toNumber: () => 5 } as any,
description: { toString: () => 'Test' } as any
},
status: { toString: () => 'approved' } as any,
filedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
],
};
presenter.present(result);
expect(presenter.responseModel.protests[0]?.status).toBe('approved');
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
protests: [{
id: 'p1',
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: {
lap: { toNumber: () => 1 } as any,
description: { toString: () => 'test' } as any
},
status: { toString: () => 'pending' } as any,
filedAt: new Date()
} as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any],
});
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
});
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetRaceProtestsResult = {
protests: [{
id: 'p1',
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: {
lap: { toNumber: () => 1 } as any,
description: { toString: () => 'test' } as any
},
status: { toString: () => 'pending' } as any,
filedAt: new Date()
} as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any],
};
presenter.present(result);
expect(presenter.viewModel).toEqual(presenter.responseModel);
});
});
});

View File

@@ -0,0 +1,79 @@
import { CreateTeamPresenter } from './CreateTeamPresenter';
describe('CreateTeamPresenter', () => {
let presenter: CreateTeamPresenter;
beforeEach(() => {
presenter = new CreateTeamPresenter();
});
describe('present', () => {
it('should map result to response model', () => {
const result = {
team: {
id: 'team-123',
} as any,
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
id: 'team-123',
success: true,
});
});
it('should handle different team IDs', () => {
const result = {
team: {
id: 'team-456',
} as any,
};
presenter.present(result);
expect(presenter.responseModel).toEqual({
id: 'team-456',
success: true,
});
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({ team: { id: 'team-123' } } as any);
expect(presenter.responseModel).toBeDefined();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('getResponseModel', () => {
it('should return null before present()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after present()', () => {
presenter.present({ team: { id: 'team-123' } } as any);
expect(presenter.getResponseModel()).toEqual({
id: 'team-123',
success: true,
});
});
});
describe('responseModel', () => {
it('should throw error when accessed before present()', () => {
expect(() => presenter.responseModel).toThrow('Presenter not presented');
});
it('should return model after present()', () => {
presenter.present({ team: { id: 'team-123' } } as any);
expect(presenter.responseModel).toEqual({
id: 'team-123',
success: true,
});
});
});
});

View File

@@ -0,0 +1,218 @@
import { DriverTeamPresenter } from './DriverTeamPresenter';
describe('DriverTeamPresenter', () => {
let presenter: DriverTeamPresenter;
beforeEach(() => {
presenter = new DriverTeamPresenter();
});
describe('present', () => {
it('should map result to response model for owner', () => {
const result = {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team Alpha' } as any,
tag: { toString: () => 'TA' } as any,
description: { toString: () => 'Best team' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [{ toString: () => 'league-1' } as any],
isRecruiting: true,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
};
presenter.present(result);
const model = presenter.getResponseModel();
expect(model).toEqual({
team: {
id: 'team-456',
name: 'Team Alpha',
tag: 'TA',
description: 'Best team',
ownerId: 'driver-123',
leagues: ['league-1'],
isRecruiting: true,
createdAt: '2024-01-01T00:00:00.000Z',
},
membership: {
role: 'owner',
joinedAt: '2024-01-01T00:00:00.000Z',
isActive: true,
},
isOwner: true,
canManage: true,
});
});
it('should map result for manager', () => {
const result = {
driverId: 'driver-456',
team: {
id: 'team-789',
name: { toString: () => 'Team Beta' } as any,
tag: { toString: () => 'TB' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-02') } as any,
} as any,
membership: {
role: 'manager' as const,
joinedAt: new Date('2024-01-02'),
status: 'active' as const,
} as any,
};
presenter.present(result);
const model = presenter.getResponseModel();
expect(model?.isOwner).toBe(false);
expect(model?.canManage).toBe(true);
expect(model?.membership.role).toBe('manager');
});
it('should map result for member', () => {
const result = {
driverId: 'driver-789',
team: {
id: 'team-abc',
name: { toString: () => 'Team Gamma' } as any,
tag: { toString: () => 'TG' } as any,
description: { toString: () => 'Test team' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [{ toString: () => 'league-2' } as any],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-03') } as any,
} as any,
membership: {
role: 'driver' as const,
joinedAt: new Date('2024-01-03'),
status: 'active' as const,
} as any,
};
presenter.present(result);
const model = presenter.getResponseModel();
expect(model?.isOwner).toBe(false);
expect(model?.canManage).toBe(false);
expect(model?.membership.role).toBe('member');
});
it('should handle empty description', () => {
const result = {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
};
presenter.present(result);
const model = presenter.getResponseModel();
expect(model?.team.description).toBe('');
});
it('should handle empty leagues', () => {
const result = {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => 'Test' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
};
presenter.present(result);
const model = presenter.getResponseModel();
expect(model?.team.leagues).toEqual([]);
});
});
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
});
expect(presenter.getResponseModel()).toBeDefined();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
});
describe('getResponseModel', () => {
it('should return null before present()', () => {
expect(presenter.getResponseModel()).toBeNull();
});
it('should return model after present()', () => {
presenter.present({
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
});
expect(presenter.getResponseModel()).not.toBeNull();
});
});
});