Files
anbraten cbe1b42f6e
tinyedge/deploy Deployed by TinyEdge
🐛 add env to useDb and apply prettier
2026-08-07 13:28:26 +02:00

99 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { Expense, Participant } from '../types';
import { calculateBalances } from './balances';
const participants: Participant[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Carol' },
];
describe('calculateBalances', () => {
it('nets an equal split expense among all participants', () => {
const expenses: Expense[] = [
{ id: 1, amountCents: 900, payerId: 1, participantIds: [1, 2, 3] },
];
const { netBalances, settlements } = calculateBalances(
participants,
expenses,
);
expect(netBalances).toEqual({ 1: 600, 2: -300, 3: -300 });
expect(settlements).toEqual(
expect.arrayContaining([
{ fromId: 2, toId: 1, amountCents: 300 },
{ fromId: 3, toId: 1, amountCents: 300 },
]),
);
expect(settlements).toHaveLength(2);
});
it('handles an expense split among a subset of participants', () => {
const expenses: Expense[] = [
{ id: 1, amountCents: 1000, payerId: 2, participantIds: [2, 3] },
];
const { netBalances } = calculateBalances(participants, expenses);
expect(netBalances).toEqual({ 1: 0, 2: 500, 3: -500 });
});
it('produces a zero-sum, fully-settled group when expenses cancel out', () => {
const expenses: Expense[] = [
{ id: 1, amountCents: 900, payerId: 1, participantIds: [1, 2, 3] },
{ id: 2, amountCents: 900, payerId: 2, participantIds: [1, 2, 3] },
{ id: 3, amountCents: 900, payerId: 3, participantIds: [1, 2, 3] },
];
const { netBalances, settlements } = calculateBalances(
participants,
expenses,
);
expect(Object.values(netBalances).every((v) => v === 0)).toBe(true);
expect(settlements).toEqual([]);
});
it('folds in a settlement payment recorded as a one-person split expense', () => {
const expenses: Expense[] = [
{ id: 1, amountCents: 900, payerId: 1, participantIds: [1, 2, 3] },
// Bob (2) settles 300 with Alice (1): payerId is who pays, the sole
// participant is who receives it.
{ id: 2, amountCents: 300, payerId: 2, participantIds: [1] },
];
const { netBalances, settlements } = calculateBalances(
participants,
expenses,
);
expect(netBalances).toEqual({ 1: 300, 2: 0, 3: -300 });
expect(settlements).toEqual([{ fromId: 3, toId: 1, amountCents: 300 }]);
});
it('handles a single-participant Pot (self-paid expense nets to zero)', () => {
const solo: Participant[] = [{ id: 1, name: 'Alice' }];
const expenses: Expense[] = [
{ id: 1, amountCents: 500, payerId: 1, participantIds: [1] },
];
const { netBalances, settlements } = calculateBalances(solo, expenses);
expect(netBalances).toEqual({ 1: 0 });
expect(settlements).toEqual([]);
});
it('produces a minimal number of settlements for a multi-person scenario', () => {
const four: Participant[] = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' },
{ id: 4, name: 'D' },
];
// A pays 400 split 4 ways (each owes 100), B pays 800 split 4 ways (each owes 200).
const expenses: Expense[] = [
{ id: 1, amountCents: 400, payerId: 1, participantIds: [1, 2, 3, 4] },
{ id: 2, amountCents: 800, payerId: 2, participantIds: [1, 2, 3, 4] },
];
const { netBalances, settlements } = calculateBalances(four, expenses);
// A: +400-100-200=100, B: +800-100-200=500, C: -300, D: -300
expect(netBalances).toEqual({ 1: 100, 2: 500, 3: -300, 4: -300 });
// 2 creditors, 2 debtors -> minimal settlement count is at most 3 (n-1 participants with nonzero balance)
expect(settlements.length).toBeLessThanOrEqual(3);
const total = settlements.reduce((sum, s) => sum + s.amountCents, 0);
expect(total).toBe(600);
});
});