Add typed ReactLink to ReactProps
Adds a PropType that checks for proper use of the ReactLink API and optionally validates the type of value passed in via the link. Basically, it's a wrapper around PropTypes.shape that hides the implementation of ReactLink.
Jonas Gebhardt committed
Apr 18, 2014 at 12:47 UTC
3a49ee7d82f6b3481012e81bcd18506eea150641
2 files changed
+186
src/addons/link/ReactLink.js
+24
@@ -42,6 +42,8 @@
42
* consumption of ReactLink easier; see LinkedValueUtils and LinkedStateMixin.
43
*/
44
45
+var React = require('React');
46
+
47
/**
48
* @param {*} value current value of the link
49
* @param {function} requestChange callback to request a change
@@ -51,4 +53,26 @@ function ReactLink(value, requestChange) {
53
this.requestChange = requestChange;
54
}
55
56
+/**
57
+ * Creates a PropType that enforces the ReactLink API and optionally checks the
58
+ * type of the value being passed inside the link. Example:
59
+ *
60
+ * MyComponent.propTypes = {
61
+ * tabIndexLink: ReactLink.PropTypes.link(React.PropTypes.number)
62
+ * }
63
+ */
64
+function createLinkTypeChecker(linkType) {
65
+ var shapes = {
66
+ value: typeof linkType === 'undefined'
67
+ ? React.PropTypes.any.isRequired
68
+ : linkType.isRequired,
69
+ requestChange: React.PropTypes.func.isRequired
70
+ };
71
+ return React.PropTypes.shape(shapes);
72
+}
73
+
74
+ReactLink.PropTypes = {
75
+ link: createLinkTypeChecker
76
+};
77
+
78
module.exports = ReactLink;
src/addons/link/__tests__/ReactLinkPropTypes-test.js
new
+162
@@ -0,0 +1,162 @@
1
+/**
2
+ * Copyright 2013-2014 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @jsx React.DOM
17
+ * @emails react-core
18
+ */
19
+
20
+"use strict";
21
+
22
+var emptyFunction = require('emptyFunction');
23
+var LinkPropTypes = require('ReactLink').PropTypes;
24
+var React = require('React');
25
+var ReactPropTypeLocations = require('ReactPropTypeLocations');
26
+
27
+var invalidMessage = 'Invalid prop `testProp` supplied to `testComponent`.';
28
+var requiredMessage =
29
+ 'Required prop `testProp` was not specified in `testComponent`.';
30
+
31
+function typeCheckFail(declaration, value, message) {
32
+ var props = {testProp: value};
33
+ var error = declaration(
34
+ props,
35
+ 'testProp',
36
+ 'testComponent',
37
+ ReactPropTypeLocations.prop
38
+ );
39
+ expect(error instanceof Error).toBe(true);
40
+ expect(error.message).toBe(message);
41
+}
42
+
43
+function typeCheckPass(declaration, value) {
44
+ var props = {testProp: value};
45
+ var error = declaration(
46
+ props,
47
+ 'testProp',
48
+ 'testComponent',
49
+ ReactPropTypeLocations.prop
50
+ );
51
+ expect(error).toBe(undefined);
52
+}
53
+
54
+describe('ReactLink', function() {
55
+ it('should fail if the argument does not implement the Link API', function() {
56
+ typeCheckFail(
57
+ LinkPropTypes.link(React.PropTypes.any),
58
+ {},
59
+ 'Required prop `value` was not specified in `testComponent`.'
60
+ );
61
+ typeCheckFail(
62
+ LinkPropTypes.link(React.PropTypes.any),
63
+ {value: 123},
64
+ 'Required prop `requestChange` was not specified in `testComponent`.'
65
+ );
66
+ typeCheckFail(
67
+ LinkPropTypes.link(React.PropTypes.any),
68
+ {requestChange: emptyFunction},
69
+ 'Required prop `value` was not specified in `testComponent`.'
70
+ );
71
+ typeCheckFail(
72
+ LinkPropTypes.link(React.PropTypes.any),
73
+ {value: null, requestChange: null},
74
+ 'Required prop `value` was not specified in `testComponent`.'
75
+ );
76
+ });
77
+
78
+ it('should allow valid links even if no type was specified', function() {
79
+ typeCheckPass(
80
+ LinkPropTypes.link(),
81
+ {value: 42, requestChange: emptyFunction}
82
+ );
83
+ typeCheckPass(
84
+ LinkPropTypes.link(),
85
+ {value: {}, requestChange: emptyFunction
86
+ });
87
+ });
88
+
89
+ it('should allow no link to be passed at all', function() {
90
+ typeCheckPass(
91
+ LinkPropTypes.link(React.PropTypes.string),
92
+ undefined
93
+ );
94
+ });
95
+
96
+ it('should allow valid links with correct value format', function() {
97
+ typeCheckPass(
98
+ LinkPropTypes.link(React.PropTypes.any),
99
+ {value: 42, requestChange: emptyFunction}
100
+ );
101
+ typeCheckPass(
102
+ LinkPropTypes.link(React.PropTypes.number),
103
+ {value: 42, requestChange: emptyFunction}
104
+ );
105
+ typeCheckPass(
106
+ LinkPropTypes.link(React.PropTypes.renderable),
107
+ {value: 42, requestChange: emptyFunction}
108
+ );
109
+ });
110
+
111
+ it('should fail if the link`s value type does not match', function() {
112
+ typeCheckFail(
113
+ LinkPropTypes.link(React.PropTypes.string),
114
+ {value: 123, requestChange: emptyFunction},
115
+ 'Invalid prop `value` of type `number` supplied to `testComponent`,' +
116
+ ' expected `string`.'
117
+ );
118
+ });
119
+
120
+ it('should be implicitly optional and not warn without values', function() {
121
+ typeCheckPass(LinkPropTypes.link(), null);
122
+ typeCheckPass(LinkPropTypes.link(), undefined);
123
+ typeCheckPass(LinkPropTypes.link(React.PropTypes.string), null);
124
+ typeCheckPass(LinkPropTypes.link(React.PropTypes.string), undefined);
125
+ });
126
+
127
+ it('should warn for missing required values', function() {
128
+ typeCheckFail(LinkPropTypes.link().isRequired, null, requiredMessage);
129
+ typeCheckFail(LinkPropTypes.link().isRequired, undefined, requiredMessage);
130
+ typeCheckFail(
131
+ LinkPropTypes.link(React.PropTypes.string).isRequired,
132
+ null,
133
+ requiredMessage
134
+ );
135
+ typeCheckFail(
136
+ LinkPropTypes.link(React.PropTypes.string).isRequired,
137
+ undefined,
138
+ requiredMessage
139
+ );
140
+ });
141
+
142
+ it('should be compatible with React.PropTypes.oneOfType', function() {
143
+ typeCheckPass(
144
+ React.PropTypes.oneOfType([LinkPropTypes.link(React.PropTypes.number)]),
145
+ {value: 123, requestChange: emptyFunction}
146
+ );
147
+ typeCheckFail(
148
+ React.PropTypes.oneOfType([LinkPropTypes.link(React.PropTypes.number)]),
149
+ 123,
150
+ invalidMessage
151
+ );
152
+ typeCheckPass(
153
+ LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])),
154
+ {value: 123, requestChange: emptyFunction}
155
+ );
156
+ typeCheckFail(
157
+ LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])),
158
+ {value: 'imastring', requestChange: emptyFunction},
159
+ 'Invalid prop `value` supplied to `testComponent`.'
160
+ );
161
+ });
162
+});