├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── docs └── future.md ├── index.js ├── package.json └── test ├── add.js ├── index.js ├── remove.js └── where.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | scripts/parsecsv/node_modules 3 | app/node_modules/public/js/vendor/html5shiv.js 4 | app/node_modules/public/js/bundle.js 5 | app/node_modules/public/js/bundle.map.json 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr": 10, 3 | "node": true, 4 | "browser": true, 5 | "browserify": true, 6 | "typed": true, 7 | "worker": true, 8 | "predef": ["define", "require"], 9 | "camelcase": false, 10 | "curly": true, 11 | "eqeqeq": true, 12 | "eqnull": true, 13 | "forin": true, 14 | "freeze": true, 15 | "immed": true, 16 | "indent": 4, 17 | "latedef": true, 18 | "newcap": true, 19 | "noarg": true, 20 | "noempty": false, 21 | "nonbsp": false, 22 | "nonew": true, 23 | "plusplus": false, 24 | "quotmark": false, 25 | "undef": true, 26 | "unused": true, 27 | "strict": true, 28 | "maxparams": 5, 29 | "maxdepth": 5, 30 | "maxstatements": false, 31 | "maxcomplexity": 5, 32 | "validthis": true, 33 | "maxlen": 160 34 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | script: "npm run validate" 5 | notifications: 6 | email: false 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Eric Elliott 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # object-list [![Build Status](https://travis-ci.org/therealklanni/object-list.svg?branch=travis)](https://travis-ci.org/ericelliott/object-list) 2 | 3 | Treat arrays of objects like a db you can query. A single object from an `object-list` is called a record. 4 | 5 | ## Status - DEPRECATED - Use [lenses](https://medium.com/javascript-scene/lenses-b85976cb0534) or [transducers](https://medium.com/javascript-scene/transducers-efficient-data-processing-pipelines-in-javascript-7985330fe73d) instead. (Both abstract away from the underlying data structures). 6 | 7 | ## A common API for object collections 8 | 9 | You may be scratching your head right now and wondering how this is different from Underscore, Lodash, or Rx Observables. Astute observations. The implementation will likely lean heavily on both Lodash and Rx Observables. 10 | 11 | The difference is that this is intended to provide a universal facade for many types of object collections. It is an interface contract that will hopefully support a number of modular collection adapters. Read on for more details. 12 | 13 | 14 | ## Status 15 | 16 | Developer preview. 17 | 18 | 19 | ## API Design Goals 20 | 21 | Only a few of these design goals have been met in the current implementation, so read this section like everything is prefixed with "eventually..." See [future](https://github.com/ericelliott/object-list/blob/master/docs/future.md). 22 | 23 | * A common API for object collections (e.g. arrays of objects). Adapters for: 24 | - Array 25 | - Immutable.js List 26 | - Rx Observable 27 | - Siren-Resource API 28 | - Mongo client 29 | - Redis client 30 | * object-lists are immutable. Instead of mutating the source data, a new object-list will be returned. 31 | * Completely modular. Enable `require()` at the function level similar to `require('lodash/object/assign')`, etc... Compatibility transforms for things that aren't required for hard dependencies should also be separate modules. Minimize browserify bundle weight. 32 | * Node or Browser. 33 | * Provide ES6 `Array.prototype` compatible API. Almost anything that takes an array as input should be able to take an object-list, as well, provided the API does not rely on array mutation. 34 | * Use ES6 and make compiled ES5 version available on npm. 35 | * Compatible with infinite streams and generators. 36 | * Should be capable of sync or async. Add `.subscribe()` method for async results. 37 | 38 | 39 | ## Getting Started 40 | 41 | Install: 42 | 43 | ``` 44 | $ npm install object-list 45 | ``` 46 | 47 | Use: 48 | 49 | ```js 50 | var list = require('object-list'); 51 | 52 | var records = [{a: 'a'}, {b: 'b'},{c: 'c'}]; 53 | 54 | list(records).getByKey('a'); // -> 'a' 55 | ``` 56 | 57 | 58 | ## .getByKey() 59 | 60 | Take a key name and return the first value found. Returns a single value, not an array. 61 | 62 | ### Usage 63 | 64 | list(records).getByKey(key) -> val 65 | 66 | ```js 67 | var 68 | records = [ 69 | {a: 'a'}, 70 | {b: 'b'}, 71 | {c: 'c'} 72 | ], 73 | expected = 'a', 74 | copy = slice.call(records), 75 | 76 | val = list(records).getByKey('a'); 77 | 78 | assert.equal(val, expected, 79 | 'should return the first value it finds.'); 80 | 81 | assert.equal(Array.isArray(val), false, 82 | 'should only return a single result, not an array.'); 83 | 84 | assert.deepEqual(records, copy, 85 | 'should not alter original list.'); 86 | ``` 87 | 88 | 89 | ## .whitelist() 90 | 91 | Take a list of keys and return a filtered list of records which contain those keys. Exclude any records that don't contain any of the whitelisted keys. 92 | 93 | ### Usage 94 | 95 | list(records).whitelist(whitelist) -> records 96 | 97 | ```js 98 | var 99 | records = [ 100 | {a: 'a'}, 101 | {b: 'b'}, 102 | {c: 'c'}, 103 | {d: 'd', a: 'a'} 104 | ], 105 | copy = slice.call(records), 106 | 107 | whitelisted = list(records).whitelist(['a', 'c']), 108 | 109 | expected = [ 110 | {a: 'a'}, 111 | {c: 'c'}, 112 | {d: 'd', a: 'a'} 113 | ], 114 | 115 | missing = list(expected).getByKey('b'); 116 | 117 | assert.deepEqual(whitelisted, expected, 118 | 'should contain all the expected keys.'); 119 | 120 | assert.strictEqual(missing, undefined, 121 | 'should not contain excluded keys'); 122 | 123 | assert.deepEqual(records, copy, 124 | 'should not alter original list.'); 125 | ``` 126 | 127 | 128 | ## .concat() 129 | 130 | Mix a list into a single record. 131 | 132 | ### Usage 133 | 134 | list(records).concat() -> record 135 | 136 | ```js 137 | var 138 | records = [ 139 | {a: 'a'}, 140 | {b: 'b'}, 141 | {c: 'c'}, 142 | {c: 'override'} 143 | ], 144 | copy = slice.call(records), 145 | 146 | record = list(records).concat(), 147 | 148 | expected = { 149 | a: 'a', 150 | b: 'b', 151 | c: 'override' 152 | }; 153 | 154 | assert.deepEqual(record, expected, 155 | 'should combine records similar to Object.assign()'); 156 | 157 | assert.deepEqual(records, copy, 158 | 'should not alter original list.'); 159 | ``` 160 | 161 | 162 | ## .where() 163 | 164 | Select all records which match the given predicate pair. 165 | 166 | ### Usage 167 | 168 | list(records).where({key: value}) -> records 169 | 170 | ```js 171 | // Find all orders by customer email 172 | var records = [ 173 | { 174 | "id": "ci6r6aliv00007poxc2zgnjvf", 175 | "date": "2014-12-30 05:29:28", 176 | "billingEmail": "dennis@example.com", 177 | "firstName": "Dennis", 178 | "lastName": "Chambers", 179 | "lineItems": { 180 | "name": "Zildjian K Custom Organic Ride - 21\"", 181 | "sku": "h617xrh", 182 | "quantity": "1", 183 | "total": "379.95" 184 | } 185 | }, 186 | { 187 | "id": "ci6r6aliv00007poxc2zgnjvf", 188 | "date": "2014-12-30 05:29:28", 189 | "billingEmail": "carlos@example.com", 190 | "firstName": "Carlos", 191 | "lastName": "Santana", 192 | "lineItems": { 193 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 194 | "sku": "sc37x3m", 195 | "quantity": "1", 196 | "total": "3999.00" 197 | } 198 | }, 199 | { 200 | "id": "ci6r6aliv00007poxc2zgnjvf", 201 | "date": "2014-12-30 05:29:28", 202 | "billingEmail": "dennis@example.com", 203 | "firstName": "Dennis", 204 | "lastName": "Chambers", 205 | "lineItems": { 206 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 207 | "sku": "sc37x3m", 208 | "quantity": "1", 209 | "total": "379.95" 210 | } 211 | } 212 | ], 213 | copy = slice.call(records), 214 | 215 | result = list(records).where({ 216 | billingEmail: 'dennis@example.com' 217 | }), 218 | 219 | expected = [ 220 | { 221 | "id": "ci6r6aliv00007poxc2zgnjvf", 222 | "date": "2014-12-30 05:29:28", 223 | "billingEmail": "dennis@example.com", 224 | "firstName": "Dennis", 225 | "lastName": "Chambers", 226 | "lineItems": { 227 | "name": "Zildjian K Custom Organic Ride - 21\"", 228 | "sku": "h617xrh", 229 | "quantity": "1", 230 | "total": "379.95" 231 | } 232 | }, 233 | { 234 | "id": "ci6r6aliv00007poxc2zgnjvf", 235 | "date": "2014-12-30 05:29:28", 236 | "billingEmail": "dennis@example.com", 237 | "firstName": "Dennis", 238 | "lastName": "Chambers", 239 | "lineItems": { 240 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 241 | "sku": "sc37x3m", 242 | "quantity": "1", 243 | "total": "379.95" 244 | } 245 | } 246 | ]; 247 | 248 | 249 | assert.deepEqual(result, expected, 250 | 'should contain all the expected records.'); 251 | 252 | assert.equal(result.length, 2, 253 | 'should not contain excluded records.'); 254 | 255 | assert.deepEqual(records, copy, 256 | 'should not alter original list.'); 257 | ``` 258 | 259 | 260 | ## Courses 261 | 262 | This was written for the ["Learn JavaScript with Eric Elliott"](https://ericelliottjs.com/) course series. 263 | 264 | Students will get a series of short videos, lots of interactive lessons explaining concepts in-depth, the ability to help and learn from each other, and a lot more. 265 | 266 | Here's a sneak peek at our members-only site: 267 | 268 | ![Course homepage](https://cloud.githubusercontent.com/assets/364727/6434012/b3ff7a04-c03b-11e4-9b33-51889c74036f.png) 269 | 270 | ![Student profile](https://cloud.githubusercontent.com/assets/364727/6434016/c7a0b802-c03b-11e4-9f4b-867464bd88c6.png) 271 | 272 | 273 | # [Learn JavaScript](https://ericelliottjs.com/) 274 | -------------------------------------------------------------------------------- /docs/future.md: -------------------------------------------------------------------------------- 1 | # Future 2 | 3 | ## How should we trigger async? 4 | 5 | By default, the methods should be sync so that you can easily replace arrays with object-lists, but since a lot of what we do in JS is async, it should be trivial to trigger async mode and magically be compatible with infinite streams, generators, asyncronous event listeners, and so on. 6 | 7 | 8 | ### 'async' flag 9 | 10 | Normally, methods are synchronous: 11 | 12 | ```js 13 | let myList = list([{a: 1}, {b: 2}, {c: 3}]) 14 | .reverse(); // [{c: 3}, {b: 2}, {a: 1}] 15 | ``` 16 | 17 | But what happens when you're dealing with asynchronous data sources? 18 | 19 | ```js 20 | let myList = list({ list: apiResource, async: true }) 21 | .reverse() 22 | .subscribe(onNext, onError, onCompleted); 23 | ``` 24 | 25 | When you pass in a siren-resource object, an API error (such as 400 - 500 range status codes) should trigger `onError()` instead of `onNext()`, so it should probably throw an error if you omit the `onError()` handler. If you don't want anything to happen, pass in a no-op function. 26 | 27 | Let's pass a lambda into the next handler: 28 | 29 | ```js 30 | // apiResource normally returns [{a: 1}, {b: 2}, {c: 3}] 31 | // for GET operation 32 | let myList = list({ list: apiResource, async: true }) 33 | .reverse() 34 | .subscribe(function onNext(item) { 35 | console.log(item); 36 | }, onError, onCompleted); 37 | 38 | // {"c":3} 39 | // {"b":2} 40 | // {"a":1} 41 | ``` 42 | 43 | 44 | ## Method Ideas 45 | 46 | * All of ES6 `Array.prototype`, spec compatible. 47 | * `.push(record)`, `.add(record)` - Append the record to the list. 48 | * `.remove(obj)` – Removes an item from the collection if it looks like the passed-in object (deepEqual). 49 | * `.removeWhere({key1: value, key2: value2...})` - Remove all objects that match the key:value where clauses. 50 | * `.removeSlice(startIndex, endIndex)` – Filter out a range of indexes. Basically the opposite of `.slice()` (return the filtered list instead of the sliced out subset). 51 | * `.distinct(predicate)` - Returns only distinct records which satisfy the predicate function. 52 | * `.distinctWhere({key: value, key2: value2})` - Returns only distinct records which match all supplied where clauses. 53 | * `.sortBy({foo: 'descending'}, [fn])` – Sorts the list based on the property passed in using optional `[].sort()` compatible custom sort function. 54 | * `.at(index)`, `.recordAt(index)` – Returns the record at `index`. 55 | * `.minBy('key', [fn])` - Return min record by key value using optional `[].sort()` compatible custom sort function. 56 | * `.maxBy('key', [fn])` - Return max record by key value using optional `[].sort()` compatible custom sort function. 57 | * `.orDefault(defaultList)` - Returns the list it receives, unless that list is empty, in which case it returns the default list passed in. 58 | * `.first()` - Alias of `.find()`. 59 | * `.last()` - Like `.first()` / `.find()`, but return the last matching record. 60 | * `.head([n])` - Return the first record or first `n` records. 61 | * `.tail([n])` - Return the last record, or last `n` records. 62 | * `.reverse()` - Return the whole list in reverse order (last to first). 63 | * `.loop([cycles])` - Returns all values and then starts over from the beginning to produce an infinitely repeating stream. Will cause an infinite loop if `cylces` is omitted. 64 | * `.pingPong([cycles])` - Like loop, but when it reaches the end, it reverses order and plays back values from last to first. When it reaches the beginning, it reverses order again and plays back values from first to last... Will cause an infinite loop if `cylces` is omitted. 65 | * `.repeat(records [, n])` - Repeat supplied records `n` times. 66 | * `.skip(n, [firstIndex])` - Returns a list of one record for every n records. `firstIndex` defaults to 0. For instance, `list([a, b, c, d, e, f, g]).skip(2)` returns `list([a, d, g])`. 67 | * `.includes()` – Currently experimental for [`Array.prototype` (ES7)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). See [`lodash .includes()`](https://lodash.com/docs#includes). 68 | * `.count()` - Basically like `[].length`. Also support `[].length` with getter for array compat. 69 | * `.toJSON()` – A safe `JSON.stringify()` that does not throw. See [json-stringify-safe](https://github.com/isaacs/json-stringify-safe). Pass in `{ canThrow: true }` 70 | * `.toArray()` - Return as Array. 71 | * `.toImmutable()` - Return as Immutable.List. 72 | * `.toObservable()` - Return as RxJS Observable. 73 | * `.toGenerator()` - Return ES6 generator function if runtime supports it. 74 | * `.toNodeStream()` - Return collection as a Node stream. 75 | * `.pipe(nodeStream, options)` - Shorthand for `.toNodeStream().pipe(nodeStream, options)` 76 | 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | Rx = require('rx'), 5 | assert = require('assert'), 6 | lodash = require('lodash'), 7 | cWhere = require('lodash').where; 8 | 9 | var 10 | assign = lodash.assign, 11 | getKeys = Object.keys; 12 | 13 | var 14 | createObserver = function createObserver(source) { 15 | return function () { 16 | var observer = Rx.Observer.create.apply(Rx.Observer, arguments); 17 | return source.subscribe(observer); 18 | }; 19 | }; 20 | 21 | var 22 | objectList, 23 | 24 | fnVersions = { 25 | sync: { 26 | add: function add(collection, records) { 27 | var newCollection = collection.concat(records); 28 | 29 | return objectList(newCollection); 30 | }, 31 | remove: function remove(collection, record) { 32 | var newCollection = lodash.cloneDeep(collection); 33 | 34 | lodash.remove(newCollection, 35 | lodash.partial(lodash.isEqual, record)); 36 | 37 | return objectList(newCollection); 38 | }, 39 | removeWhere: function removeWhere(collection, query) { 40 | var recordsToRemove = cWhere(collection, query), 41 | newCollection = lodash.reject(collection, 42 | lodash.partial(lodash.find, recordsToRemove)); 43 | 44 | return objectList(newCollection); 45 | }, 46 | removeSlice: function removeSlice(collection, start, end) { 47 | var newCollection = lodash(collection).cloneDeep(), 48 | rest = newCollection.slice(end || newCollection.length); 49 | 50 | newCollection.length = start < 0 ? newCollection.length + start : start; 51 | [].push.apply(newCollection, rest); 52 | 53 | return objectList(newCollection); 54 | } 55 | }, 56 | async: { 57 | add: function addAsync(collection, records) { 58 | var 59 | newCollection = objectList(collection), 60 | 61 | // README: this is a temporary workaround to be replaced with 62 | // .fromNodeCallback() or similar method when adapters get implemented 63 | source = Rx.Observable.from(fnVersions.sync.add(collection, records).toArray()); 64 | 65 | newCollection.subscribe = createObserver(source); 66 | 67 | return newCollection; 68 | }, 69 | remove: function removeAsync(collection, record) { 70 | var 71 | newCollection = objectList(collection), 72 | 73 | // README: this is a temporary workaround to be replaced with 74 | // .fromNodeCallback() or similar method when adapters get implemented 75 | source = Rx.Observable.from(fnVersions.sync.remove(collection, record).toArray()); 76 | 77 | newCollection.subscribe = createObserver(source); 78 | 79 | return newCollection; 80 | }, 81 | removeWhere: function removeWhereAsync(collection, query) { 82 | var 83 | newCollection = objectList(collection), 84 | 85 | // README: this is a temporary workaround to be replaced with 86 | // .fromNodeCallback() or similar method when adapters get implemented 87 | source = Rx.Observable.from(fnVersions.sync.removeWhere(collection, query).toArray()); 88 | 89 | newCollection.subscribe = createObserver(source); 90 | 91 | return newCollection; 92 | }, 93 | removeSlice: function removeSliceAsync(collection, start, end) { 94 | var 95 | newCollection = objectList(collection), 96 | 97 | // README: this is a temporary workaround to be replaced with 98 | // .fromNodeCallback() or similar method when adapters get implemented 99 | source = Rx.Observable.from(fnVersions.sync.removeSlice(collection, start, end).toArray()); 100 | 101 | newCollection.subscribe = createObserver(source); 102 | 103 | return newCollection; 104 | } 105 | } 106 | }, 107 | 108 | getByKey = function getByKey (collection, key) { 109 | return lodash(collection).pluck(key).first(); 110 | }, 111 | 112 | whitelist = function whitelist (collection, keyWhitelist) { 113 | assert(typeof collection.filter === 'function', 114 | 'collection should supply .filter() method.'); 115 | 116 | assert(typeof keyWhitelist.indexOf === 'function', 117 | 'keyWhitelist should supply .indexOf method.'); 118 | 119 | var whitelisted = collection.filter(function (record) { 120 | var keys = getKeys(record), 121 | 122 | result = keys.filter(function (key) { 123 | return keyWhitelist.indexOf(key) >= 0; 124 | }); 125 | 126 | return Boolean(result.length); 127 | }); 128 | 129 | return whitelisted; 130 | }, 131 | 132 | concat = function concat (collection) { 133 | return assign.apply(null, [{}].concat(collection)); 134 | }, 135 | 136 | where = function where (collection, query) { 137 | return cWhere(collection, query); 138 | }; 139 | 140 | objectList = function objectList (options) { 141 | var 142 | collection = options.async ? options.list : options, 143 | version = options.async ? 'async' : 'sync', 144 | 145 | api = { 146 | getByKey: function (key) { 147 | return getByKey.apply(null, [collection, key]); 148 | }, 149 | whitelist: function (keyWhitelist) { 150 | return whitelist.apply(null, [collection, keyWhitelist]); 151 | }, 152 | concat: function () { 153 | return concat(collection); 154 | }, 155 | where: function (keyWhitelist) { 156 | return where.apply(null, [collection, keyWhitelist]); 157 | }, 158 | add: function (records) { 159 | return fnVersions[version].add.apply(null, [collection, records]); 160 | }, 161 | push: function () { 162 | return api.add.apply(null, arguments); 163 | }, 164 | remove: function (records) { 165 | return fnVersions[version].remove.apply(null, [collection, records]); 166 | }, 167 | removeWhere: function (query) { 168 | return fnVersions[version].removeWhere.apply(null, [collection, query]); 169 | }, 170 | removeSlice: function (start, end) { 171 | return fnVersions[version].removeSlice.apply(null, [collection, start, end]); 172 | }, 173 | toArray: function () { 174 | return lodash.cloneDeep(collection); 175 | }, 176 | get length () { 177 | return collection.length; 178 | } 179 | }; 180 | 181 | return api; 182 | }; 183 | 184 | assign(objectList, { 185 | getByKey: getByKey, 186 | whitelist: whitelist, 187 | concat: concat, 188 | where: where 189 | }); 190 | 191 | module.exports = objectList; 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object-list", 3 | "version": "1.4.0", 4 | "description": "Treat arrays of objects like a db you can query.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape test/*.js", 8 | "lint": "jshint .", 9 | "validate": "npm run lint && npm test && npm outdated --depth 0", 10 | "dev": "watch --wait=8 'npm run validate' ." 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:ericelliott/object-list.git" 15 | }, 16 | "keywords": [ 17 | "array", 18 | "collection", 19 | "list" 20 | ], 21 | "author": "Eric Elliott", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/ericelliott/object-list/issues" 25 | }, 26 | "homepage": "https://github.com/ericelliott/object-list", 27 | "devDependencies": { 28 | "jshint": "2.6.3", 29 | "tape": "4.0.0", 30 | "watch": "0.16.0" 31 | }, 32 | "dependencies": { 33 | "lodash": "3.6.0", 34 | "rx": "^2.5.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/add.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | test = require('tape'), 5 | list = require('../index.js'); 6 | 7 | var 8 | slice = [].slice; 9 | 10 | test('.add() sync operation', function (assert) { 11 | var records = [ 12 | { 13 | "id": "ci6r6aliv00007poxc2zgnjvf", 14 | "date": "2014-12-30 05:29:28", 15 | "billingEmail": "dennis@example.com", 16 | "firstName": "Dennis", 17 | "lastName": "Chambers", 18 | "lineItems": { 19 | "name": "Zildjian K Custom Organic Ride - 21\"", 20 | "sku": "h617xrh", 21 | "quantity": "1", 22 | "total": "379.95" 23 | } 24 | }, 25 | { 26 | "id": "ci6r6aliv00007poxc2zgnjvf", 27 | "date": "2014-12-30 05:29:28", 28 | "billingEmail": "carlos@example.com", 29 | "firstName": "Carlos", 30 | "lastName": "Santana", 31 | "lineItems": { 32 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 33 | "sku": "sc37x3m", 34 | "quantity": "1", 35 | "total": "3999.00" 36 | } 37 | }, 38 | { 39 | "id": "ci6r6aliv00007poxc2zgnjvf", 40 | "date": "2014-12-30 05:29:28", 41 | "billingEmail": "dennis@example.com", 42 | "firstName": "Dennis", 43 | "lastName": "Chambers", 44 | "lineItems": { 45 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 46 | "sku": "sc37x3m", 47 | "quantity": "1", 48 | "total": "379.95" 49 | } 50 | } 51 | ], 52 | copy = slice.call(records), 53 | 54 | record = { 55 | id: 'ci6r6aliv00008poxc2zgnjvf', 56 | date: '2014-12-30 05:29:28', 57 | billingEmail: 'dennis@example.com', 58 | firstName: 'Dennis', 59 | lastName: 'Chambers', 60 | lineItems: { 61 | name: 'Zildjian K Custom Organic Ride - 21"', 62 | sku: 'h123xrh', 63 | quantity: '1', 64 | total: '379.95' 65 | } 66 | }, 67 | 68 | result = list(records).add(record), 69 | 70 | expected = result.where({ id: 'ci6r6aliv00008poxc2zgnjvf' })[0]; 71 | 72 | assert.deepEqual(record, expected, 73 | 'should contain the new record'); 74 | 75 | assert.deepEqual(records, copy, 76 | 'should not alter original list'); 77 | 78 | assert.end(); 79 | }); 80 | 81 | test('.add() async operation', function (assert) { 82 | assert.plan(4); 83 | 84 | var 85 | records = [ 86 | { 87 | "id": "ci6r6aliv00007poxc2zgnjvf", 88 | "date": "2014-12-30 05:29:28", 89 | "billingEmail": "dennis@example.com", 90 | "firstName": "Dennis", 91 | "lastName": "Chambers", 92 | "lineItems": { 93 | "name": "Zildjian K Custom Organic Ride - 21\"", 94 | "sku": "h617xrh", 95 | "quantity": "1", 96 | "total": "379.95" 97 | } 98 | } 99 | ], 100 | copy = slice.call(records), 101 | 102 | newRecord = { 103 | id: 'ci6r6aliv00008poxc2zgnjvf', 104 | date: '2014-12-30 05:29:28', 105 | billingEmail: 'dennis@example.com', 106 | firstName: 'Dennis', 107 | lastName: 'Chambers', 108 | lineItems: { 109 | name: 'Zildjian K Custom Organic Ride - 21"', 110 | sku: 'h123xrh', 111 | quantity: '1', 112 | total: '379.95' 113 | } 114 | }, 115 | 116 | result = list({ 117 | list: records, 118 | async: true 119 | }), 120 | 121 | count = 0; 122 | 123 | result = result.add(newRecord); 124 | result.subscribe( 125 | function onNext(item) { 126 | assert.deepEqual(item, count ? newRecord : records[count++], 127 | 'should contain the new record'); 128 | }, 129 | function onError(err) { 130 | assert.ok(!err, 'should not throw an error'); 131 | }, 132 | function onCompleted() { 133 | assert.ok(true, 'should call onCompleted callback'); 134 | 135 | assert.deepEqual(records, copy, 136 | 'should not alter original list'); 137 | 138 | assert.end(); 139 | } 140 | ); 141 | }); 142 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | test = require('tape'), 5 | list = require('../index.js'); 6 | 7 | var 8 | slice = [].slice; 9 | 10 | test('.getByKey()', function (assert) { 11 | var 12 | records = [ 13 | {a: 'a'}, 14 | {b: 'b'}, 15 | {c: 'c'} 16 | ], 17 | expected = 'a', 18 | 19 | copy = slice.call(records), 20 | 21 | val = list(records).getByKey('a'); 22 | 23 | assert.equal(val, expected, 24 | 'should return the first value it finds.'); 25 | 26 | assert.equal(Array.isArray(val), false, 27 | 'should only return a single result, not an array.'); 28 | 29 | assert.deepEqual(records, copy, 30 | 'should not alter original list.'); 31 | 32 | assert.end(); 33 | }); 34 | 35 | test('.whitelist()', function (assert) { 36 | var 37 | records = [ 38 | {a: 'a'}, 39 | {b: 'b'}, 40 | {c: 'c'}, 41 | {d: 'd', a: 'a'} 42 | ], 43 | copy = slice.call(records), 44 | 45 | whitelisted = list(records).whitelist(['a', 'c']), 46 | 47 | expected = [ 48 | {a: 'a'}, 49 | {c: 'c'}, 50 | {d: 'd', a: 'a'} 51 | ], 52 | 53 | missing = list(expected).getByKey('b'); 54 | 55 | assert.deepEqual(whitelisted, expected, 56 | 'should contain all the expected keys.'); 57 | 58 | assert.strictEqual(missing, undefined, 59 | 'should not contain excluded keys'); 60 | 61 | assert.deepEqual(records, copy, 62 | 'should not alter original list.'); 63 | 64 | assert.end(); 65 | }); 66 | 67 | test('.concat()', function (assert) { 68 | var 69 | records = [ 70 | {a: 'a'}, 71 | {b: 'b'}, 72 | {c: 'c'}, 73 | {c: 'override'} 74 | ], 75 | copy = slice.call(records), 76 | 77 | record = list(records).concat(), 78 | 79 | expected = { 80 | a: 'a', 81 | b: 'b', 82 | c: 'override' 83 | }; 84 | 85 | assert.deepEqual(record, expected, 86 | 'should combine records similar to Object.assign()'); 87 | 88 | assert.deepEqual(records, copy, 89 | 'should not alter original list.'); 90 | 91 | assert.end(); 92 | }); 93 | 94 | test('.toArray()', function (assert) { 95 | var 96 | records = [ 97 | {a: 'a'}, 98 | {b: 'b'}, 99 | {c: 'c'}, 100 | {c: 'override'} 101 | ], 102 | copy = slice.call(records), 103 | 104 | newRecords = list(records).toArray(), 105 | 106 | expected = [ 107 | {a: 'a'}, 108 | {b: 'b'}, 109 | {c: 'c'}, 110 | {c: 'override'} 111 | ]; 112 | 113 | assert.deepEqual(newRecords, expected, 114 | 'should return a plain array'); 115 | 116 | assert.deepEqual(records, copy, 117 | 'should not alter original list.'); 118 | 119 | assert.end(); 120 | }); 121 | -------------------------------------------------------------------------------- /test/remove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | test = require('tape'), 5 | list = require('../index.js'); 6 | 7 | var 8 | slice = [].slice; 9 | 10 | test('.remove() sync operation', function (assert) { 11 | var records = [ 12 | { 13 | "id": "ci6r6aliv00007poxc2zgnjvf", 14 | "date": "2014-12-30 05:29:28", 15 | "billingEmail": "dennis@example.com", 16 | "firstName": "Dennis", 17 | "lastName": "Chambers", 18 | "lineItems": { 19 | "name": "Zildjian K Custom Organic Ride - 21\"", 20 | "sku": "h617xrh", 21 | "quantity": "1", 22 | "total": "379.95" 23 | } 24 | }, 25 | { 26 | "id": "ci6r6aliv00008poxc2zgnjvf", 27 | "date": "2014-12-30 05:29:28", 28 | "billingEmail": "carlos@example.com", 29 | "firstName": "Carlos", 30 | "lastName": "Santana", 31 | "lineItems": { 32 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 33 | "sku": "sc37x3m", 34 | "quantity": "1", 35 | "total": "3999.00" 36 | } 37 | }, 38 | { 39 | "id": "ci6r6aliv00007poxc2zgnjvf", 40 | "date": "2014-12-30 05:29:28", 41 | "billingEmail": "dennis@example.com", 42 | "firstName": "Dennis", 43 | "lastName": "Chambers", 44 | "lineItems": { 45 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 46 | "sku": "sc37x3m", 47 | "quantity": "1", 48 | "total": "379.95" 49 | } 50 | } 51 | ], 52 | copy = slice.call(records), 53 | 54 | record = { 55 | "id": "ci6r6aliv00008poxc2zgnjvf", 56 | "date": "2014-12-30 05:29:28", 57 | "billingEmail": "carlos@example.com", 58 | "firstName": "Carlos", 59 | "lastName": "Santana", 60 | "lineItems": { 61 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 62 | "sku": "sc37x3m", 63 | "quantity": "1", 64 | "total": "3999.00" 65 | } 66 | }, 67 | 68 | result = list(records).remove(record), 69 | 70 | actual = result.where({ id: 'ci6r6aliv00008poxc2zgnjvf' })[0]; 71 | 72 | assert.deepEqual(actual, undefined, 73 | 'should not contain the removed record'); 74 | 75 | assert.deepEqual(records, copy, 76 | 'should not alter original list'); 77 | 78 | assert.end(); 79 | }); 80 | 81 | test('.remove() async operation', function (assert) { 82 | assert.plan(4); 83 | 84 | var 85 | records = [ 86 | { 87 | "id": "ci6r6aliv00007poxc2zgnjvf", 88 | "date": "2014-12-30 05:29:28", 89 | "billingEmail": "dennis@example.com", 90 | "firstName": "Dennis", 91 | "lastName": "Chambers", 92 | "lineItems": { 93 | "name": "Zildjian K Custom Organic Ride - 21\"", 94 | "sku": "h617xrh", 95 | "quantity": "1", 96 | "total": "379.95" 97 | } 98 | }, 99 | { 100 | "id": "ci6r6aliv00008poxc2zgnjvf", 101 | "date": "2014-12-30 05:29:28", 102 | "billingEmail": "carlos@example.com", 103 | "firstName": "Carlos", 104 | "lastName": "Santana", 105 | "lineItems": { 106 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 107 | "sku": "sc37x3m", 108 | "quantity": "1", 109 | "total": "3999.00" 110 | } 111 | }, 112 | { 113 | "id": "ci6r6aliv00007poxc2zgnjvf", 114 | "date": "2014-12-30 05:29:28", 115 | "billingEmail": "dennis@example.com", 116 | "firstName": "Dennis", 117 | "lastName": "Chambers", 118 | "lineItems": { 119 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 120 | "sku": "sc37x3m", 121 | "quantity": "1", 122 | "total": "379.95" 123 | } 124 | } 125 | ], 126 | copy = slice.call(records), 127 | 128 | record = { 129 | "id": "ci6r6aliv00008poxc2zgnjvf", 130 | "date": "2014-12-30 05:29:28", 131 | "billingEmail": "carlos@example.com", 132 | "firstName": "Carlos", 133 | "lastName": "Santana", 134 | "lineItems": { 135 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 136 | "sku": "sc37x3m", 137 | "quantity": "1", 138 | "total": "3999.00" 139 | } 140 | }, 141 | 142 | result = list({ 143 | list: records, 144 | async: true 145 | }); 146 | 147 | result = result.remove(record); 148 | result.subscribe( 149 | function onNext(item) { 150 | assert.notDeepEqual(item, record, 151 | 'should not contain the removed record'); 152 | }, 153 | function onError(err) { 154 | assert.ok(!err, 'should not throw an error'); 155 | }, 156 | function onCompleted() { 157 | assert.ok(true, 'should call onCompleted callback'); 158 | 159 | assert.deepEqual(records, copy, 160 | 'should not alter original list'); 161 | 162 | assert.end(); 163 | } 164 | ); 165 | }); 166 | 167 | test('.removeWhere() sync operation', function (assert) { 168 | var records = [ 169 | { 170 | "id": "ci6r6aliv00007poxc2zgnjvf", 171 | "date": "2014-12-30 05:29:28", 172 | "billingEmail": "dennis@example.com", 173 | "firstName": "Dennis", 174 | "lastName": "Chambers", 175 | "lineItems": { 176 | "name": "Zildjian K Custom Organic Ride - 21\"", 177 | "sku": "h617xrh", 178 | "quantity": "1", 179 | "total": "379.95" 180 | } 181 | }, 182 | { 183 | "id": "ci6r6aliv00008poxc2zgnjvf", 184 | "date": "2014-12-30 05:29:28", 185 | "billingEmail": "carlos@example.com", 186 | "firstName": "Carlos", 187 | "lastName": "Santana", 188 | "lineItems": { 189 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 190 | "sku": "sc37x3m", 191 | "quantity": "1", 192 | "total": "3999.00" 193 | } 194 | }, 195 | { 196 | "id": "ci6r6aliv00007poxc2zgnjvf", 197 | "date": "2014-12-30 05:29:28", 198 | "billingEmail": "dennis@example.com", 199 | "firstName": "Dennis", 200 | "lastName": "Chambers", 201 | "lineItems": { 202 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 203 | "sku": "sc37x3m", 204 | "quantity": "1", 205 | "total": "379.95" 206 | } 207 | } 208 | ], 209 | copy = slice.call(records), 210 | 211 | query = { 212 | "lastName": "Chambers", 213 | }, 214 | 215 | result = list(records).removeWhere(query), 216 | 217 | actual = result.where({ lastName: 'Chambers' })[0]; 218 | 219 | assert.deepEqual(actual, undefined, 220 | 'should not contain the removed record'); 221 | 222 | assert.deepEqual(records, copy, 223 | 'should not alter original list'); 224 | 225 | assert.end(); 226 | }); 227 | 228 | test('.removeWhere() async operation', function (assert) { 229 | assert.plan(4); 230 | 231 | var 232 | records = [ 233 | { 234 | "id": "ci6r6aliv00007poxc2zgnjvf", 235 | "date": "2014-12-30 05:29:28", 236 | "billingEmail": "dennis@example.com", 237 | "firstName": "Dennis", 238 | "lastName": "Chambers", 239 | "lineItems": { 240 | "name": "Zildjian K Custom Organic Ride - 21\"", 241 | "sku": "h617xrh", 242 | "quantity": "1", 243 | "total": "379.95" 244 | } 245 | }, 246 | { 247 | "id": "ci6r6aliv00008poxc2zgnjvf", 248 | "date": "2014-12-30 05:29:28", 249 | "billingEmail": "carlos@example.com", 250 | "firstName": "Carlos", 251 | "lastName": "Santana", 252 | "lineItems": { 253 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 254 | "sku": "sc37x3m", 255 | "quantity": "1", 256 | "total": "3999.00" 257 | } 258 | }, 259 | { 260 | "id": "ci6r6aliv00007poxc2zgnjvf", 261 | "date": "2014-12-30 05:29:28", 262 | "billingEmail": "dennis@example.com", 263 | "firstName": "Dennis", 264 | "lastName": "Chambers", 265 | "lineItems": { 266 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 267 | "sku": "sc37x3m", 268 | "quantity": "1", 269 | "total": "379.95" 270 | } 271 | } 272 | ], 273 | copy = slice.call(records), 274 | 275 | query = { 276 | "lastName": "Chambers", 277 | }, 278 | 279 | result = list({ 280 | list: records, 281 | async: true 282 | }); 283 | 284 | result = result.removeWhere(query); 285 | result.subscribe( 286 | function onNext(item) { 287 | assert.notDeepEqual(item, records[0], 288 | 'should not contain the removed record'); 289 | 290 | assert.notDeepEqual(item, records[2], 291 | 'should not contain the removed record'); 292 | }, 293 | function onError(err) { 294 | assert.ok(!err, 'should not throw an error'); 295 | }, 296 | function onCompleted() { 297 | assert.ok(true, 'should call onCompleted callback'); 298 | 299 | assert.deepEqual(records, copy, 300 | 'should not alter original list'); 301 | 302 | assert.end(); 303 | } 304 | ); 305 | }); 306 | 307 | test('.removeSlice() sync operation', function (assert) { 308 | var records = [ 309 | { 310 | "id": "ci6r6aliv00007poxc2zgnjvf", 311 | "date": "2014-12-30 05:29:28", 312 | "billingEmail": "dennis@example.com", 313 | "firstName": "Dennis", 314 | "lastName": "Chambers", 315 | "lineItems": { 316 | "name": "Zildjian K Custom Organic Ride - 21\"", 317 | "sku": "h617xrh", 318 | "quantity": "1", 319 | "total": "379.95" 320 | } 321 | }, 322 | { 323 | "id": "ci6r6aliv00008poxc2zgnjvf", 324 | "date": "2014-12-30 05:29:28", 325 | "billingEmail": "carlos@example.com", 326 | "firstName": "Carlos", 327 | "lastName": "Santana", 328 | "lineItems": { 329 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 330 | "sku": "sc37x3m", 331 | "quantity": "1", 332 | "total": "3999.00" 333 | } 334 | }, 335 | { 336 | "id": "ci6r6aliv00007poxc2zgnjvf", 337 | "date": "2014-12-30 05:29:28", 338 | "billingEmail": "dennis@example.com", 339 | "firstName": "Dennis", 340 | "lastName": "Chambers", 341 | "lineItems": { 342 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 343 | "sku": "sc37x3m", 344 | "quantity": "1", 345 | "total": "379.95" 346 | } 347 | } 348 | ], 349 | copy = slice.call(records), 350 | 351 | result = list(records).removeSlice(1, 2), 352 | 353 | actual = result.where({ id: 'ci6r6aliv00008poxc2zgnjvf' })[0]; 354 | 355 | assert.deepEqual(actual, undefined, 356 | 'should not contain the removed record'); 357 | 358 | assert.ok(result.length < copy.length, 359 | 'result should have less records than copy'); 360 | 361 | assert.deepEqual(records, copy, 362 | 'should not alter original list'); 363 | 364 | assert.end(); 365 | }); 366 | 367 | test('.removeSlice() async operation', function (assert) { 368 | assert.plan(4); 369 | 370 | var 371 | records = [ 372 | { 373 | "id": "ci6r6aliv00007poxc2zgnjvf", 374 | "date": "2014-12-30 05:29:28", 375 | "billingEmail": "dennis@example.com", 376 | "firstName": "Dennis", 377 | "lastName": "Chambers", 378 | "lineItems": { 379 | "name": "Zildjian K Custom Organic Ride - 21\"", 380 | "sku": "h617xrh", 381 | "quantity": "1", 382 | "total": "379.95" 383 | } 384 | }, 385 | { 386 | "id": "ci6r6aliv00008poxc2zgnjvf", 387 | "date": "2014-12-30 05:29:28", 388 | "billingEmail": "carlos@example.com", 389 | "firstName": "Carlos", 390 | "lastName": "Santana", 391 | "lineItems": { 392 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 393 | "sku": "sc37x3m", 394 | "quantity": "1", 395 | "total": "3999.00" 396 | } 397 | }, 398 | { 399 | "id": "ci6r6aliv00007poxc2zgnjvf", 400 | "date": "2014-12-30 05:29:28", 401 | "billingEmail": "dennis@example.com", 402 | "firstName": "Dennis", 403 | "lastName": "Chambers", 404 | "lineItems": { 405 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 406 | "sku": "sc37x3m", 407 | "quantity": "1", 408 | "total": "379.95" 409 | } 410 | } 411 | ], 412 | copy = slice.call(records), 413 | 414 | result = list({ 415 | list: records, 416 | async: true 417 | }); 418 | 419 | result = result.removeSlice(1, 2); 420 | result.subscribe( 421 | function onNext(item) { 422 | assert.notDeepEqual(item, records[1], 423 | 'should not contain the removed record'); 424 | }, 425 | function onError(err) { 426 | assert.ok(!err, 'should not throw an error'); 427 | }, 428 | function onCompleted() { 429 | assert.ok(true, 'should call onCompleted callback'); 430 | 431 | assert.deepEqual(records, copy, 432 | 'should not alter original list'); 433 | 434 | assert.end(); 435 | } 436 | ); 437 | }); 438 | -------------------------------------------------------------------------------- /test/where.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | test = require('tape'), 5 | list = require('../index.js'); 6 | 7 | var 8 | slice = [].slice; 9 | 10 | test('.where() single clause', function (assert) { 11 | // Find all orders by customer email 12 | var records = [ 13 | { 14 | "id": "ci6r6aliv00007poxc2zgnjvf", 15 | "date": "2014-12-30 05:29:28", 16 | "billingEmail": "dennis@example.com", 17 | "firstName": "Dennis", 18 | "lastName": "Chambers", 19 | "lineItems": { 20 | "name": "Zildjian K Custom Organic Ride - 21\"", 21 | "sku": "h617xrh", 22 | "quantity": "1", 23 | "total": "379.95" 24 | } 25 | }, 26 | { 27 | "id": "ci6r6aliv00007poxc2zgnjvf", 28 | "date": "2014-12-30 05:29:28", 29 | "billingEmail": "carlos@example.com", 30 | "firstName": "Carlos", 31 | "lastName": "Santana", 32 | "lineItems": { 33 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 34 | "sku": "sc37x3m", 35 | "quantity": "1", 36 | "total": "3999.00" 37 | } 38 | }, 39 | { 40 | "id": "ci6r6aliv00007poxc2zgnjvf", 41 | "date": "2014-12-30 05:29:28", 42 | "billingEmail": "dennis@example.com", 43 | "firstName": "Dennis", 44 | "lastName": "Chambers", 45 | "lineItems": { 46 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 47 | "sku": "sc37x3m", 48 | "quantity": "1", 49 | "total": "379.95" 50 | } 51 | } 52 | ], 53 | copy = slice.call(records), 54 | 55 | result = list(records).where({ 56 | billingEmail: 'dennis@example.com' 57 | }), 58 | 59 | expected = [ 60 | { 61 | "id": "ci6r6aliv00007poxc2zgnjvf", 62 | "date": "2014-12-30 05:29:28", 63 | "billingEmail": "dennis@example.com", 64 | "firstName": "Dennis", 65 | "lastName": "Chambers", 66 | "lineItems": { 67 | "name": "Zildjian K Custom Organic Ride - 21\"", 68 | "sku": "h617xrh", 69 | "quantity": "1", 70 | "total": "379.95" 71 | } 72 | }, 73 | { 74 | "id": "ci6r6aliv00007poxc2zgnjvf", 75 | "date": "2014-12-30 05:29:28", 76 | "billingEmail": "dennis@example.com", 77 | "firstName": "Dennis", 78 | "lastName": "Chambers", 79 | "lineItems": { 80 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 81 | "sku": "sc37x3m", 82 | "quantity": "1", 83 | "total": "379.95" 84 | } 85 | } 86 | ]; 87 | 88 | 89 | assert.deepEqual(result, expected, 90 | 'should contain all the expected records.'); 91 | 92 | assert.equal(result.length, 2, 93 | 'should not contain excluded records.'); 94 | 95 | assert.deepEqual(records, copy, 96 | 'should not alter original list.'); 97 | 98 | assert.end(); 99 | }); 100 | 101 | test('.where() multiple clause', function (assert) { 102 | // Find the order where Dennis ordered a cymbal. 103 | var records = [ 104 | { 105 | "id": "ci6r6aliv00007poxc2zgnjvf", 106 | "date": "2014-12-30 05:29:28", 107 | "billingEmail": "dennis@example.com", 108 | "firstName": "Dennis", 109 | "lastName": "Chambers", 110 | "lineItems": { 111 | "name": "Zildjian K Custom Organic Ride - 21\"", 112 | "sku": "h617xrh", 113 | "quantity": "1", 114 | "total": "379.95" 115 | } 116 | }, 117 | { 118 | "id": "ci6r6aliv00007poxc2zgnjvf", 119 | "date": "2014-12-30 05:29:28", 120 | "billingEmail": "carlos@example.com", 121 | "firstName": "Carlos", 122 | "lastName": "Santana", 123 | "lineItems": { 124 | "name": "Gibson Memphis 1963 ES-335 TD - '60s Cherry 2014", 125 | "sku": "sc37x3m", 126 | "quantity": "1", 127 | "total": "3999.00" 128 | } 129 | }, 130 | { 131 | "id": "ci6r6aliv00007poxc2zgnjvf", 132 | "date": "2014-12-30 05:29:28", 133 | "billingEmail": "dennis@example.com", 134 | "firstName": "Dennis", 135 | "lastName": "Chambers", 136 | "lineItems": { 137 | "name": "DW Collector's Series Metal Snare - 6.5\"x14\" Titanium 1mm", 138 | "sku": "sc37x3m", 139 | "quantity": "1", 140 | "total": "379.95" 141 | } 142 | } 143 | ], 144 | copy = slice.call(records), 145 | 146 | result = list(records).where({ 147 | billingEmail: 'dennis@example.com', 148 | lineItems: { 149 | sku: 'h617xrh' 150 | } 151 | }), 152 | 153 | expected = [ 154 | { 155 | "id": "ci6r6aliv00007poxc2zgnjvf", 156 | "date": "2014-12-30 05:29:28", 157 | "billingEmail": "dennis@example.com", 158 | "firstName": "Dennis", 159 | "lastName": "Chambers", 160 | "lineItems": { 161 | "name": "Zildjian K Custom Organic Ride - 21\"", 162 | "sku": "h617xrh", 163 | "quantity": "1", 164 | "total": "379.95" 165 | } 166 | } 167 | ]; 168 | 169 | assert.deepEqual(result, expected, 170 | 'should contain all the expected records.'); 171 | 172 | assert.equal(result.length, 1, 173 | 'should not contain excluded records.'); 174 | 175 | assert.deepEqual(records, copy, 176 | 'should not alter original list.'); 177 | 178 | assert.end(); 179 | }); 180 | --------------------------------------------------------------------------------