test/sort.test.ts   A
last analyzed

Complexity

Total Complexity 1
Complexity/F 1

Size

Lines of Code 693
Function Count 1

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 543
dl 0
loc 693
rs 10
c 0
b 0
f 0
wmc 1
mnd 0
bc 0
fnc 1
bpm 0
cpm 1
noi 0
1
import { sort } from '../src';
2
3
describe('JSON Deep Sort Library', () => {
4
  describe('Object Sorting', () => {
5
    it('should sort object keys in ascending order by default', () => {
6
      const input = { c: 'c', b: 'b', a: 'a' };
7
      const expected = { a: 'a', b: 'b', c: 'c' };
8
      expect(sort(input)).toEqual(expected);
9
    });
10
11
    it('should sort object keys in descending order when specified', () => {
12
      const input = { a: 'a', b: 'b', c: 'c' };
13
      const expected = { c: 'c', b: 'b', a: 'a' };
14
      expect(sort(input, false)).toEqual(expected);
15
    });
16
17
    it('should sort nested object keys recursively', () => {
18
      const input = {
19
        b: { z: 'z', y: 'y' },
20
        a: { x: 'x', w: 'w' },
21
      };
22
      const expected = {
23
        a: { w: 'w', x: 'x' },
24
        b: { y: 'y', z: 'z' },
25
      };
26
      expect(sort(input)).toEqual(expected);
27
    });
28
29
    it('should sort mixed nested structures', () => {
30
      const input = {
31
        b: ['b', 'a'],
32
        a: { d: 'd', c: 'c' },
33
      };
34
      const expected = {
35
        a: { c: 'c', d: 'd' },
36
        b: ['b', 'a'],
37
      };
38
      expect(sort(input)).toEqual(expected);
39
    });
40
41
    it('should sort deeply nested objects', () => {
42
      const input = {
43
        z: {
44
          y: [
45
            { c: 3, b: 2, a: 1 },
46
            { f: 6, e: 5, d: 4 },
47
          ],
48
          x: {
49
            w: { beta: 'β', alpha: 'α', gamma: 'γ' },
50
            v: [3, 1, 4, 1, 5, 9, 2, 6, 5],
51
          },
52
        },
53
        a: [
54
          { foo: { bar: { baz: 'qux' } } },
55
          [{ nested: { array: [3, 2, 1] } }],
56
        ],
57
      };
58
      const expected = {
59
        a: [
60
          { foo: { bar: { baz: 'qux' } } },
61
          [{ nested: { array: [3, 2, 1] } }],
62
        ],
63
        z: {
64
          x: {
65
            v: [3, 1, 4, 1, 5, 9, 2, 6, 5],
66
            w: { alpha: 'α', beta: 'β', gamma: 'γ' },
67
          },
68
          y: [
69
            { a: 1, b: 2, c: 3 },
70
            { d: 4, e: 5, f: 6 },
71
          ],
72
        },
73
      };
74
      expect(sort(input)).toEqual(expected);
75
    });
76
77
    it('should sort numeric string keys numerically', () => {
78
      const input = {
79
        '10': 'ten',
80
        '1': 'one',
81
        '2': 'two',
82
        foo: 'bar',
83
      };
84
      const expected = {
85
        '1': 'one',
86
        '2': 'two',
87
        '10': 'ten',
88
        foo: 'bar',
89
      };
90
      expect(sort(input)).toEqual(expected);
91
    });
92
93
    it('should handle objects with symbol keys', () => {
94
      const sym1 = Symbol('test1');
95
      const sym2 = Symbol('test2');
96
      const input = {
97
        [sym1]: 'value1',
98
        [sym2]: 'value2',
99
        c: 'c',
100
        a: 'a',
101
        b: 'b',
102
      };
103
      const result = sort(input);
104
      expect(Object.getOwnPropertySymbols(result)).toEqual([sym1, sym2]);
105
      expect(Object.keys(result)).toEqual(['a', 'b', 'c']);
106
    });
107
108
    it('should handle objects with only symbol keys', () => {
109
      const sym1 = Symbol('a');
110
      const sym2 = Symbol('b');
111
      const input = {
112
        [sym2]: 'value2',
113
        [sym1]: 'value1',
114
      };
115
      const result = sort(input);
116
      // Symbol keys should maintain their order (symbols are not sorted)
117
      expect(Object.getOwnPropertySymbols(result)).toEqual([sym2, sym1]);
118
    });
119
120
    it('should handle objects with mixed key types', () => {
121
      const sym = Symbol('symbol');
122
      const input = {
123
        z: 'last',
124
        [sym]: 'symbol value',
125
        a: 'first',
126
        m: 'middle',
127
      };
128
      const result = sort(input);
129
      // String keys should be sorted, symbol should remain
130
      expect(Object.keys(result)).toEqual(['a', 'm', 'z']);
131
      expect(Object.getOwnPropertySymbols(result)).toEqual([sym]);
132
      expect(result[sym]).toBe('symbol value');
133
    });
134
135
    it('should handle objects with getter properties', () => {
136
      const obj = {
137
        b: 'b',
138
        a: 'a',
139
        get computed() {
140
          return 'computed';
141
        },
142
      };
143
144
      const result = sort(obj);
145
      // Getters are enumerable properties, so they get included in sorting
146
      expect(Object.keys(result)).toEqual(['a', 'b', 'computed']);
147
      expect(result.computed).toBe('computed');
148
    });
149
150
    it('should handle objects with setter properties', () => {
151
      const obj = {
152
        b: 'b',
153
        a: 'a',
154
        set computed(_value: string) {
155
          // Setter is called but we don't need to store the value for this test
156
        },
157
      };
158
159
      const result = sort(obj);
160
      // Setters are enumerable properties, so they get included in sorting
161
      expect(Object.keys(result)).toEqual(['a', 'b', 'computed']);
162
      expect(typeof result.computed).toBe('undefined');
163
    });
164
165
    it('should handle empty objects', () => {
166
      expect(sort({})).toEqual({});
167
    });
168
  });
169
170
  describe('Array Handling', () => {
171
    it('should not modify arrays of primitives by default', () => {
172
      const input = ['b', 'a', 'c'];
173
      expect(sort(input)).toEqual(input);
174
    });
175
176
    it('should not modify arrays of objects by default', () => {
177
      const input = [
178
        { b: 'b', a: 'a' },
179
        { d: 'd', c: 'c' },
180
      ];
181
      expect(sort(input)).toEqual(input);
182
    });
183
184
    it('should sort nested arrays of objects', () => {
185
      const input = [
186
        { b: 'b', a: 'a' },
187
        { d: 'd', c: 'c' },
188
      ];
189
      const expected = [
190
        { a: 'a', b: 'b' },
191
        { c: 'c', d: 'd' },
192
      ];
193
      expect(sort(input)).toEqual(expected);
194
    });
195
196
    it('should handle empty arrays', () => {
197
      expect(sort([])).toEqual([]);
198
    });
199
200
    it('should handle arrays with single items', () => {
201
      expect(sort(['a'])).toEqual(['a']);
202
      expect(sort([1])).toEqual([1]);
203
      expect(sort([true])).toEqual([true]);
204
    });
205
206
    it('should handle deeply nested arrays', () => {
207
      const input = {
208
        z: {
209
          y: [
210
            [3, 1, 4],
211
            [1, 5, 9],
212
          ],
213
          x: {
214
            w: { beta: 'β', alpha: 'α', gamma: 'γ' },
215
            v: [3, 1, 4, 1, 5, 9, 2, 6, 5],
216
          },
217
        },
218
        a: [
219
          { foo: { bar: { baz: 'qux' } } },
220
          [{ nested: { array: [3, 2, 1] } }],
221
        ],
222
      };
223
      const expected = {
224
        a: [
225
          { foo: { bar: { baz: 'qux' } } },
226
          [{ nested: { array: [3, 2, 1] } }],
227
        ],
228
        z: {
229
          x: {
230
            v: [3, 1, 4, 1, 5, 9, 2, 6, 5],
231
            w: { alpha: 'α', beta: 'β', gamma: 'γ' },
232
          },
233
          y: [
234
            [3, 1, 4],
235
            [1, 5, 9],
236
          ],
237
        },
238
      };
239
      expect(sort(input)).toEqual(expected);
240
    });
241
  });
242
243
  describe('Primitive Array Sorting (when enabled)', () => {
244
    it('should sort arrays of strings when sortPrimitiveArrays is true', () => {
245
      const input = ['b', 'a', 'c'];
246
      const expected = ['a', 'b', 'c'];
247
      expect(sort(input, true, true)).toEqual(expected);
248
    });
249
250
    it('should sort arrays of strings in descending order when sortPrimitiveArrays is true', () => {
251
      const input = ['a', 'b', 'c'];
252
      const expected = ['c', 'b', 'a'];
253
      expect(sort(input, false, true)).toEqual(expected);
254
    });
255
256
    it('should sort arrays of numbers when sortPrimitiveArrays is true', () => {
257
      const input = [3, 1, 4, 1, 5];
258
      const expected = [1, 1, 3, 4, 5];
259
      expect(sort(input, true, true)).toEqual(expected);
260
    });
261
262
    it('should sort arrays of numbers in descending order when sortPrimitiveArrays is true', () => {
263
      const input = [1, 2, 3, 4, 5];
264
      const expected = [5, 4, 3, 2, 1];
265
      expect(sort(input, false, true)).toEqual(expected);
266
    });
267
268
    it('should sort arrays of booleans when sortPrimitiveArrays is true', () => {
269
      const input = [true, false, true, false];
270
      const expected = [false, false, true, true];
271
      expect(sort(input, true, true)).toEqual(expected);
272
    });
273
274
    it('should sort arrays of booleans in descending order when sortPrimitiveArrays is true', () => {
275
      const input = [false, true, false, true];
276
      const expected = [true, true, false, false];
277
      expect(sort(input, false, true)).toEqual(expected);
278
    });
279
280
    it('should handle NaN values correctly when sorting arrays of numbers', () => {
281
      const input = [3, NaN, 1, NaN, 5, 2];
282
      const expected = [1, 2, 3, 5, NaN, NaN];
283
      expect(sort(input, true, true)).toEqual(expected);
284
    });
285
286
    it('should handle NaN values correctly when sorting arrays of numbers in descending order', () => {
287
      const input = [1, NaN, 5, NaN, 2, 3];
288
      const expected = [5, 3, 2, 1, NaN, NaN];
289
      expect(sort(input, false, true)).toEqual(expected);
290
    });
291
292
    it('should handle arrays with duplicate primitive values', () => {
293
      const input = ['b', 'a', 'b', 'a', 'c'];
294
      const expected = ['a', 'a', 'b', 'b', 'c'];
295
      expect(sort(input, true, true)).toEqual(expected);
296
    });
297
298
    it('should handle arrays with extreme number values', () => {
299
      const input = [
300
        Number.MAX_SAFE_INTEGER,
301
        Number.MIN_SAFE_INTEGER,
302
        0,
303
        -Infinity,
304
        Infinity,
305
      ];
306
      const expected = [
307
        -Infinity,
308
        Number.MIN_SAFE_INTEGER,
309
        0,
310
        Number.MAX_SAFE_INTEGER,
311
        Infinity,
312
      ];
313
      expect(sort(input, true, true)).toEqual(expected);
314
    });
315
316
    it('should handle arrays with very large numbers', () => {
317
      const input = [1e308, -1e308, 0, 1e-308, -1e-308];
318
      const result = sort(input, true, true);
319
      // Check that the order is correct (ascending: smallest to largest)
320
      expect(result[0]).toBe(-1e308); // Smallest negative
321
      expect(result[1]).toBe(-1e-308); // Largest negative
322
      expect(result[2]).toBe(0); // Zero
323
      expect(result[3]).toBe(1e-308); // Smallest positive
324
      expect(result[4]).toBe(1e308); // Largest positive
325
    });
326
327
    it('should handle arrays with special string characters', () => {
328
      const input = ['z', 'a', 'Z', 'A', '0', '9'];
329
      const result = sort(input, true, true);
330
      // Check that numbers come first, then letters (case-insensitive)
331
      expect(result[0]).toBe('0');
332
      expect(result[1]).toBe('9');
333
      expect(result.slice(2)).toContain('A');
334
      expect(result.slice(2)).toContain('Z');
335
      expect(result.slice(2)).toContain('a');
336
      expect(result.slice(2)).toContain('z');
337
    });
338
339
    it('should handle arrays with unicode characters', () => {
340
      const input = ['ñ', 'a', 'z', 'é', 'ü'];
341
      const expected = ['a', 'é', 'ñ', 'ü', 'z'];
342
      expect(sort(input, true, true)).toEqual(expected);
343
    });
344
345
    it('should handle arrays with very long strings', () => {
346
      const longString1 = 'a'.repeat(1000);
347
      const longString2 = 'b'.repeat(1000);
348
      const longString3 = 'c'.repeat(1000);
349
      const input = [longString2, longString1, longString3];
350
      const expected = [longString1, longString2, longString3];
351
      expect(sort(input, true, true)).toEqual(expected);
352
    });
353
354
    it('should handle arrays with single primitive items', () => {
355
      expect(sort(['a'], true, true)).toEqual(['a']);
356
      expect(sort([1], true, true)).toEqual([1]);
357
      expect(sort([true], true, true)).toEqual([true]);
358
      expect(sort([false], true, true)).toEqual([false]);
359
    });
360
361
    it('should handle empty arrays when sortPrimitiveArrays is true', () => {
362
      expect(sort([], true, true)).toEqual([]);
363
    });
364
365
    it('should sort nested primitive arrays when sortPrimitiveArrays is true', () => {
366
      const input = {
367
        b: ['z', 'y', 'x'],
368
        a: ['c', 'b', 'a'],
369
      };
370
      const expected = {
371
        a: ['a', 'b', 'c'],
372
        b: ['x', 'y', 'z'],
373
      };
374
      expect(sort(input, true, true)).toEqual(expected);
375
    });
376
377
    it('should handle deeply nested primitive arrays when sortPrimitiveArrays is true', () => {
378
      const input = {
379
        z: {
380
          y: [
381
            [3, 1, 4],
382
            [1, 5, 9],
383
          ],
384
          x: {
385
            w: { beta: 'β', alpha: 'α', gamma: 'γ' },
386
            v: [3, 1, 4, 1, 5, 9, 2, 6, 5],
387
          },
388
        },
389
        a: [
390
          { foo: { bar: { baz: 'qux' } } },
391
          [{ nested: { array: [3, 2, 1] } }],
392
        ],
393
      };
394
      const expected = {
395
        a: [
396
          { foo: { bar: { baz: 'qux' } } },
397
          [{ nested: { array: [1, 2, 3] } }],
398
        ],
399
        z: {
400
          x: {
401
            v: [1, 1, 2, 3, 4, 5, 5, 6, 9],
402
            w: { alpha: 'α', beta: 'β', gamma: 'γ' },
403
          },
404
          y: [
405
            [1, 3, 4],
406
            [1, 5, 9],
407
          ],
408
        },
409
      };
410
      expect(sort(input, true, true)).toEqual(expected);
411
    });
412
  });
413
414
  describe('Mixed Primitive Array Handling', () => {
415
    it('should handle mixed primitive arrays when sortPrimitiveArrays is true', () => {
416
      const input = [true, 'b', 3, 'a', false, 1];
417
      // Mixed types should maintain original order
418
      expect(sort(input, true, true)).toEqual(input);
419
    });
420
421
    it('should handle arrays with mixed sortable and non-sortable primitives', () => {
422
      const input = [true, 'string', Symbol('test'), 42];
423
      // Mixed types should maintain original order when sortPrimitiveArrays is true
424
      expect(sort(input, true, true)).toEqual(input);
425
    });
426
427
    it('should handle mixed primitive type comparison fallback', () => {
428
      // This specifically tests the return 0; fallback when comparing
429
      // different primitive types in compareSortablePrimitives
430
      const input = [42, 'hello', true, 3.14, 'world', false];
431
432
      // When sortPrimitiveArrays is true, mixed types should maintain order
433
      const result = sort(input, true, true);
434
      expect(result).toEqual(input);
435
436
      // Also test with a different order to ensure the fallback is triggered
437
      const input2 = ['hello', 42, false, true, 3.14, 'world'];
438
      const result2 = sort(input2, true, true);
439
      expect(result2).toEqual(input2);
440
    });
441
442
    it('should force comparison of different primitive types for coverage', () => {
443
      // This test specifically targets the fallback return 0; in compareSortablePrimitives
444
      const input = ['z', 1, 'a', true, 'm', false];
445
446
      // When sorting, the algorithm will compare 'z' vs 1, 'a' vs true, etc.
447
      // This should trigger the fallback return 0; for different types
448
      const result = sort(input, true, true);
449
450
      // Since all comparisons between different types return 0, the order should be maintained
451
      expect(result).toEqual(input);
452
453
      // Test descending order as well
454
      const resultDesc = sort(input, false, true);
455
      expect(resultDesc).toEqual(input);
456
    });
457
  });
458
459
  describe('Null and Undefined Handling', () => {
460
    it('should not sort arrays with null values when sortPrimitiveArrays is true', () => {
461
      const input = ['b', null, 'a'];
462
      // Arrays with null should maintain original order (not sortable)
463
      expect(sort(input, true, true)).toEqual(input);
464
    });
465
466
    it('should not sort arrays with undefined values when sortPrimitiveArrays is true', () => {
467
      const input = ['b', undefined, 'a'];
468
      // Arrays with undefined should maintain original order (not sortable)
469
      expect(sort(input, true, true)).toEqual(input);
470
    });
471
472
    it('should not sort arrays with mixed null and undefined values when sortPrimitiveArrays is true', () => {
473
      const input = ['b', null, 'a', undefined, 'c'];
474
      // Arrays with mixed null/undefined should maintain original order (not sortable)
475
      expect(sort(input, true, true)).toEqual(input);
476
    });
477
478
    it('should not sort arrays with mixed null and undefined values in descending order when sortPrimitiveArrays is true', () => {
479
      const input = ['b', null, 'a', undefined, 'c'];
480
      // Arrays with mixed null/undefined should maintain original order (not sortable)
481
      expect(sort(input, false, true)).toEqual(input);
482
    });
483
484
    it('should handle arrays with only null values when sortPrimitiveArrays is true', () => {
485
      const input = [null, null, null];
486
      // Arrays with all null values should maintain order (all same type)
487
      expect(sort(input, true, true)).toEqual(input);
488
    });
489
490
    it('should handle arrays with only undefined values when sortPrimitiveArrays is true', () => {
491
      const input = [undefined, undefined, undefined];
492
      // Arrays with all undefined values should maintain order (all same type)
493
      expect(sort(input, true, true)).toEqual(input);
494
    });
495
  });
496
497
  describe('Primitive Value Handling', () => {
498
    it('should return primitive inputs unchanged', () => {
499
      expect(sort('string')).toBe('string');
500
      expect(sort(123)).toBe(123);
501
      expect(sort(true)).toBe(true);
502
      expect(sort(null)).toBe(null);
503
      expect(sort(undefined)).toBe(undefined);
504
    });
505
506
    it('should handle edge case: empty string comparison', () => {
507
      const input = ['', 'a', 'b', 'c'];
508
      const expected = ['', 'a', 'b', 'c'];
509
      expect(sort(input, true, true)).toEqual(expected);
510
    });
511
512
    it('should handle edge case: zero and negative numbers', () => {
513
      const input = [-5, 0, 5, -10, 10];
514
      const expected = [-10, -5, 0, 5, 10];
515
      expect(sort(input, true, true)).toEqual(expected);
516
    });
517
518
    it('should handle edge case: boolean false vs true ordering', () => {
519
      const input = [true, false, true, false];
520
      const expected = [false, false, true, true];
521
      expect(sort(input, true, true)).toEqual(expected);
522
    });
523
524
    it('should handle edge case: boolean true vs false ordering in descending', () => {
525
      const input = [false, true, false, true];
526
      const expected = [true, true, false, false];
527
      expect(sort(input, false, true)).toEqual(expected);
528
    });
529
  });
530
531
  describe('Non-Sortable Object Handling', () => {
532
    it('should not sort non-sortable objects', () => {
533
      const date = new Date('2023-01-01');
534
      const regex = /test/;
535
      const func = () => {};
536
      const error = new Error('test');
537
      const map = new Map();
538
      const set = new Set();
539
      const weakMap = new WeakMap();
540
      const weakSet = new WeakSet();
541
      const promise = Promise.resolve();
542
      const iterable = {
543
        *[Symbol.iterator]() {
544
          yield 1;
545
          yield 2;
546
        },
547
      };
548
549
      const input = {
550
        date,
551
        regex,
552
        func,
553
        error,
554
        map,
555
        set,
556
        weakMap,
557
        weakSet,
558
        promise,
559
        iterable,
560
      };
561
562
      const result = sort(input) as typeof input;
563
564
      expect(result).toEqual({
565
        date,
566
        error,
567
        func,
568
        iterable,
569
        map,
570
        promise,
571
        regex,
572
        set,
573
        weakMap,
574
        weakSet,
575
      });
576
577
      // Ensure the objects are the same instances
578
      Object.keys(input).forEach((key) => {
579
        expect(result[key as keyof typeof input]).toBe(
580
          input[key as keyof typeof input]
581
        );
582
      });
583
    });
584
585
    it('should handle objects with various value types', () => {
586
      const date = new Date('2023-01-01');
587
      const regex = /regex/;
588
      const func = () => 'function';
589
      const input = {
590
        e: date,
591
        d: regex,
592
        c: [1, 2, 3],
593
        b: { nested: 'object' },
594
        a: func,
595
      };
596
      const expected = {
597
        a: func,
598
        b: { nested: 'object' },
599
        c: [1, 2, 3],
600
        d: regex,
601
        e: date,
602
      };
603
      expect(sort(input)).toEqual(expected);
604
      expect(sort(input).e).toBe(date);
605
      expect(sort(input).d).toBe(regex);
606
      expect(sort(input).a).toBe(func);
607
    });
608
609
    it('should handle edge case: BigInt values (non-sortable)', () => {
610
      const bigIntValue = BigInt(123);
611
      const result = sort(bigIntValue);
612
      expect(result).toBe(bigIntValue);
613
    });
614
615
    it('should handle edge case: Symbol values (non-sortable)', () => {
616
      const symbolValue = Symbol('test');
617
      const result = sort(symbolValue);
618
      expect(result).toBe(symbolValue);
619
    });
620
621
    it('should handle edge case: function that returns itself', () => {
622
      const selfReferencingFunction = () => selfReferencingFunction;
623
      const result = sort(selfReferencingFunction);
624
      expect(result).toBe(selfReferencingFunction);
625
    });
626
627
    it('should handle edge case: data that is neither array, primitive, nor object', () => {
628
      const testFunction = () => {
629
        return testFunction;
630
      };
631
632
      const result = sort(testFunction);
633
      expect(result).toBe(testFunction);
634
    });
635
  });
636
637
  describe('Backward Compatibility', () => {
638
    it('should maintain backward compatibility when sortPrimitiveArrays is false (default)', () => {
639
      const input = ['b', 'a', 'c'];
640
      expect(sort(input)).toEqual(input);
641
      expect(sort(input, true)).toEqual(input);
642
      expect(sort(input, true, false)).toEqual(input);
643
    });
644
645
    it('should not modify arrays of objects when sortPrimitiveArrays is true', () => {
646
      const input = [
647
        { b: 'b', a: 'a' },
648
        { d: 'd', c: 'c' },
649
      ];
650
      const expected = [
651
        { a: 'a', b: 'b' },
652
        { c: 'c', d: 'd' },
653
      ];
654
      // Arrays of objects should still be sorted by their keys
655
      expect(sort(input, true, true)).toEqual(expected);
656
    });
657
  });
658
659
  describe('Error Handling', () => {
660
    it('should handle circular references gracefully', () => {
661
      const circular: Record<string, unknown> = {};
662
      circular.self = circular;
663
664
      // Circular references should be detected and handled to prevent infinite recursion
665
      expect(() => sort(circular)).toThrow(RangeError);
666
    });
667
668
    it('should handle null input gracefully', () => {
669
      expect(sort(null)).toBe(null);
670
    });
671
672
    it('should handle undefined input gracefully', () => {
673
      expect(sort(undefined)).toBe(undefined);
674
    });
675
676
    it('should handle empty string input gracefully', () => {
677
      expect(sort('')).toBe('');
678
    });
679
680
    it('should handle zero input gracefully', () => {
681
      expect(sort(0)).toBe(0);
682
    });
683
684
    it('should handle false input gracefully', () => {
685
      expect(sort(false)).toBe(false);
686
    });
687
688
    it('should handle true input gracefully', () => {
689
      expect(sort(true)).toBe(true);
690
    });
691
  });
692
});
693