Skip to content

Commit

Permalink
feat: Move Animated to @react-native/animated
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrieldonadel committed Dec 20, 2022
1 parent 1b7127b commit 0bcf394
Show file tree
Hide file tree
Showing 71 changed files with 1,000 additions and 607 deletions.
186 changes: 4 additions & 182 deletions Libraries/Animated/AnimatedMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,192 +4,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @flow
*/

'use strict';

import type {Numeric as AnimatedNumeric} from './AnimatedImplementation';
import type {EndResult} from './animations/Animation';
import type {EndCallback} from './animations/Animation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
import type {TimingAnimationConfig} from './animations/TimingAnimation';

import {AnimatedEvent, attachNativeEvent} from './AnimatedEvent';
import AnimatedImplementation from './AnimatedImplementation';
import createAnimatedComponent from './createAnimatedComponent';
import AnimatedColor from './nodes/AnimatedColor';
import AnimatedInterpolation from './nodes/AnimatedInterpolation';
import AnimatedNode from './nodes/AnimatedNode';
import AnimatedValue from './nodes/AnimatedValue';
import AnimatedValueXY from './nodes/AnimatedValueXY';

/**
* Animations are a source of flakiness in snapshot testing. This mock replaces
* animation functions from AnimatedImplementation with empty animations for
* predictability in tests. When possible the animation will run immediately
* to the final state.
*/

// Prevent any callback invocation from recursively triggering another
// callback, which may trigger another animation
let inAnimationCallback = false;
function mockAnimationStart(
start: (callback?: ?EndCallback) => void,
): (callback?: ?EndCallback) => void {
return callback => {
const guardedCallback =
callback == null
? callback
: (...args: Array<EndResult>) => {
if (inAnimationCallback) {
console.warn(
'Ignoring recursive animation callback when running mock animations',
);
return;
}
inAnimationCallback = true;
try {
callback(...args);
} finally {
inAnimationCallback = false;
}
};
start(guardedCallback);
};
}

export type CompositeAnimation = {
start: (callback?: ?EndCallback) => void,
stop: () => void,
reset: () => void,
_startNativeLoop: (iterations?: number) => void,
_isUsingNativeDriver: () => boolean,
...
};

const emptyAnimation = {
start: () => {},
stop: () => {},
reset: () => {},
_startNativeLoop: () => {},
_isUsingNativeDriver: () => {
return false;
},
};

const mockCompositeAnimation = (
animations: Array<CompositeAnimation>,
): CompositeAnimation => ({
...emptyAnimation,
start: mockAnimationStart((callback?: ?EndCallback): void => {
animations.forEach(animation => animation.start());
callback?.({finished: true});
}),
});

const spring = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: SpringAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
return {
...emptyAnimation,
start: mockAnimationStart((callback?: ?EndCallback): void => {
anyValue.setValue(config.toValue);
callback?.({finished: true});
}),
};
};

const timing = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: TimingAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
return {
...emptyAnimation,
start: mockAnimationStart((callback?: ?EndCallback): void => {
anyValue.setValue(config.toValue);
callback?.({finished: true});
}),
};
};

const decay = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: DecayAnimationConfig,
): CompositeAnimation {
return emptyAnimation;
};

const sequence = function (
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return mockCompositeAnimation(animations);
};

type ParallelConfig = {stopTogether?: boolean, ...};
const parallel = function (
animations: Array<CompositeAnimation>,
config?: ?ParallelConfig,
): CompositeAnimation {
return mockCompositeAnimation(animations);
};

const delay = function (time: number): CompositeAnimation {
return emptyAnimation;
};

const stagger = function (
time: number,
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return mockCompositeAnimation(animations);
};

type LoopAnimationConfig = {
iterations: number,
resetBeforeIteration?: boolean,
...
};

const loop = function (
animation: CompositeAnimation,
// $FlowFixMe[prop-missing]
{iterations = -1}: LoopAnimationConfig = {},
): CompositeAnimation {
return emptyAnimation;
};
import {AnimatedMock} from '@react-native/animated';

export type {AnimatedNumeric as Numeric};
export type * from '@react-native/animated/AnimatedMock';

export default {
Value: AnimatedValue,
ValueXY: AnimatedValueXY,
Color: AnimatedColor,
Interpolation: AnimatedInterpolation,
Node: AnimatedNode,
decay,
timing,
spring,
add: AnimatedImplementation.add,
subtract: AnimatedImplementation.subtract,
divide: AnimatedImplementation.divide,
multiply: AnimatedImplementation.multiply,
modulo: AnimatedImplementation.modulo,
diffClamp: AnimatedImplementation.diffClamp,
delay,
sequence,
parallel,
stagger,
loop,
event: AnimatedImplementation.event,
createAnimatedComponent,
attachNativeEvent,
forkEvent: AnimatedImplementation.forkEvent,
unforkEvent: AnimatedImplementation.unforkEvent,
Event: AnimatedEvent,
};
export default AnimatedMock;
54 changes: 5 additions & 49 deletions Libraries/Animated/createAnimatedComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,14 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @flow
*/

import View from '../Components/View/View';
import useMergeRefs from '../Utilities/useMergeRefs';
import useAnimatedProps from './useAnimatedProps';
import * as React from 'react';

export type AnimatedComponentType<
-Props: {+[string]: mixed, ...},
+Instance = mixed,
> = React.AbstractComponent<
$ObjMap<
Props &
$ReadOnly<{
passthroughAnimatedPropExplicitValues?: React.ElementConfig<
typeof View,
>,
}>,
() => any,
>,
Instance,
>;
'use strict';

export default function createAnimatedComponent<TProps: {...}, TInstance>(
Component: React.AbstractComponent<TProps, TInstance>,
): AnimatedComponentType<TProps, TInstance> {
return React.forwardRef((props, forwardedRef) => {
const [reducedProps, callbackRef] = useAnimatedProps<TProps, TInstance>(
// $FlowFixMe[incompatible-call]
props,
);
const ref = useMergeRefs<TInstance | null>(callbackRef, forwardedRef);
import {createAnimatedComponent} from '@react-native/animated';

// Some components require explicit passthrough values for animation
// to work properly. For example, if an animated component is
// transformed and Pressable, onPress will not work after transform
// without these passthrough values.
// $FlowFixMe[prop-missing]
const {passthroughAnimatedPropExplicitValues, style} = reducedProps;
const {style: passthroughStyle, ...passthroughProps} =
passthroughAnimatedPropExplicitValues ?? {};
const mergedStyle = {...style, ...passthroughStyle};
export type * from '@react-native/animated/createAnimatedComponent';

return (
<Component
{...reducedProps}
{...passthroughProps}
style={mergedStyle}
ref={ref}
/>
);
});
}
export default createAnimatedComponent;
Loading

0 comments on commit 0bcf394

Please sign in to comment.