add icons to lists and solve money tracker bugs
All checks were successful
Test and deploy staging / staging (push) Successful in 2m30s

This commit is contained in:
2026-09-24 19:24:09 +00:00
parent a10585a4d2
commit c757d0882a
18 changed files with 1358 additions and 731 deletions

View File

@@ -1,10 +1,12 @@
import { describe, expect, it } from 'vitest';
import {
accountBalances,
accountColor,
balanceSeries,
forecastSeries,
utilityEstimate,
wealthProjection
wealthProjection,
wealthTimeline
} from './charts';
describe('money tracker calculations', () => {
@@ -31,6 +33,18 @@ describe('money tracker calculations', () => {
);
});
it("uses an account's chosen color and falls back to the palette", () => {
const series = balanceSeries(
[
{ id: 1, name: 'Revolut', openingBalanceCents: 0, color: '#000000' },
{ id: 2, name: 'Knab', openingBalanceCents: 0, color: null }
],
[{ accountId: 1, date: '2026-01-01', amountCents: 100 }]
);
expect(series[0].color).toBe('#000000');
expect(series[1].color).toBe(accountColor({ color: null }, 1));
});
it('plots combined transactions in date order even if the input is unordered', () => {
const combined = balanceSeries(
[{ id: 1, name: 'Cash', openingBalanceCents: 1000 }],
@@ -113,6 +127,108 @@ describe('money tracker calculations', () => {
expect(series[0].points.at(-1)?.value).toBe(15000);
});
it('invests excess each month without requiring an existing investment account', () => {
const projection = wealthProjection(
[{ id: 1, kind: 'spendable', openingBalanceCents: 40054 }],
new Map([[1, 40054]]),
[],
[],
[{ amountCents: 127137, cadence: 'monthly', accountId: 1 }],
{
investmentAccountId: null,
reserveCents: 100000,
annualReturnPercent: '5.00',
contributionMode: 'excess',
fixedMonthlyCents: 0,
includeRecurring: 1,
endDate: '2026-11-30'
},
'2026-09-23'
);
expect(projection[0].points.map((point) => point.value)).toEqual([
40054, 40054, 167191, 294602
]);
expect(projection[1].points.at(-1)?.value).toBe(100000);
expect(projection[2].points.at(-1)?.value).toBe(194602);
});
it('continues actual history into the projection as one dashed line', () => {
const accounts = [
{ id: 1, name: 'Bank', kind: 'spendable', openingBalanceCents: 0 },
{ id: 2, name: 'Broker', kind: 'investment', openingBalanceCents: 0 }
];
const actual = [
{ accountId: 1, date: '2026-09-01', amountCents: 50000 },
{ accountId: 2, date: '2026-09-10', amountCents: 20000 }
];
const projection = [
{
label: 'Total wealth',
color: '',
points: [
{ date: '2026-09-23', value: 70000 },
{ date: '2026-10-31', value: 90000 }
]
},
{
label: 'Spendable',
color: '',
points: [
{ date: '2026-09-23', value: 50000 },
{ date: '2026-10-31', value: 60000 }
]
},
{
label: 'Invested',
color: '',
points: [
{ date: '2026-09-23', value: 20000 },
{ date: '2026-10-31', value: 30000 }
]
}
];
const [total] = wealthTimeline(accounts, actual, projection, '2026-09-23', 'total');
expect(total.dashedFrom).toBe('2026-09-23');
expect(total.points).toEqual([
{ date: '2026-09-01', value: 50000 },
{ date: '2026-09-10', value: 70000 },
{ date: '2026-09-23', value: 70000 },
{ date: '2026-10-31', value: 90000 }
]);
const [, spendable, invested] = wealthTimeline(
accounts,
actual,
projection,
'2026-09-23',
'split'
);
expect(spendable.points.map((point) => point.value)).toEqual([50000, 50000, 50000, 60000]);
expect(invested.points.map((point) => point.value)).toEqual([0, 20000, 20000, 30000]);
const byAccount = wealthTimeline(accounts, actual, projection, '2026-09-23', 'accounts');
expect(byAccount.map((line) => line.label)).toEqual(['Bank', 'Broker', 'Total']);
expect(byAccount[0].points.at(-1)).toEqual({ date: '2026-09-23', value: 50000 });
});
it('starts the timeline at today when there is no history', () => {
const [total] = wealthTimeline(
[],
[],
[
{
label: 'Total wealth',
color: '',
points: [
{ date: '2026-09-23', value: 0 },
{ date: '2026-10-31', value: 100 }
]
}
],
'2026-09-23',
'total'
);
expect(total.points.map((point) => point.date)).toEqual(['2026-09-23', '2026-10-31']);
});
it('compares estimated usage cost with prorated advances', () => {
const result = utilityEstimate(
[