+ 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/deprecated/Navbar.js b/src/deprecated/Navbar.js
new file mode 100644
index 0000000000..4b9555c6bc
--- /dev/null
+++ b/src/deprecated/Navbar.js
@@ -0,0 +1,227 @@
+import React from 'react';
+import classNames from 'classnames';
+import deprecated from 'react-prop-types/lib/deprecated';
+import elementType from 'react-prop-types/lib/elementType';
+
+import Grid from '../Grid';
+import NavBrand from '../NavBrand';
+
+import tbsUtils, { bsClass, bsStyles } from '../utils/bootstrapUtils';
+import { DEFAULT, INVERSE } from '../styleMaps';
+import createChainedFunction from '../utils/createChainedFunction';
+import ValidComponentChildren from '../utils/ValidComponentChildren';
+
+const Navbar = React.createClass({
+
+ propTypes: {
+ fixedTop: React.PropTypes.bool,
+ fixedBottom: React.PropTypes.bool,
+ staticTop: React.PropTypes.bool,
+ inverse: React.PropTypes.bool,
+ fluid: React.PropTypes.bool,
+ role: React.PropTypes.string,
+ /**
+ * You can use a custom element for this component
+ */
+ componentClass: elementType,
+ brand: deprecated(React.PropTypes.node, 'Use the `NavBrand` component.'),
+ toggleButton: React.PropTypes.node,
+ toggleNavKey: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.number
+ ]),
+ onToggle: React.PropTypes.func,
+ navExpanded: React.PropTypes.bool,
+ defaultNavExpanded: React.PropTypes.bool
+ },
+
+ // TODO Remove in 0.29
+ childContextTypes: {
+ $bs_deprecated_navbar: React.PropTypes.bool
+ },
+
+ getChildContext() {
+ return {
+ $bs_deprecated_navbar: true
+ };
+ },
+
+ getDefaultProps() {
+ return {
+ role: 'navigation',
+ componentClass: 'nav',
+ fixedTop: false,
+ fixedBottom: false,
+ staticTop: false,
+ inverse: false,
+ fluid: false,
+ defaultNavExpanded: false
+ };
+ },
+
+ getInitialState() {
+ return {
+ navExpanded: this.props.defaultNavExpanded
+ };
+ },
+
+ shouldComponentUpdate() {
+ // Defer any updates to this component during the `onSelect` handler.
+ return !this._isChanging;
+ },
+
+ handleToggle() {
+ if (this.props.onToggle) {
+ this._isChanging = true;
+ this.props.onToggle();
+ this._isChanging = false;
+ }
+
+ this.setState({
+ navExpanded: !this.state.navExpanded
+ });
+ },
+
+ isNavExpanded() {
+ return this.props.navExpanded != null ? this.props.navExpanded : this.state.navExpanded;
+ },
+
+ hasNavBrandChild() {
+ return ValidComponentChildren.findValidComponents(
+ this.props.children, child => child.props.bsRole === 'brand'
+ ).length > 0;
+ },
+
+ render() {
+ const {
+ brand,
+ toggleButton,
+ toggleNavKey,
+ fixedTop,
+ fixedBottom,
+ staticTop,
+ inverse,
+ componentClass: ComponentClass,
+ fluid,
+ className,
+ children,
+ ...props
+ } = this.props;
+
+ // will result in some false positives but that seems better
+ // than false negatives. strict `undefined` check allows explicit
+ // "nulling" of the role if the user really doesn't want one
+ if (props.role === undefined && ComponentClass !== 'nav') {
+ props.role = 'navigation';
+ }
+
+ const classes = tbsUtils.getClassSet(this.props);
+
+ classes[tbsUtils.prefix(this.props, 'fixed-top')] = this.props.fixedTop;
+ classes[tbsUtils.prefix(this.props, 'fixed-bottom')] = this.props.fixedBottom;
+ classes[tbsUtils.prefix(this.props, 'static-top')] = this.props.staticTop;
+
+ // handle built-in styles manually to provide the convenience `inverse` prop
+ classes[tbsUtils.prefix(this.props, INVERSE)] = this.props.inverse;
+ classes[tbsUtils.prefix(this.props, DEFAULT)] = !this.props.inverse;
+
+ const showHeader =
+ (brand || toggleButton || toggleNavKey != null) &&
+ !this.hasNavBrandChild();
+
+ return (
+
+
+ {showHeader ? this.renderBrandHeader() : null}
+ {ValidComponentChildren.map(children, this.renderChild)}
+
+
+ );
+ },
+
+ renderBrandHeader() {
+ let {brand} = this.props;
+ if (brand) {
+ brand =
{brand} ;
+ }
+
+ return this.renderHeader(brand);
+ },
+
+
+ renderHeader(brand) {
+ const hasToggle = this.props.toggleButton || this.props.toggleNavKey != null;
+ const headerClass = tbsUtils.prefix(this.props, 'header');
+
+ return (
+
+ {brand}
+ {hasToggle ? this.renderToggleButton() : null}
+
+ );
+ },
+
+ renderChild(child, index) {
+ const key = child.key != null ? child.key : index;
+
+ if (child.props.bsRole === 'brand') {
+ return React.cloneElement(this.renderHeader(child), {key});
+ }
+
+ const {toggleNavKey} = this.props;
+ const collapsible =
+ toggleNavKey != null && toggleNavKey === child.props.eventKey;
+
+ return React.cloneElement(child, {
+ navbar: true,
+ collapsible,
+ expanded: collapsible && this.isNavExpanded(),
+ key
+ });
+ },
+
+ renderToggleButton() {
+ const {toggleButton} = this.props;
+ const toggleClass = tbsUtils.prefix(this.props, 'toggle');
+
+ if (React.isValidElement(toggleButton)) {
+ return React.cloneElement(toggleButton, {
+ className: classNames(toggleButton.props.className, toggleClass),
+ onClick: createChainedFunction(
+ this.handleToggle, toggleButton.props.onClick
+ )
+ });
+ }
+
+ let children;
+ if (toggleButton != null) {
+ children = toggleButton;
+ } else {
+ children = [
+
Toggle navigation ,
+
,
+
,
+
+ ];
+ }
+
+ return (
+
+ {children}
+
+ );
+ }
+
+});
+
+const NAVBAR_STATES = [DEFAULT, INVERSE];
+
+export default bsStyles(NAVBAR_STATES, DEFAULT,
+ bsClass('navbar',
+ Navbar
+ )
+);
diff --git a/src/index.js b/src/index.js
index 7461e38dcc..5a708d6740 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,9 +1,6 @@
export Accordion from './Accordion';
-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';
@@ -34,6 +31,7 @@ export ModalTitle from './ModalTitle';
export Nav from './Nav';
export Navbar from './Navbar';
export NavBrand from './NavBrand';
+export NavbarBrand from './NavbarBrand';
export NavDropdown from './NavDropdown';
export NavItem from './NavItem';
export Overlay from './Overlay';
@@ -51,8 +49,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';
export Tabs from './Tabs';
@@ -68,9 +64,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(
-
+
Title
diff --git a/test/CollapsibleNavSpec.js b/test/CollapsibleNavSpec.js
deleted file mode 100644
index 1d48b8bb6d..0000000000
--- a/test/CollapsibleNavSpec.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from 'react';
-import ReactTestUtils from 'react/lib/ReactTestUtils';
-import ReactDOM from 'react-dom';
-
-import CollapsibleNav from '../src/CollapsibleNav';
-import Nav from '../src/Nav';
-import Navbar from '../src/Navbar';
-import NavItem from '../src/NavItem';
-
-describe('CollapsibleNav', () => {
- it('Should create div and add collapse class', () => {
- let Parent = React.createClass({
- render() {
- return (
-
-
-
- Item 1 content
- Item 2 content
-
-
-
- );
- }
- });
- let instance = ReactTestUtils.renderIntoDocument( );
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-collapse'));
- });
-
- it('Should handle multiple Nav elements', () => {
- let Parent = React.createClass({
- render() {
- return (
-
-
-
- Item 1 content
- Item 2 content
-
-
- Item 1 content
- Item 2 content
-
-
-
- );
- }
- });
- let instance = ReactTestUtils.renderIntoDocument( );
- assert.ok(ReactTestUtils.findRenderedComponentWithType(instance.refs.collapsible_object.refs.collapsible_0, Nav));
- assert.ok(ReactTestUtils.findRenderedComponentWithType(instance.refs.collapsible_object.refs.collapsible_1, Nav));
- });
-
- it('Should just render children and move along if not in ', () => {
- let Parent = React.createClass({
- render() {
- return (
-
-
- Item 1 content
- Item 2 content
-
-
- );
- }
- });
- let instance = ReactTestUtils.renderIntoDocument( );
- let collapsibleNav = ReactTestUtils.findRenderedComponentWithType(instance, CollapsibleNav);
- assert.notOk(ReactDOM.findDOMNode(collapsibleNav).className.match(/\navbar-collapse\b/));
- let nav = ReactTestUtils.findRenderedComponentWithType(collapsibleNav.refs.nocollapse_0, Nav);
- assert.ok(nav);
- });
-
- it('Should retain childrens classes set by className', () => {
- let Parent = React.createClass({
- render() {
- return (
-
-
-
- Item 1 content
- Item 2 content
-
-
-
- );
- }
- });
- let instance = ReactTestUtils.renderIntoDocument( );
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsible_object.refs.collapsible_0, 'foo'));
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsible_object.refs.collapsible_0, 'bar'));
- assert.ok(ReactTestUtils.findRenderedDOMComponentWithClass(instance.refs.collapsible_object.refs.collapsible_0, 'baz'));
- });
-});
diff --git a/test/ModalSpec.js b/test/ModalSpec.js
index 332905d321..5a720b1125 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 { render } from './helpers';
describe('Modal', () => {
let mountPoint;
@@ -27,60 +27,8 @@ describe('Modal', () => {
, mountPoint);
- assert.ok(getOne(instance.refs.modal.querySelectorAll('strong')));
- });
-
- it('Should add modal-open class to the modal container while open', (done) => {
-
- let Container = React.createClass({
- getInitialState() {
- return { modalOpen: true };
- },
- handleCloseModal() {
- this.setState({ modalOpen: false });
- },
- render() {
- return (
-
-
- Message
-
-
- );
- }
- });
-
- let instance = render(
-
- , mountPoint);
-
- let modal = ReactTestUtils.findRenderedComponentWithType(instance, Modal);
-
- assert.ok(ReactDOM.findDOMNode(instance).className.match(/\bmodal-open\b/));
-
- ReactTestUtils.Simulate.click(modal.refs.backdrop);
-
- setTimeout(() => {
- assert.equal(ReactDOM.findDOMNode(instance).className.length, 0);
- done();
- }, 0);
-
- });
-
- it('Should close the modal when the backdrop is clicked', (done) => {
- let doneOp = () => { done(); };
- let instance = render(
-
- Message
-
- , mountPoint);
-
- ReactTestUtils.Simulate.click(instance.refs.backdrop);
+ assert.ok(
+ ReactTestUtils.findRenderedDOMComponentWithTag(instance._modal, 'strong'));
});
it('Should close the modal when the modal dialog is clicked', (done) => {
@@ -92,20 +40,23 @@ describe('Modal', () => {
, mountPoint);
- let dialog = ReactDOM.findDOMNode(instance.refs.dialog);
+
+ let dialog = ReactDOM.findDOMNode(instance._modal);
ReactTestUtils.Simulate.click(dialog);
});
- it('Should not close the modal when the "static" backdrop is clicked', () => {
+ it('Should not close the modal when the "static" dialog is clicked', () => {
let onHideSpy = sinon.spy();
let instance = render(
-
+
Message
, mountPoint);
- ReactTestUtils.Simulate.click(instance.refs.backdrop);
+ let dialog = ReactDOM.findDOMNode(instance._modal);
+
+ ReactTestUtils.Simulate.click(dialog);
expect(onHideSpy).to.not.have.been.called;
});
@@ -120,7 +71,8 @@ describe('Modal', () => {
, mountPoint);
- let button = instance.refs.modal.getElementsByClassName('close')[0];
+ let button = ReactDOM.findDOMNode(instance._modal)
+ .getElementsByClassName('close')[0];
ReactTestUtils.Simulate.click(button);
});
@@ -128,12 +80,12 @@ describe('Modal', () => {
it('Should pass className to the dialog', () => {
let noOp = () => {};
let instance = render(
-
+
Message
, mountPoint);
- let dialog = ReactDOM.findDOMNode(instance.refs.dialog);
+ let dialog = ReactDOM.findDOMNode(instance._modal);
assert.ok(dialog.className.match(/\bmymodal\b/));
});
@@ -141,33 +93,31 @@ describe('Modal', () => {
it('Should use bsClass on the dialog', () => {
let noOp = () => {};
let instance = render(
-
+
Message
, mountPoint);
- let dialog = ReactDOM.findDOMNode(instance.refs.dialog);
-
- assert.ok(dialog.className.match(/\bmymodal\b/));
- assert.ok(dialog.children[0].className.match(/\bmymodal-dialog\b/));
- assert.ok(dialog.children[0].children[0].className.match(/\bmymodal-content\b/));
-
- assert.ok(instance.refs.backdrop.className.match(/\bmymodal-backdrop\b/));
+ let modal = ReactDOM.findDOMNode(instance._modal);
-
- shouldWarn("Invalid prop 'bsClass' of value 'mymodal'");
+ assert.ok(modal.className.match(/\bmymodal\b/));
+ assert.ok(modal.children[0].className.match(/\bmymodal-dialog\b/));
+ assert.ok(modal.children[0].children[0].className.match(/\bmymodal-content\b/));
+ assert.ok(instance._backdrop.className.match(/\bmymodal-backdrop\b/));
});
it('Should pass bsSize to the dialog', () => {
let noOp = () => {};
let instance = render(
-
+
Message
, mountPoint);
- let dialog = getOne(instance.refs.modal.getElementsByClassName('modal-dialog'));
+ let dialog = ReactDOM.findDOMNode(instance._modal).getElementsByClassName('modal-dialog')[0];
+
assert.ok(dialog.className.match(/\bmodal-sm\b/));
+
});
it('Should pass dialogClassName to the dialog', () => {
@@ -178,19 +128,9 @@ describe('Modal', () => {
, mountPoint);
- let dialog = instance.refs.modal.querySelector('.modal-dialog');
- assert.match(dialog.className, /\btestCss\b/);
- });
-
- it('Should assign refs correctly when no backdrop', () => {
+ let dialog = ReactTestUtils.findRenderedDOMComponentWithClass(instance._modal, 'modal-dialog');
- let test = () => render(
- {}}>
- Message
-
- , mountPoint);
-
- expect(test).not.to.throw();
+ assert.ok(dialog.className.match(/\btestCss\b/));
});
it('Should use dialogComponent', () => {
@@ -206,7 +146,7 @@ describe('Modal', () => {
, mountPoint);
- assert.ok(instance.refs.dialog instanceof CustomDialog);
+ assert.ok(instance._modal instanceof CustomDialog);
});
it('Should pass transition callbacks to Transition', (done) => {
@@ -235,79 +175,4 @@ describe('Modal', () => {
, mountPoint);
});
- it('Should unbind listeners when unmounted', () => {
- render(
-
- null} animation={false}>
- Foo bar
-
-
- , mountPoint);
-
- assert.include(document.body.className, 'modal-open');
-
- render(
, mountPoint);
-
- assert.notInclude(document.body.className, 'modal-open');
- });
-
- describe('Focused state', () => {
- let focusableContainer = null;
-
- beforeEach(() => {
- focusableContainer = document.createElement('div');
- focusableContainer.tabIndex = 0;
- document.body.appendChild(focusableContainer);
- focusableContainer.focus();
- });
-
- afterEach(() => {
- ReactDOM.unmountComponentAtNode(focusableContainer);
- document.body.removeChild(focusableContainer);
- });
-
- it('Should focus on the Modal when it is opened', () => {
-
- document.activeElement.should.equal(focusableContainer);
-
- let instance = render(
- {}} animation={false}>
- Message
-
- , focusableContainer);
-
- document.activeElement.className.should.contain('modal');
-
- instance.renderWithProps({ show: false });
-
- document.activeElement.should.equal(focusableContainer);
- });
-
-
- it('Should not focus on the Modal when autoFocus is false', () => {
- render(
- {}} animation={false}>
- Message
-
- , focusableContainer);
-
- document.activeElement.should.equal(focusableContainer);
- });
-
- it('Should not focus Modal when child has focus', () => {
-
- document.activeElement.should.equal(focusableContainer);
-
- render(
- {}} animation={false}>
-
-
- , focusableContainer);
-
- let input = document.getElementsByTagName('input')[0];
-
- document.activeElement.should.equal(input);
- });
- });
-
});
diff --git a/test/NavBrandSpec.js b/test/NavBrandSpec.js
index d47e562262..2f20099a2a 100644
--- a/test/NavBrandSpec.js
+++ b/test/NavBrandSpec.js
@@ -2,13 +2,13 @@ import React from 'react';
import ReactTestUtils from 'react/lib/ReactTestUtils';
import ReactDOM from 'react-dom';
-import NavBrand from '../src/NavBrand';
+import NavbarBrand from '../src/NavbarBrand';
-describe('Navbrand', () => {
+describe('NavbarBrand', () => {
- it('Should create navbrand SPAN element', () => {
+ it('Should create NavbarBrand SPAN element', () => {
let instance = ReactTestUtils.renderIntoDocument(
- Brand
+ Brand
);
let brand = ReactDOM.findDOMNode(instance);
@@ -18,9 +18,9 @@ describe('Navbrand', () => {
assert.equal(brand.innerText, 'Brand');
});
- it('Should create navbrand A (link) element', () => {
+ it('Should create NavbarBrand A (link) element', () => {
let instance = ReactTestUtils.renderIntoDocument(
- BrandLink
+ BrandLink
);
let brand = ReactDOM.findDOMNode(instance);
diff --git a/test/NavSpec.js b/test/NavSpec.js
index 0bae89cd48..0d33df38c8 100644
--- a/test/NavSpec.js
+++ b/test/NavSpec.js
@@ -65,7 +65,7 @@ describe('Nav', () => {
it('Should add navbar-right class', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
Tab 1 content
Tab 2 content
@@ -118,68 +118,6 @@ describe('Nav', () => {
assert.ok(items[0].props.navItem);
});
- it('Should apply className only to the wrapper nav element', () => {
- const instance = ReactTestUtils.renderIntoDocument(
-
- Tab 1 content
- Tab 2 content
-
- );
-
- let ulNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'ul');
- assert.notInclude(ulNode.className, 'nav-specific');
-
- let navNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'nav');
- assert.include(navNode.className, 'nav-specific');
- });
-
- it('Should apply ulClassName to the inner ul element', () => {
- const instance = ReactTestUtils.renderIntoDocument(
-
- Tab 1 content
- Tab 2 content
-
- );
-
- let ulNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'ul');
- assert.include(ulNode.className, 'ul-specific');
- assert.notInclude(ulNode.className, 'nav-specific');
-
- let navNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'nav');
- assert.notInclude(navNode.className, 'ul-specific');
- assert.include(navNode.className, 'nav-specific');
- });
-
- it('Should apply id to the wrapper nav element', () => {
- const instance = ReactTestUtils.renderIntoDocument(
-
- Tab 1 content
- Tab 2 content
-
- );
-
- let navNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'nav');
- assert.equal(navNode.id, 'nav-id');
-
- let ulNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'ul');
- assert.notEqual(ulNode.id, 'nav-id');
- });
-
- it('Should apply ulId to the inner ul element', () => {
- const instance = ReactTestUtils.renderIntoDocument(
-
- Tab 1 content
- Tab 2 content
-
- );
-
- let ulNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'ul');
- assert.equal(ulNode.id, 'ul-id');
-
- let navNode = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'nav');
- assert.equal(navNode.id, 'nav-id');
- });
-
it('Should warn when attempting to use a justified navbar nav', () => {
ReactTestUtils.renderIntoDocument(
diff --git a/test/NavbarSpec.js b/test/NavbarSpec.js
index 4c483d3205..8eacd68ec7 100644
--- a/test/NavbarSpec.js
+++ b/test/NavbarSpec.js
@@ -3,10 +3,9 @@ import ReactTestUtils from 'react/lib/ReactTestUtils';
import ReactDOM from 'react-dom';
import Nav from '../src/Nav';
-import NavBrand from '../src/NavBrand';
import Navbar from '../src/Navbar';
-import {getOne, render, shouldWarn} from './helpers';
+import { getOne, shouldWarn } from './helpers';
describe('Navbar', () => {
@@ -78,168 +77,205 @@ describe('Navbar', () => {
assert.equal(ReactDOM.findDOMNode(instance).nodeName, 'HEADER');
});
- it('Should throw a deprecation warning message when brand is passed', () => {
- ReactTestUtils.renderIntoDocument(
-
- );
- shouldWarn('deprecated');
- });
-
it('Should add header with brand', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
+
+ Brand
+
+
);
let header = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
- assert.ok(header);
-
let brand = getOne(header.getElementsByClassName('navbar-brand'));
assert.ok(brand);
assert.equal(brand.nodeName, 'SPAN');
assert.equal(brand.innerText, 'Brand');
-
- shouldWarn('deprecated');
});
- it('Should add span element with navbar-brand class using NavBrand Component', () => {
+ it('Should add link element with navbar-brand class using NavBrand Component', () => {
let instance = ReactTestUtils.renderIntoDocument(
- Brand
+
+ Brand
+
);
let brand = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-brand');
assert.ok(brand);
- assert.equal(brand.nodeName, 'SPAN');
+ assert.equal(brand.nodeName, 'A');
assert.equal(brand.innerText, 'Brand');
});
- it('Should add header with brand component', () => {
+ it('Should pass navbar context to navs', () => {
let instance = ReactTestUtils.renderIntoDocument(
- Brand} />
+
+
+
);
- let header = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
-
- assert.ok(header);
+ let nav = ReactTestUtils.findRenderedComponentWithType(instance, Nav);
- let brand = getOne(header.getElementsByClassName('navbar-brand'));
+ assert.ok(nav.context.$bs_navbar);
+ });
- assert.ok(brand);
- assert.equal(brand.nodeName, 'A');
- assert.equal(brand.innerText, 'Brand');
+ it('Should add default toggle', () => {
+ let instance = ReactTestUtils.renderIntoDocument(
+
+
+
+
+
+ );
- shouldWarn('deprecated');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-toggle');
+ ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, 'icon-bar');
});
- it('Should add link element with navbar-brand class using NavBrand Component', () => {
+ it('Should add custom toggle', () => {
let instance = ReactTestUtils.renderIntoDocument(
- Brand
+
+
+ hi
+
+
);
- let brand = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-brand');
-
- assert.ok(brand);
- assert.equal(brand.nodeName, 'A');
- assert.equal(brand.innerText, 'Brand');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-toggle');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'test');
});
- it('Should add only one element with navbar-brand class using NavBrand Component', () => {
+ it('Should trigger onToggle', () => {
+ let toggleSpy = sinon.spy();
let instance = ReactTestUtils.renderIntoDocument(
-
- Brand
+
+
+
+
);
- let brands = ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, 'navbar-brand');
+ let toggle = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-toggle');
- assert.equal(brands.length, 1);
- assert.equal(brands[0].nodeName, 'SPAN');
- assert.equal(brands[0].innerText, 'Brand');
+ ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(toggle));
- shouldWarn('deprecated');
+ expect(toggleSpy).to.be.calledOnce;
+ expect(toggleSpy).to.be.calledWith(true);
});
- it('Should pass navbar prop to navs', () => {
+ it('Should render collapse', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
+
+ hello
+
);
- let nav = ReactTestUtils.findRenderedComponentWithType(instance, Nav);
-
- assert.ok(nav.props.navbar);
- });
-
- it('Should pass nav prop to ul', () => {
- let instance = render( );
-
- let navNode = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'nav');
- assert.ok(navNode);
- assert.equal(navNode.nodeName, 'UL');
- assert.equal(navNode.parentNode.nodeName, 'NAV');
-
- instance = instance.renderWithProps({navbar: true});
-
- navNode = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'nav');
- assert.ok(navNode);
- assert.equal(navNode.nodeName, 'UL');
- assert.equal(navNode.parentNode.nodeName, 'DIV');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-collapse');
});
- it('Should add header when toggleNavKey is 0', () => {
+ it('Should pass expanded to Collapse', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
-
+
+
+ hello
+
);
- let header = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
+ let collapse = ReactTestUtils.findRenderedComponentWithType(instance, Navbar.Collapse);
- assert.ok(header);
+ expect(collapse.context.$bs_navbar_expanded).to.equal(true);
});
- it('Should add header when toggleNavKey is 1', () => {
+ it('Should wire the toggle to the collapse', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
-
+
+
+
+
+
+ hello
+
);
- let header = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
+ let toggle = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-toggle');
+ let collapse = ReactTestUtils.findRenderedComponentWithType(instance, Navbar.Collapse);
+
+ expect(collapse.context.$bs_navbar_expanded).to.not.be.ok;
- assert.ok(header);
+ ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(toggle));
+
+ expect(collapse.context.$bs_navbar_expanded).to.equal(true);
});
- it('Should add header when toggleNavKey is string', () => {
+ it('Should pass `bsClass` down to sub components', () => {
let instance = ReactTestUtils.renderIntoDocument(
-
-
+
+
+
+
+
+
+
+
+
+
+
);
- let header = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
-
- assert.ok(header);
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-header');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-brand');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-toggle');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-text');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-link');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-form');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-collapse');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-nav');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'my-navbar-right');
});
- it('Should show toggle button when using NavBrand', () => {
- const instance = ReactTestUtils.renderIntoDocument(
-
- Brand
-
-
- );
+ describe('deprecations', ()=> {
+ it('Should add header with brand', () => {
+ let instance = ReactTestUtils.renderIntoDocument(
+
+ );
- const toggle = ReactTestUtils.findRenderedDOMComponentWithClass(
- instance, 'navbar-toggle'
- );
- expect(toggle).to.be.ok;
+ let header = ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
+
+ assert.ok(header);
+
+ let brand = getOne(header.getElementsByClassName('navbar-brand'));
+
+ assert.ok(brand);
+ assert.equal(brand.nodeName, 'SPAN');
+ assert.equal(brand.innerText, 'Brand');
+
+ shouldWarn('deprecated');
+ });
+
+ it('Should add header when toggleNavKey is 0', () => {
+ let instance = ReactTestUtils.renderIntoDocument(
+
+
+
+ );
+
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-header');
+ ReactTestUtils.findRenderedDOMComponentWithClass(instance, 'navbar-toggle');
+
+ shouldWarn('deprecated');
+ });
});
+
});
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"\]/);
+ });
+ });
+});