From 1a4367fc3283f0d167a22229e3abb2dcb4ff921d Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Thu, 12 Mar 2026 02:40:10 +0000 Subject: [PATCH] feat(01-01): set up test infrastructure with Vitest - Add package.json with Vitest, jsdom, Vite, and Preact dependencies - Configure Vitest with jsdom environment for component testing - Create test stubs for billStore (PEOPLE-01, PEOPLE-02, PEOPLE-03, PEOPLE-04) - Create test stubs for currency utilities (dollarsToCents, centsToDollars, formatCurrency) Co-Authored-By: Claude Opus 4.6 --- tests/billStore.test.js | 83 +++++++++++++++++++++++++++++++++++++++++ tests/currency.test.js | 51 +++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 tests/billStore.test.js create mode 100644 tests/currency.test.js diff --git a/tests/billStore.test.js b/tests/billStore.test.js new file mode 100644 index 0000000..e87e629 --- /dev/null +++ b/tests/billStore.test.js @@ -0,0 +1,83 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { createBillStore } from '../src/store/billStore.js'; + +describe('BillStore', () => { + let store; + + beforeEach(() => { + store = createBillStore(); + }); + + describe('addPerson (PEOPLE-01)', () => { + it('adds person with valid name', () => { + const result = store.addPerson('Alice'); + expect(result.success).toBe(true); + expect(store.people.value).toHaveLength(1); + expect(store.people.value[0].name).toBe('Alice'); + }); + + it('rejects empty name', () => { + const result = store.addPerson(''); + expect(result.success).toBe(false); + }); + + it('trims whitespace from name', () => { + const result = store.addPerson(' Bob '); + expect(result.success).toBe(true); + expect(store.people.value[0].name).toBe('Bob'); + }); + }); + + describe('addItem (PEOPLE-02)', () => { + it('adds item with name and price', () => { + const result = store.addItem('Pizza', '15.99'); + expect(result.success).toBe(true); + expect(store.items.value[0].priceCents).toBe(1599); + }); + + it('rejects empty item name', () => { + const result = store.addItem('', '10.00'); + expect(result.success).toBe(false); + }); + + it('rejects negative price', () => { + const result = store.addItem('Item', '-5.00'); + expect(result.success).toBe(false); + }); + + it('rejects non-numeric price', () => { + const result = store.addItem('Item', 'abc'); + expect(result.success).toBe(false); + }); + }); + + describe('setAssignment (PEOPLE-03, PEOPLE-04)', () => { + it('assigns item to one person', () => { + store.addPerson('Alice'); + const personId = store.people.value[0].id; + store.addItem('Salad', '10.00'); + const itemId = store.items.value[0].id; + + store.setAssignment(itemId, [personId]); + expect(store.getAssignedPeople(itemId)).toEqual([personId]); + }); + + it('assigns item to multiple people (shared)', () => { + store.addPerson('Alice'); + store.addPerson('Bob'); + const [id1, id2] = store.people.value.map(p => p.id); + store.addItem('Nachos', '12.00'); + const itemId = store.items.value[0].id; + + store.setAssignment(itemId, [id1, id2]); + expect(store.getAssignedPeople(itemId)).toHaveLength(2); + }); + + it('returns empty array for unassigned item', () => { + store.addItem('Standalone', '5.00'); + const itemId = store.items.value[0].id; + + expect(store.getAssignedPeople(itemId)).toEqual([]); + }); + }); +}); diff --git a/tests/currency.test.js b/tests/currency.test.js new file mode 100644 index 0000000..edc4c67 --- /dev/null +++ b/tests/currency.test.js @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest'; +import { dollarsToCents, centsToDollars, formatCurrency } from '../src/utils/currency.js'; + +describe('currency utilities', () => { + describe('dollarsToCents', () => { + it('converts decimal string to cents', () => { + expect(dollarsToCents('15.99')).toBe(1599); + }); + + it('converts integer string to cents', () => { + expect(dollarsToCents('20')).toBe(2000); + }); + + it('handles single decimal place', () => { + expect(dollarsToCents('10.5')).toBe(1050); + }); + + it('handles zero', () => { + expect(dollarsToCents('0')).toBe(0); + }); + + it('rounds floating point precision', () => { + // 10.99 * 100 = 1098.9999... in JS + expect(dollarsToCents('10.99')).toBe(1099); + }); + }); + + describe('centsToDollars', () => { + it('converts cents to dollar string', () => { + expect(centsToDollars(1599)).toBe('15.99'); + }); + + it('handles zero', () => { + expect(centsToDollars(0)).toBe('0.00'); + }); + + it('pads single digit cents', () => { + expect(centsToDollars(1050)).toBe('10.50'); + }); + }); + + describe('formatCurrency', () => { + it('formats cents as dollar string with $ prefix', () => { + expect(formatCurrency(1599)).toBe('$15.99'); + }); + + it('formats zero', () => { + expect(formatCurrency(0)).toBe('$0.00'); + }); + }); +});