mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
ef0db95300
Summary: This change caused crashes in animations on some surfaces. Changelog: [Internal] Reviewed By: JoshuaGross Differential Revision: D28013969 fbshipit-source-id: 95845c69d6e67d59582ea14ad08cbf42fd3e2f8f
922 lines
28 KiB
JavaScript
922 lines
28 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
* @emails oncall+react_native
|
|
*/
|
|
|
|
jest
|
|
.clearAllMocks()
|
|
.mock('../../BatchedBridge/NativeModules', () => ({
|
|
NativeAnimatedModule: {},
|
|
PlatformConstants: {
|
|
getConstants() {
|
|
return {};
|
|
},
|
|
},
|
|
}))
|
|
.mock('../NativeAnimatedModule')
|
|
.mock('../../EventEmitter/NativeEventEmitter')
|
|
// findNodeHandle is imported from ReactNative so mock that whole module.
|
|
.setMock('../../Renderer/shims/ReactNative', {findNodeHandle: () => 1});
|
|
|
|
import TestRenderer from 'react-test-renderer';
|
|
import * as React from 'react';
|
|
|
|
const Animated = require('../Animated');
|
|
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
|
|
|
describe('Native Animated', () => {
|
|
const NativeAnimatedModule = require('../NativeAnimatedModule').default;
|
|
|
|
beforeEach(() => {
|
|
Object.assign(NativeAnimatedModule, {
|
|
getValue: jest.fn(),
|
|
addAnimatedEventToView: jest.fn(),
|
|
connectAnimatedNodes: jest.fn(),
|
|
connectAnimatedNodeToView: jest.fn(),
|
|
createAnimatedNode: jest.fn(),
|
|
disconnectAnimatedNodeFromView: jest.fn(),
|
|
disconnectAnimatedNodes: jest.fn(),
|
|
dropAnimatedNode: jest.fn(),
|
|
extractAnimatedNodeOffset: jest.fn(),
|
|
flattenAnimatedNodeOffset: jest.fn(),
|
|
removeAnimatedEventFromView: jest.fn(),
|
|
restoreDefaultValues: jest.fn(),
|
|
setAnimatedNodeOffset: jest.fn(),
|
|
setAnimatedNodeValue: jest.fn(),
|
|
startAnimatingNode: jest.fn(),
|
|
startListeningToAnimatedNodeValue: jest.fn(),
|
|
stopAnimation: jest.fn(),
|
|
stopListeningToAnimatedNodeValue: jest.fn(),
|
|
});
|
|
});
|
|
|
|
describe('Animated Value', () => {
|
|
it('proxies `setValue` correctly', () => {
|
|
const opacity = new Animated.Value(0);
|
|
const ref = React.createRef(null);
|
|
|
|
Animated.timing(opacity, {
|
|
toValue: 10,
|
|
duration: 1000,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
|
|
TestRenderer.create(<Animated.View ref={ref} style={{opacity}} />);
|
|
|
|
expect(ref.current).not.toBeNull();
|
|
jest.spyOn(ref.current, 'setNativeProps');
|
|
|
|
opacity.setValue(0.5);
|
|
|
|
expect(NativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(
|
|
expect.any(Number),
|
|
0.5,
|
|
);
|
|
expect(ref.current.setNativeProps).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should set offset', () => {
|
|
const opacity = new Animated.Value(0);
|
|
opacity.setOffset(10);
|
|
opacity.__makeNative();
|
|
|
|
TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'value', value: 0, offset: 10},
|
|
);
|
|
opacity.setOffset(20);
|
|
expect(NativeAnimatedModule.setAnimatedNodeOffset).toBeCalledWith(
|
|
expect.any(Number),
|
|
20,
|
|
);
|
|
});
|
|
|
|
it('should flatten offset', () => {
|
|
const opacity = new Animated.Value(0);
|
|
opacity.__makeNative();
|
|
|
|
TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'value', value: 0, offset: 0},
|
|
);
|
|
opacity.flattenOffset();
|
|
expect(NativeAnimatedModule.flattenAnimatedNodeOffset).toBeCalledWith(
|
|
expect.any(Number),
|
|
);
|
|
});
|
|
|
|
it('should save value on unmount', () => {
|
|
NativeAnimatedModule.getValue = jest.fn((tag, saveCallback) => {
|
|
saveCallback(1);
|
|
});
|
|
const opacity = new Animated.Value(0);
|
|
|
|
opacity.__makeNative();
|
|
|
|
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
const tag = opacity.__getNativeTag();
|
|
|
|
root.unmount();
|
|
|
|
expect(NativeAnimatedModule.getValue).toBeCalledWith(
|
|
tag,
|
|
expect.any(Function),
|
|
);
|
|
expect(opacity.__getValue()).toBe(1);
|
|
});
|
|
|
|
it('should extract offset', () => {
|
|
const opacity = new Animated.Value(0);
|
|
opacity.__makeNative();
|
|
|
|
TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'value', value: 0, offset: 0},
|
|
);
|
|
opacity.extractOffset();
|
|
expect(NativeAnimatedModule.extractAnimatedNodeOffset).toBeCalledWith(
|
|
expect.any(Number),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Animated Listeners', () => {
|
|
it('should get updates', () => {
|
|
const value1 = new Animated.Value(0);
|
|
value1.__makeNative();
|
|
const listener = jest.fn();
|
|
const id = value1.addListener(listener);
|
|
expect(
|
|
NativeAnimatedModule.startListeningToAnimatedNodeValue,
|
|
).toHaveBeenCalledWith(value1.__getNativeTag());
|
|
|
|
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
|
value: 42,
|
|
tag: value1.__getNativeTag(),
|
|
});
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(listener).toBeCalledWith({value: 42});
|
|
expect(value1.__getValue()).toBe(42);
|
|
|
|
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
|
value: 7,
|
|
tag: value1.__getNativeTag(),
|
|
});
|
|
expect(listener).toHaveBeenCalledTimes(2);
|
|
expect(listener).toBeCalledWith({value: 7});
|
|
expect(value1.__getValue()).toBe(7);
|
|
|
|
value1.removeListener(id);
|
|
expect(
|
|
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
|
|
).toHaveBeenCalledWith(value1.__getNativeTag());
|
|
|
|
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
|
value: 1492,
|
|
tag: value1.__getNativeTag(),
|
|
});
|
|
expect(listener).toHaveBeenCalledTimes(2);
|
|
expect(value1.__getValue()).toBe(7);
|
|
});
|
|
|
|
it('should removeAll', () => {
|
|
const value1 = new Animated.Value(0);
|
|
value1.__makeNative();
|
|
const listener = jest.fn();
|
|
[1, 2, 3, 4].forEach(() => value1.addListener(listener));
|
|
expect(
|
|
NativeAnimatedModule.startListeningToAnimatedNodeValue,
|
|
).toHaveBeenCalledWith(value1.__getNativeTag());
|
|
|
|
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
|
value: 42,
|
|
tag: value1.__getNativeTag(),
|
|
});
|
|
expect(listener).toHaveBeenCalledTimes(4);
|
|
expect(listener).toBeCalledWith({value: 42});
|
|
|
|
value1.removeAllListeners();
|
|
expect(
|
|
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
|
|
).toHaveBeenCalledWith(value1.__getNativeTag());
|
|
|
|
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
|
|
value: 7,
|
|
tag: value1.__getNativeTag(),
|
|
});
|
|
expect(listener).toHaveBeenCalledTimes(4);
|
|
});
|
|
});
|
|
|
|
describe('Animated Events', () => {
|
|
it('should map events', () => {
|
|
const value = new Animated.Value(0);
|
|
value.__makeNative();
|
|
const event = Animated.event([{nativeEvent: {state: {foo: value}}}], {
|
|
useNativeDriver: true,
|
|
});
|
|
|
|
const root = TestRenderer.create(<Animated.View onTouchMove={event} />);
|
|
expect(NativeAnimatedModule.addAnimatedEventToView).toBeCalledWith(
|
|
expect.any(Number),
|
|
'onTouchMove',
|
|
{
|
|
nativeEventPath: ['state', 'foo'],
|
|
animatedValueTag: value.__getNativeTag(),
|
|
},
|
|
);
|
|
|
|
expect(
|
|
NativeAnimatedModule.removeAnimatedEventFromView,
|
|
).not.toHaveBeenCalled();
|
|
root.unmount();
|
|
expect(NativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith(
|
|
expect.any(Number),
|
|
'onTouchMove',
|
|
value.__getNativeTag(),
|
|
);
|
|
});
|
|
|
|
it('should throw on invalid event path', () => {
|
|
const value = new Animated.Value(0);
|
|
value.__makeNative();
|
|
const event = Animated.event([{notNativeEvent: {foo: value}}], {
|
|
useNativeDriver: true,
|
|
});
|
|
|
|
jest.spyOn(console, 'error').mockImplementationOnce((...args) => {
|
|
if (args[0].startsWith('The above error occurred in the')) {
|
|
return;
|
|
}
|
|
console.errorDebug(...args);
|
|
});
|
|
|
|
expect(() => {
|
|
TestRenderer.create(<Animated.View onTouchMove={event} />);
|
|
}).toThrowError(/nativeEvent/);
|
|
expect(NativeAnimatedModule.addAnimatedEventToView).not.toBeCalled();
|
|
|
|
console.error.mockRestore();
|
|
});
|
|
|
|
it('should call listeners', () => {
|
|
const value = new Animated.Value(0);
|
|
value.__makeNative();
|
|
const listener = jest.fn();
|
|
const event = Animated.event([{nativeEvent: {foo: value}}], {
|
|
useNativeDriver: true,
|
|
listener,
|
|
});
|
|
const handler = event.__getHandler();
|
|
handler({nativeEvent: {foo: 42}});
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(listener).toBeCalledWith({nativeEvent: {foo: 42}});
|
|
});
|
|
});
|
|
|
|
describe('Animated Graph', () => {
|
|
it('creates and detaches nodes', () => {
|
|
const opacity = new Animated.Value(0);
|
|
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
|
|
Animated.timing(opacity, {
|
|
toValue: 10,
|
|
duration: 1000,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(3);
|
|
expect(NativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes(
|
|
2,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{
|
|
type: 'frames',
|
|
frames: expect.any(Array),
|
|
toValue: expect.any(Number),
|
|
iterations: 1,
|
|
},
|
|
expect.any(Function),
|
|
);
|
|
|
|
expect(
|
|
NativeAnimatedModule.disconnectAnimatedNodes,
|
|
).not.toHaveBeenCalled();
|
|
expect(NativeAnimatedModule.dropAnimatedNode).not.toHaveBeenCalled();
|
|
|
|
root.unmount();
|
|
|
|
expect(
|
|
NativeAnimatedModule.disconnectAnimatedNodes,
|
|
).toHaveBeenCalledTimes(2);
|
|
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('sends a valid description for value, style and props nodes', () => {
|
|
const opacity = new Animated.Value(0);
|
|
TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
|
|
Animated.timing(opacity, {
|
|
toValue: 10,
|
|
duration: 1000,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'value', value: 0, offset: 0},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'style', style: {opacity: expect.any(Number)}},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'props', props: {style: expect.any(Number)}},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for Animated.add nodes', () => {
|
|
const first = new Animated.Value(1);
|
|
const second = new Animated.Value(2);
|
|
first.__makeNative();
|
|
second.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{opacity: Animated.add(first, second)}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'addition', input: expect.any(Array)},
|
|
);
|
|
const additionCalls = NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
|
call => call[1].type === 'addition',
|
|
);
|
|
expect(additionCalls.length).toBe(1);
|
|
const additionCall = additionCalls[0];
|
|
const additionNodeTag = additionCall[0];
|
|
const additionConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
|
call => call[1] === additionNodeTag,
|
|
);
|
|
expect(additionConnectionCalls.length).toBe(2);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
additionCall[1].input[0],
|
|
{
|
|
type: 'value',
|
|
value: 1,
|
|
offset: 0,
|
|
},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
additionCall[1].input[1],
|
|
{
|
|
type: 'value',
|
|
value: 2,
|
|
offset: 0,
|
|
},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for Animated.subtract nodes', () => {
|
|
const first = new Animated.Value(2);
|
|
const second = new Animated.Value(1);
|
|
first.__makeNative();
|
|
second.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{opacity: Animated.subtract(first, second)}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'subtraction', input: expect.any(Array)},
|
|
);
|
|
const subtractionCalls = NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
|
call => call[1].type === 'subtraction',
|
|
);
|
|
expect(subtractionCalls.length).toBe(1);
|
|
const subtractionCall = subtractionCalls[0];
|
|
const subtractionNodeTag = subtractionCall[0];
|
|
const subtractionConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
|
call => call[1] === subtractionNodeTag,
|
|
);
|
|
expect(subtractionConnectionCalls.length).toBe(2);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
subtractionCall[1].input[0],
|
|
{
|
|
type: 'value',
|
|
value: 2,
|
|
offset: 0,
|
|
},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
subtractionCall[1].input[1],
|
|
{
|
|
type: 'value',
|
|
value: 1,
|
|
offset: 0,
|
|
},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for Animated.multiply nodes', () => {
|
|
const first = new Animated.Value(2);
|
|
const second = new Animated.Value(1);
|
|
first.__makeNative();
|
|
second.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{opacity: Animated.multiply(first, second)}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'multiplication', input: expect.any(Array)},
|
|
);
|
|
const multiplicationCalls = NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
|
call => call[1].type === 'multiplication',
|
|
);
|
|
expect(multiplicationCalls.length).toBe(1);
|
|
const multiplicationCall = multiplicationCalls[0];
|
|
const multiplicationNodeTag = multiplicationCall[0];
|
|
const multiplicationConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
|
call => call[1] === multiplicationNodeTag,
|
|
);
|
|
expect(multiplicationConnectionCalls.length).toBe(2);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
multiplicationCall[1].input[0],
|
|
{
|
|
type: 'value',
|
|
value: 2,
|
|
offset: 0,
|
|
},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
multiplicationCall[1].input[1],
|
|
{
|
|
type: 'value',
|
|
value: 1,
|
|
offset: 0,
|
|
},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for Animated.divide nodes', () => {
|
|
const first = new Animated.Value(4);
|
|
const second = new Animated.Value(2);
|
|
first.__makeNative();
|
|
second.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{opacity: Animated.divide(first, second)}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'division', input: expect.any(Array)},
|
|
);
|
|
const divisionCalls = NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
|
call => call[1].type === 'division',
|
|
);
|
|
expect(divisionCalls.length).toBe(1);
|
|
const divisionCall = divisionCalls[0];
|
|
const divisionNodeTag = divisionCall[0];
|
|
const divisionConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
|
call => call[1] === divisionNodeTag,
|
|
);
|
|
expect(divisionConnectionCalls.length).toBe(2);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
divisionCall[1].input[0],
|
|
{
|
|
type: 'value',
|
|
value: 4,
|
|
offset: 0,
|
|
},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
divisionCall[1].input[1],
|
|
{
|
|
type: 'value',
|
|
value: 2,
|
|
offset: 0,
|
|
},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for Animated.modulo nodes', () => {
|
|
const value = new Animated.Value(4);
|
|
value.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{opacity: Animated.modulo(value, 4)}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'modulus', modulus: 4, input: expect.any(Number)},
|
|
);
|
|
const moduloCalls = NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
|
call => call[1].type === 'modulus',
|
|
);
|
|
expect(moduloCalls.length).toBe(1);
|
|
const moduloCall = moduloCalls[0];
|
|
const moduloNodeTag = moduloCall[0];
|
|
const moduloConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
|
call => call[1] === moduloNodeTag,
|
|
);
|
|
expect(moduloConnectionCalls.length).toBe(1);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
moduloCall[1].input,
|
|
{
|
|
type: 'value',
|
|
value: 4,
|
|
offset: 0,
|
|
},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for interpolate() nodes', () => {
|
|
const value = new Animated.Value(10);
|
|
value.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View
|
|
style={{
|
|
opacity: value.interpolate({
|
|
inputRange: [10, 20],
|
|
outputRange: [0, 1],
|
|
}),
|
|
}}
|
|
/>,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'value', value: 10, offset: 0},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{
|
|
type: 'interpolation',
|
|
inputRange: [10, 20],
|
|
outputRange: [0, 1],
|
|
extrapolateLeft: 'extend',
|
|
extrapolateRight: 'extend',
|
|
},
|
|
);
|
|
const interpolationNodeTag = NativeAnimatedModule.createAnimatedNode.mock.calls.find(
|
|
call => call[1].type === 'interpolation',
|
|
)[0];
|
|
const valueNodeTag = NativeAnimatedModule.createAnimatedNode.mock.calls.find(
|
|
call => call[1].type === 'value',
|
|
)[0];
|
|
expect(NativeAnimatedModule.connectAnimatedNodes).toBeCalledWith(
|
|
valueNodeTag,
|
|
interpolationNodeTag,
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for transform nodes', () => {
|
|
const translateX = new Animated.Value(0);
|
|
translateX.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{transform: [{translateX}, {scale: 2}]}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{
|
|
type: 'transform',
|
|
transforms: [
|
|
{
|
|
nodeTag: expect.any(Number),
|
|
property: 'translateX',
|
|
type: 'animated',
|
|
},
|
|
{
|
|
value: 2,
|
|
property: 'scale',
|
|
type: 'static',
|
|
},
|
|
],
|
|
},
|
|
);
|
|
});
|
|
|
|
it('sends a valid graph description for Animated.diffClamp nodes', () => {
|
|
const value = new Animated.Value(2);
|
|
value.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View style={{opacity: Animated.diffClamp(value, 0, 20)}} />,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'diffclamp', input: expect.any(Number), max: 20, min: 0},
|
|
);
|
|
const diffClampCalls = NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
|
call => call[1].type === 'diffclamp',
|
|
);
|
|
expect(diffClampCalls.length).toBe(1);
|
|
const diffClampCall = diffClampCalls[0];
|
|
const diffClampNodeTag = diffClampCall[0];
|
|
const diffClampConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
|
call => call[1] === diffClampNodeTag,
|
|
);
|
|
expect(diffClampConnectionCalls.length).toBe(1);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
diffClampCall[1].input,
|
|
{
|
|
type: 'value',
|
|
value: 2,
|
|
offset: 0,
|
|
},
|
|
);
|
|
});
|
|
|
|
it("doesn't call into native API if useNativeDriver is set to false", () => {
|
|
const opacity = new Animated.Value(0);
|
|
|
|
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
|
|
Animated.timing(opacity, {
|
|
toValue: 10,
|
|
duration: 1000,
|
|
useNativeDriver: false,
|
|
}).start();
|
|
|
|
root.unmount();
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).not.toBeCalled();
|
|
});
|
|
|
|
it('fails when trying to run non-native animation on native node', () => {
|
|
const opacity = new Animated.Value(0);
|
|
const ref = React.createRef(null);
|
|
|
|
TestRenderer.create(<Animated.View ref={ref} style={{opacity}} />);
|
|
|
|
// Necessary to simulate the native animation.
|
|
expect(ref.current).not.toBeNull();
|
|
ref.current.setNativeProps = jest.fn();
|
|
|
|
Animated.timing(opacity, {
|
|
toValue: 10,
|
|
duration: 50,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
jest.runAllTimers();
|
|
|
|
Animated.timing(opacity, {
|
|
toValue: 4,
|
|
duration: 500,
|
|
useNativeDriver: false,
|
|
}).start();
|
|
try {
|
|
process.env.NODE_ENV = 'development';
|
|
expect(jest.runAllTimers).toThrow(
|
|
'Attempting to run JS driven animation on animated node that has ' +
|
|
'been moved to "native" earlier by starting an animation with ' +
|
|
'`useNativeDriver: true`',
|
|
);
|
|
} finally {
|
|
process.env.NODE_ENV = 'test';
|
|
}
|
|
});
|
|
|
|
it('fails for unsupported styles', () => {
|
|
const left = new Animated.Value(0);
|
|
|
|
TestRenderer.create(<Animated.View style={{left}} />);
|
|
|
|
const animation = Animated.timing(left, {
|
|
toValue: 10,
|
|
duration: 50,
|
|
useNativeDriver: true,
|
|
});
|
|
expect(animation.start).toThrowError(/left/);
|
|
});
|
|
|
|
it('works for any `static` props and styles', () => {
|
|
// Passing "unsupported" props should work just fine as long as they are not animated
|
|
const opacity = new Animated.Value(0);
|
|
opacity.__makeNative();
|
|
|
|
TestRenderer.create(
|
|
<Animated.View
|
|
removeClippedSubviews={true}
|
|
style={{left: 10, opacity, top: 20}}
|
|
/>,
|
|
);
|
|
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'style', style: {opacity: expect.any(Number)}},
|
|
);
|
|
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
{type: 'props', props: {style: expect.any(Number)}},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Animations', () => {
|
|
it('sends a valid timing animation description', () => {
|
|
const anim = new Animated.Value(0);
|
|
Animated.timing(anim, {
|
|
toValue: 10,
|
|
duration: 1000,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{
|
|
type: 'frames',
|
|
frames: expect.any(Array),
|
|
toValue: expect.any(Number),
|
|
iterations: 1,
|
|
},
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it('sends a valid spring animation description', () => {
|
|
const anim = new Animated.Value(0);
|
|
Animated.spring(anim, {
|
|
toValue: 10,
|
|
friction: 5,
|
|
tension: 164,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{
|
|
type: 'spring',
|
|
stiffness: 679.08,
|
|
damping: 16,
|
|
mass: 1,
|
|
initialVelocity: 0,
|
|
overshootClamping: false,
|
|
restDisplacementThreshold: 0.001,
|
|
restSpeedThreshold: 0.001,
|
|
toValue: 10,
|
|
iterations: 1,
|
|
},
|
|
expect.any(Function),
|
|
);
|
|
|
|
Animated.spring(anim, {
|
|
toValue: 10,
|
|
stiffness: 1000,
|
|
damping: 500,
|
|
mass: 3,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{
|
|
type: 'spring',
|
|
stiffness: 1000,
|
|
damping: 500,
|
|
mass: 3,
|
|
initialVelocity: 0,
|
|
overshootClamping: false,
|
|
restDisplacementThreshold: 0.001,
|
|
restSpeedThreshold: 0.001,
|
|
toValue: 10,
|
|
iterations: 1,
|
|
},
|
|
expect.any(Function),
|
|
);
|
|
|
|
Animated.spring(anim, {
|
|
toValue: 10,
|
|
bounciness: 8,
|
|
speed: 10,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{
|
|
type: 'spring',
|
|
damping: 23.05223140901191,
|
|
initialVelocity: 0,
|
|
overshootClamping: false,
|
|
restDisplacementThreshold: 0.001,
|
|
restSpeedThreshold: 0.001,
|
|
stiffness: 299.61882352941177,
|
|
mass: 1,
|
|
toValue: 10,
|
|
iterations: 1,
|
|
},
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it('sends a valid decay animation description', () => {
|
|
const anim = new Animated.Value(0);
|
|
Animated.decay(anim, {
|
|
velocity: 10,
|
|
deceleration: 0.1,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 1},
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it('works with Animated.loop', () => {
|
|
const anim = new Animated.Value(0);
|
|
Animated.loop(
|
|
Animated.decay(anim, {
|
|
velocity: 10,
|
|
deceleration: 0.1,
|
|
useNativeDriver: true,
|
|
}),
|
|
{iterations: 10},
|
|
).start();
|
|
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 10},
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it('sends stopAnimation command to native', () => {
|
|
const value = new Animated.Value(0);
|
|
const animation = Animated.timing(value, {
|
|
toValue: 10,
|
|
duration: 50,
|
|
useNativeDriver: true,
|
|
});
|
|
|
|
animation.start();
|
|
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
|
expect.any(Number),
|
|
expect.any(Number),
|
|
{
|
|
type: 'frames',
|
|
frames: expect.any(Array),
|
|
toValue: expect.any(Number),
|
|
iterations: 1,
|
|
},
|
|
expect.any(Function),
|
|
);
|
|
const animationId =
|
|
NativeAnimatedModule.startAnimatingNode.mock.calls[0][0];
|
|
|
|
animation.stop();
|
|
expect(NativeAnimatedModule.stopAnimation).toBeCalledWith(animationId);
|
|
});
|
|
});
|
|
|
|
describe('Animated Components', () => {
|
|
it('Should restore default values on prop updates only', () => {
|
|
const opacity = new Animated.Value(0);
|
|
opacity.__makeNative();
|
|
|
|
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
|
|
expect(NativeAnimatedModule.restoreDefaultValues).not.toHaveBeenCalled();
|
|
|
|
root.update(<Animated.View style={{opacity}} />);
|
|
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledWith(
|
|
expect.any(Number),
|
|
);
|
|
|
|
root.unmount();
|
|
// Make sure it doesn't get called on unmount.
|
|
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
|
|
1,
|
|
);
|
|
});
|
|
});
|
|
});
|