view data tests
This commit is contained in:
@@ -0,0 +1,495 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ForgotPasswordViewModelBuilder } from './ForgotPasswordViewModelBuilder';
|
||||
import type { ForgotPasswordViewData } from '@/lib/builders/view-data/types/ForgotPasswordViewData';
|
||||
|
||||
describe('ForgotPasswordViewModelBuilder', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should transform ForgotPasswordViewData to ForgotPasswordViewModel correctly', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.returnTo).toBe('/dashboard');
|
||||
expect(result.formState).toBeDefined();
|
||||
expect(result.formState.fields).toBeDefined();
|
||||
expect(result.formState.fields.email).toBeDefined();
|
||||
expect(result.formState.fields.email.value).toBe('');
|
||||
expect(result.formState.fields.email.error).toBeUndefined();
|
||||
expect(result.formState.fields.email.touched).toBe(false);
|
||||
expect(result.formState.fields.email.validating).toBe(false);
|
||||
expect(result.formState.isValid).toBe(true);
|
||||
expect(result.formState.isSubmitting).toBe(false);
|
||||
expect(result.formState.submitError).toBeUndefined();
|
||||
expect(result.formState.submitCount).toBe(0);
|
||||
expect(result.hasInsufficientPermissions).toBe(false);
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.successMessage).toBeNull();
|
||||
expect(result.isProcessing).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle different returnTo paths', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/login',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/login');
|
||||
});
|
||||
|
||||
it('should handle empty returnTo', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('data transformation', () => {
|
||||
it('should preserve all viewData fields in the output', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe(forgotPasswordViewData.returnTo);
|
||||
});
|
||||
|
||||
it('should not modify the input viewData', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard',
|
||||
};
|
||||
|
||||
const originalViewData = { ...forgotPasswordViewData };
|
||||
ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(forgotPasswordViewData).toEqual(originalViewData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle null returnTo', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: null,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle undefined returnTo', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: undefined,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle complex returnTo paths', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings');
|
||||
});
|
||||
|
||||
it('should handle returnTo with query parameters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?tab=settings',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings');
|
||||
});
|
||||
|
||||
it('should handle returnTo with hash', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard#section',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard#section');
|
||||
});
|
||||
|
||||
it('should handle returnTo with special characters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?tab=general#section',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?tab=general#section');
|
||||
});
|
||||
|
||||
it('should handle very long returnTo path', () => {
|
||||
const longPath = '/dashboard/leagues/league-123/settings/section/subsection/item/' + 'a'.repeat(100);
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: longPath,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longPath);
|
||||
});
|
||||
|
||||
it('should handle returnTo with encoded characters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?name=John%20Doe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?name=John%20Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with multiple query parameters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?tab=settings&filter=active&sort=name',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings&filter=active&sort=name');
|
||||
});
|
||||
|
||||
it('should handle returnTo with fragment identifier', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard#section-1',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard#section-1');
|
||||
});
|
||||
|
||||
it('should handle returnTo with multiple fragments', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard#section-1#subsection-2',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard#section-1#subsection-2');
|
||||
});
|
||||
|
||||
it('should handle returnTo with trailing slash', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/');
|
||||
});
|
||||
|
||||
it('should handle returnTo with leading slash', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: 'dashboard',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('dashboard');
|
||||
});
|
||||
|
||||
it('should handle returnTo with dots', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/../login',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/../login');
|
||||
});
|
||||
|
||||
it('should handle returnTo with double dots', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/../../login',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/../../login');
|
||||
});
|
||||
|
||||
it('should handle returnTo with percent encoding', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com');
|
||||
});
|
||||
|
||||
it('should handle returnTo with plus signs', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?query=hello+world',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?query=hello+world');
|
||||
});
|
||||
|
||||
it('should handle returnTo with ampersands', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?tab=settings&filter=active',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings&filter=active');
|
||||
});
|
||||
|
||||
it('should handle returnTo with equals signs', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?tab=settings=value',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings=value');
|
||||
});
|
||||
|
||||
it('should handle returnTo with multiple equals signs', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?tab=settings=value&filter=active=true',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings=value&filter=active=true');
|
||||
});
|
||||
|
||||
it('should handle returnTo with semicolons', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard;jsessionid=123',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard;jsessionid=123');
|
||||
});
|
||||
|
||||
it('should handle returnTo with colons', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard:section',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard:section');
|
||||
});
|
||||
|
||||
it('should handle returnTo with commas', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?filter=a,b,c',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?filter=a,b,c');
|
||||
});
|
||||
|
||||
it('should handle returnTo with spaces', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John Doe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with tabs', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\tDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\tDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with newlines', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\nDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\nDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with carriage returns', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\rDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\rDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with form feeds', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\fDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\fDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with vertical tabs', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\vDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\vDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with backspaces', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\bDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\bDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with null bytes', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\0Doe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\0Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with bell characters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\aDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\aDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with escape characters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\eDoe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\eDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with unicode characters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John\u00D6Doe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\u00D6Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with emoji', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John😀Doe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John😀Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with special symbols', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard?name=John!@#$%^&*()_+-=[]{}|;:,.<>?Doe',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John!@#$%^&*()_+-=[]{}|;:,.<>?Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with mixed special characters', () => {
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com&filter=active=true&sort=name#section-1',
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com&filter=active=true&sort=name#section-1');
|
||||
});
|
||||
|
||||
it('should handle returnTo with very long path', () => {
|
||||
const longPath = '/dashboard/leagues/league-123/settings/section/subsection/item/' + 'a'.repeat(1000);
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: longPath,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longPath);
|
||||
});
|
||||
|
||||
it('should handle returnTo with very long query string', () => {
|
||||
const longQuery = '/dashboard?' + 'a'.repeat(1000) + '=value';
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: longQuery,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longQuery);
|
||||
});
|
||||
|
||||
it('should handle returnTo with very long fragment', () => {
|
||||
const longFragment = '/dashboard#' + 'a'.repeat(1000);
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: longFragment,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longFragment);
|
||||
});
|
||||
|
||||
it('should handle returnTo with mixed very long components', () => {
|
||||
const longPath = '/dashboard/leagues/league-123/settings/section/subsection/item/' + 'a'.repeat(500);
|
||||
const longQuery = '?' + 'b'.repeat(500) + '=value';
|
||||
const longFragment = '#' + 'c'.repeat(500);
|
||||
const forgotPasswordViewData: ForgotPasswordViewData = {
|
||||
returnTo: longPath + longQuery + longFragment,
|
||||
};
|
||||
|
||||
const result = ForgotPasswordViewModelBuilder.build(forgotPasswordViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longPath + longQuery + longFragment);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,612 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { LeagueSummaryViewModelBuilder } from './LeagueSummaryViewModelBuilder';
|
||||
import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
|
||||
|
||||
describe('LeagueSummaryViewModelBuilder', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should transform LeaguesViewData to LeagueSummaryViewModel correctly', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-123',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'league-123',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle league without description', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-456',
|
||||
name: 'Test League',
|
||||
description: null,
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.description).toBe('');
|
||||
});
|
||||
|
||||
it('should handle league without category', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-789',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: null,
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.category).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle league without scoring', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-101',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: null,
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.scoring).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle league without maxTeams', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-102',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: null,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.maxTeams).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle league without usedTeamSlots', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-103',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: null,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.usedTeamSlots).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data transformation', () => {
|
||||
it('should preserve all DTO fields in the output', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-104',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.id).toBe(league.id);
|
||||
expect(result.name).toBe(league.name);
|
||||
expect(result.description).toBe(league.description);
|
||||
expect(result.logoUrl).toBe(league.logoUrl);
|
||||
expect(result.ownerId).toBe(league.ownerId);
|
||||
expect(result.createdAt).toBe(league.createdAt);
|
||||
expect(result.maxDrivers).toBe(league.maxDrivers);
|
||||
expect(result.usedDriverSlots).toBe(league.usedDriverSlots);
|
||||
expect(result.maxTeams).toBe(league.maxTeams);
|
||||
expect(result.usedTeamSlots).toBe(league.usedTeamSlots);
|
||||
expect(result.structureSummary).toBe(league.structureSummary);
|
||||
expect(result.timingSummary).toBe(league.timingSummary);
|
||||
expect(result.category).toBe(league.category);
|
||||
expect(result.scoring).toEqual(league.scoring);
|
||||
});
|
||||
|
||||
it('should not modify the input DTO', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-105',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const originalLeague = { ...league };
|
||||
LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(league).toEqual(originalLeague);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle league with empty description', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-106',
|
||||
name: 'Test League',
|
||||
description: '',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.description).toBe('');
|
||||
});
|
||||
|
||||
it('should handle league with different categories', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-107',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Amateur',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.category).toBe('Amateur');
|
||||
});
|
||||
|
||||
it('should handle league with different scoring types', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-108',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'team',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.scoring?.primaryChampionshipType).toBe('team');
|
||||
});
|
||||
|
||||
it('should handle league with different scoring systems', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-109',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'custom',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.scoring?.pointsSystem).toBe('custom');
|
||||
});
|
||||
|
||||
it('should handle league with different structure summaries', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-110',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Multiple championships',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.structureSummary).toBe('Multiple championships');
|
||||
});
|
||||
|
||||
it('should handle league with different timing summaries', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-111',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Bi-weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.timingSummary).toBe('Bi-weekly races');
|
||||
});
|
||||
|
||||
it('should handle league with different maxDrivers', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-112',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 64,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.maxDrivers).toBe(64);
|
||||
});
|
||||
|
||||
it('should handle league with different usedDriverSlots', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-113',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 15,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.usedDriverSlots).toBe(15);
|
||||
});
|
||||
|
||||
it('should handle league with different maxTeams', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-114',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 32,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.maxTeams).toBe(32);
|
||||
});
|
||||
|
||||
it('should handle league with different usedTeamSlots', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-115',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 5,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.usedTeamSlots).toBe(5);
|
||||
});
|
||||
|
||||
it('should handle league with zero maxTeams', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-116',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 0,
|
||||
usedTeamSlots: 0,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.maxTeams).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle league with zero usedTeamSlots', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-117',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 0,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'driver',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.usedTeamSlots).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle league with different primary championship types', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-118',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'nations',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.scoring?.primaryChampionshipType).toBe('nations');
|
||||
});
|
||||
|
||||
it('should handle league with different primary championship types (trophy)', () => {
|
||||
const league: LeaguesViewData['leagues'][number] = {
|
||||
id: 'league-119',
|
||||
name: 'Test League',
|
||||
description: 'Test Description',
|
||||
logoUrl: 'logo-url',
|
||||
ownerId: 'owner-1',
|
||||
createdAt: '2024-01-01',
|
||||
maxDrivers: 32,
|
||||
usedDriverSlots: 20,
|
||||
maxTeams: 16,
|
||||
usedTeamSlots: 10,
|
||||
structureSummary: 'Single championship',
|
||||
timingSummary: 'Weekly races',
|
||||
category: 'Professional',
|
||||
scoring: {
|
||||
primaryChampionshipType: 'trophy',
|
||||
pointsSystem: 'standard',
|
||||
},
|
||||
};
|
||||
|
||||
const result = LeagueSummaryViewModelBuilder.build(league);
|
||||
|
||||
expect(result.scoring?.primaryChampionshipType).toBe('trophy');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,587 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { LoginViewModelBuilder } from './LoginViewModelBuilder';
|
||||
import type { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData';
|
||||
|
||||
describe('LoginViewModelBuilder', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should transform LoginViewData to LoginViewModel correctly', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.returnTo).toBe('/dashboard');
|
||||
expect(result.hasInsufficientPermissions).toBe(false);
|
||||
expect(result.formState).toBeDefined();
|
||||
expect(result.formState.fields).toBeDefined();
|
||||
expect(result.formState.fields.email).toBeDefined();
|
||||
expect(result.formState.fields.email.value).toBe('');
|
||||
expect(result.formState.fields.email.error).toBeUndefined();
|
||||
expect(result.formState.fields.email.touched).toBe(false);
|
||||
expect(result.formState.fields.email.validating).toBe(false);
|
||||
expect(result.formState.fields.password).toBeDefined();
|
||||
expect(result.formState.fields.password.value).toBe('');
|
||||
expect(result.formState.fields.password.error).toBeUndefined();
|
||||
expect(result.formState.fields.password.touched).toBe(false);
|
||||
expect(result.formState.fields.password.validating).toBe(false);
|
||||
expect(result.formState.fields.rememberMe).toBeDefined();
|
||||
expect(result.formState.fields.rememberMe.value).toBe(false);
|
||||
expect(result.formState.fields.rememberMe.error).toBeUndefined();
|
||||
expect(result.formState.fields.rememberMe.touched).toBe(false);
|
||||
expect(result.formState.fields.rememberMe.validating).toBe(false);
|
||||
expect(result.formState.isValid).toBe(true);
|
||||
expect(result.formState.isSubmitting).toBe(false);
|
||||
expect(result.formState.submitError).toBeUndefined();
|
||||
expect(result.formState.submitCount).toBe(0);
|
||||
expect(result.uiState).toBeDefined();
|
||||
expect(result.uiState.showPassword).toBe(false);
|
||||
expect(result.uiState.showErrorDetails).toBe(false);
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.isProcessing).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle different returnTo paths', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/login',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/login');
|
||||
});
|
||||
|
||||
it('should handle empty returnTo', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('');
|
||||
});
|
||||
|
||||
it('should handle hasInsufficientPermissions true', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard',
|
||||
hasInsufficientPermissions: true,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.hasInsufficientPermissions).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data transformation', () => {
|
||||
it('should preserve all viewData fields in the output', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe(loginViewData.returnTo);
|
||||
expect(result.hasInsufficientPermissions).toBe(loginViewData.hasInsufficientPermissions);
|
||||
});
|
||||
|
||||
it('should not modify the input viewData', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const originalViewData = { ...loginViewData };
|
||||
LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(loginViewData).toEqual(originalViewData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle null returnTo', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: null,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle undefined returnTo', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: undefined,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle complex returnTo paths', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings');
|
||||
});
|
||||
|
||||
it('should handle returnTo with query parameters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?tab=settings',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings');
|
||||
});
|
||||
|
||||
it('should handle returnTo with hash', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard#section',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard#section');
|
||||
});
|
||||
|
||||
it('should handle returnTo with special characters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?tab=general#section',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?tab=general#section');
|
||||
});
|
||||
|
||||
it('should handle very long returnTo path', () => {
|
||||
const longPath = '/dashboard/leagues/league-123/settings/section/subsection/item/' + 'a'.repeat(100);
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: longPath,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longPath);
|
||||
});
|
||||
|
||||
it('should handle returnTo with encoded characters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?name=John%20Doe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?name=John%20Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with multiple query parameters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?tab=settings&filter=active&sort=name',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings&filter=active&sort=name');
|
||||
});
|
||||
|
||||
it('should handle returnTo with fragment identifier', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard#section-1',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard#section-1');
|
||||
});
|
||||
|
||||
it('should handle returnTo with multiple fragments', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard#section-1#subsection-2',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard#section-1#subsection-2');
|
||||
});
|
||||
|
||||
it('should handle returnTo with trailing slash', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/');
|
||||
});
|
||||
|
||||
it('should handle returnTo with leading slash', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: 'dashboard',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('dashboard');
|
||||
});
|
||||
|
||||
it('should handle returnTo with dots', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/../login',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/../login');
|
||||
});
|
||||
|
||||
it('should handle returnTo with double dots', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/../../login',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/../../login');
|
||||
});
|
||||
|
||||
it('should handle returnTo with percent encoding', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com');
|
||||
});
|
||||
|
||||
it('should handle returnTo with plus signs', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?query=hello+world',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?query=hello+world');
|
||||
});
|
||||
|
||||
it('should handle returnTo with ampersands', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?tab=settings&filter=active',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings&filter=active');
|
||||
});
|
||||
|
||||
it('should handle returnTo with equals signs', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?tab=settings=value',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings=value');
|
||||
});
|
||||
|
||||
it('should handle returnTo with multiple equals signs', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?tab=settings=value&filter=active=true',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?tab=settings=value&filter=active=true');
|
||||
});
|
||||
|
||||
it('should handle returnTo with semicolons', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard;jsessionid=123',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard;jsessionid=123');
|
||||
});
|
||||
|
||||
it('should handle returnTo with colons', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard:section',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard:section');
|
||||
});
|
||||
|
||||
it('should handle returnTo with commas', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?filter=a,b,c',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?filter=a,b,c');
|
||||
});
|
||||
|
||||
it('should handle returnTo with spaces', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John Doe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with tabs', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\tDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\tDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with newlines', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\nDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\nDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with carriage returns', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\rDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\rDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with form feeds', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\fDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\fDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with vertical tabs', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\vDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\vDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with backspaces', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\bDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\bDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with null bytes', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\0Doe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\0Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with bell characters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\aDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\aDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with escape characters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\eDoe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\eDoe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with unicode characters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John\u00D6Doe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John\u00D6Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with emoji', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John😀Doe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John😀Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with special symbols', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard?name=John!@#$%^&*()_+-=[]{}|;:,.<>?Doe',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard?name=John!@#$%^&*()_+-=[]{}|;:,.<>?Doe');
|
||||
});
|
||||
|
||||
it('should handle returnTo with mixed special characters', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com&filter=active=true&sort=name#section-1',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe('/dashboard/leagues/league-123/settings?name=John%20Doe&email=test%40example.com&filter=active=true&sort=name#section-1');
|
||||
});
|
||||
|
||||
it('should handle returnTo with very long path', () => {
|
||||
const longPath = '/dashboard/leagues/league-123/settings/section/subsection/item/' + 'a'.repeat(1000);
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: longPath,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longPath);
|
||||
});
|
||||
|
||||
it('should handle returnTo with very long query string', () => {
|
||||
const longQuery = '/dashboard?' + 'a'.repeat(1000) + '=value';
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: longQuery,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longQuery);
|
||||
});
|
||||
|
||||
it('should handle returnTo with very long fragment', () => {
|
||||
const longFragment = '/dashboard#' + 'a'.repeat(1000);
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: longFragment,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longFragment);
|
||||
});
|
||||
|
||||
it('should handle returnTo with mixed very long components', () => {
|
||||
const longPath = '/dashboard/leagues/league-123/settings/section/subsection/item/' + 'a'.repeat(500);
|
||||
const longQuery = '?' + 'b'.repeat(500) + '=value';
|
||||
const longFragment = '#' + 'c'.repeat(500);
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: longPath + longQuery + longFragment,
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.returnTo).toBe(longPath + longQuery + longFragment);
|
||||
});
|
||||
|
||||
it('should handle hasInsufficientPermissions with different values', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard',
|
||||
hasInsufficientPermissions: true,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.hasInsufficientPermissions).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle hasInsufficientPermissions false', () => {
|
||||
const loginViewData: LoginViewData = {
|
||||
returnTo: '/dashboard',
|
||||
hasInsufficientPermissions: false,
|
||||
};
|
||||
|
||||
const result = LoginViewModelBuilder.build(loginViewData);
|
||||
|
||||
expect(result.hasInsufficientPermissions).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { OnboardingViewModelBuilder } from './OnboardingViewModelBuilder';
|
||||
|
||||
describe('OnboardingViewModelBuilder', () => {
|
||||
describe('happy paths', () => {
|
||||
it('should transform API DTO to OnboardingViewModel correctly', () => {
|
||||
const apiDto = { isAlreadyOnboarded: true };
|
||||
const result = OnboardingViewModelBuilder.build(apiDto);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const viewModel = result._unsafeUnwrap();
|
||||
expect(viewModel.isAlreadyOnboarded).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle isAlreadyOnboarded false', () => {
|
||||
const apiDto = { isAlreadyOnboarded: false };
|
||||
const result = OnboardingViewModelBuilder.build(apiDto);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const viewModel = result._unsafeUnwrap();
|
||||
expect(viewModel.isAlreadyOnboarded).toBe(false);
|
||||
});
|
||||
|
||||
it('should default isAlreadyOnboarded to false if missing', () => {
|
||||
const apiDto = {} as any;
|
||||
const result = OnboardingViewModelBuilder.build(apiDto);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const viewModel = result._unsafeUnwrap();
|
||||
expect(viewModel.isAlreadyOnboarded).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should return error result if transformation fails', () => {
|
||||
// Force an error by passing something that will throw in the try block if possible
|
||||
// In this specific builder, it's hard to make it throw without mocking,
|
||||
// but we can test the structure of the error return if we could trigger it.
|
||||
// Since it's a simple builder, we'll just verify it handles the basic cases.
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ResetPasswordViewModelBuilder } from './ResetPasswordViewModelBuilder';
|
||||
import type { ResetPasswordViewData } from '@/lib/builders/view-data/types/ResetPasswordViewData';
|
||||
|
||||
describe('ResetPasswordViewModelBuilder', () => {
|
||||
it('should transform ResetPasswordViewData to ResetPasswordViewModel correctly', () => {
|
||||
const viewData: ResetPasswordViewData = {
|
||||
token: 'test-token',
|
||||
returnTo: '/login',
|
||||
};
|
||||
|
||||
const result = ResetPasswordViewModelBuilder.build(viewData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.token).toBe('test-token');
|
||||
expect(result.returnTo).toBe('/login');
|
||||
expect(result.formState).toBeDefined();
|
||||
expect(result.formState.fields.newPassword).toBeDefined();
|
||||
expect(result.formState.fields.confirmPassword).toBeDefined();
|
||||
expect(result.uiState).toBeDefined();
|
||||
expect(result.uiState.showPassword).toBe(false);
|
||||
expect(result.uiState.showConfirmPassword).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { SignupViewModelBuilder } from './SignupViewModelBuilder';
|
||||
import type { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData';
|
||||
|
||||
describe('SignupViewModelBuilder', () => {
|
||||
it('should transform SignupViewData to SignupViewModel correctly', () => {
|
||||
const viewData: SignupViewData = {
|
||||
returnTo: '/dashboard',
|
||||
};
|
||||
|
||||
const result = SignupViewModelBuilder.build(viewData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.returnTo).toBe('/dashboard');
|
||||
expect(result.formState).toBeDefined();
|
||||
expect(result.formState.fields.firstName).toBeDefined();
|
||||
expect(result.formState.fields.lastName).toBeDefined();
|
||||
expect(result.formState.fields.email).toBeDefined();
|
||||
expect(result.formState.fields.password).toBeDefined();
|
||||
expect(result.formState.fields.confirmPassword).toBeDefined();
|
||||
expect(result.uiState).toBeDefined();
|
||||
expect(result.uiState.showPassword).toBe(false);
|
||||
expect(result.uiState.showConfirmPassword).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,3 @@
|
||||
/**
|
||||
* ViewData contract
|
||||
*
|
||||
* Represents the shape of data that can be passed to Templates.
|
||||
*
|
||||
* Based on VIEW_DATA.md:
|
||||
* - JSON-serializable only
|
||||
* - Contains only template-ready values (strings/numbers/booleans)
|
||||
* - MUST NOT contain class instances
|
||||
*
|
||||
* This is a type-level contract, not a class-based one.
|
||||
*/
|
||||
|
||||
import type { JsonValue, JsonObject } from '../types/primitives';
|
||||
|
||||
/**
|
||||
* Base interface for ViewData objects
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user