QuitFree MVP: subscription tracker, budget manager, cancel helper, debt calculator, CSV/JSON export. 13 tests passing.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
monthlyCost, annualCost, totalMonthlyCost, totalAnnualCost, daysUntil,
|
||||
subscriptionsExpiringSoon, subscriptionsToCSV, expensesToCSV, generateId, formatCurrency
|
||||
} from '../src/shared/storage'
|
||||
import type { Subscription, Expense } from '../src/shared/storage'
|
||||
|
||||
describe('monthlyCost', () => {
|
||||
it('returns monthly cost for monthly interval', () => {
|
||||
const s: Subscription = { id: '1', name: 'Test', cost: 10, interval: 'monthly', startedAt: '2024-01-01', category: 'Other' }
|
||||
expect(monthlyCost(s)).toBe(10)
|
||||
})
|
||||
it('returns monthly cost for yearly interval', () => {
|
||||
const s: Subscription = { id: '1', name: 'Test', cost: 120, interval: 'yearly', startedAt: '2024-01-01', category: 'Other' }
|
||||
expect(monthlyCost(s)).toBe(10)
|
||||
})
|
||||
it('returns monthly cost for weekly interval', () => {
|
||||
const s: Subscription = { id: '1', name: 'Test', cost: 10, interval: 'weekly', startedAt: '2024-01-01', category: 'Other' }
|
||||
expect(monthlyCost(s)).toBeCloseTo(43.3, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('annualCost', () => {
|
||||
it('returns annual cost for monthly interval', () => {
|
||||
const s: Subscription = { id: '1', name: 'Test', cost: 10, interval: 'monthly', startedAt: '2024-01-01', category: 'Other' }
|
||||
expect(annualCost(s)).toBe(120)
|
||||
})
|
||||
it('returns annual cost for yearly interval', () => {
|
||||
const s: Subscription = { id: '1', name: 'Test', cost: 120, interval: 'yearly', startedAt: '2024-01-01', category: 'Other' }
|
||||
expect(annualCost(s)).toBe(120)
|
||||
})
|
||||
})
|
||||
|
||||
describe('totalMonthlyCost', () => {
|
||||
it('sums only active subscriptions', () => {
|
||||
const subs: Subscription[] = [
|
||||
{ id: '1', name: 'A', cost: 10, interval: 'monthly', startedAt: '2024-01-01', category: 'Other' },
|
||||
{ id: '2', name: 'B', cost: 20, interval: 'monthly', startedAt: '2024-01-01', category: 'Other', cancelled: true },
|
||||
{ id: '3', name: 'C', cost: 120, interval: 'yearly', startedAt: '2024-01-01', category: 'Other' }
|
||||
]
|
||||
expect(totalMonthlyCost(subs)).toBe(20)
|
||||
})
|
||||
})
|
||||
|
||||
describe('daysUntil', () => {
|
||||
it('returns days until a future date', () => {
|
||||
const future = new Date(Date.now() + 3 * 86400000).toISOString().slice(0, 10)
|
||||
expect(daysUntil(future)).toBe(3)
|
||||
})
|
||||
it('returns 0 for today', () => {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
expect(daysUntil(today)).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('subscriptionsExpiringSoon', () => {
|
||||
it('returns trials expiring within given days', () => {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const tomorrow = new Date(Date.now() + 86400000).toISOString().slice(0, 10)
|
||||
const nextWeek = new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 10)
|
||||
const subs: Subscription[] = [
|
||||
{ id: '1', name: 'A', cost: 10, interval: 'monthly', startedAt: '2024-01-01', category: 'Other', trialEndsAt: today },
|
||||
{ id: '2', name: 'B', cost: 10, interval: 'monthly', startedAt: '2024-01-01', category: 'Other', trialEndsAt: tomorrow },
|
||||
{ id: '3', name: 'C', cost: 10, interval: 'monthly', startedAt: '2024-01-01', category: 'Other', trialEndsAt: nextWeek }
|
||||
]
|
||||
expect(subscriptionsExpiringSoon(subs, 3).length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatCurrency', () => {
|
||||
it('formats dollars correctly', () => {
|
||||
expect(formatCurrency(10)).toBe('$10.00')
|
||||
expect(formatCurrency(10.5)).toBe('$10.50')
|
||||
expect(formatCurrency(0)).toBe('$0.00')
|
||||
})
|
||||
})
|
||||
|
||||
describe('generateId', () => {
|
||||
it('generates unique ids', () => {
|
||||
const id1 = generateId()
|
||||
const id2 = generateId()
|
||||
expect(id1).not.toBe(id2)
|
||||
expect(id1.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('subscriptionsToCSV', () => {
|
||||
it('produces valid CSV', () => {
|
||||
const subs: Subscription[] = [
|
||||
{ id: '1', name: 'Netflix', cost: 15.99, interval: 'monthly', startedAt: '2024-01-01', category: 'Entertainment' }
|
||||
]
|
||||
const csv = subscriptionsToCSV(subs)
|
||||
expect(csv).toContain('Name')
|
||||
expect(csv).toContain('Netflix')
|
||||
expect(csv).toContain('15.99')
|
||||
})
|
||||
})
|
||||
|
||||
describe('expensesToCSV', () => {
|
||||
it('produces valid CSV', () => {
|
||||
const expenses: Expense[] = [
|
||||
{ id: '1', amount: 25.50, description: 'Lunch', category: 'Food', date: '2024-01-01' }
|
||||
]
|
||||
const csv = expensesToCSV(expenses)
|
||||
expect(csv).toContain('Description')
|
||||
expect(csv).toContain('Lunch')
|
||||
expect(csv).toContain('25.50')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user