Skip to content

Commit 34179be

Browse files
committed
feat(utils): groupAsync
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent 9a8b86d commit 34179be

File tree

5 files changed

+95
-9
lines changed

5 files changed

+95
-9
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @file Type Tests - groupAsync
3+
* @module tutils/utils/tests/unit-d/groupAsync
4+
*/
5+
6+
import type Vehicle from '#fixtures/types/vehicle'
7+
import type testSubject from '../group-async'
8+
9+
describe('unit-d:utils/groupAsync', () => {
10+
it('should return Promise<Record<K, T[number][]>>', () => {
11+
// Arrange
12+
type T = Vehicle[]
13+
type K = Vehicle['year']
14+
type Expect = Promise<Record<K, T[number][]>>
15+
16+
// Expect
17+
expectTypeOf<typeof testSubject<T, K>>().returns.toEqualTypeOf<Expect>()
18+
})
19+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @file Unit Tests - groupAsync
3+
* @module tutils/utils/tests/unit/groupAsync
4+
*/
5+
6+
import testSubject from '../group-async'
7+
8+
describe('unit:utils/groupAsync', () => {
9+
it('should return groups object', async () => {
10+
expect(await testSubject([3.1, 4.2, 3.3], Math.floor)).to.eql({
11+
3: [3.1, 3.3],
12+
4: [4.2]
13+
})
14+
})
15+
})

src/utils/__tests__/group.spec.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,9 @@ import testSubject from '../group'
77

88
describe('unit:utils/group', () => {
99
it('should return groups object', () => {
10-
// Arrange
11-
const cases: [...Parameters<typeof testSubject<number[]>>, object][] = [
12-
[[], vi.fn(), {}],
13-
[[3.1, 4.2, 3.3], Math.floor, { 3: [3.1, 3.3], 4: [4.2] }]
14-
]
15-
16-
// Act + Expect
17-
cases.forEach(([arr, key, expected]) => {
18-
expect(testSubject(arr, key)).to.eql(expected)
10+
expect(testSubject([3.1, 4.2, 3.3], Math.floor)).to.eql({
11+
3: [3.1, 3.3],
12+
4: [4.2]
1913
})
2014
})
2115
})

src/utils/group-async.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @file Utilities - groupAsync
3+
* @module tutils/utils/groupAsync
4+
*/
5+
6+
import type { Mapper, PropertyKey } from '#src/types'
7+
import cast from './cast'
8+
import isUndefined from './is-undefined'
9+
import reduceAsync from './reduce-async'
10+
11+
/**
12+
* Group each item in an array.
13+
*
14+
* The return value is a plain object with a key for each group, where each key
15+
* value is an array containing group items.
16+
*
17+
* A `key` function is used to map array items to group keys.
18+
*
19+
* @see {@linkcode Mapper}
20+
*
21+
* @todo examples
22+
*
23+
* @async
24+
*
25+
* @template T - Array to group
26+
* @template K - Identity key type
27+
*
28+
* @param {T} arr - Array to group
29+
* @param {Mapper<T, K | Promise<K>>} key - Group key mapper
30+
* @return {Promise<Record<K, T[number][]>>} Groups object
31+
*/
32+
const groupAsync = async <
33+
T extends readonly unknown[],
34+
K extends PropertyKey = PropertyKey
35+
>(
36+
arr: T,
37+
key: Mapper<T, K | Promise<K>>
38+
): Promise<{ [H in K]: T[number][] }> => {
39+
return reduceAsync(arr, async (acc, item, i) => {
40+
/**
41+
* Group key.
42+
*
43+
* @const {K} k
44+
*/
45+
const k: K = await key(item, i, arr)
46+
47+
// initialize group
48+
isUndefined(acc[k]) && (acc[k] = [])
49+
50+
// add group item
51+
acc[k].push(item)
52+
53+
return acc
54+
}, cast({}))
55+
}
56+
57+
export default groupAsync

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export { default as flat } from './flat'
3232
export { default as fork } from './fork'
3333
export { default as get } from './get'
3434
export { default as group } from './group'
35+
export { default as groupAsync } from './group-async'
3536
export { default as hasOwn } from './has-own'
3637
export { default as identity } from './identity'
3738
export { default as ifelse } from './ifelse'

0 commit comments

Comments
 (0)