diff --git a/src/lib.js b/src/lib.js index 85a688e..0c5ddfb 100644 --- a/src/lib.js +++ b/src/lib.js @@ -152,7 +152,22 @@ export function sorted_items(items, sort, sortings) { } if (sort.field) { - return orderBy(items, sort.field, sort.order || 'asc'); + const fields = Array.isArray(sort.field) ? sort.field : [sort.field]; + const orders = Array.isArray(sort.order) ? sort.order : [sort.order || 'asc']; + + // push null/undefined to the end for each field by prefixing with a boolean iteratee + const iteratees = []; + const iterateeOrders = []; + + fields.forEach((field, idx) => { + iteratees.push((item) => (item[field] === null || item[field] === undefined ? 1 : 0)); + iterateeOrders.push('asc'); // keep non-null before nulls + + iteratees.push(field); + iterateeOrders.push(orders[idx] || 'asc'); + }); + + return orderBy(items, iteratees, iterateeOrders); } return items; diff --git a/tests/sortingSpec.js b/tests/sortingSpec.js index f5023b6..1db432f 100644 --- a/tests/sortingSpec.js +++ b/tests/sortingSpec.js @@ -22,6 +22,13 @@ describe('aggregations', function () { }, ]; + const itemsWithNulls = [ + { name: 'movie1', rating: null }, + { name: 'movie2', rating: 5 }, + { name: 'movie3', rating: null }, + { name: 'movie4', rating: 3 }, + ]; + it('makes items sorting', function test(done) { const sortings = { name_asc: { @@ -72,6 +79,24 @@ describe('aggregations', function () { 'movie7', 'movie2', ]); + + // nulls should be last regardless of order + const nullSortings = { + rating_desc: { + field: 'rating', + order: 'desc', + }, + rating_asc: { + field: 'rating', + order: 'asc', + }, + }; + + result = sorted_items(itemsWithNulls, 'rating_desc', nullSortings); + assert.deepEqual(map(result, 'rating'), [5, 3, null, null]); + + result = sorted_items(itemsWithNulls, 'rating_asc', nullSortings); + assert.deepEqual(map(result, 'rating'), [3, 5, null, null]); done(); }); });