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) }) })