diff --git a/README.md b/README.md index 7784775..7ddbe72 100644 --- a/README.md +++ b/README.md @@ -82,27 +82,27 @@ ReactDOM.render(, content); ### Prop Values #### data -`React.PropTypes.oneOfType([React.PropTypes.object,React.PropTypes.array]).isRequired` +`PropTypes.oneOfType([PropTypes.object,PropTypes.array]).isRequired` Data that drives the tree view. State-driven effects can be built by manipulating the attributes in this object. Also supports an array for multiple nodes at the root level. An example can be found in `example/data.js` #### onToggle -`React.PropTypes.func` +`PropTypes.func` Callback function when a node is toggled / clicked. Passes 2 attributes: the data node and it's toggled boolean state. #### style -`React.PropTypes.object` +`PropTypes.object` Sets the treeview styling. Defaults to `src/themes/default`. #### animations -`React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.bool])` +`PropTypes.oneOfType([PropTypes.object, PropTypes.bool])` Sets the treeview animations. Set to `false` if you want to turn off animations. See [velocity-react](https://github.com/twitter-fabric/velocity-react) for more details. Defaults to `src/themes/animations`. #### decorators -`React.PropTypes.object` +`PropTypes.object` Decorates the treeview. Here you can use your own Container, Header, Toggle and Loading components. Defaults to `src/decorators`. See example below: diff --git a/example/app.js b/example/app.js index baa9f13..9787955 100644 --- a/example/app.js +++ b/example/app.js @@ -1,8 +1,9 @@ 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; -import { StyleRoot } from 'radium'; +import {StyleRoot} from 'radium'; import {Treebeard, decorators} from '../src/index'; import data from './data'; @@ -12,87 +13,96 @@ import * as filters from './filter'; const HELP_MSG = 'Select A Node To See Its Data Structure Here...'; // Example: Customising The Header Decorator To Include Icons -decorators.Header = (props) => { - const style = props.style; - const iconType = props.node.children ? 'folder' : 'file-text'; +decorators.Header = ({style, node}) => { + const iconType = node.children ? 'folder' : 'file-text'; const iconClass = `fa fa-${iconType}`; - const iconStyle = { marginRight: '5px' }; + const iconStyle = {marginRight: '5px'}; + return (
- {props.node.name} + + {node.name}
); }; class NodeViewer extends React.Component { - constructor(props){ - super(props); - } - render(){ + render() { const style = styles.viewer; let json = JSON.stringify(this.props.node, null, 4); - if(!json){ json = HELP_MSG; } - return ( -
- {json} -
- ); + + if (!json) { + json = HELP_MSG; + } + + return
{json}
; } } - NodeViewer.propTypes = { - node: React.PropTypes.object + node: PropTypes.object }; class DemoTree extends React.Component { - constructor(props){ - super(props); + constructor() { + super(); + this.state = {data}; this.onToggle = this.onToggle.bind(this); } - onToggle(node, toggled){ - if(this.state.cursor){this.state.cursor.active = false;} + + onToggle(node, toggled) { + const {cursor} = this.state; + + if (cursor) { + cursor.active = false; + } + node.active = true; - if(node.children){ node.toggled = toggled; } - this.setState({ cursor: node }); + if (node.children) { + node.toggled = toggled; + } + + this.setState({cursor: node}); } - onFilterMouseUp(e){ + + onFilterMouseUp(e) { const filter = e.target.value.trim(); - if(!filter){ return this.setState({data}); } + if (!filter) { + return this.setState({data}); + } var filtered = filters.filterTree(data, filter); filtered = filters.expandFilteredNodes(filtered, filter); this.setState({data: filtered}); } - render(){ + + render() { + const {data: stateData, cursor} = this.state; + return (
- + - +
- +
- +
- ); } } diff --git a/package.json b/package.json index d06c0f6..08d6caa 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,13 @@ "prepublish": "npm run lib", "lib": "npm run babel", "babel": "rimraf lib && babel src/ -d lib/", - "test": "./node_modules/.bin/karma start karma.conf.js", - "test-travis": "./node_modules/karma/bin/karma start --browsers Firefox --single-run", + "test": "karma start karma.conf.js", + "test-travis": "karma start --browsers Firefox --single-run", "example": "webpack-dev-server --content-base ./example/ --config ./example/webpack.config.js" }, "peerDependencies": { - "react": "^0.14 || ^15.0", - "react-dom": "^0.14 || ^15.0" + "react": "^15.5.4", + "react-dom": "^15.5.4" }, "repository": { "type": "git", @@ -60,9 +60,8 @@ "karma-webpack": "^1.7.0", "mocha": "^2.3.3", "node-libs-browser": "^0.5.3", - "react": "^0.14.7", - "react-addons-test-utils": "^0.14.7", - "react-dom": "^0.14.7", + "react": "^15.5.4", + "react-dom": "^15.5.4", "react-hot-loader": "^1.3.0", "rimraf": "^2.4.4", "sinon": "uberVU/Sinon.JS.git", @@ -73,8 +72,9 @@ "dependencies": { "babel-runtime": "^5.8.29", "deep-equal": "^1.0.1", - "radium": "^0.18.0", + "prop-types": "^15.5.8", + "radium": "^0.19.0", "shallowequal": "^0.2.2", - "velocity-react": "^1.1.2" + "velocity-react": "^1.3.1" } } diff --git a/src/components/decorators.js b/src/components/decorators.js index 0ebe13f..ed4a250 100644 --- a/src/components/decorators.js +++ b/src/components/decorators.js @@ -1,108 +1,100 @@ 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; import Radium from 'radium'; import {VelocityComponent} from 'velocity-react'; -const Loading = (props) => { - return ( -
- loading... -
- ); +const Loading = ({style}) => { + return
loading...
; }; - Loading.propTypes = { - style: React.PropTypes.object + style: PropTypes.object }; -const Toggle = (props) => { - const style = props.style; - const height = style.height; - const width = style.width; - let midHeight = height * 0.5; - let points = `0,0 0,${height} ${width},${midHeight}`; +const Toggle = ({style}) => { + const {height, width} = style; + const midHeight = height * 0.5; + const points = `0,0 0,${height} ${width},${midHeight}`; + return (
- +
); }; - Toggle.propTypes = { - style: React.PropTypes.object + style: PropTypes.object }; -const Header = (props) => { - const style = props.style; +const Header = ({node, style}) => { return (
- {props.node.name} + {node.name}
); }; - Header.propTypes = { - style: React.PropTypes.object, - node: React.PropTypes.object.isRequired + style: PropTypes.object, + node: PropTypes.object.isRequired }; @Radium class Container extends React.Component { - constructor(props){ - super(props); - } - render(){ + render() { const {style, decorators, terminal, onClick, node} = this.props; + return ( -
- { !terminal ? this.renderToggle() : null } - +
this.clickableRef = ref} + style={style.container}> + {!terminal ? this.renderToggle() : null} + +
); } - renderToggle(){ - const animations = this.props.animations; - if(!animations){ return this.renderToggleDecorator(); } + + renderToggle() { + const {animations} = this.props; + + if (!animations) { + return this.renderToggleDecorator(); + } + return ( - + this.velocityRef = ref}> {this.renderToggleDecorator()} ); } - renderToggleDecorator(){ + + renderToggleDecorator() { const {style, decorators} = this.props; - return (); + + return ; } } - Container.propTypes = { - style: React.PropTypes.object.isRequired, - decorators: React.PropTypes.object.isRequired, - terminal: React.PropTypes.bool.isRequired, - onClick: React.PropTypes.func.isRequired, - animations: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.bool + style: PropTypes.object.isRequired, + decorators: PropTypes.object.isRequired, + terminal: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, + animations: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.bool ]).isRequired, - node: React.PropTypes.object.isRequired + node: PropTypes.object.isRequired }; export default { diff --git a/src/components/header.js b/src/components/header.js index 8b4acb3..c5edd09 100644 --- a/src/components/header.js +++ b/src/components/header.js @@ -1,52 +1,57 @@ 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; import shallowEqual from 'shallowequal'; import deepEqual from 'deep-equal'; class NodeHeader extends React.Component { - constructor(props){ - super(props); - } - shouldComponentUpdate(nextProps){ + shouldComponentUpdate(nextProps) { const props = this.props; const nextPropKeys = Object.keys(nextProps); - for(let i = 0; i < nextPropKeys.length; i++){ + + for (let i = 0; i < nextPropKeys.length; i++) { const key = nextPropKeys[i]; - if(key === 'animations'){ continue; } + if (key === 'animations') { + continue; + } + const isEqual = shallowEqual(props[key], nextProps[key]); - if(!isEqual){ return true; } + if (!isEqual) { + return true; + } } - return !deepEqual(props.animations, nextProps.animations, { strict: true }); + + return !deepEqual(props.animations, nextProps.animations, {strict: true}); } - render(){ - const {style, decorators} = this.props; - const terminal = !this.props.node.children; - const active = this.props.node.active; + + render() { + const {animations, decorators, node, onClick, style} = this.props; + const {active, children} = node; + const terminal = !children; const container = [style.link, active ? style.activeLink : null]; - const headerStyles = Object.assign({ container }, this.props.style); + const headerStyles = Object.assign({container}, style); + return ( - + ); } } NodeHeader.propTypes = { - style: React.PropTypes.object.isRequired, - decorators: React.PropTypes.object.isRequired, - animations: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.bool + style: PropTypes.object.isRequired, + decorators: PropTypes.object.isRequired, + animations: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.bool ]).isRequired, - node: React.PropTypes.object.isRequired, - onClick: React.PropTypes.func + node: PropTypes.object.isRequired, + onClick: PropTypes.func }; export default NodeHeader; diff --git a/src/components/node.js b/src/components/node.js index 16393aa..eb1c5ca 100644 --- a/src/components/node.js +++ b/src/components/node.js @@ -1,110 +1,150 @@ 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; import {VelocityTransitionGroup} from 'velocity-react'; import NodeHeader from './header'; class TreeNode extends React.Component { - constructor(props){ - super(props); + constructor() { + super(); + this.onClick = this.onClick.bind(this); } - onClick(){ - let toggled = !this.props.node.toggled; - let onToggle = this.props.onToggle; - if(onToggle){ onToggle(this.props.node, toggled); } + + onClick() { + const {node, onToggle} = this.props; + const {toggled} = node; + + if (onToggle) { + onToggle(node, !toggled); + } } - animations(){ - const props = this.props; - if(props.animations === false){ return false; } - let anim = Object.assign({}, props.animations, props.node.animations); + + animations() { + const {animations, node} = this.props; + + if (animations === false) { + return false; + } + + const anim = Object.assign({}, animations, node.animations); return { toggle: anim.toggle(this.props), drawer: anim.drawer(this.props) }; } - decorators(){ + + decorators() { // Merge Any Node Based Decorators Into The Pack - const props = this.props; - let nodeDecorators = props.node.decorators || {}; - return Object.assign({}, props.decorators, nodeDecorators); + const {decorators, node} = this.props; + let nodeDecorators = node.decorators || {}; + + return Object.assign({}, decorators, nodeDecorators); } - render(){ + + render() { + const {style} = this.props; const decorators = this.decorators(); const animations = this.animations(); + return ( -
  • +
  • this.topLevelRef = ref} + style={style.base}> {this.renderHeader(decorators, animations)} + {this.renderDrawer(decorators, animations)}
  • ); } - renderDrawer(decorators, animations){ - const toggled = this.props.node.toggled; - if(!animations && !toggled){ return null; } - if(!animations && toggled){ + + renderDrawer(decorators, animations) { + const {node: {toggled}} = this.props; + + if (!animations && !toggled) { + return null; + } else if (!animations && toggled) { return this.renderChildren(decorators, animations); } + + const {animation, duration, ...restAnimationInfo} = animations.drawer; return ( - + this.velocityRef = ref}> {toggled ? this.renderChildren(decorators, animations) : null} ); } - renderHeader(decorators, animations){ + + renderHeader(decorators, animations) { + const {node, style} = this.props; + return ( - + ); } - renderChildren(decorators){ - if(this.props.node.loading){ return this.renderLoading(decorators); } - let children = this.props.node.children; - if (!Array.isArray(children)) { children = children ? [children] : []; } + + renderChildren(decorators) { + const {animations, decorators: propDecorators, node, style} = this.props; + + if (node.loading) { + return this.renderLoading(decorators); + } + + let children = node.children; + if (!Array.isArray(children)) { + children = children ? [children] : []; + } + return ( -
      - {children.map((child, index) => - +
        this.subtreeRef = ref}> + {children.map((child, index) => )}
      ); } - renderLoading(decorators){ + + renderLoading(decorators) { + const {style} = this.props; + return ( -
        +
        • - +
        ); } - _eventBubbles(){ - return { onToggle: this.props.onToggle }; + + _eventBubbles() { + const {onToggle} = this.props; + + return { + onToggle + }; } } TreeNode.propTypes = { - style: React.PropTypes.object.isRequired, - node: React.PropTypes.object.isRequired, - decorators: React.PropTypes.object.isRequired, - animations: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.bool + style: PropTypes.object.isRequired, + node: PropTypes.object.isRequired, + decorators: PropTypes.object.isRequired, + animations: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.bool ]).isRequired, - onToggle: React.PropTypes.func + onToggle: PropTypes.func }; export default TreeNode; diff --git a/src/components/treebeard.js b/src/components/treebeard.js index 7573f55..2e15254 100644 --- a/src/components/treebeard.js +++ b/src/components/treebeard.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; import TreeNode from './node'; import defaultDecorators from './decorators'; @@ -8,24 +9,24 @@ import defaultTheme from '../themes/default'; import defaultAnimations from '../themes/animations'; class TreeBeard extends React.Component { - constructor(props){ - super(props); - } - render(){ - let data = this.props.data; + render() { + const {animations, decorators, data: propsData, onToggle, style} = this.props; + let data = propsData; + // Support Multiple Root Nodes. Its not formally a tree, but its a use-case. - if(!Array.isArray(data)){ data = [data]; } + if (!Array.isArray(data)) { + data = [data]; + } return ( -
          +
            this.treeBaseRef = ref}> {data.map((node, index) => - + )}
          ); @@ -33,17 +34,17 @@ class TreeBeard extends React.Component { } TreeBeard.propTypes = { - style: React.PropTypes.object, - data: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.array + style: PropTypes.object, + data: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.array ]).isRequired, - animations: React.PropTypes.oneOfType([ - React.PropTypes.object, - React.PropTypes.bool + animations: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.bool ]), - onToggle: React.PropTypes.func, - decorators: React.PropTypes.object + onToggle: PropTypes.func, + decorators: PropTypes.object }; TreeBeard.defaultProps = { diff --git a/src/index.js b/src/index.js index 734094f..f04b432 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,13 @@ 'use strict'; -module.exports = { - Treebeard: require('./components/treebeard'), - decorators: require('./components/decorators'), - animations: require('./themes/animations'), - theme: require('./themes/default') + +import Treebeard from './components/treebeard'; +import decorators from './components/decorators'; +import animations from './themes/animations'; +import theme from './themes/default'; + +export default { + Treebeard, + decorators, + animations, + theme }; diff --git a/src/themes/animations.js b/src/themes/animations.js index e4ce091..642c33c 100644 --- a/src/themes/animations.js +++ b/src/themes/animations.js @@ -1,22 +1,18 @@ 'use strict'; export default { - toggle: (props) => { - return { - animation: { rotateZ: props.node.toggled ? 90 : 0 }, + toggle: ({node: {toggled}}) => ({ + animation: {rotateZ: toggled ? 90 : 0}, + duration: 300 + }), + drawer: (/* props */) => ({ + enter: { + animation: 'slideDown', duration: 300 - }; - }, - drawer: (/* props */) => { - return { - enter: { - animation: 'slideDown', - duration: 300 - }, - leave: { - animation: 'slideUp', - duration: 300 - } - }; - } + }, + leave: { + animation: 'slideUp', + duration: 300 + } + }) }; diff --git a/test/src/components/decorator-tests.js b/test/src/components/decorator-tests.js index 248c4a9..7321ce1 100644 --- a/test/src/components/decorator-tests.js +++ b/test/src/components/decorator-tests.js @@ -2,20 +2,23 @@ 'use strict'; -const sinon = require('sinon'); -const React = require('react'); -const TestUtils = require('react-addons-test-utils'); -const VelocityComponent = require('velocity-react').VelocityComponent; -const defaultDecorators = require('../../../src/components/decorators'); -const factory = require('../utils/factory'); +import React from 'react'; +import TestUtils from 'react-dom/test-utils'; + +import {VelocityComponent} from 'velocity-react'; +import sinon from 'sinon'; + +import defaultDecorators from '../../../src/components/decorators'; + +import {createAnimations, createDecorators} from '../utils/factory'; const defaults = { style: {}, - node: { children: [] }, - animations: { toggle: {} }, + node: {children: []}, + animations: {toggle: {}}, terminal: false, - decorators: factory.createDecorators(), - onClick: function(){} + decorators: createDecorators(), + onClick: () => null }; const Container = defaultDecorators.Container; @@ -25,120 +28,139 @@ describe('container decorator component', () => { const onClick = sinon.spy(); const container = TestUtils.renderIntoDocument( + onClick={onClick}/> ); - const clickable = container.refs.clickable; + const clickable = container.clickableRef; TestUtils.Simulate.click(clickable); + onClick.should.be.called.once; }); it('should render the toggle decorator not terminal', () => { - const toggleType = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ toggle: toggleType }); + class toggleType extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({toggle: toggleType}); const container = TestUtils.renderIntoDocument( + decorators={decorators} + terminal={false}/> ); const toggle = TestUtils.findRenderedComponentWithType(container, toggleType); + toggle.should.exist; }); it('should not render the toggle decorator if the node is terminal', () => { - const toggleType = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ toggle: toggleType }); + class toggleType extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({toggle: toggleType}); const container = TestUtils.renderIntoDocument( + decorators={decorators} + terminal={true}/> ); const toggle = TestUtils.scryRenderedComponentsWithType(container, toggleType); + toggle.should.be.empty; }); it('should pass the style to the toggle decorator', () => { - const style = { toggle: { color: 'red' } }; - const toggleType = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ toggle: toggleType }); + const style = {toggle: {color: 'red'}}; + class toggleType extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({toggle: toggleType}); const container = TestUtils.renderIntoDocument( + decorators={decorators} + style={style}/> ); const toggle = TestUtils.findRenderedComponentWithType(container, toggleType); + toggle.props.style.should.equal(style.toggle); }); it('should render the toggle decorator in a velocity component', () => { - const container = TestUtils.renderIntoDocument( - - ); + const container = TestUtils.renderIntoDocument(); const component = TestUtils.findRenderedComponentWithType(container, VelocityComponent); + component.should.exist; }); it('should not render a velocity component if animations is false', () => { const container = TestUtils.renderIntoDocument( + animations={false}/> ); - const velocity = container.refs.velocity; + const velocity = container.velocityRef; + global.should.not.exist(velocity); }); it('should render a velocity component if animations is an object', () => { - const animations = factory.createAnimations(); + const animations = createAnimations(); const container = TestUtils.renderIntoDocument( + animations={animations}/> ); - const velocity = container.refs.velocity; + const velocity = container.velocityRef; + velocity.should.exist; }); it('should pass velocity the toggle animation and duration props', () => { - const animations = { toggle: { duration: 1, animation: 'slideUp' } }; + const animations = {toggle: {duration: 1, animation: 'slideUp'}}; const container = TestUtils.renderIntoDocument( + animations={animations}/> ); - const velocity = container.refs.velocity; + const velocity = container.velocityRef; + velocity.props.duration.should.equal(animations.toggle.duration); velocity.props.animation.should.equal(animations.toggle.animation); }); it('should render the header decorator', () => { - const headType = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ header: headType }); + class headType extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({header: headType}); const container = TestUtils.renderIntoDocument( + decorators={decorators}/> ); const head = TestUtils.findRenderedComponentWithType(container, headType); + head.should.exist; }); it('should pass the node and style to the header decorator', () => { - const style = { header: { color: 'red' } }; - const node = { name: 'terminal-node' }; - const headType = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ header: headType }); + const style = {header: {color: 'red'}}; + const node = {name: 'terminal-node'}; + class headType extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({header: headType}); const container = TestUtils.renderIntoDocument( + decorators={decorators} + node={node} + style={style}/> ); const head = TestUtils.findRenderedComponentWithType(container, headType); + head.props.style.should.equal(style.header); head.props.node.should.equal(node); }); diff --git a/test/src/components/header-tests.js b/test/src/components/header-tests.js index ce61edb..7aff8b4 100644 --- a/test/src/components/header-tests.js +++ b/test/src/components/header-tests.js @@ -2,131 +2,136 @@ 'use strict'; -const React = require('react'); -const TestUtils = require('react-addons-test-utils'); -const Header = require('../../../src/components/header'); -const factory = require('../utils/factory'); +import React from 'react'; +import TestUtils from 'react-dom/test-utils'; -const ContainerType = React.createClass({ render: () =>
          }); +import Header from '../../../src/components/header'; + +import {createDecorators} from '../utils/factory'; + +class ContainerType extends React.Component { + render() { + return
          ; + } +} const defaults = { style: {}, - node: { children: [] }, - animations: { toggle: {} }, - decorators: factory.createDecorators({ container: ContainerType }) + node: {children: []}, + animations: {toggle: {}}, + decorators: createDecorators({container: ContainerType}) }; describe('header component', () => { it('should render the container decorator', () => { - const header = TestUtils.renderIntoDocument( -
          - ); + const header = TestUtils.renderIntoDocument(
          ); const container = TestUtils.findRenderedComponentWithType(header, ContainerType); + container.should.exist; }); it('should update the component if a prop changes', () => { - const node = { toggled: false }; + const node = {toggled: false}; const header = TestUtils.renderIntoDocument(
          + node={node}/> ); - const nextProps = { node: { toggled: !node.toggled } }; + const nextProps = {node: {toggled: !node.toggled}}; + header.shouldComponentUpdate(nextProps).should.be.true; }); it('should not update the component if no props change', () => { - const node = { toggled: false }; + const node = {toggled: false}; const header = TestUtils.renderIntoDocument(
          + node={node}/> ); - const nextProps = Object.assign({}, defaults, { node: { toggled: node.toggled } }); + const nextProps = Object.assign({}, defaults, {node: {toggled: node.toggled}}); + header.shouldComponentUpdate(nextProps).should.be.false; }); it('should not update when deep nested animation props have not changed value', () => { - const animations = { nested: { prop: 'value' } }; + const animations = {nested: {prop: 'value'}}; const header = TestUtils.renderIntoDocument(
          + animations={animations}/> ); - const sameAnimationProp = { animations: { nested: { prop: animations.nested.prop } } }; + const sameAnimationProp = {animations: {nested: {prop: animations.nested.prop}}}; const nextProps = Object.assign({}, defaults, sameAnimationProp); + header.shouldComponentUpdate(nextProps).should.be.false; }); it('should update when deep nested animation props have changed value', () => { - const animations = { nested: { prop: 'value' } }; + const animations = {nested: {prop: 'value'}}; const header = TestUtils.renderIntoDocument(
          + animations={animations}/> ); - const diffAnimationProp = { animations: { nested: { prop: 'new-value' } } }; + const diffAnimationProp = {animations: {nested: {prop: 'new-value'}}}; const nextProps = Object.assign({}, defaults, diffAnimationProp); + header.shouldComponentUpdate(nextProps).should.be.true; }); it('should pass a true terminal prop to the container when there are no children in the node', () => { - const node = { name: 'terminal-node' }; + const node = {name: 'terminal-node'}; const header = TestUtils.renderIntoDocument(
          + node={node}/> ); const container = TestUtils.findRenderedComponentWithType(header, ContainerType); + container.props.terminal.should.be.true; }); it('should pass a false terminal prop to the container when there are children in the node', () => { - const node = { children: [{ name: 'child-node'}] }; + const node = {children: [{name: 'child-node'}]}; const header = TestUtils.renderIntoDocument(
          + node={node}/> ); const container = TestUtils.findRenderedComponentWithType(header, ContainerType); + container.props.terminal.should.be.false; }); it('should pass in the high-level link style to the container', () => { - const style = { link: { backgroundColor: 'black' } }; + const style = {link: {backgroundColor: 'black'}}; const header = TestUtils.renderIntoDocument(
          + style={style}/> ); const container = TestUtils.findRenderedComponentWithType(header, ContainerType); + container.props.style.container[0].should.equal(style.link); }); it('should pass the active link style prop to the container when the node is active', () => { - const node = { active: true }; - const style = { activeLink: { color: 'red' } }; + const node = {active: true}; + const style = {activeLink: {color: 'red'}}; const header = TestUtils.renderIntoDocument(
          + node={node} + style={style}/> ); const container = TestUtils.findRenderedComponentWithType(header, ContainerType); + container.props.style.container[1].should.equal(style.activeLink); }); it('should not pass the active link style prop to the container when the node is inactive', () => { - const node = { active: false }; - const style = { activeLink: { color: 'red' } }; + const node = {active: false}; + const style = {activeLink: {color: 'red'}}; const header = TestUtils.renderIntoDocument(
          + node={node} + style={style}/> ); const container = TestUtils.findRenderedComponentWithType(header, ContainerType); + global.should.not.exist(container.props.style.container[1]); }); diff --git a/test/src/components/node-tests.js b/test/src/components/node-tests.js index 2120965..892e7cb 100644 --- a/test/src/components/node-tests.js +++ b/test/src/components/node-tests.js @@ -2,40 +2,41 @@ 'use strict'; -const sinon = require('sinon'); -const React = require('react'); -const ReactDOM = require('react-dom'); -const TestUtils = require('react-addons-test-utils'); -const TreeNode = require('../../../src/components/node'); -const factory = require('../utils/factory'); +import React from 'react'; +import TestUtils from 'react-dom/test-utils'; + +import sinon from 'sinon'; +import {VelocityTransitionGroup as TransitionGroup} from 'velocity-react'; + +import NodeHeader from '../../../src/components/header'; +import TreeNode from '../../../src/components/node'; + +import {createAnimations, createDecorators} from '../utils/factory'; const defaults = { style: {}, - node: { chilren: [] }, - animations: factory.createAnimations(), - decorators: factory.createDecorators() + node: {chilren: []}, + animations: createAnimations(), + decorators: createDecorators() }; describe('node component', () => { it('should not have any internal state', () => { - const treeNode = TestUtils.renderIntoDocument( - - ); + const treeNode = TestUtils.renderIntoDocument(); + global.should.not.exist(treeNode.state); }); it('should invert the toggle state on click', (done) => { - const node = { toggled: true }; - const onToggle = function(toggledNode, toggled){ + const node = {toggled: true}; + const onToggle = (toggledNode, toggled) => { toggled.should.equal(!toggledNode.toggled); done(); }; const treeNode = TestUtils.renderIntoDocument( - + ); treeNode.onClick(); }); @@ -49,276 +50,252 @@ describe('node component', () => { /> ); treeNode.onClick(); + onToggle.should.be.called.once; }); it('should not throw an exception if a callback is not registered on click', () => { - const treeNode = TestUtils.renderIntoDocument( - - ); - (() => { treeNode.onClick(); }).should.not.throw(Error); + const treeNode = TestUtils.renderIntoDocument(); + + (() => treeNode.onClick()).should.not.throw(Error); }); it('should use the node animations if defined', () => { const nodeAnimations = { - toggle: sinon.stub().returns({ duration: 0, animation: 'fadeIn' }), - drawer: sinon.stub().returns({ duration: 0, animation: 'fadeIn' }) + toggle: sinon.stub().returns({duration: 0, animation: 'fadeIn'}), + drawer: sinon.stub().returns({duration: 0, animation: 'fadeIn'}) }; - const node = { animations: nodeAnimations }; + const node = {animations: nodeAnimations}; const treeNode = TestUtils.renderIntoDocument( - + ); treeNode.animations(); + nodeAnimations.toggle.should.be.calledWith(treeNode.props); nodeAnimations.drawer.should.be.calledWith(treeNode.props); }); it('should fallback to the prop animations if the node animations are not defined', () => { const animations = { - toggle: sinon.stub().returns({ duration: 0, animation: 'fadeIn' }), - drawer: sinon.stub().returns({ duration: 0, animation: 'fadeIn' }) + toggle: sinon.stub().returns({duration: 0, animation: 'fadeIn'}), + drawer: sinon.stub().returns({duration: 0, animation: 'fadeIn'}) }; const treeNode = TestUtils.renderIntoDocument( - + ); treeNode.animations(); + animations.toggle.should.be.calledWith(treeNode.props); animations.drawer.should.be.calledWith(treeNode.props); }); it('should use the node decorators if defined', () => { - const ContainerDecorator = React.createClass({ render: () =>
          }); + class ContainerDecorator extends React.Component { + render() { + return
          ; + } + } const nodeDecorators = { Container: ContainerDecorator }; - const node = { decorators: nodeDecorators, children: [] }; + const node = {decorators: nodeDecorators, children: []}; const treeNode = TestUtils.renderIntoDocument( - + ); - TestUtils.findRenderedComponentWithType(treeNode, ContainerDecorator).should.exist; + const component = TestUtils.findRenderedComponentWithType(treeNode, ContainerDecorator); + + component.should.exist; }); it('should fallback to the prop decorators if the node decorators are not defined', () => { - const ContainerDecorator = React.createClass({ render: () =>
          }); + class ContainerDecorator extends React.Component { + render() { + return
          ; + } + } const decorators = { Container: ContainerDecorator }; - const node = { children: [] }; + const node = {children: []}; const treeNode = TestUtils.renderIntoDocument( - + ); - TestUtils.findRenderedComponentWithType(treeNode, ContainerDecorator).should.exist; + const component = TestUtils.findRenderedComponentWithType(treeNode, ContainerDecorator); + + component.should.exist; }); it('should render a list item at the top level', () => { const treeNode = TestUtils.renderIntoDocument( ); - const topLevel = treeNode.refs.topLevel; + const topLevel = treeNode.topLevelRef; topLevel.tagName.toLowerCase().should.equal('li'); }); it('should render the NodeHeader component', () => { - const NodeHeader = require('../../../src/components/header'); - const treeNode = TestUtils.renderIntoDocument( - - ); - TestUtils.findRenderedComponentWithType(treeNode, NodeHeader).should.exist; + const treeNode = TestUtils.renderIntoDocument(); + const component = TestUtils.findRenderedComponentWithType(treeNode, NodeHeader); + + component.should.exist; }); it('should render the subtree if toggled', () => { - const node = { toggled: true }; - const treeNode = TestUtils.renderIntoDocument( - - ); - treeNode.refs.subtree.should.exist; + const node = {toggled: true}; + const treeNode = TestUtils.renderIntoDocument(); + + treeNode.subtreeRef.should.exist; }); it('should not render the children if not toggled', () => { - const node = { toggled: false }; - const treeNode = TestUtils.renderIntoDocument( - - ); - global.should.not.exist(treeNode.refs.subtree); + const node = {toggled: false}; + const treeNode = TestUtils.renderIntoDocument(); + + global.should.not.exist(treeNode.subtreeRef); }); it('should wrap the children in a velocity transition group', () => { - const TransitionGroup = require('velocity-react').VelocityTransitionGroup; - const treeNode = TestUtils.renderIntoDocument( - - ); + const treeNode = TestUtils.renderIntoDocument(); const component = TestUtils.findRenderedComponentWithType(treeNode, TransitionGroup); + component.should.exist; }); it('should pass velocity the drawer enter animation and duration props', () => { - const animations = factory.createAnimations(); + const animations = createAnimations(); const treeNode = TestUtils.renderIntoDocument( + animations={animations}/> ); - const velocity = treeNode.refs.velocity; + const velocity = treeNode.velocityRef; const drawer = animations.drawer(); + velocity.props.enter.animation.should.equal(drawer.enter.animation); velocity.props.enter.duration.should.equal(drawer.enter.duration); }); it('should pass velocity the drawer leave animation and duration props', () => { - const animations = factory.createAnimations(); + const animations = createAnimations(); const treeNode = TestUtils.renderIntoDocument( + animations={animations}/> ); - const velocity = treeNode.refs.velocity; + const velocity = treeNode.velocityRef; const drawer = animations.drawer(); + velocity.props.leave.animation.should.equal(drawer.leave.animation); velocity.props.leave.duration.should.equal(drawer.leave.duration); }); it('should not render a velocity component if animations is false and not toggled', () => { - const node = { toggled: false }; + const node = {toggled: false}; const treeNode = TestUtils.renderIntoDocument( + animations={false} + node={node}/> ); - const velocity = treeNode.refs.velocity; + const velocity = treeNode.velocityRef; + global.should.not.exist(velocity); }); it('should not render a velocity component if animations is false and toggled', () => { - const node = { toggled: true }; + const node = {toggled: true}; const treeNode = TestUtils.renderIntoDocument( + animations={false} + node={node}/> ); - const velocity = treeNode.refs.velocity; + const velocity = treeNode.velocityRef; + global.should.not.exist(velocity); }); it('should render a velocity component if animations is an object', () => { - const animations = factory.createAnimations(); + const animations = createAnimations(); const treeNode = TestUtils.renderIntoDocument( + animations={animations}/> ); - const velocity = treeNode.refs.velocity; + const velocity = treeNode.velocityRef; + velocity.should.exist; }); it('should wrap the children in a list', () => { - const node = { toggled: true }; + const node = {toggled: true}; const treeNode = TestUtils.renderIntoDocument( + node={node}/> ); - const subtree = treeNode.refs.subtree; + const subtree = treeNode.subtreeRef; + subtree.tagName.toLowerCase().should.equal('ul'); }); it('should render a TreeNode component for each child', () => { const node = { toggled: true, - children: [ {node: {}}, {node: {}}, {node: {}} ] + children: [{node: {}}, {node: {}}, {node: {}}] }; const treeNode = TestUtils.renderIntoDocument( + node={node}/> ); // Find All TreeNodes (+ Top Level TreeNode) const nodes = TestUtils.scryRenderedComponentsWithType(treeNode, TreeNode); + nodes.length.should.equal(node.children.length + 1); }); it('should render the loading decorator if the node is loading and toggled', () => { - const node = { toggled: true, loading: true }; - const LoadingDecorator = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ loading: LoadingDecorator }); + const node = {toggled: true, loading: true}; + class LoadingDecorator extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({loading: LoadingDecorator}); const treeNode = TestUtils.renderIntoDocument( + node={node} + decorators={decorators}/> ); const loading = TestUtils.findRenderedComponentWithType(treeNode, LoadingDecorator); + loading.should.exist; }); it('should not render the loading decorator if the node is not loading but toggled', () => { - const node = { toggled: true, loading: false }; - const LoadingDecorator = React.createClass({ render: () =>
          }); - const decorators = factory.createDecorators({ loading: LoadingDecorator }); + const node = {toggled: true, loading: false}; + class LoadingDecorator extends React.Component { + render() { + return
          ; + } + } + const decorators = createDecorators({loading: LoadingDecorator}); const treeNode = TestUtils.renderIntoDocument( + node={node} + decorators={decorators}/> ); const loading = TestUtils.scryRenderedComponentsWithType(treeNode, LoadingDecorator); + loading.should.be.empty; }); it('should not render the children if the node is Loading', () => { - const node = { toggled: true, loading: true }; - const treeNode = TestUtils.renderIntoDocument( - - ); - global.should.not.exist(treeNode.refs.subtree); - }); - - it('should render a child with an id key if available', () => { - const id = 'SpecialNode'; - const node = { - toggled: true, - children: [{ id }] - }; + const node = {toggled: true, loading: true}; const treeNode = TestUtils.renderIntoDocument( + node={node}/> ); - const nodes = TestUtils.scryRenderedComponentsWithType(treeNode, TreeNode); - const element = ReactDOM.findDOMNode(nodes[1]); - const expectedId = '$' + id; - element.dataset.reactid.should.contain(expectedId); - }); - it('should render a child with an index key if id is not available', () => { - const node = { - toggled: true, - children: [{ name: 'node' }] - }; - const treeNode = TestUtils.renderIntoDocument( - - ); - const nodes = TestUtils.scryRenderedComponentsWithType(treeNode, TreeNode); - const element = ReactDOM.findDOMNode(nodes[1]); - const expectedId = '$0'; - element.dataset.reactid.should.contain(expectedId); + global.should.not.exist(treeNode.subtreeRef); }); }); diff --git a/test/src/components/treebeard-tests.js b/test/src/components/treebeard-tests.js index 7f3b35b..bea7134 100644 --- a/test/src/components/treebeard-tests.js +++ b/test/src/components/treebeard-tests.js @@ -2,11 +2,14 @@ 'use strict'; -const React = require('react'); -const ReactDOM = require('react-dom'); -const TestUtils = require('react-addons-test-utils'); -const TreeNode = require('../../../src/components/node'); -const Treebeard = require('../../../src/components/treebeard'); +import React from 'react'; +import TestUtils from 'react-dom/test-utils'; + +import defaultDecorators from '../../../src/components/decorators'; +import TreeNode from '../../../src/components/node'; +import Treebeard from '../../../src/components/treebeard'; +import defaultAnimations from '../../../src/themes/animations'; +import defaultTheme from '../../../src/themes/default'; const defaults = { name: '', @@ -15,93 +18,59 @@ const defaults = { describe('treebeard component', () => { it('should render the treebase as a list', () => { - const treebeard = TestUtils.renderIntoDocument( - - ); - const treeBase = treebeard.refs.treeBase; + const treebeard = TestUtils.renderIntoDocument(); + const treeBase = treebeard.treeBaseRef; + treeBase.tagName.toLowerCase().should.equal('ul'); }); it('should render the treebase as a list', () => { - const treebeard = TestUtils.renderIntoDocument( - - ); + const treebeard = TestUtils.renderIntoDocument(); const nodes = TestUtils.scryRenderedComponentsWithType(treebeard, TreeNode); + nodes.length.should.equal(1); }); it('should pass the top level tree node the associated props', () => { const treebeard = TestUtils.renderIntoDocument( - {}} - /> + null}/> ); const node = TestUtils.findRenderedComponentWithType(treebeard, TreeNode); + node.props.node.should.equal(treebeard.props.data); node.props.onToggle.should.equal(treebeard.props.onToggle); }); it('should use the default theme if none specified', () => { - const treebeard = TestUtils.renderIntoDocument( - - ); + const treebeard = TestUtils.renderIntoDocument(); const node = TestUtils.findRenderedComponentWithType(treebeard, TreeNode); - const defaultTheme = require('../../../src/themes/default'); + node.props.style.should.equal(defaultTheme.tree.node); }); it('should use the default animations if none specified', () => { - const treebeard = TestUtils.renderIntoDocument( - - ); + const treebeard = TestUtils.renderIntoDocument(); const node = TestUtils.findRenderedComponentWithType(treebeard, TreeNode); - const defaultAnimations = require('../../../src/themes/animations'); + node.props.animations.should.equal(defaultAnimations); }); it('should use the default decorators if none specified', () => { - const treebeard = TestUtils.renderIntoDocument( - - ); + const treebeard = TestUtils.renderIntoDocument(); const node = TestUtils.findRenderedComponentWithType(treebeard, TreeNode); - const defaultDecorators = require('../../../src/components/decorators'); + node.props.decorators.should.equal(defaultDecorators); }); it('should support rendering multiple nodes at the root level', () => { const multipleRootNodes = [ - { name: 'root-1', children: [] }, - { name: 'root-2', children: [] } + {name: 'root-1', children: []}, + {name: 'root-2', children: []} ]; - const treebeard = TestUtils.renderIntoDocument( - - ); + const treebeard = TestUtils.renderIntoDocument(); const nodes = TestUtils.scryRenderedComponentsWithType(treebeard, TreeNode); - nodes.length.should.equal(multipleRootNodes.length); - }); - it('should render a root node with an id key if available', () => { - const id = 'RootNode'; - const rootNode = { id: id, name: 'root-1', children: [] }; - const treebeard = TestUtils.renderIntoDocument( - - ); - const node = TestUtils.findRenderedComponentWithType(treebeard, TreeNode); - const element = ReactDOM.findDOMNode(node); - const expectedId = '$' + id; - element.dataset.reactid.should.contain(expectedId); - }); - - it('should render a root node with an index key if id is not available', () => { - const rootNode = { name: 'root-1', children: [] }; - const treebeard = TestUtils.renderIntoDocument( - - ); - const node = TestUtils.findRenderedComponentWithType(treebeard, TreeNode); - const element = ReactDOM.findDOMNode(node); - const expectedId = '$0'; - element.dataset.reactid.should.contain(expectedId); + nodes.length.should.equal(multipleRootNodes.length); }); - }); diff --git a/test/src/utils/factory.js b/test/src/utils/factory.js index c6d4fe0..30f9a35 100644 --- a/test/src/utils/factory.js +++ b/test/src/utils/factory.js @@ -1,46 +1,45 @@ 'use strict'; -const React = require('react'); +import React from 'react'; -module.exports = { - createDecorators: function(spec){ - spec = spec || {}; - return { - Loading: (props) => { - return spec.loading ? :
          ; - }, - Toggle: (props) => { - return spec.toggle ? :
          ; - }, - Header: (props) => { - return spec.header ? :
          ; - }, - Container: (props) => { - return spec.container ? :
          ; - } +export const createDecorators = (spec) => { + spec = spec || {}; + return { + Loading: (props) => { + return spec.loading ? :
          ; + }, + Toggle: (props) => { + return spec.toggle ? :
          ; + }, + Header: (props) => { + return spec.header ? :
          ; + }, + Container: (props) => { + return spec.container ? :
          ; + } - }; - }, - createAnimations: function(){ - return { - toggle: () => { - return { - animation: 'fadeOut', + }; +}; + +export const createAnimations = () => { + return { + toggle: () => { + return { + animation: 'fadeOut', + duration: 0 + }; + }, + drawer: () => { + return { + enter: { + animation: 'slideDown', + duration: 0 + }, + leave: { + animation: 'slideUp', duration: 0 - }; - }, - drawer: () => { - return { - enter: { - animation: 'slideDown', - duration: 0 - }, - leave: { - animation: 'slideUp', - duration: 0 - } - }; - } - }; - } + } + }; + } + }; };