diff --git a/CHANGES.md b/CHANGES.md index 9c38282..6455edb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased +- Add splitBill(items, people) to split the taxed total into per-person shares. ## 1.2.0 - total() rounds through roundCents(). diff --git a/src/lib/cart.js b/src/lib/cart.js index 385fd69..903adb9 100644 --- a/src/lib/cart.js +++ b/src/lib/cart.js @@ -8,3 +8,15 @@ export function subtotal(items) { export function total(items) { return roundCents(subtotal(items) * (1 + TAX_RATE)); } + +// Splits the taxed total between `people` (a count). Works in whole cents so the +// shares always add up to total(); leftover cents go to the first people. +export function splitBill(items, people) { + if (!Number.isInteger(people) || people < 1) { + throw new RangeError('people must be a positive integer'); + } + const cents = roundCents(total(items) * 100); + const extra = cents % people; + const base = (cents - extra) / people; + return Array.from({ length: people }, (_, i) => (base + (i < extra ? 1 : 0)) / 100); +} diff --git a/tests/lib/cart.test.js b/tests/lib/cart.test.js index 7a9eab3..901cf2b 100644 --- a/tests/lib/cart.test.js +++ b/tests/lib/cart.test.js @@ -1,6 +1,6 @@ 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'; const items = [{ price: 10, qty: 2 }, { price: 5, qty: 1 }]; @@ -11,3 +11,26 @@ 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 sum to the total, extra cents go to the first people', () => { + const shares = splitBill(items, 4); // 27.00 / 4 + assert.deepEqual(shares, [6.75, 6.75, 6.75, 6.75]); + const uneven = splitBill(items, 7); // 2700 cents / 7 + assert.equal(uneven.length, 7); + assert.deepEqual(uneven.slice(0, 5), [3.86, 3.86, 3.86, 3.86, 3.86]); + assert.deepEqual(uneven.slice(5), [3.85, 3.85]); + assert.equal(Math.round(uneven.reduce((a, b) => a + b, 0) * 100), 2700); +}); + +test('splitBill with one person returns the whole total', () => { + assert.deepEqual(splitBill(items, 1), [27]); +}); + +test('splitBill rejects an invalid number of people', () => { + assert.throws(() => splitBill(items, 0), RangeError); + assert.throws(() => splitBill(items, 1.5), RangeError); +});