This commit is contained in:
@@ -1,48 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Expense, Participant } from '../types'
|
||||
import { calculateBalances } from './balances'
|
||||
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)
|
||||
];
|
||||
const { netBalances, settlements } = calculateBalances(
|
||||
participants,
|
||||
expenses,
|
||||
);
|
||||
|
||||
expect(netBalances).toEqual({ 1: 600, 2: -300, 3: -300 })
|
||||
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)
|
||||
})
|
||||
);
|
||||
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 })
|
||||
})
|
||||
];
|
||||
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([])
|
||||
})
|
||||
];
|
||||
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[] = [
|
||||
@@ -50,21 +56,24 @@ describe('calculateBalances', () => {
|
||||
// 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 }])
|
||||
})
|
||||
];
|
||||
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 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([])
|
||||
})
|
||||
];
|
||||
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[] = [
|
||||
@@ -72,18 +81,18 @@ describe('calculateBalances', () => {
|
||||
{ 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)
|
||||
];
|
||||
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 })
|
||||
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)
|
||||
})
|
||||
})
|
||||
expect(settlements.length).toBeLessThanOrEqual(3);
|
||||
const total = settlements.reduce((sum, s) => sum + s.amountCents, 0);
|
||||
expect(total).toBe(600);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user