A code sample from a mobile app
//Item | |
import { h } from 'preact'; | |
import { appMethodsToProps } from '../appData'; | |
import { classNames } from '../../util'; | |
export default appMethodsToProps(({ clickOnItem, index, itemName, cost, selected, alreadyPaid })=> ( | |
<button | |
onClick={ ()=>clickOnItem(index) } | |
disabled={alreadyPaid} | |
className={classNames({ | |
'list-group-item': true, | |
'list-group-item-action': true, | |
active: !alreadyPaid && selected | |
})} | |
> | |
<span> { itemName } </span> | |
<span className='float-right'> ${ cost } </span> | |
</button> | |
)); |
//Item Component | |
import { h } from 'preact'; | |
import Item from './item'; | |
import { appStateToProps } from '../appData'; | |
export default appStateToProps(({ items = []})=> ( | |
<section className='card'> | |
<h3 className='card-header'>Items</h3> | |
<ul class="list-group"> | |
{items.map((item, index)=><li><Item {...item} index={ index } /></li>)} | |
</ul> | |
</section> | |
)); |
//This is code from a pay at your table app I worked on. This is the code to support display of items on the bill. | |
import { addAppMethodsWithState } from './bindings'; | |
import { pushAtPath, clearAtPath } from '../../firebase'; | |
export const clickOnItem = ({ items = [] }, index)=>{ | |
const modItemsArray = selectItem(items, index); | |
return itemsArrayToState(modItemsArray); | |
}; | |
export const selectItem = (items = [], index)=>{ | |
if(index !== undefined && index < items.length && typeof items[index] === 'object'){ | |
const moddedItems = [...items]; | |
const clickedItem = {...moddedItems[index]}; | |
clickedItem.selected = !clickedItem.selected; | |
moddedItems[index] = clickedItem; | |
return moddedItems; | |
} | |
return null; | |
}; | |
export const addItem = ({ url }, item)=>{ | |
const path = `${url.firebase}/items`; | |
pushAtPath(path, item); | |
}; | |
export const clearTable = ({ url })=>{ | |
const path = `${url.firebase}`; | |
clearAtPath(path); | |
}; | |
export const calcTotalBill = (itemsArray)=>{ | |
return itemsArray.reduce((total, { cost })=> Number(cost) + total, 0); | |
}; | |
export const calcPayingFor = (itemsArray)=>{ | |
const unpaidArray = itemsArray.filter(({ alreadyPaid })=> !alreadyPaid); | |
const anySelected = unpaidArray.some(({ selected })=> selected); | |
if(anySelected){ | |
return unpaidArray.reduce((total, { selected, cost })=>{ | |
if(selected){ | |
return total + Number(cost); | |
} | |
return total; | |
}, 0); | |
} | |
return unpaidArray.reduce((total, { cost })=> Number(cost) + total, 0); | |
}; | |
export const calcAlreadyPaid = (itemsArray)=>{ | |
return itemsArray | |
.filter(({ alreadyPaid })=> alreadyPaid) | |
.reduce((total, { cost })=> total + Number(cost), 0); | |
}; | |
export const collectPayingForItems = (itemsArray)=>{ | |
const unpaidArray = itemsArray.filter(({ alreadyPaid })=> !alreadyPaid); | |
const anySelected = unpaidArray.some(({ selected })=> selected); | |
if (anySelected){ | |
return unpaidArray.filter(({ selected })=> selected ); | |
} | |
return unpaidArray; | |
}; | |
export const itemsArrayToState = (itemsArray)=>{ | |
if(itemsArray === null){ | |
return { | |
items: null, | |
alreadyPaid: null, | |
totalBill: null, | |
payingFor: null, | |
}; | |
} | |
return { | |
items: itemsArray, | |
alreadyPaid: calcAlreadyPaid(itemsArray), | |
totalBill: calcTotalBill(itemsArray), | |
payingFor: calcPayingFor(itemsArray) | |
}; | |
}; | |
addAppMethodsWithState({ clickOnItem, addItem, clearTable }); | |
//This was test driven, so these were written first. | |
jest.mock('../../firebase'); | |
import { pushAtPath, clearAtPath } from '../../firebase'; | |
import { selectItem, addItem, clearTable, calcTotalBill, calcPayingFor, calcAlreadyPaid } from './items'; | |
let bill1, bill2, bill3; | |
let stringBill; | |
beforeAll(()=>{ | |
stringBill = [ | |
{cost: '1'}, | |
{cost: '2'}, | |
{cost: '4', alreadyPaid: true}, | |
]; | |
bill1 = [ | |
{cost: 1}, | |
{cost: 2}, | |
{cost: 4, alreadyPaid: true}, | |
]; | |
bill2 = [ | |
{cost: 1, selected: true}, | |
{cost: 2, alreadyPaid: true, selected: true}, | |
{cost: 4} | |
]; | |
bill3 = [ | |
{cost: 1, selected: false, alreadyPaid: true}, | |
{cost: 2, selected: true, alreadyPaid: true}, | |
{cost: 4} | |
]; | |
}); | |
test('selectItem should add selected to an item if it doesn\'t exist', ()=>{ | |
const items = [{name: 'pad-see-ew', cost: 10}, {name: 'thai tea', cost: 3.5}] | |
const modItems = selectItem(items, 0); | |
expect(modItems).toEqual([ | |
{name: 'pad-see-ew', cost: 10, selected: true}, | |
{name: 'thai tea', cost: 3.5} | |
]); | |
}); | |
test('selectItem should flip the selected state', ()=>{ | |
const items = [ | |
{name: 'pad-see-ew', cost: 10, selected: false}, | |
{name: 'thai tea', cost: 3.5, selected: true} | |
]; | |
const firstClick = selectItem(items, 0); | |
const secondClick = selectItem(firstClick, 1); | |
expect(secondClick).toEqual([ | |
{name: 'pad-see-ew', cost: 10, selected: true}, | |
{name: 'thai tea', cost: 3.5, selected: false} | |
]); | |
}); | |
test('selectItem should return null if you give it bad info', ()=>{ | |
const items = [ | |
{name: 'pad-see-ew', cost: 10, selected: false}, | |
{name: 'thai tea', cost: 3.5, selected: true} | |
]; | |
const clicked = selectItem(items, 4); | |
expect(clicked).toBeNull(); | |
}); | |
test('addItem should push the item to db based on location', ()=>{ | |
const location = '/restaurant/thainakorn/1/chain/1/table/3'; | |
const state = { | |
url:{ | |
firebase: location | |
} | |
}; | |
const item = { | |
name: 'thaiTea', | |
cost: 3.5 | |
}; | |
addItem(state, item); | |
expect(pushAtPath).toHaveBeenCalledWith(`${location}/items`, item); | |
}); | |
test('clearTable should clear items from db based on location', ()=>{ | |
const location = '/restaurant/thainakorn/1/chain/1/table/3'; | |
const state = { | |
url:{ | |
firebase: location | |
} | |
}; | |
clearTable(state); | |
expect(clearAtPath).toHaveBeenCalledWith(location); | |
}); | |
test('totalBill should calculate the full cost of all items',()=>{ | |
const total1 = calcTotalBill(bill1); | |
const total2 = calcTotalBill(bill2); | |
const total3 = calcTotalBill(bill3); | |
expect(total1).toEqual(7); | |
expect(total2).toEqual(7); | |
expect(total3).toEqual(7); | |
}); | |
test('totalBill should handle string numbers', ()=>{ | |
const total4 = calcTotalBill(stringBill); | |
expect(total4).toEqual(7); | |
}); | |
test('payingFor should default to everything unpaid', ()=>{ | |
const payingFor1 = calcPayingFor(bill1); | |
expect(payingFor1).toEqual(3); | |
}); | |
test('payingFor should not take into account things already paid',()=>{ | |
const payingFor2 = calcPayingFor(bill2); | |
const payingFor3 = calcPayingFor(bill3); | |
expect(payingFor2).toEqual(1); | |
expect(payingFor3).toEqual(4); | |
}); | |
test('payingFor should handle string numbers', ()=>{ | |
const payingFor4 = calcPayingFor(stringBill); | |
expect(payingFor4).toEqual(3); | |
}); | |
test('alreadyPaid should calculate the total of what has already been paid', ()=>{ | |
const alreadyPaid3 = calcAlreadyPaid(bill3); | |
expect(alreadyPaid3).toEqual(3); | |
}); | |
test('alreadyPaid should handle string numbers', ()=>{ | |
const alreadyPaid4 = calcAlreadyPaid(stringBill); | |
expect(alreadyPaid4).toEqual(4); | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment