UNPKG

forto-sorter

Version:

Fast and powerful array sorting. Sort by any property in any direction with easy to read syntax.

631 lines (526 loc) 20.8 kB
import { assert } from 'chai'; import { sort, inPlaceSort, createNewSortInstance, } from '../src/sort'; describe('sort', () => { let flatArray:number[]; let flatNaturalArray:string[]; let students:{ name:string, dob:Date, address:{ streetNumber?:number }, }[]; let multiPropArray:{ name:string, lastName:string, age:number, unit:string, }[]; beforeEach(() => { flatArray = [1, 5, 3, 2, 4, 5]; flatNaturalArray = ['A10', 'A2', 'B10', 'B2']; students = [{ name: 'Mate', dob: new Date(1987, 14, 11), address: { streetNumber: 3 }, }, { name: 'Ante', dob: new Date(1987, 14, 9), address: {}, }, { name: 'Dino', dob: new Date(1987, 14, 10), address: { streetNumber: 1 }, }]; multiPropArray = [{ name: 'aa', lastName: 'aa', age: 10, unit: 'A10', }, { name: 'aa', lastName: undefined, age: 8, unit: 'A2', }, { name: 'aa', lastName: null, age: 9, unit: 'A01', }, { name: 'aa', lastName: 'bb', age: 11, unit: 'C2', }, { name: 'bb', lastName: 'aa', age: 6, unit: 'B3', }]; }); it('Should sort flat array in ascending order', () => { const sorted = sort(flatArray).asc(); assert.deepStrictEqual(sorted, [1, 2, 3, 4, 5, 5]); // flatArray should not be modified assert.deepStrictEqual(flatArray, [1, 5, 3, 2, 4, 5]); assert.notEqual(sorted,flatArray); }); it('Should in place sort flat array in ascending order', () => { const sorted = inPlaceSort(flatArray).asc(); assert.deepStrictEqual(sorted, [1, 2, 3, 4, 5, 5]); assert.deepStrictEqual(flatArray, [1, 2, 3, 4, 5, 5]); assert.equal(sorted, flatArray); }); it('Should sort flat array in descending order', () => { const sorted = sort(flatArray).desc(); assert.deepStrictEqual(sorted, [5, 5, 4, 3, 2, 1]); // Passed array is not mutated assert.deepStrictEqual(flatArray, [1, 5, 3, 2, 4, 5]); // Can do in place sorting const sorted2 = inPlaceSort(flatArray).desc(); assert.equal(sorted2, flatArray); assert.deepStrictEqual(flatArray, [5, 5, 4, 3, 2, 1]); }); it('Should sort flat array with by sorter', () => { const sorted = sort(flatArray).by({ asc: true }); assert.deepStrictEqual(sorted, [1, 2, 3, 4, 5, 5]); const sorted2 = sort(flatArray).by({ desc: true }); assert.deepStrictEqual(sorted2, [5, 5, 4, 3, 2, 1]); // Passed array is not mutated assert.deepStrictEqual(flatArray, [1, 5, 3, 2, 4, 5]); // Can do in place sorting const sorted3 = inPlaceSort(flatArray).by({ desc: true }); assert.equal(sorted3, flatArray); assert.deepStrictEqual(flatArray, [5, 5, 4, 3, 2, 1]); }); it('Should sort by student name in ascending order', () => { const sorted = sort(students).asc(p => p.name.toLowerCase()); assert.deepStrictEqual(['Ante', 'Dino', 'Mate'], sorted.map(p => p.name)); }); it('Should sort by student name in descending order', () => { const sorted = sort(students).desc((p) => p.name.toLowerCase()); assert.deepStrictEqual(['Mate', 'Dino', 'Ante'], sorted.map(p => p.name)); }); it('Should sort nil values to the bottom', () => { const sorted1 = sort(students).asc((p) => p.address.streetNumber); assert.deepStrictEqual([1, 3, undefined], sorted1.map(p => p.address.streetNumber)); const sorted2 = sort(students).desc((p) => p.address.streetNumber); assert.deepStrictEqual([3, 1, undefined], sorted2.map(p => p.address.streetNumber)); assert.deepStrictEqual( sort([1, undefined, 3, null, 2]).asc(), [1, 2, 3, null, undefined], ); assert.deepStrictEqual( sort([1, undefined, 3, null, 2]).desc(), [3, 2, 1, null, undefined], ); }); it('Should ignore values that are not sortable', () => { assert.equal(sort('string' as any).asc(), 'string' as any); assert.equal(sort(undefined).desc(), undefined); assert.equal(sort(null).desc(), null); assert.equal(sort(33 as any).asc(), 33 as any); assert.deepStrictEqual(sort({ name: 'test' } as any).desc(), { name: 'test' } as any); assert.equal((sort(33 as any) as any).by({ asc: true }), 33 as any); }); it('Should sort dates correctly', () => { const sorted = sort(students).asc('dob'); assert.deepStrictEqual(sorted.map(p => p.dob), [ new Date(1987, 14, 9), new Date(1987, 14, 10), new Date(1987, 14, 11), ]); }); it('Should sort on single property when passed as array', () => { const sorted = sort(students).asc(['name']); assert.deepStrictEqual(['Ante', 'Dino', 'Mate'], sorted.map(p => p.name)); }); it('Should sort on multiple properties', () => { const sorted = sort(multiPropArray).asc([ p => p.name, p => p.lastName, p => p.age, ]); const sortedArray = sorted.map(arr => ({ name: arr.name, lastName: arr.lastName, age: arr.age, })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', lastName: 'aa', age: 10 }, { name: 'aa', lastName: 'bb', age: 11 }, { name: 'aa', lastName: undefined, age: 8 }, { name: 'aa', lastName: null, age: 9 }, { name: 'bb', lastName: 'aa', age: 6 }, ]); }); it('Should sort on multiple properties by string sorter', () => { const sorted = sort(multiPropArray).asc(['name', 'age', 'lastName']); const sortedArray = sorted.map(arr => ({ name: arr.name, lastName: arr.lastName, age: arr.age, })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', lastName: undefined, age: 8 }, { name: 'aa', lastName: null, age: 9 }, { name: 'aa', lastName: 'aa', age: 10 }, { name: 'aa', lastName: 'bb', age: 11 }, { name: 'bb', lastName: 'aa', age: 6 }, ]); }); it('Should sort on multiple mixed properties', () => { const sorted = sort(multiPropArray).asc(['name', p => p.lastName, 'age']); const sortedArray = sorted.map(arr => ({ name: arr.name, lastName: arr.lastName, age: arr.age, })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', lastName: 'aa', age: 10 }, { name: 'aa', lastName: 'bb', age: 11 }, { name: 'aa', lastName: undefined, age: 8 }, { name: 'aa', lastName: null, age: 9 }, { name: 'bb', lastName: 'aa', age: 6 }, ]); }); it('Should sort with all equal values', () => { const same = [ { name: 'a', age: 1 }, { name: 'a', age: 1 }, ]; const sorted = sort(same).asc(['name', 'age']); assert.deepStrictEqual(sorted, [ { name: 'a', age: 1 }, { name: 'a', age: 1 }, ]); }); it('Should sort descending by name and ascending by lastName', () => { const sorted = sort(multiPropArray).by([ { desc: 'name' }, { asc: 'lastName' }, ]); const sortedArray = sorted.map(arr => ({ name: arr.name, lastName: arr.lastName, })); assert.deepStrictEqual(sortedArray, [ { name: 'bb', lastName: 'aa' }, { name: 'aa', lastName: 'aa' }, { name: 'aa', lastName: 'bb' }, { name: 'aa', lastName: undefined }, { name: 'aa', lastName: null }, ]); }); it('Should sort ascending by name and descending by age', () => { const sorted = sort(multiPropArray).by([ { asc: 'name' }, { desc: 'age' }, ]); const sortedArray = sorted.map(arr => ({ name: arr.name, age: arr.age })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', age: 11 }, { name: 'aa', age: 10 }, { name: 'aa', age: 9 }, { name: 'aa', age: 8 }, { name: 'bb', age: 6 }, ]); }); it('Should sort ascending by lastName, descending by name and ascending by age', () => { const sorted = sort(multiPropArray).by([ { asc: p => p.lastName }, { desc: p => p.name }, { asc: p => p.age }, ]); const sortedArray = sorted.map(arr => ({ name: arr.name, lastName: arr.lastName, age: arr.age, })); assert.deepStrictEqual(sortedArray, [ { name: 'bb', lastName: 'aa', age: 6 }, { name: 'aa', lastName: 'aa', age: 10 }, { name: 'aa', lastName: 'bb', age: 11 }, { name: 'aa', lastName: undefined, age: 8 }, { name: 'aa', lastName: null, age: 9 }, ]); }); it('Should throw error if asc or desc props not provided with object config', () => { const errorMessage = 'Invalid sort config: Expected `asc` or `desc` property'; assert.throws( () => sort(multiPropArray).by([{ asci: 'name' }] as any), Error, errorMessage, ); assert.throws( () => sort(multiPropArray).by([{ asc: 'lastName' }, { ass: 'name' }] as any), Error, errorMessage, ); assert.throws(() => sort([1, 2]).asc(null), Error, errorMessage); assert.throws(() => sort([1, 2]).desc([1, 2, 3] as any), Error, errorMessage); }); it('Should throw error if both asc and dsc props provided with object config', () => { const errorMessage = 'Invalid sort config: Ambiguous object with `asc` and `desc` config properties'; assert.throws( () => sort(multiPropArray).by([{ asc: 'name', desc: 'lastName' }] as any), Error, errorMessage, ); }); it('Should throw error if using nested property with string syntax', () => { assert.throw( () => sort(students).desc('address.streetNumber' as any), Error, 'Invalid sort config: String syntax not allowed for nested properties.', ); }); it('Should sort ascending on single property with by sorter', () => { const sorted = sort(multiPropArray).by([{ asc: p => p.age }]); assert.deepStrictEqual([6, 8, 9, 10, 11], sorted.map(m => m.age)); }); it('Should sort descending on single property with by sorter', () => { const sorted = sort(multiPropArray).by([{ desc: 'age' }]); assert.deepStrictEqual([11, 10, 9, 8, 6], sorted.map(m => m.age)); }); it('Should sort flat array in asc order using natural sort comparer', () => { const sorted = sort(flatNaturalArray).by([{ asc: true, comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }]); assert.deepStrictEqual(sorted, ['A2', 'A10', 'B2', 'B10']); }); it('Should sort flat array in desc order using natural sort comparer', () => { const sorted = sort(flatNaturalArray).by([{ desc: true, comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }]); assert.deepStrictEqual(sorted, ['B10', 'B2', 'A10', 'A2']); }); it('Should sort object in asc order using natural sort comparer', () => { const sorted = sort(multiPropArray).by([{ asc: p => p.unit, comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }]); assert.deepStrictEqual(['A01', 'A2', 'A10', 'B3', 'C2'], sorted.map(m => m.unit)); }); it('Should sort object in desc order using natural sort comparer', () => { const sorted = sort(multiPropArray).by([{ desc: p => p.unit, comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }]); assert.deepStrictEqual(['C2', 'B3', 'A10', 'A2', 'A01'], sorted.map(m => m.unit)); }); it('Should sort object on multiple props using both default and custom comparer', () => { const testArr = [ { a: 'A2', b: 'A2' }, { a: 'A2', b: 'A10' }, { a: 'A10', b: 'A2' }, ]; const sorted = sort(testArr).by([{ desc: p => p.a, }, { asc: 'b', comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }]); assert.deepStrictEqual(sorted, [ { a: 'A2', b: 'A2' }, { a: 'A2', b: 'A10' }, // <= B is sorted using natural sort comparer { a: 'A10', b: 'A2' }, // <= A is sorted using default sort comparer ]); }); // BUG repo case: https://github.com/snovakovic/fast-sort/issues/18 it('Sort by comparer should not override default sort of other array property', () => { const rows = [ { status: 0, title: 'A' }, { status: 0, title: 'D' }, { status: 0, title: 'B' }, { status: 1, title: 'C' }, ]; const sorted = sort(rows).by([{ asc: row => row.status, comparer: (a, b) => a - b, }, { asc: row => row.title, }]); assert.deepStrictEqual(sorted, [ { status: 0, title: 'A' }, { status: 0, title: 'B' }, { status: 0, title: 'D' }, { status: 1, title: 'C' }, ]); }); it('Should create natural sort instance and handle sorting correctly', () => { const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }); const sorted1 = naturalSort(multiPropArray).desc('unit'); assert.deepStrictEqual(['C2', 'B3', 'A10', 'A2', 'A01'], sorted1.map(m => m.unit)); const sorted2 = naturalSort(multiPropArray).by({ asc: 'unit' }); assert.deepStrictEqual(['A01', 'A2', 'A10', 'B3', 'C2'], sorted2.map(m => m.unit)); const sorted3 = naturalSort(multiPropArray).asc('lastName'); assert.deepStrictEqual(['aa', 'aa', 'bb', null, undefined], sorted3.map(m => m.lastName)); const sorted4 = naturalSort(multiPropArray).desc(p => p.lastName); assert.deepStrictEqual([undefined, null, 'bb', 'aa', 'aa'], sorted4.map(m => m.lastName)); const sorted5 = naturalSort(flatArray).desc(); assert.deepStrictEqual(sorted5, [5, 5, 4, 3, 2, 1]); const sorted6 = naturalSort(flatNaturalArray).asc(); assert.deepStrictEqual(sorted6, ['A2', 'A10', 'B2', 'B10']); }); it('Should handle sorting on multiples props with custom sorter instance', () => { const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }); const arr = [ { a: 'a', b: 'A2' }, { a: 'a', b: 'A20' }, { a: 'a', b: null }, { a: 'a', b: 'A3' }, { a: 'a', b: undefined }, ]; const sort1 = naturalSort(arr).asc('b'); assert.deepStrictEqual(sort1.map(a => a.b), ['A2', 'A3', 'A20', null, undefined]); const sorted2 = naturalSort(arr).asc(['a', 'b']); assert.deepStrictEqual(sorted2.map(a => a.b), ['A2', 'A3', 'A20', null, undefined]); const sorted3 = naturalSort(arr).desc('b'); assert.deepStrictEqual(sorted3.map(a => a.b), [undefined, null, 'A20', 'A3', 'A2']); const sorted4 = naturalSort(arr).desc(['a', 'b']); assert.deepStrictEqual(sorted4.map(a => a.b), [undefined, null, 'A20', 'A3', 'A2']); }); it('Should create custom tag sorter instance', () => { const tagImportance = { vip: 3, influencer: 2, captain: 1 }; const customTagComparer = (a, b) => (tagImportance[a] || 0) - (tagImportance[b] || 0); const tags = ['influencer', 'unknown', 'vip', 'captain']; const tagSorter = createNewSortInstance({ comparer: customTagComparer }); assert.deepStrictEqual(tagSorter(tags).asc(), ['unknown', 'captain', 'influencer', 'vip']); assert.deepStrictEqual(tagSorter(tags).desc(), ['vip', 'influencer', 'captain', 'unknown']); assert.deepStrictEqual(sort(tags).asc(tag => tagImportance[tag] || 0), ['unknown', 'captain', 'influencer', 'vip']); }); it('Should be able to override natural sort comparer', () => { const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }); const sorted1 = naturalSort(multiPropArray).by([{ asc: 'name', }, { desc: 'unit', comparer(a, b) { // NOTE: override natural sort if (a === b) return 0; return a < b ? -1 : 1; }, }]); let sortedArray = sorted1.map(arr => ({ name: arr.name, unit: arr.unit })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', unit: 'C2' }, { name: 'aa', unit: 'A2' }, { name: 'aa', unit: 'A10' }, { name: 'aa', unit: 'A01' }, { name: 'bb', unit: 'B3' }, ]); const sorted2 = naturalSort(multiPropArray).by([{ asc: 'name' }, { desc: 'unit' }]); sortedArray = sorted2.map(arr => ({ name: arr.name, unit: arr.unit })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', unit: 'C2' }, { name: 'aa', unit: 'A10' }, { name: 'aa', unit: 'A2' }, { name: 'aa', unit: 'A01' }, { name: 'bb', unit: 'B3' }, ]); }); it('Should sort in asc order with by sorter if object config not provided', () => { const sorted = sort(multiPropArray).by(['name', 'unit'] as any); const sortedArray = sorted.map(arr => ({ name: arr.name, unit: arr.unit })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', unit: 'A01' }, { name: 'aa', unit: 'A10' }, { name: 'aa', unit: 'A2' }, { name: 'aa', unit: 'C2' }, { name: 'bb', unit: 'B3' }, ]); }); it('Should ignore empty array as a sorting prop', () => { assert.deepStrictEqual(sort([2, 1, 4]).asc([]), [1, 2, 4]); }); it('Should sort by computed property', () => { const repos = [ { openIssues: 0, closedIssues: 5 }, { openIssues: 4, closedIssues: 4 }, { openIssues: 3, closedIssues: 3 }, ]; const sorted1 = sort(repos).asc(r => r.openIssues + r.closedIssues); assert.deepStrictEqual(sorted1, [ { openIssues: 0, closedIssues: 5 }, { openIssues: 3, closedIssues: 3 }, { openIssues: 4, closedIssues: 4 }, ]); const sorted2 = sort(repos).desc(r => r.openIssues + r.closedIssues); assert.deepStrictEqual(sorted2, [ { openIssues: 4, closedIssues: 4 }, { openIssues: 3, closedIssues: 3 }, { openIssues: 0, closedIssues: 5 }, ]); }); it('Should not mutate sort by array', () => { const sortBy = [{ asc: 'name' }, { asc: 'unit' }]; const sorted = sort(multiPropArray).by(sortBy as any); assert.deepStrictEqual(sortBy, [{ asc: 'name' }, { asc: 'unit' }]); const sortedArray = sorted.map(arr => ({ name: arr.name, unit: arr.unit })); assert.deepStrictEqual(sortedArray, [ { name: 'aa', unit: 'A01' }, { name: 'aa', unit: 'A10' }, { name: 'aa', unit: 'A2' }, { name: 'aa', unit: 'C2' }, { name: 'bb', unit: 'B3' }, ]); }); it('Should sort readme example for natural sort correctly', () => { const testArr = ['image-2.jpg', 'image-11.jpg', 'image-3.jpg']; // By default fast-sort is not doing natural sort const sorted1 = sort(testArr).desc(); // => assert.deepStrictEqual(sorted1, ['image-3.jpg', 'image-2.jpg', 'image-11.jpg']); const sorted2 = sort(testArr).by({ desc: true, comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }); assert.deepStrictEqual(sorted2, ['image-11.jpg', 'image-3.jpg', 'image-2.jpg']); // If we want to reuse natural sort in multiple places we can create new sort instance const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, }); const sorted3 = naturalSort(testArr).asc(); assert.deepStrictEqual(sorted3, ['image-2.jpg', 'image-3.jpg', 'image-11.jpg']); const sorted4 = naturalSort(testArr).desc(); assert.deepStrictEqual(sorted4, ['image-11.jpg', 'image-3.jpg', 'image-2.jpg']); assert.notEqual(sorted3, testArr); }); it('Should create sort instance that sorts nil value to the top in desc order', () => { const nilSort = createNewSortInstance({ comparer(a, b):number { if (a == null) return 1; if (b == null) return -1; if (a < b) return -1; if (a === b) return 0; return 1; }, }); const sorter1 = nilSort(multiPropArray).asc(p => p.lastName); assert.deepStrictEqual(['aa', 'aa', 'bb', undefined, null], sorter1.map(p => p.lastName)); const sorter2 = nilSort(multiPropArray).desc(p => p.lastName); assert.deepStrictEqual([null, undefined, 'bb', 'aa', 'aa'], sorter2.map(p => p.lastName)); // By default custom sorter should not mutate provided array assert.notEqual(sorter1, multiPropArray); assert.notEqual(sorter2, multiPropArray); }); it('Should mutate array with custom sorter if inPlaceSorting provided', () => { const customInPlaceSorting = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare, inPlaceSorting: true, // <= NOTE }); const sorted = customInPlaceSorting(flatArray).asc(); assert.equal(sorted, flatArray); assert.deepStrictEqual(flatArray, [1, 2, 3, 4, 5, 5]); }); });