From a77c5bef659e5e2fc59e81ab5f0e25ab4f84fbd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 22 May 2019 17:09:00 -0700 Subject: [PATCH] Add spec for AnimatedModule (#24911) Summary: Part of #24875. Added `strict-local` to the NativeModuleHelper btw. ## Changelog [General] [Added] - TM add spec for AnimatedModule Pull Request resolved: https://github.com/facebook/react-native/pull/24911 Reviewed By: rickhanlonii Differential Revision: D15434114 Pulled By: fkgozali fbshipit-source-id: ea9215306ebf969795ce755270b8fdc14c52da9c --- .../Animated/src/NativeAnimatedHelper.js | 77 ++++++++++--------- .../Animated/src/NativeAnimatedModule.js | 70 +++++++++++++++++ .../Animated/src/__tests__/Animated-test.js | 4 + .../src/__tests__/AnimatedNative-test.js | 8 +- 4 files changed, 118 insertions(+), 41 deletions(-) create mode 100644 Libraries/Animated/src/NativeAnimatedModule.js diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index 05bd6e37a8a4d2..a5352046fbbf9f 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -4,30 +4,27 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict-local * @format */ 'use strict'; -const NativeAnimatedModule = require('../../BatchedBridge/NativeModules') - .NativeAnimatedModule; -const NativeEventEmitter = require('../../EventEmitter/NativeEventEmitter'); +import NativeEventEmitter from '../../EventEmitter/NativeEventEmitter'; +import type { + EventMapping, + AnimatedNodeConfig, + AnimatingNodeConfig, +} from './NativeAnimatedModule'; +import NativeAnimatedModule from './NativeAnimatedModule'; +import invariant from 'invariant'; -const invariant = require('invariant'); - -import type {AnimationConfig} from './animations/Animation'; +import type {AnimationConfig, EndCallback} from './animations/Animation'; +import type {InterpolationConfigType} from './nodes/AnimatedInterpolation'; import type {EventConfig} from './AnimatedEvent'; let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ let __nativeAnimationIdCount = 1; /* used for started animations */ -type EndResult = {finished: boolean}; -type EndCallback = (result: EndResult) => void; -type EventMapping = { - nativeEventPath: Array, - animatedValueTag: ?number, -}; - let nativeEventEmitter; /** @@ -35,36 +32,36 @@ let nativeEventEmitter; * the native module methods */ const API = { - createAnimatedNode: function(tag: ?number, config: Object): void { - assertNativeAnimatedModule(); + createAnimatedNode: function(tag: ?number, config: AnimatedNodeConfig): void { + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.createAnimatedNode(tag, config); }, startListeningToAnimatedNodeValue: function(tag: ?number) { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.startListeningToAnimatedNodeValue(tag); }, stopListeningToAnimatedNodeValue: function(tag: ?number) { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag); }, connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag); }, disconnectAnimatedNodes: function( parentTag: ?number, childTag: ?number, ): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag); }, startAnimatingNode: function( animationId: ?number, nodeTag: ?number, - config: Object, + config: AnimatingNodeConfig, endCallback: EndCallback, ): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.startAnimatingNode( animationId, nodeTag, @@ -73,41 +70,41 @@ const API = { ); }, stopAnimation: function(animationId: ?number) { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.stopAnimation(animationId); }, setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value); }, setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset); }, flattenAnimatedNodeOffset: function(nodeTag: ?number): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag); }, extractAnimatedNodeOffset: function(nodeTag: ?number): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag); }, connectAnimatedNodeToView: function( nodeTag: ?number, viewTag: ?number, ): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag); }, disconnectAnimatedNodeFromView: function( nodeTag: ?number, viewTag: ?number, ): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag); }, dropAnimatedNode: function(tag: ?number): void { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.dropAnimatedNode(tag); }, addAnimatedEventToView: function( @@ -115,7 +112,7 @@ const API = { eventName: string, eventMapping: EventMapping, ) { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.addAnimatedEventToView( viewTag, eventName, @@ -127,7 +124,7 @@ const API = { eventName: string, animatedNodeTag: ?number, ) { - assertNativeAnimatedModule(); + invariant(NativeAnimatedModule, 'Native animated module is not available'); NativeAnimatedModule.removeAnimatedEventFromView( viewTag, eventName, @@ -197,7 +194,12 @@ function addWhitelistedInterpolationParam(param: string): void { SUPPORTED_INTERPOLATION_PARAMS[param] = true; } -function validateTransform(configs: Array): void { +function validateTransform( + configs: Array< + | {type: 'animated', property: string, nodeTag: ?number} + | {type: 'static', property: string, value: number}, + >, +): void { configs.forEach(config => { if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) { throw new Error( @@ -209,7 +211,7 @@ function validateTransform(configs: Array): void { }); } -function validateStyles(styles: Object): void { +function validateStyles(styles: {[key: string]: ?number}): void { for (const key in styles) { if (!STYLES_WHITELIST.hasOwnProperty(key)) { throw new Error( @@ -219,7 +221,7 @@ function validateStyles(styles: Object): void { } } -function validateInterpolation(config: Object): void { +function validateInterpolation(config: InterpolationConfigType): void { for (const key in config) { if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) { throw new Error( @@ -244,7 +246,7 @@ function assertNativeAnimatedModule(): void { let _warnedMissingNativeAnimated = false; function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean { - if (config.useNativeDriver && !NativeAnimatedModule) { + if (config.useNativeDriver === true && !NativeAnimatedModule) { if (!_warnedMissingNativeAnimated) { console.warn( 'Animated: `useNativeDriver` is not supported because the native ' + @@ -261,7 +263,7 @@ function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean { return config.useNativeDriver || false; } -function transformDataType(value: any): number { +function transformDataType(value: number | string): number { // Change the string type to number type so we can reuse the same logic in // iOS and Android platform if (typeof value !== 'string') { @@ -290,6 +292,7 @@ module.exports = { assertNativeAnimatedModule, shouldUseNativeDriver, transformDataType, + // $FlowExpectedError - unsafe getter lint suppresion get nativeEventEmitter() { if (!nativeEventEmitter) { nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule); diff --git a/Libraries/Animated/src/NativeAnimatedModule.js b/Libraries/Animated/src/NativeAnimatedModule.js new file mode 100644 index 00000000000000..b13d6f33191bc4 --- /dev/null +++ b/Libraries/Animated/src/NativeAnimatedModule.js @@ -0,0 +1,70 @@ +/** + * 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. + * + * @flow + * @format + */ + +'use strict'; + +import type {TurboModule} from 'RCTExport'; +import * as TurboModuleRegistry from 'TurboModuleRegistry'; + +type EndResult = {finished: boolean}; +type EndCallback = (result: EndResult) => void; + +export type EventMapping = {| + nativeEventPath: Array, + animatedValueTag: ?number, +|}; + +export type AnimatedNodeConfig = {| + // TODO: Type this with better enums. + type: string, +|}; + +export type AnimatingNodeConfig = {| + // TODO: Type this with better enums. + type: string, +|}; + +export interface Spec extends TurboModule { + +createAnimatedNode: (tag: ?number, config: AnimatedNodeConfig) => void; + +startListeningToAnimatedNodeValue: (tag: ?number) => void; + +stopListeningToAnimatedNodeValue: (tag: ?number) => void; + +connectAnimatedNodes: (parentTag: ?number, childTag: ?number) => void; + +disconnectAnimatedNodes: (parentTag: ?number, childTag: ?number) => void; + +startAnimatingNode: ( + animationId: ?number, + nodeTag: ?number, + config: AnimatingNodeConfig, + endCallback: EndCallback, + ) => void; + +stopAnimation: (animationId: ?number) => void; + +setAnimatedNodeValue: (nodeTag: ?number, value: ?number) => void; + +setAnimatedNodeOffset: (nodeTag: ?number, offset: ?number) => void; + +flattenAnimatedNodeOffset: (nodeTag: ?number) => void; + +extractAnimatedNodeOffset: (nodeTag: ?number) => void; + +connectAnimatedNodeToView: (nodeTag: ?number, viewTag: ?number) => void; + +disconnectAnimatedNodeFromView: (nodeTag: ?number, viewTag: ?number) => void; + +dropAnimatedNode: (tag: ?number) => void; + +addAnimatedEventToView: ( + viewTag: ?number, + eventName: string, + eventMapping: EventMapping, + ) => void; + +removeAnimatedEventFromView: ( + viewTag: ?number, + eventName: string, + animatedNodeTag: ?number, + ) => void; + + // Events + +addListener: (eventName: string) => void; + +removeListeners: (count: number) => void; +} + +export default TurboModuleRegistry.get('NativeAnimatedModule'); diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js index 11c1ad922efede..fbfdc5dd73f3a1 100644 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ b/Libraries/Animated/src/__tests__/Animated-test.js @@ -10,6 +10,10 @@ 'use strict'; +jest.mock('../../../BatchedBridge/NativeModules', () => ({ + NativeAnimatedModule: {}, +})); + let Animated = require('../Animated'); describe('Animated tests', () => { beforeEach(() => { diff --git a/Libraries/Animated/src/__tests__/AnimatedNative-test.js b/Libraries/Animated/src/__tests__/AnimatedNative-test.js index e90f2341b7d90c..38e201928c2e5e 100644 --- a/Libraries/Animated/src/__tests__/AnimatedNative-test.js +++ b/Libraries/Animated/src/__tests__/AnimatedNative-test.js @@ -22,9 +22,10 @@ jest .setMock('../../../Lists/FlatList', ClassComponentMock) .setMock('../../../Lists/SectionList', ClassComponentMock) .setMock('react', {Component: class {}}) - .setMock('../../../BatchedBridge/NativeModules', { + .mock('../../../BatchedBridge/NativeModules', () => ({ NativeAnimatedModule: {}, - }) + })) + .mock('../NativeAnimatedModule') .mock('../../../EventEmitter/NativeEventEmitter') // findNodeHandle is imported from ReactNative so mock that whole module. .setMock('../../../Renderer/shims/ReactNative', {findNodeHandle: () => 1}); @@ -43,8 +44,7 @@ function createAndMountComponent(ComponentClass, props) { } describe('Native Animated', () => { - const nativeAnimatedModule = require('../../../BatchedBridge/NativeModules') - .NativeAnimatedModule; + const nativeAnimatedModule = require('../NativeAnimatedModule').default; beforeEach(() => { nativeAnimatedModule.addAnimatedEventToView = jest.fn();