From eeb0aea00c4f75208948d40b1f95aface057478c Mon Sep 17 00:00:00 2001 From: jquense Date: Mon, 17 Aug 2015 10:27:25 -0400 Subject: [PATCH] added react-overlays for base overlay components --- docs/src/PropTable.js | 19 +- package.json | 3 +- src/Collapse.js | 6 +- src/Fade.js | 6 +- src/Modal.js | 2 +- src/Overlay.js | 98 ++--------- src/Portal.js | 92 +--------- src/Position.js | 135 +-------------- src/RootCloseWrapper.js | 87 ---------- src/Transition.js | 272 +---------------------------- src/utils/overlayPositionUtils.js | 109 +----------- test/OverlayTriggerSpec.js | 1 - test/PortalSpec.js | 78 --------- test/PositionSpec.js | 218 ----------------------- test/TransitionSpec.js | 279 ------------------------------ 15 files changed, 35 insertions(+), 1370 deletions(-) delete mode 100644 src/RootCloseWrapper.js delete mode 100644 test/PortalSpec.js delete mode 100644 test/PositionSpec.js delete mode 100644 test/TransitionSpec.js diff --git a/docs/src/PropTable.js b/docs/src/PropTable.js index 250a70a8f6..3c35233e72 100644 --- a/docs/src/PropTable.js +++ b/docs/src/PropTable.js @@ -7,21 +7,22 @@ import Table from '../../src/Table'; let cleanDocletValue = str => str.trim().replace(/^\{/, '').replace(/\}$/, ''); -function getPropsData(componentData, metadata){ - +function getPropsData(component, metadata){ + let componentData = metadata[component] || {}; let props = componentData.props || {}; if (componentData.composes) { - componentData.composes.forEach( other => { - props = merge({}, getPropsData(metadata[other] || {}, metadata), props); - + componentData.composes.forEach(other => { + if (other !== component) { + props = merge({}, getPropsData(other, metadata), props); + } }); } if (componentData.mixins) { componentData.mixins.forEach( other => { - if ( componentData.composes.indexOf(other) === -1) { - props = merge({}, getPropsData(metadata[other] || {}, metadata), props); + if (other !== component && componentData.composes.indexOf(other) === -1) { + props = merge({}, getPropsData(other, metadata), props); } }); } @@ -36,9 +37,7 @@ const PropTable = React.createClass({ }, componentWillMount(){ - let componentData = this.context.metadata[this.props.component] || {}; - - this.propsData = getPropsData(componentData, this.context.metadata); + this.propsData = getPropsData(this.props.component, this.context.metadata); }, render(){ diff --git a/package.json b/package.json index 84e2a3b43d..b0b0181636 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ }, "dependencies": { "babel-runtime": "^5.8.19", + "classnames": "^2.1.3", "lodash": "^3.10.0", - "classnames": "^2.1.3" + "react-overlays": "^0.4.2" } } diff --git a/src/Collapse.js b/src/Collapse.js index ab8e4b652f..e38e3fcb0c 100644 --- a/src/Collapse.js +++ b/src/Collapse.js @@ -1,5 +1,5 @@ import React from 'react'; -import Transition from './Transition'; +import Transition from 'react-overlays/lib/Transition'; import domUtils from './utils/domUtils'; import createChainedFunction from './utils/createChainedFunction'; @@ -138,7 +138,7 @@ Collapse.propTypes = { * finishing callbacks are fired even if the original browser transition end * events are canceled */ - duration: React.PropTypes.number, + timeout: React.PropTypes.number, /** * Callback fired before the component expands @@ -194,7 +194,7 @@ Collapse.propTypes = { Collapse.defaultProps = { in: false, - duration: 300, + timeout: 300, unmountOnExit: false, transitionAppear: false, diff --git a/src/Fade.js b/src/Fade.js index ce5a9a3370..a707ea386b 100644 --- a/src/Fade.js +++ b/src/Fade.js @@ -1,5 +1,5 @@ import React from 'react'; -import Transition from './Transition'; +import Transition from 'react-overlays/lib/Transition'; class Fade extends React.Component { render() { @@ -41,7 +41,7 @@ Fade.propTypes = { * callbacks are fired even if the original browser transition end events are * canceled */ - duration: React.PropTypes.number, + timeout: React.PropTypes.number, /** * Callback fired before the component fades in @@ -71,7 +71,7 @@ Fade.propTypes = { Fade.defaultProps = { in: false, - duration: 300, + timeout: 300, unmountOnExit: false, transitionAppear: false }; diff --git a/src/Modal.js b/src/Modal.js index 48de68689f..7bc2677c4a 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -6,7 +6,7 @@ import EventListener from './utils/EventListener'; import createChainedFunction from './utils/createChainedFunction'; import CustomPropTypes from './utils/CustomPropTypes'; -import Portal from './Portal'; +import Portal from 'react-overlays/lib/Portal'; import Fade from './Fade'; import ModalDialog from './ModalDialog'; import Body from './ModalBody'; diff --git a/src/Overlay.js b/src/Overlay.js index 379aea35f9..c8b3c0f6bf 100644 --- a/src/Overlay.js +++ b/src/Overlay.js @@ -2,115 +2,43 @@ /* These properties are validated in 'Portal' and 'Position' components */ import React, { cloneElement } from 'react'; -import Portal from './Portal'; -import Position from './Position'; -import RootCloseWrapper from './RootCloseWrapper'; +import BaseOverlay from 'react-overlays/lib/Overlay'; import CustomPropTypes from './utils/CustomPropTypes'; import Fade from './Fade'; import classNames from 'classnames'; class Overlay extends React.Component { - constructor(props, context) { - super(props, context); - - this.state = {exited: !props.show}; - this.onHiddenListener = this.handleHidden.bind(this); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.show) { - this.setState({exited: false}); - } else if (!nextProps.animation) { - // Otherwise let handleHidden take care of marking exited. - this.setState({exited: true}); - } - } render() { let { - container - , containerPadding - , target - , placement - , rootClose - , children - , animation: Transition + children: child + , animation: transition , ...props } = this.props; - if (Transition === true) { - Transition = Fade; + if (transition === true) { + transition = Fade; } - // Don't un-render the overlay while it's transitioning out. - const mountOverlay = props.show || (Transition && !this.state.exited); - if (!mountOverlay) { - // Don't bother showing anything if we don't have to. - return null; - } - - let child = children; - - // Position is be inner-most because it adds inline styles into the child, - // which the other wrappers don't forward correctly. - child = ( - - {child} - - ); - - if (Transition) { - let { onExit, onExiting, onEnter, onEntering, onEntered } = props; - - // This animates the child node by injecting props, so it must precede - // anything that adds a wrapping div. - child = ( - - {child} - - ); - } else { + if (!transition) { child = cloneElement(child, { className: classNames('in', child.props.className) }); } - // This goes after everything else because it adds a wrapping div. - if (rootClose) { - child = ( - - {child} - - ); - } - return ( - + {child} - + ); } - - handleHidden(...args) { - this.setState({exited: true}); - - if (this.props.onExited) { - this.props.onExited(...args); - } - } } Overlay.propTypes = { - ...Portal.propTypes, - ...Position.propTypes, + ...BaseOverlay.propTypes, + /** * Set the visibility of the Overlay */ diff --git a/src/Portal.js b/src/Portal.js index 7e87fcf08f..9741a0f1d5 100644 --- a/src/Portal.js +++ b/src/Portal.js @@ -1,93 +1,3 @@ -import React from 'react'; -import CustomPropTypes from './utils/CustomPropTypes'; -import domUtils from './utils/domUtils'; - -let Portal = React.createClass({ - - displayName: 'Portal', - - propTypes: { - /** - * The DOM Node that the Component will render it's children into - */ - container: CustomPropTypes.mountable - }, - - componentDidMount() { - this._renderOverlay(); - }, - - componentDidUpdate() { - this._renderOverlay(); - }, - - componentWillUnmount() { - this._unrenderOverlay(); - this._unmountOverlayTarget(); - }, - - _mountOverlayTarget() { - if (!this._overlayTarget) { - this._overlayTarget = document.createElement('div'); - this.getContainerDOMNode() - .appendChild(this._overlayTarget); - } - }, - - _unmountOverlayTarget() { - if (this._overlayTarget) { - this.getContainerDOMNode() - .removeChild(this._overlayTarget); - this._overlayTarget = null; - } - }, - - _renderOverlay() { - let overlay = !this.props.children - ? null - : React.Children.only(this.props.children); - - // Save reference for future access. - if (overlay !== null) { - this._mountOverlayTarget(); - this._overlayInstance = React.render(overlay, this._overlayTarget); - } else { - // Unrender if the component is null for transitions to null - this._unrenderOverlay(); - this._unmountOverlayTarget(); - } - }, - - _unrenderOverlay() { - if (this._overlayTarget) { - React.unmountComponentAtNode(this._overlayTarget); - this._overlayInstance = null; - } - }, - - render() { - return null; - }, - - getOverlayDOMNode() { - if (!this.isMounted()) { - throw new Error('getOverlayDOMNode(): A component must be mounted to have a DOM node.'); - } - - if (this._overlayInstance) { - if (this._overlayInstance.getWrappedDOMNode) { - return this._overlayInstance.getWrappedDOMNode(); - } else { - return React.findDOMNode(this._overlayInstance); - } - } - - return null; - }, - - getContainerDOMNode() { - return React.findDOMNode(this.props.container) || domUtils.ownerDocument(this).body; - } -}); +import Portal from 'react-overlays/lib/Portal'; export default Portal; diff --git a/src/Position.js b/src/Position.js index 523c7973f7..4c20168ae9 100644 --- a/src/Position.js +++ b/src/Position.js @@ -1,136 +1,3 @@ -import React, { cloneElement } from 'react'; -import classNames from 'classnames'; -import domUtils from './utils/domUtils'; -import { calcOverlayPosition } from './utils/overlayPositionUtils'; -import CustomPropTypes from './utils/CustomPropTypes'; - -class Position extends React.Component { - constructor(props, context) { - super(props, context); - - this.state = { - positionLeft: null, - positionTop: null, - arrowOffsetLeft: null, - arrowOffsetTop: null - }; - - this._needsFlush = false; - this._lastTarget = null; - } - - componentDidMount() { - this.updatePosition(); - } - - componentWillReceiveProps() { - this._needsFlush = true; - } - - componentDidUpdate() { - if (this._needsFlush) { - this._needsFlush = false; - this.updatePosition(); - } - } - - componentWillUnmount() { - // Probably not necessary, but just in case holding a reference to the - // target causes problems somewhere. - this._lastTarget = null; - } - - render() { - const {children, className, ...props} = this.props; - const {positionLeft, positionTop, ...arrowPosition} = this.state; - - const child = React.Children.only(children); - return cloneElement( - child, - { - ...props, - ...arrowPosition, - positionTop, - positionLeft, - className: classNames(className, child.props.className), - style: { - ...child.props.style, - left: positionLeft, - top: positionTop - } - } - ); - } - - getTargetSafe() { - if (!this.props.target) { - return null; - } - - const target = this.props.target(this.props); - if (!target) { - // This is so we can just use === check below on all falsy targets. - return null; - } - - return target; - } - - updatePosition() { - const target = this.getTargetSafe(); - if (target === this._lastTarget) { - return; - } - this._lastTarget = target; - - if (!target) { - this.setState({ - positionLeft: null, - positionTop: null, - arrowOffsetLeft: null, - arrowOffsetTop: null - }); - - return; - } - - const overlay = React.findDOMNode(this); - const container = - React.findDOMNode(this.props.container) || - domUtils.ownerDocument(this).body; - - this.setState(calcOverlayPosition( - this.props.placement, - overlay, - target, - container, - this.props.containerPadding - )); - } -} - -Position.propTypes = { - /** - * Function mapping props to DOM node the component is positioned next to - */ - target: React.PropTypes.func, - /** - * "offsetParent" of the component - */ - container: CustomPropTypes.mountable, - /** - * Minimum spacing in pixels between container border and component border - */ - containerPadding: React.PropTypes.number, - /** - * How to position the component relative to the target - */ - placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']) -}; - -Position.defaultProps = { - containerPadding: 0, - placement: 'right' -}; +import Position from 'react-overlays/lib/Position'; export default Position; diff --git a/src/RootCloseWrapper.js b/src/RootCloseWrapper.js deleted file mode 100644 index 35fecab769..0000000000 --- a/src/RootCloseWrapper.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import domUtils from './utils/domUtils'; -import EventListener from './utils/EventListener'; - -// TODO: Merge this logic with dropdown logic once #526 is done. - -// TODO: Consider using an ES6 symbol here, once we use babel-runtime. -const CLICK_WAS_INSIDE = '__click_was_inside'; - -function suppressRootClose(event) { - // Tag the native event to prevent the root close logic on document click. - // This seems safer than using event.nativeEvent.stopImmediatePropagation(), - // which is only supported in IE >= 9. - event.nativeEvent[CLICK_WAS_INSIDE] = true; -} - -export default class RootCloseWrapper extends React.Component { - constructor(props) { - super(props); - - this.handleDocumentClick = this.handleDocumentClick.bind(this); - this.handleDocumentKeyUp = this.handleDocumentKeyUp.bind(this); - } - - bindRootCloseHandlers() { - const doc = domUtils.ownerDocument(this); - - this._onDocumentClickListener = - EventListener.listen(doc, 'click', this.handleDocumentClick); - this._onDocumentKeyupListener = - EventListener.listen(doc, 'keyup', this.handleDocumentKeyUp); - } - - handleDocumentClick(e) { - // This is now the native event. - if (e[CLICK_WAS_INSIDE]) { - return; - } - - this.props.onRootClose(); - } - - handleDocumentKeyUp(e) { - if (e.keyCode === 27) { - this.props.onRootClose(); - } - } - - unbindRootCloseHandlers() { - if (this._onDocumentClickListener) { - this._onDocumentClickListener.remove(); - } - - if (this._onDocumentKeyupListener) { - this._onDocumentKeyupListener.remove(); - } - } - - componentDidMount() { - this.bindRootCloseHandlers(); - } - - render() { - // Wrap the child in a new element, so the child won't have to handle - // potentially combining multiple onClick listeners. - return ( -
- {React.Children.only(this.props.children)} -
- ); - } - - getWrappedDOMNode() { - // We can't use a ref to identify the wrapped child, since we might be - // stealing the ref from the owner, but we know exactly the DOM structure - // that will be rendered, so we can just do this to get the child's DOM - // node for doing size calculations in OverlayMixin. - return React.findDOMNode(this).children[0]; - } - - componentWillUnmount() { - this.unbindRootCloseHandlers(); - } -} -RootCloseWrapper.propTypes = { - onRootClose: React.PropTypes.func.isRequired -}; diff --git a/src/Transition.js b/src/Transition.js index 8235d41f9f..3ba7d50deb 100644 --- a/src/Transition.js +++ b/src/Transition.js @@ -1,273 +1,3 @@ -import React from 'react'; -import TransitionEvents from './utils/TransitionEvents'; -import classnames from 'classnames'; - -export const UNMOUNTED = 0; -export const EXITED = 1; -export const ENTERING = 2; -export const ENTERED = 3; -export const EXITING = 4; - -class Transition extends React.Component { - constructor(props, context) { - super(props, context); - - let initialStatus; - if (props.in) { - // Start enter transition in componentDidMount. - initialStatus = props.transitionAppear ? EXITED : ENTERED; - } else { - initialStatus = props.unmountOnExit ? UNMOUNTED : EXITED; - } - this.state = {status: initialStatus}; - - this.nextCallback = null; - } - - componentDidMount() { - if (this.props.transitionAppear && this.props.in) { - this.performEnter(this.props); - } - } - - componentWillReceiveProps(nextProps) { - const status = this.state.status; - if (nextProps.in) { - if (status === EXITING) { - this.performEnter(nextProps); - } else if (this.props.unmountOnExit) { - if (status === UNMOUNTED) { - // Start enter transition in componentDidUpdate. - this.setState({status: EXITED}); - } - } else if (status === EXITED) { - this.performEnter(nextProps); - } - - // Otherwise we're already entering or entered. - } else { - if (status === ENTERING || status === ENTERED) { - this.performExit(nextProps); - } - - // Otherwise we're already exited or exiting. - } - } - - componentDidUpdate() { - if (this.props.unmountOnExit && this.state.status === EXITED) { - // EXITED is always a transitional state to either ENTERING or UNMOUNTED - // when using unmountOnExit. - if (this.props.in) { - this.performEnter(this.props); - } else { - this.setState({status: UNMOUNTED}); - } - } - } - - componentWillUnmount() { - this.cancelNextCallback(); - } - - performEnter(props) { - this.cancelNextCallback(); - const node = React.findDOMNode(this); - - // Not this.props, because we might be about to receive new props. - props.onEnter(node); - - this.safeSetState({status: ENTERING}, () => { - this.props.onEntering(node); - - this.onTransitionEnd(node, () => { - this.safeSetState({status: ENTERED}, () => { - this.props.onEntered(node); - }); - }); - }); - } - - performExit(props) { - this.cancelNextCallback(); - const node = React.findDOMNode(this); - - // Not this.props, because we might be about to receive new props. - props.onExit(node); - - this.safeSetState({status: EXITING}, () => { - this.props.onExiting(node); - - this.onTransitionEnd(node, () => { - this.safeSetState({status: EXITED}, () => { - this.props.onExited(node); - }); - }); - }); - } - - cancelNextCallback() { - if (this.nextCallback !== null) { - this.nextCallback.cancel(); - this.nextCallback = null; - } - } - - safeSetState(nextState, callback) { - // This shouldn't be necessary, but there are weird race conditions with - // setState callbacks and unmounting in testing, so always make sure that - // we can cancel any pending setState callbacks after we unmount. - this.setState(nextState, this.setNextCallback(callback)); - } - - setNextCallback(callback) { - let active = true; - - this.nextCallback = (event) => { - if (active) { - active = false; - this.nextCallback = null; - - callback(event); - } - }; - - this.nextCallback.cancel = () => { - active = false; - }; - - return this.nextCallback; - } - - onTransitionEnd(node, handler) { - this.setNextCallback(handler); - - if (node) { - TransitionEvents.addEndEventListener(node, this.nextCallback); - setTimeout(this.nextCallback, this.props.duration); - } else { - setTimeout(this.nextCallback, 0); - } - } - - render() { - const status = this.state.status; - if (status === UNMOUNTED) { - return null; - } - - const {children, className, ...childProps} = this.props; - Object.keys(Transition.propTypes).forEach(key => delete childProps[key]); - - let transitionClassName; - if (status === EXITED) { - transitionClassName = this.props.exitedClassName; - } else if (status === ENTERING) { - transitionClassName = this.props.enteringClassName; - } else if (status === ENTERED) { - transitionClassName = this.props.enteredClassName; - } else if (status === EXITING) { - transitionClassName = this.props.exitingClassName; - } - - const child = React.Children.only(children); - return React.cloneElement( - child, - { - ...childProps, - className: classnames( - child.props.className, - className, - transitionClassName - ) - } - ); - } -} - -Transition.propTypes = { - /** - * Show the component; triggers the enter or exit animation - */ - in: React.PropTypes.bool, - - /** - * Unmount the component (remove it from the DOM) when it is not shown - */ - unmountOnExit: React.PropTypes.bool, - - /** - * Run the enter animation when the component mounts, if it is initially - * shown - */ - transitionAppear: React.PropTypes.bool, - - /** - * Duration of the animation in milliseconds, to ensure that finishing - * callbacks are fired even if the original browser transition end events are - * canceled - */ - duration: React.PropTypes.number, - - /** - * CSS class or classes applied when the component is exited - */ - exitedClassName: React.PropTypes.string, - /** - * CSS class or classes applied while the component is exiting - */ - exitingClassName: React.PropTypes.string, - /** - * CSS class or classes applied when the component is entered - */ - enteredClassName: React.PropTypes.string, - /** - * CSS class or classes applied while the component is entering - */ - enteringClassName: React.PropTypes.string, - - /** - * Callback fired before the "entering" classes are applied - */ - onEnter: React.PropTypes.func, - /** - * Callback fired after the "entering" classes are applied - */ - onEntering: React.PropTypes.func, - /** - * Callback fired after the "enter" classes are applied - */ - onEntered: React.PropTypes.func, - /** - * Callback fired before the "exiting" classes are applied - */ - onExit: React.PropTypes.func, - /** - * Callback fired after the "exiting" classes are applied - */ - onExiting: React.PropTypes.func, - /** - * Callback fired after the "exited" classes are applied - */ - onExited: React.PropTypes.func -}; - -// Name the function so it is clearer in the documentation -function noop() {} - -Transition.defaultProps = { - in: false, - duration: 300, - unmountOnExit: false, - transitionAppear: false, - - onEnter: noop, - onEntering: noop, - onEntered: noop, - - onExit: noop, - onExiting: noop, - onExited: noop -}; +import Transition from 'react-overlays/lib/Transition'; export default Transition; diff --git a/src/utils/overlayPositionUtils.js b/src/utils/overlayPositionUtils.js index d8909ec2ac..de8cfba920 100644 --- a/src/utils/overlayPositionUtils.js +++ b/src/utils/overlayPositionUtils.js @@ -1,109 +1,2 @@ -import domUtils from './domUtils'; -const utils = { - - getContainerDimensions(containerNode) { - let size, scroll; - - if (containerNode.tagName === 'BODY') { - size = { - width: window.innerWidth, - height: window.innerHeight - }; - scroll = - domUtils.ownerDocument(containerNode).documentElement.scrollTop || - containerNode.scrollTop; - } else { - size = domUtils.getSize(containerNode); - scroll = containerNode.scrollTop; - } - - return {...size, scroll}; - }, - - getPosition(target, container) { - const offset = container.tagName === 'BODY' ? - domUtils.getOffset(target) : domUtils.getPosition(target, container); - const size = domUtils.getSize(target); - return {...offset, ...size}; - }, - - calcOverlayPosition(placement, overlayNode, target, container, padding) { - const childOffset = utils.getPosition(target, container); - - const {height: overlayHeight, width: overlayWidth} = domUtils.getSize(overlayNode); - - let positionLeft, positionTop, arrowOffsetLeft, arrowOffsetTop; - - if (placement === 'left' || placement === 'right') { - positionTop = childOffset.top + (childOffset.height - overlayHeight) / 2; - - if (placement === 'left') { - positionLeft = childOffset.left - overlayWidth; - } else { - positionLeft = childOffset.left + childOffset.width; - } - - const topDelta = getTopDelta(positionTop, overlayHeight, container, padding); - - positionTop += topDelta; - arrowOffsetTop = 50 * (1 - 2 * topDelta / overlayHeight) + '%'; - arrowOffsetLeft = null; - - } else if (placement === 'top' || placement === 'bottom') { - positionLeft = childOffset.left + (childOffset.width - overlayWidth) / 2; - - if (placement === 'top') { - positionTop = childOffset.top - overlayHeight; - } else { - positionTop = childOffset.top + childOffset.height; - } - - const leftDelta = getLeftDelta(positionLeft, overlayWidth, container, padding); - positionLeft += leftDelta; - arrowOffsetLeft = 50 * (1 - 2 * leftDelta / overlayWidth) + '%'; - arrowOffsetTop = null; - } else { - throw new Error( - `calcOverlayPosition(): No such placement of "${placement }" found.` - ); - } - - return { positionLeft, positionTop, arrowOffsetLeft, arrowOffsetTop }; - } -}; - - -function getTopDelta(top, overlayHeight, container, padding) { - const containerDimensions = utils.getContainerDimensions(container); - const containerScroll = containerDimensions.scroll; - const containerHeight = containerDimensions.height; - - const topEdgeOffset = top - padding - containerScroll; - const bottomEdgeOffset = top + padding - containerScroll + overlayHeight; - - if (topEdgeOffset < 0) { - return -topEdgeOffset; - } else if (bottomEdgeOffset > containerHeight) { - return containerHeight - bottomEdgeOffset; - } else { - return 0; - } -} - -function getLeftDelta(left, overlayWidth, container, padding) { - const containerDimensions = utils.getContainerDimensions(container); - const containerWidth = containerDimensions.width; - - const leftEdgeOffset = left - padding; - const rightEdgeOffset = left + padding + overlayWidth; - - if (leftEdgeOffset < 0) { - return -leftEdgeOffset; - } else if (rightEdgeOffset > containerWidth) { - return containerWidth - rightEdgeOffset; - } else { - return 0; - } -} -export default utils; +export * from 'react-overlays/lib/utils/overlayPositionUtils'; diff --git a/test/OverlayTriggerSpec.js b/test/OverlayTriggerSpec.js index 78b63502b4..cb48067872 100644 --- a/test/OverlayTriggerSpec.js +++ b/test/OverlayTriggerSpec.js @@ -82,7 +82,6 @@ describe('OverlayTrigger', function() { ); overlayTrigger = React.findDOMNode(instance); - ReactTestUtils.Simulate.click(overlayTrigger); }); diff --git a/test/PortalSpec.js b/test/PortalSpec.js deleted file mode 100644 index 66a1b49fc0..0000000000 --- a/test/PortalSpec.js +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; -import Portal from '../src/Portal'; - -describe('Portal', function () { - let instance; - - let Overlay = React.createClass({ - render() { - return ( -
- {this.props.overlay} -
- ); - }, - getOverlayDOMNode(){ - return this.refs.p.getOverlayDOMNode(); - } - }); - - afterEach(function() { - if (instance && ReactTestUtils.isCompositeComponent(instance) && instance.isMounted()) { - React.unmountComponentAtNode(React.findDOMNode(instance)); - } - }); - - it('Should render overlay into container (DOMNode)', function() { - let container = document.createElement('div'); - - instance = ReactTestUtils.renderIntoDocument( - } /> - ); - - assert.equal(container.querySelectorAll('#test1').length, 1); - }); - - it('Should render overlay into container (ReactComponent)', function() { - let Container = React.createClass({ - render() { - return } />; - } - }); - - instance = ReactTestUtils.renderIntoDocument( - - ); - - assert.equal(React.findDOMNode(instance).querySelectorAll('#test1').length, 1); - }); - - it('Should not render a null overlay', function() { - let Container = React.createClass({ - render() { - return ; - } - }); - - instance = ReactTestUtils.renderIntoDocument( - - ); - - assert.equal(instance.refs.overlay.getOverlayDOMNode(), null); - }); - - it('Should render only an overlay', function() { - let OnlyOverlay = React.createClass({ - render() { - return {this.props.overlay}; - } - }); - - let overlayInstance = ReactTestUtils.renderIntoDocument( - } /> - ); - - assert.equal(overlayInstance.refs.p.getOverlayDOMNode().nodeName, 'DIV'); - }); -}); diff --git a/test/PositionSpec.js b/test/PositionSpec.js deleted file mode 100644 index ea1405fd14..0000000000 --- a/test/PositionSpec.js +++ /dev/null @@ -1,218 +0,0 @@ -import pick from 'lodash/object/pick'; -import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; - -import Position from '../src/Position'; -import overlayPositionUtils from '../src/utils/overlayPositionUtils'; - -import {render} from './helpers'; - -describe('Position', function () { - it('Should output a child', function () { - let instance = ReactTestUtils.renderIntoDocument( - - Text - - ); - assert.equal(React.findDOMNode(instance).nodeName, 'SPAN'); - }); - - it('Should warn about several children', function () { - expect(() => { - ReactTestUtils.renderIntoDocument( - - Text - Another Text - - ); - }).to.throw(Error, /onlyChild must be passed a children with exactly one child/); - }); - - describe('position recalculation', function () { - beforeEach(function () { - sinon.spy(overlayPositionUtils, 'calcOverlayPosition'); - sinon.spy(Position.prototype, 'componentWillReceiveProps'); - }); - - afterEach(function () { - overlayPositionUtils.calcOverlayPosition.restore(); - Position.prototype.componentWillReceiveProps.restore(); - }); - - it('Should only recalculate when target changes', function () { - class TargetChanger extends React.Component { - constructor(props) { - super(props); - - this.state = { - target: 'foo', - fakeProp: 0 - }; - } - - render() { - return ( -
-
-
- - this.refs[this.state.target]} - fakeProp={this.state.fakeProp} - > -
- -
- ); - } - } - - const instance = ReactTestUtils.renderIntoDocument(); - - // Position calculates initial position. - expect(Position.prototype.componentWillReceiveProps) - .to.have.not.been.called; - expect(overlayPositionUtils.calcOverlayPosition) - .to.have.been.calledOnce; - - instance.setState({target: 'bar'}); - - // Position receives new props and recalculates position. - expect(Position.prototype.componentWillReceiveProps) - .to.have.been.calledOnce; - expect(overlayPositionUtils.calcOverlayPosition) - .to.have.been.calledTwice; - - instance.setState({fakeProp: 1}); - - // Position receives new props but should not recalculate position. - expect(Position.prototype.componentWillReceiveProps) - .to.have.been.calledTwice; - expect(overlayPositionUtils.calcOverlayPosition) - .to.have.been.calledTwice; - }); - }); - - describe('position calculation', function () { - let mountPoint; - - beforeEach(function () { - mountPoint = document.createElement('div'); - document.body.appendChild(mountPoint); - }); - - afterEach(function () { - React.unmountComponentAtNode(mountPoint); - document.body.removeChild(mountPoint); - }); - - function checkPosition(placement, targetPosition, expected) { - class FakeOverlay extends React.Component { - render() { - return ( -
- ); - } - } - - class FakeContainer extends React.Component { - render() { - return ( -
-
- - React.findDOMNode(this.refs.target)} - container={this} - containerPadding={50} - placement={placement} - > - - -
- ); - } - } - - const expectedPosition = { - positionLeft: expected[0], - positionTop: expected[1], - arrowOffsetLeft: expected[2], - arrowOffsetTop: expected[3] - }; - - it('Should calculate the correct position', function() { - const instance = render(, mountPoint); - - const calculatedPosition = pick( - instance.refs.overlay.props, Object.keys(expectedPosition) - ); - expect(calculatedPosition).to.eql(expectedPosition); - }); - } - - [ - { - placement: 'left', - noOffset: [50, 200, null, '50%'], - offsetBefore: [-200, 50, null, '0%'], - offsetAfter: [300, 350, null, '100%'] - }, - { - placement: 'top', - noOffset: [200, 50, '50%', null], - offsetBefore: [50, -200, '0%', null], - offsetAfter: [350, 300, '100%', null] - }, - { - placement: 'bottom', - noOffset: [200, 350, '50%', null], - offsetBefore: [50, 100, '0%', null], - offsetAfter: [350, 600, '100%', null] - }, - { - placement: 'right', - noOffset: [350, 200, null, '50%'], - offsetBefore: [100, 50, null, '0%'], - offsetAfter: [600, 350, null, '100%'] - } - ].forEach(function(testCase) { - const placement = testCase.placement; - - describe(`placement = ${placement}`, function() { - describe('no viewport offset', function() { - checkPosition( - placement, {left: 250, top: 250}, testCase.noOffset - ); - }); - - describe('viewport offset before', function() { - checkPosition( - placement, {left: 0, top: 0}, testCase.offsetBefore - ); - }); - - describe('viewport offset after', function() { - checkPosition( - placement, {left: 500, top: 500}, testCase.offsetAfter - ); - }); - }); - }); - }); - - // ToDo: add remaining tests -}); diff --git a/test/TransitionSpec.js b/test/TransitionSpec.js deleted file mode 100644 index 1592e646f7..0000000000 --- a/test/TransitionSpec.js +++ /dev/null @@ -1,279 +0,0 @@ -import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; -import { render } from './helpers'; -import Transition, {UNMOUNTED, EXITED, ENTERING, ENTERED, EXITING} from - '../src/Transition'; - -describe('Transition', function () { - it('should not transition on mount', function(){ - let instance = render( - { throw new Error('should not Enter'); }}> -
-
- ); - - expect(instance.state.status).to.equal(ENTERED); - }); - - it('should transition on mount with transitionAppear', done =>{ - let instance = ReactTestUtils.renderIntoDocument( - done()} - > -
-
- ); - - expect(instance.state.status).to.equal(EXITED); - }); - - describe('entering', ()=> { - let instance; - - beforeEach(function(){ - instance = render( - -
- - ); - }); - - it('should fire callbacks', done => { - let onEnter = sinon.spy(); - let onEntering = sinon.spy(); - - expect(instance.state.status).to.equal(EXITED); - - instance = instance.renderWithProps({ - in: true, - - onEnter, - - onEntering, - - onEntered(){ - expect(onEnter.calledOnce).to.be.ok; - expect(onEntering.calledOnce).to.be.ok; - expect(onEnter.calledBefore(onEntering)).to.be.ok; - done(); - } - }); - }); - - it('should move to each transition state', done => { - let count = 0; - - expect(instance.state.status).to.equal(EXITED); - - instance = instance.renderWithProps({ - in: true, - - onEnter(){ - count++; - expect(instance.state.status).to.equal(EXITED); - }, - - onEntering(){ - count++; - expect(instance.state.status).to.equal(ENTERING); - }, - - onEntered(){ - expect(instance.state.status).to.equal(ENTERED); - expect(count).to.equal(2); - done(); - } - }); - }); - - it('should apply classes at each transition state', done => { - let count = 0; - - expect(instance.state.status).to.equal(EXITED); - - instance = instance.renderWithProps({ - in: true, - - onEnter(node){ - count++; - expect(node.className).to.equal(''); - }, - - onEntering(node){ - count++; - expect(node.className).to.equal('test-entering'); - }, - - onEntered(node){ - expect(node.className).to.equal('test-enter'); - expect(count).to.equal(2); - done(); - } - }); - }); - }); - - describe('exiting', ()=> { - let instance; - - beforeEach(function(){ - instance = render( - -
- - ); - }); - - it('should fire callbacks', done => { - let onExit = sinon.spy(); - let onExiting = sinon.spy(); - - expect(instance.state.status).to.equal(ENTERED); - - instance = instance.renderWithProps({ - in: false, - - onExit, - - onExiting, - - onExited(){ - expect(onExit.calledOnce).to.be.ok; - expect(onExiting.calledOnce).to.be.ok; - expect(onExit.calledBefore(onExiting)).to.be.ok; - done(); - } - }); - }); - - it('should move to each transition state', done => { - let count = 0; - - expect(instance.state.status).to.equal(ENTERED); - - instance = instance.renderWithProps({ - in: false, - - onExit(){ - count++; - expect(instance.state.status).to.equal(ENTERED); - }, - - onExiting(){ - count++; - expect(instance.state.status).to.equal(EXITING); - }, - - onExited(){ - expect(instance.state.status).to.equal(EXITED); - expect(count).to.equal(2); - done(); - } - }); - }); - - it('should apply classes at each transition state', done => { - let count = 0; - - expect(instance.state.status).to.equal(ENTERED); - - instance = instance.renderWithProps({ - in: false, - - onExit(node){ - count++; - expect(node.className).to.equal(''); - }, - - onExiting(node){ - count++; - expect(node.className).to.equal('test-exiting'); - }, - - onExited(node){ - expect(node.className).to.equal('test-exit'); - expect(count).to.equal(2); - done(); - } - }); - }); - }); - - describe('unmountOnExit', () => { - class UnmountTransition extends React.Component { - constructor(props) { - super(props); - - this.state = {in: props.initialIn}; - } - - render() { - return ( - -
- - ); - } - - getStatus() { - return this.refs.transition.state.status; - } - } - - it('should mount when entering', done => { - const instance = render( - { - expect(instance.getStatus()).to.equal(EXITED); - expect(React.findDOMNode(instance)).to.exist; - - done(); - }} - /> - ); - - expect(instance.getStatus()).to.equal(UNMOUNTED); - expect(React.findDOMNode(instance)).to.not.exist; - - instance.setState({in: true}); - }); - - it('should unmount after exiting', done => { - const instance = render( - { - expect(instance.getStatus()).to.equal(UNMOUNTED); - expect(React.findDOMNode(instance)).to.not.exist; - - done(); - }} - /> - ); - - expect(instance.getStatus()).to.equal(ENTERED); - expect(React.findDOMNode(instance)).to.exist; - - instance.setState({in: false}); - }); - }); -});