@samitouri / QOS-React-2 / commits / 9ade3c26d3

Introduce PropTypes.shape

This diff introduces PropType.shape which lets you type an object. PropType.object was already defined and since it isn't a function I couldn't really overload the meaning to also accept a type list. Instead of doing hackery, I decided to name it `shape`. An example where this could be used is style: ``` propTypes: { style: PropTypes.shape({ color: PropStyle.string, fontSize: PropTypes.number }) } ```

Christopher Chedeau committed Jan 15, 2014 at 16:26 UTC 9ade3c26d316a67d37406a662d5c3da43c9310d0
2 files changed +104 -4
src/core/ReactPropTypes.js
+41 -4
@@ -81,6 +81,7 @@ var Props = {
81 object: createPrimitiveTypeChecker('object'),
82 string: createPrimitiveTypeChecker('string'),
83
84 + shape: createShapeTypeChecker,
85 oneOf: createEnumTypeChecker,
86 oneOfType: createUnionTypeChecker,
87
@@ -128,14 +129,20 @@ function isRenderable(propValue) {
129 }
130 }
131
132 +// Equivalent of typeof but with special handling for arrays
133 +function getPropType(propValue) {
134 + var propType = typeof propValue;
135 + if (propType === 'object' && Array.isArray(propValue)) {
136 + return 'array';
137 + }
138 + return propType;
139 +}
140 +
141 function createPrimitiveTypeChecker(expectedType) {
142 function validatePrimitiveType(
143 shouldThrow, propValue, propName, componentName, location
144 ) {
135 - var propType = typeof propValue;
136 - if (propType === 'object' && Array.isArray(propValue)) {
137 - propType = 'array';
138 - }
145 + var propType = getPropType(propValue);
146 var isValid = propType === expectedType;
147 if (!shouldThrow) {
148 return isValid;
@@ -174,6 +181,36 @@ function createEnumTypeChecker(expectedValues) {
181 return createChainableTypeChecker(validateEnumType);
182 }
183
184 +function createShapeTypeChecker(shapeTypes) {
185 + function validateShapeType(
186 + shouldThrow, propValue, propName, componentName, location
187 + ) {
188 + var propType = getPropType(propValue);
189 + var isValid = propType === 'object';
190 + if (isValid) {
191 + for (var key in shapeTypes) {
192 + var checker = shapeTypes[key];
193 + if (checker) {
194 + // It's going to throw an exception if it doesn't pass
195 + checker(propValue, key, componentName, location);
196 + }
197 + }
198 + }
199 + if (!shouldThrow) {
200 + return isValid;
201 + }
202 + invariant(
203 + isValid,
204 + 'Invalid %s `%s` of type `%s` supplied to `%s`, expected `object`.',
205 + ReactPropTypeLocationNames[location],
206 + propName,
207 + propType,
208 + componentName
209 + );
210 + }
211 + return createChainableTypeChecker(validateShapeType);
212 +}
213 +
214 function createInstanceTypeChecker(expectedClass) {
215 function validateInstanceType(
216 shouldThrow, propValue, propName, componentName, location
src/core/__tests__/ReactPropTypes-test.js
+63
@@ -141,6 +141,69 @@ describe('Enum Types', function() {
141 });
142 });
143
144 +describe('Shape Types', function() {
145 + beforeEach(function() {
146 + require('mock-modules').dumpCache();
147 + });
148 +
149 + it("should throw for non objects", function() {
150 + expect(typeCheck(Props.shape({}), 'some string')).toThrow(
151 + 'Invariant Violation: Invalid prop `testProp` of type `string` ' +
152 + 'supplied to `testComponent`, expected `object`.'
153 + );
154 + expect(typeCheck(Props.shape({}), ['array'])).toThrow(
155 + 'Invariant Violation: Invalid prop `testProp` of type `array` ' +
156 + 'supplied to `testComponent`, expected `object`.'
157 + );
158 + });
159 +
160 + it("should not throw for empty values", function() {
161 + expect(typeCheck(Props.shape({}), undefined)).not.toThrow();
162 + expect(typeCheck(Props.shape({}), null)).not.toThrow();
163 + expect(typeCheck(Props.shape({}), {})).not.toThrow();
164 + });
165 +
166 + it("should throw for empty required value", function() {
167 + expect(typeCheck(Props.shape({}).isRequired, undefined)).toThrow(
168 + 'Invariant Violation: Required prop `testProp` was not specified in ' +
169 + '`testComponent`.'
170 + );
171 + expect(typeCheck(Props.shape({}).isRequired, null)).toThrow(
172 + 'Invariant Violation: Required prop `testProp` was not specified in ' +
173 + '`testComponent`.'
174 + );
175 + expect(typeCheck(Props.shape({}).isRequired, {})).not.toThrow();
176 + });
177 +
178 + it("should not throw for non specified types", function() {
179 + expect(typeCheck(Props.shape({}), {key: 1})).not.toThrow();
180 + });
181 +
182 + it("should not throw for valid types", function() {
183 + expect(typeCheck(Props.shape({
184 + key: Props.number
185 + }), {key: 1})).not.toThrow();
186 + });
187 +
188 + it("should throw for required valid types", function() {
189 + expect(typeCheck(Props.shape({
190 + key: Props.number.isRequired
191 + }), {})).toThrow(
192 + 'Invariant Violation: Required prop `key` was not specified in ' +
193 + '`testComponent`.'
194 + );
195 + });
196 +
197 + it("should throw for invalid key types", function() {
198 + expect(typeCheck(Props.shape({
199 + key: Props.number
200 + }), {key: 'abc'})).toThrow(
201 + 'Invariant Violation: Invalid prop `key` of type `string` supplied to ' +
202 + '`testComponent`, expected `number`.'
203 + );
204 + });
205 +});
206 +
207 describe('Instance Types', function() {
208 beforeEach(function() {
209 require('mock-modules').dumpCache();