+ const style = {
+ 'left': this.props.positionLeft,
+ 'top': this.props.positionTop,
+ ...this.props.style
+ };
+
+ const arrowStyle = {
+ 'left': this.props.arrowOffsetLeft,
+ 'top': this.props.arrowOffsetTop
+ };
-
- {children}
+ return (
+
+
+
+ {this.props.children}
);
}
-}
-
-Tooltip.propTypes = {
- /**
- * An html id attribute, necessary for accessibility
- * @type {string}
- * @required
- */
- id: isRequiredForA11y(
- React.PropTypes.oneOfType([
- React.PropTypes.string,
- React.PropTypes.number
- ])
- ),
-
- /**
- * The direction the tooltip is positioned towards
- */
- placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
-
- /**
- * The `left` position value for the tooltip
- */
- positionLeft: React.PropTypes.number,
- /**
- * The `top` position value for the tooltip
- */
- positionTop: React.PropTypes.number,
- /**
- * The `left` position value for the tooltip arrow
- */
- arrowOffsetLeft: React.PropTypes.oneOfType([
- React.PropTypes.number, React.PropTypes.string
- ]),
- /**
- * The `top` position value for the tooltip arrow
- */
- arrowOffsetTop: React.PropTypes.oneOfType([
- React.PropTypes.number, React.PropTypes.string
- ])
-};
+});
-Tooltip.defaultProps = {
- placement: 'right'
-};
+export default Tooltip;
diff --git a/src/Well.js b/src/Well.js
index 20398b040e..22d0f9cb69 100644
--- a/src/Well.js
+++ b/src/Well.js
@@ -1,18 +1,13 @@
import React from 'react';
import classNames from 'classnames';
-import BootstrapMixin from './BootstrapMixin';
-
-const Well = React.createClass({
- mixins: [BootstrapMixin],
-
- getDefaultProps() {
- return {
- bsClass: 'well'
- };
- },
+import bootstrapUtils, { bsSizes, bsClass } from './utils/bootstrapUtils';
+import { Sizes } from './styleMaps';
+@bsClass('well')
+@bsSizes([Sizes.LARGE, Sizes.SMALL])
+class Well extends React.Component {
render() {
- let classes = this.getBsClassSet();
+ let classes = bootstrapUtils.getClassSet(this.props);
return (
@@ -20,6 +15,6 @@ const Well = React.createClass({
);
}
-});
+}
export default Well;
diff --git a/src/index.js b/src/index.js
index 7461e38dcc..19c15517e3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,7 +3,6 @@ export Affix from './Affix';
export AffixMixin from './AffixMixin';
export Alert from './Alert';
export Badge from './Badge';
-export BootstrapMixin from './BootstrapMixin';
export Breadcrumb from './Breadcrumb';
export BreadcrumbItem from './BreadcrumbItem';
export Button from './Button';
@@ -51,7 +50,6 @@ export Row from './Row';
export SafeAnchor from './SafeAnchor';
export SplitButton from './SplitButton';
export SplitButton from './SplitButton';
-export styleMaps from './styleMaps';
export SubNav from './SubNav';
export Tab from './Tab';
export Table from './Table';
@@ -68,9 +66,10 @@ export * as FormControls from './FormControls';
import childrenValueInputValidation from './utils/childrenValueInputValidation';
import createChainedFunction from './utils/createChainedFunction';
import ValidComponentChildren from './utils/ValidComponentChildren';
-
+import bootstrapUtils from './utils/bootstrapUtils';
export const utils = {
+ bootstrapUtils,
childrenValueInputValidation,
createChainedFunction,
ValidComponentChildren
diff --git a/src/styleMaps.js b/src/styleMaps.js
index 4360d1a675..fc5297ab40 100644
--- a/src/styleMaps.js
+++ b/src/styleMaps.js
@@ -1,41 +1,15 @@
+
+let constant = obj => {
+ return Object.assign(
+ Object.create({
+ values() {
+ return Object.keys(this).map(k => this[k]);
+ }
+ }), obj);
+};
+
const styleMaps = {
- CLASSES: {
- 'alert': 'alert',
- 'button': 'btn',
- 'button-group': 'btn-group',
- 'button-toolbar': 'btn-toolbar',
- 'column': 'col',
- 'input-group': 'input-group',
- 'form': 'form',
- 'glyphicon': 'glyphicon',
- 'label': 'label',
- 'thumbnail': 'thumbnail',
- 'list-group-item': 'list-group-item',
- 'panel': 'panel',
- 'panel-group': 'panel-group',
- 'pagination': 'pagination',
- 'progress-bar': 'progress-bar',
- 'nav': 'nav',
- 'navbar': 'navbar',
- 'modal': 'modal',
- 'row': 'row',
- 'well': 'well'
- },
- STYLES: [
- 'default',
- 'primary',
- 'success',
- 'info',
- 'warning',
- 'danger',
- 'link',
- 'inline',
- 'tabs',
- 'pills'
- ],
- addStyle(name) {
- styleMaps.STYLES.push(name);
- },
+
SIZES: {
'large': 'lg',
'medium': 'md',
@@ -49,4 +23,23 @@ const styleMaps = {
GRID_COLUMNS: 12
};
+export const Sizes = constant({
+ LARGE: 'large',
+ MEDIUM: 'medium',
+ SMALL: 'small',
+ XSMALL: 'xsmall'
+});
+
+export const State = constant({
+ SUCCESS: 'success',
+ WARNING: 'warning',
+ DANGER: 'danger',
+ INFO: 'info'
+});
+
+export const DEFAULT = 'default';
+export const PRIMARY = 'primary';
+export const LINK = 'link';
+export const INVERSE = 'inverse';
+
export default styleMaps;
diff --git a/src/utils/bootstrapUtils.js b/src/utils/bootstrapUtils.js
new file mode 100644
index 0000000000..9cae75e759
--- /dev/null
+++ b/src/utils/bootstrapUtils.js
@@ -0,0 +1,152 @@
+import { PropTypes } from 'react';
+import styleMaps from '../styleMaps';
+import invariant from 'invariant';
+import warning from 'warning';
+
+function curry(fn) {
+ return (...args) => {
+ let last = args[args.length - 1];
+ if (typeof last === 'function') {
+ return fn(...args);
+ }
+ return Component => fn(...args, Component);
+ };
+}
+
+function prefix(props = {}, variant) {
+ invariant((props.bsClass || '').trim(), 'A `bsClass` prop is required for this component');
+ return props.bsClass + (variant ? '-' + variant : '');
+}
+
+export let bsClass = curry((defaultClass, Component) => {
+ let propTypes = Component.propTypes || (Component.propTypes = {});
+ let defaultProps = Component.defaultProps || (Component.defaultProps = {});
+
+ propTypes.bsClass = PropTypes.string;
+ defaultProps.bsClass = defaultClass;
+
+ return Component;
+});
+
+export let bsStyles = curry((styles, defaultStyle, Component) => {
+ if (typeof defaultStyle !== 'string') {
+ Component = defaultStyle;
+ defaultStyle = undefined;
+ }
+
+ let existing = Component.STYLES || [];
+ let propTypes = Component.propTypes || {};
+
+ styles.forEach(style => {
+ if (existing.indexOf(style) === -1) {
+ existing.push(style);
+ }
+ });
+
+ let propType = PropTypes.oneOf(existing);
+
+ // expose the values on the propType function for documentation
+ Component.STYLES = propType._values = existing;
+
+ Component.propTypes = {
+ ...propTypes,
+ bsStyle: propType
+ };
+
+ if (defaultStyle !== undefined) {
+ let defaultProps = Component.defaultProps || (Component.defaultProps = {});
+ defaultProps.bsStyle = defaultStyle;
+ }
+
+ return Component;
+});
+
+export let bsSizes = curry((sizes, defaultSize, Component) => {
+ if (typeof defaultSize !== 'string') {
+ Component = defaultSize;
+ defaultSize = undefined;
+ }
+
+ let existing = Component.SIZES || [];
+ let propTypes = Component.propTypes || {};
+
+ sizes.forEach(size => {
+ if (existing.indexOf(size) === -1) {
+ existing.push(size);
+ }
+ });
+
+ let values = existing.reduce((result, size) => {
+ if (styleMaps.SIZES[size] && styleMaps.SIZES[size] !== size) {
+ result.push(styleMaps.SIZES[size]);
+ }
+ return result.concat(size);
+ }, []);
+
+ let propType = PropTypes.oneOf(values);
+
+ propType._values = values;
+
+ // expose the values on the propType function for documentation
+ Component.SIZES = existing;
+
+ Component.propTypes = {
+ ...propTypes,
+ bsSize: propType
+ };
+
+ if (defaultSize !== undefined) {
+ let defaultProps = Component.defaultProps || (Component.defaultProps = {});
+ defaultProps.bsSize = defaultSize;
+ }
+
+ return Component;
+});
+
+export default {
+
+ prefix,
+
+ getClassSet(props) {
+ let classes = {};
+ let bsClassName = prefix(props);
+
+ if (bsClassName) {
+ let bsSize;
+
+ classes[bsClassName] = true;
+
+ if (props.bsSize) {
+ bsSize = styleMaps.SIZES[props.bsSize] || bsSize;
+ }
+
+ if (bsSize) {
+ classes[prefix(props, bsSize)] = true;
+ }
+
+ if (props.bsStyle) {
+ if (props.bsStyle.indexOf(prefix(props)) === 0) {
+ warning(false, // small migration convenience, since the old method required manual prefixing
+ 'bsStyle will automatically prefix custom values with the bsClass, so there is no ' +
+ 'need to append it manually. (bsStyle: ' + props.bsStyle + ', bsClass: ' + prefix(props) + ')'
+ );
+ classes[props.bsStyle] = true;
+ } else {
+ classes[prefix(props, props.bsStyle)] = true;
+ }
+ }
+ }
+
+ return classes;
+ },
+
+ /**
+ * Add a style variant to a Component. Mutates the propTypes of the component
+ * in order to validate the new variant.
+ */
+ addStyle(Component, styleVariant) {
+ bsStyles(styleVariant, Component);
+ }
+};
+
+export let _curry = curry;
diff --git a/test/BootstrapMixinSpec.js b/test/BootstrapMixinSpec.js
deleted file mode 100644
index 560770162d..0000000000
--- a/test/BootstrapMixinSpec.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React from 'react';
-import ReactTestUtils from 'react/lib/ReactTestUtils';
-import BootstrapMixin from '../src/BootstrapMixin';
-import styleMaps from '../src/styleMaps';
-import { shouldWarn } from './helpers';
-
-let Component;
-
-describe('BootstrapMixin', () => {
- beforeEach(() => {
- Component = React.createClass({
- mixins: [BootstrapMixin],
-
- render() {
- return React.DOM.button(this.props);
- }
- });
- });
-
- describe('#getBsClassSet', () => {
- it('should return blank', () => {
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- assert.deepEqual(instance.getBsClassSet(), {});
- });
-
- it('maps and validates OK default classes', () => {
- function instanceClassSet(bsClass) {
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- return instance.getBsClassSet();
- }
-
- assert.deepEqual(instanceClassSet('column'), {'col': true});
- assert.deepEqual(instanceClassSet('button'), {'btn': true});
- assert.deepEqual(instanceClassSet('button-group'), {'btn-group': true});
- assert.deepEqual(instanceClassSet('label'), {'label': true});
- assert.deepEqual(instanceClassSet('alert'), {'alert': true});
- assert.deepEqual(instanceClassSet('input-group'), {'input-group': true});
- assert.deepEqual(instanceClassSet('form'), {'form': true});
- assert.deepEqual(instanceClassSet('panel'), {'panel': true});
- });
-
- describe('Predefined Bootstrap styles', () => {
- it('maps and validates OK default styles', () => {
- function instanceClassSet(style) {
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- return instance.getBsClassSet();
- }
-
- assert.deepEqual(instanceClassSet('default'), {'btn': true, 'btn-default': true});
- assert.deepEqual(instanceClassSet('primary'), {'btn': true, 'btn-primary': true});
- assert.deepEqual(instanceClassSet('success'), {'btn': true, 'btn-success': true});
- assert.deepEqual(instanceClassSet('info'), {'btn': true, 'btn-info': true});
- assert.deepEqual(instanceClassSet('link'), {'btn': true, 'btn-link': true});
- assert.deepEqual(instanceClassSet('inline'), {'btn': true, 'btn-inline': true});
- });
- });
-
- describe('Sizes', () => {
- it('maps english words for sizes to bootstrap sizes constants', () => {
- function instanceClassSet(size) {
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- return instance.getBsClassSet();
- }
-
- assert.deepEqual(instanceClassSet('large'), {'btn': true, 'btn-lg': true});
- assert.deepEqual(instanceClassSet('small'), {'btn': true, 'btn-sm': true});
- assert.deepEqual(instanceClassSet('medium'), {'btn': true, 'btn-md': true});
- assert.deepEqual(instanceClassSet('xsmall'), {'btn': true, 'btn-xs': true});
- });
- });
-
- describe('Custom styles', () => {
- it('should validate OK custom styles added via "addStyle()"', () => {
-
- styleMaps.addStyle('wacky');
-
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- assert.deepEqual(instance.getBsClassSet(), {'btn': true, 'btn-wacky': true});
- });
-
- it('should allow custom styles as is but with validation warning', () => {
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- assert.deepEqual(instance.getBsClassSet(), {'btn': true, 'my-custom-class': true});
- shouldWarn('Invalid prop `bsStyle` of value `my-custom-class`');
- });
- });
- });
-
- // todo: fix bad naming
- describe('#prefixClass', () => {
- it('allows custom sub-classes', () => {
- let instance = ReactTestUtils.renderIntoDocument(
-
- content
-
- );
- assert.equal(instance.prefixClass('title'), 'btn-title');
- });
- });
-});
diff --git a/test/ButtonGroupSpec.js b/test/ButtonGroupSpec.js
index 411ac7712d..66b7447672 100644
--- a/test/ButtonGroupSpec.js
+++ b/test/ButtonGroupSpec.js
@@ -22,7 +22,7 @@ describe('ButtonGroup', () => {
it('Should add size', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
diff --git a/test/ModalSpec.js b/test/ModalSpec.js
index 332905d321..0ab4546ab6 100644
--- a/test/ModalSpec.js
+++ b/test/ModalSpec.js
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom';
import Modal from '../src/Modal';
-import {getOne, render, shouldWarn} from './helpers';
+import {getOne, render } from './helpers';
describe('Modal', () => {
let mountPoint;
@@ -128,7 +128,7 @@ describe('Modal', () => {
it('Should pass className to the dialog', () => {
let noOp = () => {};
let instance = render(
-
+
Message
, mountPoint);
@@ -141,7 +141,7 @@ describe('Modal', () => {
it('Should use bsClass on the dialog', () => {
let noOp = () => {};
let instance = render(
-
+
Message
, mountPoint);
@@ -153,9 +153,6 @@ describe('Modal', () => {
assert.ok(dialog.children[0].children[0].className.match(/\bmymodal-content\b/));
assert.ok(instance.refs.backdrop.className.match(/\bmymodal-backdrop\b/));
-
-
- shouldWarn("Invalid prop 'bsClass' of value 'mymodal'");
});
it('Should pass bsSize to the dialog', () => {
diff --git a/test/ProgressBarSpec.js b/test/ProgressBarSpec.js
index 8ab3d1965c..0a6654b0fa 100644
--- a/test/ProgressBarSpec.js
+++ b/test/ProgressBarSpec.js
@@ -24,23 +24,15 @@ describe('ProgressBar', () => {
it('Should have the default class', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
- );
-
- assert.ok(getProgressBarNode(instance).className.match(/\bprogress-bar-default\b/));
- });
-
- it('Should have the primary class', () => {
- let instance = ReactTestUtils.renderIntoDocument(
-
+
);
- assert.ok(getProgressBarNode(instance).className.match(/\bprogress-bar-primary\b/));
+ assert.ok(getProgressBarNode(instance).className.match(/\bprogress-bar\b/));
});
it('Should have the success class', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
assert.ok(getProgressBarNode(instance).className.match(/\bprogress-bar-success\b/));
@@ -48,7 +40,7 @@ describe('ProgressBar', () => {
it('Should have the warning class', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
assert.ok(getProgressBarNode(instance).className.match(/\bprogress-bar-warning\b/));
@@ -98,7 +90,7 @@ describe('ProgressBar', () => {
it('Should not have label', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
assert.equal(ReactDOM.findDOMNode(instance).innerText, '');
@@ -106,21 +98,21 @@ describe('ProgressBar', () => {
it('Should have label', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
- assert.equal(ReactDOM.findDOMNode(instance).innerText, 'min:0, max:10, now:5, percent:50, bsStyle:primary');
+ assert.equal(ReactDOM.findDOMNode(instance).innerText, 'min:0, max:10, now:5, percent:50, bsStyle:success');
});
it('Should have screen reader only label', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
let srLabel = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'sr-only');
- assert.equal(srLabel.innerText, 'min:0, max:10, now:5, percent:50, bsStyle:primary');
+ assert.equal(srLabel.innerText, 'min:0, max:10, now:5, percent:50, bsStyle:success');
});
it('Should have a label that is a React component', () => {
@@ -129,7 +121,7 @@ describe('ProgressBar', () => {
);
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'special-label'));
@@ -141,7 +133,7 @@ describe('ProgressBar', () => {
);
let instance = ReactTestUtils.renderIntoDocument(
-
+
);
let srLabel = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'sr-only');
diff --git a/test/helpers.js b/test/helpers.js
index cabf22a8d0..6de6d2c073 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -18,7 +18,7 @@ export function render(element, mountPoint) {
let mount = mountPoint || document.createElement('div');
let instance = ReactDOM.render(element, mount);
- if (!instance.renderWithProps) {
+ if (instance && !instance.renderWithProps) {
instance.renderWithProps = newProps => {
return render(
diff --git a/test/utils/bootstrapUtilsSpec.js b/test/utils/bootstrapUtilsSpec.js
new file mode 100644
index 0000000000..644b78d403
--- /dev/null
+++ b/test/utils/bootstrapUtilsSpec.js
@@ -0,0 +1,209 @@
+import React from 'react';
+import tbsUtils, { bsStyles, bsSizes, _curry } from '../../src/utils/bootstrapUtils';
+import { render, shouldWarn } from '../helpers';
+
+describe('bootstrapUtils', ()=> {
+
+ function validatePropType(propTypes, prop, value, match) {
+ let result = propTypes[prop]({ [prop]: value }, prop, 'Component');
+
+ if (match) {
+ expect(result.message).to.match(match);
+ } else {
+ expect(result).to.not.exist;
+ }
+ }
+
+ it('should prefix with bsClass', ()=> {
+ expect(tbsUtils.prefix({ bsClass: 'yolo'}, 'pie')).to.equal('yolo-pie');
+ });
+
+ it('should return bsClass when there is no suffix', ()=> {
+ expect(tbsUtils.prefix({ bsClass: 'yolo'})).to.equal('yolo');
+ expect(tbsUtils.prefix({ bsClass: 'yolo'}, '')).to.equal('yolo');
+ expect(tbsUtils.prefix({ bsClass: 'yolo'}, null)).to.equal('yolo');
+ });
+
+ it('returns a classSet of bsClass', ()=> {
+ expect(tbsUtils.getClassSet({ bsClass: 'btn' })).to.eql({'btn': true });
+ });
+
+ it('returns a classSet of bsClass and style', ()=> {
+ expect(
+ tbsUtils.getClassSet({ bsClass: 'btn', bsStyle: 'primary' })
+ )
+ .to.eql({'btn': true, 'btn-primary': true });
+ });
+
+ it('returns a classSet of bsClass and size', ()=> {
+ expect(tbsUtils
+ .getClassSet({ bsClass: 'btn', bsSize: 'large' }))
+ .to.eql({'btn': true, 'btn-lg': true });
+
+ expect(tbsUtils
+ .getClassSet({ bsClass: 'btn', bsSize: 'lg' }))
+ .to.eql({'btn': true, 'btn-lg': true });
+ });
+
+ it('returns a classSet of bsClass, style and size', ()=> {
+ expect(tbsUtils
+ .getClassSet({ bsClass: 'btn', bsSize: 'lg', bsStyle: 'primary' }))
+ .to.eql({'btn': true, 'btn-lg': true, 'btn-primary': true });
+ });
+
+ describe('decorators', ()=> {
+ it('should apply immediately if a component is supplied', ()=> {
+ let spy = sinon.spy();
+ let component = function noop() {};
+
+ _curry(spy)(true, 'hi', component);
+
+ expect(spy).to.have.been.calledOnce;
+ expect(spy).to.have.been.calledWith(true, 'hi', component);
+ });
+
+ it('should curry the method as a decorator', ()=> {
+ let spy = sinon.spy();
+ let component = function noop() {};
+ let decorator = _curry(spy)(true, 'hi');
+
+ expect(spy).to.have.not.been.calledOnce;
+
+ decorator(component);
+
+ expect(spy).to.have.been.calledOnce;
+ expect(spy).to.have.been.calledWith(true, 'hi', component);
+ });
+ });
+
+ describe('bsStyles', ()=> {
+
+ it('should add style to allowed propTypes', ()=> {
+ let Component = {};
+
+ bsStyles(['minimal', 'boss', 'plaid'])(Component);
+
+ expect(Component.propTypes).to.exist;
+
+ validatePropType(Component.propTypes, 'bsStyle', 'plaid');
+
+ validatePropType(Component.propTypes, 'bsStyle', 'not-plaid',
+ /expected one of \["minimal","boss","plaid"\]/);
+ });
+
+ it('should not override other propTypes', ()=> {
+ let Component = { propTypes: {other() {}}};
+
+ bsStyles(['minimal', 'boss', 'plaid'])(Component);
+
+ expect(Component.propTypes).to.exist;
+ expect(Component.propTypes.other).to.exist;
+ });
+
+ it('should set a default if provided', ()=> {
+ let Component = { propTypes: {other() {}}};
+
+ bsStyles(['minimal', 'boss', 'plaid'], 'plaid')(Component);
+
+ expect(Component.defaultProps).to.exist;
+ expect(Component.defaultProps.bsStyle).to.equal('plaid');
+ });
+
+ it('should work with es6 classes', ()=> {
+ @bsStyles(['minimal', 'boss', 'plaid'], 'plaid')
+ class Component {
+ render() { return ; }
+ }
+
+ let instance = render();
+
+ expect(instance.props.bsStyle).to.equal('plaid');
+
+ render();
+
+ shouldWarn(/expected one of \["minimal","boss","plaid"\]/);
+ });
+
+ it('should work with createClass', ()=> {
+ let Component = bsStyles(['minimal', 'boss', 'plaid'], 'plaid')(
+ React.createClass({
+ render() { return ; }
+ })
+ );
+
+ let instance = render();
+
+ expect(instance.props.bsStyle).to.equal('plaid');
+
+ render();
+
+ shouldWarn(/expected one of \["minimal","boss","plaid"\]/);
+ });
+ });
+
+ describe('bsSizes', ()=> {
+
+ it('should add size to allowed propTypes', ()=> {
+ let Component = {};
+
+ bsSizes(['large', 'small'])(Component);
+
+ expect(Component.propTypes).to.exist;
+
+ validatePropType(Component.propTypes, 'bsSize', 'small');
+ validatePropType(Component.propTypes, 'bsSize', 'sm');
+
+ validatePropType(Component.propTypes, 'bsSize', 'superSmall',
+ /expected one of \["lg","large","sm","small"\]/);
+ });
+
+ it('should not override other propTypes', ()=> {
+ let Component = { propTypes: {other() {}}};
+
+ bsSizes(['smallish', 'micro', 'planet'])(Component);
+
+ expect(Component.propTypes).to.exist;
+ expect(Component.propTypes.other).to.exist;
+ });
+
+ it('should set a default if provided', ()=> {
+ let Component = { propTypes: {other() {}}};
+
+ bsSizes(['smallish', 'micro', 'planet'], 'smallish')(Component);
+
+ expect(Component.defaultProps).to.exist;
+ expect(Component.defaultProps.bsSize).to.equal('smallish');
+ });
+
+ it('should work with es6 classes', ()=> {
+ @bsSizes(['smallish', 'micro', 'planet'], 'smallish')
+ class Component {
+ render() { return ; }
+ }
+
+ let instance = render();
+
+ expect(instance.props.bsSize).to.equal('smallish');
+
+ render();
+
+ shouldWarn(/expected one of \["smallish","micro","planet"\]/);
+ });
+
+ it('should work with createClass', ()=> {
+ let Component = bsSizes(['smallish', 'micro', 'planet'], 'smallish')(
+ React.createClass({
+ render() { return ; }
+ })
+ );
+
+ let instance = render();
+
+ expect(instance.props.bsSize).to.equal('smallish');
+
+ render();
+
+ shouldWarn(/expected one of \["smallish","micro","planet"\]/);
+ });
+ });
+});