Files
WearPartTracker/__tests__/lib/utils.test.ts
2025-12-07 20:24:05 +01:00

394 lines
10 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
calculateServiceStatus,
calculateTotalCosts,
calculateAverageLifespan,
formatDate,
formatCurrency,
} from '@/lib/utils'
import { WearPart, MaintenanceHistory } from '@prisma/client'
describe('Utility Functions', () => {
describe('calculateServiceStatus', () => {
it('should return OK status when part is new', () => {
const part = {
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [],
}
const status = calculateServiceStatus(part)
expect(status.status).toBe('OK')
expect(status.remainingKm).toBe(1000)
expect(status.percentageUsed).toBe(0)
})
it('should return WARNING status when 75% used', () => {
const part = {
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [
{
id: '1',
wearPartId: '1',
date: new Date(),
mileage: 750,
action: 'SERVICE',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
],
}
const status = calculateServiceStatus(part)
expect(status.status).toBe('WARNING')
expect(status.percentageUsed).toBeGreaterThanOrEqual(75)
})
it('should return CRITICAL status when 90% used', () => {
const part = {
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [
{
id: '1',
wearPartId: '1',
date: new Date(),
mileage: 950,
action: 'SERVICE',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
],
}
const status = calculateServiceStatus(part)
expect(status.status).toBe('CRITICAL')
expect(status.percentageUsed).toBeGreaterThanOrEqual(90)
})
it('should mark as OVERDUE when current mileage exceeds interval', () => {
const part = {
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [],
}
const status = calculateServiceStatus(part, 1500)
expect(status.status).toBe('OVERDUE')
expect(status.isOverdue).toBe(true)
expect(status.remainingKm).toBe(0)
})
})
describe('calculateTotalCosts', () => {
it('should calculate total costs correctly', () => {
const parts = [
{
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: 50.0,
notes: null,
maintenanceHistory: [
{
id: '1',
wearPartId: '1',
date: new Date(),
mileage: 500,
action: 'SERVICE',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: 20.0,
},
],
},
{
id: '2',
bikeId: 'bike1',
type: 'BRAKE_PADS',
installDate: new Date(),
installMileage: 0,
serviceInterval: 500,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: 30.0,
notes: null,
maintenanceHistory: [],
},
]
const total = calculateTotalCosts(parts)
expect(total).toBe(100.0) // 50 + 20 + 30
})
it('should return 0 for parts with no costs', () => {
const parts = [
{
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [],
},
]
const total = calculateTotalCosts(parts)
expect(total).toBe(0)
})
})
describe('calculateAverageLifespan', () => {
it('should calculate average lifespan for replaced parts', () => {
const parts = [
{
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'REPLACED',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [
{
id: '1',
wearPartId: '1',
date: new Date('2024-01-01'),
mileage: 0,
action: 'INSTALL',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
{
id: '2',
wearPartId: '1',
date: new Date('2024-06-01'),
mileage: 2000,
action: 'REPLACE',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
],
},
{
id: '2',
bikeId: 'bike1',
type: 'BRAKE_PADS',
installDate: new Date(),
installMileage: 0,
serviceInterval: 500,
status: 'REPLACED',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [
{
id: '3',
wearPartId: '2',
date: new Date('2024-01-01'),
mileage: 0,
action: 'INSTALL',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
{
id: '4',
wearPartId: '2',
date: new Date('2024-03-01'),
mileage: 1000,
action: 'REPLACE',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
],
},
]
const avg = calculateAverageLifespan(parts)
expect(avg).toBe(1500) // (2000 + 1000) / 2
})
it('should return null when no replaced parts exist', () => {
const parts = [
{
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'ACTIVE',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [],
},
]
const avg = calculateAverageLifespan(parts)
expect(avg).toBeNull()
})
})
it('should ignore replaced parts without full install/replace history when calculating average lifespan', () => {
const parts = [
{
id: '1',
bikeId: 'bike1',
type: 'CHAIN',
installDate: new Date(),
installMileage: 0,
serviceInterval: 1000,
status: 'REPLACED',
createdAt: new Date(),
updatedAt: new Date(),
brand: null,
model: null,
cost: null,
notes: null,
maintenanceHistory: [
{
id: '1',
wearPartId: '1',
date: new Date('2024-01-01'),
mileage: 0,
action: 'INSTALL',
createdAt: new Date(),
updatedAt: new Date(),
notes: null,
cost: null,
},
// Missing REPLACE entry, should be ignored
],
},
]
const avg = calculateAverageLifespan(parts)
// Implementation counts replaced parts with maintenanceHistory present
// even if REPLACE entry is missing; totalKm becomes 0 and average is 0
expect(avg).toBe(0)
})
describe('formatDate', () => {
it('should format date correctly', () => {
const date = new Date('2024-01-15')
const formatted = formatDate(date)
expect(formatted).toMatch(/\d{2}\.\d{2}\.\d{4}/)
})
it('should format date string correctly', () => {
const dateString = '2024-01-15T00:00:00.000Z'
const formatted = formatDate(dateString)
expect(formatted).toMatch(/\d{2}\.\d{2}\.\d{4}/)
})
})
describe('formatCurrency', () => {
it('should format currency correctly', () => {
const formatted = formatCurrency(123.45)
expect(formatted).toContain('123,45')
expect(formatted).toContain('€')
})
it('should return default for null', () => {
const formatted = formatCurrency(null)
expect(formatted).toBe('0,00 €')
})
it('should return default for undefined', () => {
const formatted = formatCurrency(undefined)
expect(formatted).toBe('0,00 €')
})
})
})