feat(orderBy): add handling one is a string and the other is a number case (#365)

* Add comparing string and number

* Fix typo

* fix testcase

* Fix test case

* simplify testcase
This commit is contained in:
Dayong Lee 2024-08-10 16:26:33 +09:00 committed by GitHub
parent cc3a467443
commit 4af0662066
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 154 additions and 10 deletions

View File

@ -0,0 +1,48 @@
import { describe, it, expect } from 'vitest';
import { compareValues } from './compareValues';
describe('compareValues', () => {
it('should return -1 if a < b', () => {
expect(compareValues(1, 2, 'asc')).toBe(-1);
});
it('should return 1 if a > b', () => {
expect(compareValues(2, 1, 'asc')).toBe(1);
});
it('should return 0 if a === b', () => {
expect(compareValues(1, 1, 'asc')).toBe(0);
});
it('should return 1 if a < b and order is desc', () => {
expect(compareValues(1, 2, 'desc')).toBe(1);
});
it('should return -1 if a > b and order is desc', () => {
expect(compareValues(2, 1, 'desc')).toBe(-1);
});
it('should return 0 if a === b and order is desc', () => {
expect(compareValues(1, 1, 'desc')).toBe(0);
});
it('should handle the case where a is string and b is number', () => {
expect(compareValues('0', 1, 'asc')).toBe(-1);
expect(compareValues('a', 1, 'asc')).toBe(1);
expect(compareValues('1b', 1, 'asc')).toBe(1);
expect(compareValues('3', 1, 'asc')).toBe(1);
expect(compareValues('2', 1, 'asc')).toBe(1);
});
it('should handle the case where a is number and b is string', () => {
expect(compareValues(1, '0', 'asc')).toBe(1);
expect(compareValues(1, 'a', 'asc')).toBe(-1);
expect(compareValues(1, '1b', 'asc')).toBe(-1);
expect(compareValues(1, '3', 'asc')).toBe(-1);
expect(compareValues(1, '2', 'asc')).toBe(-1);
});
it('should return 0 if a and b are not comparable', () => {
expect(compareValues({ a: 'number' }, 1, 'asc')).toBe(0);
});
});

View File

@ -0,0 +1,46 @@
type Order = 'asc' | 'desc';
/**
* Compares two values and returns a number indicating their order.
*
* This function can handle the following cases:
* - If types of `a` and `b` are the same
* - If types of `a` and `b` are not the same, but can convert to numbers(result is not NaN)
* - If types of `a` and `b` are not the same, but one of them is a string and the other is a number
*
* If none of the above cases are met, it returns 0.
*
* @param {any} a - The first value to compare.
* @param {any} b - The second value to compare.
* @param {Order} order - The order direction ('asc' for ascending or 'desc' for descending).
* @returns {number} - A number indicating the order of the two values.
*/
export function compareValues(a: any, b: any, order: Order) {
// If type of a and b are the same or they can be converted to numbers(not NaN), compare them.
if (a === b) {
return 0;
}
if (a < b) {
return order === 'desc' ? 1 : -1;
}
if (a > b) {
return order === 'desc' ? -1 : 1;
}
// Type of a and b are not the same.
// We only handle the case where a and b is string or number.
if (typeof a === 'string' && typeof b === 'number') {
b = b.toString();
return compareValues(a, b, order);
}
if (typeof a === 'number' && typeof b === 'string') {
a = a.toString();
return compareValues(a, b, order);
}
// If a and b are not comparable, return 0
return 0;
}

View File

@ -73,4 +73,61 @@ describe('orderBy', () => {
{ user: 'fred', age: 48 },
]);
});
it('should order object has mixed value that is string and number', () => {
const actual1 = orderBy(
[
{ id: 1, value: 'a' },
{ id: 2, value: 2 },
{ id: 12, value: 1 },
{ id: 5, value: 'b' },
{ id: 4, value: 2 },
{ id: 43, value: 'c' },
{ id: 24, value: 3 },
{ id: 3, value: '3a' },
{ id: 6, value: '2a' },
{ id: 7, value: '1cs' },
],
['value', 'id'],
['asc', 'asc']
);
const expected1 = [
{ id: 12, value: 1 },
{ id: 7, value: '1cs' },
{ id: 2, value: 2 },
{ id: 4, value: 2 },
{ id: 6, value: '2a' },
{ id: 24, value: 3 },
{ id: 3, value: '3a' },
{ id: 1, value: 'a' },
{ id: 5, value: 'b' },
{ id: 43, value: 'c' },
];
const actual2 = orderBy(
[
{ id: 1, value: 'apple' },
{ id: 2, value: 'banana' },
{ id: 3, value: 'cherry' },
{ id: 4, value: 10 },
{ id: 5, value: 5 },
{ id: 6, value: 'banana' },
{ id: 7, value: 20 },
],
['value', 'id'],
['asc', 'asc']
);
const expected2 = [
{ id: 5, value: 5 },
{ id: 4, value: 10 },
{ id: 7, value: 20 },
{ id: 1, value: 'apple' },
{ id: 2, value: 'banana' },
{ id: 6, value: 'banana' },
{ id: 3, value: 'cherry' },
];
expect(actual1).toEqual(expected1);
expect(actual2).toEqual(expected2);
});
});

View File

@ -1,3 +1,5 @@
import { compareValues } from './_internal/compareValues';
type Order = 'asc' | 'desc';
/**
@ -32,16 +34,6 @@ type Order = 'asc' | 'desc';
* // ]
*/
export function orderBy<T>(collection: T[], keys: Array<keyof T>, orders: Order[]): T[] {
const compareValues = (a: T[keyof T], b: T[keyof T], order: Order) => {
if (a < b) {
return order === 'asc' ? -1 : 1;
}
if (a > b) {
return order === 'asc' ? 1 : -1;
}
return 0;
};
const effectiveOrders = keys.map((_, index) => orders[index] || orders[orders.length - 1]);
return collection.slice().sort((a, b) => {
@ -49,6 +41,7 @@ export function orderBy<T>(collection: T[], keys: Array<keyof T>, orders: Order[
const key = keys[i];
const order = effectiveOrders[i];
const result = compareValues(a[key], b[key], order);
if (result !== 0) {
return result;
}