-
Notifications
You must be signed in to change notification settings - Fork 29.9k
/
prop-types-tests.ts
202 lines (179 loc) · 7.68 KB
/
prop-types-tests.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import { ReactElement, ReactNode } from "react";
import * as PropTypes from "prop-types";
declare const uniqueType: unique symbol;
class TestClass { }
interface Props {
any?: any;
array: string[];
bool: boolean;
element: ReactElement;
func(foo: string): void;
node?: ReactNode;
requiredNode: NonNullable<ReactNode>;
number: number;
object: object;
string: string;
symbol: symbol;
instanceOf: TestClass;
oneOf: 'a' | 'b' | 'c';
oneOfType: string | boolean | {
foo?: string;
bar: number;
};
numberOrFalse: false | number;
nodeOrRenderFn?: ReactNode | (() => ReactNode);
arrayOf: boolean[];
objectOf: { [K: string]: number };
shape: {
foo: string;
bar?: boolean;
baz?: any
};
optionalNumber?: number | null;
customProp?: typeof uniqueType;
component: PropTypes.ReactComponentLike;
}
const innerProps = {
foo: PropTypes.string.isRequired,
bar: PropTypes.bool,
baz: PropTypes.any
};
const arrayOfTypes = [PropTypes.string, PropTypes.bool, PropTypes.shape({
foo: PropTypes.string,
bar: PropTypes.number.isRequired
})];
type PropTypesMap = PropTypes.ValidationMap<Props>;
// TS checking
const propTypes: PropTypesMap = {
any: PropTypes.any,
array: PropTypes.array.isRequired,
bool: PropTypes.bool.isRequired,
element: PropTypes.element.isRequired,
func: PropTypes.func.isRequired,
node: PropTypes.node,
requiredNode: PropTypes.node.isRequired,
number: PropTypes.number.isRequired,
object: PropTypes.object.isRequired,
string: PropTypes.string.isRequired,
symbol: PropTypes.symbol.isRequired,
instanceOf: PropTypes.instanceOf(TestClass).isRequired,
oneOf: PropTypes.oneOf<'a' | 'b' | 'c'>(['a', 'b', 'c']).isRequired,
oneOfType: PropTypes.oneOfType(arrayOfTypes).isRequired,
numberOrFalse: PropTypes.oneOfType([PropTypes.oneOf<false>([false]), PropTypes.number]).isRequired,
// The generic function type (() => any) is assignable to ReactNode because ReactNode extends the empty object type {}
// Which widens the array literal of validators to just Array<Requireable<() => any>>
// It's too risky to change ReactNode to exclude {} even though it's invalid, as it's required for children-as-function props to work
// So we assert the explicit tuple type
nodeOrRenderFn: PropTypes.oneOfType([PropTypes.node, PropTypes.func] as [PropTypes.Requireable<ReactNode>, PropTypes.Requireable<() => any>]),
arrayOf: PropTypes.arrayOf(PropTypes.bool.isRequired).isRequired,
objectOf: PropTypes.objectOf(PropTypes.number.isRequired).isRequired,
shape: PropTypes.shape(innerProps).isRequired,
optionalNumber: PropTypes.number,
customProp: (() => null) as PropTypes.Validator<typeof uniqueType | undefined>,
component: PropTypes.elementType.isRequired
};
// JS checking
const propTypesWithoutAnnotation = {
any: PropTypes.any,
array: PropTypes.array.isRequired,
bool: PropTypes.bool.isRequired,
element: PropTypes.element.isRequired,
func: PropTypes.func.isRequired,
node: PropTypes.node,
requiredNode: PropTypes.node.isRequired,
number: PropTypes.number.isRequired,
object: PropTypes.object.isRequired,
string: PropTypes.string.isRequired,
symbol: PropTypes.symbol.isRequired,
instanceOf: PropTypes.instanceOf(TestClass).isRequired,
// required generic specification because of array type widening
oneOf: PropTypes.oneOf<'a' | 'b' | 'c'>(['a', 'b', 'c']).isRequired,
oneOfType: PropTypes.oneOfType(arrayOfTypes).isRequired,
numberOrFalse: PropTypes.oneOfType([PropTypes.oneOf<false>([false]), PropTypes.number]).isRequired,
nodeOrRenderFn: PropTypes.oneOfType([PropTypes.node, PropTypes.func] as [PropTypes.Requireable<ReactNode>, PropTypes.Requireable<() => any>]),
arrayOf: PropTypes.arrayOf(PropTypes.bool.isRequired).isRequired,
objectOf: PropTypes.objectOf(PropTypes.number.isRequired).isRequired,
shape: PropTypes.shape(innerProps).isRequired,
optionalNumber: PropTypes.number,
customProp: (() => null) as PropTypes.Validator<typeof uniqueType | undefined>,
component: PropTypes.elementType.isRequired
};
const partialPropTypes = {
number: PropTypes.number.isRequired,
object: PropTypes.object.isRequired,
string: PropTypes.string.isRequired,
symbol: PropTypes.symbol.isRequired,
};
const outerPropTypes = {
props: PropTypes.shape(propTypes).isRequired
};
const outerPropTypesWithoutAnnotation = {
props: PropTypes.shape(propTypesWithoutAnnotation).isRequired
};
type ExtractedArrayProps = PropTypes.InferType<(typeof arrayOfTypes)[number]>;
type ExtractedInnerProps = PropTypes.InferProps<typeof innerProps>;
type ExtractedProps = PropTypes.InferProps<typeof propTypes>;
type ExtractedPropsFromOuterProps = PropTypes.InferProps<typeof outerPropTypes>['props'];
type ExtractedPartialProps = PropTypes.InferProps<typeof partialPropTypes>;
type ExtractedPropsWithoutAnnotation = PropTypes.InferProps<typeof propTypesWithoutAnnotation>;
type ExtractedPropsFromOuterPropsWithoutAnnotation = PropTypes.InferProps<typeof outerPropTypesWithoutAnnotation>['props'];
// $ExpectType true
type ExtractPropsMatch = ExtractedProps extends ExtractedPropsWithoutAnnotation ? true : false;
// $ExpectType true
type ExtractPropsMatch2 = ExtractedPropsWithoutAnnotation extends ExtractedProps ? true : false;
// $ExpectType true
type ExtractPropsMatch3 = ExtractedProps extends Props ? true : false;
// $ExpectType true
type ExtractPropsMatch4 = Props extends ExtractedPropsWithoutAnnotation ? true : false;
// $ExpectType true
type ExtractFromOuterPropsMatch = ExtractedPropsFromOuterProps extends ExtractedPropsFromOuterPropsWithoutAnnotation ? true : false;
// $ExpectType true
type ExtractFromOuterPropsMatch2 = ExtractedPropsFromOuterPropsWithoutAnnotation extends ExtractedPropsFromOuterProps ? true : false;
// $ExpectType true
type ExtractFromOuterPropsMatch3 = ExtractedPropsFromOuterProps extends Props ? true : false;
// $ExpectType true
type ExtractFromOuterPropsMatch4 = Props extends ExtractedPropsFromOuterPropsWithoutAnnotation ? true : false;
// $ExpectType false
type ExtractPropsMismatch = ExtractedPartialProps extends Props ? true : false;
PropTypes.checkPropTypes({ xs: PropTypes.array }, { xs: [] }, 'location', 'componentName');
PropTypes.resetWarningCache();
// This would be the type that JSX sees
type Defaultize<T, D> =
& Pick<T, Exclude<keyof T, keyof D>>
& Partial<Pick<T, Extract<keyof T, keyof D>>>
& Partial<Pick<D, Exclude<keyof D, keyof T>>>;
// This would be the type inside the component
type Undefaultize<T, D> =
& Pick<T, Exclude<keyof T, keyof D>>
& { [K in Extract<keyof T, keyof D>]-?: Exclude<T[K], undefined>; }
& Required<Pick<D, Exclude<keyof D, keyof T>>>;
const componentPropTypes = {
fi: PropTypes.func.isRequired,
foo: PropTypes.string,
bar: PropTypes.number.isRequired,
baz: PropTypes.bool,
bat: PropTypes.node
};
const componentDefaultProps = {
fi: () => null,
baz: false,
bat: ['This', 'is', 'a', 'string']
};
type DefaultizedProps = Defaultize<PropTypes.InferProps<typeof componentPropTypes>, typeof componentDefaultProps>;
type UndefaultizedProps = Undefaultize<PropTypes.InferProps<typeof componentPropTypes>, typeof componentDefaultProps>;
// $ExpectType true
type DefaultizedPropsTest = {
fi?: (...args: any[]) => any;
foo?: string | null;
bar: number;
baz?: boolean | null;
bat?: ReactNode;
} extends DefaultizedProps ? true : false;
// $ExpectType true
type UndefaultizedPropsTest = {
fi: (...args: any[]) => any;
foo?: string | null;
bar: number;
baz: boolean;
bat: Exclude<ReactNode, undefined>;
} extends UndefaultizedProps ? true : false;