Files
flowbudget/tests/budget.test.ts
T

113 lines
4.1 KiB
TypeScript

import { describe, it } from 'node:test'
import assert from 'node:assert'
import {
getTotalBudget,
getTotalSpent,
getRemaining,
getProgressPercent,
addTransaction,
removeTransaction,
rollOverMonth,
computeLifestyleCreep,
getNetWorth,
} from '../src/lib/budget.js'
import type { Category, Transaction } from '../src/types.js'
const baseCats: Category[] = [
{ id: 'food', name: 'Food', budget: 500, spent: 100, color: '#4CAF50', isSinkingFund: false, rolloverEnabled: false, rolloverAmount: 0 },
{ id: 'vacation', name: 'Vacation', budget: 200, spent: 0, color: '#00BCD4', isSinkingFund: true, rolloverEnabled: true, rolloverAmount: 50 },
]
describe('getTotalBudget', () => {
it('sums category budgets including rollover', () => {
assert.strictEqual(getTotalBudget(baseCats), 750)
})
})
describe('getTotalSpent', () => {
it('sums category spending', () => {
assert.strictEqual(getTotalSpent(baseCats), 100)
})
})
describe('getRemaining', () => {
it('computes remaining for non-sinking fund', () => {
assert.strictEqual(getRemaining(baseCats[0]), 400)
})
it('includes rollover for sinking fund', () => {
assert.strictEqual(getRemaining(baseCats[1]), 250)
})
})
describe('getProgressPercent', () => {
it('returns correct percent', () => {
assert.strictEqual(getProgressPercent(baseCats[0]), 20)
})
it('caps at 100', () => {
const cat: Category = { ...baseCats[0], spent: 600 }
assert.strictEqual(getProgressPercent(cat), 100)
})
})
describe('addTransaction', () => {
it('adds a simple transaction', () => {
const tx: Transaction = { id: '1', date: '2024-01-01', amount: 50, merchant: 'Store', categoryId: 'food', description: '', isSplit: false }
const result = addTransaction(baseCats, [], tx)
assert.strictEqual(result.categories[0].spent, 150)
assert.strictEqual(result.transactions.length, 1)
})
it('adds a split transaction', () => {
const tx: Transaction = {
id: '2', date: '2024-01-01', amount: 100, merchant: 'Amazon', categoryId: 'food', description: '', isSplit: true,
items: [{ name: 'Food', amount: 60, categoryId: 'food' }, { name: 'Thing', amount: 40, categoryId: 'vacation' }],
originalAmount: 100,
}
const result = addTransaction(baseCats, [], tx)
assert.strictEqual(result.categories[0].spent, 160)
assert.strictEqual(result.categories[1].spent, 40)
})
})
describe('removeTransaction', () => {
it('removes a transaction and reverts spending', () => {
const tx: Transaction = { id: '1', date: '2024-01-01', amount: 50, merchant: 'Store', categoryId: 'food', description: '', isSplit: false }
const added = addTransaction(baseCats, [], tx)
const result = removeTransaction(added.categories, added.transactions, '1')
assert.strictEqual(result.categories[0].spent, 100)
assert.strictEqual(result.transactions.length, 0)
})
})
describe('rollOverMonth', () => {
it('rolls over remaining to next month for sinking funds', () => {
const cats = rollOverMonth(baseCats)
assert.strictEqual(cats[1].rolloverAmount, 300)
assert.strictEqual(cats[1].spent, 0)
})
it('does not roll over non-sinking funds', () => {
const cats = rollOverMonth(baseCats)
assert.strictEqual(cats[0].rolloverAmount, 0)
assert.strictEqual(cats[0].spent, 0)
})
})
describe('computeLifestyleCreep', () => {
it('computes year-over-year changes', () => {
const snapshots = [
{ month: '2023-06', categories: { food: { budget: 400, spent: 300 } }, netWorth: 10000 },
{ month: '2024-06', categories: { food: { budget: 500, spent: 350 } }, netWorth: 12000 },
]
const creep = computeLifestyleCreep(snapshots, '2024-06')
assert.strictEqual(creep.length, 1)
assert.strictEqual(creep[0].changePercent, 25)
assert.strictEqual(creep[0].isInflation, true)
})
})
describe('getNetWorth', () => {
it('sums all account balances', () => {
const accounts = [{ id: '1', name: 'A', type: 'checking' as const, balance: 1000 }, { id: '2', name: 'B', type: 'savings' as const, balance: 2000 }]
assert.strictEqual(getNetWorth(accounts), 3000)
})
})