view data fixes
This commit is contained in:
@@ -1,11 +1,22 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { BillingViewData } from '@/lib/view-data/BillingViewData';
|
||||
import { BillingViewModel, PaymentMethodViewModel, InvoiceViewModel, BillingStatsViewModel } from './BillingViewModel';
|
||||
|
||||
describe('BillingViewModel', () => {
|
||||
it('maps arrays of payment methods, invoices and stats into view models', () => {
|
||||
const data = {
|
||||
const viewData: BillingViewData = {
|
||||
paymentMethods: [
|
||||
{ id: 'pm-1', type: 'card', last4: '4242', brand: 'Visa', isDefault: true, expiryMonth: 12, expiryYear: 2030 },
|
||||
{
|
||||
id: 'pm-1',
|
||||
type: 'card',
|
||||
last4: '4242',
|
||||
brand: 'Visa',
|
||||
isDefault: true,
|
||||
expiryMonth: 12,
|
||||
expiryYear: 2030,
|
||||
displayLabel: 'Visa •••• 4242',
|
||||
expiryDisplay: '12/2030',
|
||||
},
|
||||
],
|
||||
invoices: [
|
||||
{
|
||||
@@ -20,6 +31,10 @@ describe('BillingViewModel', () => {
|
||||
description: 'Sponsorship',
|
||||
sponsorshipType: 'league',
|
||||
pdfUrl: 'https://example.com/invoice.pdf',
|
||||
formattedTotalAmount: '€119,00',
|
||||
formattedVatAmount: '€19,00',
|
||||
formattedDate: '2024-01-01',
|
||||
isOverdue: false,
|
||||
},
|
||||
],
|
||||
stats: {
|
||||
@@ -29,10 +44,15 @@ describe('BillingViewModel', () => {
|
||||
nextPaymentAmount: 50,
|
||||
activeSponsorships: 3,
|
||||
averageMonthlySpend: 250,
|
||||
formattedTotalSpent: '€1.000,00',
|
||||
formattedPendingAmount: '€200,00',
|
||||
formattedNextPaymentAmount: '€50,00',
|
||||
formattedAverageMonthlySpend: '€250,00',
|
||||
formattedNextPaymentDate: '2024-03-01',
|
||||
},
|
||||
} as any;
|
||||
};
|
||||
|
||||
const vm = new BillingViewModel(data);
|
||||
const vm = new BillingViewModel(viewData);
|
||||
|
||||
expect(vm.paymentMethods).toHaveLength(1);
|
||||
expect(vm.paymentMethods[0]).toBeInstanceOf(PaymentMethodViewModel);
|
||||
@@ -44,53 +64,67 @@ describe('BillingViewModel', () => {
|
||||
|
||||
describe('PaymentMethodViewModel', () => {
|
||||
it('builds displayLabel based on type and bankName/brand', () => {
|
||||
const card = new PaymentMethodViewModel({
|
||||
const card = {
|
||||
id: 'pm-1',
|
||||
type: 'card',
|
||||
type: 'card' as const,
|
||||
last4: '4242',
|
||||
brand: 'Visa',
|
||||
isDefault: true,
|
||||
});
|
||||
displayLabel: 'Visa •••• 4242',
|
||||
expiryDisplay: null,
|
||||
};
|
||||
|
||||
const sepa = new PaymentMethodViewModel({
|
||||
const sepa = {
|
||||
id: 'pm-2',
|
||||
type: 'sepa',
|
||||
type: 'sepa' as const,
|
||||
last4: '1337',
|
||||
bankName: 'Test Bank',
|
||||
isDefault: false,
|
||||
});
|
||||
displayLabel: 'Test Bank •••• 1337',
|
||||
expiryDisplay: null,
|
||||
};
|
||||
|
||||
expect(card.displayLabel).toBe('Visa •••• 4242');
|
||||
expect(sepa.displayLabel).toBe('Test Bank •••• 1337');
|
||||
const cardVm = new PaymentMethodViewModel(card);
|
||||
const sepaVm = new PaymentMethodViewModel(sepa);
|
||||
|
||||
expect(cardVm.displayLabel).toBe('Visa •••• 4242');
|
||||
expect(sepaVm.displayLabel).toBe('Test Bank •••• 1337');
|
||||
});
|
||||
|
||||
it('returns expiryDisplay when month and year are provided', () => {
|
||||
const withExpiry = new PaymentMethodViewModel({
|
||||
const withExpiry = {
|
||||
id: 'pm-1',
|
||||
type: 'card',
|
||||
type: 'card' as const,
|
||||
last4: '4242',
|
||||
brand: 'Visa',
|
||||
isDefault: true,
|
||||
expiryMonth: 3,
|
||||
expiryYear: 2030,
|
||||
});
|
||||
displayLabel: 'Visa •••• 4242',
|
||||
expiryDisplay: '03/2030',
|
||||
};
|
||||
|
||||
const withoutExpiry = new PaymentMethodViewModel({
|
||||
const withoutExpiry = {
|
||||
id: 'pm-2',
|
||||
type: 'card',
|
||||
type: 'card' as const,
|
||||
last4: '9999',
|
||||
brand: 'Mastercard',
|
||||
isDefault: false,
|
||||
});
|
||||
displayLabel: 'Mastercard •••• 9999',
|
||||
expiryDisplay: null,
|
||||
};
|
||||
|
||||
expect(withExpiry.expiryDisplay).toBe('03/2030');
|
||||
expect(withoutExpiry.expiryDisplay).toBeNull();
|
||||
const withExpiryVm = new PaymentMethodViewModel(withExpiry);
|
||||
const withoutExpiryVm = new PaymentMethodViewModel(withoutExpiry);
|
||||
|
||||
expect(withExpiryVm.expiryDisplay).toBe('03/2030');
|
||||
expect(withoutExpiryVm.expiryDisplay).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('InvoiceViewModel', () => {
|
||||
it('formats monetary amounts and dates', () => {
|
||||
const dto = {
|
||||
const viewData = {
|
||||
id: 'inv-1',
|
||||
invoiceNumber: 'INV-1',
|
||||
date: '2024-01-15',
|
||||
@@ -98,16 +132,20 @@ describe('InvoiceViewModel', () => {
|
||||
amount: 100,
|
||||
vatAmount: 19,
|
||||
totalAmount: 119,
|
||||
status: 'paid',
|
||||
status: 'paid' as const,
|
||||
description: 'Sponsorship',
|
||||
sponsorshipType: 'league',
|
||||
sponsorshipType: 'league' as const,
|
||||
pdfUrl: 'https://example.com/invoice.pdf',
|
||||
} as any;
|
||||
formattedTotalAmount: '€119,00',
|
||||
formattedVatAmount: '€19,00',
|
||||
formattedDate: '2024-01-15',
|
||||
isOverdue: false,
|
||||
};
|
||||
|
||||
const vm = new InvoiceViewModel(dto);
|
||||
const vm = new InvoiceViewModel(viewData);
|
||||
|
||||
expect(vm.formattedTotalAmount).toBe(`€${(119).toLocaleString('de-DE', { minimumFractionDigits: 2 })}`);
|
||||
expect(vm.formattedVatAmount).toBe(`€${(19).toLocaleString('de-DE', { minimumFractionDigits: 2 })}`);
|
||||
expect(vm.formattedTotalAmount).toBe('€119,00');
|
||||
expect(vm.formattedVatAmount).toBe('€19,00');
|
||||
expect(typeof vm.formattedDate).toBe('string');
|
||||
});
|
||||
|
||||
@@ -116,7 +154,7 @@ describe('InvoiceViewModel', () => {
|
||||
const pastDate = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
|
||||
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
const overdue = new InvoiceViewModel({
|
||||
const overdue = {
|
||||
id: 'inv-1',
|
||||
invoiceNumber: 'INV-1',
|
||||
date: pastDate,
|
||||
@@ -124,13 +162,17 @@ describe('InvoiceViewModel', () => {
|
||||
amount: 0,
|
||||
vatAmount: 0,
|
||||
totalAmount: 0,
|
||||
status: 'overdue',
|
||||
status: 'overdue' as const,
|
||||
description: '',
|
||||
sponsorshipType: 'league',
|
||||
sponsorshipType: 'league' as const,
|
||||
pdfUrl: '',
|
||||
} as any);
|
||||
formattedTotalAmount: '€0,00',
|
||||
formattedVatAmount: '€0,00',
|
||||
formattedDate: pastDate,
|
||||
isOverdue: true,
|
||||
};
|
||||
|
||||
const pendingPastDue = new InvoiceViewModel({
|
||||
const pendingPastDue = {
|
||||
id: 'inv-2',
|
||||
invoiceNumber: 'INV-2',
|
||||
date: pastDate,
|
||||
@@ -138,13 +180,17 @@ describe('InvoiceViewModel', () => {
|
||||
amount: 0,
|
||||
vatAmount: 0,
|
||||
totalAmount: 0,
|
||||
status: 'pending',
|
||||
status: 'pending' as const,
|
||||
description: '',
|
||||
sponsorshipType: 'league',
|
||||
sponsorshipType: 'league' as const,
|
||||
pdfUrl: '',
|
||||
} as any);
|
||||
formattedTotalAmount: '€0,00',
|
||||
formattedVatAmount: '€0,00',
|
||||
formattedDate: pastDate,
|
||||
isOverdue: true,
|
||||
};
|
||||
|
||||
const pendingFuture = new InvoiceViewModel({
|
||||
const pendingFuture = {
|
||||
id: 'inv-3',
|
||||
invoiceNumber: 'INV-3',
|
||||
date: pastDate,
|
||||
@@ -152,35 +198,48 @@ describe('InvoiceViewModel', () => {
|
||||
amount: 0,
|
||||
vatAmount: 0,
|
||||
totalAmount: 0,
|
||||
status: 'pending',
|
||||
status: 'pending' as const,
|
||||
description: '',
|
||||
sponsorshipType: 'league',
|
||||
sponsorshipType: 'league' as const,
|
||||
pdfUrl: '',
|
||||
} as any);
|
||||
formattedTotalAmount: '€0,00',
|
||||
formattedVatAmount: '€0,00',
|
||||
formattedDate: pastDate,
|
||||
isOverdue: false,
|
||||
};
|
||||
|
||||
expect(overdue.isOverdue).toBe(true);
|
||||
expect(pendingPastDue.isOverdue).toBe(true);
|
||||
expect(pendingFuture.isOverdue).toBe(false);
|
||||
const overdueVm = new InvoiceViewModel(overdue);
|
||||
const pendingPastDueVm = new InvoiceViewModel(pendingPastDue);
|
||||
const pendingFutureVm = new InvoiceViewModel(pendingFuture);
|
||||
|
||||
expect(overdueVm.isOverdue).toBe(true);
|
||||
expect(pendingPastDueVm.isOverdue).toBe(true);
|
||||
expect(pendingFutureVm.isOverdue).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BillingStatsViewModel', () => {
|
||||
it('formats monetary fields and next payment date', () => {
|
||||
const dto = {
|
||||
const viewData = {
|
||||
totalSpent: 1234,
|
||||
pendingAmount: 56.78,
|
||||
nextPaymentDate: '2024-03-01',
|
||||
nextPaymentAmount: 42,
|
||||
activeSponsorships: 2,
|
||||
averageMonthlySpend: 321,
|
||||
} as any;
|
||||
formattedTotalSpent: '€1.234,00',
|
||||
formattedPendingAmount: '€56,78',
|
||||
formattedNextPaymentAmount: '€42,00',
|
||||
formattedAverageMonthlySpend: '€321,00',
|
||||
formattedNextPaymentDate: '2024-03-01',
|
||||
};
|
||||
|
||||
const vm = new BillingStatsViewModel(dto);
|
||||
const vm = new BillingStatsViewModel(viewData);
|
||||
|
||||
expect(vm.formattedTotalSpent).toBe(`€${(1234).toLocaleString('de-DE')}`);
|
||||
expect(vm.formattedPendingAmount).toBe(`€${(56.78).toLocaleString('de-DE', { minimumFractionDigits: 2 })}`);
|
||||
expect(vm.formattedNextPaymentAmount).toBe(`€${(42).toLocaleString('de-DE', { minimumFractionDigits: 2 })}`);
|
||||
expect(vm.formattedAverageMonthlySpend).toBe(`€${(321).toLocaleString('de-DE')}`);
|
||||
expect(vm.formattedTotalSpent).toBe('€1.234,00');
|
||||
expect(vm.formattedPendingAmount).toBe('€56,78');
|
||||
expect(vm.formattedNextPaymentAmount).toBe('€42,00');
|
||||
expect(vm.formattedAverageMonthlySpend).toBe('€321,00');
|
||||
expect(typeof vm.formattedNextPaymentDate).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user