diff --git a/src/addons/TransitionablePortal/TransitionablePortal.js b/src/addons/TransitionablePortal/TransitionablePortal.js index ba94069e6f..6d455471b8 100644 --- a/src/addons/TransitionablePortal/TransitionablePortal.js +++ b/src/addons/TransitionablePortal/TransitionablePortal.js @@ -4,6 +4,7 @@ import React, { Component } from 'react' import Portal from '../Portal' import Transition from '../../modules/Transition' +import { TRANSITION_STATUS_ENTERING } from '../../modules/Transition/utils/computeStatuses' import { getUnhandledProps, makeDebugger } from '../../lib' const debug = makeDebugger('transitionable_portal') @@ -117,7 +118,7 @@ export default class TransitionablePortal extends Component { debug('handleTransitionStart()') const { portalOpen } = this.state const { status } = data - const transitionVisible = status === Transition.ENTERING + const transitionVisible = status === TRANSITION_STATUS_ENTERING _.invoke(this.props, 'onStart', null, { ...data, portalOpen, transitionVisible }) @@ -134,7 +135,7 @@ export default class TransitionablePortal extends Component { render() { debug('render()', this.state) - // console.log('render', this.state) + const { children, transition } = this.props const { portalOpen, transitionVisible } = this.state diff --git a/src/modules/Transition/Transition.js b/src/modules/Transition/Transition.js index cfa7d11d31..792a366dd5 100644 --- a/src/modules/Transition/Transition.js +++ b/src/modules/Transition/Transition.js @@ -5,12 +5,25 @@ import { cloneElement, Component } from 'react' import { makeDebugger, normalizeTransitionDuration, SUI, useKeyOnly } from '../../lib' import TransitionGroup from './TransitionGroup' +import { + computeStatuses, + TRANSITION_STATUS_ENTERED, + TRANSITION_STATUS_ENTERING, + TRANSITION_STATUS_EXITED, + TRANSITION_STATUS_EXITING, + TRANSITION_STATUS_INITIAL, + TRANSITION_STATUS_UNMOUNTED, +} from './utils/computeStatuses' const debug = makeDebugger('transition') -const TRANSITION_TYPE = { - ENTERING: 'show', - EXITING: 'hide', +const TRANSITION_CALLBACK_TYPE = { + [TRANSITION_STATUS_ENTERED]: 'show', + [TRANSITION_STATUS_EXITED]: 'hide', +} +const TRANSITION_STYLE_TYPE = { + [TRANSITION_STATUS_ENTERING]: 'show', + [TRANSITION_STATUS_EXITING]: 'hide', } /** @@ -94,51 +107,50 @@ export default class Transition extends Component { unmountOnHide: false, } - static ENTERED = 'ENTERED' - static ENTERING = 'ENTERING' - static EXITED = 'EXITED' - static EXITING = 'EXITING' - static UNMOUNTED = 'UNMOUNTED' + /** @deprecated Static properties will be removed in v1 */ + static INITIAL = TRANSITION_STATUS_INITIAL + static ENTERED = TRANSITION_STATUS_ENTERED + static ENTERING = TRANSITION_STATUS_ENTERING + static EXITED = TRANSITION_STATUS_EXITED + static EXITING = TRANSITION_STATUS_EXITING + static UNMOUNTED = TRANSITION_STATUS_UNMOUNTED static Group = TransitionGroup - constructor(...args) { - super(...args) - - const { initial: status, next } = this.computeInitialStatuses() - this.nextStatus = next - this.state = { status } + state = { + status: TRANSITION_STATUS_INITIAL, } // ---------------------------------------- // Lifecycle // ---------------------------------------- - componentDidMount() { - debug('componentDidMount()') - - this.updateStatus() - } + static getDerivedStateFromProps(props, state) { + const derivedState = computeStatuses({ + mountOnShow: props.mountOnShow, + status: state.status, + transitionOnMount: props.transitionOnMount, + visible: props.visible, + unmountOnHide: props.unmountOnHide, + }) - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - debug('componentWillReceiveProps()') + debug('getDerivedStateFromProps()', props, state, derivedState) - const { current: status, next } = this.computeStatuses(nextProps) + return derivedState + } - this.nextStatus = next - if (status) this.setState({ status }) + componentDidMount() { + debug('componentDidMount()') + this.updateStatus({}) } - componentDidUpdate() { + componentDidUpdate(prevProps, prevState) { debug('componentDidUpdate()') - - this.updateStatus() + this.updateStatus(prevState) } componentWillUnmount() { debug('componentWillUnmount()') - clearTimeout(this.timeoutId) } @@ -146,44 +158,33 @@ export default class Transition extends Component { // Callback handling // ---------------------------------------- - handleStart = () => { + handleStart = (nextStatus) => { const { duration } = this.props - const status = this.nextStatus - this.nextStatus = null - this.setState({ status, animating: true }, () => { - const durationType = TRANSITION_TYPE[status] - const durationValue = normalizeTransitionDuration(duration, durationType) + const durationType = TRANSITION_CALLBACK_TYPE[nextStatus] + const durationValue = normalizeTransitionDuration(duration, durationType) - _.invoke(this.props, 'onStart', null, { ...this.props, status }) - this.timeoutId = setTimeout(this.handleComplete, durationValue) - }) + clearTimeout(this.timeoutId) + this.timeoutId = setTimeout( + () => this.setState((state) => ({ status: state.nextStatus })), + durationValue, + ) } - handleComplete = () => { - const { status: current } = this.state - - _.invoke(this.props, 'onComplete', null, { ...this.props, status: current }) - - if (this.nextStatus) { - this.handleStart() - return + updateStatus = (prevState) => { + if (this.state.status !== this.state.nextStatus && this.state.nextStatus) { + this.handleStart(this.state.nextStatus) } - const status = this.computeCompletedStatus() - const callback = current === Transition.ENTERING ? 'onShow' : 'onHide' - - this.setState({ status, animating: false }, () => { - _.invoke(this.props, callback, null, { ...this.props, status }) - }) - } + if (!prevState.animating && this.state.animating) { + _.invoke(this.props, 'onStart', null, { ...this.props, status: this.state.status }) + } - updateStatus = () => { - const { animating } = this.state + if (prevState.animating && !this.state.animating) { + const callback = this.state.status === TRANSITION_STATUS_ENTERED ? 'onShow' : 'onHide' - if (this.nextStatus) { - this.nextStatus = this.computeNextStatus() - if (!animating) this.handleStart() + _.invoke(this.props, 'onComplete', null, { ...this.props, status: this.state.status }) + _.invoke(this.props, callback, null, { ...this.props, status: this.state.status }) } } @@ -205,10 +206,10 @@ export default class Transition extends Component { animation, childClasses, useKeyOnly(animating, 'animating'), - useKeyOnly(status === Transition.ENTERING, 'in'), - useKeyOnly(status === Transition.EXITING, 'out'), - useKeyOnly(status === Transition.EXITED, 'hidden'), - useKeyOnly(status !== Transition.EXITED, 'visible'), + useKeyOnly(status === TRANSITION_STATUS_ENTERING, 'in'), + useKeyOnly(status === TRANSITION_STATUS_EXITING, 'out'), + useKeyOnly(status === TRANSITION_STATUS_EXITED, 'hidden'), + useKeyOnly(status !== TRANSITION_STATUS_EXITED, 'visible'), 'transition', ) } @@ -216,61 +217,12 @@ export default class Transition extends Component { return cx(animation, childClasses, useKeyOnly(animating, 'animating transition')) } - computeCompletedStatus = () => { - const { unmountOnHide } = this.props - const { status } = this.state - - if (status === Transition.ENTERING) return Transition.ENTERED - return unmountOnHide ? Transition.UNMOUNTED : Transition.EXITED - } - - computeInitialStatuses = () => { - const { visible, mountOnShow, transitionOnMount, unmountOnHide } = this.props - - if (visible) { - if (transitionOnMount) { - return { - initial: Transition.EXITED, - next: Transition.ENTERING, - } - } - return { initial: Transition.ENTERED } - } - - if (mountOnShow || unmountOnHide) return { initial: Transition.UNMOUNTED } - return { initial: Transition.EXITED } - } - - computeNextStatus = () => { - const { animating, status } = this.state - - if (animating) return status === Transition.ENTERING ? Transition.EXITING : Transition.ENTERING - return status === Transition.ENTERED ? Transition.EXITING : Transition.ENTERING - } - - computeStatuses = (props) => { - const { status } = this.state - const { visible } = props - - if (visible) { - return { - current: status === Transition.UNMOUNTED && Transition.EXITED, - next: - status !== Transition.ENTERING && status !== Transition.ENTERED && Transition.ENTERING, - } - } - - return { - next: (status === Transition.ENTERING || status === Transition.ENTERED) && Transition.EXITING, - } - } - computeStyle = () => { const { children, duration } = this.props const { status } = this.state const childStyle = _.get(children, 'props.style') - const type = TRANSITION_TYPE[status] + const type = TRANSITION_STYLE_TYPE[status] const animationDuration = type && `${normalizeTransitionDuration(duration, type)}ms` return { ...childStyle, animationDuration } @@ -281,14 +233,16 @@ export default class Transition extends Component { // ---------------------------------------- render() { - debug('render()') - debug('props', this.props) - debug('state', this.state) + debug('render(): props', this.props) + debug('render(): state', this.state) const { children } = this.props const { status } = this.state - if (status === Transition.UNMOUNTED) return null + if (status === TRANSITION_STATUS_UNMOUNTED) { + return null + } + return cloneElement(children, { className: this.computeClasses(), style: this.computeStyle(), diff --git a/src/modules/Transition/utils/computeStatuses.js b/src/modules/Transition/utils/computeStatuses.js new file mode 100644 index 0000000000..0444f89dc8 --- /dev/null +++ b/src/modules/Transition/utils/computeStatuses.js @@ -0,0 +1,115 @@ +export const TRANSITION_STATUS_INITIAL = 'INITIAL' +export const TRANSITION_STATUS_ENTERED = 'ENTERED' +export const TRANSITION_STATUS_ENTERING = 'ENTERING' +export const TRANSITION_STATUS_EXITED = 'EXITED' +export const TRANSITION_STATUS_EXITING = 'EXITING' +export const TRANSITION_STATUS_UNMOUNTED = 'UNMOUNTED' + +/** + * @param {Object} [options] + * @param {String} [options.status] + * @param {Boolean} [options.mountOnShow] + * @param {Boolean} [options.transitionOnMount] + * @param {Boolean} [options.visible] + * @param {Boolean} [options.unmountOnHide] + */ +export function computeStatuses(options) { + const { mountOnShow, status, transitionOnMount, visible, unmountOnHide } = options + + if (visible) { + if (status === TRANSITION_STATUS_INITIAL) { + if (transitionOnMount) { + return { + animating: true, + status: TRANSITION_STATUS_ENTERING, + nextStatus: TRANSITION_STATUS_ENTERED, + } + } + + return { + animating: false, + status: TRANSITION_STATUS_ENTERED, + nextStatus: undefined, + } + } + + if (status === TRANSITION_STATUS_UNMOUNTED) { + return { + animating: true, + status: TRANSITION_STATUS_ENTERING, + nextStatus: TRANSITION_STATUS_ENTERED, + } + } + + if (status === TRANSITION_STATUS_EXITED || status === TRANSITION_STATUS_EXITING) { + return { + animating: true, + status: TRANSITION_STATUS_ENTERING, + nextStatus: TRANSITION_STATUS_ENTERED, + } + } + + if (status === TRANSITION_STATUS_ENTERING) { + return {} + } + + /* istanbul ignore else */ + if (status === TRANSITION_STATUS_ENTERED) { + return { + animating: false, + status: TRANSITION_STATUS_ENTERED, + nextStatus: undefined, + } + } + } + + if (status === TRANSITION_STATUS_INITIAL) { + if (mountOnShow || unmountOnHide) { + return { + animating: false, + status: TRANSITION_STATUS_UNMOUNTED, + nextStatus: undefined, + } + } + + return { + animating: false, + status: TRANSITION_STATUS_EXITED, + nextStatus: undefined, + } + } + + if (status === TRANSITION_STATUS_ENTERED || status === TRANSITION_STATUS_ENTERING) { + return { + animating: true, + status: TRANSITION_STATUS_EXITING, + nextStatus: unmountOnHide ? TRANSITION_STATUS_UNMOUNTED : TRANSITION_STATUS_EXITED, + } + } + + if (status === TRANSITION_STATUS_EXITING) { + return {} + } + + if (status === TRANSITION_STATUS_EXITED) { + return { + animating: false, + status: TRANSITION_STATUS_EXITED, + nextStatus: undefined, + } + } + + /* istanbul ignore else */ + if (status === TRANSITION_STATUS_UNMOUNTED) { + return { + animating: false, + status: TRANSITION_STATUS_UNMOUNTED, + nextStatus: undefined, + } + } + + /* istanbul ignore next */ + throw new Error( + `Transition:computeStatuses(): an unexpected status transition: { visible: ${visible}, status: ${status} }`, + ) +} diff --git a/test/specs/modules/Transition/Transition-test.js b/test/specs/modules/Transition/Transition-test.js index 471e7ceaad..ac3b932a6e 100644 --- a/test/specs/modules/Transition/Transition-test.js +++ b/test/specs/modules/Transition/Transition-test.js @@ -3,6 +3,13 @@ import React from 'react' import { SUI } from 'src/lib' import Transition from 'src/modules/Transition/Transition' import TransitionGroup from 'src/modules/Transition/TransitionGroup' +import { + TRANSITION_STATUS_ENTERED, + TRANSITION_STATUS_ENTERING, + TRANSITION_STATUS_EXITED, + TRANSITION_STATUS_EXITING, + TRANSITION_STATUS_UNMOUNTED, +} from 'src/modules/Transition/utils/computeStatuses' import * as common from 'test/specs/commonTests' import { sandbox } from 'test/utils' @@ -26,58 +33,61 @@ describe('Transition', () => { describe('animation', () => { SUI.DIRECTIONAL_TRANSITIONS.forEach((animation) => { it(`directional ${animation}`, () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) - wrapper.should.have.className(animation) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + animation.split(' ').forEach((className) => wrapper.should.have.className(className)) wrapper.should.have.className('in') - wrapper.setState({ status: Transition.EXITING }) - wrapper.should.have.className(animation) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + animation.split(' ').forEach((className) => wrapper.should.have.className(className)) wrapper.should.have.className('out') }) }) SUI.STATIC_TRANSITIONS.forEach((animation) => { it(`static ${animation}`, () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.className(animation) wrapper.should.not.have.className('in') - wrapper.setState({ status: Transition.EXITING }) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) wrapper.should.have.className(animation) wrapper.should.not.have.className('out') }) }) it('supports custom animations', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.className('jump') - wrapper.setState({ status: Transition.EXITING }) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) wrapper.should.have.className('jump') }) }) describe('className', () => { it("passes element's className", () => { - wrapperShallow( + wrapperMount(

, @@ -88,7 +98,7 @@ describe('Transition', () => { }) it('adds classes when ENTERED', () => { - wrapperShallow( + wrapperMount(

, @@ -99,12 +109,11 @@ describe('Transition', () => { }) it('adds classes when ENTERING', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ animating: true, status: Transition.ENTERING }) wrapper.should.have.className('animating') wrapper.should.have.className('visible') @@ -112,24 +121,23 @@ describe('Transition', () => { }) it('adds classes when EXITED', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.EXITED }) wrapper.should.have.className('hidden') wrapper.should.have.className('transition') }) it('adds classes when EXITING', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.setState({ animating: true, status: Transition.EXITING }) + wrapper.setProps({ visible: false }) wrapper.should.have.className('animating') wrapper.should.have.className('visible') @@ -139,30 +147,32 @@ describe('Transition', () => { describe('directional', () => { it('adds classes when is "true"', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.className('in') - wrapper.setState({ status: Transition.EXITING }) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) wrapper.should.have.className('out') }) it('do not add classes when is "false"', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.not.className('in') - wrapper.setState({ status: Transition.EXITING }) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) wrapper.should.have.not.className('out') }) }) @@ -173,180 +183,186 @@ describe('Transition', () => {

, - ).should.have.descendants('p.foo') + ) + wrapper.should.have.descendants('p.foo') }) it('returns null when UNMOUNTED', () => { wrapperShallow( - +

, ) - - wrapper.setState({ status: Transition.UNMOUNTED }) wrapper.should.be.blank() }) }) describe('constructor', () => { it('has default statuses', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.should.have.state('status', Transition.ENTERED) - wrapper.instance().should.include({ nextStatus: undefined }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERED) + wrapper.should.have.not.state('nextStatus') }) it('sets statuses when `visible` is false', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.should.have.state('status', Transition.UNMOUNTED) - wrapper.instance().should.include({ nextStatus: undefined }) + wrapper.should.have.state('status', TRANSITION_STATUS_UNMOUNTED) + wrapper.should.have.not.state('nextStatus') }) it('sets statuses when mount is disabled', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.should.have.state('status', Transition.EXITED) - wrapper.instance().should.include({ nextStatus: undefined }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITED) + wrapper.should.have.not.state('nextStatus') }) }) describe('duration', () => { it('does not apply to style when ENTERED', () => { - wrapperShallow( + wrapperMount(

, - ).should.not.have.style('animation-duration') + ) + + wrapper.should.not.have.style('animation-duration') }) it('applies default value to style when ENTERING', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.style('animation-duration', '500ms') }) it('applies numeric value to style when ENTERING', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.style('animation-duration', '1000ms') }) it('applies object value to style when ENTERING', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.style('animation-duration', '2000ms') }) it('does not apply to style when EXITED', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.EXITED }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITED) wrapper.should.not.have.style('animation-duration') }) it('applies default value to style when EXITING', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.setState({ animating: true, status: Transition.EXITING }) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) wrapper.should.have.style('animation-duration') }) it('applies numeric value to style when EXITING', () => { wrapperShallow( - +

, ) - wrapper.setState({ status: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) wrapper.should.have.style('animation-duration', '1000ms') }) it('applies object value to style when EXITING', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.setState({ status: Transition.EXITING }) + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) wrapper.should.have.style('animation-duration', '1000ms') }) }) describe('visible', () => { it('updates status when set to false while ENTERING', () => { - wrapperShallow( - + wrapperMount( +

, ) - wrapper.setState({ status: Transition.ENTERING }) - wrapper.setProps({ visible: false }) - wrapper.instance().should.include({ nextStatus: Transition.EXITING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_EXITED) }) it('updates status when set to false while ENTERED', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.should.have.state('status', Transition.ENTERED) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERED) wrapper.setProps({ visible: false }) - wrapper.instance().should.include({ nextStatus: Transition.EXITING }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_EXITED) }) it('updates status when set to true while UNMOUNTED', () => { - wrapperShallow( + wrapperMount(

, ) - wrapper.should.have.state('status', Transition.UNMOUNTED) - wrapper.instance().mounted = true + wrapper.should.have.state('status', TRANSITION_STATUS_UNMOUNTED) + wrapper.setProps({ visible: true }) - wrapper.should.have.state('status', Transition.EXITED) - wrapper.instance().should.include({ nextStatus: Transition.ENTERING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_ENTERED) }) it('updates next status when set to true while performs an ENTERING transition', (done) => { @@ -355,23 +371,30 @@ describe('Transition', () => {

, ) - wrapper.setProps({ visible: false }) - wrapper.should.have.state('status', Transition.ENTERING) - wrapper.instance().should.include({ nextStatus: Transition.EXITING }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + + wrapper.setProps({ visible: false }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_EXITED) }) it('updates next status when set to true while performs an EXITING transition', (done) => { wrapperMount( - +

, ) + + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERED) + wrapper.setProps({ visible: false }) - wrapper.setProps({ visible: true }) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_EXITED) - wrapper.should.have.state('status', Transition.EXITING) - wrapper.instance().should.include({ nextStatus: Transition.ENTERING }) + wrapper.setProps({ visible: true }) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_ENTERED) }) }) @@ -384,7 +407,7 @@ describe('Transition', () => { onComplete.should.have.been.calledOnce() onComplete.should.have.been.calledWithMatch(null, { duration: 0, - status: Transition.ENTERING, + status: TRANSITION_STATUS_ENTERED, }) done() @@ -405,7 +428,10 @@ describe('Transition', () => { onHide(...args) onHide.should.have.been.calledOnce() - onHide.should.have.been.calledWithMatch(null, { duration: 0, status: Transition.EXITED }) + onHide.should.have.been.calledWithMatch(null, { + duration: 0, + status: TRANSITION_STATUS_EXITED, + }) done() } @@ -427,18 +453,41 @@ describe('Transition', () => { ) wrapper.setProps({ visible: false }) - wrapper.should.have.state('status', Transition.EXITING) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) setTimeout(() => { - wrapper.should.have.state('status', Transition.EXITING) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) }, 100) setTimeout(() => { onHide.should.have.been.calledOnce() - wrapper.should.have.state('status', Transition.EXITED) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITED) done() }, 200) }) + + it('will be called once even during rerender', (done) => { + const onStart = sandbox.spy() + + wrapperMount( + +

+ , + ) + + wrapper.setProps({ visible: false }) + + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_EXITED) + + wrapper.setProps({}) + + wrapper.should.have.state('status', TRANSITION_STATUS_EXITING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_EXITED) + + onStart.should.have.been.calledOnce() + done() + }) }) describe('onShow', () => { @@ -448,7 +497,10 @@ describe('Transition', () => { onShow(...args) onShow.should.have.been.calledOnce() - onShow.should.have.been.calledWithMatch(null, { duration: 0, status: Transition.ENTERED }) + onShow.should.have.been.calledWithMatch(null, { + duration: 0, + status: TRANSITION_STATUS_ENTERED, + }) done() } @@ -468,15 +520,14 @@ describe('Transition', () => { , ) - wrapper.should.have.state('status', Transition.ENTERING) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) setTimeout(() => { - wrapper.should.have.state('status', Transition.ENTERING) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) }, 100) setTimeout(() => { onShow.should.have.been.calledOnce() - wrapper.should.have.state('status', Transition.ENTERED) - + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERED) done() }, 200) }) @@ -489,7 +540,10 @@ describe('Transition', () => { onStart(...args) onStart.should.have.been.calledOnce() - onStart.should.have.been.calledWithMatch(null, { duration: 0, status: Transition.ENTERING }) + onStart.should.have.been.calledWithMatch(null, { + duration: 0, + status: TRANSITION_STATUS_ENTERING, + }) done() } @@ -500,6 +554,27 @@ describe('Transition', () => { , ) }) + + it('will be called once even during rerender', (done) => { + const onStart = sandbox.spy() + + wrapperMount( + +

+ , + ) + + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_ENTERED) + + wrapper.setProps({}) + + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_ENTERED) + + onStart.should.have.been.calledOnce() + done() + }) }) describe('style', () => { @@ -523,23 +598,15 @@ describe('Transition', () => { , ) - wrapper.should.have.state('status', Transition.EXITED) - wrapper.instance().should.include({ nextStatus: Transition.ENTERING }) - }) - - it('updates status after mount when is true', () => { - wrapperMount( - -

- , - ).should.have.state('status', Transition.ENTERING) + wrapper.should.have.state('status', TRANSITION_STATUS_ENTERING) + wrapper.should.have.state('nextStatus', TRANSITION_STATUS_ENTERED) }) }) describe('unmountOnHide', () => { it('unmounts child when true', (done) => { const onHide = () => { - wrapper.should.have.state('status', Transition.UNMOUNTED) + wrapper.should.have.state('status', TRANSITION_STATUS_UNMOUNTED) done() } @@ -553,7 +620,7 @@ describe('Transition', () => { it('lefts mounted when false', (done) => { const onHide = () => { - wrapper.should.have.state('status', Transition.EXITED) + wrapper.should.have.state('status', TRANSITION_STATUS_EXITED) done() }