├── .gitignore ├── .browserslistrc ├── .babelrc ├── SECURITY.md ├── rollup.config.js ├── utils └── localstorage.js ├── LICENSE ├── package.json ├── index.js ├── dist ├── cart-localstorage.min.js └── cart-localstorage.min.js.map ├── .github └── workflows │ └── codeql-analysis.yml ├── README.md ├── docs └── index.html └── tests └── index.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 1 version 2 | > 1% 3 | maintained node versions 4 | not dead -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-transform-runtime" 7 | ] 8 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | All | :white_check_mark: | 8 | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Vulnerability issues should be reported directly on the peet86/cart-localstorage repository via github issues. 13 | Expected response time: 72h 14 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import minify from "rollup-plugin-babel-minify"; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import resolve from 'rollup-plugin-node-resolve'; 5 | 6 | export default { 7 | input: './index.js', 8 | output: { 9 | file: './dist/cart-localstorage.min.js', 10 | name: 'cartLS', 11 | format: 'iife', 12 | compact: true, 13 | sourcemap: true, 14 | }, 15 | plugins: [ 16 | babel({ 17 | runtimeHelpers: true, 18 | externalHelpers: true, 19 | }), 20 | resolve(), 21 | commonjs(), 22 | minify({ 23 | comments: false 24 | }) 25 | ] 26 | } -------------------------------------------------------------------------------- /utils/localstorage.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = '__cart' 2 | 3 | let saveListener = null; 4 | export const listen = (cb) => { saveListener = cb }; // ugly but storage listener is not working for the same window.. 5 | 6 | export const list = (key) => JSON.parse(localStorage.getItem(key || STORAGE_KEY)) || []; 7 | 8 | export const save = (data, key) => { 9 | localStorage.setItem(key || STORAGE_KEY, JSON.stringify(data)); 10 | if(saveListener) saveListener(list(key || STORAGE_KEY)) 11 | } 12 | 13 | export const clear = (key) => { 14 | localStorage.removeItem(key || STORAGE_KEY) 15 | if(saveListener) saveListener(list(key || STORAGE_KEY)) 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Peter Varga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cart-localstorage", 3 | "version": "1.1.5", 4 | "description": "Tiny shopping cart on top of LolcalStorage - ES6, < 1Kb, full test coverage", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "rollup -c" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://peet86@github.com/peet86/cart-localstorage.git" 13 | }, 14 | "keywords": [ 15 | "shopping", 16 | "cart", 17 | "cart", 18 | "localstorage", 19 | "js", 20 | "javascript" 21 | ], 22 | "author": "Peter Varga", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/peet86/cart-localstorage/issues" 26 | }, 27 | "publishConfig": { "registry": "https://npm.pkg.github.com/" }, 28 | "homepage": "https://github.com/peet86/cart-localstorage#readme", 29 | "devDependencies": { 30 | "@babel/core": "^7.9.0", 31 | "@babel/plugin-transform-runtime": "^7.9.0", 32 | "@babel/preset-env": "^7.9.5", 33 | "@babel/runtime": "^7.9.2", 34 | "babel-loader": "^8.1.0", 35 | "jest": "^24.9.0", 36 | "rollup": "^1.32.1", 37 | "rollup-plugin-babel": "^4.4.0", 38 | "rollup-plugin-babel-minify": "^9.1.1", 39 | "rollup-plugin-commonjs": "^10.1.0", 40 | "rollup-plugin-node-resolve": "^5.2.0" 41 | }, 42 | "dependencies": {} 43 | } 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { list, save, clear, listen } from './utils/localstorage' 2 | 3 | const get = (id) => list().find((product) => product.id === id) 4 | 5 | const exists = (id) => !!get(id) 6 | 7 | const add = (product, quantity) => isValid(product) ? exists(product.id) ? update(product.id, 'quantity', get(product.id).quantity + (quantity || 1)) : save(list().concat({ ...product, quantity: quantity || 1 })) : null; 8 | 9 | const remove = (id) => save(list().filter((product) => product.id !== id)) 10 | 11 | const quantity = (id, diff) => exists(id) && get(id).quantity + diff > 0 ? update(id, 'quantity', get(id).quantity + diff) : remove(id); 12 | 13 | const update = (id, field, value) => save(list().map((product) => product.id === id ? ({ ...product, [field]: value }) : product)) 14 | 15 | const total = (cb) => list().reduce((sum, product) => isCallback(cb) ? cb(sum, product) : (sum += subtotal(product)), 0); 16 | 17 | const destroy = () => clear() 18 | 19 | const onChange = (cb) => isCallback(cb) ? listen(cb) : console.log(typeof cb) 20 | 21 | 22 | const isValid = (product) => product.id && product.price 23 | 24 | const subtotal = (product) => isCalcable(product) ? (product.price * product.quantity) : 0 25 | 26 | const isCalcable = (product) => (product && product.price && product.quantity) 27 | 28 | const isCallback = (cb) => cb && typeof cb === "function" 29 | 30 | export { list, get, add, remove, update, quantity, total, destroy, exists, subtotal, onChange }; 31 | -------------------------------------------------------------------------------- /dist/cart-localstorage.min.js: -------------------------------------------------------------------------------- 1 | var cartLS=function(a){'use strict';function b(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function c(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function d(a){for(var b,d=1;d 39 | 40 | ``` 41 | 42 | ```javascript 43 | cartLS.add({id: 1, name: "Product 1", price: 100}) 44 | cartLS.add({id: 2, name: "Product 2", price: 100}, 2) 45 | 46 | console.log(cartLS.total()) 47 | // output: 300 48 | ``` 49 | 50 | 51 | 52 | ## Documentation 53 | 54 | #### add(product, [quantity:1]) 55 | 56 | Adds product to the cart. If the product already exists it increases the quantity with 1. 57 | The product object structure is flexible, only "id" and "price" are mandatory properties. 58 | 59 | ```javascript 60 | const myproduct = {id: 3, name: "Vans", price: 75} 61 | add(myproduct, 2) 62 | ``` 63 | 64 | #### get(id) 65 | 66 | Get product from the cart by id 67 | 68 | ```javascript 69 | get(1) 70 | // {id: 1, name: "Nike Air", price: 100, quantity: 1} 71 | ``` 72 | 73 | #### exists(id) 74 | 75 | Checks if the product already exists in the cart 76 | 77 | ```javascript 78 | exists(21) 79 | // true or false 80 | ``` 81 | 82 | #### list() 83 | 84 | Get the content of the cart as an array of products. 85 | 86 | ```javascript 87 | list() 88 | // [{id: 1, name: "Nike Air", price: 100, quantity: 1}, {id: 2, name: "Adidas Superstar", price: 120, quantity: 2}] 89 | ``` 90 | 91 | #### remove(id) 92 | 93 | Removes the product from the cart 94 | 95 | ```javascript 96 | remove(1) 97 | ``` 98 | 99 | #### update(id, property, value) 100 | 101 | Updates the product's property with a certain value. 102 | 103 | ```javascript 104 | update(1,'price',200) 105 | ``` 106 | 107 | #### quantity(id, diff) 108 | 109 | Increase / decrease product's quantity with a positive or negative value. 110 | 111 | ```javascript 112 | quantity(22,-1) // will decrease the quantity of product [id:22] with 1. 113 | ``` 114 | 115 | 116 | #### total([reducer]) 117 | 118 | By default returns with the total price: 119 | 120 | ```javascript 121 | total() 122 | // 220 123 | ``` 124 | or you can pass a custom reducer function to summarize other attributes, like product quantity: 125 | 126 | ```javascript 127 | total((sum, product) => (sum += product.quantity), 0) 128 | ``` 129 | 130 | #### destroy() 131 | 132 | Deletes the cart array from the browser's localStorage. 133 | 134 | ```javascript 135 | destroy() 136 | ``` 137 | 138 | 139 | 140 | ## License 141 | 142 | This library is available under MIT license. 143 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

cart-localstorage

16 | 17 | 18 | Github Repository 19 |
20 |
21 |

Products

22 | 23 |
24 |
25 |
26 |

Nike Air

27 |
28 |
29 |

$100

30 | 33 |
34 |
35 |
36 |
37 |

Adidas Superstar

38 |
39 |
40 |

$120

41 | 44 |
45 |
46 |
47 |
48 |

Puma St Runner

49 |
50 |
51 |

$130

52 | 55 |
56 |
57 |
58 | 59 |
60 |
61 |

Cart ( items)

62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
Total:
77 |
78 |
79 |
80 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /dist/cart-localstorage.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"cart-localstorage.min.js","sources":["../node_modules/@babel/runtime/helpers/defineProperty.js","../node_modules/@babel/runtime/helpers/typeof.js","../utils/localstorage.js","../index.js"],"sourcesContent":["function _defineProperty(obj, key, value) {\n if (key in obj) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n } else {\n obj[key] = value;\n }\n\n return obj;\n}\n\nmodule.exports = _defineProperty;","function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n module.exports = _typeof = function _typeof(obj) {\n return typeof obj;\n };\n } else {\n module.exports = _typeof = function _typeof(obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n }\n\n return _typeof(obj);\n}\n\nmodule.exports = _typeof;","const STORAGE_KEY = '__cart'\n\nlet saveListener = null;\nexport const listen = (cb) => { saveListener = cb }; // ugly but storage listener is not working for the same window..\n\nexport const list = (key) => JSON.parse(localStorage.getItem(key || STORAGE_KEY)) || [];\n\nexport const save = (data, key) => {\n\tlocalStorage.setItem(key || STORAGE_KEY, JSON.stringify(data));\n\tif(saveListener) saveListener(list(key || STORAGE_KEY))\n}\n\nexport const clear = (key) => {\n\tlocalStorage.removeItem(key || STORAGE_KEY)\n\tif(saveListener) saveListener(list(key || STORAGE_KEY))\n}\n","import { list, save, clear, listen } from './utils/localstorage'\n\nconst get = (id) => list().find((product) => product.id === id)\n\nconst exists = (id) => !!get(id)\n\nconst add = (product, quantity) => isValid(product) ? exists(product.id) ? update(product.id, 'quantity', get(product.id).quantity + (quantity || 1)) : save(list().concat({ ...product, quantity: quantity || 1 })) : null;\n\nconst remove = (id) => save(list().filter((product) => product.id !== id))\n\nconst quantity = (id, diff) => exists(id) && get(id).quantity + diff > 0 ? update(id, 'quantity', get(id).quantity + diff) : remove(id);\n\nconst update = (id, field, value) => save(list().map((product) => product.id === id ? ({ ...product, [field]: value }) : product))\n\nconst total = (cb) => list().reduce((sum, product) => isCallback(cb) ? cb(sum, product) : (sum += subtotal(product)), 0);\n\nconst destroy = () => clear()\n\nconst onChange = (cb) => isCallback(cb) ? listen(cb) : console.log(typeof cb)\n\n\nconst isValid = (product) => product.id && product.price\n\nconst subtotal = (product) => isCalcable(product) ? (product.price * product.quantity) : 0\n\nconst isCalcable = (product) => (product && product.price && product.quantity)\n\nconst isCallback = (cb) => cb && typeof cb === \"function\"\n\nexport { list, get, add, remove, update, quantity, total, destroy, exists, subtotal, onChange };\n"],"names":["_defineProperty","obj","key","value","Object","defineProperty","enumerable","configurable","writable","_typeof","module","Symbol","iterator","constructor","prototype","STORAGE_KEY","saveListener","listen","cb","list","JSON","parse","localStorage","getItem","save","data","setItem","stringify","clear","removeItem","get","id","find","product","exists","remove","filter","update","field","map","isValid","price","subtotal","isCalcable","quantity","isCallback","concat","console","log","diff","reduce","sum"],"mappings":"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"} -------------------------------------------------------------------------------- /tests/index.test.js: -------------------------------------------------------------------------------- 1 | 2 | jest.mock('../utils/localstorage.js'); 3 | 4 | import { get, add, destroy, remove, update, subtotal, total, quantity } from '../index' 5 | 6 | import { list, save, clear } from '../utils/localstorage.js'; 7 | 8 | const PRODUCT_1 = { id: 1, name: "1", quantity: 1, price: 10 } 9 | const PRODUCT_1B = { id: 1, name: "b", quantity: 1, price: 10 } 10 | const PRODUCT_2 = { id: 2, name: "2", quantity: 1, price: 20 } 11 | const PRODUCT_3 = { id: 3, name: "3", quantity: 1, price: 30 } 12 | const PRODUCT_4 = { id: 4, name: "4", quantity: 2, price: 30 } 13 | 14 | 15 | describe('Cart', () => { 16 | 17 | describe('get', () => { 18 | 19 | it('should return with one object', () => { 20 | 21 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 22 | 23 | const item = get(1); 24 | 25 | expect(item).toBeInstanceOf(Object); 26 | }); 27 | 28 | it('should return with the first matching product', () => { 29 | 30 | list.mockReturnValue([PRODUCT_1, PRODUCT_1B, PRODUCT_2]); 31 | 32 | const item = get(1); 33 | 34 | expect(item).toEqual(PRODUCT_1); 35 | }); 36 | 37 | it('should return with the correct product', () => { 38 | 39 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 40 | 41 | const item = get(2); 42 | 43 | expect(item).toEqual(PRODUCT_2); 44 | }); 45 | 46 | }); 47 | 48 | describe('list', () => { 49 | 50 | it('should return with all the products', () => { 51 | 52 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 53 | 54 | expect(list()).toHaveLength(2); 55 | 56 | }); 57 | 58 | }); 59 | 60 | describe('add', () => { 61 | 62 | it('should not add product to the cart without id', () => { 63 | 64 | list.mockReturnValue([]); 65 | 66 | add({ name: "c", price: 100 }); 67 | 68 | expect(save).not.toHaveBeenCalled(); 69 | 70 | }); 71 | 72 | it('should not add product to the cart without price', () => { 73 | 74 | list.mockReturnValue([]); 75 | 76 | add({ name: "c", id: 10 }); 77 | 78 | expect(save).not.toHaveBeenCalled(); 79 | 80 | }); 81 | 82 | it('should add the new product to the empty cart', () => { 83 | 84 | list.mockReturnValue([]); 85 | 86 | add(PRODUCT_1); 87 | 88 | expect(save).toBeCalledWith([PRODUCT_1]); 89 | 90 | }); 91 | 92 | 93 | it('should add new product when the product does not exists in the cart ', () => { 94 | 95 | list.mockReturnValue([PRODUCT_1]); 96 | 97 | add(PRODUCT_2); 98 | 99 | expect(save).toBeCalledWith([PRODUCT_1, PRODUCT_2]); 100 | 101 | }); 102 | 103 | it('should increase the quantity when the product exists in the cart ', () => { 104 | 105 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 106 | 107 | add(PRODUCT_2); 108 | 109 | expect(save).toBeCalledWith([PRODUCT_1, { ...PRODUCT_2, quantity: 2 }]); 110 | 111 | }); 112 | 113 | it('should increase the quantity with the second parameter\'s value when the product exists in the cart ', () => { 114 | 115 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 116 | 117 | add(PRODUCT_2, 2); 118 | 119 | expect(save).toBeCalledWith([PRODUCT_1, { ...PRODUCT_2, quantity: 3 }]); 120 | 121 | }); 122 | 123 | it('should add the new product with the second parameter\'s value to the cart', () => { 124 | 125 | list.mockReturnValue([]); 126 | 127 | add(PRODUCT_1, 3); 128 | 129 | expect(save).toBeCalledWith([{ ...PRODUCT_1, quantity: 3 }]); 130 | 131 | }); 132 | 133 | }) 134 | 135 | describe('remove', () => { 136 | 137 | 138 | it('should remove the correct item', () => { 139 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 140 | 141 | remove(1); 142 | 143 | expect(save).toBeCalledWith([PRODUCT_2]); 144 | 145 | }) 146 | 147 | it('should not remove anything when the id is null', () => { 148 | 149 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 150 | 151 | remove(); 152 | 153 | expect(save).toBeCalledWith([PRODUCT_1, PRODUCT_2]); 154 | 155 | }) 156 | 157 | it('should not remove anything when the id is wrong', () => { 158 | 159 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 160 | 161 | remove(5); 162 | 163 | expect(save).toBeCalledWith([PRODUCT_1, PRODUCT_2]); 164 | 165 | }) 166 | }) 167 | 168 | describe('update', () => { 169 | it('should update existing product\'s quantity', () => { 170 | 171 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 172 | 173 | update(1, "quantity", 5); 174 | expect(save).toBeCalledWith([{ ...PRODUCT_1, quantity: 5 }, PRODUCT_2]); 175 | 176 | }) 177 | 178 | it('should do nothing when the product does not exists', () => { 179 | 180 | list.mockReturnValue([PRODUCT_1]); 181 | 182 | update(2, "quantity", 3); 183 | 184 | expect(save).toBeCalledWith([PRODUCT_1]); 185 | 186 | }) 187 | 188 | it('should do nothing when the value is negative', () => { 189 | 190 | list.mockReturnValue([PRODUCT_1]); 191 | 192 | update(1, "quantity", -1); 193 | expect(save).toBeCalledWith([PRODUCT_1]); 194 | 195 | }) 196 | 197 | 198 | }) 199 | 200 | 201 | describe('quantity', () => { 202 | it('should increase existing product\'s quantity by 2', () => { 203 | 204 | list.mockReturnValue([PRODUCT_4]); 205 | 206 | quantity(4, 2); 207 | expect(save).toBeCalledWith([{ ...PRODUCT_4, quantity: 4 }]); 208 | 209 | }) 210 | 211 | it('should decrease existing product\'s quantity by 1', () => { 212 | 213 | list.mockReturnValue([PRODUCT_4]); 214 | 215 | quantity(4, -1); 216 | 217 | expect(save).toBeCalledWith([{ ...PRODUCT_4, quantity: 1 }]); 218 | 219 | }) 220 | 221 | it('should remove existing product when quantity diff decreases new quantity bellow 0', () => { 222 | 223 | list.mockReturnValue([PRODUCT_4]); 224 | 225 | quantity(4, -5); 226 | expect(save).toBeCalledWith([]); 227 | }) 228 | 229 | it('should remove existing product when quantity diff lowers quantity to 0', () => { 230 | 231 | list.mockReturnValue([PRODUCT_4]); 232 | 233 | quantity(4, -2); 234 | expect(save).toBeCalledWith([]); 235 | }) 236 | 237 | 238 | }) 239 | 240 | describe('destroy', () => { 241 | 242 | it('should clear localStorage', () => { 243 | 244 | destroy(); 245 | 246 | expect(clear).toBeCalled(); 247 | 248 | }) 249 | 250 | }) 251 | 252 | describe('subtotal', () => { 253 | 254 | it('should be equal with price * quantity', () => { 255 | 256 | expect(subtotal(PRODUCT_1)).toBe(10); 257 | 258 | }) 259 | 260 | it('should return 0 when price is missing ', () => { 261 | 262 | expect(subtotal({ quantity: 1 })).toBe(0); 263 | 264 | }) 265 | 266 | it('should return 0 when quantity is missing ', () => { 267 | 268 | expect(subtotal({ price: 1 })).toBe(0); 269 | 270 | }) 271 | 272 | it('should return 0 when the product is missing ', () => { 273 | 274 | expect(subtotal()).toBe(0); 275 | 276 | }) 277 | 278 | }) 279 | 280 | 281 | describe('total', () => { 282 | 283 | it('should return with cart total price', () => { 284 | 285 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 286 | 287 | expect(total()).toBe(30); 288 | 289 | }) 290 | 291 | it('should return 0 when cart is empty ', () => { 292 | 293 | list.mockReturnValue([]); 294 | 295 | expect(total()).toBe(0); 296 | 297 | }) 298 | 299 | it('should accept custom accumulator', () => { 300 | 301 | list.mockReturnValue([PRODUCT_1, PRODUCT_2]); 302 | 303 | const acc = (sum, product) => (sum += subtotal(product)) 304 | 305 | expect(total(acc)).toBe(30); 306 | 307 | }) 308 | 309 | }) 310 | }); --------------------------------------------------------------------------------