/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow * @format */ '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) => { 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 => ({ ...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 { return mockCompositeAnimation(animations); }; type ParallelConfig = {stopTogether?: boolean, ...}; const parallel = function ( animations: Array, config?: ?ParallelConfig, ): CompositeAnimation { return mockCompositeAnimation(animations); }; const delay = function (time: number): CompositeAnimation { return emptyAnimation; }; const stagger = function ( time: number, animations: Array, ): CompositeAnimation { return mockCompositeAnimation(animations); }; type LoopAnimationConfig = { iterations: number, resetBeforeIteration?: boolean, ... }; const loop = function ( animation: CompositeAnimation, // $FlowFixMe[prop-missing] {iterations = -1}: LoopAnimationConfig = {}, ): CompositeAnimation { return emptyAnimation; }; export type {AnimatedNumeric as Numeric}; 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, };