diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1ff40e6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false +insert_final_newline = false + +[{,test/}{actual,fixtures}/**] +trim_trailing_whitespace = false +insert_final_newline = false + +[templates/**] +trim_trailing_whitespace = false +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..660957e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Enforce Unix newlines +* text eol=lf + +# binaries +*.ai binary +*.psd binary +*.jpg binary +*.gif binary +*.png binary +*.jpeg binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80a228c --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.DS_Store +*.sublime-* +_gh_pages +bower_components +node_modules +npm-debug.log +actual +test/actual +temp +tmp +TODO.md +vendor +.idea +benchmark +coverage diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..1a2f7b9 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,18 @@ +{ + "asi": false, + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "esnext": true, + "immed": true, + "latedef": false, + "laxcomma": false, + "mocha": true, + "newcap": true, + "noarg": true, + "node": true, + "sub": true, + "undef": true, + "unused": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0fc9381 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +sudo: false +language: node_js +node_js: + - "0.10" + - "0.12" + - "0.13" + - "iojs" +matrix: + fast_finish: true + allow_failures: + - node_js: "0.13" diff --git a/.verb.md b/.verb.md new file mode 100644 index 0000000..c57e4d1 --- /dev/null +++ b/.verb.md @@ -0,0 +1,178 @@ +# {%= name %} {%= badge("fury") %} + +> {%= description %} + +## Install +{%= include("install-npm", {save: true}) %} + +## Usage + +Sort an array by the given object property: + +```js +var arraySort = require('{%= name %}'); + +arraySort([{foo: 'y'}, {foo: 'z'}, {foo: 'x'}], 'foo'); +//=> [{foo: 'x'}, {foo: 'y'}, {foo: 'z'}] +``` + +**Reverse order** + +```js +arraySort([{foo: 'y'}, {foo: 'z'}, {foo: 'x'}], 'foo', {reverse: true}); +//=> [{foo: 'z'}, {foo: 'y'}, {foo: 'x'}] +``` + +## Table of contents + + + +## Params + +```js +arraySort(array, comparisonArgs); +``` + +- `array`: **{Array}** The array to sort +- `comparisonArgs`: **{Function|String|Array}**: Any number of functions, object paths, or arrays or objects paths and functions may be passed. + + +## Examples + +### Object paths + +Say you have the following blog posts: + +```js +var collection = [ + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'c.md', locals: { date: '2015-04-12' } }, + { path: 'g.md', locals: { date: '2014-02-02' } }, +]; +``` + +and you want to sort them by `locals.date`: + +```js +arraySort(collection, 'locals.date'); +``` + +**Result** + +```js +[ + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'g.md', locals: { date: '2014-02-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'c.md', locals: { date: '2015-04-12' } } +]; +``` + +### Sort by multiple properties and functions + +Here are the "posts" we want to sort: + +```js +var posts = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, +]; +``` + +**The goal** + +Sort "posts" by the following: + +- `locals.date`: our first choice +- `locals.foo`: Use this when mulitiple posts have the same `locals.date` value +- `locals.bar`: Use this when mulitiple posts have the same `locals.foo` value + + +```js +// let's create a reusable comparison function +var compare = function(prop) { + // the last arg, `fn` is the internal comparison function + // used by the lib. it's exposed here for convenience + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; +}; +``` + +**Time to sort!** + +```js +// the comparison args can be an array or a list, makes no difference +arraySort(posts, ['locals.date', compare('locals.foo'), compare('locals.bar')]); +``` + +**Result** + +```js +[ + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } +]); +``` + +### Custom functions + +If custom functions are supplied, array elements are sorted according to the return value of the compare function. See the [docs for `Array.sort()`][mozilla]. + + +## Related projects +{%= related(verb.related.list, {remove: name}) %} + +## Running tests +{%= include("tests") %} + +## Contributing +{%= include("contributing") %} + +## Author +{%= include("author") %} + +## License +{%= copyright() %} +{%= license() %} + +*** + +{%= include("footer") %} + +[mozilla]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort + +{%= reflinks(['verb']) %} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65f90ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..648300a --- /dev/null +++ b/README.md @@ -0,0 +1,207 @@ +# array-sort [![NPM version](https://badge.fury.io/js/array-sort.svg)](http://badge.fury.io/js/array-sort) + +> Fast and simple array sorting. Sort an array of primitives, or an array of objects by any number of properties or nested properties. + +## Install + +Install with [npm](https://www.npmjs.com/) + +```sh +$ npm i array-sort --save +``` + +## Usage + +Sort an array by the given object property: + +```js +var arraySort = require('array-sort'); + +arraySort([{foo: 'y'}, {foo: 'z'}, {foo: 'x'}], 'foo'); +//=> [{foo: 'x'}, {foo: 'y'}, {foo: 'z'}] +``` + +**Reverse order** + +```js +arraySort([{foo: 'y'}, {foo: 'z'}, {foo: 'x'}], 'foo', {reverse: true}); +//=> [{foo: 'z'}, {foo: 'y'}, {foo: 'x'}] +``` + +## Table of contents + + + +* [Params](#params) +* [Examples](#examples) + - [Object paths](#object-paths) + - [Sort by multiple properties and functions](#sort-by-multiple-properties-and-functions) + - [Custom functions](#custom-functions) +* [Related projects](#related-projects) +* [Running tests](#running-tests) +* [Contributing](#contributing) +* [Author](#author) +* [License](#license) + +_(Table of contents generated by [verb](https://github.com/assemble/verb))_ + + + +## Params + +```js +arraySort(array, comparisonArgs); +``` + +* `array`: **{Array}** The array to sort +* `comparisonArgs`: **{Function|String|Array}**: Any number of functions, object paths, or arrays or objects paths and functions may be passed. + +## Examples + +### Object paths + +Say you have the following blog posts: + +```js +var collection = [ + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'c.md', locals: { date: '2015-04-12' } }, + { path: 'g.md', locals: { date: '2014-02-02' } }, +]; +``` + +and you want to sort them by `locals.date`: + +```js +arraySort(collection, 'locals.date'); +``` + +**Result** + +```js +[ + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'g.md', locals: { date: '2014-02-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'c.md', locals: { date: '2015-04-12' } } +]; +``` + +### Sort by multiple properties and functions + +Here are the "posts" we want to sort: + +```js +var posts = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, +]; +``` + +**The goal** + +Sort "posts" by the following: + +* `locals.date`: our first choice +* `locals.foo`: Use this when mulitiple posts have the same `locals.date` value +* `locals.bar`: Use this when mulitiple posts have the same `locals.foo` value + +```js +// let's create a reusable comparison function +var compare = function(prop) { + // the last arg, `fn` is the internal comparison function + // used by the lib. it's exposed here for convenience + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; +}; +``` + +**Time to sort!** + +```js +// the comparison args can be an array or a list, makes no difference +arraySort(posts, ['locals.date', compare('locals.foo'), compare('locals.bar')]); +``` + +**Result** + +```js +[ + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } +]); +``` + +### Custom functions + +If custom functions are supplied, array elements are sorted according to the return value of the compare function. See the [docs for ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)`Array.sort()`. + +## Related projects + +* [get-value](https://github.com/jonschlinkert/get-value): Use property paths (` a.b.c`) to get a nested value from an object. +* [sort-asc](https://github.com/jonschlinkert/sort-asc): Sort array elements in ascending order. +* [sort-desc](https://github.com/jonschlinkert/sort-desc): Sort array elements in descending order. +* [set-value](https://github.com/jonschlinkert/set-value): Create nested values and any intermediaries using dot notation (`'a.b.c'`) paths. +* [sort-object](https://github.com/doowb/sort-object): Sort the keys in an object. + +## Running tests + +Install dev dependencies: + +```sh +$ npm i -d && npm test +``` + +## Contributing + +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/array-sort/issues/new) + +## Author + +**Jon Schlinkert** + ++ [github/jonschlinkert](https://github.com/jonschlinkert) ++ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) + +## License + +Copyright © 2015 Jon Schlinkert +Released under the MIT license. + +*** + +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on July 18, 2015._ \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..26406e1 --- /dev/null +++ b/index.js @@ -0,0 +1,125 @@ +/*! + * array-sort + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +var typeOf = require('kind-of'); +var get = require('get-value'); + +/** + * Sort an array of objects by one or more properties. + * + * @param {Array} `arr` The Array to sort. + * @param {String|Array|Function} `props` One or more object paths or comparison functions. + * @param {Object} `opts` Pass `{ reverse: true }` to reverse the sort order. + * @return {Array} Returns a sorted array. + * @api public + */ + +function arraySort(arr, props, opts) { + if (arr == null) { + return []; + } + + if (!Array.isArray(arr)) { + throw new TypeError('array-sort expects an array.'); + } + + if (arguments.length === 1) { + return arr.sort(); + } + + var args = flatten([].slice.call(arguments, 1)); + + // if the last argument appears to be a plain object, + // it's not a valid `compare` arg, so it must be options. + if (typeOf(args[args.length - 1]) === 'object') { + opts = args.pop(); + } + return arr.sort(sortBy(args, opts)); +} + +/** + * Iterate over each comparison property or function until `1` or `-1` + * is returned. + * + * @param {String|Array|Function} `props` One or more object paths or comparison functions. + * @param {Object} `opts` Pass `{ reverse: true }` to reverse the sort order. + * @return {Array} + */ + +function sortBy(props, opts) { + opts = opts || {}; + + return function compareFn(a, b) { + var len = props.length, i = -1; + var result; + + while (++i < len) { + result = compare(props[i], a, b); + if (result !== 0) { + break; + } + } + if (opts.reverse === true) { + return result * -1; + } + return result; + }; +} + +/** + * Compare `a` to `b`. If an object `prop` is passed, then + * `a[prop]` is compared to `b[prop]` + */ + +function compare(prop, a, b) { + // custom function + if (typeof prop === 'function') { + // expose `compare` to custom function + return prop(a, b, compare.bind(null, null)); + } + + // compare strings + if (typeof a === 'string' && typeof b === 'string') { + return a.localeCompare(b); + } + + // compare numbers + if (typeOf(a) === 'number' && typeOf(b) === 'number') { + return a - b; + } + + // compare object values + if (prop && typeOf(a) === 'object' && typeOf(b) === 'object') { + return compare(null, get(a, prop), get(b, prop)); + } + return defaultCompare(a, b); +} + +/** + * Default compare function used as a fallback + * for sorting. + */ + +function defaultCompare(a, b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +/** + * Flatten the given array. + */ + +function flatten(arr) { + return [].concat.apply([], arr); +} + +/** + * Expose `arraySort` + */ + +module.exports = arraySort; diff --git a/package.json b/package.json new file mode 100644 index 0000000..dbe433c --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "array-sort", + "description": "Fast and simple array sorting. Sort an array of primitives, or an array of objects by any number of properties or nested properties.", + "version": "0.1.0", + "homepage": "https://github.com/jonschlinkert/array-sort", + "author": "Jon Schlinkert (https://github.com/jonschlinkert)", + "repository": "jonschlinkert/array-sort", + "bugs": { + "url": "https://github.com/jonschlinkert/array-sort/issues" + }, + "license": "MIT", + "files": [ + "index.js" + ], + "main": "index.js", + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "get-value": "^1.1.5", + "kind-of": "^2.0.0" + }, + "devDependencies": { + "mocha": "*", + "should": "*" + }, + "keywords": [ + "arr", + "array", + "asc", + "ascend", + "ascending", + "desc", + "descend", + "descending", + "dot", + "element", + "elements", + "get", + "multiple", + "nested", + "obj", + "object", + "order", + "ordered", + "path", + "prop", + "properties", + "property", + "sort", + "sorted", + "sorting" + ], + "verb": { + "related": { + "list": [ + "sort-asc", + "sort-desc", + "get-value", + "set-value", + "sort-object" + ] + } + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..e799721 --- /dev/null +++ b/test.js @@ -0,0 +1,281 @@ +'use strict'; + +/* deps: mocha */ +var should = require('should'); +var get = require('get-value'); +var arraySort = require('./'); + +describe('errors', function () { + it('should throw an error when invalid args are passed:', function () { + (function () { + arraySort({}); + }).should.throw('array-sort expects an array.'); + }); +}); + +describe('empty array', function () { + it('should return an empty array when null or undefined is passed', function () { + arraySort().should.eql([]); + arraySort(undefined).should.eql([]); + arraySort(null).should.eql([]); + }) +}); + +describe('basic sort', function () { + it('should sort an array of primitives', function () { + var arr = ['d', 3, 'b', 'a', 'd', 1, 0, 'z']; + arraySort(arr).should.eql([ 0, 1, 3, 'a', 'b', 'd', 'd', 'z' ]); + }) +}); + +describe('arraySort', function () { + var collection = [ + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'c.md', locals: { date: '2015-04-12' } }, + { path: 'g.md', locals: { date: '2014-02-02' } }, + ]; + + it('should sort by a property:', function () { + var arr = [{key: 'y'}, {key: 'z'}, {key: 'x'}]; + arraySort(arr, 'key').should.eql([ + {key: 'x'}, + {key: 'y'}, + {key: 'z'} + ]); + + arraySort(collection, 'path').should.eql([ + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'c.md', locals: { date: '2015-04-12' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'g.md', locals: { date: '2014-02-02' } } + ]); + }); + + it('should sort by a nested property:', function () { + var res = arraySort(collection, 'locals.date'); + res.should.eql([ + { path: 'b.md', locals: { date: '2012-01-02' } }, + { path: 'd.md', locals: { date: '2013-05-06' } }, + { path: 'f.md', locals: { date: '2014-01-02' } }, + { path: 'a.md', locals: { date: '2014-01-09' } }, + { path: 'g.md', locals: { date: '2014-02-02' } }, + { path: 'f.md', locals: { date: '2014-06-01' } }, + { path: 'e.md', locals: { date: '2015-01-02' } }, + { path: 'c.md', locals: { date: '2015-04-12' } } + ]); + }); + + it('should do nothing when the specified property is not a string:', function () { + var arr = [ + {a: {b: {c: 'c'}}}, + {a: {b: {z: 'z'}}}, + {a: {b: {u: 'u'}}}, + {a: {b: {y: 'y'}}} + ]; + + arraySort(arr, 'a.b').should.eql([ + {a: {b: {c: 'c'}}}, + {a: {b: {z: 'z'}}}, + {a: {b: {u: 'u'}}}, + {a: {b: {y: 'y'}}} + ]); + }); + + it('should sort by multiple properties:', function () { + var collection = [ + { key: 'bbb', locals: { date: '2013-05-06' } }, + { key: 'aaa', locals: { date: '2012-01-02' } }, + { key: 'ddd', locals: { date: '2015-04-12' } }, + { key: 'ccc', locals: { date: '2014-01-02' } }, + { key: 'ccc', locals: { date: '2015-01-02' } }, + { key: 'ddd', locals: { date: '2014-01-09' } }, + { key: 'bbb', locals: { date: '2014-06-01' } }, + { key: 'aaa', locals: { date: '2014-02-02' } }, + ]; + + var actual = arraySort(collection, ['key', 'locals.date']); + + actual.should.eql([ + { key: 'aaa', locals: { date: '2012-01-02' } }, + { key: 'aaa', locals: { date: '2014-02-02' } }, + { key: 'bbb', locals: { date: '2013-05-06' } }, + { key: 'bbb', locals: { date: '2014-06-01' } }, + { key: 'ccc', locals: { date: '2014-01-02' } }, + { key: 'ccc', locals: { date: '2015-01-02' } }, + { key: 'ddd', locals: { date: '2014-01-09' } }, + { key: 'ddd', locals: { date: '2015-04-12' } } + ]); + }); + + it('should sort with a function:', function () { + var arr = [{key: 'y'}, {key: 'z'}, {key: 'x'}]; + + var actual = arraySort(arr, function (a, b) { + return a.key > b.key; + }); + + actual.should.eql([ + {key: 'x'}, + {key: 'y'}, + {key: 'z'} + ]); + }); + + it('should support sorting with a list of function:', function () { + var arr = [ + {foo: 'w', bar: 'y', baz: 'w', quux: 'a'}, + {foo: 'x', bar: 'y', baz: 'w', quux: 'b'}, + {foo: 'x', bar: 'y', baz: 'z', quux: 'c'}, + {foo: 'x', bar: 'x', baz: 'w', quux: 'd'}, + ]; + + var compare = function(prop) { + return function (a, b) { + return a[prop].localeCompare(b[prop]); + }; + }; + + var actual = arraySort(arr, + compare('foo'), + compare('bar'), + compare('baz'), + compare('quux')); + + actual.should.eql([ + { foo: 'w', bar: 'y', baz: 'w', quux: 'a' }, + { foo: 'x', bar: 'x', baz: 'w', quux: 'd' }, + { foo: 'x', bar: 'y', baz: 'w', quux: 'b' }, + { foo: 'x', bar: 'y', baz: 'z', quux: 'c' } + ]); + }); + + it('should support sorting with an array of function:', function () { + var arr = [ + {foo: 'w', bar: 'y', baz: 'w', quux: 'a'}, + {foo: 'x', bar: 'y', baz: 'w', quux: 'b'}, + {foo: 'x', bar: 'y', baz: 'z', quux: 'c'}, + {foo: 'x', bar: 'x', baz: 'w', quux: 'd'}, + ]; + + var compare = function(prop) { + return function (a, b) { + return a[prop].localeCompare(b[prop]); + }; + }; + + var actual = arraySort(arr, [ + compare('foo'), + compare('bar'), + compare('baz'), + compare('quux') + ]); + + actual.should.eql([ + { foo: 'w', bar: 'y', baz: 'w', quux: 'a' }, + { foo: 'x', bar: 'x', baz: 'w', quux: 'd' }, + { foo: 'x', bar: 'y', baz: 'w', quux: 'b' }, + { foo: 'x', bar: 'y', baz: 'z', quux: 'c' } + ]); + }); + + it('should support sorting with any combination of functions and properties:', function () { + var posts = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + ]; + + var compare = function(prop) { + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var actual = arraySort(posts, 'locals.date', 'doesnt.exist', compare('locals.foo'), [ + compare('locals.bar') + ]); + + actual.should.eql([ + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } } + ]); + }); + + it('should support reverse sorting with any combination of functions and properties:', function () { + var posts = [ + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + ]; + + var compare = function(prop) { + return function (a, b, fn) { + var valA = get(a, prop); + var valB = get(b, prop); + return fn(valA, valB); + }; + }; + + var actual = arraySort(posts, 'locals.date', 'doesnt.exist', compare('locals.foo'), [ + compare('locals.bar') + ], { reverse: true }); + + actual.should.eql([ + { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }, + { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } }, + { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } }, + { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } }, + { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } }, + { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } }, + { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } }, + { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } }, + { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } }, + { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } }, + { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } }, + { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } }, + { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } } + ]); + }); +});