Skip to content

Commit

Permalink
refactor: animation components extensibility
Browse files Browse the repository at this point in the history
  • Loading branch information
romelperez committed Nov 12, 2019
1 parent a3f2483 commit 0a22036
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 123 deletions.
20 changes: 10 additions & 10 deletions packages/animation/src/Energy/Energy.animation.test.js
Expand Up @@ -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 <Energy activate={activate} duration={100} onActivate={onActivate} />;
return <Energy activate={activate} duration={100} onActivation={onActivation} />;
}
}
render(<Example ref={r => (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', () => {
Expand Down
56 changes: 37 additions & 19 deletions 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';
Expand All @@ -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,
Expand All @@ -31,24 +29,26 @@ 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);

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;
Expand All @@ -70,7 +70,8 @@ class Component extends React.PureComponent {
}

componentWillUnmount () {
this.scheduler.stop();
this.flowManager.checkUnmount();
this.scheduler.stopAll();
}

render () {
Expand Down Expand Up @@ -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;

Expand All @@ -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));
});
}

Expand All @@ -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));
});
}
}
Expand Down
30 changes: 15 additions & 15 deletions packages/animation/src/Energy/Energy.test.js
Expand Up @@ -33,6 +33,21 @@ test('Should provide energy interface API as immutable', () => {
render(<Energy><Example /></Energy>);
});

describe('getFlow()', () => {
test('Should return current flow object', () => {
let energy;
render(<Energy ref={r => (energy = r)} />);
expect(energy.getFlow()).toEqual({ value: 'exited', exited: true });
});

test('Should return a frozen flow object', () => {
let energy;
render(<Energy ref={r => (energy = r)} animate={false} />);
const flow = energy.getFlow();
expect(() => (flow.value = 'xxx')).toThrow();
});
});

describe('getDuration()', () => {
test('Should get duration', () => {
let energy;
Expand Down Expand Up @@ -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 ref={r => (energy = r)} />);
expect(energy.getFlow()).toEqual({ value: 'exited', exited: true });
});

test('Should return a frozen flow object', () => {
let energy;
render(<Energy ref={r => (energy = r)} animate={false} />);
const flow = energy.getFlow();
expect(() => (flow.value = 'xxx')).toThrow();
});
});
6 changes: 6 additions & 0 deletions 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';
Expand Up @@ -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;

Expand Down
Expand Up @@ -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 });
});
});
38 changes: 28 additions & 10 deletions 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();
}
Expand All @@ -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 };

0 comments on commit 0a22036

Please sign in to comment.