Compare commits

..
23 Commits
Author SHA1 Message Date
Kevin Gozali ab50c6ee2e [0.66.0-rc.3] Bump version numbers 2021-09-16 23:34:25 -07:00
Kevin Gozali dc453da92c Update rn-tester Podfile.lock to prepare for 0.66.0-rc.3 2021-09-16 23:25:48 -07:00
Kevin Gozali 8b6d7fddd6 Link RCT-Folly against libc++abi
Summary:
Folly now depends on libc++abi. This solves linker error for RCT-Folly.podspec like this:

```
Undefined symbols for architecture arm64:
  "___cxa_increment_exception_refcount", referenced from:
      folly::exception_ptr_get_type(std::exception_ptr const&) in libRCT-Folly.a(Exception.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

See https://github.com/react-native-community/releases/issues/251

Note: RNTester was not affected by this bug for some reason, so the only way to verify is via the new app generated via `npx react-native init`.

Changelog: [Fixed][iOS] Unbreak Folly linker error

Reviewed By: lunaleaps

Differential Revision: D30950944

fbshipit-source-id: 3eb146e23faa308a02363761d08849d6801e21ca
2021-09-16 22:56:53 -07:00
Luna Wei 614a370d86 [0.66.0-rc.2] Bump version numbers 2021-09-09 19:54:02 -07:00
Luna Wei c97015d2fa Update Podfile.lock 2021-09-09 12:24:35 -07:00
Xuan Huang c282c2d3b7 Bump Hermes pod to 0.9.0
Summary:
Changelog:
[General][Changed] - Bump Hermes to 0.9.0

(Note: this ignores all push blocking failures!)

Reviewed By: lunaleaps

Differential Revision: D30726473

fbshipit-source-id: add4149454b3f0333f3c1cb8b5d632371fd1bd80
2021-09-09 00:28:55 -07:00
Xuan Huang 2133172ea2 Bump Hermes npm to 0.9.0
Summary:
Changelog:
[General][Changed] - Bump Hermes to 0.9.0

allow-large-files

Reviewed By: lunaleaps

Differential Revision: D30726474

fbshipit-source-id: 742cf68b046d8768e83e00d754e8efcc97586c00
2021-09-08 23:57:55 -07:00
Xuan Huang 013e623fbb Revert the Android specific max heap size GCConfig
Summary:
Changelog:
[Category][Internal]

This diff reverts the [Androids-specific heap size overrides](github.com/facebook/react-native/commit/63d20d3b1ef35cb4398d63d62f631f7f5d2935c7#diff-4c59ddca85e294a90a0e1bd15ed323ff4e130911d9642680dde44aacbcd7d32c) after
[Hermes has changed its default max heap size to 3GiB](https://github.com/facebook/hermes/commit/5f2b47d0be6281fd2605d24efc0b43af42b4033d).
You can read about more context there.

Reviewed By: yungsters

Differential Revision: D30726067

fbshipit-source-id: 1bcc93fdf4da817f3b3d60bd09c6a5a64166eb7e
2021-09-08 23:57:49 -07:00
Neil Dhar b4a1d2ba64 Make JSI a dynamic library
Summary:
Ship libjsi as a standalone dynamic library. This prevents problems
with exception handling caused by duplicate typeinfo across multiple
shared libs, and reduces bundle size by removing duplicate copies of
JSI.

Changelog: [Internal]

Reviewed By: fkgozali

Differential Revision: D30599215

fbshipit-source-id: abad1398342a5328daa825f3f684e0067cad7a96
2021-09-08 23:57:38 -07:00
Tommy Nguyen 0d7586c620 [LOCAL] postfix timestamp to bust yarn cache 2021-09-08 23:57:19 -07:00
Luna Wei d552362f85 [0.66.0-rc.1] Bump version numbers 2021-09-01 15:20:26 -07:00
Luna Wei 1594af13bc Copy repo-config dependencies for bumping release version
Summary:
Changelog: [Internal[Fixed] - Revert, yarn workspaces only used in private packages. Copy dependencies over from repo-config instead

Original commit changeset: 1dd2adc6a036

Reviewed By: fkgozali

Differential Revision: D30599065

fbshipit-source-id: 0efffaaf38bc23bac339e6e1d917736243e1750e
2021-09-01 15:16:08 -07:00
Samuel Susla 038cdda238 Switch order of search libraries to fix M1 build error
Summary: changelog: Resolve Xcode 13 build error on M1 Macs for projects created from RN template

Reviewed By: fkgozali

Differential Revision: D30693466

fbshipit-source-id: f0b4fd471de38119d636c8e337831aa4d4599c4e
2021-09-01 13:11:50 -07:00
Kevin Gozali ea5109fc0e OSS: add Xcode 12.5 + M1 machines CocoaPods post_install workaround
Summary:
Context: there are multiple issues currently exposed by Xcode 12.5 and/or M1 machine + Flipper. To unblock the new 0.66 release, let's add this workaround in the official react_native_pods.rb recipe and make RNTester and new app Podfile's call it directly.

Changelog: [iOS][Fixed] Added workaround for Xcode 12.5 / M1 machines build issues

Reviewed By: lunaleaps

Differential Revision: D30691291

fbshipit-source-id: 8b24cc60da3d620dbc90f95c77f2345e18c28212
2021-09-01 13:11:19 -07:00
Kevin Gozali b23dd8565a OSS: Fix $ENTRY_FILE check for non-Debug Xcode builds
Summary:
The original $ENTRY_FILE check was added in https://github.com/facebook/react-native/pull/29012 to help catch misconfiguration for the entry JS file. That turned out breaking some RNTester builds/tests, so https://github.com/facebook/react-native/pull/29263 was added to accommodate the fact that RNTester .xcodeproj file has its own directory hierarchy.

The 2nd PR had multiple issues:
* It is incorrect to assume that the $ENTRY_FILE always exists in the parent dir of the .xcodeproj location. This caused an issue in RC 0.66: https://github.com/react-native-community/releases/issues/249#issue-983474535
* RNTester has since moved to packages/rn-tester/ (from RNTester/), hence breaking that assumption

It turns out RNTester .xcodeproj has incorrectly misconfigured this JS bundling step (not sure since when). The original script invocation passed in the correct path for `RNTesterApp.ios.js`, but as an arg to the `react-native-xcode.sh` instead of by setting `ENTRY_FILE` env var.

So this diff does 2 things:
* Undid https://github.com/facebook/react-native/pull/29263
* Fix RNTester JS bundling invocation to set the ENTRY_FILE correctly

{F659123377}

Changelog: [iOS][Fixed] Unbreak $ENTRY_FILE handling for JS bundling

Reviewed By: lunaleaps

Differential Revision: D30690900

fbshipit-source-id: 7c5802b3eac56c0456edcd4b7478bfa4af48fc27
2021-09-01 13:11:00 -07:00
Andrew Coates d20b34a562 Native component check in deprecatedPropType was inverted (#31164)
Summary:
While investigating an issue hit on a recent sync of [react-native-windows](https://github.com/microsoft/react-native-windows) I noticed that https://github.com/facebook/react-native/commit/e68cf7cee9d36271a1d3899fecff817304bb8bdc appears to have accidently inverted the logic to avoid checking native components.

`!UIManager.getViewManagerConfig(componentName)`
become
`UIManager.hasViewManagerConfig(componentName)`
losing the !

Also adding a check in PaperUIManager's getViewManagerConfig to avoid trying to call a sync method when using Chrome Debugging.

[Internal] [Fixed] - Restored the previous logic of deprecatedPropType

Pull Request resolved: https://github.com/facebook/react-native/pull/31164

Test Plan:
Change tested and being submitted in react-native-windows:
https://github.com/microsoft/react-native-windows/pull/7397

Reviewed By: hramos

Differential Revision: D30624302

Pulled By: fkgozali

fbshipit-source-id: 0f26e750283a1fa5eb5f44ecd2cf90617b6d931f
2021-09-01 13:08:54 -07:00
Luna Wei a0429ebb54 [0.66.0-rc.0] Bump version numbers 2021-08-27 01:12:10 -07:00
Luna Wei ab829005bd Move react-native-codegen to root 2021-08-26 23:40:46 -07:00
Luna Wei 27073242f2 Update manual testing script to also test Hermes for RNTester
Summary: Changelog: [Internal] - Update test-manual-e2e.sh to test Hermes for RNTester

Reviewed By: ShikaSD

Differential Revision: D30569403

fbshipit-source-id: fd45c8158c4c5ad93f33bc7b80464c5fc387a737
2021-08-26 23:40:46 -07:00
Connor Tumbleson b78f6cee7d feat: add Android 12 BLUETOOTH_ADVERTISE to PermissionsAndroid (#32079)
Summary:
This PR adds BLUETOOTH_ADVERTISE, which showed up in the latest Android 12 Beta build as new `dangerous` permissions requiring approval for them.

https://developer.android.com/reference/android/Manifest.permission.html#BLUETOOTH_ADVERTISE

You can see the new set of `SCAN/ADVERTISE/CONNECT` added in this doc - https://developer.android.com/about/versions/12/features/bluetooth-permissions, previously SCAN/CONNECT were added in: https://github.com/facebook/react-native/pull/31488

## Changelog

[Android] [Changed] - Add BLUETOOTH_ADVERTISE to PermissionsAndroid

Pull Request resolved: https://github.com/facebook/react-native/pull/32079

Test Plan:
```
PermissionsAndroid.BLUETOOTH_ADVERTISE === 'android.permission.BLUETOOTH_ADVERTISE'
```

Reviewed By: cortinico

Differential Revision: D30532656

Pulled By: yungsters

fbshipit-source-id: 986ad8cbfc27913df13ab24bba36f6e13104e7d9
2021-08-26 22:12:28 -07:00
Tommy Nguyen 10daf76e68 chore: prefer the local react-native-codegen package (#32096)
Summary:
Currently, the build breaks if we move `react-native-codegen` from the template's dependencies to root. This is due to `scripts/generate-specs-cli.js` using the one installed under `node_modules` instead of the local one.

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[Internal] [Fixed] - `scripts/generate-specs-cli.js` should prefer the local `react-native-codegen` package

Pull Request resolved: https://github.com/facebook/react-native/pull/32096

Test Plan:
1. Make the following changes
   ```diff
   diff --git a/package.json b/package.json
   index 847c726a69b..78da8232988 100644
   --- a/package.json
   +++ b/package.json
   @@ -107,6 +107,7 @@
        "promise": "^8.0.3",
        "prop-types": "^15.7.2",
        "react-devtools-core": "^4.13.0",
   +    "react-native-codegen": "^0.0.7",
        "react-refresh": "^0.4.0",
        "regenerator-runtime": "^0.13.2",
        "scheduler": "^0.20.2",
   diff --git a/template/package.json b/template/package.json
   index 715614112ac..5e0762b1b25 100644
   --- a/template/package.json
   +++ b/template/package.json
   @@ -21,7 +21,6 @@
        "eslint": "7.14.0",
        "jest": "^26.6.3",
        "metro-react-native-babel-preset": "^0.66.2",
   -    "react-native-codegen": "^0.0.7",
        "react-test-renderer": "17.0.2"
      },
      "jest": {
   ```
2. Run `scripts/test-manual-e2e.sh`

## Expected Behavior

Task `:ReactAndroid:buildReactNdkLib` succeeds.

## Actual Behavior

```
> Task :ReactAndroid:buildReactNdkLib FAILED
make: Entering directory '~/Source/react-native/ReactAndroid/src/main/jni/react/jni'
fcntl(): Bad file descriptor
make: Leaving directory '~/Source/react-native/ReactAndroid/src/main/jni/react/jni'
~/Library/Android/sdk/ndk/21.4.7075529/build/core/build-binary.mk:651: Android NDK: Module react_codegen_rncore depends on undefined modules: react_render_components_view
~/Library/Android/sdk/ndk/21.4.7075529/build/core/build-binary.mk:664: *** Android NDK: Note that old versions of ndk-build silently ignored this error case. If your project worked on those versions, the missing libraries were not needed and you can remove those dependencies from the module to fix your build. Alternatively, set APP_ALLOW_MISSING_DEPS=true to allow missing dependencies.    .  Stop.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':ReactAndroid:buildReactNdkLib'.
> Process 'command '~/Library/Android/sdk/ndk/21.4.7075529/ndk-build'' finished with non-zero exit value 2

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.9/userguide/command_line_interface.html#sec:command_line_warnings

BUILD FAILED in 19s
20 actionable tasks: 20 executed
Couldn't generate artifacts
```

Reviewed By: ShikaSD

Differential Revision: D30581194

Pulled By: hramos

fbshipit-source-id: 3f7a707b33377042502e50887856ff5641fdd52c
2021-08-26 22:12:28 -07:00
Luna Wei e6eb81e097 Include Swift lib in LIBRARY_SEARCH_PATHS
Summary:
changelog: Fix Xcode 13 build error in RNTester

Including `usr/lib/swift` fixes error:
{F642876047}

Reviewed By: fkgozali

Differential Revision: D30559838

fbshipit-source-id: 65aad16b550d156c8670eaefcc8bedae99606329
2021-08-25 17:10:30 -07:00
Nick Gerleman 72eb06009b Bump @react-native/polyfills version (#32074)
Summary:
https://github.com/facebook/react-native/commit/8a62583f794875e6dc5d1e4a24889b3b702d9f86 did some renaming inside of the react-native/polyfills project, with the jest preset updated to use the new name. The new package for polyfills has not yet been published, so the jest preset in the main branch will be looking for the new name, while the old name is provided by the currently published react-native/polyfills@1.0.0. This is not hit inside the repo, since the dependency is linked instead of using the published one.

Bump react-native/polyfills to 2.0 (breaking change), in preparation for publish.

## Changelog

[Internal][Fixed] - Bump react-native/polyfills version

Pull Request resolved: https://github.com/facebook/react-native/pull/32074

Reviewed By: lunaleaps, cortinico

Differential Revision: D30498104

Pulled By: yungsters

fbshipit-source-id: 92dcb159d76bd74cd93cfa09e2155c9c1b2c0a86
2021-08-25 17:10:07 -07:00
5969 changed files with 289381 additions and 338639 deletions
-8
View File
@@ -1,8 +0,0 @@
{
"presets": [
"module:metro-react-native-babel-preset"
],
"plugins": [
"babel-plugin-transform-flow-enums"
]
}
+1 -8
View File
@@ -1,10 +1,6 @@
[android]
target = android-31
[kotlin]
compile_against_abis = True
kotlin_version = 1.6.10
target = android-30
[download]
max_number_of_retries = 3
@@ -15,6 +11,3 @@
[alias]
rntester = //packages/rn-tester/android/app:app
[buildfile]
includes = //tools/build_defs/oss/preload.bzl
-2
View File
@@ -1,2 +0,0 @@
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1
+19 -9
View File
@@ -14,31 +14,41 @@
# and build a Android application that can be used to run the
# tests specified in the scripts/ directory.
#
FROM reactnativecommunity/react-native-android:7.0
FROM reactnativecommunity/react-native-android:4.0
LABEL Description="React Native Android Test Image"
LABEL maintainer="Héctor Ramos <hector@fb.com>"
# set default environment variables
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dfile.encoding=utf-8 -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
ENV KOTLIN_HOME="packages/react-native/third-party/kotlin"
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=\"-Xmx512m -XX:+HeapDumpOnOutOfMemoryError\""
ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
ADD .buckconfig /app/.buckconfig
ADD .buckjavaargs /app/.buckjavaargs
ADD BUCK /app/BUCK
ADD packages/react-native/Libraries /app/packages/react-native/Libraries
ADD packages/react-native/ReactAndroid /app/packages/react-native/ReactAndroid
ADD packages/react-native/ReactCommon /app/packages/react-native/ReactCommon
ADD packages/react-native/React /app/packages/react-native/React
ADD Libraries /app/Libraries
ADD ReactAndroid /app/ReactAndroid
ADD ReactCommon /app/ReactCommon
ADD React /app/React
ADD keystores /app/keystores
ADD packages/react-native-codegen /app/packages/react-native-codegen
ADD tools /app/tools
ADD scripts /app/scripts
WORKDIR /app
RUN buck fetch ReactAndroid/src/test/java/com/facebook/react/modules
RUN buck fetch ReactAndroid/src/main/java/com/facebook/react
RUN buck fetch ReactAndroid/src/main/java/com/facebook/react/shell
RUN buck fetch ReactAndroid/src/test/...
RUN buck fetch ReactAndroid/src/androidTest/...
RUN buck build ReactAndroid/src/main/java/com/facebook/react
RUN buck build ReactAndroid/src/main/java/com/facebook/react/shell
ADD . /app
RUN yarn
RUN ./gradlew :packages:react-native:ReactAndroid:assembleDebug
RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# 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.
@@ -33,7 +33,7 @@ node cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/a
# build test APK
# shellcheck disable=SC1091
source ./scripts/android-setup.sh && NO_BUCKD=1 scripts/retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1
source ./scripts/android-setup.sh && NO_BUCKD=1 retry3 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1
# run installed apk with tests
node ./.circleci/Dockerfiles/scripts/run-android-ci-instrumentation-tests.js "$*"
@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# 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.
@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# 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.
@@ -141,7 +141,7 @@ function e2e_suite() {
fi
fi
if ! (cd packages/react-native && FILENAME=$(npm pack | tail -1) && mv "$FILENAME" ../../)
if ! npm pack
then
echo "Failed to pack react-native"
return 1
@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# 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.
+472 -1456
View File
File diff suppressed because it is too large Load Diff
-36
View File
@@ -1,36 +0,0 @@
storage: ./storage
auth:
htpasswd:
file: ./htpasswd
uplinks:
npmjs:
url: https://registry.npmjs.org/
max_fails: 40
maxage: 30m
timeout: 60s
fail_timeout: 10m
cache: false
agent_options:
keepAlive: true
maxSockets: 40
maxFreeSockets: 10
packages:
# Get @react-native/normalize-color from npm registry, since its used in deprecated-react-native-prop-types package
'@react-native/normalize-color':
access: $all
publish: $authenticated
proxy: npmjs
# Group and isolate all local packages, avoid being proxy from outside
'@react-native/*':
access: $all
publish: $all
'@*/*':
access: $all
publish: $authenticated
proxy: npmjs
'**':
access: $all
publish: $all
proxy: npmjs
logs:
- {type: file, path: verdaccio.log, format: json, level: warn}
+4
View File
@@ -18,3 +18,7 @@ indent_size = 4
[BUCK]
indent_size = 4
# Windows files
[*.bat]
end_of_line = crlf
+4 -4
View File
@@ -1,10 +1,10 @@
**/main.js
**/staticBundle.js
bots/node_modules
docs/generatedComponentApiDocs.js
packages/react-native/flow/
packages/react-native/Libraries/Renderer/*
packages/react-native/Libraries/vendor/**/*
flow/
Libraries/Renderer/*
Libraries/vendor/**/*
node_modules/
packages/*/node_modules
packages/react-native-codegen/lib
tools/eslint/rules/sort-imports.js
+60
View File
@@ -0,0 +1,60 @@
{
"root": true,
"extends": [
"./packages/eslint-config-react-native-community/index.js"
],
"plugins": [
"@react-native/eslint-plugin-codegen"
],
"overrides": [
{
"files": [
"Libraries/**/*.js",
],
"rules": {
"@react-native-community/no-haste-imports": 2,
"@react-native-community/error-subclass-name": 2,
"@react-native-community/platform-colors": 2,
"@react-native/codegen/react-native-modules": 2
}
},
{
"files": [
"flow-typed/**/*.js",
],
"rules": {
quotes: 0
}
},
{
"files": [
"**/__fixtures__/**/*.js",
"**/__mocks__/**/*.js",
"**/__tests__/**/*.js",
"jest/**/*.js",
"packages/rn-tester/**/*.js",
],
"globals": {
// Expose some Jest globals for test helpers
"afterAll": true,
"afterEach": true,
"beforeAll": true,
"beforeEach": true,
"expect": true,
"jest": true,
},
},
{
"files": [
"**/__tests__/**/*-test.js",
],
"env": {
"jasmine": true,
"jest": true
}
}
]
}
-105
View File
@@ -1,105 +0,0 @@
/**
* 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.
*
* @format
*/
'use strict';
const path = require('node:path');
require('eslint-plugin-lint').load(path.join(__dirname, 'tools/eslint/rules'));
module.exports = {
root: true,
extends: ['@react-native'],
plugins: ['@react-native/eslint-plugin-specs', 'lint'],
overrides: [
// overriding the JS config from eslint-config-react-native-community config to ensure
// that we use hermes-eslint for all js files
{
files: ['*.js'],
parser: 'hermes-eslint',
rules: {
// These rules are not required with hermes-eslint
'ft-flow/define-flow-type': 0,
'ft-flow/use-flow-type': 0,
// flow handles this check for us, so it's not required
'no-undef': 0,
},
},
{
files: ['packages/react-native/Libraries/**/*.js'],
rules: {
'@react-native/platform-colors': 2,
'@react-native/specs/react-native-modules': 2,
'lint/no-haste-imports': 2,
'lint/no-react-native-imports': 2,
'lint/require-extends-error': 2,
'lint/sort-imports': 1,
},
},
{
files: ['packages/react-native/flow-typed/**/*.js'],
rules: {
'lint/valid-flow-typed-signature': 2,
'no-unused-vars': 0,
quotes: 0,
},
},
{
files: [
'**/__fixtures__/**/*.js',
'**/__mocks__/**/*.js',
'**/__tests__/**/*.js',
'packages/react-native/jest/**/*.js',
'packages/rn-tester/**/*.js',
],
globals: {
// Expose some Jest globals for test helpers
afterAll: true,
afterEach: true,
beforeAll: true,
beforeEach: true,
expect: true,
jest: true,
},
},
{
files: ['**/__tests__/**/*-test.js'],
env: {
jasmine: true,
jest: true,
},
},
{
files: ['**/*.{ts,tsx}'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint/eslint-plugin'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'react-native/no-inline-styles': 'off',
'@typescript-eslint/no-shadow': 'off',
'no-self-compare': 'off',
'react/self-closing-comp': 'off',
},
},
{
files: ['**/*.d.ts'],
plugins: ['redundant-undefined'],
rules: {
'redundant-undefined/redundant-undefined': [
'error',
{followExactOptionalPropertyTypes: true},
],
},
},
],
};
+11 -14
View File
@@ -3,18 +3,16 @@
.*/*[.]android.js
; Ignore templates for 'react-native init'
<PROJECT_ROOT>/packages/react-native/template/.*
<PROJECT_ROOT>/template/.*
; Ignore the Dangerfile
<PROJECT_ROOT>/packages/react-native-bots/dangerfile.js
<PROJECT_ROOT>/bots/dangerfile.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Flow doesn't support platforms
.*/packages/react-native/Libraries/Utilities/LoadingView.js
.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$
.*/Libraries/Utilities/LoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
@@ -25,16 +23,14 @@
.*/node_modules/.*
[libs]
packages/react-native/interface.js
packages/react-native/flow/
packages/react-native/flow-typed/
interface.js
flow/
[options]
enums=true
emoji=true
exact_by_default=true
indexed_access=false
format.bracket_spacing=false
@@ -44,9 +40,9 @@ module.file_ext=.ios.js
munge_underscores=true
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/packages/react-native\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/index.js'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
@@ -63,6 +59,7 @@ nonstrict-import=warn
deprecated-type=error
unsafe-getters-setters=warn
unnecessary-invariant=warn
signature-verification-failure=warn
[strict]
deprecated-type
@@ -74,4 +71,4 @@ untyped-import
untyped-type-import
[version]
^0.202.0
^0.158.0
+11 -14
View File
@@ -3,18 +3,16 @@
.*/*[.]ios.js
; Ignore templates for 'react-native init'
<PROJECT_ROOT>/packages/react-native/template/.*
<PROJECT_ROOT>/template/.*
; Ignore the Dangerfile
<PROJECT_ROOT>/packages/react-native-bots/dangerfile.js
<PROJECT_ROOT>/bots/dangerfile.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Flow doesn't support platforms
.*/packages/react-native/Libraries/Utilities/LoadingView.js
.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$
.*/Libraries/Utilities/LoadingView.js
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
@@ -25,16 +23,14 @@
.*/node_modules/.*
[libs]
packages/react-native/interface.js
packages/react-native/flow/
packages/react-native/flow-typed/
interface.js
flow/
[options]
enums=true
emoji=true
exact_by_default=true
indexed_access=false
format.bracket_spacing=false
@@ -44,9 +40,9 @@ module.file_ext=.android.js
munge_underscores=true
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/packages/react-native\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/index.js'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
@@ -63,6 +59,7 @@ nonstrict-import=warn
deprecated-type=error
unsafe-getters-setters=warn
unnecessary-invariant=warn
signature-verification-failure=warn
[strict]
deprecated-type
@@ -74,4 +71,4 @@ untyped-import
untyped-type-import
[version]
^0.202.0
^0.158.0
+3
View File
@@ -0,0 +1,3 @@
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf
+1 -1
View File
@@ -9,7 +9,7 @@
# GitHub Settings, Bots
/.github/ @hramos
/packages/react-native-bots @hramos
/bots @hramos
# Continuous Integration
/.circleci/ @hramos
+30
View File
@@ -0,0 +1,30 @@
---
name: "🐛 Bug Report"
about: Report a reproducible bug or regression in React Native.
title: ''
labels: 'Needs: Triage :mag:'
---
Please provide all the information requested. Issues that do not follow this format are likely to stall.
## Description
Please provide a clear and concise description of what the bug is. Include screenshots if needed.
Please test using the latest React Native release to make sure your issue has not already been fixed: https://reactnative.dev/docs/upgrading.html
## React Native version:
Run `react-native info` in your terminal and copy the results here.
## Steps To Reproduce
Provide a detailed list of steps that reproduce the issue.
1.
2.
## Expected Results
Describe what you expected to happen.
## Snack, code example, screenshot, or link to a repository:
Please provide a Snack (https://snack.expo.io/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.
You may provide a screenshot of the application if you think it is relevant to your bug report.
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
-48
View File
@@ -1,48 +0,0 @@
name: 🐛 Bug Report
description: Report a reproducible bug or regression in React Native.
labels: ["Needs: Triage :mag:"]
body:
- type: markdown
attributes:
value: |
Please provide all the information requested. Issues that do not follow this format are likely to stall.
- type: textarea
id: description
attributes:
label: Description
description: Please provide a clear and concise description of what the bug is. Include screenshots if needed. Test using the [latest React Native release](https://github.com/facebook/react-native/releases/latest) to make sure your issue has not already been fixed.
validations:
required: true
- type: input
id: version
attributes:
label: React Native Version
description: What is the latest version of react-native that this issue reproduces on? Please only list the highest version you tested. Bear in mind that only issues on [supported versions](https://github.com/reactwg/react-native-releases#which-versions-are-currently-supported) will be looked into.
placeholder: ex. 0.71.0
validations:
required: true
- type: textarea
id: react-native-info
attributes:
label: Output of `npx react-native info`
description: Run `npx react-native info` in your terminal, copy and paste the results here.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Provide a detailed list of steps that reproduce the issue.
validations:
required: true
- type: textarea
id: extra
attributes:
label: Snack, code example, screenshot, or link to a repository
description: |
Please provide a Snack (https://snack.expo.dev/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.
You may provide a screenshot of the application if you think it is relevant to your bug report.
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
Please note that a reproducer is **mandatory**. Issues without reproducer are more likely to stall and will be closed.
validations:
required: true
+3 -3
View File
@@ -3,12 +3,12 @@ contact_links:
- name: 📃 Documentation Issue
url: https://github.com/facebook/react-native-website/issues
about: Please report documentation issues in the React Native website repository.
- name: ⤴️ Upgrade Issue
url: https://github.com/react-native-community/upgrade-support
about: Need help upgrading to a newer React Native version? Visit the Upgrade Support repository.
- name: 🤔 Questions and Help
url: https://reactnative.dev/help
about: Looking for help with your app? Please refer to the React Native community's support resources.
- name: 💫 New Architecture - Questions & Technical Deep dive insights
url: https://github.com/reactwg/react-native-new-architecture
about: Questions and doubts related to technical questions for the New Architecture should be directed to the Working Group. Instructions on how to join are available in the README.
- name: 🚀 Discussions and Proposals
url: https://github.com/react-native-community/discussions-and-proposals
about: Discuss the future of React Native in the React Native community's discussions and proposals repository.
@@ -1,53 +0,0 @@
name: 💫 New Architecture - Bug Report
description: Report a reproducible bug or a build issue when using the New Architecture (Fabric & TurboModules) in React Native.
labels: ["Needs: Triage :mag:", "Type: New Architecture"]
body:
- type: markdown
attributes:
value: |
Please provide all the information requested. Issues that do not follow this format are going to be closed.
This issue report is reserved to bug & build issues for users on the New Architecture. If you're not using
the New Architecture, please don't open issues on this category.
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a clear and concise description of what the bug or issue is. Include screenshots if needed.
Please make sure you check the New Architecture documentation first, as your issue might
already be answered there - https://reactnative.dev/docs/next/new-architecture-intro
validations:
required: true
- type: input
id: version
attributes:
label: React Native Version
description: What is the latest version of react-native that this issue reproduces on? Please test against the latest stable version, and list only the highest version you tested. Bug reports against older versions are more likely to stall.
placeholder: ex. 0.71.0
validations:
required: true
- type: textarea
id: react-native-info
attributes:
label: Output of `npx react-native info`
description: Run `npx react-native info` in your terminal, copy and paste the results here.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Provide a detailed list of steps that reproduce the issue.
validations:
required: true
- type: textarea
id: extra
attributes:
label: Snack, code example, screenshot, or link to a repository
description: |
Please provide a Snack (https://snack.expo.dev/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.
You may provide a screenshot of the application if you think it is relevant to your bug report.
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
Please note that a reproducer is **mandatory**. Issues without reproducer are more likely to stall and will be closed.
validations:
required: true
@@ -1,46 +0,0 @@
name: ⬆️ Upgrade - Build Regression
description: If you are upgrading to a new React Native version (stable or pre-release) and encounter a build regression.
labels: ["Needs: Triage :mag:", "Type: Upgrade Issue"]
body:
- type: markdown
attributes:
value: |
Please use this form to file an issue if you have upgraded or are upgrading to [latest stable release](https://github.com/facebook/react-native/releases/latest) and have experienced a regression (something that used to work in previous version).
- type: input
id: new-version
attributes:
label: New Version
description: This is the version you are attempting to upgrade to.
placeholder: ex. 0.66.1
validations:
required: true
- type: input
id: old-version
attributes:
label: Old Version
description: This is the version you were on where the behavior was working.
placeholder: ex. 0.65.1
validations:
required: true
- type: input
id: target
attributes:
label: Build Target(s)
description: What target(s) are encountering this issue?
placeholder: iOS simulator in release flavor
validations:
required: true
- type: textarea
id: react-native-info
attributes:
label: Output of `react-native info`
description: Run `react-native info` in your terminal, copy and paste the results here.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Issue and Reproduction Steps
description: Please describe the issue and list out commands run to reproduce.
validations:
required: true
+4 -8
View File
@@ -6,16 +6,12 @@
## Changelog
<!-- Help reviewers and the release process by writing your own changelog entry.
Pick one each for the category and type tags:
[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message
For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->
[CATEGORY] [TYPE] - Message
## Test Plan
<!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. -->
-23
View File
@@ -1,23 +0,0 @@
<!-- Template for pre-release GitHub release -->
<!-- TODO Post about this release to https://github.com/reactwg/react-native-releases/discussions) -->
- <!-- TODO List out notable picks for this patch -->
---
To test it, run:
npx react-native init RN__SHORT_VERSION__ --version __VERSION__
---
You can participate in the conversation on the status of this release in the [working group](https://github.com/reactwg/react-native-releases/discussions).
---
To help you upgrade to this version, you can use the [upgrade helper](https://react-native-community.github.io/upgrade-helper/) ⚛️
---
See changes from this release in the [changelog PR](https://github.com/facebook/react-native/labels/%F0%9F%93%9D%20Changelog)
@@ -0,0 +1,56 @@
# Configuration for Respond To Issue Based on Label https://github.com/marketplace/actions/respond-to-issue-based-on-label
"Type: Invalid":
close: true
"Type: Question":
comment: >
We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/).
close: true
"Type: Docs":
comment: >
Please report documentation issues in the [`react-native-website`](https://github.com/facebook/react-native-website/issues) repository.
close: true
"Type: Upgrade Issue":
comment: >
Do you need help upgrading to a newer React Native version? Visit the [Upgrade Support repository](https://github.com/react-native-community/upgrade-support) or use the [upgrade helper](https://react-native-community.github.io/upgrade-helper/) to see the changes that need to be made to upgrade your app.
close: true
"Resolution: For Stack Overflow":
comment: >
We are using GitHub issues exclusively to track bugs in the core React Native library. Please try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) as it is better suited for this type of question.
close: true
"Needs: Issue Template":
comment: >
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
Missing Required Fields
</th></tr><tr><td>:information_source:</td><td>
It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a <a href="https://github.com/facebook/react-native/issues/new?template=bug_report.md">new issue is created</a>. Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one.
</td></tr></tbody></table>
labels:
- "Needs: Author Feedback"
"Needs: Environment Info":
comment: >
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
Missing Environment Information
</th></tr><tr><td>:information_source:</td><td>
Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console.
</td></tr></tbody></table>
labels:
- "Needs: Author Feedback"
"Needs: Verify on Latest Version":
comment: >
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
Using Old Version
</th></tr><tr><td>:information_source:</td><td>
It looks like you are using an older version of React Native. Please <a href="https://reactnative.dev/docs/upgrading">upgrade</a> to the latest version, and verify if the issue persists. If it does not, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the current release.
</td></tr></tbody></table>
labels:
- "Needs: Author Feedback"
"Needs: Repro":
comment: >
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
Missing Reproducible Example
</th></tr><tr><td>:information_source:</td><td>
It looks like your issue is missing a reproducible example. Please provide a <a href="https://snack.expo.io">Snack</a> or a repository that demonstrates the issue you are reporting in a <a href="https://stackoverflow.com/help/minimal-reproducible-example">minimal, complete, and reproducible</a> manner.
</td></tr></tbody></table>
labels:
- "Needs: Author Feedback"
+26
View File
@@ -0,0 +1,26 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- Good first issue
- "Type: Discussion"
- Partner
- Core Team
- "Help Wanted :octocat:"
- "Impact: Regression"
- "Resolution: PR Submitted"
- "Resolution: Backlog"
# Label to use when marking an issue as stale
staleLabel: Stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs.
You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open.
Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.
only: issues
-137
View File
@@ -1,137 +0,0 @@
/**
* 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.
*
* @format
*/
module.exports = async (github, context, label) => {
const closeIssue = async () => {
await github.rest.issues.update({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
});
};
const addComment = async comment => {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment,
});
};
const requestAuthorFeedback = async () => {
// Remove the triage label if it exists (ignore the 404 if not; it's not expected to always be there)
try {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'Needs: Triage :mag:',
});
} catch {}
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Needs: Author Feedback'],
});
};
switch (label) {
case 'Type: Invalid':
await addComment(
`| :warning: | Issue is Invalid |\n` +
`| --- | --- |\n` +
`| :information_source: | This issue doesn't match any of the expected types for this repository - closing. |`,
);
await closeIssue();
return;
case 'Type: Question':
await addComment(
`| :warning: | Issue is a Question |\n` +
`| --- | --- |\n` +
`| :information_source: | We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/). |`,
);
await closeIssue();
return;
case 'Type: Docs':
await addComment(
`| :warning: | Documentation Issue |\n` +
`| --- | --- |\n` +
`| :information_source: | Please report documentation issues in the [react-native-website](https://github.com/facebook/react-native-website/issues) repository. |`,
);
await closeIssue();
return;
case 'Resolution: For Stack Overflow':
await addComment(
`| :warning: | Issue is a Question |\n` +
`| --- | --- |\n` +
`| :information_source: | We are using GitHub issues exclusively to track bugs in the core React Native library. Please try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) as it is better suited for this type of question. |`,
);
await closeIssue();
return;
case 'Type: Expo':
await addComment(
`| :warning: | Issue is Related to Expo |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue is related to Expo and not React Native core. Please open your issue in [Expo's repository](https://github.com/expo/expo/issues/new). If you are able to create a repro that showcases that this issue is also happening in React Native vanilla, we will be happy to re-open. |`,
);
await closeIssue();
return;
case 'Needs: Issue Template':
await addComment(
`| :warning: | Missing Required Fields |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a [new issue is created](https://github.com/facebook/react-native/issues/new?template=bug_report.md). Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one. |`,
);
await requestAuthorFeedback();
return;
case 'Needs: Environment Info':
await addComment(
`| :warning: | Missing Environment Information |\n` +
`| --- | --- |\n` +
`| :information_source: | Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console. |`,
);
await requestAuthorFeedback();
return;
case 'Needs: Verify on Latest Version':
await addComment(
`| :warning: | Newer Version of React Native is Available! |\n` +
`| --- | --- |\n` +
`| :information_source: | You are on a supported minor version, but it looks like there's a newer patch available. Please [upgrade](https://reactnative.dev/docs/upgrading) to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases. |`,
);
return;
case 'Needs: Version Info':
await addComment(
`| :warning: | Add or Reformat Version Info |\n` +
`| --- | --- |\n` +
`| :information_source: | We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.70.2 |`,
);
await requestAuthorFeedback();
return;
case 'Needs: Repro':
await addComment(
`| :warning: | Missing Reproducible Example |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue is missing a reproducible example. Please provide a [Snack](https://snack.expo.dev) or a repository that demonstrates the issue you are reporting in a [minimal, complete, and reproducible](https://stackoverflow.com/help/minimal-reproducible-example) manner. |`,
);
await requestAuthorFeedback();
return;
case 'Type: Unsupported Version':
await addComment(
`| :warning: | Unsupported Version of React Native |\n` +
`| --- | --- |\n` +
`| :information_source: | It looks like your issue or the example you provided uses an [unsupported version of React Native](https://github.com/reactwg/react-native-releases/blob/main/README.md#releases-support-policy). Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please [upgrade](https://reactnative.dev/docs/upgrading) to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native) to get further community support. |`,
);
await requestAuthorFeedback();
return;
}
};
@@ -1,22 +0,0 @@
name: Apply version label to issue
on:
issues:
types: [opened, edited]
permissions:
contents: read
jobs:
add-version-label-issue:
permissions:
issues: write # for react-native-community/actions-apply-version-label to label issues
runs-on: ubuntu-latest
continue-on-error: true
if: github.repository == 'facebook/react-native'
steps:
- uses: react-native-community/actions-apply-version-label@v0.0.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
required-label: "Type: Upgrade Issue"
-24
View File
@@ -1,24 +0,0 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
permissions:
contents: read
jobs:
rebase:
permissions:
contents: write # for cirrus-actions/rebase to push code to rebase
pull-requests: read # for cirrus-actions/rebase to get info about PR
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-63
View File
@@ -1,63 +0,0 @@
name: Label closed PR as merged and leave a comment
on:
push
permissions:
contents: read
pull-requests: write
jobs:
comment-and-label:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
if(!context.payload.commits || !context.payload.commits.length) return;
const sha = context.payload.commits[0].id;
const {commit, author} = (await github.rest.repos.getCommit({
ref: sha,
owner: context.repo.owner,
repo: context.repo.repo,
})).data;
// Looking at the commit message, checks which PR number, if any, was closed by this commit
const getClosedPrIfExists = (commit) => {
if(!commit || !commit.message) return;
const prClosingRegex = /Closes https:\/\/github.com\/facebook\/react-native\/pull\/([0-9]+)|Pull Request resolved: https:\/\/github.com\/facebook\/react-native\/pull\/([0-9]+)/;
const prClosingMatch = commit.message.match(prClosingRegex);
if(!prClosingMatch || (!prClosingMatch[1] && ! prClosingMatch[2])) return;
return prClosingMatch[1] ?? prClosingMatch[2];
};
const closedPrNumber = getClosedPrIfExists(commit);
if(!closedPrNumber) return;
const pr = (await github.rest.pulls.get({
pull_number: closedPrNumber,
owner: context.repo.owner,
repo: context.repo.repo,
})).data;
// If the PR has already been processed (labeled as Merged), skip it
const mergedLabel = "Merged";
if(pr.labels && pr.labels.some(label => label.name === mergedLabel)) return;
const authorName = author?.login ? `@${author.login}` : commit.author.name;
github.rest.issues.createComment({
issue_number: closedPrNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: `This pull request was successfully merged by ${authorName} in **${sha}**.\n\n<sup>[When will my fix make it into a release?](https://github.com/facebook/react-native/wiki/Release-FAQ#when-will-my-fix-make-it-into-a-release) | [Upcoming Releases](https://github.com/reactwg/react-native-releases/discussions/categories/releases)</sup>`
});
github.rest.issues.addLabels({
issue_number: closedPrNumber,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [mergedLabel]
});
-28
View File
@@ -1,28 +0,0 @@
name: Run Danger on PR
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]
permissions:
actions: write
checks: write
contents: write
issues: write
pull-requests: write
statuses: write
jobs:
danger:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- uses: actions/checkout@v3
- name: Run Yarn Install on Root
run: yarn install
working-directory: .
- name: Danger
run: yarn danger ci --use-github-checks --failOnErrors
working-directory: packages/react-native-bots
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -8
View File
@@ -4,19 +4,12 @@ on:
issue_comment:
types: created
permissions:
contents: read
jobs:
applyNeedsAttentionLabel:
permissions:
contents: read # for actions/checkout to fetch code
issues: write # for hramos/needs-attention to label issues
name: Apply Needs Attention Label
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Apply Needs Attention Label
uses: hramos/needs-attention@v1
with:
+6 -36
View File
@@ -4,43 +4,13 @@ on:
issues:
types: labeled
permissions:
contents: write
issues: write
jobs:
# Runs automatic checks on issues labeled with "Needs: Triage",
# then invokes actOnLabel to react to any added labels
triage-issue:
respondToIssueBasedOnLabel:
name: Respond to Issue Based on Label
runs-on: ubuntu-latest
if: "${{ github.repository == 'facebook/react-native' && contains(github.event.label.name, 'Needs: Triage :mag:') }}"
steps:
- uses: actions/checkout@v3
- uses: actions/github-script@v6
- uses: actions/checkout@v2
- name: Respond to Issue Based on Label
uses: hramos/respond-to-issue-based-on-label@v2
with:
script: |
const verifyVersion = require('./.github/workflows/verifyVersion.js')
const labelToAdd = await verifyVersion(github, context);
if(labelToAdd) {
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [labelToAdd]
})
const actOnLabel = require('./.github/workflows/actOnLabel.js')
await actOnLabel(github, context, labelToAdd)
}
# Reacts to the label that triggered this workflow (added manually or via other workflows)
act-on-label:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- uses: actions/checkout@v3
- uses: actions/github-script@v6
with:
script: |
const actOnLabel = require('./.github/workflows/actOnLabel.js')
await actOnLabel(github, context, context.payload.label.name)
repo-token: ${{ secrets.GITHUB_TOKEN }}
-77
View File
@@ -1,77 +0,0 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0,6,12,18 * * *"
jobs:
stale:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 180
stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
stale-asc:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
ascending: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 180
stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
stale-needs-author-feedback:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
any-of-labels: 'Needs: Author Feedback'
days-before-stale: 24
stale-issue-message: "This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days."
stale-pr-message: "This PR is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days"
close-issue-message: "This issue was closed because the author hasn't provided the requested feedback after 7 days."
close-pr-message: "This PR was closed because the author hasn't provided the requested feedback after 7 days."
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
stale-needs-author-feedback-asc:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
ascending: true
repo-token: ${{ secrets.GITHUB_TOKEN }}
any-of-labels: 'Needs: Author Feedback'
days-before-stale: 24
stale-issue-message: "This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days."
stale-pr-message: "This PR is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days"
close-issue-message: "This issue was closed because the author hasn't provided the requested feedback after 7 days."
close-pr-message: "This PR was closed because the author hasn't provided the requested feedback after 7 days."
exempt-issue-labels: Help Wanted :octocat:, Good first issue, Never gets stale
exempt-pr-labels: Help Wanted :octocat:, Good first issue, Never gets stale
-117
View File
@@ -1,117 +0,0 @@
/**
* 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.
*
* @format
*/
module.exports = async (github, context) => {
const issue = context.payload.issue;
// Ignore issues using upgrade template (they use a special label)
if (issue.labels.find(label => label.name == 'Type: Upgrade Issue')) {
return;
}
const issueVersionUnparsed =
getReactNativeVersionFromIssueBodyIfExists(issue);
const issueVersion = parseVersionFromString(issueVersionUnparsed);
// Nightly versions are always supported
if (reportedVersionIsNightly(issueVersionUnparsed, issueVersion)) return;
if (!issueVersion) {
return 'Needs: Version Info';
}
// Ensure the version matches one we support
const recentReleases = (
await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
})
).data.map(release => release.name);
const latestRelease = (
await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo,
})
).data;
const latestVersion = parseVersionFromString(latestRelease.name);
if (!isVersionSupported(issueVersion, latestVersion)) {
return 'Type: Unsupported Version';
}
// We want to encourage users to repro the issue on the highest available patch for the given minor.
const latestPatchForVersion = getLatestPatchForVersion(
issueVersion,
recentReleases,
);
if (latestPatchForVersion > issueVersion.patch) {
return 'Needs: Verify on Latest Version';
}
};
// We support N-2 minor versions, and the latest major.
function isVersionSupported(actualVersion, latestVersion) {
return (
actualVersion.major >= latestVersion.major &&
actualVersion.minor >= latestVersion.minor - 2
);
}
// Assumes that releases are sorted in the order of recency (i.e. most recent releases are earlier in the list)
// This enables us to stop looking as soon as we find the first release with a matching major/minor version, since
// we know it's the most recent release, therefore the highest patch available.
function getLatestPatchForVersion(version, releases) {
for (releaseName of releases) {
const release = parseVersionFromString(releaseName);
if (
release &&
release.major == version.major &&
release.minor == version.minor
) {
return release.patch;
}
}
}
function getReactNativeVersionFromIssueBodyIfExists(issue) {
if (!issue || !issue.body) return;
const rnVersionRegex = /React Native Version[\r\n]+(?<version>.+)[\r\n]*/;
const rnVersionMatch = issue.body.match(rnVersionRegex);
if (!rnVersionMatch || !rnVersionMatch.groups.version) return;
return rnVersionMatch.groups.version;
}
function reportedVersionIsNightly(unparsedVersionString, version) {
if (!unparsedVersionString && !version) return false;
const nightlyRegex = /nightly/i;
const nightlyMatch = unparsedVersionString.match(nightlyRegex);
const versionIsNightly = nightlyMatch && nightlyMatch[0];
const versionIsZero =
version && version.major == 0 && version.minor == 0 && version.patch == 0;
return versionIsZero || versionIsNightly;
}
function parseVersionFromString(version) {
if (!version) return;
// This will match the standard x.x.x semver format, as well as the non-standard prerelease x.x.x-rc.x
const semverRegex =
/(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(-[rR]{1}[cC]{1}\.(?<prerelease>\d+))?/;
const versionMatch = version.match(semverRegex);
if (!versionMatch) return;
const {major, minor, patch, prerelease} = versionMatch.groups;
return {
major: parseInt(major),
minor: parseInt(minor),
patch: parseInt(patch),
prerelease: parseInt(prerelease),
};
}
+24 -58
View File
@@ -20,36 +20,28 @@ DerivedData
*.ipa
*.xcuserstate
project.xcworkspace
**/.xcode.env.local
# Gradle
/build/
/packages/react-native-codegen/android/build/
/packages/react-native-gradle-plugin/build/
/packages/rn-tester/build
/packages/rn-tester/android/app/.cxx/
/packages/rn-tester/android/app/build/
/packages/rn-tester/android/app/gradle/
/packages/rn-tester/android/app/gradlew
/packages/rn-tester/android/app/gradlew.bat
/packages/react-native/ReactAndroid/build/
/packages/react-native/ReactAndroid/.cxx/
/packages/react-native/ReactAndroid/gradle/
/packages/react-native/ReactAndroid/gradlew
/packages/react-native/ReactAndroid/gradlew.bat
/packages/react-native/ReactAndroid/external-artifacts/build/
/packages/react-native/ReactAndroid/external-artifacts/artifacts/
/packages/react-native/ReactAndroid/hermes-engine/build/
/packages/react-native/ReactAndroid/hermes-engine/.cxx/
/packages/react-native/template/android/app/build/
/packages/react-native/template/android/build/
/ReactAndroid/build/
/ReactAndroid/gradle/
/ReactAndroid/gradlew
/ReactAndroid/gradlew.bat
# Buck
.buckd
buck-out
/.lsp.buckd
/.lsp-buck-out
/packages/react-native/ReactAndroid/src/main/jni/prebuilt/lib/
/packages/react-native/ReactAndroid/src/main/gen
/ReactAndroid/src/main/jni/prebuilt/lib/
/ReactAndroid/src/main/gen
# Android Studio
.project
@@ -64,76 +56,53 @@ buck-out
.gradle
local.properties
*.iml
/packages/react-native/android/*
!/packages/react-native/android/README.md
/android/
# Node
node_modules
*.log
.nvm
/bots/node_modules/
package-lock.json
# OS X
.DS_Store
# Test generated files
/packages/react-native/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
*.js.meta
/coverage
/third-party
# Test Reports
/reports
# Stack Dumps generated when programs crash (Ex. bash.exe.stackdump on Win)
*.stackdump
# Root dir shouldn't have Xcode project
/*.xcodeproj
# ReactCommon subdir shouldn't have Xcode project
/packages/react-native/ReactCommon/**/*.xcodeproj
/ReactCommon/**/*.xcodeproj
/packages/rn-tester/build
/packages/rn-tester/android/app/build/*
# Libs that shouldn't have Xcode project
/packages/react-native/Libraries/FBLazyVector/**/*.xcodeproj
/packages/react-native/Libraries/RCTRequired/**/*.xcodeproj
/packages/react-native/React/CoreModules/**/*.xcodeproj
/packages/react-native/React/FBReactNativeSpec/**/*.xcodeproj
/Libraries/FBLazyVector/**/*.xcodeproj
/Libraries/RCTRequired/**/*.xcodeproj
/React/CoreModules/**/*.xcodeproj
/React/FBReactNativeSpec/**/*.xcodeproj
/packages/react-native-codegen/**/*.xcodeproj
# Ruby Gems (Bundler)
/packages/react-native/vendor
/packages/react-native/template/vendor
.ruby-version
/**/.ruby-version
./vendor/
vendor/
# iOS / CocoaPods
/packages/react-native/template/ios/build/
/packages/react-native/template/ios/Pods/
/packages/react-native/template/ios/Podfile.lock
# CocoaPods
/template/ios/Pods/
/template/ios/Podfile.lock
/packages/rn-tester/Gemfile.lock
# Ignore RNTester specific Pods, but keep the __offline_mirrors__ here.
/packages/rn-tester/Pods/*
!/packages/rn-tester/Pods/__offline_mirrors_hermes__
!/packages/rn-tester/Pods/__offline_mirrors_jsc__
!/packages/rn-tester/Pods/__offline_mirrors__
# @react-native/codegen
/packages/react-native/React/FBReactNativeSpec/FBReactNativeSpec
# react-native-codegen
/React/FBReactNativeSpec/FBReactNativeSpec
/packages/react-native-codegen/lib
/packages/react-native-codegen/tmp/
/packages/react-native/ReactCommon/react/renderer/components/rncore/
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
/**/RCTThirdPartyFabricComponentsProvider.*
# Additional SDKs
/packages/react-native/sdks/download
/packages/react-native/sdks/hermes
/packages/react-native/sdks/hermesc
/ReactCommon/react/renderer/components/rncore/
# Visual studio
.vscode
@@ -141,6 +110,3 @@ vendor/
# Android memory profiler files
*.hprof
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
+4 -4
View File
@@ -1,8 +1,8 @@
{
"arrowParens": "avoid",
"bracketSameLine": true,
"bracketSpacing": false,
"requirePragma": true,
"singleQuote": true,
"trailingComma": "all"
"trailingComma": "all",
"bracketSpacing": false,
"jsxBracketSameLine": true,
"arrowParens": "avoid"
}
+235 -364
View File
File diff suppressed because it is too large Load Diff
-5537
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -67,8 +67,8 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1,
available at https://www.contributor-covenant.org/version/2/1/code_of_conduct/
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
+109 -4
View File
@@ -1,7 +1,112 @@
# Contributing to React Native
Thank you for your interest in contributing to React Native! From commenting on and triaging issues, to reviewing and sending Pull Requests, all contributions are welcome.
We aim to build a vibrant and inclusive [ecosystem of partners, core contributors, and community](ECOSYSTEM.md) that goes beyond the main React Native GitHub repository.
Thank you for your interest in contributing to React Native! From commenting on and triaging issues, to reviewing and sending Pull Requests, all contributions are welcome. We aim to build a vibrant and inclusive [ecosystem of partners, core contributors, and community](ECOSYSTEM.md) that goes beyond the main React Native GitHub repository.
To learn more about how to contribute check out the Contributing section on the React Native website:
* https://reactnative.dev/contributing/overview
The [Open Source Guides](https://opensource.guide/) website has a collection of resources for individuals, communities, and companies who want to learn how to run and contribute to an open source project. Contributors and people new to open source alike will find the following guides especially useful:
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
* [Building Welcoming Communities](https://opensource.guide/building-community/)
### [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md)
As a reminder, all contributors are expected to adhere to the [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md).
## Ways to Contribute
If you are eager to start contributing code right away, we have a list of [good first issues](https://github.com/facebook/react-native/labels/good%20first%20issue) that contain bugs which have a relatively limited scope. As you gain more experience and demonstrate a commitment to evolving React Native, you may be granted issue management permissions in the repository.
There are other ways you can contribute without writing a single line of code. Here are a few things you can do to help out:
1. **Replying and handling open issues.** We get a lot of issues every day, and some of them may lack necessary information. You can help out by guiding people through the process of filling out the issue template, asking for clarifying information, or pointing them to existing issues that match their description of the problem. We cover more about this process in the [Issue Triage wiki](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues).
2. **Reviewing pull requests for the docs.** Reviewing [documentation updates](https://github.com/facebook/react-native-website/pulls) can be as simple as checking for spelling and grammar. If you encounter situations that can be explained better in the docs, click **Edit** at the top of most docs pages to get started with your own contribution.
3. **Help people write test plans.** Some pull requests sent to the main repository may lack a proper test plan. These help reviewers understand how the change was tested, and can speed up the time it takes for a contribution to be accepted.
Each of these tasks is highly impactful, and maintainers will greatly appreciate your help.
### Our Development Process
We use GitHub issues and pull requests to keep track of bug reports and contributions from the community. All changes from engineers at Facebook will sync to [GitHub](https://github.com/facebook/react-native) through a bridge with Facebook's internal source control. Changes from the community are handled through GitHub pull requests. Once a change made on GitHub is approved, it will first be imported into Facebook's internal source control and tested against Facebook's codebase. Once merged at Facebook, the change will eventually sync back to GitHub as a single commit once it has passed Facebook's internal tests.
You can learn more about the contribution process in the following documents:
* [Issues](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues)
* [Pull Requests](https://github.com/facebook/react-native/wiki/Managing-Pull-Requests)
We also have a thriving community of contributors who would be happy to help you get set up. You can reach out to us through [@ReactNative](http://twitter.com/reactnative) (the React Native team) and [@ReactNativeComm](http://twitter.com/reactnativecomm) (the React Native Community organization).
### Repositories
The main repository, <https://github.com/facebook/react-native>, contains the React Native framework itself and it is here where we keep track of bug reports and manage pull requests.
There are a few other repositories you might want to familiarize yourself with:
* **React Native website** which contains the source code for the website, including the documentation, located at <https://github.com/facebook/react-native-website>
* **Releases** are coordinated through the <https://github.com/react-native-community/releases> repository. This includes important documents such as the Changelog.
* **Discussions** about the future of React Native take place in the <https://github.com/react-native-community/discussions-and-proposals> repository.
* **High-quality plugins** for React Native can be found throughout the [React Native Community GitHub Organization](http://github.com/react-native-community/).
Browsing through these repositories should provide some insight into how the React Native open source project is managed.
## GitHub Issues
We use GitHub issues to track bugs exclusively. We have documented our issue handling processes in the [Issues wiki](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues).
### Security Bugs
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue.
## Helping with Documentation
The React Native documentation is hosted as part of the React Native website repository at https://github.com/facebook/react-native-website. The website itself is located at <https://reactnative.dev/> and it is built using [Docusaurus](https://docusaurus.io/). If there's anything you'd like to change in the docs, you can get started by clicking on the "Edit" button located on the upper right of most pages in the website.
If you are adding new functionality or introducing a change in behavior, we will ask you to update the documentation to reflect your changes.
### Contributing to the Blog
The React Native blog is generated [from the Markdown sources for the blog](https://github.com/facebook/react-native-website/tree/HEAD/website/blog).
Please open an issue in the https://github.com/facebook/react-native-website repository or tag us on [@ReactNative on Twitter](http://twitter.com/reactnative) and get the go-ahead from a maintainer before writing an article intended for the React Native blog. In most cases, you might want to share your article on your own blog or writing medium instead. It's worth asking, though, in case we find your article is a good fit for the blog.
We recommend referring to the [CONTRIBUTING](https://github.com/facebook/react-native-website/blob/HEAD/CONTRIBUTING.md) document for the `react-native-website` repository to learn more about contributing to the website in general.
## Contributing Code
Code-level contributions to React Native generally come in the form of [pull requests](https://help.github.com/en/articles/about-pull-requests). These are done by forking the repo and making changes locally. Directly in the repo, there is the [`rn-tester` app](/packages/rn-tester) that you can install on your device (or simulators) and use to test the changes you're making to React Native sources.
The process of proposing a change to React Native can be summarized as follows:
1. Fork the React Native repository and create your branch from `main`.
2. Make the desired changes to React Native sources. Use the `packages/rn-tester` app to test them out.
3. If you've added code that should be tested, add tests.
4. If you've changed APIs, update the documentation, which lives in [another repo](https://github.com/facebook/react-native-website/).
5. Ensure the test suite passes, either locally or on CI once you opened a pull request.
6. Make sure your code lints (for example via `yarn lint --fix`).
7. Push the changes to your fork.
8. Create a pull request to the React Native repository.
9. Review and address comments on your pull request.
1. A bot may comment with suggestions. Generally we ask you to resolve these first before a maintainer will review your code.
2. If changes are requested and addressed, please [request review](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to notify reviewers to take another look.
10. If you haven't already, please complete the [Contributor License Agreement](https://github.com/facebook/react-native/wiki/Contributor-License-Agreement) ("CLA"). **[Complete your CLA here.](https://code.facebook.com/cla)**
If all goes well, your pull request will be merged. If it is not merged, maintainers will do their best to explain the reason why.
### Step-by-step Guide
Whenever you are ready to contribute code, check out our [step-by-step guide to sending your first pull request](https://github.com/facebook/react-native/wiki/How-to-Open-a-Pull-Request), or read the [How to Contribute Code](https://github.com/facebook/react-native/wiki/How-to-Contribute-Code) wiki for more details.
### Tests
Tests help us prevent regressions from being introduced to the codebase. The GitHub repository is continuously tested using Circle and Appveyor, the results of which are available through the Checks functionality on [commits](https://github.com/facebook/react-native/commits/HEAD) and pull requests. You can learn more about running and writing tests in the [Tests wiki](http://github.com/facebook/react-native/wiki/Tests).
## Community Contributions
Contributions to React Native are not limited to GitHub. You can help others by sharing your experience using React Native, whether that is through blog posts, presenting talks at conferences, or simply sharing your thoughts on Twitter and tagging @ReactNative.
## Where to Get Help
As you work on React Native, it is natural that sooner or later you may require help. In addition to the resources listed in [SUPPORT](.github/SUPPORT.md), people interested in contributing may take advantage of the following:
* **Twitter**. The React Native team at Facebook has its own account at [@reactnative](https://twitter.com/reactnative), and the React Native Community uses [@reactnativecomm](https://twitter.com/reactnativecomm). If you feel stuck, or need help contributing, please do not hesitate to reach out.
* **Proposals Repository**. If you are considering working on a feature large in scope, consider [creating a proposal first](https://github.com/react-native-community/discussions-and-proposals). The community can help you figure out the right approach, and we'd be happy to help.
* **React Native Community Discord**. While we try to hold most discussions in public, sometimes it can be beneficial to have conversations in real time with other contributors. People who have demonstrated a commitment to moving React Native forward through sustained contributions to the project may eventually be invited to join the React Native Community Discord.
+14 -29
View File
@@ -1,36 +1,27 @@
# The React Native Ecosystem
We aim to build a vibrant and inclusive ecosystem of partners, core contributors, and community that goes beyond the main React Native GitHub repository. This document explains the roles and responsibilities of various stakeholders and provides guidelines for the community organization. The structure outlined in this document has been in place for a while but had not been written down before.
We aim to build a vibrant and inclusive ecosystem of partners, core contributors, and community that goes beyond the main React Native GitHub repository. This document explains the roles and responsibilities of various stakeholders and provides guidelines for the community organization. The structure outlined in this document has been in place for a while but hadn't been written down before.
There are three types of stakeholders:
* **Partners:** Companies significantly invested in React Native and take responsibility for the React Native vision and community.
* **Partners:** Companies that are significantly invested in React Native and have been for years.
* **Core Contributors:** Individual people who contribute to the React Native project.
* **Community Contributors:** Individuals who support projects in the [react-native-community](https://github.com/react-native-community) organization.
## Partners
Partners are companies that are significantly invested in React Native and demonstrate ownership. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Examples of this may include large scale contributions to `react-native` or owning essential tools or libraries.
Partners are companies that are significantly invested in React Native and have been for years. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Partners think of React Native as a product: they understand the trade offs that the project makes as well as future plans and goals. Together we shape the vision for React Native to make it the best way to build applications.
Partners think of React Native as a product; they understand the trade offs that the project makes as well as future plans and goals. Together we shape the vision for React Native to make it the best way to build applications.
React Native's current set of partners include Callstack, Expo, Facebook, Infinite Red, Microsoft and Software Mansion. Many engineers from these companies are core contributors, and their partner responsibilities also include:
Baseline responsibilities of partners include:
* Attending monthly meeting
* Contributing to the release process. Examples include being a [community releaser](https://reactnative.dev/contributing/release-roles-responsibilities#release-role-2-community-releaser), testing new releases, technical support for release issues.
* Engage on the core contributor Discord
* **[Callstack](https://callstack.com/):** Manages releases, maintains the [React Native CLI](https://github.com/react-native-community/react-native-cli) and organizes [React Native EU](https://react-native.eu/)
* **[Expo](https://expo.io/):** Builds [expo](https://github.com/expo/expo) on top of React Native to simplify app development
* **[Facebook](https://opensource.facebook.com):** Oversees the React Native product and maintains the [React Native core repo](https://reactnative.dev/)
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://cr.infinite.red/)
* **[Microsoft](http://aka.ms/reactnative):** Develops [React Native Windows](https://github.com/Microsoft/react-native-windows) and [React Native macOS](https://github.com/microsoft/react-native-macos) for building apps that target Windows and macOS
* **[Software Mansion](https://swmansion.com/):** Maintain core infrastructure including JSC, Animated, and other popular third-party plugins.
Our current partners and other areas of ownership:
* **[Coinbase](https://www.coinbase.com/):** Publishes [posts](https://blog.coinbase.com/tagged/react-native) advocating React Native usage. Supports `@react-native-community/datetimepicker` and other community modules to migrate to the new architecture. Supports releases in testing and feedback.
* **[Callstack](https://callstack.com/):** Maintains the [React Native CLI](https://github.com/react-native-community/react-native-cli) and [other community libraries](https://github.com/callstack), organizes [React Native EU](https://react-native.eu/) and hosts [The React Native Show podcast](https://www.callstack.com/podcast-react-native-show)
* **[Expo](https://expo.dev/):** Builds [Expo Go and SDK](https://github.com/expo/expo), [Snack](https://snack.expo.dev/), and [Expo Application Services](https://expo.dev/eas). Maintains [React Native Directory](https://reactnative.directory/), stewards [React Navigation](https://reactnavigation.org/) along with other partners.
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://cr.infinite.red/), hosts the [React Native Radio podcast](https://reactnativeradio.com), publishes the [React Native Newsletter](https://reactnativenewsletter.com)
* **[Meta](https://opensource.fb.com/):** Oversees the React Native product and maintains the [React Native core repo](https://reactnative.dev/)
* **[Microsoft](https://twitter.com/ReactNativeMSFT):** Develops [React Native Windows](https://github.com/Microsoft/react-native-windows) and [React Native macOS](https://github.com/microsoft/react-native-macos) for building apps that target Windows and macOS; maintains [rnx-kit](https://github.com/microsoft/rnx-kit), [react-native-test-app](https://github.com/microsoft/react-native-test-app) and coordinates cross-companies efforts such as the [bundle working group](https://github.com/microsoft/rnx-kit/discussions/categories/bundle-working-group).
* **[Shopify](https://www.shopify.com/):** Maintains React Native open source libraries such as [flash-list](https://github.com/Shopify/flash-list) or [@shopify/react-native-skia](https://github.com/Shopify/react-native-skia) and sponsors Software Mansion.
* **[Software Mansion](https://swmansion.com/):** Maintain core infrastructure including JSC, Animated, and other popular third-party plugins and organizes [App.js Conf](https://appjs.co/)
* **[Wix.com](https://wix.engineering/open-source):** Maintains a variety of React Native open source projects ([see all](https://github.com/orgs/wix/repositories?q=react-native)), including: [Detox](https://wix.github.io/Detox/) end-to-end testing library for React Native apps, [RN UILib](https://wix.github.io/react-native-ui-lib/), [RN Navigation](https://wix.github.io/react-native-navigation/), [RN Calendars](https://wix.github.io/react-native-calendars/) and [RN Notifications](https://github.com/wix/react-native-notifications).
This list may fluctuate in response to newcomers who meet our partner definition. In terms of open source work, pull requests from partners are commonly prioritized. When you are contributing to React Native, you'll most likely meet somebody who works at one of the partner companies and who is a core contributor:
In terms of open source work, pull requests from partners are commonly prioritized. When you are contributing to React Native, you'll most likely meet somebody who works at one of the partner companies and who is a core contributor:
## Core Contributors
@@ -47,19 +38,13 @@ Core contributors are individuals who contribute to the React Native project. A
These are behaviors we have observed in our existing core contributors. They aren't strict rules but rather outline their usual responsibilities. We do not expect every core contributor to do all of the above things all the time. Most importantly, we want to create a supportive and friendly environment that fosters collaboration. Above all else, **we are always polite and friendly.**
Core contributor status is attained after consistently contributing and taking on the responsibilities outlined above and granted by other core contributors. Similarly, after a long period of inactivity (~6 months or more), a core contributor may be contacted to understand if theyre still interested in being part of the program.
Core contributor status is attained after consistently contributing and taking on the responsibilities outlined above and granted by other core contributors. Similarly, after a long period of inactivity, a core contributor may be removed.
You can use this [form](https://forms.gle/4jpA4QeNUvAUDnNe8) to either:
* Apply yourself to become a Core Contributor. Make sure to include a list of valuable contributions you did to the React Native repository and ecosystem.
* Nominate someone to become a Core Contributor.
As a core contributor, you will have access to the core contributor Discord which is used for light-weight coordination and discussion.
**We aim to make contributing to React Native as easy and transparent as possible.** We have discussion groups dedicated to the [new architecture rollout](https://github.com/reactwg/react-native-new-architecture), [releases](https://github.com/reactwg/react-native-releases), and [general questions and proposals](https://github.com/react-native-community/discussions-and-proposals). We are always looking for active, enthusiastic members of the React Native community to become core contributors.
We aim to make contributing to React Native as easy and transparent as possible. All important topics are handled through a [discussion or RFC process on GitHub](https://github.com/react-native-community/discussions-and-proposals). We are always looking for active, enthusiastic members of the React Native community to become core contributors.
## Community Contributors
Community contributors are individuals who support projects in the [react-native-community](https://github.com/react-native-community) organization. This organization exists as an incubator for high quality components that extend the capabilities of React Native with functionality that many but not all applications require. Meta engineers will provide guidance to help build a vibrant community of people and components that make React Native better.
Community contributors are individuals who support projects in the [react-native-community](https://github.com/react-native-community) organization. This organization exists as an incubator for high quality components that extend the capabilities of React Native with functionality that many but not all applications require. Facebook engineers will provide guidance to help build a vibrant community of people and components that make React Native better.
This structure has multiple benefits:
-7
View File
@@ -1,7 +0,0 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
gem 'cocoapods', '~> 1.12'
gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'
-103
View File
@@ -1,103 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.7)
base64
nkf
rexml
activesupport (7.0.8.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
base64 (0.2.0)
claide (1.1.0)
cocoapods (1.15.2)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.15.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.23.0, < 2.0)
cocoapods-core (1.15.2)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (2.1)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.2.3)
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
ffi (1.16.3)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.8.3)
i18n (1.14.4)
concurrent-ruby (~> 1.0)
json (2.7.1)
minitest (5.22.2)
molinillo (0.8.0)
nanaimo (0.3.0)
nap (1.1.0)
netrc (0.11.0)
nkf (0.2.0)
public_suffix (4.0.7)
rexml (3.2.6)
ruby-macho (2.5.1)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
xcodeproj (1.24.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
PLATFORMS
ruby
DEPENDENCIES
activesupport (>= 6.1.7.3, < 7.1.0)
cocoapods (~> 1.12)
RUBY VERSION
ruby 3.0.6p216
BUNDLED WITH
2.4.13
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -16,7 +16,7 @@ import * as React from 'react';
const {TestModule} = NativeModules;
class AccessibilityManagerTest extends React.Component<{...}> {
componentDidMount(): void {
componentDidMount() {
invariant(
NativeAccessibilityManager,
"NativeAccessibilityManager doesn't exist",
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
+243
View File
@@ -0,0 +1,243 @@
/**
* 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
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {AsyncStorage, Text, View, StyleSheet} = ReactNative;
const {TestModule} = ReactNative.NativeModules;
const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer');
const nullthrows = require('nullthrows');
const DEBUG = false;
const KEY_1 = 'key_1';
const VAL_1 = 'val_1';
const KEY_2 = 'key_2';
const VAL_2 = 'val_2';
const KEY_MERGE = 'key_merge';
const VAL_MERGE_1 = {foo: 1, bar: {hoo: 1, boo: 1}, moo: {a: 3}};
const VAL_MERGE_2 = {bar: {hoo: 2}, baz: 2, moo: {a: 3}};
const VAL_MERGE_EXPECT = {foo: 1, bar: {hoo: 2, boo: 1}, baz: 2, moo: {a: 3}};
// setup in componentDidMount
let done = (result: ?boolean) => {};
let updateMessage = (message: string) => {};
function runTestCase(description: string, fn) {
updateMessage(description);
fn();
}
function expectTrue(condition: boolean, message: string) {
if (!condition) {
throw new Error(message);
}
}
// Type-safe wrapper around JSON.stringify
function stringify(
value:
| void
| null
| string
| number
| boolean
| {...}
| $ReadOnlyArray<mixed>,
): string {
if (typeof value === 'undefined') {
return 'undefined';
}
return JSON.stringify(value);
}
function expectEqual(lhs, rhs, testname: string) {
expectTrue(
!deepDiffer(lhs, rhs),
'Error in test ' +
testname +
': expected\n' +
stringify(rhs) +
'\ngot\n' +
stringify(lhs),
);
}
function expectAsyncNoError(place, err) {
if (err instanceof Error) {
err = err.message;
}
expectTrue(
err === null,
'Unexpected error in ' + place + ': ' + stringify(err),
);
}
function testSetAndGet() {
AsyncStorage.setItem(KEY_1, VAL_1, err1 => {
expectAsyncNoError('testSetAndGet/setItem', err1);
AsyncStorage.getItem(KEY_1, (err2, result) => {
expectAsyncNoError('testSetAndGet/getItem', err2);
expectEqual(result, VAL_1, 'testSetAndGet setItem');
updateMessage('get(key_1) correctly returned ' + String(result));
runTestCase('should get null for missing key', testMissingGet);
});
});
}
function testMissingGet() {
AsyncStorage.getItem(KEY_2, (err, result) => {
expectAsyncNoError('testMissingGet/setItem', err);
expectEqual(result, null, 'testMissingGet');
updateMessage('missing get(key_2) correctly returned ' + String(result));
runTestCase('check set twice results in a single key', testSetTwice);
});
}
function testSetTwice() {
AsyncStorage.setItem(KEY_1, VAL_1, () => {
AsyncStorage.setItem(KEY_1, VAL_1, () => {
AsyncStorage.getItem(KEY_1, (err, result) => {
expectAsyncNoError('testSetTwice/setItem', err);
expectEqual(result, VAL_1, 'testSetTwice');
updateMessage('setTwice worked as expected');
runTestCase('test removeItem', testRemoveItem);
});
});
});
}
function testRemoveItem() {
AsyncStorage.setItem(KEY_1, VAL_1, () => {
AsyncStorage.setItem(KEY_2, VAL_2, () => {
AsyncStorage.getAllKeys((err, result) => {
expectAsyncNoError('testRemoveItem/getAllKeys', err);
expectTrue(
nullthrows(result).indexOf(KEY_1) >= 0 &&
nullthrows(result).indexOf(KEY_2) >= 0,
'Missing KEY_1 or KEY_2 in ' + '(' + nullthrows(result).join() + ')',
);
updateMessage('testRemoveItem - add two items');
AsyncStorage.removeItem(KEY_1, err2 => {
expectAsyncNoError('testRemoveItem/removeItem', err2);
updateMessage('delete successful ');
AsyncStorage.getItem(KEY_1, (err3, result2) => {
expectAsyncNoError('testRemoveItem/getItem', err3);
expectEqual(
result2,
null,
'testRemoveItem: key_1 present after delete',
);
updateMessage('key properly removed ');
AsyncStorage.getAllKeys((err4, result3) => {
expectAsyncNoError('testRemoveItem/getAllKeys', err4);
expectTrue(
nullthrows(result3).indexOf(KEY_1) === -1,
'Unexpected: KEY_1 present in ' + nullthrows(result3).join(),
);
updateMessage('proper length returned.');
runTestCase('should merge values', testMerge);
});
});
});
});
});
});
}
function testMerge() {
AsyncStorage.setItem(KEY_MERGE, stringify(VAL_MERGE_1), err1 => {
expectAsyncNoError('testMerge/setItem', err1);
AsyncStorage.mergeItem(KEY_MERGE, stringify(VAL_MERGE_2), err2 => {
expectAsyncNoError('testMerge/mergeItem', err2);
AsyncStorage.getItem(KEY_MERGE, (err3, result) => {
expectAsyncNoError('testMerge/setItem', err3);
expectEqual(
JSON.parse(nullthrows(result)),
VAL_MERGE_EXPECT,
'testMerge',
);
updateMessage('objects deeply merged\nDone!');
runTestCase('multi set and get', testOptimizedMultiGet);
});
});
});
}
function testOptimizedMultiGet() {
let batch = [
[KEY_1, VAL_1],
[KEY_2, VAL_2],
];
let keys = batch.map(([key, value]) => key);
AsyncStorage.multiSet(batch, err1 => {
// yes, twice on purpose
[1, 2].forEach(i => {
expectAsyncNoError(`${i} testOptimizedMultiGet/multiSet`, err1);
AsyncStorage.multiGet(keys, (err2, result) => {
expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2);
expectEqual(result, batch, `${i} testOptimizedMultiGet multiGet`);
updateMessage(
'multiGet([key_1, key_2]) correctly returned ' + stringify(result),
);
done();
});
});
});
}
class AsyncStorageTest extends React.Component<{...}, $FlowFixMeState> {
state: any | {|done: boolean, messages: string|} = {
messages: 'Initializing...',
done: false,
};
componentDidMount() {
done = () =>
this.setState({done: true}, () => {
TestModule.markTestCompleted();
});
updateMessage = msg => {
this.setState({messages: this.state.messages.concat('\n' + msg)});
DEBUG && console.log(msg);
};
AsyncStorage.clear(testSetAndGet);
}
render(): React.Node {
return (
<View style={styles.container}>
<Text>
{/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_
* native_oss) This comment suppresses an error found when Flow v0.54
* was deployed. To see the error delete this comment and run Flow.
*/
this.constructor.displayName + ': '}
{this.state.done ? 'Done' : 'Testing...'}
{'\n\n' + this.state.messages}
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
padding: 40,
},
});
AsyncStorageTest.displayName = 'AsyncStorageTest';
module.exports = AsyncStorageTest;
+3 -5
View File
@@ -1,4 +1,4 @@
load("@fbsource//tools/build_defs:js_library_glob.bzl", "js_library_glob")
load("@fbsource//tools/build_defs:js_glob.bzl", "js_glob")
load("@fbsource//tools/build_defs/oss:metro_defs.bzl", "rn_library")
# This file was generated by running
@@ -6,7 +6,7 @@ load("@fbsource//tools/build_defs/oss:metro_defs.bzl", "rn_library")
rn_library(
name = "IntegrationTests",
srcs = js_library_glob(
srcs = js_glob(
[
"**/*",
],
@@ -17,9 +17,7 @@ rn_library(
"websocket_integration_test_server.js",
],
),
labels = [
"pfh:ReactNative_CommonInfrastructurePlaceholder",
],
labels = ["supermodule:xplat/default/public.react_native.tests"],
skip_processors = True,
visibility = ["PUBLIC"],
deps = [
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -20,7 +20,7 @@ const {View} = ReactNative;
const {TestModule} = ReactNative.NativeModules;
class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
componentDidMount(): void {
componentDidMount() {
if (typeof global.globalEvalWithSourceUrl !== 'function') {
throw new Error(
'Expected to find globalEvalWithSourceUrl function on global object but found ' +
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -16,7 +16,7 @@ const {Image} = ReactNative;
const {TestModule} = ReactNative.NativeModules;
class ImageSnapshotTest extends React.Component<{...}> {
componentDidMount(): void {
componentDidMount() {
if (!TestModule.verifySnapshot) {
throw new Error('TestModule.verifySnapshot not defined.');
}
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -56,14 +56,11 @@ class IntegrationTestHarnessTest extends React.Component<Props, State> {
return (
<View style={styles.container}>
<Text>
{
/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_
* native_oss) This comment suppresses an error found when Flow v0.54
* was deployed. To see the error delete this comment and run Flow.
*/
// $FlowFixMe[unsafe-addition]
this.constructor.displayName + ': '
}
{/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_
* native_oss) This comment suppresses an error found when Flow v0.54
* was deployed. To see the error delete this comment and run Flow.
*/
this.constructor.displayName + ': '}
{this.state.done ? 'Done' : 'Testing...'}
</Text>
</View>
+12 -5
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -13,13 +13,20 @@
require('react-native/Libraries/Core/InitializeCore');
const React = require('react');
const ReactNative = require('react-native');
const {AppRegistry, ScrollView, StyleSheet, Text, TouchableOpacity, View} =
ReactNative;
const {
AppRegistry,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} = ReactNative;
// Keep this list in sync with RNTesterIntegrationTests.m
const TESTS = [
require('./IntegrationTestHarnessTest'),
require('./TimersTest'),
require('./AsyncStorageTest'),
require('./LayoutEventsTest'),
require('./AppEventsTest'),
require('./SimpleSnapshotTest'),
@@ -45,11 +52,11 @@ require('./LoggingTestModule');
type Test = any;
class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
state: {test: ?Test} = {
state = {
test: (null: ?Test),
};
render(): React.Node {
render() {
if (this.state.test) {
return (
<ScrollView>
+2 -5
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -19,7 +19,7 @@ import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer');
function debug(...args: Array<void | Layout | string>) {
function debug(...args) {
// console.log.apply(null, arguments);
}
@@ -119,19 +119,16 @@ class LayoutEventsTest extends React.Component<Props, State> {
}
onViewLayout: (e: LayoutEvent) => void = (e: LayoutEvent) => {
// $FlowFixMe[incompatible-call]
debug('received view layout event\n', e.nativeEvent);
this.setState({viewLayout: e.nativeEvent.layout}, this.checkLayout);
};
onTextLayout: (e: LayoutEvent) => void = (e: LayoutEvent) => {
// $FlowFixMe[incompatible-call]
debug('received text layout event\n', e.nativeEvent);
this.setState({textLayout: e.nativeEvent.layout}, this.checkLayout);
};
onImageLayout: (e: LayoutEvent) => void = (e: LayoutEvent) => {
// $FlowFixMe[incompatible-call]
debug('received image layout event\n', e.nativeEvent);
this.setState({imageLayout: e.nativeEvent.layout}, this.checkLayout);
};
+8 -8
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -14,24 +14,24 @@ const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridg
const invariant = require('invariant');
const LoggingTestModule = {
logToConsole: function (str) {
logToConsole: function(str) {
console.log(str);
},
logToConsoleAfterWait: function (str, timeout_ms) {
setTimeout(function () {
logToConsoleAfterWait: function(str, timeout_ms) {
setTimeout(function() {
console.log(str);
}, timeout_ms);
},
warning: function (str) {
warning: function(str) {
console.warn(str);
},
invariant: function (str) {
invariant: function(str) {
invariant(false, str);
},
logErrorToConsole: function (str) {
logErrorToConsole: function(str) {
console.error(str);
},
throwError: function (str) {
throwError: function(str) {
throw new Error(str);
},
};
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -12,8 +12,14 @@
const React = require('react');
const ReactNative = require('react-native');
const {AppRegistry, ScrollView, StyleSheet, Text, TouchableOpacity, View} =
ReactNative;
const {
AppRegistry,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} = ReactNative;
/* Keep this list in sync with RCTRootViewIntegrationTests.m */
const TESTS = [
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -17,7 +17,7 @@ const {StyleSheet, View} = ReactNative;
const {TestModule} = ReactNative.NativeModules;
class SimpleSnapshotTest extends React.Component<{...}> {
componentDidMount(): void {
componentDidMount() {
if (!TestModule.verifySnapshot) {
throw new Error('TestModule.verifySnapshot not defined.');
}
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -17,7 +17,7 @@ const {View} = ReactNative;
const {TestModule, RNTesterTestModule} = ReactNative.NativeModules;
class SyncMethodTest extends React.Component<{...}> {
componentDidMount(): void {
componentDidMount() {
if (
RNTesterTestModule.echoString('test string value') !== 'test string value'
) {
+5 -5
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -39,7 +39,7 @@ class TimersTest extends React.Component<Props, State> {
};
setTimeout(fn: () => void, time: number): TimeoutID {
const id: TimeoutID = setTimeout(() => {
const id = setTimeout(() => {
this._timeoutIDs.delete(id);
fn();
}, time);
@@ -70,7 +70,7 @@ class TimersTest extends React.Component<Props, State> {
}
setImmediate(fn: () => void): ImmediateID {
const id: any = setImmediate(() => {
const id = setImmediate(() => {
this._immediateIDs.delete(id);
fn();
});
@@ -81,7 +81,7 @@ class TimersTest extends React.Component<Props, State> {
}
requestAnimationFrame(fn: () => void): AnimationFrameID {
const id: AnimationFrameID = requestAnimationFrame(() => {
const id = requestAnimationFrame(() => {
this._animationFrameIDs.delete(id);
fn();
});
@@ -253,7 +253,7 @@ class TimersTest extends React.Component<Props, State> {
);
}
_incrementInterval(): void {
_incrementInterval() {
if (this.state.count > 3) {
throw new Error('interval incremented past end.');
}
+5 -5
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -45,7 +45,7 @@ class WebSocketTest extends React.Component<{...}, State> {
_waitFor = (condition: any, timeout: any, callback: any) => {
let remaining = timeout;
const timeoutFunction = function () {
const timeoutFunction = function() {
if (condition()) {
callback(true);
return;
@@ -69,11 +69,11 @@ class WebSocketTest extends React.Component<{...}, State> {
});
};
_socketIsConnected = (): boolean => {
_socketIsConnected = () => {
return this.state.socketState === 1; //'OPEN'
};
_socketIsDisconnected = (): boolean => {
_socketIsDisconnected = () => {
return this.state.socketState === 3; //'CLOSED'
};
@@ -106,7 +106,7 @@ class WebSocketTest extends React.Component<{...}, State> {
this._sendText(this.state.testMessage);
};
_receivedTestExpectedResponse = (): boolean => {
_receivedTestExpectedResponse = () => {
return this.state.lastMessage === this.state.testExpectedResponse;
};
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# 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.
# Set terminal title
echo -en "\033]0;Web Socket Test Server\a"
clear
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
pushd "$THIS_DIR"
./websocket_integration_test_server.js
popd
echo "Process terminated. Press <enter> to close the window"
read
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
# 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.
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
pushd "$THIS_DIR" || exit
./websocket_integration_test_server.js
popd || exit
echo "Process terminated."
@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -8,18 +8,17 @@
* @format
*/
import type {ProcessedColorValue} from '../StyleSheet/processColor';
import type {ColorValue} from '../StyleSheet/StyleSheet';
import RCTActionSheetManager from './NativeActionSheetManager';
const processColor = require('../StyleSheet/processColor').default;
const invariant = require('invariant');
const processColor = require('../StyleSheet/processColor');
import type {ColorValue} from '../StyleSheet/StyleSheet';
import type {ProcessedColorValue} from '../StyleSheet/processColor';
/**
* Display action sheets and share sheets on iOS.
*
* See https://reactnative.dev/docs/actionsheetios
* See https://reactnative.dev/docs/actionsheetios.html
*/
const ActionSheetIOS = {
/**
@@ -37,7 +36,7 @@ const ActionSheetIOS = {
* The 'callback' function takes one parameter, the zero-based index
* of the selected item.
*
* See https://reactnative.dev/docs/actionsheetios#showactionsheetwithoptions
* See https://reactnative.dev/docs/actionsheetios.html#showactionsheetwithoptions
*/
showActionSheetWithOptions(
options: {|
@@ -48,7 +47,6 @@ const ActionSheetIOS = {
+cancelButtonIndex?: ?number,
+anchor?: ?number,
+tintColor?: ColorValue | ProcessedColorValue,
+cancelButtonTintColor?: ColorValue | ProcessedColorValue,
+userInterfaceStyle?: string,
+disabledButtonIndices?: Array<number>,
|},
@@ -61,12 +59,7 @@ const ActionSheetIOS = {
invariant(typeof callback === 'function', 'Must provide a valid callback');
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
const {
tintColor,
cancelButtonTintColor,
destructiveButtonIndex,
...remainingOptions
} = options;
const {tintColor, destructiveButtonIndex, ...remainingOptions} = options;
let destructiveButtonIndices = null;
if (Array.isArray(destructiveButtonIndex)) {
@@ -76,21 +69,14 @@ const ActionSheetIOS = {
}
const processedTintColor = processColor(tintColor);
const processedCancelButtonTintColor = processColor(cancelButtonTintColor);
invariant(
processedTintColor == null || typeof processedTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor',
);
invariant(
processedCancelButtonTintColor == null ||
typeof processedCancelButtonTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions cancelButtonTintColor',
);
RCTActionSheetManager.showActionSheetWithOptions(
{
...remainingOptions,
tintColor: processedTintColor,
cancelButtonTintColor: processedCancelButtonTintColor,
destructiveButtonIndices,
},
callback,
@@ -118,7 +104,7 @@ const ActionSheetIOS = {
* - a boolean value signifying success or failure
* - a string that, in the case of success, indicates the method of sharing
*
* See https://reactnative.dev/docs/actionsheetios#showshareactionsheetwithoptions
* See https://reactnative.dev/docs/actionsheetios.html#showshareactionsheetwithoptions
*/
showShareActionSheetWithOptions(
options: Object,
@@ -144,13 +130,6 @@ const ActionSheetIOS = {
successCallback,
);
},
dismissActionSheet: () => {
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
if (typeof RCTActionSheetManager.dismissActionSheet === 'function') {
RCTActionSheetManager.dismissActionSheet();
}
},
};
module.exports = ActionSheetIOS;
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -9,7 +9,6 @@
*/
import type {TurboModule} from '../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
@@ -23,7 +22,6 @@ export interface Spec extends TurboModule {
+cancelButtonIndex?: ?number,
+anchor?: ?number,
+tintColor?: ?number,
+cancelButtonTintColor?: ?number,
+userInterfaceStyle?: ?string,
+disabledButtonIndices?: Array<number>,
|},
@@ -36,7 +34,6 @@ export interface Spec extends TurboModule {
+subject?: ?string,
+anchor?: ?number,
+tintColor?: ?number,
+cancelButtonTintColor?: ?number,
+excludedActivityTypes?: ?Array<string>,
+userInterfaceStyle?: ?string,
|},
@@ -48,7 +45,6 @@ export interface Spec extends TurboModule {
|}) => void,
successCallback: (completed: boolean, activityType: ?string) => void,
) => void;
+dismissActionSheet?: () => void;
}
export default (TurboModuleRegistry.get<Spec>('ActionSheetManager'): ?Spec);
@@ -1,4 +1,4 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# 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.
@@ -23,8 +23,8 @@ Pod::Spec.new do |s|
s.homepage = "https://reactnative.dev/"
s.documentation_url = "https://reactnative.dev/docs/actionsheetios"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = { :ios => "12.4" }
s.author = "Facebook, Inc. and its affiliates"
s.platforms = { :ios => "11.0" }
s.source = source
s.source_files = "*.{m}"
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -8,9 +8,8 @@
* @flow
*/
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
import Platform from '../Utilities/Platform';
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
import RCTAlertManager from './RCTAlertManager';
export type AlertType =
@@ -22,14 +21,12 @@ export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
export type Buttons = Array<{
text?: string,
onPress?: ?Function,
isPreferred?: boolean,
style?: AlertButtonStyle,
...
}>;
type Options = {
cancelable?: ?boolean,
userInterfaceStyle?: 'unspecified' | 'light' | 'dark',
onDismiss?: ?() => void,
...
};
@@ -37,7 +34,7 @@ type Options = {
/**
* Launches an alert dialog with the specified title and message.
*
* See https://reactnative.dev/docs/alert
* See https://reactnative.dev/docs/alert.html
*/
class Alert {
static alert(
@@ -47,18 +44,10 @@ class Alert {
options?: Options,
): void {
if (Platform.OS === 'ios') {
Alert.prompt(
title,
message,
buttons,
'default',
undefined,
undefined,
options,
);
Alert.prompt(title, message, buttons, 'default');
} else if (Platform.OS === 'android') {
const NativeDialogManagerAndroid =
require('../NativeModules/specs/NativeDialogManagerAndroid').default;
const NativeDialogManagerAndroid = require('../NativeModules/specs/NativeDialogManagerAndroid')
.default;
if (!NativeDialogManagerAndroid) {
return;
}
@@ -93,8 +82,6 @@ class Alert {
config.buttonPositive = buttonPositive.text || defaultPositiveText;
}
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by
* Flow's LTI update could not be added via codemod */
const onAction = (action, buttonKey) => {
if (action === constants.buttonClicked) {
if (buttonKey === constants.buttonNeutral) {
@@ -108,7 +95,7 @@ class Alert {
options && options.onDismiss && options.onDismiss();
}
};
const onError = (errorMessage: string) => console.warn(errorMessage);
const onError = errorMessage => console.warn(errorMessage);
NativeDialogManagerAndroid.showAlert(config, onError, onAction);
}
}
@@ -120,14 +107,12 @@ class Alert {
type?: ?AlertType = 'plain-text',
defaultValue?: string,
keyboardType?: string,
options?: Options,
): void {
if (Platform.OS === 'ios') {
let callbacks: Array<?any> = [];
let callbacks = [];
const buttons = [];
let cancelButtonKey;
let destructiveButtonKey;
let preferredButtonKey;
if (typeof callbackOrButtons === 'function') {
callbacks = [callbackOrButtons];
} else if (Array.isArray(callbackOrButtons)) {
@@ -138,11 +123,8 @@ class Alert {
} else if (btn.style === 'destructive') {
destructiveButtonKey = String(index);
}
if (btn.isPreferred) {
preferredButtonKey = String(index);
}
if (btn.text || index < (callbackOrButtons || []).length - 1) {
const btnDef: {[number]: string} = {};
const btnDef = {};
btnDef[index] = btn.text || '';
buttons.push(btnDef);
}
@@ -158,9 +140,7 @@ class Alert {
defaultValue,
cancelButtonKey,
destructiveButtonKey,
preferredButtonKey,
keyboardType,
userInterfaceStyle: options?.userInterfaceStyle || undefined,
},
(id, value) => {
const cb = callbacks[id];
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -9,7 +9,6 @@
*/
import type {TurboModule} from '../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
export type Args = {|
@@ -20,9 +19,7 @@ export type Args = {|
defaultValue?: string,
cancelButtonKey?: string,
destructiveButtonKey?: string,
preferredButtonKey?: string,
keyboardType?: string,
userInterfaceStyle?: string,
|};
export interface Spec extends TurboModule {
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -12,7 +12,7 @@ import NativeDialogManagerAndroid from '../NativeModules/specs/NativeDialogManag
function emptyCallback() {}
module.exports = {
alertWithArgs: function (args, callback) {
alertWithArgs: function(args, callback) {
// TODO(5998984): Polyfill it correctly with DialogManagerAndroid
if (!NativeDialogManagerAndroid) {
return;
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -8,9 +8,8 @@
* @flow strict-local
*/
import type {Args} from './NativeAlertManager';
import NativeAlertManager from './NativeAlertManager';
import type {Args} from './NativeAlertManager';
module.exports = {
alertWithArgs(
+46
View File
@@ -0,0 +1,46 @@
/**
* 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
*/
import Platform from '../Utilities/Platform';
import typeof AnimatedFlatList from './components/AnimatedFlatList';
import typeof AnimatedImage from './components/AnimatedImage';
import typeof AnimatedScrollView from './components/AnimatedScrollView';
import typeof AnimatedSectionList from './components/AnimatedSectionList';
import typeof AnimatedText from './components/AnimatedText';
import typeof AnimatedView from './components/AnimatedView';
const AnimatedMock = require('./AnimatedMock');
const AnimatedImplementation = require('./AnimatedImplementation');
const Animated = ((Platform.isTesting
? AnimatedMock
: AnimatedImplementation): typeof AnimatedMock);
module.exports = {
get FlatList(): AnimatedFlatList {
return require('./components/AnimatedFlatList');
},
get Image(): AnimatedImage {
return require('./components/AnimatedImage');
},
get ScrollView(): AnimatedScrollView {
return require('./components/AnimatedScrollView');
},
get SectionList(): AnimatedSectionList {
return require('./components/AnimatedSectionList');
},
get Text(): AnimatedText {
return require('./components/AnimatedText');
},
get View(): AnimatedView {
return require('./components/AnimatedView');
},
...Animated,
};
+234
View File
@@ -0,0 +1,234 @@
/**
* 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';
const AnimatedValue = require('./nodes/AnimatedValue');
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
const ReactNative = require('../Renderer/shims/ReactNative');
const invariant = require('invariant');
const {shouldUseNativeDriver} = require('./NativeAnimatedHelper');
export type Mapping = {[key: string]: Mapping, ...} | AnimatedValue;
export type EventConfig = {
listener?: ?Function,
useNativeDriver: boolean,
};
function attachNativeEvent(
viewRef: any,
eventName: string,
argMapping: $ReadOnlyArray<?Mapping>,
): {detach: () => void} {
// Find animated values in `argMapping` and create an array representing their
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
const eventMappings = [];
const traverse = (value, path) => {
if (value instanceof AnimatedValue) {
value.__makeNative();
eventMappings.push({
nativeEventPath: path,
animatedValueTag: value.__getNativeTag(),
});
} else if (typeof value === 'object') {
for (const key in value) {
traverse(value[key], path.concat(key));
}
}
};
invariant(
argMapping[0] && argMapping[0].nativeEvent,
'Native driven events only support animated values contained inside `nativeEvent`.',
);
// Assume that the event containing `nativeEvent` is always the first argument.
traverse(argMapping[0].nativeEvent, []);
const viewTag = ReactNative.findNodeHandle(viewRef);
if (viewTag != null) {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.addAnimatedEventToView(
viewTag,
eventName,
mapping,
);
});
}
return {
detach() {
if (viewTag != null) {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.removeAnimatedEventFromView(
viewTag,
eventName,
// $FlowFixMe[incompatible-call]
mapping.animatedValueTag,
);
});
}
},
};
}
function validateMapping(argMapping, args) {
const validate = (recMapping, recEvt, key) => {
if (recMapping instanceof AnimatedValue) {
invariant(
typeof recEvt === 'number',
'Bad mapping of event key ' +
key +
', should be number but got ' +
typeof recEvt,
);
return;
}
if (typeof recEvt === 'number') {
invariant(
recMapping instanceof AnimatedValue,
'Bad mapping of type ' +
typeof recMapping +
' for key ' +
key +
', event value must map to AnimatedValue',
);
return;
}
invariant(
typeof recMapping === 'object',
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
);
invariant(
typeof recEvt === 'object',
'Bad event of type ' + typeof recEvt + ' for key ' + key,
);
for (const mappingKey in recMapping) {
validate(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
}
};
invariant(
args.length >= argMapping.length,
'Event has less arguments than mapping',
);
argMapping.forEach((mapping, idx) => {
validate(mapping, args[idx], 'arg' + idx);
});
}
class AnimatedEvent {
_argMapping: $ReadOnlyArray<?Mapping>;
_listeners: Array<Function> = [];
_callListeners: Function;
_attachedEvent: ?{detach: () => void, ...};
__isNative: boolean;
constructor(argMapping: $ReadOnlyArray<?Mapping>, config: EventConfig) {
this._argMapping = argMapping;
if (config == null) {
console.warn('Animated.event now requires a second argument for options');
config = {useNativeDriver: false};
}
if (config.listener) {
this.__addListener(config.listener);
}
this._callListeners = this._callListeners.bind(this);
this._attachedEvent = null;
this.__isNative = shouldUseNativeDriver(config);
}
__addListener(callback: Function): void {
this._listeners.push(callback);
}
__removeListener(callback: Function): void {
this._listeners = this._listeners.filter(listener => listener !== callback);
}
__attach(viewRef: any, eventName: string) {
invariant(
this.__isNative,
'Only native driven events need to be attached.',
);
this._attachedEvent = attachNativeEvent(
viewRef,
eventName,
this._argMapping,
);
}
__detach(viewTag: any, eventName: string) {
invariant(
this.__isNative,
'Only native driven events need to be detached.',
);
this._attachedEvent && this._attachedEvent.detach();
}
__getHandler(): any | ((...args: any) => void) {
if (this.__isNative) {
if (__DEV__) {
let validatedMapping = false;
return (...args: any) => {
if (!validatedMapping) {
validateMapping(this._argMapping, args);
validatedMapping = true;
}
this._callListeners(...args);
};
} else {
return this._callListeners;
}
}
let validatedMapping = false;
return (...args: any) => {
if (__DEV__ && !validatedMapping) {
validateMapping(this._argMapping, args);
validatedMapping = true;
}
const traverse = (recMapping, recEvt, key) => {
if (recMapping instanceof AnimatedValue) {
if (typeof recEvt === 'number') {
recMapping.setValue(recEvt);
}
} else if (typeof recMapping === 'object') {
for (const mappingKey in recMapping) {
/* $FlowFixMe[prop-missing] (>=0.120.0) This comment suppresses an
* error found when Flow v0.120 was deployed. To see the error,
* delete this comment and run Flow. */
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
}
}
};
this._argMapping.forEach((mapping, idx) => {
traverse(mapping, args[idx], 'arg' + idx);
});
this._callListeners(...args);
};
}
_callListeners(...args: any) {
this._listeners.forEach(listener => listener(...args));
}
}
module.exports = {AnimatedEvent, attachNativeEvent};
@@ -0,0 +1,709 @@
/**
* 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';
const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent');
const AnimatedAddition = require('./nodes/AnimatedAddition');
const AnimatedDiffClamp = require('./nodes/AnimatedDiffClamp');
const AnimatedDivision = require('./nodes/AnimatedDivision');
const AnimatedInterpolation = require('./nodes/AnimatedInterpolation');
const AnimatedModulo = require('./nodes/AnimatedModulo');
const AnimatedMultiplication = require('./nodes/AnimatedMultiplication');
const AnimatedNode = require('./nodes/AnimatedNode');
const AnimatedProps = require('./nodes/AnimatedProps');
const AnimatedSubtraction = require('./nodes/AnimatedSubtraction');
const AnimatedTracking = require('./nodes/AnimatedTracking');
const AnimatedValue = require('./nodes/AnimatedValue');
const AnimatedValueXY = require('./nodes/AnimatedValueXY');
const DecayAnimation = require('./animations/DecayAnimation');
const SpringAnimation = require('./animations/SpringAnimation');
const TimingAnimation = require('./animations/TimingAnimation');
const createAnimatedComponent = require('./createAnimatedComponent');
import type {
AnimationConfig,
EndCallback,
EndResult,
} from './animations/Animation';
import type {TimingAnimationConfig} from './animations/TimingAnimation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
import type {Mapping, EventConfig} from './AnimatedEvent';
export type CompositeAnimation = {
start: (callback?: ?EndCallback) => void,
stop: () => void,
reset: () => void,
_startNativeLoop: (iterations?: number) => void,
_isUsingNativeDriver: () => boolean,
...
};
const add = function(
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedAddition {
return new AnimatedAddition(a, b);
};
const subtract = function(
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedSubtraction {
return new AnimatedSubtraction(a, b);
};
const divide = function(
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedDivision {
return new AnimatedDivision(a, b);
};
const multiply = function(
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedMultiplication {
return new AnimatedMultiplication(a, b);
};
const modulo = function(a: AnimatedNode, modulus: number): AnimatedModulo {
return new AnimatedModulo(a, modulus);
};
const diffClamp = function(
a: AnimatedNode,
min: number,
max: number,
): AnimatedDiffClamp {
return new AnimatedDiffClamp(a, min, max);
};
const _combineCallbacks = function(
callback: ?EndCallback,
config: {...AnimationConfig, ...},
) {
if (callback && config.onComplete) {
return (...args) => {
config.onComplete && config.onComplete(...args);
callback && callback(...args);
};
} else {
return callback || config.onComplete;
}
};
const maybeVectorAnim = function(
value: AnimatedValue | AnimatedValueXY,
config: Object,
anim: (value: AnimatedValue, config: Object) => CompositeAnimation,
): ?CompositeAnimation {
if (value instanceof AnimatedValueXY) {
const configX = {...config};
const configY = {...config};
for (const key in config) {
const {x, y} = config[key];
if (x !== undefined && y !== undefined) {
configX[key] = x;
configY[key] = y;
}
}
const aX = anim((value: AnimatedValueXY).x, configX);
const aY = anim((value: AnimatedValueXY).y, configY);
// We use `stopTogether: false` here because otherwise tracking will break
// because the second animation will get stopped before it can update.
return parallel([aX, aY], {stopTogether: false});
}
return null;
};
const spring = function(
value: AnimatedValue | AnimatedValueXY,
config: SpringAnimationConfig,
): CompositeAnimation {
const start = function(
animatedValue: AnimatedValue | AnimatedValueXY,
configuration: SpringAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
if (configuration.toValue instanceof AnimatedNode) {
singleValue.track(
new AnimatedTracking(
singleValue,
configuration.toValue,
SpringAnimation,
singleConfig,
callback,
),
);
} else {
singleValue.animate(new SpringAnimation(singleConfig), callback);
}
};
return (
maybeVectorAnim(value, config, spring) || {
start: function(callback?: ?EndCallback): void {
start(value, config, callback);
},
stop: function(): void {
value.stopAnimation();
},
reset: function(): void {
value.resetAnimation();
},
_startNativeLoop: function(iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function(): boolean {
return config.useNativeDriver || false;
},
}
);
};
const timing = function(
value: AnimatedValue | AnimatedValueXY,
config: TimingAnimationConfig,
): CompositeAnimation {
const start = function(
animatedValue: AnimatedValue | AnimatedValueXY,
configuration: TimingAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
if (configuration.toValue instanceof AnimatedNode) {
singleValue.track(
new AnimatedTracking(
singleValue,
configuration.toValue,
TimingAnimation,
singleConfig,
callback,
),
);
} else {
singleValue.animate(new TimingAnimation(singleConfig), callback);
}
};
return (
maybeVectorAnim(value, config, timing) || {
start: function(callback?: ?EndCallback): void {
start(value, config, callback);
},
stop: function(): void {
value.stopAnimation();
},
reset: function(): void {
value.resetAnimation();
},
_startNativeLoop: function(iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function(): boolean {
return config.useNativeDriver || false;
},
}
);
};
const decay = function(
value: AnimatedValue | AnimatedValueXY,
config: DecayAnimationConfig,
): CompositeAnimation {
const start = function(
animatedValue: AnimatedValue | AnimatedValueXY,
configuration: DecayAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
singleValue.animate(new DecayAnimation(singleConfig), callback);
};
return (
maybeVectorAnim(value, config, decay) || {
start: function(callback?: ?EndCallback): void {
start(value, config, callback);
},
stop: function(): void {
value.stopAnimation();
},
reset: function(): void {
value.resetAnimation();
},
_startNativeLoop: function(iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function(): boolean {
return config.useNativeDriver || false;
},
}
);
};
const sequence = function(
animations: Array<CompositeAnimation>,
): CompositeAnimation {
let current = 0;
return {
start: function(callback?: ?EndCallback) {
const onComplete = function(result) {
if (!result.finished) {
callback && callback(result);
return;
}
current++;
if (current === animations.length) {
callback && callback(result);
return;
}
animations[current].start(onComplete);
};
if (animations.length === 0) {
callback && callback({finished: true});
} else {
animations[current].start(onComplete);
}
},
stop: function() {
if (current < animations.length) {
animations[current].stop();
}
},
reset: function() {
animations.forEach((animation, idx) => {
if (idx <= current) {
animation.reset();
}
});
current = 0;
},
_startNativeLoop: function() {
throw new Error(
'Loops run using the native driver cannot contain Animated.sequence animations',
);
},
_isUsingNativeDriver: function(): boolean {
return false;
},
};
};
type ParallelConfig = {
// If one is stopped, stop all. default: true
stopTogether?: boolean,
...
};
const parallel = function(
animations: Array<CompositeAnimation>,
config?: ?ParallelConfig,
): CompositeAnimation {
let doneCount = 0;
// Make sure we only call stop() at most once for each animation
const hasEnded = {};
const stopTogether = !(config && config.stopTogether === false);
const result = {
start: function(callback?: ?EndCallback) {
if (doneCount === animations.length) {
callback && callback({finished: true});
return;
}
animations.forEach((animation, idx) => {
const cb = function(endResult) {
hasEnded[idx] = true;
doneCount++;
if (doneCount === animations.length) {
doneCount = 0;
callback && callback(endResult);
return;
}
if (!endResult.finished && stopTogether) {
result.stop();
}
};
if (!animation) {
cb({finished: true});
} else {
animation.start(cb);
}
});
},
stop: function(): void {
animations.forEach((animation, idx) => {
!hasEnded[idx] && animation.stop();
hasEnded[idx] = true;
});
},
reset: function(): void {
animations.forEach((animation, idx) => {
animation.reset();
hasEnded[idx] = false;
doneCount = 0;
});
},
_startNativeLoop: function() {
throw new Error(
'Loops run using the native driver cannot contain Animated.parallel animations',
);
},
_isUsingNativeDriver: function(): boolean {
return false;
},
};
return result;
};
const delay = function(time: number): CompositeAnimation {
// Would be nice to make a specialized implementation
return timing(new AnimatedValue(0), {
toValue: 0,
delay: time,
duration: 0,
useNativeDriver: false,
});
};
const stagger = function(
time: number,
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return parallel(
animations.map((animation, i) => {
return sequence([delay(time * i), animation]);
}),
);
};
type LoopAnimationConfig = {
iterations: number,
resetBeforeIteration?: boolean,
...
};
const loop = function(
animation: CompositeAnimation,
{iterations = -1, resetBeforeIteration = true}: LoopAnimationConfig = {},
): CompositeAnimation {
let isFinished = false;
let iterationsSoFar = 0;
return {
start: function(callback?: ?EndCallback) {
const restart = function(result: EndResult = {finished: true}): void {
if (
isFinished ||
iterationsSoFar === iterations ||
result.finished === false
) {
callback && callback(result);
} else {
iterationsSoFar++;
resetBeforeIteration && animation.reset();
animation.start(restart);
}
};
if (!animation || iterations === 0) {
callback && callback({finished: true});
} else {
if (animation._isUsingNativeDriver()) {
animation._startNativeLoop(iterations);
} else {
restart(); // Start looping recursively on the js thread
}
}
},
stop: function(): void {
isFinished = true;
animation.stop();
},
reset: function(): void {
iterationsSoFar = 0;
isFinished = false;
animation.reset();
},
_startNativeLoop: function() {
throw new Error(
'Loops run using the native driver cannot contain Animated.loop animations',
);
},
_isUsingNativeDriver: function(): boolean {
return animation._isUsingNativeDriver();
},
};
};
function forkEvent(
event: ?AnimatedEvent | ?Function,
listener: Function,
): AnimatedEvent | Function {
if (!event) {
return listener;
} else if (event instanceof AnimatedEvent) {
event.__addListener(listener);
return event;
} else {
return (...args) => {
typeof event === 'function' && event(...args);
listener(...args);
};
}
}
function unforkEvent(
event: ?AnimatedEvent | ?Function,
listener: Function,
): void {
if (event && event instanceof AnimatedEvent) {
event.__removeListener(listener);
}
}
const event = function(
argMapping: $ReadOnlyArray<?Mapping>,
config: EventConfig,
): any {
const animatedEvent = new AnimatedEvent(argMapping, config);
if (animatedEvent.__isNative) {
return animatedEvent;
} else {
return animatedEvent.__getHandler();
}
};
/**
* The `Animated` library is designed to make animations fluid, powerful, and
* easy to build and maintain. `Animated` focuses on declarative relationships
* between inputs and outputs, with configurable transforms in between, and
* simple `start`/`stop` methods to control time-based animation execution.
* If additional transforms are added, be sure to include them in
* AnimatedMock.js as well.
*
* See https://reactnative.dev/docs/animated.html
*/
module.exports = {
/**
* Standard value class for driving animations. Typically initialized with
* `new Animated.Value(0);`
*
* See https://reactnative.dev/docs/animated.html#value
*/
Value: AnimatedValue,
/**
* 2D value class for driving 2D animations, such as pan gestures.
*
* See https://reactnative.dev/docs/animatedvaluexy.html
*/
ValueXY: AnimatedValueXY,
/**
* Exported to use the Interpolation type in flow.
*
* See https://reactnative.dev/docs/animated.html#interpolation
*/
Interpolation: AnimatedInterpolation,
/**
* Exported for ease of type checking. All animated values derive from this
* class.
*
* See https://reactnative.dev/docs/animated.html#node
*/
Node: AnimatedNode,
/**
* Animates a value from an initial velocity to zero based on a decay
* coefficient.
*
* See https://reactnative.dev/docs/animated.html#decay
*/
decay,
/**
* Animates a value along a timed easing curve. The Easing module has tons of
* predefined curves, or you can use your own function.
*
* See https://reactnative.dev/docs/animated.html#timing
*/
timing,
/**
* Animates a value according to an analytical spring model based on
* damped harmonic oscillation.
*
* See https://reactnative.dev/docs/animated.html#spring
*/
spring,
/**
* Creates a new Animated value composed from two Animated values added
* together.
*
* See https://reactnative.dev/docs/animated.html#add
*/
add,
/**
* Creates a new Animated value composed by subtracting the second Animated
* value from the first Animated value.
*
* See https://reactnative.dev/docs/animated.html#subtract
*/
subtract,
/**
* Creates a new Animated value composed by dividing the first Animated value
* by the second Animated value.
*
* See https://reactnative.dev/docs/animated.html#divide
*/
divide,
/**
* Creates a new Animated value composed from two Animated values multiplied
* together.
*
* See https://reactnative.dev/docs/animated.html#multiply
*/
multiply,
/**
* Creates a new Animated value that is the (non-negative) modulo of the
* provided Animated value.
*
* See https://reactnative.dev/docs/animated.html#modulo
*/
modulo,
/**
* Create a new Animated value that is limited between 2 values. It uses the
* difference between the last value so even if the value is far from the
* bounds it will start changing when the value starts getting closer again.
*
* See https://reactnative.dev/docs/animated.html#diffclamp
*/
diffClamp,
/**
* Starts an animation after the given delay.
*
* See https://reactnative.dev/docs/animated.html#delay
*/
delay,
/**
* Starts an array of animations in order, waiting for each to complete
* before starting the next. If the current running animation is stopped, no
* following animations will be started.
*
* See https://reactnative.dev/docs/animated.html#sequence
*/
sequence,
/**
* Starts an array of animations all at the same time. By default, if one
* of the animations is stopped, they will all be stopped. You can override
* this with the `stopTogether` flag.
*
* See https://reactnative.dev/docs/animated.html#parallel
*/
parallel,
/**
* Array of animations may run in parallel (overlap), but are started in
* sequence with successive delays. Nice for doing trailing effects.
*
* See https://reactnative.dev/docs/animated.html#stagger
*/
stagger,
/**
* Loops a given animation continuously, so that each time it reaches the
* end, it resets and begins again from the start.
*
* See https://reactnative.dev/docs/animated.html#loop
*/
loop,
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls `setValue` on the mapped outputs.
*
* See https://reactnative.dev/docs/animated.html#event
*/
event,
/**
* Make any React component Animatable. Used to create `Animated.View`, etc.
*
* See https://reactnative.dev/docs/animated.html#createanimatedcomponent
*/
createAnimatedComponent,
/**
* Imperative API to attach an animated value to an event on a view. Prefer
* using `Animated.event` with `useNativeDrive: true` if possible.
*
* See https://reactnative.dev/docs/animated.html#attachnativeevent
*/
attachNativeEvent,
/**
* Advanced imperative API for snooping on animated events that are passed in
* through props. Use values directly where possible.
*
* See https://reactnative.dev/docs/animated.html#forkevent
*/
forkEvent,
unforkEvent,
/**
* Expose Event class, so it can be used as a type for type checkers.
*/
Event: AnimatedEvent,
};
+149
View File
@@ -0,0 +1,149 @@
/**
* 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';
const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent');
const AnimatedImplementation = require('./AnimatedImplementation');
const AnimatedInterpolation = require('./nodes/AnimatedInterpolation');
const AnimatedNode = require('./nodes/AnimatedNode');
const AnimatedValue = require('./nodes/AnimatedValue');
const AnimatedValueXY = require('./nodes/AnimatedValueXY');
const createAnimatedComponent = require('./createAnimatedComponent');
import type {EndCallback} from './animations/Animation';
import type {TimingAnimationConfig} from './animations/TimingAnimation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
/**
* Animations are a source of flakiness in snapshot testing. This mock replaces
* animation functions from AnimatedImplementation with empty animations for
* predictability in tests.
*/
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 spring = function(
value: AnimatedValue | AnimatedValueXY,
config: SpringAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
return {
...emptyAnimation,
start: (callback?: ?EndCallback): void => {
anyValue.setValue(config.toValue);
callback && callback({finished: true});
},
};
};
const timing = function(
value: AnimatedValue | AnimatedValueXY,
config: TimingAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
return {
...emptyAnimation,
start: (callback?: ?EndCallback): void => {
anyValue.setValue(config.toValue);
callback && callback({finished: true});
},
};
};
const decay = function(
value: AnimatedValue | AnimatedValueXY,
config: DecayAnimationConfig,
): CompositeAnimation {
return emptyAnimation;
};
const sequence = function(
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return emptyAnimation;
};
type ParallelConfig = {stopTogether?: boolean, ...};
const parallel = function(
animations: Array<CompositeAnimation>,
config?: ?ParallelConfig,
): CompositeAnimation {
return emptyAnimation;
};
const delay = function(time: number): CompositeAnimation {
return emptyAnimation;
};
const stagger = function(
time: number,
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return emptyAnimation;
};
type LoopAnimationConfig = {
iterations: number,
resetBeforeIteration?: boolean,
...
};
const loop = function(
animation: CompositeAnimation,
{iterations = -1}: LoopAnimationConfig = {},
): CompositeAnimation {
return emptyAnimation;
};
module.exports = {
Value: AnimatedValue,
ValueXY: AnimatedValueXY,
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,
};
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -10,9 +10,9 @@
'use strict';
import AnimatedImplementation from './AnimatedImplementation';
const AnimatedImplementation = require('./AnimatedImplementation');
export default {
module.exports = {
...AnimatedImplementation,
/* $FlowFixMe[incompatible-call] createAnimatedComponent expects to receive
* types. Plain intrinsic components can't be typed like this */
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -57,20 +57,20 @@ let ease;
* - [`inOut`](docs/easing.html#inout) makes any easing function symmetrical
* - [`out`](docs/easing.html#out) runs an easing function backwards
*/
const Easing = {
class Easing {
/**
* A stepping function, returns 1 for any positive value of `n`.
*/
step0(n: number): number {
static step0(n: number): number {
return n > 0 ? 1 : 0;
},
}
/**
* A stepping function, returns 1 if `n` is greater than or equal to 1.
*/
step1(n: number): number {
static step1(n: number): number {
return n >= 1 ? 1 : 0;
},
}
/**
* A linear function, `f(t) = t`. Position correlates to elapsed time one to
@@ -78,9 +78,9 @@ const Easing = {
*
* http://cubic-bezier.com/#0,0,1,1
*/
linear(t: number): number {
static linear(t: number): number {
return t;
},
}
/**
* A simple inertial interaction, similar to an object slowly accelerating to
@@ -88,12 +88,12 @@ const Easing = {
*
* http://cubic-bezier.com/#.42,0,1,1
*/
ease(t: number): number {
static ease(t: number): number {
if (!ease) {
ease = Easing.bezier(0.42, 0, 1, 1);
}
return ease(t);
},
}
/**
* A quadratic function, `f(t) = t * t`. Position equals the square of elapsed
@@ -101,9 +101,9 @@ const Easing = {
*
* http://easings.net/#easeInQuad
*/
quad(t: number): number {
static quad(t: number): number {
return t * t;
},
}
/**
* A cubic function, `f(t) = t * t * t`. Position equals the cube of elapsed
@@ -111,9 +111,9 @@ const Easing = {
*
* http://easings.net/#easeInCubic
*/
cubic(t: number): number {
static cubic(t: number): number {
return t * t * t;
},
}
/**
* A power function. Position is equal to the Nth power of elapsed time.
@@ -121,36 +121,36 @@ const Easing = {
* n = 4: http://easings.net/#easeInQuart
* n = 5: http://easings.net/#easeInQuint
*/
poly(n: number): (t: number) => number {
static poly(n: number): (t: number) => number {
return (t: number) => Math.pow(t, n);
},
}
/**
* A sinusoidal function.
*
* http://easings.net/#easeInSine
*/
sin(t: number): number {
static sin(t: number): number {
return 1 - Math.cos((t * Math.PI) / 2);
},
}
/**
* A circular function.
*
* http://easings.net/#easeInCirc
*/
circle(t: number): number {
static circle(t: number): number {
return 1 - Math.sqrt(1 - t * t);
},
}
/**
* An exponential function.
*
* http://easings.net/#easeInExpo
*/
exp(t: number): number {
static exp(t: number): number {
return Math.pow(2, 10 * (t - 1));
},
}
/**
* A simple elastic interaction, similar to a spring oscillating back and
@@ -162,27 +162,29 @@ const Easing = {
*
* http://easings.net/#easeInElastic
*/
elastic(bounciness: number = 1): (t: number) => number {
static elastic(bounciness: number = 1): (t: number) => number {
const p = bounciness * Math.PI;
return t => 1 - Math.pow(Math.cos((t * Math.PI) / 2), 3) * Math.cos(t * p);
},
}
/**
* Use with `Animated.parallel()` to create a simple effect where the object
* animates back slightly as the animation starts.
*
* https://easings.net/#easeInBack
* Wolfram Plot:
*
* - http://tiny.cc/back_default (s = 1.70158, default)
*/
back(s: number = 1.70158): (t: number) => number {
static back(s: number = 1.70158): (t: number) => number {
return t => t * t * ((s + 1) * t - s);
},
}
/**
* Provides a simple bouncing effect.
*
* http://easings.net/#easeInBounce
*/
bounce(t: number): number {
static bounce(t: number): number {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
}
@@ -199,7 +201,7 @@ const Easing = {
const t2 = t - 2.625 / 2.75;
return 7.5625 * t2 * t2 + 0.984375;
},
}
/**
* Provides a cubic bezier curve, equivalent to CSS Transitions'
@@ -208,43 +210,43 @@ const Easing = {
* A useful tool to visualize cubic bezier curves can be found at
* http://cubic-bezier.com/
*/
bezier(
static bezier(
x1: number,
y1: number,
x2: number,
y2: number,
): (t: number) => number {
const _bezier = require('./bezier').default;
const _bezier = require('./bezier');
return _bezier(x1, y1, x2, y2);
},
}
/**
* Runs an easing function forwards.
*/
in(easing: (t: number) => number): (t: number) => number {
static in(easing: (t: number) => number): (t: number) => number {
return easing;
},
}
/**
* Runs an easing function backwards.
*/
out(easing: (t: number) => number): (t: number) => number {
static out(easing: (t: number) => number): (t: number) => number {
return t => 1 - easing(1 - t);
},
}
/**
* Makes any easing function symmetrical. The easing function will run
* forwards for half of the duration, then backwards for the rest of the
* duration.
*/
inOut(easing: (t: number) => number): (t: number) => number {
static inOut(easing: (t: number) => number): (t: number) => number {
return t => {
if (t < 0.5) {
return easing(t * 2) / 2;
}
return 1 - easing((1 - t) * 2) / 2;
};
},
};
}
}
export default Easing;
module.exports = Easing;
+408
View File
@@ -0,0 +1,408 @@
/**
* 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 strict-local
* @format
*/
import NativeAnimatedNonTurboModule from './NativeAnimatedModule';
import NativeAnimatedTurboModule from './NativeAnimatedTurboModule';
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import Platform from '../Utilities/Platform';
import type {EventConfig} from './AnimatedEvent';
import type {
EventMapping,
AnimatedNodeConfig,
AnimatingNodeConfig,
} from './NativeAnimatedModule';
import type {AnimationConfig, EndCallback} from './animations/Animation';
import type {InterpolationConfigType} from './nodes/AnimatedInterpolation';
import invariant from 'invariant';
// TODO T69437152 @petetheheat - Delete this fork when Fabric ships to 100%.
const NativeAnimatedModule =
Platform.OS === 'ios' && global.RN$Bridgeless
? NativeAnimatedTurboModule
: NativeAnimatedNonTurboModule;
let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
let __nativeAnimationIdCount = 1; /* used for started animations */
let nativeEventEmitter;
let waitingForQueuedOperations = new Set();
let queueOperations = false;
let queue: Array<() => void> = [];
/**
* Simple wrappers around NativeAnimatedModule to provide flow and autocomplete support for
* the native module methods
*/
const API = {
getValue: function(
tag: number,
saveValueCallback: (value: number) => void,
): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() => {
NativeAnimatedModule.getValue(tag, saveValueCallback);
});
},
setWaitingForIdentifier: function(id: string): void {
waitingForQueuedOperations.add(id);
queueOperations = true;
},
unsetWaitingForIdentifier: function(id: string): void {
waitingForQueuedOperations.delete(id);
if (waitingForQueuedOperations.size === 0) {
queueOperations = false;
API.disableQueue();
}
},
disableQueue: function(): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
if (Platform.OS === 'android') {
NativeAnimatedModule.startOperationBatch();
}
for (let q = 0, l = queue.length; q < l; q++) {
queue[q]();
}
queue.length = 0;
if (Platform.OS === 'android') {
NativeAnimatedModule.finishOperationBatch();
}
},
queueOperation: (fn: () => void): void => {
if (queueOperations) {
queue.push(fn);
} else {
fn();
}
},
createAnimatedNode: function(tag: number, config: AnimatedNodeConfig): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.createAnimatedNode(tag, config),
);
},
startListeningToAnimatedNodeValue: function(tag: number) {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.startListeningToAnimatedNodeValue(tag),
);
},
stopListeningToAnimatedNodeValue: function(tag: number) {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag),
);
},
connectAnimatedNodes: function(parentTag: number, childTag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag),
);
},
disconnectAnimatedNodes: function(parentTag: number, childTag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag),
);
},
startAnimatingNode: function(
animationId: number,
nodeTag: number,
config: AnimatingNodeConfig,
endCallback: EndCallback,
): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.startAnimatingNode(
animationId,
nodeTag,
config,
endCallback,
),
);
},
stopAnimation: function(animationId: number) {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() => NativeAnimatedModule.stopAnimation(animationId));
},
setAnimatedNodeValue: function(nodeTag: number, value: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value),
);
},
setAnimatedNodeOffset: function(nodeTag: number, offset: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset),
);
},
flattenAnimatedNodeOffset: function(nodeTag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag),
);
},
extractAnimatedNodeOffset: function(nodeTag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag),
);
},
connectAnimatedNodeToView: function(nodeTag: number, viewTag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag),
);
},
disconnectAnimatedNodeFromView: function(
nodeTag: number,
viewTag: number,
): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag),
);
},
restoreDefaultValues: function(nodeTag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
// Backwards compat with older native runtimes, can be removed later.
if (NativeAnimatedModule.restoreDefaultValues != null) {
API.queueOperation(() =>
NativeAnimatedModule.restoreDefaultValues(nodeTag),
);
}
},
dropAnimatedNode: function(tag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() => NativeAnimatedModule.dropAnimatedNode(tag));
},
addAnimatedEventToView: function(
viewTag: number,
eventName: string,
eventMapping: EventMapping,
) {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.addAnimatedEventToView(
viewTag,
eventName,
eventMapping,
),
);
},
removeAnimatedEventFromView(
viewTag: number,
eventName: string,
animatedNodeTag: number,
) {
invariant(NativeAnimatedModule, 'Native animated module is not available');
API.queueOperation(() =>
NativeAnimatedModule.removeAnimatedEventFromView(
viewTag,
eventName,
animatedNodeTag,
),
);
},
};
/**
* Styles allowed by the native animated implementation.
*
* In general native animated implementation should support any numeric property that doesn't need
* to be updated through the shadow view hierarchy (all non-layout properties).
*/
const SUPPORTED_STYLES = {
opacity: true,
transform: true,
borderRadius: true,
borderBottomEndRadius: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderTopEndRadius: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopStartRadius: true,
elevation: true,
zIndex: true,
/* ios styles */
shadowOpacity: true,
shadowRadius: true,
/* legacy android transform properties */
scaleX: true,
scaleY: true,
translateX: true,
translateY: true,
};
const SUPPORTED_TRANSFORMS = {
translateX: true,
translateY: true,
scale: true,
scaleX: true,
scaleY: true,
rotate: true,
rotateX: true,
rotateY: true,
rotateZ: true,
perspective: true,
};
const SUPPORTED_INTERPOLATION_PARAMS = {
inputRange: true,
outputRange: true,
extrapolate: true,
extrapolateRight: true,
extrapolateLeft: true,
};
function addWhitelistedStyleProp(prop: string): void {
SUPPORTED_STYLES[prop] = true;
}
function addWhitelistedTransformProp(prop: string): void {
SUPPORTED_TRANSFORMS[prop] = true;
}
function addWhitelistedInterpolationParam(param: string): void {
SUPPORTED_INTERPOLATION_PARAMS[param] = true;
}
function validateTransform(
configs: Array<
| {
type: 'animated',
property: string,
nodeTag: ?number,
...
}
| {
type: 'static',
property: string,
value: number | string,
...
},
>,
): void {
configs.forEach(config => {
if (!SUPPORTED_TRANSFORMS.hasOwnProperty(config.property)) {
throw new Error(
`Property '${config.property}' is not supported by native animated module`,
);
}
});
}
function validateStyles(styles: {[key: string]: ?number, ...}): void {
for (const key in styles) {
if (!SUPPORTED_STYLES.hasOwnProperty(key)) {
throw new Error(
`Style property '${key}' is not supported by native animated module`,
);
}
}
}
function validateInterpolation(config: InterpolationConfigType): void {
for (const key in config) {
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
throw new Error(
`Interpolation property '${key}' is not supported by native animated module`,
);
}
}
}
function generateNewNodeTag(): number {
return __nativeAnimatedNodeTagCount++;
}
function generateNewAnimationId(): number {
return __nativeAnimationIdCount++;
}
function assertNativeAnimatedModule(): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
}
let _warnedMissingNativeAnimated = false;
function shouldUseNativeDriver(
config: {...AnimationConfig, ...} | EventConfig,
): boolean {
if (config.useNativeDriver == null) {
console.warn(
'Animated: `useNativeDriver` was not specified. This is a required ' +
'option and must be explicitly set to `true` or `false`',
);
}
if (config.useNativeDriver === true && !NativeAnimatedModule) {
if (!_warnedMissingNativeAnimated) {
console.warn(
'Animated: `useNativeDriver` is not supported because the native ' +
'animated module is missing. Falling back to JS-based animation. To ' +
'resolve this, add `RCTAnimation` module to this app, or remove ' +
'`useNativeDriver`. ' +
'Make sure to run `pod install` first. Read more about autolinking: https://github.com/react-native-community/cli/blob/master/docs/autolinking.md',
);
_warnedMissingNativeAnimated = true;
}
return false;
}
return config.useNativeDriver || false;
}
function transformDataType(value: number | string): number | string {
// Change the string type to number type so we can reuse the same logic in
// iOS and Android platform
if (typeof value !== 'string') {
return value;
}
if (/deg$/.test(value)) {
const degrees = parseFloat(value) || 0;
const radians = (degrees * Math.PI) / 180.0;
return radians;
} else {
return value;
}
}
module.exports = {
API,
addWhitelistedStyleProp,
addWhitelistedTransformProp,
addWhitelistedInterpolationParam,
validateStyles,
validateTransform,
validateInterpolation,
generateNewNodeTag,
generateNewAnimationId,
assertNativeAnimatedModule,
shouldUseNativeDriver,
transformDataType,
// $FlowExpectedError[unsafe-getters-setters] - unsafe getter lint suppresion
// $FlowExpectedError[missing-type-arg] - unsafe getter lint suppresion
get nativeEventEmitter(): NativeEventEmitter {
if (!nativeEventEmitter) {
nativeEventEmitter = new NativeEventEmitter(
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
// If you want to use the native module on other platforms, please remove this condition and test its behavior
Platform.OS !== 'ios' ? null : NativeAnimatedModule,
);
}
return nativeEventEmitter;
},
};
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -9,7 +9,6 @@
*/
import type {TurboModule} from '../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
type EndResult = {finished: boolean, ...};
@@ -30,7 +29,6 @@ export interface Spec extends TurboModule {
+startOperationBatch: () => void;
+finishOperationBatch: () => void;
+createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void;
+updateAnimatedNodeConfig?: (tag: number, config: AnimatedNodeConfig) => void;
+getValue: (tag: number, saveValueCallback: SaveValueCallback) => void;
+startListeningToAnimatedNodeValue: (tag: number) => void;
+stopListeningToAnimatedNodeValue: (tag: number) => void;
@@ -65,9 +63,6 @@ export interface Spec extends TurboModule {
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
// All of the above in a batched mode
+queueAndExecuteBatchedOperations?: (operationsAndArgs: Array<any>) => void;
}
export default (TurboModuleRegistry.get<Spec>('NativeAnimatedModule'): ?Spec);
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -9,7 +9,6 @@
*/
import type {TurboModule} from '../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
type EndResult = {finished: boolean, ...};
@@ -30,7 +29,6 @@ export interface Spec extends TurboModule {
+startOperationBatch: () => void;
+finishOperationBatch: () => void;
+createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void;
+updateAnimatedNodeConfig?: (tag: number, config: AnimatedNodeConfig) => void;
+getValue: (tag: number, saveValueCallback: SaveValueCallback) => void;
+startListeningToAnimatedNodeValue: (tag: number) => void;
+stopListeningToAnimatedNodeValue: (tag: number) => void;
@@ -65,9 +63,6 @@ export interface Spec extends TurboModule {
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
// All of the above in a batched mode
+queueAndExecuteBatchedOperations?: (operationsAndArgs: Array<any>) => void;
}
export default (TurboModuleRegistry.get<Spec>(
+103
View File
@@ -0,0 +1,103 @@
/**
* 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
* @flow strict
*/
'use strict';
type SpringConfigType = {
stiffness: number,
damping: number,
...
};
function stiffnessFromOrigamiValue(oValue) {
return (oValue - 30) * 3.62 + 194;
}
function dampingFromOrigamiValue(oValue) {
return (oValue - 8) * 3 + 25;
}
function fromOrigamiTensionAndFriction(
tension: number,
friction: number,
): SpringConfigType {
return {
stiffness: stiffnessFromOrigamiValue(tension),
damping: dampingFromOrigamiValue(friction),
};
}
function fromBouncinessAndSpeed(
bounciness: number,
speed: number,
): SpringConfigType {
function normalize(value, startValue, endValue) {
return (value - startValue) / (endValue - startValue);
}
function projectNormal(n, start, end) {
return start + n * (end - start);
}
function linearInterpolation(t, start, end) {
return t * end + (1 - t) * start;
}
function quadraticOutInterpolation(t, start, end) {
return linearInterpolation(2 * t - t * t, start, end);
}
function b3Friction1(x) {
return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28;
}
function b3Friction2(x) {
return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2;
}
function b3Friction3(x) {
return (
0.00000045 * Math.pow(x, 3) -
0.000332 * Math.pow(x, 2) +
0.1078 * x +
5.84
);
}
function b3Nobounce(tension) {
if (tension <= 18) {
return b3Friction1(tension);
} else if (tension > 18 && tension <= 44) {
return b3Friction2(tension);
} else {
return b3Friction3(tension);
}
}
let b = normalize(bounciness / 1.7, 0, 20);
b = projectNormal(b, 0, 0.8);
const s = normalize(speed / 1.7, 0, 20);
const bouncyTension = projectNormal(s, 0.5, 200);
const bouncyFriction = quadraticOutInterpolation(
b,
b3Nobounce(bouncyTension),
0.01,
);
return {
stiffness: stiffnessFromOrigamiValue(bouncyTension),
damping: dampingFromOrigamiValue(bouncyFriction),
};
}
module.exports = {
fromOrigamiTensionAndFriction,
fromBouncinessAndSpeed,
};
@@ -1,20 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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
* @oncall react_native
* @emails oncall+react_native
*/
import AnimatedProps from '../nodes/AnimatedProps';
import TestRenderer from 'react-test-renderer';
import * as React from 'react';
const {PlatformColor} = require('../../StyleSheet/PlatformColorValueTypes');
let Animated = require('../Animated').default;
const AnimatedProps = require('../nodes/AnimatedProps').default;
const TestRenderer = require('react-test-renderer');
jest.mock('../../BatchedBridge/NativeModules', () => ({
NativeAnimatedModule: {},
PlatformConstants: {
@@ -24,6 +21,8 @@ jest.mock('../../BatchedBridge/NativeModules', () => ({
},
}));
let Animated = require('../Animated');
describe('Animated tests', () => {
beforeEach(() => {
jest.resetModules();
@@ -32,10 +31,6 @@ describe('Animated tests', () => {
describe('Animated', () => {
it('works end to end', () => {
const anim = new Animated.Value(0);
const translateAnim = anim.interpolate({
inputRange: [0, 1],
outputRange: [100, 200],
});
const callback = jest.fn();
@@ -46,10 +41,10 @@ describe('Animated tests', () => {
opacity: anim,
transform: [
{
translate: [translateAnim, translateAnim],
},
{
translateX: translateAnim,
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [100, 200],
}),
},
{scale: anim},
],
@@ -66,7 +61,7 @@ describe('Animated tests', () => {
style: {
backgroundColor: 'red',
opacity: 0,
transform: [{translate: [100, 100]}, {translateX: 100}, {scale: 0}],
transform: [{translateX: 100}, {scale: 0}],
shadowOffset: {
width: 0,
height: 0,
@@ -88,7 +83,7 @@ describe('Animated tests', () => {
style: {
backgroundColor: 'red',
opacity: 0.5,
transform: [{translate: [150, 150]}, {translateX: 150}, {scale: 0.5}],
transform: [{translateX: 150}, {scale: 0.5}],
shadowOffset: {
width: 0.5,
height: 0.5,
@@ -145,6 +140,21 @@ describe('Animated tests', () => {
expect(callback).toBeCalled();
});
// This test is flaky and we are asking open source to fix it
// https://github.com/facebook/react-native/issues/21517
it.skip('send toValue when an underdamped spring stops', () => {
const anim = new Animated.Value(0);
const listener = jest.fn();
anim.addListener(listener);
Animated.spring(anim, {toValue: 15, useNativeDriver: false}).start();
jest.runAllTimers();
const lastValue =
listener.mock.calls[listener.mock.calls.length - 2][0].value;
expect(lastValue).not.toBe(15);
expect(lastValue).toBeCloseTo(15);
expect(anim.__getValue()).toBe(15);
});
it('send toValue when a critically damped spring stops', () => {
const anim = new Animated.Value(0);
const listener = jest.fn();
@@ -176,13 +186,11 @@ describe('Animated tests', () => {
expect(testRenderer.toJSON().props.style.opacity).toEqual(0);
TestRenderer.act(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 0,
useNativeDriver: false,
}).start();
});
Animated.timing(opacity, {
toValue: 1,
duration: 0,
useNativeDriver: false,
}).start();
expect(testRenderer.toJSON().props.style.opacity).toEqual(1);
});
@@ -630,16 +638,6 @@ describe('Animated tests', () => {
handler({bar: 'ignoreBar'}, {state: {baz: 'ignoreBaz', foo: 42}});
expect(value.__getValue()).toBe(42);
});
it('should validate AnimatedValueXY mappings', () => {
const value = new Animated.ValueXY({x: 0, y: 0});
const handler = Animated.event([{state: value}], {
useNativeDriver: false,
});
handler({state: {x: 1, y: 2}});
expect(value.__getValue()).toMatchObject({x: 1, y: 2});
});
it('should call listeners', () => {
const value = new Animated.Value(0);
const listener = jest.fn();
@@ -652,7 +650,6 @@ describe('Animated tests', () => {
expect(listener.mock.calls.length).toBe(1);
expect(listener).toBeCalledWith({foo: 42});
});
it('should call forked event listeners, with Animated.event() listener', () => {
const value = new Animated.Value(0);
const listener = jest.fn();
@@ -669,7 +666,6 @@ describe('Animated tests', () => {
expect(listener2.mock.calls.length).toBe(1);
expect(listener2).toBeCalledWith({foo: 42});
});
it('should call forked event listeners, with js listener', () => {
const listener = jest.fn();
const listener2 = jest.fn();
@@ -680,7 +676,6 @@ describe('Animated tests', () => {
expect(listener2.mock.calls.length).toBe(1);
expect(listener2).toBeCalledWith({foo: 42});
});
it('should call forked event listeners, with undefined listener', () => {
const listener = undefined;
const listener2 = jest.fn();
@@ -699,7 +694,7 @@ describe('Animated tests', () => {
beforeEach(() => {
jest.mock('../../Interaction/InteractionManager');
Animated = require('../Animated').default;
Animated = require('../Animated');
InteractionManager = require('../../Interaction/InteractionManager');
});
@@ -789,19 +784,6 @@ describe('Animated tests', () => {
value1.setValue(1492);
expect(value2.__getValue()).toBe(7);
});
it('should start tracking immediately on animation start', () => {
const value1 = new Animated.Value(42);
const value2 = new Animated.Value(0);
Animated.timing(value2, {
toValue: value1,
duration: 0,
useNativeDriver: false,
}).start();
expect(value2.__getValue()).toBe(42);
value1.setValue(7);
expect(value2.__getValue()).toBe(7);
});
});
describe('Animated Vectors', () => {
@@ -979,143 +961,4 @@ describe('Animated tests', () => {
}
});
});
describe('Animated Colors', () => {
it('should normalize colors', () => {
let color = new Animated.Color();
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
color = new Animated.Color({r: 11, g: 22, b: 33, a: 1.0});
expect(color.__getValue()).toEqual('rgba(11, 22, 33, 1)');
color = new Animated.Color('rgba(255, 0, 0, 1.0)');
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color('#ff0000ff');
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color('red');
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color({
r: new Animated.Value(255),
g: new Animated.Value(0),
b: new Animated.Value(0),
a: new Animated.Value(1.0),
});
expect(color.__getValue()).toEqual('rgba(255, 0, 0, 1)');
color = new Animated.Color('unknown');
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
color = new Animated.Color({key: 'value'});
expect(color.__getValue()).toEqual('rgba(0, 0, 0, 1)');
});
it('should animate colors', () => {
const color = new Animated.Color({r: 255, g: 0, b: 0, a: 1.0});
const callback = jest.fn();
const node = new AnimatedProps(
{
style: {
backgroundColor: color,
transform: [
{
scale: color.a.interpolate({
inputRange: [0, 1],
outputRange: [1, 2],
}),
},
],
},
},
callback,
);
expect(node.__getValue()).toEqual({
style: {
backgroundColor: 'rgba(255, 0, 0, 1)',
transform: [{scale: 2}],
},
});
node.__attach();
expect(callback).not.toHaveBeenCalled();
color.setValue({r: 11, g: 22, b: 33, a: 0.5});
expect(callback).toHaveBeenCalledTimes(5);
expect(node.__getValue()).toEqual({
style: {
backgroundColor: 'rgba(11, 22, 33, 0.5)',
transform: [{scale: 1.5}],
},
});
node.__detach();
color.setValue({r: 255, g: 0, b: 0, a: 1.0});
expect(callback).toHaveBeenCalledTimes(5);
});
it('should track colors', () => {
const color1 = new Animated.Color();
const color2 = new Animated.Color();
Animated.timing(color2, {
toValue: color1,
duration: 0,
useNativeDriver: false,
}).start();
color1.setValue({r: 11, g: 22, b: 33, a: 0.5});
expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)');
// Make sure tracking keeps working (see stopTogether in ParallelConfig used
// by maybeVectorAnim).
color1.setValue({r: 255, g: 0, b: 0, a: 1.0});
expect(color2.__getValue()).toEqual('rgba(255, 0, 0, 1)');
});
it('should track with springs', () => {
const color1 = new Animated.Color();
const color2 = new Animated.Color();
Animated.spring(color2, {
toValue: color1,
tension: 3000, // faster spring for faster test
friction: 60,
useNativeDriver: false,
}).start();
color1.setValue({r: 11, g: 22, b: 33, a: 0.5});
jest.runAllTimers();
expect(color2.__getValue()).toEqual('rgba(11, 22, 33, 0.5)');
color1.setValue({r: 44, g: 55, b: 66, a: 0.0});
jest.runAllTimers();
expect(color2.__getValue()).toEqual('rgba(44, 55, 66, 0)');
});
it('should provide updates for native colors', () => {
const color = new Animated.Color('red');
const listener = jest.fn();
color.addListener(listener);
const callback = jest.fn(() => {
console.log('callback', color.__getValue());
});
const node = new AnimatedProps({style: {color}}, callback);
node.__attach();
color.setValue('blue');
expect(callback).toHaveBeenCalledTimes(5);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith({value: 'rgba(0, 0, 255, 1)'});
expect(color.__getValue()).toEqual('rgba(0, 0, 255, 1)');
callback.mockClear();
listener.mockClear();
color.setValue(PlatformColor('bar'));
expect(callback).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith({value: PlatformColor('bar')});
expect(color.__getValue()).toEqual(PlatformColor('bar'));
});
});
});
@@ -1,17 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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
* @oncall react_native
* @emails oncall+react_native
*/
'use strict';
import AnimatedImplementation from '../AnimatedImplementation';
import AnimatedMock from '../AnimatedMock';
const AnimatedMock = require('../AnimatedMock');
const AnimatedImplementation = require('../AnimatedImplementation');
describe('Animated Mock', () => {
it('matches implementation keys', () => {
@@ -0,0 +1,921 @@
/**
* 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,
);
});
});
});
+467
View File
@@ -0,0 +1,467 @@
/**
* 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
*/
'use strict';
const Easing = require('../Easing');
describe('Easing', () => {
it('should work with linear', () => {
const easing = Easing.linear;
expect(easing(0)).toBe(0);
expect(easing(0.5)).toBe(0.5);
expect(easing(0.8)).toBe(0.8);
expect(easing(1)).toBe(1);
});
it('should work with ease in linear', () => {
const easing = Easing.in(Easing.linear);
expect(easing(0)).toBe(0);
expect(easing(0.5)).toBe(0.5);
expect(easing(0.8)).toBe(0.8);
expect(easing(1)).toBe(1);
});
it('should work with easy out linear', () => {
const easing = Easing.out(Easing.linear);
expect(easing(0)).toBe(0);
expect(easing(0.5)).toBe(0.5);
expect(easing(0.6)).toBe(0.6);
expect(easing(1)).toBe(1);
});
it('should work with ease in quad', () => {
function easeInQuad(t) {
return t * t;
}
const easing = Easing.in(Easing.quad);
for (let t = -0.5; t < 1.5; t += 0.1) {
expect(easing(t)).toBe(easeInQuad(t));
}
});
it('should work with ease out quad', () => {
function easeOutQuad(t) {
return -t * (t - 2);
}
const easing = Easing.out(Easing.quad);
for (let t = 0; t <= 1; t += 0.1) {
expect(easing(1)).toBe(easeOutQuad(1));
}
});
it('should work with ease in-out quad', () => {
function easeInOutQuad(t) {
t = t * 2;
if (t < 1) {
return 0.5 * t * t;
}
return -((t - 1) * (t - 3) - 1) / 2;
}
const easing = Easing.inOut(Easing.quad);
for (let t = -0.5; t < 1.5; t += 0.1) {
expect(easing(t)).toBeCloseTo(easeInOutQuad(t), 4);
}
});
it('should satisfy boundary conditions with elastic', () => {
for (let b = 0; b < 4; b += 0.3) {
const easing = Easing.elastic(b);
expect(easing(0)).toBe(0);
expect(easing(1)).toBe(1);
}
});
function sampleEasingFunction(easing) {
const DURATION = 300;
const tickCount = Math.round((DURATION * 60) / 1000);
const samples = [];
for (let i = 0; i <= tickCount; i++) {
samples.push(easing(i / tickCount));
}
return samples;
}
const Samples = {
in_quad: [
0,
0.0030864197530864196,
0.012345679012345678,
0.027777777777777776,
0.04938271604938271,
0.0771604938271605,
0.1111111111111111,
0.15123456790123457,
0.19753086419753085,
0.25,
0.308641975308642,
0.37345679012345684,
0.4444444444444444,
0.5216049382716049,
0.6049382716049383,
0.6944444444444445,
0.7901234567901234,
0.8919753086419753,
1,
],
out_quad: [
0,
0.10802469135802469,
0.20987654320987653,
0.3055555555555555,
0.3950617283950617,
0.47839506172839513,
0.5555555555555556,
0.6265432098765432,
0.691358024691358,
0.75,
0.8024691358024691,
0.8487654320987654,
0.888888888888889,
0.9228395061728394,
0.9506172839506174,
0.9722222222222221,
0.9876543209876543,
0.9969135802469136,
1,
],
inOut_quad: [
0,
0.006172839506172839,
0.024691358024691357,
0.05555555555555555,
0.09876543209876543,
0.154320987654321,
0.2222222222222222,
0.30246913580246915,
0.3950617283950617,
0.5,
0.6049382716049383,
0.697530864197531,
0.7777777777777777,
0.845679012345679,
0.9012345679012346,
0.9444444444444444,
0.9753086419753086,
0.9938271604938271,
1,
],
in_cubic: [
0,
0.00017146776406035664,
0.0013717421124828531,
0.004629629629629629,
0.010973936899862825,
0.021433470507544586,
0.037037037037037035,
0.05881344307270234,
0.0877914951989026,
0.125,
0.1714677640603567,
0.22822359396433475,
0.2962962962962963,
0.37671467764060357,
0.4705075445816187,
0.5787037037037038,
0.7023319615912208,
0.8424211248285322,
1,
],
out_cubic: [
0,
0.15757887517146785,
0.2976680384087792,
0.42129629629629617,
0.5294924554183813,
0.6232853223593964,
0.7037037037037036,
0.7717764060356652,
0.8285322359396433,
0.875,
0.9122085048010974,
0.9411865569272977,
0.9629629629629629,
0.9785665294924554,
0.9890260631001372,
0.9953703703703703,
0.9986282578875172,
0.9998285322359396,
1,
],
inOut_cubic: [
0,
0.0006858710562414266,
0.0054869684499314125,
0.018518518518518517,
0.0438957475994513,
0.08573388203017834,
0.14814814814814814,
0.23525377229080935,
0.3511659807956104,
0.5,
0.6488340192043895,
0.7647462277091908,
0.8518518518518519,
0.9142661179698217,
0.9561042524005487,
0.9814814814814815,
0.9945130315500685,
0.9993141289437586,
1,
],
in_sin: [
0,
0.003805301908254455,
0.01519224698779198,
0.03407417371093169,
0.06030737921409157,
0.09369221296335006,
0.1339745962155613,
0.1808479557110082,
0.233955556881022,
0.2928932188134524,
0.35721239031346064,
0.42642356364895384,
0.4999999999999999,
0.5773817382593005,
0.6579798566743311,
0.7411809548974793,
0.8263518223330696,
0.9128442572523416,
0.9999999999999999,
],
out_sin: [
0,
0.08715574274765817,
0.17364817766693033,
0.25881904510252074,
0.3420201433256687,
0.42261826174069944,
0.49999999999999994,
0.573576436351046,
0.6427876096865393,
0.7071067811865475,
0.766044443118978,
0.8191520442889918,
0.8660254037844386,
0.9063077870366499,
0.9396926207859083,
0.9659258262890683,
0.984807753012208,
0.9961946980917455,
1,
],
inOut_sin: [
0,
0.00759612349389599,
0.030153689607045786,
0.06698729810778065,
0.116977778440511,
0.17860619515673032,
0.24999999999999994,
0.32898992833716556,
0.4131759111665348,
0.49999999999999994,
0.5868240888334652,
0.6710100716628343,
0.7499999999999999,
0.8213938048432696,
0.883022221559489,
0.9330127018922194,
0.9698463103929542,
0.9924038765061041,
1,
],
in_exp: [
0,
0.0014352875901128893,
0.002109491677524035,
0.0031003926796253885,
0.004556754060844206,
0.006697218616039631,
0.009843133202303688,
0.014466792379488908,
0.021262343752724643,
0.03125,
0.045929202883612456,
0.06750373368076916,
0.09921256574801243,
0.1458161299470146,
0.2143109957132682,
0.31498026247371835,
0.46293735614364506,
0.6803950000871883,
1,
],
out_exp: [
0,
0.31960499991281155,
0.5370626438563548,
0.6850197375262816,
0.7856890042867318,
0.8541838700529854,
0.9007874342519875,
0.9324962663192309,
0.9540707971163875,
0.96875,
0.9787376562472754,
0.9855332076205111,
0.9901568667976963,
0.9933027813839603,
0.9954432459391558,
0.9968996073203746,
0.9978905083224759,
0.9985647124098871,
1,
],
inOut_exp: [
0,
0.0010547458387620175,
0.002278377030422103,
0.004921566601151844,
0.010631171876362321,
0.022964601441806228,
0.049606282874006216,
0.1071554978566341,
0.23146867807182253,
0.5,
0.7685313219281775,
0.892844502143366,
0.9503937171259937,
0.9770353985581938,
0.9893688281236377,
0.9950784333988482,
0.9977216229695779,
0.998945254161238,
1,
],
in_circle: [
0,
0.0015444024660317135,
0.006192010000093506,
0.013986702816730645,
0.025003956956430873,
0.03935464078941209,
0.057190958417936644,
0.07871533601238889,
0.10419358352238339,
0.1339745962155614,
0.1685205807169019,
0.20845517506805522,
0.2546440075000701,
0.3083389112228482,
0.37146063894529113,
0.4472292016074334,
0.5418771527091488,
0.6713289009389102,
1,
],
out_circle: [
0,
0.3286710990610898,
0.45812284729085123,
0.5527707983925666,
0.6285393610547089,
0.6916610887771518,
0.7453559924999298,
0.7915448249319448,
0.8314794192830981,
0.8660254037844386,
0.8958064164776166,
0.9212846639876111,
0.9428090415820634,
0.9606453592105879,
0.9749960430435691,
0.9860132971832694,
0.9938079899999065,
0.9984555975339683,
1,
],
inOut_circle: [
0,
0.003096005000046753,
0.012501978478215436,
0.028595479208968322,
0.052096791761191696,
0.08426029035845095,
0.12732200375003505,
0.18573031947264557,
0.2709385763545744,
0.5,
0.7290614236454256,
0.8142696805273546,
0.8726779962499649,
0.915739709641549,
0.9479032082388084,
0.9714045207910317,
0.9874980215217846,
0.9969039949999532,
1,
],
in_back_: [
0,
-0.004788556241426612,
-0.017301289437585736,
-0.0347587962962963,
-0.05438167352537723,
-0.07339051783264748,
-0.08900592592592595,
-0.09844849451303156,
-0.0989388203017833,
-0.08769750000000004,
-0.06194513031550073,
-0.018902307956104283,
0.044210370370370254,
0.13017230795610413,
0.2417629080932785,
0.3817615740740742,
0.5529477091906719,
0.7581007167352535,
0.9999999999999998,
],
out_back_: [
2.220446049250313e-16,
0.24189928326474652,
0.44705229080932807,
0.6182384259259258,
0.7582370919067215,
0.8698276920438959,
0.9557896296296297,
1.0189023079561044,
1.0619451303155008,
1.0876975,
1.0989388203017834,
1.0984484945130315,
1.089005925925926,
1.0733905178326475,
1.0543816735253773,
1.0347587962962963,
1.0173012894375857,
1.0047885562414267,
1,
],
};
Object.keys(Samples).forEach(function(type) {
it('should ease ' + type, function() {
const [modeName, easingName, isFunction] = type.split('_');
let easing = Easing[easingName];
if (isFunction !== undefined) {
easing = easing();
}
const computed = sampleEasingFunction(Easing[modeName](easing));
const samples = Samples[type];
computed.forEach((value, key) => {
expect(value).toBeCloseTo(samples[key], 2);
});
});
});
});
@@ -0,0 +1,316 @@
/**
* 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
*/
'use strict';
const AnimatedInterpolation = require('../nodes/AnimatedInterpolation');
const Easing = require('../Easing');
describe('Interpolation', () => {
it('should work with defaults', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [0, 1],
});
expect(interpolation(0)).toBe(0);
expect(interpolation(0.5)).toBe(0.5);
expect(interpolation(0.8)).toBe(0.8);
expect(interpolation(1)).toBe(1);
});
it('should work with output range', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [100, 200],
});
expect(interpolation(0)).toBe(100);
expect(interpolation(0.5)).toBe(150);
expect(interpolation(0.8)).toBe(180);
expect(interpolation(1)).toBe(200);
});
it('should work with input range', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [100, 200],
outputRange: [0, 1],
});
expect(interpolation(100)).toBe(0);
expect(interpolation(150)).toBe(0.5);
expect(interpolation(180)).toBe(0.8);
expect(interpolation(200)).toBe(1);
});
it('should throw for non monotonic input ranges', () => {
expect(() =>
AnimatedInterpolation.__createInterpolation({
inputRange: [0, 2, 1],
outputRange: [0, 1, 2],
}),
).toThrow();
expect(() =>
AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1, 2],
outputRange: [0, 3, 1],
}),
).not.toThrow();
});
it('should work with empty input range', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 10, 10],
outputRange: [1, 2, 3],
extrapolate: 'extend',
});
expect(interpolation(0)).toBe(1);
expect(interpolation(5)).toBe(1.5);
expect(interpolation(10)).toBe(2);
expect(interpolation(10.1)).toBe(3);
expect(interpolation(15)).toBe(3);
});
it('should work with empty output range', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [1, 2, 3],
outputRange: [0, 10, 10],
extrapolate: 'extend',
});
expect(interpolation(0)).toBe(-10);
expect(interpolation(1.5)).toBe(5);
expect(interpolation(2)).toBe(10);
expect(interpolation(2.5)).toBe(10);
expect(interpolation(3)).toBe(10);
expect(interpolation(4)).toBe(10);
});
it('should work with easing', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [0, 1],
easing: Easing.quad,
});
expect(interpolation(0)).toBe(0);
expect(interpolation(0.5)).toBe(0.25);
expect(interpolation(0.9)).toBe(0.81);
expect(interpolation(1)).toBe(1);
});
it('should work with extrapolate', () => {
let interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'extend',
easing: Easing.quad,
});
expect(interpolation(-2)).toBe(4);
expect(interpolation(2)).toBe(4);
interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
easing: Easing.quad,
});
expect(interpolation(-2)).toBe(0);
expect(interpolation(2)).toBe(1);
interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'identity',
easing: Easing.quad,
});
expect(interpolation(-2)).toBe(-2);
expect(interpolation(2)).toBe(2);
});
it('should work with keyframes with extrapolate', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 10, 100, 1000],
outputRange: [0, 5, 50, 500],
extrapolate: true,
});
expect(interpolation(-5)).toBe(-2.5);
expect(interpolation(0)).toBe(0);
expect(interpolation(5)).toBe(2.5);
expect(interpolation(10)).toBe(5);
expect(interpolation(50)).toBe(25);
expect(interpolation(100)).toBe(50);
expect(interpolation(500)).toBe(250);
expect(interpolation(1000)).toBe(500);
expect(interpolation(2000)).toBe(1000);
});
it('should work with keyframes without extrapolate', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1, 2],
outputRange: [0.2, 1, 0.2],
extrapolate: 'clamp',
});
expect(interpolation(5)).toBeCloseTo(0.2);
});
it('should throw for an infinite input range', () => {
expect(() =>
AnimatedInterpolation.__createInterpolation({
inputRange: [-Infinity, Infinity],
outputRange: [0, 1],
}),
).toThrow();
expect(() =>
AnimatedInterpolation.__createInterpolation({
inputRange: [-Infinity, 0, Infinity],
outputRange: [1, 2, 3],
}),
).not.toThrow();
});
it('should work with negative infinite', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [-Infinity, 0],
outputRange: [-Infinity, 0],
easing: Easing.quad,
extrapolate: 'identity',
});
expect(interpolation(-Infinity)).toBe(-Infinity);
expect(interpolation(-100)).toBeCloseTo(-10000);
expect(interpolation(-10)).toBeCloseTo(-100);
expect(interpolation(0)).toBeCloseTo(0);
expect(interpolation(1)).toBeCloseTo(1);
expect(interpolation(100)).toBeCloseTo(100);
});
it('should work with positive infinite', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [5, Infinity],
outputRange: [5, Infinity],
easing: Easing.quad,
extrapolate: 'identity',
});
expect(interpolation(-100)).toBeCloseTo(-100);
expect(interpolation(-10)).toBeCloseTo(-10);
expect(interpolation(0)).toBeCloseTo(0);
expect(interpolation(5)).toBeCloseTo(5);
expect(interpolation(6)).toBeCloseTo(5 + 1);
expect(interpolation(10)).toBeCloseTo(5 + 25);
expect(interpolation(100)).toBeCloseTo(5 + 95 * 95);
expect(interpolation(Infinity)).toBe(Infinity);
});
it('should work with output ranges as string', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'],
});
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.2)');
expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.4)');
});
it('should work with output ranges as short hex string', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['#024', '#9BF'],
});
expect(interpolation(0)).toBe('rgba(0, 34, 68, 1)');
expect(interpolation(0.5)).toBe('rgba(77, 111, 162, 1)');
expect(interpolation(1)).toBe('rgba(153, 187, 255, 1)');
});
it('should work with output ranges as long hex string', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['#FF9500', '#87FC70'],
});
expect(interpolation(0)).toBe('rgba(255, 149, 0, 1)');
expect(interpolation(0.5)).toBe('rgba(195, 201, 56, 1)');
expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
});
it('should work with output ranges with mixed hex and rgba strings', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'],
});
expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.4)');
expect(interpolation(0.5)).toBe('rgba(118, 186, 126, 0.7)');
expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
});
it('should work with negative and decimal values in string ranges', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['-100.5deg', '100deg'],
});
expect(interpolation(0)).toBe('-100.5deg');
expect(interpolation(0.5)).toBe('-0.25deg');
expect(interpolation(1)).toBe('100deg');
});
it('should crash when chaining an interpolation that returns a string', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: [0, 1],
});
expect(() => {
interpolation('45rad');
}).toThrow();
});
it('should support a mix of color patterns', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1, 2],
outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)', 'red'],
});
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.5)');
expect(interpolation(1.5)).toBe('rgba(153, 75, 125, 1)');
expect(interpolation(2)).toBe('rgba(255, 0, 0, 1)');
});
it('should crash when defining output range with different pattern', () => {
expect(() =>
AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['20deg', '30rad'],
}),
).toThrow();
});
it('should round the alpha channel of a color to the nearest thousandth', () => {
const interpolation = AnimatedInterpolation.__createInterpolation({
inputRange: [0, 1],
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'],
});
expect(interpolation(1e-12)).toBe('rgba(0, 0, 0, 0)');
expect(interpolation(2 / 3)).toBe('rgba(0, 0, 0, 0.667)');
});
});
@@ -1,16 +1,16 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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
* @oncall react_native
* @emails oncall+react_native
*/
'use strict';
import TimingAnimation from '../animations/TimingAnimation';
const TimingAnimation = require('../animations/TimingAnimation');
describe('Timing Animation', () => {
it('should return array of 61 items', () => {
+145
View File
@@ -0,0 +1,145 @@
/**
* Portions 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.
*
* @emails oncall+react_native
* @flow
* @format
*/
/**
* BezierEasing - use bezier curve for transition easing function
* https://github.com/gre/bezier-easing
* @copyright 2014-2015 Gaetan Renaudeau. MIT License.
*/
'use strict';
const bezier = require('../bezier');
const identity = function(x) {
return x;
};
function assertClose(a, b, precision = 3) {
expect(a).toBeCloseTo(b, precision);
}
function makeAssertCloseWithPrecision(precision) {
return function(a, b) {
assertClose(a, b, precision);
};
}
function allEquals(be1, be2, samples, assertion) {
if (!assertion) {
assertion = assertClose;
}
for (let i = 0; i <= samples; ++i) {
const x = i / samples;
assertion(be1(x), be2(x));
}
}
function repeat(n) {
return function(f) {
for (let i = 0; i < n; ++i) {
f();
}
};
}
describe('bezier', function() {
it('should be a function', function() {
expect(typeof bezier === 'function').toBe(true);
});
it('should creates an object', function() {
expect(typeof bezier(0, 0, 1, 1) === 'function').toBe(true);
});
it('should fail with wrong arguments', function() {
expect(function() {
bezier(0.5, 0.5, -5, 0.5);
}).toThrow();
expect(function() {
bezier(0.5, 0.5, 5, 0.5);
}).toThrow();
expect(function() {
bezier(-2, 0.5, 0.5, 0.5);
}).toThrow();
expect(function() {
bezier(2, 0.5, 0.5, 0.5);
}).toThrow();
});
describe('linear curves', function() {
it('should be linear', function() {
allEquals(bezier(0, 0, 1, 1), bezier(1, 1, 0, 0), 100);
allEquals(bezier(0, 0, 1, 1), identity, 100);
});
});
describe('common properties', function() {
it('should be the right value at extremes', function() {
repeat(10)(function() {
const a = Math.random(),
b = 2 * Math.random() - 0.5,
c = Math.random(),
d = 2 * Math.random() - 0.5;
const easing = bezier(a, b, c, d);
expect(easing(0)).toBe(0);
expect(easing(1)).toBe(1);
});
});
it('should approach the projected value of its x=y projected curve', function() {
repeat(10)(function() {
const a = Math.random(),
b = Math.random(),
c = Math.random(),
d = Math.random();
const easing = bezier(a, b, c, d);
const projected = bezier(b, a, d, c);
const composed = function(x) {
return projected(easing(x));
};
allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2));
});
});
});
describe('two same instances', function() {
it('should be strictly equals', function() {
repeat(10)(function() {
const a = Math.random(),
b = 2 * Math.random() - 0.5,
c = Math.random(),
d = 2 * Math.random() - 0.5;
allEquals(bezier(a, b, c, d), bezier(a, b, c, d), 100, 0);
});
});
});
describe('symmetric curves', function() {
it('should have a central value y~=0.5 at x=0.5', function() {
repeat(10)(function() {
const a = Math.random(),
b = 2 * Math.random() - 0.5,
c = 1 - a,
d = 1 - b;
const easing = bezier(a, b, c, d);
assertClose(easing(0.5), 0.5, 2);
});
});
it('should be symmetrical', function() {
repeat(10)(function() {
const a = Math.random(),
b = 2 * Math.random() - 0.5,
c = 1 - a,
d = 1 - b;
const easing = bezier(a, b, c, d);
const sym = function(x) {
return 1 - easing(1 - x);
};
allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2));
});
});
});
});
@@ -0,0 +1,67 @@
/**
* 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.
*
* @emails oncall+react_native
* @flow strict-local
* @format
*/
'use strict';
const createAnimatedComponent = require('../createAnimatedComponent');
const createAnimatedComponentInjection = require('../createAnimatedComponentInjection');
const React = require('react');
function injected<TProps: {...}, TInstance>(
Component: React.AbstractComponent<TProps, TInstance>,
): React.AbstractComponent<TProps, TInstance> {
return createAnimatedComponent(Component);
}
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
});
test('does nothing without injection', () => {
expect(typeof createAnimatedComponent).toBe('function');
expect(createAnimatedComponent).not.toBe(injected);
});
test('injection overrides `createAnimatedComponent`', () => {
createAnimatedComponentInjection.inject(injected);
expect(createAnimatedComponent).toBe(injected);
});
test('injection errors if called too late', () => {
jest.spyOn(console, 'error').mockReturnValue(undefined);
// Causes `createAnimatedComponent` to be initialized.
createAnimatedComponent;
createAnimatedComponentInjection.inject(injected);
expect(createAnimatedComponent).not.toBe(injected);
expect(console.error).toBeCalledWith(
'createAnimatedComponentInjection: Must be called before `createAnimatedComponent`.',
);
});
test('injection errors if called more than once', () => {
jest.spyOn(console, 'error').mockReturnValue(undefined);
createAnimatedComponentInjection.inject(injected);
expect(createAnimatedComponent).toBe(injected);
expect(console.error).not.toBeCalled();
createAnimatedComponentInjection.inject(injected);
expect(console.error).toBeCalledWith(
'createAnimatedComponentInjection: Cannot be called more than once.',
);
});
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -10,10 +10,9 @@
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedValue from '../nodes/AnimatedValue';
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import type AnimatedValue from '../nodes/AnimatedValue';
export type EndResult = {finished: boolean, ...};
export type EndCallback = (result: EndResult) => void;
@@ -21,7 +20,6 @@ export type EndCallback = (result: EndResult) => void;
export type AnimationConfig = {
isInteraction?: boolean,
useNativeDriver: boolean,
platformConfig?: PlatformConfig,
onComplete?: ?EndCallback,
iterations?: number,
};
@@ -31,7 +29,7 @@ let startNativeAnimationNextId = 1;
// Important note: start() and stop() will only be called at most once.
// Once an animation has been stopped or finished its course, it will
// not be reused.
export default class Animation {
class Animation {
__active: boolean;
__isInteraction: boolean;
__nativeId: number;
@@ -67,13 +65,12 @@ export default class Animation {
startNativeAnimationWaitId,
);
try {
const config = this.__getNativeAnimationConfig();
animatedValue.__makeNative(config.platformConfig);
animatedValue.__makeNative();
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
NativeAnimatedHelper.API.startAnimatingNode(
this.__nativeId,
animatedValue.__getNativeTag(),
config,
this.__getNativeAnimationConfig(),
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this.__debouncedOnEnd.bind(this),
);
@@ -86,3 +83,5 @@ export default class Animation {
}
}
}
module.exports = Animation;
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -10,13 +10,13 @@
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
const Animation = require('./Animation');
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
import type AnimatedValue from '../nodes/AnimatedValue';
import type {AnimationConfig, EndCallback} from './Animation';
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import Animation from './Animation';
export type DecayAnimationConfig = {
...AnimationConfig,
velocity:
@@ -35,7 +35,7 @@ export type DecayAnimationConfigSingle = {
deceleration?: number,
};
export default class DecayAnimation extends Animation {
class DecayAnimation extends Animation {
_startTime: number;
_lastValue: number;
_fromValue: number;
@@ -44,14 +44,12 @@ export default class DecayAnimation extends Animation {
_onUpdate: (value: number) => void;
_animationFrame: any;
_useNativeDriver: boolean;
_platformConfig: ?PlatformConfig;
constructor(config: DecayAnimationConfigSingle) {
super();
this._deceleration = config.deceleration ?? 0.998;
this._velocity = config.velocity;
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
this._platformConfig = config.platformConfig;
this._useNativeDriver = shouldUseNativeDriver(config);
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
this.__iterations = config.iterations ?? 1;
}
@@ -59,7 +57,6 @@ export default class DecayAnimation extends Animation {
__getNativeAnimationConfig(): {|
deceleration: number,
iterations: number,
platformConfig: ?PlatformConfig,
type: $TEMPORARY$string<'decay'>,
velocity: number,
|} {
@@ -68,7 +65,6 @@ export default class DecayAnimation extends Animation {
deceleration: this._deceleration,
velocity: this._velocity,
iterations: this.__iterations,
platformConfig: this._platformConfig,
};
}
@@ -122,3 +118,5 @@ export default class DecayAnimation extends Animation {
this.__debouncedOnEnd({finished: false});
}
}
module.exports = DecayAnimation;
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -10,17 +10,17 @@
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedInterpolation from '../nodes/AnimatedInterpolation';
import type AnimatedValue from '../nodes/AnimatedValue';
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
import type {AnimationConfig, EndCallback} from './Animation';
const AnimatedValue = require('../nodes/AnimatedValue');
const AnimatedValueXY = require('../nodes/AnimatedValueXY');
const AnimatedInterpolation = require('../nodes/AnimatedInterpolation');
const Animation = require('./Animation');
const SpringConfig = require('../SpringConfig');
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import AnimatedColor from '../nodes/AnimatedColor';
import * as SpringConfig from '../SpringConfig';
import Animation from './Animation';
import invariant from 'invariant';
const invariant = require('invariant');
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
import type {AnimationConfig, EndCallback} from './Animation';
export type SpringAnimationConfig = {
...AnimationConfig,
@@ -33,15 +33,7 @@ export type SpringAnimationConfig = {
...
}
| AnimatedValueXY
| {
r: number,
g: number,
b: number,
a: number,
...
}
| AnimatedColor
| AnimatedInterpolation<number>,
| AnimatedInterpolation,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
restSpeedThreshold?: number,
@@ -64,7 +56,7 @@ export type SpringAnimationConfig = {
export type SpringAnimationConfigSingle = {
...AnimationConfig,
toValue: number,
toValue: number | AnimatedValue | AnimatedInterpolation,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
restSpeedThreshold?: number,
@@ -79,7 +71,7 @@ export type SpringAnimationConfigSingle = {
delay?: number,
};
export default class SpringAnimation extends Animation {
class SpringAnimation extends Animation {
_overshootClamping: boolean;
_restDisplacementThreshold: number;
_restSpeedThreshold: number;
@@ -87,7 +79,7 @@ export default class SpringAnimation extends Animation {
_startPosition: number;
_lastPosition: number;
_fromValue: number;
_toValue: number;
_toValue: any;
_stiffness: number;
_damping: number;
_mass: number;
@@ -100,7 +92,6 @@ export default class SpringAnimation extends Animation {
_onUpdate: (value: number) => void;
_animationFrame: any;
_useNativeDriver: boolean;
_platformConfig: ?PlatformConfig;
constructor(config: SpringAnimationConfigSingle) {
super();
@@ -112,8 +103,7 @@ export default class SpringAnimation extends Animation {
this._lastVelocity = config.velocity ?? 0;
this._toValue = config.toValue;
this._delay = config.delay ?? 0;
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
this._platformConfig = config.platformConfig;
this._useNativeDriver = shouldUseNativeDriver(config);
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
this.__iterations = config.iterations ?? 1;
@@ -172,7 +162,6 @@ export default class SpringAnimation extends Animation {
initialVelocity: number,
iterations: number,
mass: number,
platformConfig: ?PlatformConfig,
overshootClamping: boolean,
restDisplacementThreshold: number,
restSpeedThreshold: number,
@@ -191,7 +180,6 @@ export default class SpringAnimation extends Animation {
initialVelocity: this._initialVelocity ?? this._lastVelocity,
toValue: this._toValue,
iterations: this.__iterations,
platformConfig: this._platformConfig,
};
}
@@ -368,3 +356,5 @@ export default class SpringAnimation extends Animation {
this.__debouncedOnEnd({finished: false});
}
}
module.exports = SpringAnimation;
@@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
* 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.
@@ -10,18 +10,16 @@
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {RgbaValue} from '../nodes/AnimatedColor';
import type AnimatedInterpolation from '../nodes/AnimatedInterpolation';
import type AnimatedValue from '../nodes/AnimatedValue';
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
const AnimatedValue = require('../nodes/AnimatedValue');
const AnimatedValueXY = require('../nodes/AnimatedValueXY');
const AnimatedInterpolation = require('../nodes/AnimatedInterpolation');
const Animation = require('./Animation');
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
import type {AnimationConfig, EndCallback} from './Animation';
import NativeAnimatedHelper from '../NativeAnimatedHelper';
import AnimatedColor from '../nodes/AnimatedColor';
import Animation from './Animation';
export type TimingAnimationConfig = $ReadOnly<{
export type TimingAnimationConfig = {
...AnimationConfig,
toValue:
| number
@@ -32,35 +30,34 @@ export type TimingAnimationConfig = $ReadOnly<{
...
}
| AnimatedValueXY
| RgbaValue
| AnimatedColor
| AnimatedInterpolation<number>,
| AnimatedInterpolation,
easing?: (value: number) => number,
duration?: number,
delay?: number,
}>;
};
export type TimingAnimationConfigSingle = $ReadOnly<{
export type TimingAnimationConfigSingle = {
...AnimationConfig,
toValue: number,
toValue: number | AnimatedValue | AnimatedInterpolation,
easing?: (value: number) => number,
duration?: number,
delay?: number,
}>;
};
let _easeInOut;
function easeInOut() {
if (!_easeInOut) {
const Easing = require('../Easing').default;
const Easing = require('../Easing');
// $FlowFixMe[method-unbinding]
_easeInOut = Easing.inOut(Easing.ease);
}
return _easeInOut;
}
export default class TimingAnimation extends Animation {
class TimingAnimation extends Animation {
_startTime: number;
_fromValue: number;
_toValue: number;
_toValue: any;
_duration: number;
_delay: number;
_easing: (value: number) => number;
@@ -68,7 +65,6 @@ export default class TimingAnimation extends Animation {
_animationFrame: any;
_timeout: any;
_useNativeDriver: boolean;
_platformConfig: ?PlatformConfig;
constructor(config: TimingAnimationConfigSingle) {
super();
@@ -77,8 +73,7 @@ export default class TimingAnimation extends Animation {
this._duration = config.duration ?? 500;
this._delay = config.delay ?? 0;
this.__iterations = config.iterations ?? 1;
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
this._platformConfig = config.platformConfig;
this._useNativeDriver = shouldUseNativeDriver(config);
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
}
@@ -95,7 +90,6 @@ export default class TimingAnimation extends Animation {
frames,
toValue: this._toValue,
iterations: this.__iterations,
platformConfig: this._platformConfig,
};
}
@@ -170,3 +164,5 @@ export default class TimingAnimation extends Animation {
this.__debouncedOnEnd({finished: false});
}
}
module.exports = TimingAnimation;

Some files were not shown because too many files have changed in this diff Show More