From bc0e996ea23630310292e07ead6dd2a4047e01b0 Mon Sep 17 00:00:00 2001 From: jonschlinkert Date: Sat, 18 Jul 2015 02:22:39 -0400 Subject: [PATCH] first commit --- .editorconfig | 22 ++++ .gitattributes | 10 ++ .gitignore | 15 +++ .jshintrc | 18 ++++ .travis.yml | 11 ++ .verb.md | 178 +++++++++++++++++++++++++++++++ LICENSE | 21 ++++ README.md | 207 ++++++++++++++++++++++++++++++++++++ index.js | 125 ++++++++++++++++++++++ package.json | 68 ++++++++++++ test.js | 281 +++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 956 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 .travis.yml create mode 100644 .verb.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 test.js 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 } } + ]); + }); +});