forked from reduxjs/redux
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompose.spec.ts
140 lines (128 loc) · 4.17 KB
/
compose.spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { compose } from '..'
describe('Utils', () => {
describe('compose', () => {
it('composes from right to left', () => {
const double = (x: number) => x * 2
const square = (x: number) => x * x
expect(compose(square)(5)).toBe(25)
const flawedDoubleSquare = compose(
square,
// double
y => y * 2
) // `flawedDoubleSquare` gets inferred as `(y: any) => number`... This is bad.
expect(flawedDoubleSquare(5)).toBe(100)
expect(
compose(
// double, // `y` is inferred as `any`... This is bad. No inference propagated through functions.
y => y * 2,
// square,
x => x * x, // `x` is inferred as `any`... This is bad. No inference propagated through functions.
double
)(5)
).toBe(200)
})
it('composes functions from right to left', () => {
// I could have pointed out more inference problems in this test, but they would have been redudant
// Look at the other tests
const a = (next: (x: string) => string) => (x: string) => next(x + 'a')
const b = (next: (x: string) => string) => (x: string) => next(x + 'b')
const c = (next: (x: string) => string) => (x: string) => next(x + 'c')
const final = (x: string) => x
expect(
compose(
a,
b,
c
)(final)('')
).toBe('abc')
expect(
compose(
b,
c,
a
)(final)('')
).toBe('bca')
expect(
compose(
c,
a,
b
)(final)('')
).toBe('cab')
})
it('throws at runtime if argument is not a function', () => {
type sFunc = (x: number, y: number) => number
const square = (x: number, _: number) => x * x
const add = (x: number, y: number) => x + y
expect(() =>
compose(
square,
add,
(false as unknown) as sFunc
)(1, 2)
).toThrow()
expect(() =>
compose(
square,
add,
undefined
)(1, 2)
).toThrow()
expect(() =>
compose(
square,
add,
(true as unknown) as sFunc
)(1, 2)
).toThrow()
expect(() =>
compose(
square,
add,
(NaN as unknown) as sFunc
)(1, 2)
).toThrow()
expect(() =>
compose(
square,
add,
('42' as unknown) as sFunc
)(1, 2)
).toThrow()
})
it('can be seeded with multiple arguments', () => {
const square = (x: number, _: number) => x * x
const add = (x: number, y: number) => x + y
// old expect assertion... commented out, but left here for reference
// expect(
// compose(
// square,
// add
// )(1, 2)
// ).toBe(9)
expect(
compose(
// any function here could easily cause a runtime error
// square, // commented out for sake of above argument ^
// add
(x, y) => x + y // `x` and `y` are both inferred as `any`... This is bad.
)(1, 2) // this entire function is now `(a: any) => any` and completely unsafe, susceptible to runtime errors because of failed inference.
).toBe(3) // expected value modified to match changed expect assertion so that tests pass
})
it('returns the first given argument if given no functions', () => {
// doesn't work, has to be commented out due to a type error
// expect(compose<number>()(1, 2)).toBe(1)
// without explicitly passing the generic type param
const res0 = compose()(1, 2) // `res0` is inferred as `never`... This is bad. There are no overloads or type inference to make this work.
expect(res0).toBe(1)
const res1 = compose()('zero', 1, 2) // again, `res1` is inferred as `never`... This is bad.
expect(res1).toBe('zero')
expect(compose()(3)).toBe(3) // `3` is inferred as `never`
expect(compose()(undefined)).toBe(undefined) // `undefined` is inferred as `never`
})
it('returns the first function if given only one', () => {
const fn = () => {}
expect(compose(fn)).toBe(fn)
})
})
})