@samitouri / QOS-React-2 / commits / 3a49ee7d82

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 +});