diff --git a/package.json b/package.json index b2d8774aab3c5..b8dbc42f2f65b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "browserify": "^12.0.1", "bundle-collapser": "^1.1.1", "coffee-script": "^1.8.0", + "core-js": "^2.2.1", "coveralls": "^2.11.6", "del": "^2.0.2", "derequire": "^2.0.3", diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index 99c036496aaba..4d9ac1d846107 100644 --- a/src/isomorphic/classic/types/ReactPropTypes.js +++ b/src/isomorphic/classic/types/ReactPropTypes.js @@ -73,6 +73,7 @@ var ReactPropTypes = { number: createPrimitiveTypeChecker('number'), object: createPrimitiveTypeChecker('object'), string: createPrimitiveTypeChecker('string'), + symbol: createPrimitiveTypeChecker('symbol'), any: createAnyTypeChecker(), arrayOf: createArrayOfTypeChecker, @@ -406,6 +407,25 @@ function isNode(propValue) { } } +function isSymbol(propType, propValue) { + // Native Symbol. + if (propType === 'symbol') { + return true; + } + + // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol' + if (propValue['@@toStringTag'] === 'Symbol') { + return true; + } + + // Fallback for non-spec compliant Symbols which are polyfilled. + if (typeof Symbol === 'function' && propValue instanceof Symbol) { + return true; + } + + return false; +} + // Equivalent of `typeof` but with special handling for array and regexp. function getPropType(propValue) { var propType = typeof propValue; @@ -418,6 +438,9 @@ function getPropType(propValue) { // passes PropTypes.object. return 'object'; } + if (isSymbol(propType, propValue)) { + return 'symbol'; + } return propType; } diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index abfa976b00fe3..68c233ceb9d11 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -80,6 +80,12 @@ describe('ReactPropTypes', function() { 'Invalid prop `testProp` of type `object` supplied to ' + '`testComponent`, expected `string`.' ); + typeCheckFail( + PropTypes.string, + Symbol(), + 'Invalid prop `testProp` of type `symbol` supplied to ' + + '`testComponent`, expected `string`.' + ); }); it('should fail date and regexp correctly', function() { @@ -106,6 +112,7 @@ describe('ReactPropTypes', function() { typeCheckPass(PropTypes.object, {}); typeCheckPass(PropTypes.object, new Date()); typeCheckPass(PropTypes.object, /please/); + typeCheckPass(PropTypes.symbol, Symbol()); }); it('should be implicitly optional and not warn without values', function() { @@ -124,6 +131,7 @@ describe('ReactPropTypes', function() { typeCheckPass(PropTypes.any, 0); typeCheckPass(PropTypes.any, 'str'); typeCheckPass(PropTypes.any, []); + typeCheckPass(PropTypes.any, Symbol()); }); it('should be implicitly optional and not warn without values', function() { @@ -150,6 +158,7 @@ describe('ReactPropTypes', function() { typeCheckPass(PropTypes.arrayOf(PropTypes.number), [1, 2, 3]); typeCheckPass(PropTypes.arrayOf(PropTypes.string), ['a', 'b', 'c']); typeCheckPass(PropTypes.arrayOf(PropTypes.oneOf(['a', 'b'])), ['a', 'b']); + typeCheckPass(PropTypes.arrayOf(PropTypes.symbol), [Symbol(), Symbol()]); }); it('should support arrayOf with complex types', function() { @@ -487,6 +496,10 @@ describe('ReactPropTypes', function() { PropTypes.objectOf(PropTypes.oneOf(['a', 'b'])), {a: 'a', b: 'b'} ); + typeCheckPass( + PropTypes.objectOf(PropTypes.symbol), + {a: Symbol(), b: Symbol(), c: Symbol()} + ); }); it('should support objectOf with complex types', function() { @@ -542,6 +555,12 @@ describe('ReactPropTypes', function() { 'Invalid prop `testProp` of type `string` supplied to ' + '`testComponent`, expected an object.' ); + typeCheckFail( + PropTypes.objectOf(PropTypes.symbol), + Symbol(), + 'Invalid prop `testProp` of type `symbol` supplied to ' + + '`testComponent`, expected an object.' + ); }); it('should not warn when passing an empty object', function() { @@ -779,6 +798,36 @@ describe('ReactPropTypes', function() { }); }); + describe('Symbol Type', function() { + it('should warn for non-symbol', function() { + typeCheckFail( + PropTypes.symbol, + 'hello', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected `symbol`.' + ); + typeCheckFail( + PropTypes.symbol, + function() { }, + 'Invalid prop `testProp` of type `function` supplied to ' + + '`testComponent`, expected `symbol`.' + ); + typeCheckFail( + PropTypes.symbol, + { + '@@toStringTag': 'Katana', + }, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected `symbol`.' + ); + }); + + it('should not warn for a polyfilled Symbol', function() { + var CoreSymbol = require('core-js/library/es6/symbol'); + typeCheckPass(PropTypes.symbol, CoreSymbol('core-js')); + }); + }); + describe('Custom validator', function() { beforeEach(function() { jest.resetModuleRegistry();