diff --git a/packages/animation/src/Energy/Energy.animation.test.js b/packages/animation/src/Energy/Energy.animation.test.js index 9d3f0976..d9955d68 100644 --- a/packages/animation/src/Energy/Energy.animation.test.js +++ b/packages/animation/src/Energy/Energy.animation.test.js @@ -32,34 +32,34 @@ test('Should delay energy flow from "exited" the "duration.delay" time', () => { expect(energy.getFlow().entering).toBe(true); }); -test('Should get notified with "onActivate" when activation changes', () => { +test('Should get notified with "onActivation" when activation changes', () => { let example; - const onActivate = jest.fn(); + const onActivation = jest.fn(); class Example extends React.PureComponent { state = { activate: true } render () { const { activate } = this.state; - return ; + return ; } } render( (example = r)} />); setTimeout(() => example.setState({ activate: false }), 200); - expect(onActivate).not.toHaveBeenCalled(); + expect(onActivation).not.toHaveBeenCalled(); jest.advanceTimersByTime(10); - expect(onActivate).toHaveBeenCalledTimes(1); - expect(onActivate).toHaveBeenCalledWith(true); + expect(onActivation).toHaveBeenCalledTimes(1); + expect(onActivation).toHaveBeenCalledWith(true); jest.advanceTimersByTime(180); // 190ms - expect(onActivate).toHaveBeenCalledTimes(1); + expect(onActivation).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(20); // 210ms - expect(onActivate).toHaveBeenCalledTimes(2); - expect(onActivate).toHaveBeenCalledWith(false); + expect(onActivation).toHaveBeenCalledTimes(2); + expect(onActivation).toHaveBeenCalledWith(false); jest.advanceTimersByTime(100); // 310ms - expect(onActivate).toHaveBeenCalledTimes(2); + expect(onActivation).toHaveBeenCalledTimes(2); }); describe('root', () => { diff --git a/packages/animation/src/Energy/Energy.js b/packages/animation/src/Energy/Energy.js index f824421e..db7d4971 100644 --- a/packages/animation/src/Energy/Energy.js +++ b/packages/animation/src/Energy/Energy.js @@ -1,3 +1,5 @@ +/* eslint-disable react/no-unused-prop-types */ + import React from 'react'; import PropTypes from 'prop-types'; import { useAnimation } from '../useAnimation'; @@ -6,18 +8,14 @@ import { useEnergy } from '../useEnergy'; import { makeIsAnimate } from '../makeIsAnimate'; import { makeIsRoot } from '../makeIsRoot'; import { makeIsActivated } from '../makeIsActivated'; +import { makeIsOutsourced } from '../makeIsOutsourced'; import { makeGetEnergyInterface } from '../makeGetEnergyInterface'; import { makeDurationManager } from '../makeDurationManager'; import { makeFlowManager } from '../makeFlowManager'; import { makeScheduler } from '../makeScheduler'; - -const ENTERING = 'entering'; -const ENTERED = 'entered'; -const EXITING = 'exiting'; -const EXITED = 'exited'; +import { ENERGY_TYPE, ENTERING, ENTERED, EXITING, EXITED } from '../constants'; class Component extends React.PureComponent { - /* eslint-disable react/no-unused-prop-types */ static propTypes = { animate: PropTypes.bool, root: PropTypes.bool, @@ -31,12 +29,12 @@ class Component extends React.PureComponent { }) ]), merge: PropTypes.bool, - onActivate: PropTypes.func, + imperative: PropTypes.bool, + onActivation: PropTypes.func, animationContext: PropTypes.any, parentEnergyContext: PropTypes.any, children: PropTypes.any }; - /* eslint-enable react/no-unused-prop-types */ constructor () { super(...arguments); @@ -44,11 +42,13 @@ class Component extends React.PureComponent { this.isAnimate = makeIsAnimate(this); this.isRoot = makeIsRoot(this); this.isActivated = makeIsActivated(this); + this.isOutsourced = makeIsOutsourced(this); this.getEnergyInterface = makeGetEnergyInterface(this); this.durationManager = makeDurationManager(this); this.flowManager = makeFlowManager(this); this.scheduler = makeScheduler(); + this.type = ENERGY_TYPE; this.state = this.getInitialState(); this._flowHasEntered = false; this._flowHasExited = false; @@ -70,7 +70,8 @@ class Component extends React.PureComponent { } componentWillUnmount () { - this.scheduler.stop(); + this.flowManager.checkUnmount(); + this.scheduler.stopAll(); } render () { @@ -101,32 +102,41 @@ class Component extends React.PureComponent { return this.state.energyInterface.flow; } - getDuration = () => { + getDuration () { return this.durationManager.get(); } - getDurationIn = () => { + getDurationIn () { const duration = this.durationManager.get(); return duration.enter + duration.delay; } - getDurationOut = () => { + getDurationOut () { const duration = this.durationManager.get(); return duration.exit; } - updateDuration = duration => { + updateDuration (duration) { this.durationManager.update(duration); } - hasEntered = () => { + hasEntered () { return this._flowHasEntered; } - hasExited = () => { + hasExited () { return this._flowHasExited; } + updateActivation (activated) { + if (this.isOutsourced()) { + activated ? this.enter() : this.exit(); + } + else { + throw new Error('"updateActivation" can not be called if not outsourced.'); + } + } + enter () { const flowValue = this.state.flowValue; @@ -137,11 +147,15 @@ class Component extends React.PureComponent { const duration = this.getDuration(); const delay = flowValue === EXITED ? duration.delay : 0; - this.scheduler.start(delay, () => { + this.scheduler.start(0, delay, () => { const duration = this.getDuration(); + if (this.props.onActivation) { + this.props.onActivation(true); + } + this.setFlowValue(ENTERING); - this.scheduler.start(duration.enter, () => this.setFlowValue(ENTERED)); + this.scheduler.start(0, duration.enter, () => this.setFlowValue(ENTERED)); }); } @@ -152,11 +166,15 @@ class Component extends React.PureComponent { return; } - this.scheduler.start(0, () => { + this.scheduler.start(0, 0, () => { const duration = this.getDuration(); + if (this.props.onActivation) { + this.props.onActivation(false); + } + this.setFlowValue(EXITING); - this.scheduler.start(duration.exit, () => this.setFlowValue(EXITED)); + this.scheduler.start(0, duration.exit, () => this.setFlowValue(EXITED)); }); } } diff --git a/packages/animation/src/Energy/Energy.test.js b/packages/animation/src/Energy/Energy.test.js index 8e53f13a..83c1d654 100644 --- a/packages/animation/src/Energy/Energy.test.js +++ b/packages/animation/src/Energy/Energy.test.js @@ -33,6 +33,21 @@ test('Should provide energy interface API as immutable', () => { render(); }); +describe('getFlow()', () => { + test('Should return current flow object', () => { + let energy; + render( (energy = r)} />); + expect(energy.getFlow()).toEqual({ value: 'exited', exited: true }); + }); + + test('Should return a frozen flow object', () => { + let energy; + render( (energy = r)} animate={false} />); + const flow = energy.getFlow(); + expect(() => (flow.value = 'xxx')).toThrow(); + }); +}); + describe('getDuration()', () => { test('Should get duration', () => { let energy; @@ -71,18 +86,3 @@ describe('updateDuration()', () => { expect(energy.getDuration()).toMatchObject({ enter: 70, exit: 70 }); }); }); - -describe('getFlow()', () => { - test('Should return current flow object', () => { - let energy; - render( (energy = r)} />); - expect(energy.getFlow()).toEqual({ value: 'exited', exited: true }); - }); - - test('Should return a frozen flow object', () => { - let energy; - render( (energy = r)} animate={false} />); - const flow = energy.getFlow(); - expect(() => (flow.value = 'xxx')).toThrow(); - }); -}); diff --git a/packages/animation/src/constants.js b/packages/animation/src/constants.js new file mode 100644 index 00000000..0a184dbe --- /dev/null +++ b/packages/animation/src/constants.js @@ -0,0 +1,6 @@ +export const ENERGY_TYPE = 'energy'; +export const STREAM_TYPE = 'stream'; +export const ENTERING = 'entering'; +export const ENTERED = 'entered'; +export const EXITING = 'exiting'; +export const EXITED = 'exited'; diff --git a/packages/animation/src/makeDurationManager/makeDurationManager.js b/packages/animation/src/makeDurationManager/makeDurationManager.js index 812752be..44d534ff 100644 --- a/packages/animation/src/makeDurationManager/makeDurationManager.js +++ b/packages/animation/src/makeDurationManager/makeDurationManager.js @@ -2,7 +2,7 @@ function makeDurationManager (component) { let customDuration; function get () { - const defaultDuration = { enter: 200, exit: 200, stagger: 0, delay: 0 }; + const defaultDuration = { enter: 200, exit: 200, stagger: 50, delay: 0 }; const providedDuration = component.props.animationContext.duration; diff --git a/packages/animation/src/makeDurationManager/makeDurationManager.test.js b/packages/animation/src/makeDurationManager/makeDurationManager.test.js index 4d71de6c..23b946da 100644 --- a/packages/animation/src/makeDurationManager/makeDurationManager.test.js +++ b/packages/animation/src/makeDurationManager/makeDurationManager.test.js @@ -39,13 +39,13 @@ describe('update()', () => { const component = { props: { animationContext: {} } }; const durationManager = makeDurationManager(component); durationManager.update({ enter: 900 }); - expect(durationManager.get()).toMatchObject({ enter: 900, exit: 200, delay: 0, stagger: 0 }); + expect(durationManager.get()).toMatchObject({ enter: 900, exit: 200, delay: 0, stagger: 50 }); }); test('Should update duration with number', () => { const component = { props: { animationContext: {} } }; const durationManager = makeDurationManager(component); durationManager.update(700); - expect(durationManager.get()).toMatchObject({ enter: 700, exit: 700, delay: 0, stagger: 0 }); + expect(durationManager.get()).toMatchObject({ enter: 700, exit: 700, delay: 0, stagger: 50 }); }); }); diff --git a/packages/animation/src/makeFlowManager/makeFlowManager.js b/packages/animation/src/makeFlowManager/makeFlowManager.js index 5124a3e4..04080e81 100644 --- a/packages/animation/src/makeFlowManager/makeFlowManager.js +++ b/packages/animation/src/makeFlowManager/makeFlowManager.js @@ -1,26 +1,35 @@ +import { STREAM_TYPE } from '../constants'; + function makeFlowManager (component) { let isFlowActivated = false; function checkMount () { - const animate = component.isAnimate(); - const activated = component.isActivated(); + const { parentEnergyContext } = component.props; + + // TODO: Add tests. + if (parentEnergyContext && parentEnergyContext.type === STREAM_TYPE) { + parentEnergyContext._subscribe(component); + } - if (animate && activated) { + if (!component.isAnimate() || component.isOutsourced()) { + return; + } + + if (component.isActivated()) { component.enter(); } } function checkUpdate () { - const animate = component.isAnimate(); + if (!component.isAnimate() || component.isOutsourced()) { + return; + } + const activated = component.isActivated(); - if (animate && activated !== isFlowActivated) { + if (activated !== isFlowActivated) { isFlowActivated = activated; - if (component.props.onActivate) { - component.props.onActivate(activated); - } - if (activated) { component.enter(); } @@ -30,7 +39,16 @@ function makeFlowManager (component) { } } - return { checkMount, checkUpdate }; + // TODO: Add tests. + function checkUnmount () { + const { parentEnergyContext } = component.props; + + if (parentEnergyContext && parentEnergyContext.type === STREAM_TYPE) { + parentEnergyContext._unsubscribe(component); + } + } + + return { checkMount, checkUpdate, checkUnmount }; } export { makeFlowManager }; diff --git a/packages/animation/src/makeFlowManager/makeFlowManager.test.js b/packages/animation/src/makeFlowManager/makeFlowManager.test.js index dec97abf..f5b7debb 100644 --- a/packages/animation/src/makeFlowManager/makeFlowManager.test.js +++ b/packages/animation/src/makeFlowManager/makeFlowManager.test.js @@ -3,11 +3,24 @@ import { makeFlowManager } from './makeFlowManager'; describe('checkMount()', () => { - test('Should do nothing if not animated not activated', () => { - const isAnimate = jest.fn(); - const isActivated = jest.fn(); + test('Should do nothing if not animated', () => { + const isAnimate = jest.fn(() => false); + const isActivated = jest.fn(() => true); + const isOutsourced = jest.fn(() => false); + const enter = jest.fn(); + const component = { props: {}, isAnimate, isActivated, isOutsourced, enter }; + const flowManager = makeFlowManager(component); + + flowManager.checkMount(); + expect(enter).not.toHaveBeenCalled(); + }); + + test('Should do nothing if outsourced', () => { + const isAnimate = jest.fn(() => true); + const isActivated = jest.fn(() => true); + const isOutsourced = jest.fn(() => true); const enter = jest.fn(); - const component = { isAnimate, isActivated, enter }; + const component = { props: {}, isAnimate, isActivated, isOutsourced, enter }; const flowManager = makeFlowManager(component); flowManager.checkMount(); @@ -17,8 +30,9 @@ describe('checkMount()', () => { test('Should enter() if animated and activated', () => { const isAnimate = jest.fn(() => true); const isActivated = jest.fn(() => true); + const isOutsourced = jest.fn(() => false); const enter = jest.fn(); - const component = { isAnimate, isActivated, enter }; + const component = { props: {}, isAnimate, isActivated, isOutsourced, enter }; const flowManager = makeFlowManager(component); flowManager.checkMount(); @@ -34,6 +48,7 @@ describe('checkUpdate()', () => { props: {}, isAnimate: jest.fn(() => true), isActivated: jest.fn(() => true), + isOutsourced: jest.fn(() => false), enter, exit }; @@ -51,6 +66,7 @@ describe('checkUpdate()', () => { props: {}, isAnimate: jest.fn(() => true), isActivated: jest.fn(() => true), + isOutsourced: jest.fn(() => false), enter, exit }; @@ -63,24 +79,21 @@ describe('checkUpdate()', () => { expect(exit).toHaveBeenCalledTimes(1); }); - test('Should call onActivate with value when activation changes', () => { - const onActivate = jest.fn(); + test('Should do nothing if outsourced', () => { const enter = jest.fn(); const exit = jest.fn(); const component = { - props: { onActivate }, + props: { imperative: true }, isAnimate: jest.fn(() => true), isActivated: jest.fn(() => true), + isOutsourced: jest.fn(() => true), enter, exit }; const flowManager = makeFlowManager(component); flowManager.checkUpdate(); - expect(onActivate).toHaveBeenCalledWith(true); - - component.isActivated = jest.fn(() => false); - flowManager.checkUpdate(); - expect(onActivate).toHaveBeenCalledWith(false); + expect(enter).not.toHaveBeenCalled(); + expect(exit).not.toHaveBeenCalled(); }); }); diff --git a/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.js b/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.js index 5c0b4682..30d6f718 100644 --- a/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.js +++ b/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.js @@ -1,26 +1,29 @@ +const METHODS = [ + 'getDuration', + 'getDurationIn', + 'getDurationOut', + 'updateDuration', + 'hasEntered', + 'hasExited', + '_subscribe', + '_unsubscribe' +]; + function makeGetEnergyInterface (component) { return function getEnergyInterface (flowValue) { - const { - getDuration, - getDurationIn, - getDurationOut, - updateDuration, - hasEntered, - hasExited - } = component; + const { type } = component; const flow = Object.freeze({ value: flowValue, [flowValue]: true }); + const methods = {}; - return Object.freeze({ - getDuration, - getDurationIn, - getDurationOut, - updateDuration, - hasEntered, - hasExited, - flow + METHODS.forEach(methodName => { + if (component[methodName]) { + methods[methodName] = component[methodName].bind(component); + } }); + + return Object.freeze({ type, flow, ...methods }); }; } -export { makeGetEnergyInterface }; +export { METHODS, makeGetEnergyInterface }; diff --git a/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.test.js b/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.test.js index 2da391aa..175706d2 100644 --- a/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.test.js +++ b/packages/animation/src/makeGetEnergyInterface/makeGetEnergyInterface.test.js @@ -1,38 +1,29 @@ /* eslint-env jest */ -import { makeGetEnergyInterface } from './makeGetEnergyInterface'; +import { METHODS, makeGetEnergyInterface } from './makeGetEnergyInterface'; test('Should get energy interface API with flow and component methods', () => { - const component = { - getDuration: 0, - getDurationIn: 1, - getDurationOut: 2, - updateDuration: 3, - hasEntered: 4, - hasExited: 5 - }; + const methods = {}; + METHODS.forEach(name => (methods[name] = { bind: jest.fn() })); + const component = { type: 0, ...methods }; const getEnergyInterface = makeGetEnergyInterface(component); const flowValue = 'entering'; const energy = getEnergyInterface(flowValue); expect(energy).toEqual({ - ...component, - flow: { - value: flowValue, - [flowValue]: true - } + type: 0, + flow: { value: flowValue, [flowValue]: true } + }); + METHODS.forEach(name => { + const method = methods[name]; + expect(method.bind).toHaveBeenCalledWith(component); }); }); test('Should get energy interface API as immutable', () => { - const component = { - getDuration: 0, - getDurationIn: 1, - getDurationOut: 2, - updateDuration: 3, - hasEntered: 4, - hasExited: 5 - }; + const methods = {}; + METHODS.forEach(name => (methods[name] = { bind: jest.fn() })); + const component = { type: 0, ...methods }; const getEnergyInterface = makeGetEnergyInterface(component); const energy = getEnergyInterface('entering'); diff --git a/packages/animation/src/makeIsOutsourced/index.js b/packages/animation/src/makeIsOutsourced/index.js new file mode 100644 index 00000000..f77a5e2e --- /dev/null +++ b/packages/animation/src/makeIsOutsourced/index.js @@ -0,0 +1 @@ +export { makeIsOutsourced } from './makeIsOutsourced'; diff --git a/packages/animation/src/makeIsOutsourced/makeIsOutsourced.js b/packages/animation/src/makeIsOutsourced/makeIsOutsourced.js new file mode 100644 index 00000000..f78eaf07 --- /dev/null +++ b/packages/animation/src/makeIsOutsourced/makeIsOutsourced.js @@ -0,0 +1,24 @@ +import { STREAM_TYPE } from '../constants'; + +function makeIsOutsourced (component) { + return function isOutsourced () { + if (component.isRoot()) { + return false; + } + + if (component.props.imperative) { + return true; + } + + if ( + component.props.parentEnergyContext && + component.props.parentEnergyContext.type === STREAM_TYPE + ) { + return true; + } + + return false; + }; +} + +export { makeIsOutsourced }; diff --git a/packages/animation/src/makeIsOutsourced/makeIsOutsourced.test.js b/packages/animation/src/makeIsOutsourced/makeIsOutsourced.test.js new file mode 100644 index 00000000..d9ee277d --- /dev/null +++ b/packages/animation/src/makeIsOutsourced/makeIsOutsourced.test.js @@ -0,0 +1,40 @@ +/* eslint-env jest */ + +import { STREAM_TYPE } from '../constants'; +import { makeIsOutsourced } from './makeIsOutsourced'; + +test('Should return false by default', () => { + const component = { + props: {}, + isRoot: jest.fn(() => false) + }; + const isOutsourced = makeIsOutsourced(component); + expect(isOutsourced()).toBe(false); +}); + +test('Should return false if root', () => { + const component = { + props: {}, + isRoot: jest.fn(() => true) + }; + const isOutsourced = makeIsOutsourced(component); + expect(isOutsourced()).toBe(false); +}); + +test('Should return true if imperative', () => { + const component = { + props: { imperative: true }, + isRoot: jest.fn(() => false) + }; + const isOutsourced = makeIsOutsourced(component); + expect(isOutsourced()).toBe(true); +}); + +test('Should return true if parent is stream', () => { + const component = { + props: { parentEnergyContext: { type: STREAM_TYPE } }, + isRoot: jest.fn(() => false) + }; + const isOutsourced = makeIsOutsourced(component); + expect(isOutsourced()).toBe(true); +}); diff --git a/packages/animation/src/makeScheduler/makeScheduler.js b/packages/animation/src/makeScheduler/makeScheduler.js index 821898a7..4e77f644 100644 --- a/packages/animation/src/makeScheduler/makeScheduler.js +++ b/packages/animation/src/makeScheduler/makeScheduler.js @@ -1,16 +1,20 @@ function makeScheduler () { - let timeout; + const timeouts = {}; - function stop () { - clearTimeout(timeout); + function stop (id) { + clearTimeout(timeouts[id]); } - function start (time, callback) { - stop(); - timeout = setTimeout(callback, time); + function stopAll () { + Object.values(timeouts).forEach(clearTimeout); } - return { stop, start }; + function start (id, time, callback) { + stop(id); + timeouts[id] = setTimeout(callback, time); + } + + return { stop, stopAll, start }; } export { makeScheduler }; diff --git a/packages/animation/src/makeScheduler/makeScheduler.test.js b/packages/animation/src/makeScheduler/makeScheduler.test.js index c2983bcc..9ac2a02c 100644 --- a/packages/animation/src/makeScheduler/makeScheduler.test.js +++ b/packages/animation/src/makeScheduler/makeScheduler.test.js @@ -10,22 +10,22 @@ afterEach(() => { jest.clearAllTimers(); }); -test('Should schedule a function call by given time', () => { +test('Should schedule a function call by given id, time', () => { const scheduler = makeScheduler(); const spy = jest.fn(); - scheduler.start(100, spy); + scheduler.start(0, 100, spy); jest.advanceTimersByTime(90); expect(spy).not.toHaveBeenCalled(); jest.advanceTimersByTime(20); // 110ms expect(spy).toHaveBeenCalledTimes(1); }); -test('Should stop scheduled function call', () => { +test('Should stop scheduled function call by id', () => { const scheduler = makeScheduler(); const spy = jest.fn(); - scheduler.start(100, spy); + scheduler.start(0, 100, spy); jest.advanceTimersByTime(90); - scheduler.stop(); + scheduler.stop(0); expect(spy).not.toHaveBeenCalled(); jest.advanceTimersByTime(20); // 110ms expect(spy).not.toHaveBeenCalled(); @@ -35,9 +35,9 @@ test('Should re-set schedule function call if called multiple times', () => { const scheduler = makeScheduler(); const spy1 = jest.fn(); const spy2 = jest.fn(); - scheduler.start(100, spy1); + scheduler.start(0, 100, spy1); jest.advanceTimersByTime(90); - scheduler.start(100, spy2); + scheduler.start(0, 100, spy2); jest.advanceTimersByTime(90); // 180ms expect(spy1).not.toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); @@ -45,3 +45,30 @@ test('Should re-set schedule function call if called multiple times', () => { expect(spy1).not.toHaveBeenCalled(); expect(spy2).toHaveBeenCalledTimes(1); }); + +test('Should be able to schedule multiple functions', () => { + const scheduler = makeScheduler(); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + scheduler.start(0, 100, spy1); + scheduler.start(1, 100, spy2); + jest.advanceTimersByTime(90); + expect(spy1).not.toHaveBeenCalled(); + expect(spy2).not.toHaveBeenCalled(); + jest.advanceTimersByTime(20); // 110ms + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(1); +}); + +test('Should be able to stop all scheduled functions', () => { + const scheduler = makeScheduler(); + const spy1 = jest.fn(); + const spy2 = jest.fn(); + scheduler.start(0, 100, spy1); + scheduler.start(1, 100, spy2); + jest.advanceTimersByTime(10); + scheduler.stopAll(); + jest.advanceTimersByTime(100); // 110ms + expect(spy1).not.toHaveBeenCalled(); + expect(spy2).not.toHaveBeenCalled(); +}); diff --git a/packages/animation/src/withEnergy/withEnergy.js b/packages/animation/src/withEnergy/withEnergy.js index 0bc98524..4c87ec0d 100644 --- a/packages/animation/src/withEnergy/withEnergy.js +++ b/packages/animation/src/withEnergy/withEnergy.js @@ -35,9 +35,15 @@ function withEnergy (options) { useEffect(() => { if (inner && cycles) { if (energy.flow.entering) { + if (!inner.current.enter) { + throw new Error('"withEnergy" provided component requires "enter" method.'); + } inner.current.enter(); } else if (energy.flow.exiting) { + if (!inner.current.exit) { + throw new Error('"withEnergy" provided component requires "exit" method.'); + } inner.current.exit(); } }