Skip to content

Commit 3a49ee7

Browse files
jgebhardtzpao
authored andcommitted
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.
1 parent 0a5c222 commit 3a49ee7

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

src/addons/link/ReactLink.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
* consumption of ReactLink easier; see LinkedValueUtils and LinkedStateMixin.
4343
*/
4444

45+
var React = require('React');
46+
4547
/**
4648
* @param {*} value current value of the link
4749
* @param {function} requestChange callback to request a change
@@ -51,4 +53,26 @@ function ReactLink(value, requestChange) {
5153
this.requestChange = requestChange;
5254
}
5355

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+
5478
module.exports = ReactLink;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});

0 commit comments

Comments
 (0)