Skip to content

Commit ed85e52

Browse files
SuperOleg39o.drapeza
andauthored
fix(object/groupBy): add types overload for arrays (#58)
Co-authored-by: o.drapeza <o.drapeza@tinkoff.ru>
1 parent d5b56e9 commit ed85e52

File tree

4 files changed

+119
-10
lines changed

4 files changed

+119
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"react": "^16.9.0",
5151
"react-is": "^16.9.0",
5252
"recursive-readdir-sync": "^1.0.6",
53+
"ts-expect": "^1.1.0",
5354
"ts-jest": "^24.1.0",
5455
"typescript": "^3.6.3",
5556
"walker": "^1.0.7"

src/object/__tests__/groupBy.ts

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,103 @@
1+
import { expectType } from "ts-expect";
12
import groupBy from '../groupBy';
3+
import { ArrValues, ObjValues } from '../../typings/types';
24

35
describe('utils/object/groupBy', () => {
46
it('group object values by function', () => {
57
const obj = { a: { b: 1, c: 2 }, b: { b: 3 }, c: { b: 1, d: 5 } };
6-
const fn = jest.fn((x) => x.b);
78

8-
expect(groupBy(fn, obj)).toEqual({
9+
expect(groupBy((x) => x.b, obj)).toEqual({
910
1: [{ b: 1, c: 2 }, { b: 1, d: 5 }],
1011
3: [{ b: 3 }],
1112
});
12-
expect(fn).toBeCalledWith({ b: 1, c: 2 }, 'a', obj);
13-
expect(fn).toBeCalledWith({ b: 3 }, 'b', obj);
14-
expect(fn).toBeCalledWith({ b: 1, d: 5 }, 'c', obj);
13+
});
14+
15+
it('group array values by function', () => {
16+
const arr = [{ b: 1, c: 2 }, { b: 3 }, { b: 1, d: 5 }];
17+
18+
expect(groupBy((x) => x.b, arr)).toEqual({
19+
1: [{ b: 1, c: 2 }, { b: 1, d: 5 }],
20+
3: [{ b: 3 }],
21+
})
22+
});
23+
24+
it('group object values by function through currying', () => {
25+
const obj = { a: { b: 1, c: 2 }, b: { b: 3 }, c: { b: 1, d: 5 } };
26+
27+
expect(groupBy((x: ObjValues<typeof obj>) => x.b)(obj)).toEqual({
28+
1: [{ b: 1, c: 2 }, { b: 1, d: 5 }],
29+
3: [{ b: 3 }],
30+
});
31+
});
32+
33+
it('group array values by function through currying', () => {
34+
const arr = [{ b: 1, c: 2 }, { b: 3 }, { b: 1, d: 5 }];
35+
36+
expect(groupBy((x: ArrValues<typeof arr>) => x.b)(arr)).toEqual({
37+
1: [{ b: 1, c: 2 }, { b: 1, d: 5 }],
38+
3: [{ b: 3 }],
39+
})
40+
});
41+
42+
it('check grouped object type', () => {
43+
const obj = { a: { b: 1, c: 2 }, b: { b: 3 }, c: { b: 1, d: 5 } };
44+
const result = groupBy((x) => x.b, obj);
45+
const curriedResult = groupBy((x) => x.b)(obj);
46+
const typedCurriedResult = groupBy((x: ObjValues<typeof obj>) => x.b)(obj);
47+
48+
type ValuesType = { b: number, c: number } | { b: number } | { b: number, d: number };
49+
50+
type ExpectedType = Record<number, Array<ValuesType>>;
51+
type CurriedExpectedType = Record<any, Array<ValuesType>>;
52+
type TypedCurriedExpectedType = Record<number, Array<ValuesType>>;
53+
54+
expectType<ExpectedType>(result);
55+
expectType<CurriedExpectedType>(curriedResult);
56+
expectType<TypedCurriedExpectedType>(typedCurriedResult);
57+
});
58+
59+
it('check grouped array type', () => {
60+
const arr = [{ b: 1, c: 2 }, { b: 3 }, { b: 1, d: 5 }];
61+
const result = groupBy((x) => x.b, arr);
62+
const curriedResult = groupBy((x) => x.b)(arr);
63+
const typedCurriedResult = groupBy((x: ArrValues<typeof arr>) => x.b)(arr);
64+
65+
type ValuesType = { b: number, c: number } | { b: number } | { b: number, d: number };
66+
67+
type ExpectedType = Record<number, Array<ValuesType>>;
68+
type CurriedExpectedType = Record<any, Array<ValuesType>>;
69+
type TypedCurriedExpectedType = Record<number, Array<ValuesType>>;
70+
71+
expectType<ExpectedType>(result);
72+
expectType<CurriedExpectedType>(curriedResult);
73+
expectType<TypedCurriedExpectedType>(typedCurriedResult);
74+
});
75+
76+
it('check grouped const object type', () => {
77+
const obj = { a: { b: 1, c: 2 }, b: { b: 3 }, c: { b: 1, d: 5 } } as const;
78+
const result = groupBy((x) => x.b, obj);
79+
80+
type ValuesType = { b: number, c: number } | { b: number } | { b: number, d: number };
81+
82+
type ExpectedType = {
83+
[1]: Array<ValuesType>;
84+
[3]: Array<ValuesType>;
85+
};
86+
87+
expectType<ExpectedType>(result);
88+
});
89+
90+
it('check grouped const array type', () => {
91+
const arr = [{ b: 1, c: 2 }, { b: 3 }, { b: 1, d: 5 }] as const;
92+
const result = groupBy((x) => x.b, arr);
93+
94+
type ValuesType = { b: number, c: number } | { b: number } | { b: number, d: number };
95+
96+
type ExpectedType = {
97+
[1]: ReadonlyArray<ValuesType>;
98+
[3]: ReadonlyArray<ValuesType>;
99+
};
100+
101+
expectType<ExpectedType>(result);
15102
});
16103
});

src/object/groupBy.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import curryN from '../function/curryN';
22
import objectKeys from './keys';
3-
import { ObjBaseBy, ObjBase } from '../typings/types';
3+
import { ObjBaseBy, ObjOrArrBaseBy, ObjValues, AnyObjOrArr, AnyArr } from '../typings/types';
44

55
interface GroupBy {
6-
<O, KT extends string>(fn: ObjBaseBy<O, KT>, obj: O): Record<KT, O[keyof O][]>;
7-
<K extends string, V, KT extends string>(fn: ObjBase<K, V, KT>): <O extends Record<K, V>>(
8-
obj: O
9-
) => Record<KT, O[keyof O][]>;
6+
<Fn extends ObjOrArrBaseBy<AnyObjOrArr, any>>(fn: Fn):
7+
<Input extends AnyObjOrArr>(obj: Input) => Input extends AnyArr
8+
? Record<ReturnType<Fn>, Input>
9+
: Record<ReturnType<Fn>, ObjValues<Input>[]>;
10+
11+
<Input extends AnyObjOrArr, Fn extends ObjOrArrBaseBy<Input, any>>(fn: Fn, obj: Input): Input extends AnyArr
12+
? Record<ReturnType<Fn>, Input>
13+
: Record<ReturnType<Fn>, ObjValues<Input>[]>;
1014
}
1115

1216
/**

src/typings/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
export type Ord = number | string | boolean;
22

3+
export type AnyObj = Record<any, any>;
4+
export type AnyArr = any[] | readonly any[];
5+
export type AnyObjOrArr = AnyObj | AnyArr;
6+
37
export type ArrBase<T, R> = (value: T, index: number, arr: ArrayLike<T>) => R;
48
export type ObjBase<K extends Prop | Paths, V, R> = (value: V, key: K, obj: Record<K extends Prop ? K : any, V>) => R;
59
export type ObjBaseBy<O extends Record<any, any>, R> = (value: O[keyof O], key: keyof O & string, obj: O) => R;
10+
export type ObjOrArrBaseBy<Input extends AnyObjOrArr, R> = Input extends AnyArr
11+
? (value: ArrValues<Input>, key: number, obj: Input) => R
12+
: (value: ObjValues<Input>, key: keyof Input, obj: Input) => R;
613

714
export type CompareFunc<T, R extends Ord> = (a: T, b: T) => R;
815
export type OrdFunc<T, R extends Ord> = (item: T) => R;
@@ -35,6 +42,16 @@ export type Pattern = RegExp | string;
3542

3643
export type ReplaceType<O, K extends Prop, V> = Pick<O, Exclude<keyof O, K>> & { [p in K]: V };
3744

45+
/**
46+
* Obtain the values type of a array
47+
*/
48+
export type ArrValues<A extends AnyArr> = A[number];
49+
50+
/**
51+
* Obtain the values type of a object
52+
*/
53+
export type ObjValues<O extends AnyObj> = O[keyof O];
54+
3855
// @see https://gist.github.com/donnut/fd56232da58d25ceecf1, comment by @albrow
3956
export interface CurriedTypeGuard2<T1, T2, R extends T2> {
4057
(t1: T1): (t2: T2) => t2 is R;

0 commit comments

Comments
 (0)