diff --git a/CHANGES.md b/CHANGES.md index 9c38282..687d622 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased +- Add splitBill(items, people) to split the taxed total between people. ## 1.2.0 - total() rounds through roundCents(). diff --git a/src/lib/cart.js b/src/lib/cart.js index 385fd69..2065158 100644 --- a/src/lib/cart.js +++ b/src/lib/cart.js @@ -8,3 +8,18 @@ export function subtotal(items) { export function total(items) { return roundCents(subtotal(items) * (1 + TAX_RATE)); } + +// `people` is a head count or an array of people. Everyone pays an equal +// rounded share; the last person absorbs the rounding remainder so the +// shares always add up to total(items). +export function splitBill(items, people) { + const count = Array.isArray(people) ? people.length : people; + if (!Number.isInteger(count) || count < 1) { + throw new RangeError('splitBill needs at least one person'); + } + const billTotal = total(items); + const share = roundCents(billTotal / count); + const shares = new Array(count - 1).fill(share); + shares.push(roundCents(billTotal - share * (count - 1))); + return shares; +} diff --git a/tests/lib/cart.test.js b/tests/lib/cart.test.js index 7a9eab3..c7c1bc7 100644 --- a/tests/lib/cart.test.js +++ b/tests/lib/cart.test.js @@ -1,6 +1,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { subtotal, total } from '../../src/lib/cart.js'; +import { subtotal, total, splitBill } from '../../src/lib/cart.js'; +import { roundCents } from '../../src/lib/money.js'; const items = [{ price: 10, qty: 2 }, { price: 5, qty: 1 }]; @@ -11,3 +12,27 @@ test('subtotal adds price times quantity', () => { test('total applies tax and rounds to cents', () => { assert.equal(total(items), 27); }); + +test('splitBill splits the taxed total evenly', () => { + assert.deepEqual(splitBill(items, 2), [13.5, 13.5]); +}); + +test('splitBill shares always add up to the total', () => { + const shares = splitBill([{ price: 10, qty: 1 }], 3); // total 10.80 + assert.equal(shares.length, 3); + assert.equal(roundCents(shares.reduce((a, b) => a + b, 0)), total([{ price: 10, qty: 1 }])); + const odd = splitBill([{ price: 1, qty: 1 }], 3); // total 1.08 -> 0.36 each + assert.deepEqual(odd, [0.36, 0.36, 0.36]); + const uneven = splitBill([{ price: 0.1, qty: 1 }], 3); // total 0.11 + assert.equal(roundCents(uneven.reduce((a, b) => a + b, 0)), 0.11); +}); + +test('splitBill accepts a list of people', () => { + assert.deepEqual(splitBill(items, ['a', 'b']), [13.5, 13.5]); +}); + +test('splitBill rejects an invalid number of people', () => { + assert.throws(() => splitBill(items, 0)); + assert.throws(() => splitBill(items, [])); + assert.throws(() => splitBill(items, 1.5)); +});