update money tracker
All checks were successful
Test and deploy staging / staging (push) Successful in 30s
All checks were successful
Test and deploy staging / staging (push) Successful in 30s
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { accountBalances, balanceSeries, forecastSeries, utilityEstimate } from './charts';
|
||||
import {
|
||||
accountBalances,
|
||||
balanceSeries,
|
||||
forecastSeries,
|
||||
utilityEstimate,
|
||||
wealthProjection
|
||||
} from './charts';
|
||||
|
||||
describe('money tracker calculations', () => {
|
||||
it('keeps balances separate across accounts and preserves transaction order', () => {
|
||||
@@ -14,8 +20,43 @@ describe('money tracker calculations', () => {
|
||||
];
|
||||
expect([...accountBalances(accounts, transactions).values()]).toEqual([8500, 25000]);
|
||||
expect(balanceSeries(accounts, transactions)[0].points.map((point) => point.value)).toEqual([
|
||||
10000, 7500, 8500
|
||||
7500, 7500, 8500
|
||||
]);
|
||||
const combined = balanceSeries(accounts, transactions).at(-1)!;
|
||||
expect(combined.label).toBe('All accounts');
|
||||
expect(combined.emphasis).toBeFalsy();
|
||||
expect(combined.points.map((point) => point.value)).toEqual([27500, 32500, 33500]);
|
||||
expect(combined.points.at(-1)?.value).toBe(
|
||||
[...accountBalances(accounts, transactions).values()].reduce((sum, value) => sum + value, 0)
|
||||
);
|
||||
});
|
||||
|
||||
it('plots combined transactions in date order even if the input is unordered', () => {
|
||||
const combined = balanceSeries(
|
||||
[{ id: 1, name: 'Cash', openingBalanceCents: 1000 }],
|
||||
[
|
||||
{ accountId: 1, date: '2026-02-02', amountCents: 200 },
|
||||
{ accountId: 1, date: '2026-01-01', amountCents: -100 }
|
||||
]
|
||||
).at(-1)!;
|
||||
expect(combined.points).toEqual([
|
||||
{ date: '2026-01-01', value: 900 },
|
||||
{ date: '2026-02-02', value: 1100 }
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not show a temporary loss when money moves between accounts on the same date', () => {
|
||||
const combined = balanceSeries(
|
||||
[
|
||||
{ id: 1, name: 'Daily', openingBalanceCents: 1000 },
|
||||
{ id: 2, name: 'Shared', openingBalanceCents: 0 }
|
||||
],
|
||||
[
|
||||
{ accountId: 1, date: '2026-01-01', amountCents: -200 },
|
||||
{ accountId: 2, date: '2026-01-01', amountCents: 200 }
|
||||
]
|
||||
).at(-1)!;
|
||||
expect(combined.points).toEqual([{ date: '2026-01-01', value: 1000 }]);
|
||||
});
|
||||
|
||||
it('projects only future estimates from the current balance', () => {
|
||||
@@ -32,6 +73,46 @@ describe('money tracker calculations', () => {
|
||||
).toEqual([10000, 7500, 12500]);
|
||||
});
|
||||
|
||||
it('keeps the reserve, transfers excess into investments, and adds growth only to invested money', () => {
|
||||
const settings = {
|
||||
investmentAccountId: 2,
|
||||
reserveCents: 100000,
|
||||
annualReturnPercent: '5.00',
|
||||
contributionMode: 'excess',
|
||||
fixedMonthlyCents: 0,
|
||||
includeRecurring: 1,
|
||||
endDate: '2026-11-30'
|
||||
};
|
||||
const accounts = [
|
||||
{ id: 1, kind: 'spendable', openingBalanceCents: 100000 },
|
||||
{ id: 2, kind: 'investment', openingBalanceCents: 0 }
|
||||
];
|
||||
const series = wealthProjection(
|
||||
accounts,
|
||||
new Map([
|
||||
[1, 100000],
|
||||
[2, 0]
|
||||
]),
|
||||
[{ date: '2026-10-01', accountId: 1, amountCents: -10000 }],
|
||||
[{ date: '2026-10-15', accountId: 1, amountCents: 20000 }],
|
||||
[{ amountCents: 50000, cadence: 'monthly', accountId: 1 }],
|
||||
settings,
|
||||
'2026-09-23'
|
||||
);
|
||||
expect(series[0].points.map((point) => point.value)).toEqual([100000, 100000, 160000, 210244]);
|
||||
expect(series[1].points.at(-1)?.value).toBe(100000);
|
||||
expect(series[2].points.at(-1)?.value).toBe(110244);
|
||||
});
|
||||
|
||||
it('does not add a future dated transaction to today’s starting balance', () => {
|
||||
const accounts = [{ id: 1, kind: 'spendable', openingBalanceCents: 10000 }];
|
||||
const future = [{ date: '2026-10-01', accountId: 1, amountCents: 5000 }];
|
||||
const balances = accountBalances(accounts, []);
|
||||
const series = wealthProjection(accounts, balances, future, [], [], null, '2026-09-23');
|
||||
expect(series[0].points[0].value).toBe(10000);
|
||||
expect(series[0].points.at(-1)?.value).toBe(15000);
|
||||
});
|
||||
|
||||
it('compares estimated usage cost with prorated advances', () => {
|
||||
const result = utilityEstimate(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user