Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Move Animated to @react-native/animated #35467

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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