From e1c95b34263537d43c30937feffead8971ef396b Mon Sep 17 00:00:00 2001 From: Trevor Robinson Date: Thu, 16 Apr 2015 21:48:14 -0500 Subject: [PATCH] [changed] Renamed constants to styleMaps and added styleMaps.addStyle() --- src/BootstrapMixin.js | 18 +++++++++--------- src/Col.js | 6 +++--- src/Glyphicon.js | 4 ++-- src/index.js | 4 ++-- src/{constants.js => styleMaps.js} | 7 ++++++- src/utils/CustomPropTypes.js | 25 ++++++++++++++++++++++++- test/BootstrapMixinSpec.js | 11 +++++++++++ test/CustomPropTypesSpec.js | 22 ++++++++++++++++++++++ 8 files changed, 79 insertions(+), 18 deletions(-) rename src/{constants.js => styleMaps.js} (97%) diff --git a/src/BootstrapMixin.js b/src/BootstrapMixin.js index 00eb552f99..f158c58bc8 100644 --- a/src/BootstrapMixin.js +++ b/src/BootstrapMixin.js @@ -1,28 +1,28 @@ -import React from 'react'; -import constants from './constants'; +import styleMaps from './styleMaps'; +import CustomPropTypes from './utils/CustomPropTypes'; const BootstrapMixin = { propTypes: { - bsClass: React.PropTypes.oneOf(Object.keys(constants.CLASSES)), - bsStyle: React.PropTypes.oneOf(Object.keys(constants.STYLES)), - bsSize: React.PropTypes.oneOf(Object.keys(constants.SIZES)) + bsClass: CustomPropTypes.keyOf(styleMaps.CLASSES), + bsStyle: CustomPropTypes.keyOf(styleMaps.STYLES), + bsSize: CustomPropTypes.keyOf(styleMaps.SIZES) }, getBsClassSet() { let classes = {}; - let bsClass = this.props.bsClass && constants.CLASSES[this.props.bsClass]; + let bsClass = this.props.bsClass && styleMaps.CLASSES[this.props.bsClass]; if (bsClass) { classes[bsClass] = true; let prefix = bsClass + '-'; - let bsSize = this.props.bsSize && constants.SIZES[this.props.bsSize]; + let bsSize = this.props.bsSize && styleMaps.SIZES[this.props.bsSize]; if (bsSize) { classes[prefix + bsSize] = true; } - let bsStyle = this.props.bsStyle && constants.STYLES[this.props.bsStyle]; + let bsStyle = this.props.bsStyle && styleMaps.STYLES[this.props.bsStyle]; if (this.props.bsStyle) { classes[prefix + bsStyle] = true; } @@ -32,7 +32,7 @@ const BootstrapMixin = { }, prefixClass(subClass) { - return constants.CLASSES[this.props.bsClass] + '-' + subClass; + return styleMaps.CLASSES[this.props.bsClass] + '-' + subClass; } }; diff --git a/src/Col.js b/src/Col.js index 23ae471ad1..0898e10b2e 100644 --- a/src/Col.js +++ b/src/Col.js @@ -1,6 +1,6 @@ import React from 'react'; import classNames from 'classnames'; -import constants from './constants'; +import styleMaps from './styleMaps'; const Col = React.createClass({ propTypes: { @@ -33,8 +33,8 @@ const Col = React.createClass({ let ComponentClass = this.props.componentClass; let classes = {}; - Object.keys(constants.SIZES).forEach(function (key) { - let size = constants.SIZES[key]; + Object.keys(styleMaps.SIZES).forEach(function (key) { + let size = styleMaps.SIZES[key]; let prop = size; let classPart = size + '-'; diff --git a/src/Glyphicon.js b/src/Glyphicon.js index bb3e41ea19..e406feaa31 100644 --- a/src/Glyphicon.js +++ b/src/Glyphicon.js @@ -1,13 +1,13 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import constants from './constants'; +import styleMaps from './styleMaps'; const Glyphicon = React.createClass({ mixins: [BootstrapMixin], propTypes: { - glyph: React.PropTypes.oneOf(constants.GLYPHS).isRequired + glyph: React.PropTypes.oneOf(styleMaps.GLYPHS).isRequired }, getDefaultProps() { diff --git a/src/index.js b/src/index.js index b7e54269fb..ab8c3f01c2 100644 --- a/src/index.js +++ b/src/index.js @@ -47,7 +47,7 @@ import Table from './Table'; import TabPane from './TabPane'; import Tooltip from './Tooltip'; import Well from './Well'; -import constants from './constants'; +import styleMaps from './styleMaps'; export default { Accordion, @@ -99,5 +99,5 @@ export default { TabPane, Tooltip, Well, - constants + styleMaps }; diff --git a/src/constants.js b/src/styleMaps.js similarity index 97% rename from src/constants.js rename to src/styleMaps.js index 02fdc0d057..83435899c6 100644 --- a/src/constants.js +++ b/src/styleMaps.js @@ -1,4 +1,4 @@ -export default { +const styleMaps = { CLASSES: { 'alert': 'alert', 'button': 'btn', @@ -31,6 +31,9 @@ export default { 'tabs': 'tabs', 'pills': 'pills' }, + addStyle: function(name) { + styleMaps.STYLES[name] = name; + }, SIZES: { 'large': 'lg', 'medium': 'md', @@ -299,3 +302,5 @@ export default { 'menu-up' ] }; + +export default styleMaps; diff --git a/src/utils/CustomPropTypes.js b/src/utils/CustomPropTypes.js index 73984de7df..c56a0a4e8a 100644 --- a/src/utils/CustomPropTypes.js +++ b/src/utils/CustomPropTypes.js @@ -13,7 +13,16 @@ let CustomPropTypes = { * @param componentName * @returns {Error|undefined} */ - mountable: createMountableChecker() + mountable: createMountableChecker(), + /** + * Checks whether a prop matches a key of an associated object + * + * @param props + * @param propName + * @param componentName + * @returns {Error|undefined} + */ + keyOf: createKeyOfChecker }; /** @@ -57,4 +66,18 @@ function createMountableChecker() { return createChainableTypeChecker(validate); } +function createKeyOfChecker(obj) { + function validate(props, propName, componentName) { + let propValue = props[propName]; + if (!obj.hasOwnProperty(propValue)) { + let valuesString = JSON.stringify(Object.keys(obj)); + return new Error( + `Invalid prop '${propName}' of value '${propValue}' ` + + `supplied to '${componentName}', expected one of ${valuesString}.` + ); + } + } + return createChainableTypeChecker(validate); +} + export default CustomPropTypes; diff --git a/test/BootstrapMixinSpec.js b/test/BootstrapMixinSpec.js index bf6d4fd628..dea0bac9ce 100644 --- a/test/BootstrapMixinSpec.js +++ b/test/BootstrapMixinSpec.js @@ -1,6 +1,7 @@ import React from 'react'; import ReactTestUtils from 'react/lib/ReactTestUtils'; import BootstrapMixin from '../src/BootstrapMixin'; +import styleMaps from '../src/styleMaps'; let Component; @@ -195,5 +196,15 @@ describe('BootstrapMixin', function () { ); assert.equal(instance.prefixClass('title'), 'btn-title'); }); + + it('should return "btn btn-wacky"', function () { + styleMaps.addStyle('wacky'); + let instance = ReactTestUtils.renderIntoDocument( + + content + + ); + assert.deepEqual(instance.getBsClassSet(), {'btn': true, 'btn-wacky': true}); + }); }); }); diff --git a/test/CustomPropTypesSpec.js b/test/CustomPropTypesSpec.js index 89031672f0..9a0b94deb2 100644 --- a/test/CustomPropTypesSpec.js +++ b/test/CustomPropTypesSpec.js @@ -25,4 +25,26 @@ describe('CustomPropTypes', function () { assert.isUndefined(validate(ReactTestUtils.renderIntoDocument(
))); }); }); + + describe('keyOf', function () { + let obj = {'foo': 1}; + function validate(prop) { + return CustomPropTypes.keyOf(obj)({p: prop}, 'p', 'Component'); + } + function validateRequired(prop) { + return CustomPropTypes.keyOf(obj).isRequired({p: prop}, 'p', 'Component'); + } + + it('Should return error with non-key values', function() { + assert.instanceOf(validateRequired(), Error); + assert.instanceOf(validateRequired(null), Error); + assert.instanceOf(validate('bar'), Error); + }); + it('Should return undefined with key values', function() { + assert.isUndefined(validate()); + assert.isUndefined(validate('foo')); + obj.bar = 2; + assert.isUndefined(validate('bar')); + }); + }); });