Animated: Forward Ref to Component

Summary:
Changes `createAnimatedComponent` so that a `ref` assigned to an Animated component will now be forwarded to the internal component. Previously, a ref to the internal component was accessed using the `getNode` method. The `getNode` method is now deprecated and will return the same `ref` but show a deprecation error.

Changelog:
[General] [Changed] - Refs on an Animated component are now the internal component. The `getNode` call has been deprecated.

Reviewed By: TheSavior

Differential Revision: D18290474

fbshipit-source-id: 5849809583a17624a89071db8be1282a12caedf3
This commit is contained in:
Tim Yung
2019-11-03 18:00:15 -08:00
committed by Facebook Github Bot
parent 894ee72278
commit 66e72bb4e0
6 changed files with 310 additions and 316 deletions
@@ -95,51 +95,33 @@ describe('Animated tests', () => {
});
it('does not detach on updates', () => {
const anim = new Animated.Value(0);
anim.__detach = jest.fn();
const opacity = new Animated.Value(0);
opacity.__detach = jest.fn();
const c = new Animated.View();
c.props = {
style: {
opacity: anim,
},
};
c.UNSAFE_componentWillMount();
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
expect(anim.__detach).not.toBeCalled();
c._component = {};
c.UNSAFE_componentWillReceiveProps({
style: {
opacity: anim,
},
});
expect(anim.__detach).not.toBeCalled();
root.update(<Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
c.componentWillUnmount();
expect(anim.__detach).toBeCalled();
root.unmount();
expect(opacity.__detach).toBeCalled();
});
it('stops animation when detached', () => {
const anim = new Animated.Value(0);
const opacity = new Animated.Value(0);
const callback = jest.fn();
const c = new Animated.View();
c.props = {
style: {
opacity: anim,
},
};
c.UNSAFE_componentWillMount();
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
Animated.timing(anim, {
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: false,
}).start(callback);
c._component = {};
c.componentWillUnmount();
expect(callback).toBeCalledWith({finished: false});
root.unmount();
expect(callback).toBeCalledWith({finished: false});
});
@@ -198,7 +180,7 @@ describe('Animated tests', () => {
<Animated.View style={{opacity}} />,
);
expect(testRenderer.toJSON()).toMatchSnapshot();
expect(testRenderer.toJSON().props.style.opacity).toEqual(0);
Animated.timing(opacity, {
toValue: 1,
@@ -206,7 +188,7 @@ describe('Animated tests', () => {
useNativeDriver: false,
}).start();
expect(testRenderer.toJSON()).toMatchSnapshot();
expect(testRenderer.toJSON().props.style.opacity).toEqual(1);
});
it('warns if `useNativeDriver` is missing', () => {
@@ -10,18 +10,8 @@
'use strict';
const ClassComponentMock = class {};
ClassComponentMock.prototype.isReactComponent = true;
jest
.clearAllMocks()
.setMock('../../../Text/Text', ClassComponentMock)
.setMock('../../../Components/View/View', ClassComponentMock)
.setMock('../../../Image/Image', ClassComponentMock)
.setMock('../../../Components/ScrollView/ScrollView', ClassComponentMock)
.setMock('../../../Lists/FlatList', ClassComponentMock)
.setMock('../../../Lists/SectionList', ClassComponentMock)
.setMock('react', {Component: class {}})
.mock('../../../BatchedBridge/NativeModules', () => ({
NativeAnimatedModule: {},
PlatformConstants: {
@@ -35,128 +25,107 @@ jest
// 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');
function createAndMountComponent(ComponentClass, props) {
const component = new ComponentClass();
component.props = props;
component.UNSAFE_componentWillMount();
// Simulate that refs were set.
component._component = {};
component.componentDidMount();
return component;
}
describe('Native Animated', () => {
const nativeAnimatedModule = require('../NativeAnimatedModule').default;
const NativeAnimatedModule = require('../NativeAnimatedModule').default;
beforeEach(() => {
nativeAnimatedModule.addAnimatedEventToView = jest.fn();
nativeAnimatedModule.connectAnimatedNodes = jest.fn();
nativeAnimatedModule.connectAnimatedNodeToView = jest.fn();
nativeAnimatedModule.createAnimatedNode = jest.fn();
nativeAnimatedModule.disconnectAnimatedNodeFromView = jest.fn();
nativeAnimatedModule.disconnectAnimatedNodes = jest.fn();
nativeAnimatedModule.dropAnimatedNode = jest.fn();
nativeAnimatedModule.extractAnimatedNodeOffset = jest.fn();
nativeAnimatedModule.flattenAnimatedNodeOffset = jest.fn();
nativeAnimatedModule.removeAnimatedEventFromView = jest.fn();
nativeAnimatedModule.setAnimatedNodeOffset = jest.fn();
nativeAnimatedModule.setAnimatedNodeValue = jest.fn();
nativeAnimatedModule.startAnimatingNode = jest.fn();
nativeAnimatedModule.startListeningToAnimatedNodeValue = jest.fn();
nativeAnimatedModule.stopAnimation = jest.fn();
nativeAnimatedModule.stopListeningToAnimatedNodeValue = jest.fn();
Object.assign(NativeAnimatedModule, {
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(),
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 anim = new Animated.Value(0);
Animated.timing(anim, {
const opacity = new Animated.Value(0);
const ref = React.createRef(null);
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: true,
}).start();
const c = createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
TestRenderer.create(<Animated.View ref={ref} style={{opacity}} />);
// We expect `setValue` not to propagate down to `setNativeProps`, otherwise it may try to access `setNativeProps`
// via component refs table that we override here.
c.refs = {
node: {
setNativeProps: jest.fn(),
},
};
expect(ref.current).not.toBeNull();
jest.spyOn(ref.current, 'setNativeProps');
anim.setValue(0.5);
opacity.setValue(0.5);
expect(nativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(
expect(NativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(
expect.any(Number),
0.5,
);
expect(c.refs.node.setNativeProps).not.toHaveBeenCalled();
expect(ref.current.setNativeProps).not.toHaveBeenCalled();
});
it('should set offset', () => {
const anim = new Animated.Value(0);
anim.setOffset(10);
anim.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
const opacity = new Animated.Value(0);
opacity.setOffset(10);
opacity.__makeNative();
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
TestRenderer.create(<Animated.View style={{opacity}} />);
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'value', value: 0, offset: 10},
);
anim.setOffset(20);
expect(nativeAnimatedModule.setAnimatedNodeOffset).toBeCalledWith(
opacity.setOffset(20);
expect(NativeAnimatedModule.setAnimatedNodeOffset).toBeCalledWith(
expect.any(Number),
20,
);
});
it('should flatten offset', () => {
const anim = new Animated.Value(0);
anim.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
const opacity = new Animated.Value(0);
opacity.__makeNative();
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
TestRenderer.create(<Animated.View style={{opacity}} />);
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'value', value: 0, offset: 0},
);
anim.flattenOffset();
expect(nativeAnimatedModule.flattenAnimatedNodeOffset).toBeCalledWith(
opacity.flattenOffset();
expect(NativeAnimatedModule.flattenAnimatedNodeOffset).toBeCalledWith(
expect.any(Number),
);
});
it('should extract offset', () => {
const anim = new Animated.Value(0);
anim.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
const opacity = new Animated.Value(0);
opacity.__makeNative();
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
TestRenderer.create(<Animated.View style={{opacity}} />);
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'value', value: 0, offset: 0},
);
anim.extractOffset();
expect(nativeAnimatedModule.extractAnimatedNodeOffset).toBeCalledWith(
opacity.extractOffset();
expect(NativeAnimatedModule.extractAnimatedNodeOffset).toBeCalledWith(
expect.any(Number),
);
});
@@ -169,7 +138,7 @@ describe('Native Animated', () => {
const listener = jest.fn();
const id = value1.addListener(listener);
expect(
nativeAnimatedModule.startListeningToAnimatedNodeValue,
NativeAnimatedModule.startListeningToAnimatedNodeValue,
).toHaveBeenCalledWith(value1.__getNativeTag());
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
@@ -190,7 +159,7 @@ describe('Native Animated', () => {
value1.removeListener(id);
expect(
nativeAnimatedModule.stopListeningToAnimatedNodeValue,
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
).toHaveBeenCalledWith(value1.__getNativeTag());
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
@@ -207,7 +176,7 @@ describe('Native Animated', () => {
const listener = jest.fn();
[1, 2, 3, 4].forEach(() => value1.addListener(listener));
expect(
nativeAnimatedModule.startListeningToAnimatedNodeValue,
NativeAnimatedModule.startListeningToAnimatedNodeValue,
).toHaveBeenCalledWith(value1.__getNativeTag());
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
@@ -219,7 +188,7 @@ describe('Native Animated', () => {
value1.removeAllListeners();
expect(
nativeAnimatedModule.stopListeningToAnimatedNodeValue,
NativeAnimatedModule.stopListeningToAnimatedNodeValue,
).toHaveBeenCalledWith(value1.__getNativeTag());
NativeAnimatedHelper.nativeEventEmitter.emit('onAnimatedValueUpdate', {
@@ -237,8 +206,9 @@ describe('Native Animated', () => {
const event = Animated.event([{nativeEvent: {state: {foo: value}}}], {
useNativeDriver: true,
});
const c = createAndMountComponent(Animated.View, {onTouchMove: event});
expect(nativeAnimatedModule.addAnimatedEventToView).toBeCalledWith(
const root = TestRenderer.create(<Animated.View onTouchMove={event} />);
expect(NativeAnimatedModule.addAnimatedEventToView).toBeCalledWith(
expect.any(Number),
'onTouchMove',
{
@@ -247,8 +217,11 @@ describe('Native Animated', () => {
},
);
c.componentWillUnmount();
expect(nativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith(
expect(
NativeAnimatedModule.removeAnimatedEventFromView,
).not.toHaveBeenCalled();
root.unmount();
expect(NativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith(
expect.any(Number),
'onTouchMove',
value.__getNativeTag(),
@@ -261,10 +234,20 @@ describe('Native Animated', () => {
const event = Animated.event([{notNativeEvent: {foo: value}}], {
useNativeDriver: true,
});
expect(() =>
createAndMountComponent(Animated.View, {onTouchMove: event}),
).toThrowError(/nativeEvent/);
expect(nativeAnimatedModule.addAnimatedEventToView).not.toBeCalled();
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', () => {
@@ -284,27 +267,21 @@ describe('Native Animated', () => {
describe('Animated Graph', () => {
it('creates and detaches nodes', () => {
const anim = new Animated.Value(0);
const c = createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
const opacity = new Animated.Value(0);
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
Animated.timing(anim, {
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: true,
}).start();
c.componentWillUnmount();
expect(nativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(3);
expect(nativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes(
expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(3);
expect(NativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes(
2,
);
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{
@@ -317,34 +294,37 @@ describe('Native Animated', () => {
);
expect(
nativeAnimatedModule.disconnectAnimatedNodes,
NativeAnimatedModule.disconnectAnimatedNodes,
).not.toHaveBeenCalled();
expect(NativeAnimatedModule.dropAnimatedNode).not.toHaveBeenCalled();
root.unmount();
expect(
NativeAnimatedModule.disconnectAnimatedNodes,
).toHaveBeenCalledTimes(2);
expect(nativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(3);
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(3);
});
it('sends a valid description for value, style and props nodes', () => {
const anim = new Animated.Value(0);
createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
const opacity = new Animated.Value(0);
TestRenderer.create(<Animated.View style={{opacity}} />);
Animated.timing(anim, {
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: true,
}).start();
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'value', value: 0, offset: 0},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'style', style: {opacity: expect.any(Number)}},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'props', props: {style: expect.any(Number)}},
);
@@ -356,31 +336,29 @@ describe('Native Animated', () => {
first.__makeNative();
second.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.add(first, second),
},
});
TestRenderer.create(
<Animated.View style={{opacity: Animated.add(first, second)}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'addition', input: expect.any(Array)},
);
const additionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
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(
const additionConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
call => call[1] === additionNodeTag,
);
expect(additionConnectionCalls.length).toBe(2);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
additionCall[1].input[0],
{type: 'value', value: 1, offset: 0},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
additionCall[1].input[1],
{type: 'value', value: 2, offset: 0},
);
@@ -392,31 +370,29 @@ describe('Native Animated', () => {
first.__makeNative();
second.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.subtract(first, second),
},
});
TestRenderer.create(
<Animated.View style={{opacity: Animated.subtract(first, second)}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'subtraction', input: expect.any(Array)},
);
const subtractionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
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(
const subtractionConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
call => call[1] === subtractionNodeTag,
);
expect(subtractionConnectionCalls.length).toBe(2);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
subtractionCall[1].input[0],
{type: 'value', value: 2, offset: 0},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
subtractionCall[1].input[1],
{type: 'value', value: 1, offset: 0},
);
@@ -428,31 +404,29 @@ describe('Native Animated', () => {
first.__makeNative();
second.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.multiply(first, second),
},
});
TestRenderer.create(
<Animated.View style={{opacity: Animated.multiply(first, second)}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'multiplication', input: expect.any(Array)},
);
const multiplicationCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
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(
const multiplicationConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
call => call[1] === multiplicationNodeTag,
);
expect(multiplicationConnectionCalls.length).toBe(2);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
multiplicationCall[1].input[0],
{type: 'value', value: 2, offset: 0},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
multiplicationCall[1].input[1],
{type: 'value', value: 1, offset: 0},
);
@@ -464,31 +438,29 @@ describe('Native Animated', () => {
first.__makeNative();
second.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.divide(first, second),
},
});
TestRenderer.create(
<Animated.View style={{opacity: Animated.divide(first, second)}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'division', input: expect.any(Array)},
);
const divisionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
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(
const divisionConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
call => call[1] === divisionNodeTag,
);
expect(divisionConnectionCalls.length).toBe(2);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
divisionCall[1].input[0],
{type: 'value', value: 4, offset: 0},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
divisionCall[1].input[1],
{type: 'value', value: 2, offset: 0},
);
@@ -498,27 +470,25 @@ describe('Native Animated', () => {
const value = new Animated.Value(4);
value.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.modulo(value, 4),
},
});
TestRenderer.create(
<Animated.View style={{opacity: Animated.modulo(value, 4)}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'modulus', modulus: 4, input: expect.any(Number)},
);
const moduloCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
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(
const moduloConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
call => call[1] === moduloNodeTag,
);
expect(moduloConnectionCalls.length).toBe(1);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
moduloCall[1].input,
{type: 'value', value: 4, offset: 0},
);
@@ -528,20 +498,22 @@ describe('Native Animated', () => {
const value = new Animated.Value(10);
value.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: value.interpolate({
inputRange: [10, 20],
outputRange: [0, 1],
}),
},
});
TestRenderer.create(
<Animated.View
style={{
opacity: value.interpolate({
inputRange: [10, 20],
outputRange: [0, 1],
}),
}}
/>,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'value', value: 10, offset: 0},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{
type: 'interpolation',
@@ -551,29 +523,27 @@ describe('Native Animated', () => {
extrapolateRight: 'extend',
},
);
const interpolationNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find(
const interpolationNodeTag = NativeAnimatedModule.createAnimatedNode.mock.calls.find(
call => call[1].type === 'interpolation',
)[0];
const valueNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find(
const valueNodeTag = NativeAnimatedModule.createAnimatedNode.mock.calls.find(
call => call[1].type === 'value',
)[0];
expect(nativeAnimatedModule.connectAnimatedNodes).toBeCalledWith(
expect(NativeAnimatedModule.connectAnimatedNodes).toBeCalledWith(
valueNodeTag,
interpolationNodeTag,
);
});
it('sends a valid graph description for transform nodes', () => {
const value = new Animated.Value(0);
value.__makeNative();
const translateX = new Animated.Value(0);
translateX.__makeNative();
createAndMountComponent(Animated.View, {
style: {
transform: [{translateX: value}, {scale: 2}],
},
});
TestRenderer.create(
<Animated.View style={{transform: [{translateX}, {scale: 2}]}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{
type: 'transform',
@@ -597,86 +567,86 @@ describe('Native Animated', () => {
const value = new Animated.Value(2);
value.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.diffClamp(value, 0, 20),
},
});
TestRenderer.create(
<Animated.View style={{opacity: Animated.diffClamp(value, 0, 20)}} />,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'diffclamp', input: expect.any(Number), max: 20, min: 0},
);
const diffClampCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
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(
const diffClampConnectionCalls = NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
call => call[1] === diffClampNodeTag,
);
expect(diffClampConnectionCalls.length).toBe(1);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
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 anim = new Animated.Value(0);
const opacity = new Animated.Value(0);
const c = createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
const root = TestRenderer.create(<Animated.View style={{opacity}} />);
Animated.timing(anim, {
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: false,
}).start();
c.componentWillUnmount();
root.unmount();
expect(nativeAnimatedModule.createAnimatedNode).not.toBeCalled();
expect(NativeAnimatedModule.createAnimatedNode).not.toBeCalled();
});
it('fails when trying to run non-native animation on native node', () => {
const anim = new Animated.Value(0);
const opacity = new Animated.Value(0);
const ref = React.createRef(null);
createAndMountComponent(Animated.View, {
style: {
opacity: anim,
},
});
TestRenderer.create(<Animated.View ref={ref} style={{opacity}} />);
Animated.timing(anim, {
// 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(anim, {
Animated.timing(opacity, {
toValue: 4,
duration: 500,
useNativeDriver: false,
}).start();
expect(jest.runAllTimers).toThrow();
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 anim = new Animated.Value(0);
const left = new Animated.Value(0);
createAndMountComponent(Animated.View, {
style: {
left: anim,
},
});
TestRenderer.create(<Animated.View style={{left}} />);
const animation = Animated.timing(anim, {
const animation = Animated.timing(left, {
toValue: 10,
duration: 50,
useNativeDriver: true,
@@ -686,23 +656,21 @@ describe('Native Animated', () => {
it('works for any `static` props and styles', () => {
// Passing "unsupported" props should work just fine as long as they are not animated
const value = new Animated.Value(0);
value.__makeNative();
const opacity = new Animated.Value(0);
opacity.__makeNative();
createAndMountComponent(Animated.View, {
style: {
left: 10,
top: 20,
opacity: value,
},
removeClippedSubviews: true,
});
TestRenderer.create(
<Animated.View
removeClippedSubviews={true}
style={{left: 10, opacity, top: 20}}
/>,
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'style', style: {opacity: expect.any(Number)}},
);
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect(NativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'props', props: {style: expect.any(Number)}},
);
@@ -718,7 +686,7 @@ describe('Native Animated', () => {
useNativeDriver: true,
}).start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{
@@ -739,7 +707,7 @@ describe('Native Animated', () => {
tension: 164,
useNativeDriver: true,
}).start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{
@@ -764,7 +732,7 @@ describe('Native Animated', () => {
mass: 3,
useNativeDriver: true,
}).start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{
@@ -788,7 +756,7 @@ describe('Native Animated', () => {
speed: 10,
useNativeDriver: true,
}).start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{
@@ -815,7 +783,7 @@ describe('Native Animated', () => {
useNativeDriver: true,
}).start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 1},
@@ -834,7 +802,7 @@ describe('Native Animated', () => {
{iterations: 10},
).start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 10},
@@ -851,7 +819,7 @@ describe('Native Animated', () => {
});
animation.start();
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect(NativeAnimatedModule.startAnimatingNode).toBeCalledWith(
expect.any(Number),
expect.any(Number),
{
@@ -863,10 +831,10 @@ describe('Native Animated', () => {
expect.any(Function),
);
const animationId =
nativeAnimatedModule.startAnimatingNode.mock.calls[0][0];
NativeAnimatedModule.startAnimatingNode.mock.calls[0][0];
animation.stop();
expect(nativeAnimatedModule.stopAnimation).toBeCalledWith(animationId);
expect(NativeAnimatedModule.stopAnimation).toBeCalledWith(animationId);
});
});
});
@@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Animated tests Animated bypasses \`setNativeProps\` in test environments 1`] = `
<View
style={
Object {
"opacity": 0,
}
}
/>
`;
exports[`Animated tests Animated bypasses \`setNativeProps\` in test environments 2`] = `
<View
style={
Object {
"opacity": 1,
}
}
/>
`;
@@ -15,6 +15,7 @@ const AnimatedProps = require('./nodes/AnimatedProps');
const React = require('react');
const invariant = require('invariant');
const setAndForwardRef = require('../../Utilities/setAndForwardRef');
export type AnimatedComponentType<Props, Instance> = React.AbstractComponent<
any,
@@ -116,20 +117,26 @@ function createAnimatedComponent<Props, Instance>(
oldPropsAnimated && oldPropsAnimated.__detach();
}
_setComponentRef = c => {
this._prevComponent = this._component;
this._component = c;
};
_setComponentRef = setAndForwardRef({
getForwardedRef: () => this.props.forwardedRef,
setLocalRef: ref => {
this._prevComponent = this._component;
this._component = ref;
// A third party library can use getNode()
// to get the node reference of the decorated component
getNode() {
return this._component;
}
setNativeProps(props) {
this._component.setNativeProps(props);
}
// TODO: Delete this in a future release.
if (ref != null && ref.getNode == null) {
ref.getNode = () => {
console.warn(
'%s: Calling `getNode()` on the ref of an Animated component ' +
'is no longer necessary. You can now directly use the ref ' +
'instead. This method will be removed in a future release.',
ref.constructor.name ?? '<<anonymous>>',
);
return ref;
};
}
},
});
render() {
const props = this._propsAnimated.__getValue();
@@ -182,7 +189,14 @@ function createAnimatedComponent<Props, Instance>(
}
}
return AnimatedComponent;
return React.forwardRef(function AnimatedComponentWrapper(props, ref) {
return (
<AnimatedComponent
{...props}
{...(ref == null ? null : {forwardedRef: ref})}
/>
);
});
}
module.exports = createAnimatedComponent;
@@ -27,7 +27,7 @@ exports[`LogBoxInspectorSourceMapStatus should render for failed 1`] = `
}
}
>
<AnimatedComponent
<ForwardRef(AnimatedComponentWrapper)
source={
Object {
"height": 16,
@@ -94,7 +94,7 @@ exports[`LogBoxInspectorSourceMapStatus should render for pending 1`] = `
}
}
>
<AnimatedComponent
<ForwardRef(AnimatedComponentWrapper)
source={
Object {
"height": 16,
@@ -25,6 +25,8 @@ const {
View,
} = require('react-native');
import {useEffect, useRef, useState} from 'react';
const forceTouchAvailable =
(Platform.OS === 'ios' && Platform.constants.forceTouchAvailable) || false;
@@ -299,6 +301,48 @@ class TouchableHitSlop extends React.Component<{}, $FlowFixMeState> {
}
}
function TouchableNativeMethodChecker<
T: React.AbstractComponent<any, any>,
>(props: {|Component: T, name: string|}): React.Node {
const [status, setStatus] = useState<?boolean>(null);
const ref = useRef<?React.ElementRef<T>>(null);
useEffect(() => {
setStatus(ref.current != null && typeof ref.current.measure === 'function');
}, []);
return (
<View style={[styles.row, styles.block]}>
<props.Component ref={ref}>
<View />
</props.Component>
<Text>
{props.name + ': '}
{status == null
? 'Missing Ref!'
: status === true
? 'Native Methods Exist'
: 'Native Methods Missing!'}
</Text>
</View>
);
}
function TouchableNativeMethods() {
return (
<View>
<TouchableNativeMethodChecker
Component={TouchableHighlight}
name="TouchableHighlight"
/>
<TouchableNativeMethodChecker
Component={TouchableOpacity}
name="TouchableOpacity"
/>
</View>
);
}
class TouchableDisabled extends React.Component<{}> {
render() {
return (
@@ -551,6 +595,13 @@ exports.examples = [
return <TouchableHitSlop />;
},
},
{
title: 'Touchable Native Methods',
description: ('Some <Touchable*> components expose native methods like `measure`.': string),
render: function(): React.Element<any> {
return <TouchableNativeMethods />;
},
},
{
title: 'Disabled Touchable*',
description: ('<Touchable*> components accept disabled prop which prevents ' +