diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a7d38a0b3f1df..13b480ad30ab85 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,8 +47,8 @@ references: # ------------------------- dependency_versions: xcode_version: &xcode_version "14.3.0" - nodelts_image: &nodelts_image "cimg/node:18.12.1" - nodeprevlts_image: &nodeprevlts_image "cimg/node:16.18.1" + nodelts_image: &nodelts_image "cimg/node:20.2.0" + nodeprevlts_image: &nodeprevlts_image "cimg/node:18.12.1" # ------------------------- # Cache Key Anchors @@ -60,8 +60,8 @@ references: gems_cache_key: &gems_cache_key v1-gems-{{ checksum "Gemfile.lock" }} gradle_cache_key: &gradle_cache_key v1-gradle-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}-{{ checksum "packages/react-native/ReactAndroid/gradle.properties" }} hermes_workspace_cache_key: &hermes_workspace_cache_key v5-hermes-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/hermes/hermesversion" }} - hermes_workspace_debug_cache_key: &hermes_workspace_debug_cache_key v2-hermes-{{ .Environment.CIRCLE_JOB }}-debug-{{ checksum "/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }} - hermes_workspace_release_cache_key: &hermes_workspace_release_cache_key v2-hermes-{{ .Environment.CIRCLE_JOB }}-release-{{ checksum "/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }} + hermes_workspace_debug_cache_key: &hermes_workspace_debug_cache_key v2-hermes-{{ .Environment.CIRCLE_JOB }}-debug-{{ checksum "/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }}-{{ checksum "packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh" }} + hermes_workspace_release_cache_key: &hermes_workspace_release_cache_key v2-hermes-{{ .Environment.CIRCLE_JOB }}-release-{{ checksum "/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }}-{{ checksum "packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh" }} hermes_linux_cache_key: &hermes_linux_cache_key v1-hermes-{{ .Environment.CIRCLE_JOB }}-linux-{{ checksum "/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }} hermes_windows_cache_key: &hermes_windows_cache_key v2-hermes-{{ .Environment.CIRCLE_JOB }}-windows-{{ checksum "/Users/circleci/project/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }} hermes_tarball_debug_cache_key: &hermes_tarball_debug_cache_key v4-hermes-tarball-debug-{{ checksum "/tmp/hermes/hermesversion" }}-{{ checksum "/tmp/react-native-version" }} @@ -795,7 +795,7 @@ jobs: name: Create Android template project command: | REPO_ROOT=$(pwd) - node ./scripts/set-rn-template-version.js "file:$REPO_ROOT/build/$(cat build/react-native-package-version)" + node ./scripts/update-template-package.js "{\"react-native\":\"file:$REPO_ROOT/build/$(cat build/react-native-package-version)\"}" node ./scripts/template/initialize.js --reactNativeRootPath $REPO_ROOT --templateName $PROJECT_NAME --templateConfigPath "$REPO_ROOT/packages/react-native" --directory "/tmp/$PROJECT_NAME" - run: name: Build the template application for << parameters.flavor >> with Architecture set to << parameters.architecture >>, and using the << parameters.jsengine>> JS engine. @@ -879,17 +879,13 @@ jobs: REPO_ROOT=$(pwd) PACKAGE=$(cat build/react-native-package-version) PATH_TO_PACKAGE="$REPO_ROOT/build/$PACKAGE" - node ./scripts/set-rn-template-version.js "file:$PATH_TO_PACKAGE" + node ./scripts/update-template-package.js "{\"react-native\":\"file:$PATH_TO_PACKAGE\"}" node ./scripts/template/initialize.js --reactNativeRootPath $REPO_ROOT --templateName $PROJECT_NAME --templateConfigPath "$REPO_ROOT/packages/react-native" --directory "/tmp/$PROJECT_NAME" - run: name: Install iOS dependencies - Configuration << parameters.flavor >>; New Architecture << parameters.architecture >>; JS Engine << parameters.jsengine>>; Flipper << parameters.flipper >> command: | cd /tmp/$PROJECT_NAME/ios - if [[ << parameters.flavor >> == "Release" ]]; then - export PRODUCTION=1 - fi - if [[ << parameters.architecture >> == "NewArch" ]]; then export RCT_NEW_ARCH_ENABLED=1 fi @@ -1132,7 +1128,7 @@ jobs: # ------------------------- prepare_hermes_workspace: docker: - - image: debian:11 + - image: debian:bullseye environment: - HERMES_WS_DIR: *hermes_workspace_root - HERMES_VERSION_FILE: "packages/react-native/sdks/.hermesversion" diff --git a/.flowconfig b/.flowconfig index d313861fc746d3..f9ab44caa8f632 100644 --- a/.flowconfig +++ b/.flowconfig @@ -31,6 +31,7 @@ packages/react-native/flow/ [options] enums=true +conditional_type=true emoji=true @@ -75,4 +76,4 @@ untyped-import untyped-type-import [version] -^0.207.0 +^0.209.0 diff --git a/.flowconfig.android b/.flowconfig.android index 543e30dc6034b2..155704621ef37f 100644 --- a/.flowconfig.android +++ b/.flowconfig.android @@ -31,6 +31,7 @@ packages/react-native/flow/ [options] enums=true +conditional_type=true emoji=true @@ -75,4 +76,4 @@ untyped-import untyped-type-import [version] -^0.207.0 +^0.209.0 diff --git a/.gitignore b/.gitignore index 491d740ad1a070..5f30b988d17c85 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ DerivedData *.xcuserstate project.xcworkspace **/.xcode.env.local +/poackages/react-native/sdks/downloads/ # Gradle /build/ diff --git a/.prettierrc b/.prettierrc index fd47e617ecc8ea..8a48dc429e1577 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,15 @@ "requirePragma": true, "singleQuote": true, "trailingComma": "all", - "endOfLine": "lf" + "endOfLine": "lf", + "overrides": [ + { + "files": [ + "*.js" + ], + "options": { + "parser": "hermes" + } + } + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 315c44efb08823..cbd665ac8382e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## v0.71.11 + +### Changed + +- Bump CLI to 10.2.4 and Metro to 0.73.10 ([69804c70cb](https://github.com/facebook/react-native/commit/69804c70cb5c1afba934e55d7c4d694450c918f0) by [@kelset](https://github.com/kelset)) + +#### iOS specific + +- Prefer `Content-Location` header in bundle response as JS source URL ([671ea383fe](https://github.com/facebook/react-native/commit/671ea383fe45dd9834a0c0481360de050df7f0c9) by [@robhogan](https://github.com/robhogan)) + +### Fixed + +#### Android specific + +- Fixed crash occurring in certain native views when handling keyboard events. ([f7e35d4ef7](https://github.com/facebook/react-native/commit/f7e35d4ef7d68d06fba1439c0aa6d9ed05b58a7f) by [@aleqsio](https://github.com/aleqsio)) +- Prevent crash on OnePlus/Oppo devices in runAnimationStep ([f2c05142](https://github.com/facebook/react-native/commit/f2c05142259563b892e593b5a018bdbb6a0cf177) by [@hsource](https://github.com/hsource)) +- Revert "fix: border width top/bottom not matching the border radius" to fix border styling issues ([fd8a19d](https://github.com/facebook/react-native/commit/fd8a19d5e2bc00f29b3cd992d24790084cc34cbd) by [@kelset](https://github.com/kelset)) + +#### iOS specific + +- Make 0.71 compatible with Xcode 15 (thanks to @AlexanderEggers for the commit in main) ([5bd1a4256e](https://github.com/facebook/react-native/commit/5bd1a4256e0f55bada2b3c277e1dc8aba67a57ce) by [@kelset](https://github.com/kelset)) + +## v0.71.10 + +### Fixed + +#### Android specific + +- Bump RNGP to 0.71.19 ([3be3a7d1a2](https://github.com/facebook/react-native/commit/3be3a7d1a2840a045892ddd8e5f2263028e15127) by [@kelset](https://github.com/kelset)) + - contains: RNGP dependency substitutions for fork with different Maven group ([012e4bd654](https://github.com/facebook/react-native/commit/012e4bd654f1eee2b00a066ba50a7f9c44cc305b) by [@douglowder](https://github.com/douglowder)) + +## v0.71.9 + +### Fixed + +- VirtualizedList scrollToEnd with no data ([98009ad94b](https://github.com/facebook/react-native/commit/98009ad94b92320307f2721ee39dbeb9152c0a58) by [@Andarius](https://github.com/Andarius)) +- Allow string `transform` style in TypeScript ([2558c3d4f5](https://github.com/facebook/react-native/commit/2558c3d4f56776699602b116aff8c22b8bfa176a) by [@NickGerleman](https://github.com/NickGerleman)) +- Fix autoComplete type for TextInput ([94356e14ec](https://github.com/facebook/react-native/commit/94356e14ec0562a1fd5a208d93021f102ba9565e) by [@iRoachie](https://github.com/iRoachie)) + ## v0.71.8 ### Fixed @@ -606,6 +645,22 @@ Read the [announcement blogpost here](https://reactnative.dev/blog/2023/01/12/ve - Bump terser minor version to mitigate CVE-2022-25858 ([743f9ff63b](https://github.com/facebook/react-native/commit/743f9ff63bf1e3825a1788978a9f6bad8ebddc0d) by [@GijsWeterings](https://github.com/GijsWeterings)) +## v0.70.10 + +### Fixed + +#### Android specific + +- Prevent crash on OnePlus/Oppo devices in runAnimationStep ([c05d822f7d](https://github.com/facebook/react-native/commit/c05d822f7daa92e8af2ec2cd97a9897425624cc2) by [@hsource](https://github.com/hsource)) + +#### iOS specific + +- USE_HERMES envvar check fixed in react-native-xcode.sh. ([61106ac680](https://github.com/facebook/react-native/commit/61106ac6805cddef97e16e473b155abdad701797)) by [@kidroca](https://github.com/kidroca)) +- USE_HERMES envvar check fixed in react-native-xcode.sh. Now source maps are generated by default. ([8ad63714](https://github.com/facebook/react-native/commit/8ad63714ed3070aa9fdf95b702d89ef8fb423d9d)) by [@dmytrorykun](https://github.com/dmytrorykun)) +- USE_HERMES envvar check fixed in react-native-xcode.sh. ([4108b3](https://github.com/facebook/react-native/commit/4108b374385f1ede69e82ca0f8ca6d6585aee8c4)) by [@dmytrorykun](https://github.com/dmytrorykun)) +- When source maps are enabled, clean up temporary files from the build directory. Reduces bundle size by at least 1MB. ([bad3949](https://github.com/facebook/react-native/commit/bad39493b976b425fdf72cd8cf1543a375d612ab)) by [@dmytrorykun](https://github.com/dmytrorykun)) +- Make 0.70 compatible with Xcode 15 (thanks to @AlexanderEggers for the commit in main) ([c5e549e694](https://github.com/facebook/react-native/commit/c5e549e694607cd576be8fcb5ed909fec2ed6dce)) + ## v0.70.9 ### Changed @@ -953,6 +1008,14 @@ Read the [announcement blogpost here](https://reactnative.dev/blog/2023/01/12/ve - Add GitHub token permissions for workflows ([3da3d82320](https://github.com/facebook/react-native/commit/3da3d82320bd035c6bd361a82ea12a70dba4e851) by [@varunsh-coder](https://github.com/varunsh-coder)) - Bump RCT-Folly to 2021-07-22 ([68f3a42fc7](https://github.com/facebook/react-native/commit/68f3a42fc7380051714253f43b42175de361f8bd) by [@luissantana](https://github.com/luissantana)) +## v0.69.11 + +### Fixed + +#### iOS specific + +- Make 0.69 compatible with Xcode 15 (thanks to @AlexanderEggers for the commit in main) ([37e8df1cdc](https://github.com/facebook/react-native/commit/37e8df1cdce4a66763c720b1b0768d049def9518)) + ## v0.69.10 ### Fixed diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000000..b93be90515ccd0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1b42c65535c204..28ae98f546a027 100644 --- a/README.md +++ b/README.md @@ -143,5 +143,5 @@ React Native is MIT licensed, as found in the [LICENSE][l] file. React Native documentation is Creative Commons licensed, as found in the [LICENSE-docs][ld] file. -[l]: https://github.com/facebook/react-native/blob/HEAD/LICENSE -[ld]: https://github.com/facebook/react-native/blob/HEAD/LICENSE-docs +[l]: https://github.com/facebook/react-native/blob/main/LICENSE +[ld]: https://github.com/facebook/react-native/blob/main/LICENSE-docs diff --git a/jest/preprocessor.js b/jest/preprocessor.js index cc0a5af4618e59..b971b4260c769f 100644 --- a/jest/preprocessor.js +++ b/jest/preprocessor.js @@ -89,9 +89,10 @@ module.exports = { ); }, - getCacheKey: (createCacheKeyFunction([ + // $FlowFixMe[signature-verification-failure] + getCacheKey: createCacheKeyFunction([ __filename, require.resolve('metro-react-native-babel-transformer'), require.resolve('@babel/core/package.json'), - ]) /*: any */), + ]), }; diff --git a/package.json b/package.json index 40ccb1a12cabb2..2f86089c27f280 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "eslint-plugin-react-native": "^4.0.0", "eslint-plugin-redundant-undefined": "^0.4.0", "eslint-plugin-relay": "^1.8.3", - "flow-bin": "^0.207.0", + "flow-bin": "^0.209.0", "hermes-eslint": "0.12.0", "inquirer": "^7.1.0", "jest": "^29.2.1", @@ -90,6 +90,7 @@ "mkdirp": "^0.5.1", "mock-fs": "^5.1.4", "prettier": "2.8.8", + "prettier-plugin-hermes-parser": "0.12.1", "react": "18.2.0", "react-test-renderer": "18.2.0", "shelljs": "^0.8.5", diff --git a/packages/react-native-codegen/e2e/__tests__/modules/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/e2e/__tests__/modules/__snapshots__/GenerateModuleH-test.js.snap index cee8760bb3bbef..e7d50c7f5c1f9c 100644 --- a/packages/react-native-codegen/e2e/__tests__/modules/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/e2e/__tests__/modules/__snapshots__/GenerateModuleH-test.js.snap @@ -206,7 +206,7 @@ struct Bridging { } } - static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusRegularEnum value, const std::shared_ptr &jsInvoker) { + static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusRegularEnum value) { if (value == NativeEnumTurboModuleStatusRegularEnum::Active) { return bridging::toJs(rt, \\"Active\\"); } else if (value == NativeEnumTurboModuleStatusRegularEnum::Paused) { @@ -238,7 +238,7 @@ struct Bridging { } } - static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusStrEnum value, const std::shared_ptr &jsInvoker) { + static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusStrEnum value) { if (value == NativeEnumTurboModuleStatusStrEnum::Active) { return bridging::toJs(rt, \\"active\\"); } else if (value == NativeEnumTurboModuleStatusStrEnum::Paused) { @@ -270,7 +270,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusNumEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusNumEnum value) { if (value == NativeEnumTurboModuleStatusNumEnum::Active) { return bridging::toJs(rt, 2); } else if (value == NativeEnumTurboModuleStatusNumEnum::Paused) { @@ -302,7 +302,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusFractionEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusFractionEnum value) { if (value == NativeEnumTurboModuleStatusFractionEnum::Active) { return bridging::toJs(rt, 0.2f); } else if (value == NativeEnumTurboModuleStatusFractionEnum::Paused) { @@ -2149,7 +2149,7 @@ struct Bridging { } } - static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusRegularEnum value, const std::shared_ptr &jsInvoker) { + static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusRegularEnum value) { if (value == NativeEnumTurboModuleStatusRegularEnum::Active) { return bridging::toJs(rt, \\"Active\\"); } else if (value == NativeEnumTurboModuleStatusRegularEnum::Paused) { @@ -2181,7 +2181,7 @@ struct Bridging { } } - static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusStrEnum value, const std::shared_ptr &jsInvoker) { + static jsi::String toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusStrEnum value) { if (value == NativeEnumTurboModuleStatusStrEnum::Active) { return bridging::toJs(rt, \\"active\\"); } else if (value == NativeEnumTurboModuleStatusStrEnum::Paused) { @@ -2213,7 +2213,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusNumEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusNumEnum value) { if (value == NativeEnumTurboModuleStatusNumEnum::Active) { return bridging::toJs(rt, 2); } else if (value == NativeEnumTurboModuleStatusNumEnum::Paused) { @@ -2245,7 +2245,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusFractionEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, NativeEnumTurboModuleStatusFractionEnum value) { if (value == NativeEnumTurboModuleStatusFractionEnum::Active) { return bridging::toJs(rt, 0.2f); } else if (value == NativeEnumTurboModuleStatusFractionEnum::Paused) { diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 8cd7acf64c8230..e4d8f53b5b5d60 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -343,7 +343,7 @@ struct Bridging<${enumName}> { ${fromCases} } - static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value, const std::shared_ptr &jsInvoker) { + static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) { ${toCases} } };`; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 040a3e9613672c..be1d454013a153 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -222,7 +222,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleCxxNumEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleCxxNumEnum value) { if (value == SampleTurboModuleCxxNumEnum::ONE) { return bridging::toJs(rt, 1); } else if (value == SampleTurboModuleCxxNumEnum::TWO) { @@ -252,7 +252,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleCxxFloatEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleCxxFloatEnum value) { if (value == SampleTurboModuleCxxFloatEnum::POINT_ZERO) { return bridging::toJs(rt, 0.0f); } else if (value == SampleTurboModuleCxxFloatEnum::POINT_ONE) { @@ -282,7 +282,7 @@ struct Bridging { } } - static jsi::String toJs(jsi::Runtime &rt, SampleTurboModuleCxxStringEnum value, const std::shared_ptr &jsInvoker) { + static jsi::String toJs(jsi::Runtime &rt, SampleTurboModuleCxxStringEnum value) { if (value == SampleTurboModuleCxxStringEnum::HELLO) { return bridging::toJs(rt, \\"hello\\"); } else if (value == SampleTurboModuleCxxStringEnum::GoodBye) { @@ -1220,7 +1220,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleNumEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleNumEnum value) { if (value == SampleTurboModuleNumEnum::ONE) { return bridging::toJs(rt, 1); } else if (value == SampleTurboModuleNumEnum::TWO) { @@ -1250,7 +1250,7 @@ struct Bridging { } } - static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleFloatEnum value, const std::shared_ptr &jsInvoker) { + static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleFloatEnum value) { if (value == SampleTurboModuleFloatEnum::POINT_ZERO) { return bridging::toJs(rt, 0.0f); } else if (value == SampleTurboModuleFloatEnum::POINT_ONE) { @@ -1280,7 +1280,7 @@ struct Bridging { } } - static jsi::String toJs(jsi::Runtime &rt, SampleTurboModuleStringEnum value, const std::shared_ptr &jsInvoker) { + static jsi::String toJs(jsi::Runtime &rt, SampleTurboModuleStringEnum value) { if (value == SampleTurboModuleStringEnum::HELLO) { return bridging::toJs(rt, \\"hello\\"); } else if (value == SampleTurboModuleStringEnum::GoodBye) { diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js index a09730d1ce36bb..722d44299eba8b 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js @@ -42,12 +42,14 @@ const { typeEnumResolution, Visitor, emitStringProp, + emitObjectProp, } = require('../parsers-primitives.js'); const {MockedParser} = require('../parserMock'); const {emitUnion} = require('../parsers-primitives'); const {UnsupportedUnionTypeAnnotationParserError} = require('../errors'); const {FlowParser} = require('../flow/parser'); const {TypeScriptParser} = require('../typescript/parser'); +const {extractArrayElementType} = require('../flow/components/events'); const parser = new MockedParser(); const flowParser = new FlowParser(); @@ -1671,3 +1673,106 @@ describe('emitBoolProp', () => { }); }); }); + +describe('emitObjectProp', () => { + const name = 'someProp'; + describe('when property is optional', () => { + it('returns optional Object Prop', () => { + const typeAnnotation = { + type: 'GenericTypeAnnotation', + id: { + name: 'ObjectTypeAnnotation', + }, + properties: [ + { + key: { + name: 'someKey', + }, + optional: true, + value: { + type: 'StringTypeAnnotation', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + }, + ], + }; + const result = emitObjectProp( + name, + true, + flowParser, + typeAnnotation, + extractArrayElementType, + ); + const expected = { + name: 'someProp', + optional: true, + typeAnnotation: { + properties: [ + { + name: 'someKey', + optional: true, + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + ], + type: 'ObjectTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when property is required', () => { + it('returns required Object Prop', () => { + const typeAnnotation = { + type: 'GenericTypeAnnotation', + id: { + name: 'ObjectTypeAnnotation', + }, + properties: [ + { + key: { + name: 'someKey', + }, + optional: false, + value: { + type: 'StringTypeAnnotation', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + }, + ], + }; + const result = emitObjectProp( + name, + false, + flowParser, + typeAnnotation, + extractArrayElementType, + ); + const expected = { + name: 'someProp', + optional: false, + typeAnnotation: { + properties: [ + { + name: 'someKey', + optional: false, + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + ], + type: 'ObjectTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); +}); diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js index 6ec9df5dd539de..4df7f89cc1df6b 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js @@ -358,6 +358,78 @@ describe('FlowParser', () => { expect(parser.interfaceDeclaration).toEqual('InterfaceDeclaration'); }); }); + + describe('extractTypeFromTypeAnnotation', () => { + it('should return the name if typeAnnotation is GenericTypeAnnotation', () => { + const typeAnnotation = { + type: 'GenericTypeAnnotation', + id: { + name: 'SomeType', + }, + }; + + expect(parser.extractTypeFromTypeAnnotation(typeAnnotation)).toEqual( + 'SomeType', + ); + }); + + it('should return the type if typeAnnotation is not GenericTypeAnnotation', () => { + const typeAnnotation = { + type: 'SomeOtherType', + }; + + expect(parser.extractTypeFromTypeAnnotation(typeAnnotation)).toEqual( + 'SomeOtherType', + ); + }); + }); + + describe('getObjectProperties', () => { + it('returns properties of an object represented by a type annotation', () => { + const properties = [ + { + type: 'ObjectTypeProperty', + key: { + type: 'Identifier', + name: 'a', + }, + value: { + type: 'StringTypeAnnotation', + range: [], + }, + }, + { + type: 'ObjectTypeProperty', + key: { + type: 'Identifier', + name: 'b', + }, + optional: true, + value: { + type: 'BooleanTypeAnnotation', + range: [], + }, + }, + ]; + + const typeAnnotation = { + type: 'TypeAlias', + properties: properties, + }; + + const expected = properties; + + expect(parser.getObjectProperties(typeAnnotation)).toEqual(expected); + }); + + it('returns undefined if typeAnnotation does not have properties', () => { + const declaration = { + type: 'TypeAlias', + }; + + expect(parser.getObjectProperties(declaration)).toEqual(undefined); + }); + }); }); describe('TypeScriptParser', () => { @@ -677,4 +749,76 @@ describe('TypeScriptParser', () => { expect(parser.interfaceDeclaration).toEqual('TSInterfaceDeclaration'); }); }); + + describe('extractTypeFromTypeAnnotation', () => { + it('should return the name if typeAnnotation is TSTypeReference', () => { + const typeAnnotation = { + type: 'TSTypeReference', + typeName: { + name: 'SomeType', + }, + }; + + expect(parser.extractTypeFromTypeAnnotation(typeAnnotation)).toEqual( + 'SomeType', + ); + }); + + it('should return the type if typeAnnotation is not TSTypeReference', () => { + const typeAnnotation = { + type: 'SomeOtherType', + }; + + expect(parser.extractTypeFromTypeAnnotation(typeAnnotation)).toEqual( + 'SomeOtherType', + ); + }); + }); + + describe('getObjectProperties', () => { + it('returns members of an object represented by a type annotation', () => { + const members = [ + { + type: 'ObjectTypeProperty', + key: { + type: 'Identifier', + name: 'a', + }, + value: { + type: 'StringTypeAnnotation', + range: [], + }, + }, + { + type: 'ObjectTypeProperty', + key: { + type: 'Identifier', + name: 'b', + }, + optional: true, + value: { + type: 'BooleanTypeAnnotation', + range: [], + }, + }, + ]; + + const typeAnnotation = { + type: 'TypeAlias', + members: members, + }; + + const expected = members; + + expect(parser.getObjectProperties(typeAnnotation)).toEqual(expected); + }); + + it('returns undefined if typeAnnotation does not have members', () => { + const declaration = { + type: 'TypeAlias', + }; + + expect(parser.getObjectProperties(declaration)).toEqual(undefined); + }); + }); }); diff --git a/packages/react-native-codegen/src/parsers/flow/components/events.js b/packages/react-native-codegen/src/parsers/flow/components/events.js index 71c865dce29d12..f0fd64e4eb187f 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/events.js +++ b/packages/react-native-codegen/src/parsers/flow/components/events.js @@ -22,7 +22,10 @@ const { throwIfBubblingTypeIsNull, throwIfArgumentPropsAreNull, } = require('../../error-utils'); -const {getEventArgument} = require('../../parsers-commons'); +const { + getEventArgument, + buildPropertiesForEvent, +} = require('../../parsers-commons'); const { emitBoolProp, emitDoubleProp, @@ -30,17 +33,18 @@ const { emitMixedProp, emitStringProp, emitInt32Prop, + emitObjectProp, } = require('../../parsers-primitives'); function getPropertyType( /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's * LTI update could not be added via codemod */ - name, + name: string, optional: boolean, typeAnnotation: $FlowFixMe, parser: Parser, ): NamedShape { - const type = extractTypeFromTypeAnnotation(typeAnnotation); + const type = parser.extractTypeFromTypeAnnotation(typeAnnotation); switch (type) { case 'BooleanTypeAnnotation': @@ -61,16 +65,13 @@ function getPropertyType( parser, ); case 'ObjectTypeAnnotation': - return { + return emitObjectProp( name, optional, - typeAnnotation: { - type: 'ObjectTypeAnnotation', - properties: typeAnnotation.properties.map(member => - buildPropertiesForEvent(member, parser), - ), - }, - }; + parser, + typeAnnotation, + extractArrayElementType, + ); case 'UnionTypeAnnotation': return { name, @@ -99,7 +100,7 @@ function extractArrayElementType( name: string, parser: Parser, ): EventTypeAnnotation { - const type = extractTypeFromTypeAnnotation(typeAnnotation); + const type = parser.extractTypeFromTypeAnnotation(typeAnnotation); switch (type) { case 'BooleanTypeAnnotation': @@ -125,9 +126,11 @@ function extractArrayElementType( case 'ObjectTypeAnnotation': return { type: 'ObjectTypeAnnotation', - properties: typeAnnotation.properties.map(member => - buildPropertiesForEvent(member, parser), - ), + properties: parser + .getObjectProperties(typeAnnotation) + .map(member => + buildPropertiesForEvent(member, parser, getPropertyType), + ), }; case 'ArrayTypeAnnotation': return { @@ -164,12 +167,6 @@ function prettify(jsonObject: $FlowFixMe): string { return JSON.stringify(jsonObject, null, 2); } -function extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string { - return typeAnnotation.type === 'GenericTypeAnnotation' - ? typeAnnotation.id.name - : typeAnnotation.type; -} - function findEventArgumentsAndType( parser: Parser, typeAnnotation: $FlowFixMe, @@ -229,19 +226,6 @@ function findEventArgumentsAndType( } } -function buildPropertiesForEvent( - /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's - * LTI update could not be added via codemod */ - property, - parser: Parser, -): NamedShape { - const name = property.key.name; - const optional = parser.isOptionalProperty(property); - const typeAnnotation = parser.getTypeAnnotationFromProperty(property); - - return getPropertyType(name, optional, typeAnnotation, parser); -} - function buildEventSchema( types: TypeMap, property: EventTypeAST, @@ -283,8 +267,8 @@ function buildEventSchema( type: 'EventTypeAnnotation', argument: getEventArgument( nonNullableArgumentProps, - buildPropertiesForEvent, parser, + getPropertyType, ), }, }; @@ -298,8 +282,8 @@ function buildEventSchema( type: 'EventTypeAnnotation', argument: getEventArgument( nonNullableArgumentProps, - buildPropertiesForEvent, parser, + getPropertyType, ), }, }; @@ -327,4 +311,5 @@ function getEvents( module.exports = { getEvents, + extractArrayElementType, }; diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index 9447e3497b009e..676a206588dad5 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -533,6 +533,16 @@ class FlowParser implements Parser { genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string { return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}') or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; } + + extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string { + return typeAnnotation.type === 'GenericTypeAnnotation' + ? typeAnnotation.id.name + : typeAnnotation.type; + } + + getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.properties; + } } module.exports = { diff --git a/packages/react-native-codegen/src/parsers/parser.js b/packages/react-native-codegen/src/parsers/parser.js index c40e50df0cc924..98901b41755c3b 100644 --- a/packages/react-native-codegen/src/parsers/parser.js +++ b/packages/react-native-codegen/src/parsers/parser.js @@ -400,4 +400,18 @@ export interface Parser { * Given a unsupported typeAnnotation, returns an error message. */ genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string; + + /** + * Given a type annotation, it extracts the type. + * @parameter typeAnnotation: the annotation for a type in the AST. + * @returns: the extracted type. + */ + extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string; + + /** + * Given a typeAnnotation return the properties of an object. + * @parameter property + * @returns: the properties of an object represented by a type annotation. + */ + getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe; } diff --git a/packages/react-native-codegen/src/parsers/parserMock.js b/packages/react-native-codegen/src/parsers/parserMock.js index eccb65db50a43f..6e5f78de77da53 100644 --- a/packages/react-native-codegen/src/parsers/parserMock.js +++ b/packages/react-native-codegen/src/parsers/parserMock.js @@ -472,4 +472,14 @@ export class MockedParser implements Parser { genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string { return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}') or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; } + + extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string { + return typeAnnotation.type === 'GenericTypeAnnotation' + ? typeAnnotation.id.name + : typeAnnotation.type; + } + + getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.properties; + } } diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 41f135329425ed..9629c5121d371b 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -894,16 +894,18 @@ function buildPropSchema( * LTI update could not be added via codemod */ function getEventArgument( argumentProps: PropAST, - buildPropertiesForEvent: ( - property: PropAST, + parser: Parser, + getPropertyType: ( + name: $FlowFixMe, + optional: boolean, + typeAnnotation: $FlowFixMe, parser: Parser, ) => NamedShape, - parser: Parser, ): ObjectTypeAnnotation { return { type: 'ObjectTypeAnnotation', properties: argumentProps.map(member => - buildPropertiesForEvent(member, parser), + buildPropertiesForEvent(member, parser, getPropertyType), ), }; } @@ -1053,6 +1055,23 @@ function handleGenericTypeAnnotation( }; } +function buildPropertiesForEvent( + property: $FlowFixMe, + parser: Parser, + getPropertyType: ( + name: $FlowFixMe, + optional: boolean, + typeAnnotation: $FlowFixMe, + parser: Parser, + ) => NamedShape, +): NamedShape { + const name = property.key.name; + const optional = parser.isOptionalProperty(property); + const typeAnnotation = parser.getTypeAnnotationFromProperty(property); + + return getPropertyType(name, optional, typeAnnotation, parser); +} + module.exports = { wrapModuleSchema, unwrapNullable, @@ -1079,4 +1098,5 @@ module.exports = { getCommandProperties, handleGenericTypeAnnotation, getTypeResolutionStatus, + buildPropertiesForEvent, }; diff --git a/packages/react-native-codegen/src/parsers/parsers-primitives.js b/packages/react-native-codegen/src/parsers/parsers-primitives.js index db5668cc5706a4..845a42e073ffca 100644 --- a/packages/react-native-codegen/src/parsers/parsers-primitives.js +++ b/packages/react-native-codegen/src/parsers/parsers-primitives.js @@ -657,6 +657,24 @@ function emitMixedProp( }; } +function emitObjectProp( + name: string, + optional: boolean, + parser: Parser, + typeAnnotation: $FlowFixMe, + extractArrayElementType: ( + typeAnnotation: $FlowFixMe, + name: string, + parser: Parser, + ) => EventTypeAnnotation, +): NamedShape { + return { + name, + optional, + typeAnnotation: extractArrayElementType(typeAnnotation, name, parser), + }; +} + module.exports = { emitArrayType, emitBoolean, @@ -687,4 +705,5 @@ module.exports = { typeEnumResolution, translateArrayTypeAnnotation, Visitor, + emitObjectProp, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/components/events.js b/packages/react-native-codegen/src/parsers/typescript/components/events.js index 5d12804238e1b1..230b9b512a6b7f 100644 --- a/packages/react-native-codegen/src/parsers/typescript/components/events.js +++ b/packages/react-native-codegen/src/parsers/typescript/components/events.js @@ -24,7 +24,10 @@ const { throwIfBubblingTypeIsNull, throwIfArgumentPropsAreNull, } = require('../../error-utils'); -const {getEventArgument} = require('../../parsers-commons'); +const { + getEventArgument, + buildPropertiesForEvent, +} = require('../../parsers-commons'); const { emitBoolProp, emitDoubleProp, @@ -32,6 +35,7 @@ const { emitMixedProp, emitStringProp, emitInt32Prop, + emitObjectProp, } = require('../../parsers-primitives'); function getPropertyType( /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's @@ -46,10 +50,7 @@ function getPropertyType( const topLevelType = parseTopLevelType(annotation); const typeAnnotation = topLevelType.type; const optional = optionalProperty || topLevelType.optional; - const type = - typeAnnotation.type === 'TSTypeReference' - ? typeAnnotation.typeName.name - : typeAnnotation.type; + const type = parser.extractTypeFromTypeAnnotation(typeAnnotation); switch (type) { case 'TSBooleanKeyword': @@ -63,16 +64,13 @@ function getPropertyType( case 'Float': return emitFloatProp(name, optional); case 'TSTypeLiteral': - return { + return emitObjectProp( name, optional, - typeAnnotation: { - type: 'ObjectTypeAnnotation', - properties: typeAnnotation.members.map(member => - buildPropertiesForEvent(member, parser), - ), - }, - }; + parser, + typeAnnotation, + extractArrayElementType, + ); case 'TSUnionType': return { name, @@ -91,7 +89,6 @@ function getPropertyType( typeAnnotation: extractArrayElementType(typeAnnotation, name, parser), }; default: - (type: empty); throw new Error(`Unable to determine event type for "${name}": ${type}`); } } @@ -101,7 +98,7 @@ function extractArrayElementType( name: string, parser: Parser, ): EventTypeAnnotation { - const type = extractTypeFromTypeAnnotation(typeAnnotation); + const type = parser.extractTypeFromTypeAnnotation(typeAnnotation); switch (type) { case 'TSParenthesizedType': @@ -135,9 +132,11 @@ function extractArrayElementType( case 'TSTypeLiteral': return { type: 'ObjectTypeAnnotation', - properties: typeAnnotation.members.map(member => - buildPropertiesForEvent(member, parser), - ), + properties: parser + .getObjectProperties(typeAnnotation) + .map(member => + buildPropertiesForEvent(member, parser, getPropertyType), + ), }; case 'TSArrayType': return { @@ -159,12 +158,6 @@ function extractArrayElementType( } } -function extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string { - return typeAnnotation.type === 'TSTypeReference' - ? typeAnnotation.typeName.name - : typeAnnotation.type; -} - function findEventArgumentsAndType( parser: Parser, typeAnnotation: $FlowFixMe, @@ -186,7 +179,7 @@ function findEventArgumentsAndType( if (typeAnnotation.type === 'TSTypeLiteral') { return { - argumentProps: typeAnnotation.members, + argumentProps: parser.getObjectProperties(typeAnnotation), paperTopLevelNameDeprecated: paperName, bubblingType, }; @@ -247,19 +240,6 @@ function findEventArgumentsAndType( } } -function buildPropertiesForEvent( - /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's - * LTI update could not be added via codemod */ - property, - parser: Parser, -): NamedShape { - const name = property.key.name; - const optional = parser.isOptionalProperty(property); - const typeAnnotation = parser.getTypeAnnotationFromProperty(property); - - return getPropertyType(name, optional, typeAnnotation, parser); -} - // $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser type EventTypeAST = Object; @@ -296,8 +276,8 @@ function buildEventSchema( type: 'EventTypeAnnotation', argument: getEventArgument( nonNullableArgumentProps, - buildPropertiesForEvent, parser, + getPropertyType, ), }, }; @@ -311,8 +291,8 @@ function buildEventSchema( type: 'EventTypeAnnotation', argument: getEventArgument( nonNullableArgumentProps, - buildPropertiesForEvent, parser, + getPropertyType, ), }, }; @@ -330,4 +310,5 @@ function getEvents( module.exports = { getEvents, + extractArrayElementType, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index d05ab5f055b065..4a18396b5c9625 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -547,6 +547,16 @@ class TypeScriptParser implements Parser { genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string { return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}'), an interface ('${this.interfaceDeclaration}'), or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; } + + extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string { + return typeAnnotation.type === 'TSTypeReference' + ? typeAnnotation.typeName.name + : typeAnnotation.type; + } + + getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.members; + } } module.exports = { diff --git a/packages/react-native/Libraries/Animated/AnimatedImplementation.js b/packages/react-native/Libraries/Animated/AnimatedImplementation.js index 7e25c65f2a461e..e516115733360b 100644 --- a/packages/react-native/Libraries/Animated/AnimatedImplementation.js +++ b/packages/react-native/Libraries/Animated/AnimatedImplementation.js @@ -378,7 +378,7 @@ const parallel = function ( } animations.forEach((animation, idx) => { - const cb = function (endResult: EndResult | {finished: boolean}) { + const cb = function (endResult: EndResult) { hasEnded[idx] = true; doneCount++; if (doneCount === animations.length) { diff --git a/packages/react-native/Libraries/Animated/NativeAnimatedModule.js b/packages/react-native/Libraries/Animated/NativeAnimatedModule.js index a080eb4d66eb0f..5094739f4fd90d 100644 --- a/packages/react-native/Libraries/Animated/NativeAnimatedModule.js +++ b/packages/react-native/Libraries/Animated/NativeAnimatedModule.js @@ -13,7 +13,7 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; import shouldUseTurboAnimatedModule from './shouldUseTurboAnimatedModule'; -type EndResult = {finished: boolean, ...}; +type EndResult = {finished: boolean, value?: number, ...}; type EndCallback = (result: EndResult) => void; type SaveValueCallback = (value: number) => void; diff --git a/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js b/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js index f9ea5881232273..35a4ffa16bf576 100644 --- a/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js +++ b/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js @@ -13,7 +13,7 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; import shouldUseTurboAnimatedModule from './shouldUseTurboAnimatedModule'; -type EndResult = {finished: boolean, ...}; +type EndResult = {finished: boolean, value?: number, ...}; type EndCallback = (result: EndResult) => void; type SaveValueCallback = (value: number) => void; diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js index 23e6a2123ddf8d..d331d283029c51 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js @@ -1027,21 +1027,17 @@ describe('Native Animated', () => { }).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'; - } + expect( + Animated.timing(opacity, { + toValue: 4, + duration: 500, + useNativeDriver: false, + }).start, + ).toThrow( + 'Attempting to run JS driven animation on animated node that has ' + + 'been moved to "native" earlier by starting an animation with ' + + '`useNativeDriver: true`', + ); }); it('fails for unsupported styles', () => { diff --git a/packages/react-native/Libraries/Animated/animations/Animation.js b/packages/react-native/Libraries/Animated/animations/Animation.js index f3d2ac6d3ac234..ad956cd79d9d6d 100644 --- a/packages/react-native/Libraries/Animated/animations/Animation.js +++ b/packages/react-native/Libraries/Animated/animations/Animation.js @@ -11,11 +11,13 @@ 'use strict'; import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type AnimatedNode from '../nodes/AnimatedNode'; import type AnimatedValue from '../nodes/AnimatedValue'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; +import AnimatedProps from '../nodes/AnimatedProps'; -export type EndResult = {finished: boolean, ...}; +export type EndResult = {finished: boolean, value?: number, ...}; export type EndCallback = (result: EndResult) => void; export type AnimationConfig = { @@ -37,6 +39,7 @@ export default class Animation { __nativeId: number; __onEnd: ?EndCallback; __iterations: number; + start( fromValue: number, onUpdate: (value: number) => void, @@ -44,22 +47,41 @@ export default class Animation { previousAnimation: ?Animation, animatedValue: AnimatedValue, ): void {} + stop(): void { if (this.__nativeId) { NativeAnimatedHelper.API.stopAnimation(this.__nativeId); } } + __getNativeAnimationConfig(): any { // Subclasses that have corresponding animation implementation done in native // should override this method throw new Error('This animation type cannot be offloaded to native'); } + // Helper function for subclasses to make sure onEnd is only called once. __debouncedOnEnd(result: EndResult): void { const onEnd = this.__onEnd; this.__onEnd = null; onEnd && onEnd(result); } + + __findAnimatedPropsNodes(node: AnimatedNode): Array { + const result = []; + + if (node instanceof AnimatedProps) { + result.push(node); + return result; + } + + for (const child of node.__getChildren()) { + result.push(...this.__findAnimatedPropsNodes(child)); + } + + return result; + } + __startNativeAnimation(animatedValue: AnimatedValue): void { const startNativeAnimationWaitId = `${startNativeAnimationNextId}:startAnimation`; startNativeAnimationNextId += 1; @@ -74,8 +96,23 @@ export default class Animation { this.__nativeId, animatedValue.__getNativeTag(), config, - // $FlowFixMe[method-unbinding] added when improving typing for this parameters - this.__debouncedOnEnd.bind(this), + result => { + this.__debouncedOnEnd(result); + + // When using natively driven animations, once the animation completes, + // we need to ensure that the JS side nodes are synced with the updated + // values. + const {value} = result; + if (value != null) { + animatedValue.__onAnimatedValueUpdateReceived(value); + + // Once the JS side node is synced with the updated values, trigger an + // update on the AnimatedProps nodes to call any registered callbacks. + this.__findAnimatedPropsNodes(animatedValue).forEach(node => + node.update(), + ); + } + }, ); } catch (e) { throw e; diff --git a/packages/react-native/Libraries/Animated/animations/DecayAnimation.js b/packages/react-native/Libraries/Animated/animations/DecayAnimation.js index f0042c5881dead..cc6a37bfaa3418 100644 --- a/packages/react-native/Libraries/Animated/animations/DecayAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/DecayAnimation.js @@ -85,6 +85,15 @@ export default class DecayAnimation extends Animation { this._onUpdate = onUpdate; this.__onEnd = onEnd; this._startTime = Date.now(); + + if (!this._useNativeDriver && animatedValue.__isNative === true) { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + if (this._useNativeDriver) { this.__startNativeAnimation(animatedValue); } else { diff --git a/packages/react-native/Libraries/Animated/animations/SpringAnimation.js b/packages/react-native/Libraries/Animated/animations/SpringAnimation.js index 69101dab030e8f..49855295b473ed 100644 --- a/packages/react-native/Libraries/Animated/animations/SpringAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/SpringAnimation.js @@ -221,6 +221,14 @@ export default class SpringAnimation extends Animation { } const start = () => { + if (!this._useNativeDriver && animatedValue.__isNative === true) { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + if (this._useNativeDriver) { this.__startNativeAnimation(animatedValue); } else { diff --git a/packages/react-native/Libraries/Animated/animations/TimingAnimation.js b/packages/react-native/Libraries/Animated/animations/TimingAnimation.js index 5c0c3ce38144de..a32c7543c68ba4 100644 --- a/packages/react-native/Libraries/Animated/animations/TimingAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/TimingAnimation.js @@ -112,6 +112,14 @@ export default class TimingAnimation extends Animation { this.__onEnd = onEnd; const start = () => { + if (!this._useNativeDriver && animatedValue.__isNative === true) { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + // Animations that sometimes have 0 duration and sometimes do not // still need to use the native driver when duration is 0 so as to // not cause intermixed JS and native animations. diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js b/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js index dc24a674bba4a7..fa4a82320e3069 100644 --- a/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js +++ b/packages/react-native/Libraries/Animated/nodes/AnimatedValueXY.js @@ -19,11 +19,7 @@ import invariant from 'invariant'; export type AnimatedValueXYConfig = $ReadOnly<{ useNativeDriver: boolean, }>; -type ValueXYListenerCallback = (value: { - x: number, - y: number, - ... -}) => mixed; +type ValueXYListenerCallback = (value: {x: number, y: number, ...}) => mixed; let _uniqueId = 1; @@ -135,11 +131,7 @@ export default class AnimatedValueXY extends AnimatedWithChildren { * See https://reactnative.dev/docs/animatedvaluexy#resetanimation */ resetAnimation( - callback?: (value: { - x: number, - y: number, - ... - }) => void, + callback?: (value: {x: number, y: number, ...}) => void, ): void { this.x.resetAnimation(); this.y.resetAnimation(); @@ -153,13 +145,7 @@ export default class AnimatedValueXY extends AnimatedWithChildren { * * See https://reactnative.dev/docs/animatedvaluexy#stopanimation */ - stopAnimation( - callback?: (value: { - x: number, - y: number, - ... - }) => void, - ): void { + stopAnimation(callback?: (value: {x: number, y: number, ...}) => void): void { this.x.stopAnimation(); this.y.stopAnimation(); callback && callback(this.__getValue()); diff --git a/packages/react-native/Libraries/Animated/useAnimatedProps.js b/packages/react-native/Libraries/Animated/useAnimatedProps.js index 865bddd8ca3cbe..bf3a6d140c97f9 100644 --- a/packages/react-native/Libraries/Animated/useAnimatedProps.js +++ b/packages/react-native/Libraries/Animated/useAnimatedProps.js @@ -66,7 +66,9 @@ export default function useAnimatedProps( // changes), but `setNativeView` already optimizes for that. node.setNativeView(instance); - // NOTE: This callback is only used by the JavaScript animation driver. + // NOTE: When using the JS animation driver, this callback is called on + // every animation frame. When using the native driver, this callback is + // called when the animation completes. onUpdateRef.current = () => { if ( process.env.NODE_ENV === 'test' || @@ -82,12 +84,6 @@ export default function useAnimatedProps( // $FlowIgnore[not-a-function] - Assume it's still a function. // $FlowFixMe[incompatible-use] instance.setNativeProps(node.__getAnimatedValue()); - } else { - throw new Error( - 'Attempting to run JS driven animation on animated node ' + - 'that has been moved to "native" earlier by starting an ' + - 'animation with `useNativeDriver: true`', - ); } }; diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index f2df4437c3da92..52763c21c4b7e9 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -36,6 +36,7 @@ * - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge moduleName:(NSString*)moduleName initProps:(NSDictionary *)initProps; * - (UIViewController *)createRootViewController; + * - (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController; * New Architecture: * - (BOOL)concurrentRootEnabled * - (BOOL)turboModuleEnabled; @@ -94,6 +95,16 @@ */ - (UIViewController *)createRootViewController; +/** + * It assigns the rootView to the rootViewController + * By default, it assigns the rootView to the view property of the rootViewController + * If you are not using a simple UIViewController, then there could be other methods to use to setup the rootView. + * For example: UISplitViewController requires `setViewController(_:for:)` + * + * @return: void + */ +- (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController; + /// This method controls whether the App will use RuntimeScheduler. Only applicable in the legacy architecture. /// /// @return: `YES` to use RuntimeScheduler, `NO` to use JavaScript scheduler. The default value is `YES`. diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index 808c315c2f1bf4..ecc438ca4a4f7a 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -77,7 +77,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [self createRootViewController]; - rootViewController.view = rootView; + [self setRootView:rootView toRootViewController:rootViewController]; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; @@ -129,6 +129,11 @@ - (UIViewController *)createRootViewController return [UIViewController new]; } +- (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController +{ + rootViewController.view = rootView; +} + - (BOOL)runtimeSchedulerEnabled { return YES; diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index 3cb77a170bb6af..c22ba9279aeb76 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -49,6 +49,7 @@ header_search_paths = [ "$(PODS_CONFIGURATION_BUILD_DIR)/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", "$(PODS_CONFIGURATION_BUILD_DIR)/React-RCTFabric/RCTFabric.framework/Headers/", "$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers/", + "${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers/" ] : []).map{|p| "\"#{p}\""}.join(" ") Pod::Spec.new do |s| @@ -93,11 +94,13 @@ Pod::Spec.new do |s| s.dependency "React-RCTFabric" s.dependency "React-graphics" s.dependency "React-debug" + s.dependency "React-utils" s.script_phases = { :name => "Generate Legacy Components Interop", :script => " -. ${PODS_ROOT}/../.xcode.env +WITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\" +source $WITH_ENVIRONMENT ${NODE_BINARY} ${REACT_NATIVE_PATH}/scripts/codegen/generate-legacy-interop-components.js -p #{ENV['APP_PATH']} -o ${REACT_NATIVE_PATH}/Libraries/AppDelegate ", :execution_position => :before_compile, diff --git a/packages/react-native/Libraries/AppState/AppState.d.ts b/packages/react-native/Libraries/AppState/AppState.d.ts index 45beaca9bde90b..6333c8190565ce 100644 --- a/packages/react-native/Libraries/AppState/AppState.d.ts +++ b/packages/react-native/Libraries/AppState/AppState.d.ts @@ -24,7 +24,7 @@ import {NativeEventSubscription} from '../EventEmitter/RCTNativeAppEventEmitter' * App States * active - The app is running in the foreground * background - The app is running in the background. The user is either in another app or on the home screen - * inactive [iOS] - This is a transition state that currently never happens for typical React Native apps. + * inactive [iOS] - This is a transition state that happens when the app launches, is asking for permissions or when a call or SMS message is received. * unknown [iOS] - Initial value until the current app state is determined * extension [iOS] - The app is running as an app extension * diff --git a/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js b/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js index 6c4cb1e2baa631..2bf4c041561fea 100644 --- a/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js +++ b/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js @@ -71,7 +71,7 @@ export default function useAndroidRippleForView( return { viewProps: - foreground === true + foreground === true && Platform.Version >= 23 ? {nativeForegroundAndroid: nativeRippleValue} : {nativeBackgroundAndroid: nativeRippleValue}, onPressIn(event: PressEvent): void { diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js index 866c4e97aaba7f..acb737fdfa593d 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js @@ -246,7 +246,18 @@ const ScrollViewStickyHeaderWithForwardedRef: React.AbstractComponent< clearTimeout(_timer.current); } }; - }, [nextHeaderLayoutY, measured, layoutHeight, layoutY, scrollViewHeight, scrollAnimatedValue, inverted, offset, animatedValueListener, isFabric]); + }, [ + nextHeaderLayoutY, + measured, + layoutHeight, + layoutY, + scrollViewHeight, + scrollAnimatedValue, + inverted, + offset, + animatedValueListener, + isFabric, + ]); const _onLayout = (event: LayoutEvent) => { setLayoutY(event.nativeEvent.layout.y); @@ -262,17 +273,7 @@ const ScrollViewStickyHeaderWithForwardedRef: React.AbstractComponent< const child = React.Children.only<$FlowFixMe>(props.children); - // TODO T68319535: remove this if NativeAnimated is rewritten for Fabric - const passthroughAnimatedPropExplicitValues = - isFabric && translateY != null - ? { - style: {transform: [{translateY: translateY}]}, - } - : null; - return ( - /* $FlowFixMe[prop-missing] passthroughAnimatedPropExplicitValues isn't properly - included in the Animated.View flow type. */ + ]}> {React.cloneElement(child, { style: styles.fill, // We transfer the child style to the wrapper. onLayout: undefined, // we call this manually through our this._onLayout diff --git a/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js index 6f693295320eb4..88d3cc8fe756e5 100644 --- a/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +++ b/packages/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js @@ -156,6 +156,7 @@ const RCTTextInputViewConfig = { showSoftInputOnFocus: true, autoFocus: true, lineBreakStrategyIOS: true, + smartInsertDelete: true, ...ConditionallyIgnoredEventHandlers({ onChange: true, onSelectionChange: true, diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts b/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts index 8badb2a9d39de5..97fe9371065419 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts @@ -291,6 +291,14 @@ export interface TextInputIOSProps { | 'hangul-word' | 'push-out' | undefined; + + /** + * If `false`, the iOS system will not insert an extra space after a paste operation + * neither delete one or two spaces after a cut or delete operation. + * + * The default value is `true`. + */ + smartInsertDelete?: boolean | undefined; } /** diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js b/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js index 7ed4579d4d87c8..be702737024815 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js @@ -295,6 +295,16 @@ type IOSProps = $ReadOnly<{| * @platform ios */ lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'), + + /** + * If `false`, the iOS system will not insert an extra space after a paste operation + * neither delete one or two spaces after a cut or delete operation. + * + * The default value is `true`. + * + * @platform ios + */ + smartInsertDelete?: ?boolean, |}>; type AndroidProps = $ReadOnly<{| diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 67ad18c0c19b42..657145ef2ad781 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -339,6 +339,16 @@ type IOSProps = $ReadOnly<{| * @platform ios */ lineBreakStrategyIOS?: ?('none' | 'standard' | 'hangul-word' | 'push-out'), + + /** + * If `false`, the iOS system will not insert an extra space after a paste operation + * neither delete one or two spaces after a cut or delete operation. + * + * The default value is `true`. + * + * @platform ios + */ + smartInsertDelete?: ?boolean, |}>; type AndroidProps = $ReadOnly<{| diff --git a/packages/react-native/Libraries/Core/setUpPerformanceObserver.js b/packages/react-native/Libraries/Core/setUpPerformanceObserver.js new file mode 100644 index 00000000000000..20e0f32f5c991c --- /dev/null +++ b/packages/react-native/Libraries/Core/setUpPerformanceObserver.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import {polyfillGlobal} from '../Utilities/PolyfillFunctions'; + +polyfillGlobal( + 'PerformanceObserver', + () => require('../WebPerformance/PerformanceObserver').default, +); diff --git a/packages/react-native/Libraries/Core/setUpRegeneratorRuntime.js b/packages/react-native/Libraries/Core/setUpRegeneratorRuntime.js index 425e715a1e3f5e..c334acca7cdd3f 100644 --- a/packages/react-native/Libraries/Core/setUpRegeneratorRuntime.js +++ b/packages/react-native/Libraries/Core/setUpRegeneratorRuntime.js @@ -22,8 +22,10 @@ let hasNativeGenerator; try { // If this function was lowered by regenerator-transform, it will try to // access `global.regeneratorRuntime` which doesn't exist yet and will throw. - hasNativeGenerator = hasNativeConstructor(function* () {}, - 'GeneratorFunction'); + hasNativeGenerator = hasNativeConstructor( + function* () {}, + 'GeneratorFunction', + ); } catch { // In this case, we know generators are not provided natively. hasNativeGenerator = false; diff --git a/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js b/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js index e9ade0185aac17..491f8d1019290a 100644 --- a/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js +++ b/packages/react-native/Libraries/DOM/Nodes/ReactNativeElement.js @@ -79,7 +79,10 @@ export default class ReactNativeElement if (node != null) { const offset = nullthrows(getFabricUIManager()).getOffset(node); - if (offset != null) { + // For children of the root node we currently return offset data + // but a `null` parent because the root node is not accessible + // in JavaScript yet. + if (offset != null && offset[0] != null) { const offsetParentInstanceHandle = offset[0]; const offsetParent = getPublicInstanceFromInternalInstanceHandle( offsetParentInstanceHandle, diff --git a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js index 163deca45fdc58..321bf45402789b 100644 --- a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js +++ b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js @@ -15,7 +15,11 @@ import type HTMLCollection from '../OldStyleCollections/HTMLCollection'; import {getFabricUIManager} from '../../ReactNative/FabricUIManager'; import DOMRect from '../Geometry/DOMRect'; import {createHTMLCollection} from '../OldStyleCollections/HTMLCollection'; -import ReadOnlyNode, {getChildNodes, getShadowNode} from './ReadOnlyNode'; +import ReadOnlyNode, { + getChildNodes, + getInstanceHandle, + getShadowNode, +} from './ReadOnlyNode'; import {getElementSibling} from './Utilities/Traversal'; import nullthrows from 'nullthrows'; @@ -55,7 +59,11 @@ export default class ReadOnlyElement extends ReadOnlyNode { } get id(): string { - throw new TypeError('Unimplemented'); + const instanceHandle = getInstanceHandle(this); + // TODO: migrate off this private React API + // $FlowExpectedError[incompatible-use] + const props = instanceHandle?.stateNode?.canonical?.currentProps; + return props?.id ?? props?.nativeID ?? ''; } get lastElementChild(): ReadOnlyElement | null { @@ -91,19 +99,41 @@ export default class ReadOnlyElement extends ReadOnlyNode { } get scrollHeight(): number { - throw new TypeError('Unimplemented'); + throw new Error('Unimplemented'); } get scrollLeft(): number { - throw new TypeError('Unimplemented'); + const node = getShadowNode(this); + + if (node != null) { + const scrollPosition = nullthrows(getFabricUIManager()).getScrollPosition( + node, + ); + if (scrollPosition != null) { + return scrollPosition[0]; + } + } + + return 0; } get scrollTop(): number { - throw new TypeError('Unimplemented'); + const node = getShadowNode(this); + + if (node != null) { + const scrollPosition = nullthrows(getFabricUIManager()).getScrollPosition( + node, + ); + if (scrollPosition != null) { + return scrollPosition[1]; + } + } + + return 0; } get scrollWidth(): number { - throw new TypeError('Unimplemented'); + throw new Error('Unimplemented'); } get tagName(): string { diff --git a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js index e92a298be913e8..8a95751f8e4798 100644 --- a/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js +++ b/packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js @@ -293,7 +293,7 @@ export default class ReadOnlyNode { const INSTANCE_HANDLE_KEY = Symbol('internalInstanceHandle'); -function getInstanceHandle(node: ReadOnlyNode): InternalInstanceHandle { +export function getInstanceHandle(node: ReadOnlyNode): InternalInstanceHandle { // $FlowExpectedError[prop-missing] return node[INSTANCE_HANDLE_KEY]; } diff --git a/packages/react-native/Libraries/Image/RCTImageLoader.h b/packages/react-native/Libraries/Image/RCTImageLoader.h index 592785573066db..ae9dc8069743e3 100644 --- a/packages/react-native/Libraries/Image/RCTImageLoader.h +++ b/packages/react-native/Libraries/Image/RCTImageLoader.h @@ -8,6 +8,7 @@ #import #import +#import #import #import #import @@ -34,3 +35,9 @@ @property (nonatomic, readonly) RCTImageLoader *imageLoader; @end + +@interface RCTBridgeProxy (RCTImageLoader) + +@property (nonatomic, readonly) RCTImageLoader *imageLoader; + +@end diff --git a/packages/react-native/Libraries/Image/RCTImageLoader.mm b/packages/react-native/Libraries/Image/RCTImageLoader.mm index bfcdf26a5d3cee..09317d99c2d653 100644 --- a/packages/react-native/Libraries/Image/RCTImageLoader.mm +++ b/packages/react-native/Libraries/Image/RCTImageLoader.mm @@ -1293,6 +1293,15 @@ - (RCTImageLoader *)imageLoader @end +@implementation RCTBridgeProxy (RCTImageLoader) + +- (RCTImageLoader *)imageLoader +{ + return [self moduleForClass:[RCTImageLoader class]]; +} + +@end + Class RCTImageLoaderCls(void) { return RCTImageLoader.class; diff --git a/packages/react-native/Libraries/Image/RCTImageStoreManager.h b/packages/react-native/Libraries/Image/RCTImageStoreManager.h index 389f613ea8afe1..61f324d51cd4e8 100644 --- a/packages/react-native/Libraries/Image/RCTImageStoreManager.h +++ b/packages/react-native/Libraries/Image/RCTImageStoreManager.h @@ -8,6 +8,7 @@ #import #import +#import #import @interface RCTImageStoreManager : NSObject @@ -44,3 +45,9 @@ @property (nonatomic, readonly) RCTImageStoreManager *imageStoreManager; @end + +@interface RCTBridgeProxy (RCTImageStoreManager) + +@property (nonatomic, readonly) RCTImageStoreManager *imageStoreManager; + +@end diff --git a/packages/react-native/Libraries/Image/RCTImageStoreManager.mm b/packages/react-native/Libraries/Image/RCTImageStoreManager.mm index acfa4d3434c4eb..242c42cce8a0e5 100644 --- a/packages/react-native/Libraries/Image/RCTImageStoreManager.mm +++ b/packages/react-native/Libraries/Image/RCTImageStoreManager.mm @@ -254,6 +254,15 @@ - (RCTImageStoreManager *)imageStoreManager @end +@implementation RCTBridgeProxy (RCTImageStoreManager) + +- (RCTImageStoreManager *)imageStoreManager +{ + return [self moduleForClass:[RCTImageStoreManager class]]; +} + +@end + Class RCTImageStoreManagerCls(void) { return RCTImageStoreManager.class; diff --git a/packages/react-native/Libraries/Interaction/JSEventLoopWatchdog.js b/packages/react-native/Libraries/Interaction/JSEventLoopWatchdog.js index 113d073ee7000c..1db16e53ed7abe 100644 --- a/packages/react-native/Libraries/Interaction/JSEventLoopWatchdog.js +++ b/packages/react-native/Libraries/Interaction/JSEventLoopWatchdog.js @@ -14,11 +14,7 @@ const infoLog = require('../Utilities/infoLog'); type Handler = { onIterate?: () => void, - onStall: (params: { - lastInterval: number, - busyTime: number, - ... - }) => ?string, + onStall: (params: {lastInterval: number, busyTime: number, ...}) => ?string, ... }; diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js index 097f152ed9f2d7..1f578db82f0f26 100644 --- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js +++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js @@ -88,7 +88,7 @@ const backgroundForLevel = (level: LogLevel) => default: 'transparent', pressed: LogBoxStyle.getFatalDarkColor(), }, - }[level]); + })[level]; function LogBoxInspectorHeaderButton( props: $ReadOnly<{| diff --git a/packages/react-native/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m b/packages/react-native/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m index 5c19386cd93650..b4aec0c33288fe 100644 --- a/packages/react-native/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m +++ b/packages/react-native/Libraries/NativeAnimation/Drivers/RCTDecayAnimation.m @@ -70,10 +70,10 @@ - (void)startAnimation - (void)stopAnimation { - _valueNode = nil; if (_callback) { - _callback(@[ @{@"finished" : @(_animationHasFinished)} ]); + _callback(@[ @{@"finished" : @(_animationHasFinished), @"value" : @(_valueNode.value)} ]); } + _valueNode = nil; } - (void)stepAnimationWithTime:(NSTimeInterval)currentTime diff --git a/packages/react-native/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m b/packages/react-native/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m index d2bf4cd618b24c..1de5cb13eb8ffa 100644 --- a/packages/react-native/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m +++ b/packages/react-native/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m @@ -76,10 +76,10 @@ - (void)startAnimation - (void)stopAnimation { - _valueNode = nil; if (_callback) { - _callback(@[ @{@"finished" : @(_animationHasFinished)} ]); + _callback(@[ @{@"finished" : @(_animationHasFinished), @"value" : @(_valueNode.value)} ]); } + _valueNode = nil; } - (void)stepAnimationWithTime:(NSTimeInterval)currentTime diff --git a/packages/react-native/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m b/packages/react-native/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m index 232916017607de..640107df09c287 100644 --- a/packages/react-native/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m +++ b/packages/react-native/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m @@ -96,10 +96,10 @@ - (void)startAnimation - (void)stopAnimation { - _valueNode = nil; if (_callback) { - _callback(@[ @{@"finished" : @(_animationHasFinished)} ]); + _callback(@[ @{@"finished" : @(_animationHasFinished), @"value" : @(_valueNode.value)} ]); } + _valueNode = nil; } - (void)stepAnimationWithTime:(NSTimeInterval)currentTime diff --git a/packages/react-native/Libraries/Network/RCTNetworking.h b/packages/react-native/Libraries/Network/RCTNetworking.h index 3ad70f0ce80fd5..fcc9fe81714a29 100644 --- a/packages/react-native/Libraries/Network/RCTNetworking.h +++ b/packages/react-native/Libraries/Network/RCTNetworking.h @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +#import #import #import #import @@ -61,6 +62,12 @@ @end +@interface RCTBridgeProxy (RCTNetworking) + +@property (nonatomic, readonly) RCTNetworking *networking; + +@end + // HACK: When uploading images/video from PHAssetLibrary, we change the URL scheme to be // ph-upload://. This is to ensure that we upload a full video when given a ph-upload:// URL, // instead of just the thumbnail. Consider the following problem: diff --git a/packages/react-native/Libraries/Network/RCTNetworking.mm b/packages/react-native/Libraries/Network/RCTNetworking.mm index 9b9c12711dea7c..4e78d8e798e8ae 100644 --- a/packages/react-native/Libraries/Network/RCTNetworking.mm +++ b/packages/react-native/Libraries/Network/RCTNetworking.mm @@ -762,6 +762,15 @@ - (RCTNetworking *)networking @end +@implementation RCTBridgeProxy (RCTNetworking) + +- (RCTNetworking *)networking +{ + return [self moduleForClass:[RCTNetworking class]]; +} + +@end + Class RCTNetworkingCls(void) { return RCTNetworking.class; diff --git a/packages/react-native/Libraries/ReactNative/AppRegistry.js b/packages/react-native/Libraries/ReactNative/AppRegistry.js index 011bb45d45c84c..afd4f08cff0774 100644 --- a/packages/react-native/Libraries/ReactNative/AppRegistry.js +++ b/packages/react-native/Libraries/ReactNative/AppRegistry.js @@ -31,7 +31,7 @@ type TaskCancelProvider = () => TaskCanceller; export type ComponentProvider = () => React$ComponentType; export type ComponentProviderInstrumentationHook = ( - component: ComponentProvider, + component_: ComponentProvider, scopedPerformanceLogger: IPerformanceLogger, ) => React$ComponentType; export type AppConfig = { diff --git a/packages/react-native/Libraries/ReactNative/FabricUIManager.js b/packages/react-native/Libraries/ReactNative/FabricUIManager.js index e0c062fb85498b..ad6c089b78fe02 100644 --- a/packages/react-native/Libraries/ReactNative/FabricUIManager.js +++ b/packages/react-native/Libraries/ReactNative/FabricUIManager.js @@ -20,77 +20,143 @@ import type { } from '../Renderer/shims/ReactNativeTypes'; import type {RootTag} from '../Types/RootTagTypes'; +import defineLazyObjectProperty from '../Utilities/defineLazyObjectProperty'; + export type NodeSet = Array; export type NodeProps = {...}; -export type Spec = {| +export interface Spec { +createNode: ( reactTag: number, viewName: string, rootTag: RootTag, props: NodeProps, instanceHandle: InternalInstanceHandle, - ) => Node, - +cloneNode: (node: Node) => Node, - +cloneNodeWithNewChildren: (node: Node) => Node, - +cloneNodeWithNewProps: (node: Node, newProps: NodeProps) => Node, - +cloneNodeWithNewChildrenAndProps: (node: Node, newProps: NodeProps) => Node, - +createChildSet: (rootTag: RootTag) => NodeSet, - +appendChild: (parentNode: Node, child: Node) => Node, - +appendChildToSet: (childSet: NodeSet, child: Node) => void, - +completeRoot: (rootTag: RootTag, childSet: NodeSet) => void, - +measure: (node: Node, callback: MeasureOnSuccessCallback) => void, + ) => Node; + +cloneNode: (node: Node) => Node; + +cloneNodeWithNewChildren: (node: Node) => Node; + +cloneNodeWithNewProps: (node: Node, newProps: NodeProps) => Node; + +cloneNodeWithNewChildrenAndProps: (node: Node, newProps: NodeProps) => Node; + +createChildSet: (rootTag: RootTag) => NodeSet; + +appendChild: (parentNode: Node, child: Node) => Node; + +appendChildToSet: (childSet: NodeSet, child: Node) => void; + +completeRoot: (rootTag: RootTag, childSet: NodeSet) => void; + +measure: (node: Node, callback: MeasureOnSuccessCallback) => void; +measureInWindow: ( node: Node, callback: MeasureInWindowOnSuccessCallback, - ) => void, + ) => void; +measureLayout: ( node: Node, relativeNode: Node, onFail: () => void, onSuccess: MeasureLayoutOnSuccessCallback, - ) => void, + ) => void; +configureNextLayoutAnimation: ( config: LayoutAnimationConfig, callback: () => void, // check what is returned here errorCallback: () => void, - ) => void, - +sendAccessibilityEvent: (node: Node, eventType: string) => void, - +findShadowNodeByTag_DEPRECATED: (reactTag: number) => ?Node, - +setNativeProps: (node: Node, newProps: NodeProps) => void, + ) => void; + +sendAccessibilityEvent: (node: Node, eventType: string) => void; + +findShadowNodeByTag_DEPRECATED: (reactTag: number) => ?Node; + +setNativeProps: (node: Node, newProps: NodeProps) => void; +dispatchCommand: ( node: Node, commandName: string, args: Array, - ) => void, + ) => void; /** * Support methods for the DOM-compatible APIs. */ - +getParentNode: (node: Node) => ?InternalInstanceHandle, - +getChildNodes: (node: Node) => $ReadOnlyArray, - +isConnected: (node: Node) => boolean, - +compareDocumentPosition: (node: Node, otherNode: Node) => number, - +getTextContent: (node: Node) => string, + +getParentNode: (node: Node) => ?InternalInstanceHandle; + +getChildNodes: (node: Node) => $ReadOnlyArray; + +isConnected: (node: Node) => boolean; + +compareDocumentPosition: (node: Node, otherNode: Node) => number; + +getTextContent: (node: Node) => string; +getBoundingClientRect: ( node: Node, ) => ?[ - /* x:*/ number, - /* y:*/ number, - /* width:*/ number, - /* height:*/ number, - ], + /* x: */ number, + /* y: */ number, + /* width: */ number, + /* height: */ number, + ]; +getOffset: ( node: Node, ) => ?[ /* offsetParent: */ InternalInstanceHandle, /* offsetTop: */ number, /* offsetLeft: */ number, - ], -|}; + ]; + +getScrollPosition: ( + node: Node, + ) => ?[/* scrollLeft: */ number, /* scrollTop: */ number]; +} + +let nativeFabricUIManagerProxy: ?Spec; + +// This is a list of all the methods in global.nativeFabricUIManager that we'll +// cache in JavaScript, as the current implementation of the binding +// creates a new host function every time methods are accessed. +const CACHED_PROPERTIES = [ + 'createNode', + 'cloneNode', + 'cloneNodeWithNewChildren', + 'cloneNodeWithNewProps', + 'cloneNodeWithNewChildrenAndProps', + 'createChildSet', + 'appendChild', + 'appendChildToSet', + 'completeRoot', + 'measure', + 'measureInWindow', + 'measureLayout', + 'configureNextLayoutAnimation', + 'sendAccessibilityEvent', + 'findShadowNodeByTag_DEPRECATED', + 'setNativeProps', + 'dispatchCommand', + 'getParentNode', + 'getChildNodes', + 'isConnected', + 'compareDocumentPosition', + 'getTextContent', + 'getBoundingClientRect', + 'getOffset', + 'getScrollPosition', +]; // This is exposed as a getter because apps using the legacy renderer AND // Fabric can define the binding lazily. If we evaluated the global and cached // it in the module we might be caching an `undefined` value before it is set. export function getFabricUIManager(): ?Spec { - return global.nativeFabricUIManager; + if ( + nativeFabricUIManagerProxy == null && + global.nativeFabricUIManager != null + ) { + nativeFabricUIManagerProxy = createProxyWithCachedProperties( + global.nativeFabricUIManager, + CACHED_PROPERTIES, + ); + } + return nativeFabricUIManagerProxy; +} + +/** + * + * Returns an object that caches the specified properties the first time they + * are accessed, and falls back to the original object for other properties. + */ +function createProxyWithCachedProperties( + implementation: Spec, + propertiesToCache: $ReadOnlyArray, +): Spec { + const proxy = Object.create(implementation); + for (const propertyName of propertiesToCache) { + defineLazyObjectProperty(proxy, propertyName, { + // $FlowExpectedError[prop-missing] + get: () => implementation[propertyName], + }); + } + return proxy; } diff --git a/packages/react-native/Libraries/ReactNative/__mocks__/FabricUIManager.js b/packages/react-native/Libraries/ReactNative/__mocks__/FabricUIManager.js index 34cf9f828713bb..0e9a67dccb0ba3 100644 --- a/packages/react-native/Libraries/ReactNative/__mocks__/FabricUIManager.js +++ b/packages/react-native/Libraries/ReactNative/__mocks__/FabricUIManager.js @@ -431,6 +431,33 @@ const FabricUIManagerMock: FabricUIManager = { ]; }, ), + getScrollPosition: jest.fn( + (node: Node): ?[/* scrollLeft: */ number, /* scrollTop: */ number] => { + ensureHostNode(node); + + const nodeInCurrentTree = getNodeInCurrentTree(node); + const currentProps = + nodeInCurrentTree != null ? fromNode(nodeInCurrentTree).props : null; + if (currentProps == null) { + return null; + } + + const scrollForTests: ?{ + scrollLeft: number, + scrollTop: number, + ... + } = + // $FlowExpectedError[prop-missing] + currentProps.__scrollForTests; + + if (scrollForTests == null) { + return null; + } + + const {scrollLeft, scrollTop} = scrollForTests; + return [scrollLeft, scrollTop]; + }, + ), }; global.nativeFabricUIManager = FabricUIManagerMock; diff --git a/packages/react-native/Libraries/ReactNative/__tests__/FabricUIManager-test.js b/packages/react-native/Libraries/ReactNative/__tests__/FabricUIManager-test.js new file mode 100644 index 00000000000000..e9d1d49c291633 --- /dev/null +++ b/packages/react-native/Libraries/ReactNative/__tests__/FabricUIManager-test.js @@ -0,0 +1,68 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +// flowlint unsafe-getters-setters:off + +describe('FabricUIManager', () => { + let getFabricUIManager; + + beforeEach(() => { + jest.resetModules(); + delete global.nativeFabricUIManager; + getFabricUIManager = require('../FabricUIManager').getFabricUIManager; + }); + + describe('getFabricUIManager', () => { + it('should return undefined if the global binding is not set', () => { + expect(getFabricUIManager()).toBeUndefined(); + }); + + it('should return an object with the same properties as the global binding', () => { + const createNode = jest.fn(); + const customProp = 'some prop'; + global.nativeFabricUIManager = { + createNode, + customProp, + }; + const fabricUIManager = getFabricUIManager(); + + expect(fabricUIManager).toEqual(expect.any(Object)); + expect(fabricUIManager?.createNode).toBe(createNode); + // $FlowExpectedError[prop-missing] + expect(fabricUIManager?.customProp).toBe(customProp); + }); + + it('should only access the cached properties of global binding once', () => { + let incrementingProp = 0; + global.nativeFabricUIManager = { + get createNode() { + return jest.fn(); + }, + get incrementingProp() { + return incrementingProp++; + }, + }; + + const fabricUIManager = getFabricUIManager(); + + expect(fabricUIManager).toEqual(expect.any(Object)); + const firstCreateNode = fabricUIManager?.createNode; + const secondCreateNode = fabricUIManager?.createNode; + // In the original object, the getter creates a new function every time. + expect(firstCreateNode).toBe(secondCreateNode); + + // $FlowExpectedError[prop-missing] + expect(fabricUIManager?.incrementingProp).toBe(0); + // $FlowExpectedError[prop-missing] + expect(fabricUIManager?.incrementingProp).toBe(1); + }); + }); +}); diff --git a/packages/react-native/Libraries/Text/RCTConvert+Text.h b/packages/react-native/Libraries/Text/RCTConvert+Text.h index b7c411a2a69366..4425cc2ccfa8e3 100644 --- a/packages/react-native/Libraries/Text/RCTConvert+Text.h +++ b/packages/react-native/Libraries/Text/RCTConvert+Text.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN + (UITextAutocorrectionType)UITextAutocorrectionType:(nullable id)json; + (UITextSpellCheckingType)UITextSpellCheckingType:(nullable id)json; + (RCTTextTransform)RCTTextTransform:(nullable id)json; ++ (UITextSmartInsertDeleteType)UITextSmartInsertDeleteType:(nullable id)json; @end diff --git a/packages/react-native/Libraries/Text/RCTConvert+Text.m b/packages/react-native/Libraries/Text/RCTConvert+Text.m index aa6e5e30a59082..3ab3cc656d0b63 100644 --- a/packages/react-native/Libraries/Text/RCTConvert+Text.m +++ b/packages/react-native/Libraries/Text/RCTConvert+Text.m @@ -34,4 +34,11 @@ + (UITextSpellCheckingType)UITextSpellCheckingType:(id)json RCTTextTransformUndefined, integerValue) ++ (UITextSmartInsertDeleteType)UITextSmartInsertDeleteType:(id)json +{ + return json == nil ? UITextSmartInsertDeleteTypeDefault + : [RCTConvert BOOL:json] ? UITextSmartInsertDeleteTypeYes + : UITextSmartInsertDeleteTypeNo; +} + @end diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m index 47da2cefee5926..a19b55569e8d71 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m @@ -47,6 +47,7 @@ @implementation RCTBaseTextInputViewManager { RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode) RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL) RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL) +RCT_REMAP_VIEW_PROPERTY(smartInsertDelete, backedTextInputView.smartInsertDeleteType, UITextSmartInsertDeleteType) RCT_EXPORT_VIEW_PROPERTY(autoFocus, BOOL) RCT_EXPORT_VIEW_PROPERTY(submitBehavior, NSString) RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL) diff --git a/packages/react-native/Libraries/WebPerformance/PerformanceEntry.js b/packages/react-native/Libraries/WebPerformance/PerformanceEntry.js index bc2cadff20209d..9c855422af86fc 100644 --- a/packages/react-native/Libraries/WebPerformance/PerformanceEntry.js +++ b/packages/react-native/Libraries/WebPerformance/PerformanceEntry.js @@ -11,6 +11,14 @@ export type HighResTimeStamp = number; export type PerformanceEntryType = 'mark' | 'measure' | 'event'; +export type PerformanceEntryJSON = { + name: string, + entryType: PerformanceEntryType, + startTime: HighResTimeStamp, + duration: HighResTimeStamp, + ... +}; + export const ALWAYS_LOGGED_ENTRY_TYPES: $ReadOnlyArray = [ 'mark', 'measure', @@ -34,12 +42,7 @@ export class PerformanceEntry { this.duration = init.duration; } - toJSON(): { - name: string, - entryType: PerformanceEntryType, - startTime: HighResTimeStamp, - duration: HighResTimeStamp, - } { + toJSON(): PerformanceEntryJSON { return { name: this.name, entryType: this.entryType, diff --git a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp index e223156ac94aa8..3d8d3aba94dc2f 100644 --- a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp +++ b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp @@ -15,10 +15,6 @@ namespace facebook::react { EventTag PerformanceEntryReporter::sCurrentEventTag_{0}; -static inline double getCurrentTimeStamp() { - return JSExecutor::performanceNow(); -} - PerformanceEntryReporter &PerformanceEntryReporter::getInstance() { static PerformanceEntryReporter instance; return instance; @@ -29,11 +25,17 @@ PerformanceEntryReporter::PerformanceEntryReporter() { // sure that marks can be referenced by measures getBuffer(PerformanceEntryType::MARK).hasNameLookup = true; } + void PerformanceEntryReporter::setReportingCallback( std::optional> callback) { callback_ = callback; } +double PerformanceEntryReporter::getCurrentTimeStamp() const { + return timeStampProvider_ != nullptr ? timeStampProvider_() + : JSExecutor::performanceNow(); +} + void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) { auto &buffer = getBuffer(entryType); buffer.isReporting = true; @@ -219,7 +221,15 @@ void PerformanceEntryReporter::measure( const std::optional &endMark) { double startTimeVal = startMark ? getMarkTime(*startMark) : startTime; double endTimeVal = endMark ? getMarkTime(*endMark) : endTime; + + if (!endMark && endTime < startTimeVal) { + // The end time is not specified, take the current time, according to the + // standard + endTimeVal = getCurrentTimeStamp(); + } + double durationVal = duration ? *duration : endTimeVal - startTimeVal; + logEntry( {name, static_cast(PerformanceEntryType::MEASURE), diff --git a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h index 8ea7c820e01893..a68df9247a4c34 100644 --- a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h +++ b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h @@ -147,6 +147,10 @@ class PerformanceEntryReporter : public EventLogger { return eventCounts_; } + void setTimeStampProvider(std::function provider) { + timeStampProvider_ = provider; + } + private: std::optional> callback_; @@ -171,6 +175,8 @@ class PerformanceEntryReporter : public EventLogger { std::unordered_map eventsInFlight_; std::mutex eventsInFlightMutex_; + std::function timeStampProvider_ = nullptr; + static EventTag sCurrentEventTag_; PerformanceEntryReporter(); @@ -182,6 +188,8 @@ class PerformanceEntryReporter : public EventLogger { PerformanceEntryType entryType, const char *entryName, std::vector &res) const; + + double getCurrentTimeStamp() const; }; } // namespace facebook::react diff --git a/packages/react-native/Libraries/WebPerformance/PerformanceEventTiming.js b/packages/react-native/Libraries/WebPerformance/PerformanceEventTiming.js index a741f494856657..609f5bc1041857 100644 --- a/packages/react-native/Libraries/WebPerformance/PerformanceEventTiming.js +++ b/packages/react-native/Libraries/WebPerformance/PerformanceEventTiming.js @@ -8,10 +8,18 @@ * @flow strict */ -import type {HighResTimeStamp} from './PerformanceEntry'; +import type {HighResTimeStamp, PerformanceEntryJSON} from './PerformanceEntry'; import {PerformanceEntry} from './PerformanceEntry'; +export type PerformanceEventTimingJSON = { + ...PerformanceEntryJSON, + processingStart: HighResTimeStamp, + processingEnd: HighResTimeStamp, + interactionId: number, + ... +}; + export class PerformanceEventTiming extends PerformanceEntry { processingStart: HighResTimeStamp; processingEnd: HighResTimeStamp; @@ -35,4 +43,13 @@ export class PerformanceEventTiming extends PerformanceEntry { this.processingEnd = init.processingEnd ?? 0; this.interactionId = init.interactionId ?? 0; } + + toJSON(): PerformanceEventTimingJSON { + return { + ...super.toJSON(), + processingStart: this.processingStart, + processingEnd: this.processingEnd, + interactionId: this.interactionId, + }; + } } diff --git a/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp b/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp index c5895021ee6c88..e7e8952a89d6ac 100644 --- a/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp @@ -146,8 +146,11 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { reporter.measure("measure3", 0.0, 0.0, 5.0, "mark1"); reporter.measure("measure4", 1.5, 0.0, std::nullopt, std::nullopt, "mark2"); + reporter.setTimeStampProvider([]() { return 3.5; }); + reporter.measure("measure5", 0.0, 0.0, std::nullopt, "mark2"); + reporter.mark("mark3", 2.0); - reporter.measure("measure5", 2.0, 2.0); + reporter.measure("measure6", 2.0, 2.0); reporter.mark("mark4", 2.0); auto res = reporter.popPendingEntries(); @@ -226,13 +229,20 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { std::nullopt, std::nullopt, std::nullopt}, - {"measure5", + {"measure6", static_cast(PerformanceEntryType::MEASURE), 2.0, 0.0, std::nullopt, std::nullopt, std::nullopt}, + {"measure5", + static_cast(PerformanceEntryType::MEASURE), + 2.0, + 1.5, + std::nullopt, + std::nullopt, + std::nullopt}, }; ASSERT_EQ(expected, entries); diff --git a/packages/react-native/React-Core.podspec b/packages/react-native/React-Core.podspec index 720e115e0b76b0..2fad76d2d9726a 100644 --- a/packages/react-native/React-Core.podspec +++ b/packages/react-native/React-Core.podspec @@ -69,7 +69,7 @@ Pod::Spec.new do |s| s.author = "Meta Platforms, Inc. and its affiliates" s.platforms = { :ios => min_ios_version_supported } s.source = source - s.resource_bundle = { "AccessibilityResources" => ["React/AccessibilityResources/*.lproj"]} + s.resource_bundle = { "RCTI18nStrings" => ["React/I18n/strings/*.lproj"]} s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags s.header_dir = "React" s.framework = "JavaScriptCore" @@ -130,6 +130,7 @@ Pod::Spec.new do |s| s.dependency "React-perflogger", version s.dependency "React-jsi", version s.dependency "React-jsiexecutor", version + s.dependency "React-utils" s.dependency "SocketRocket", socket_rocket_version s.dependency "Yoga" s.dependency "glog" diff --git a/packages/react-native/React/AccessibilityResources/en.lproj/Localizable.strings b/packages/react-native/React/AccessibilityResources/en.lproj/Localizable.strings deleted file mode 100644 index ee35fc3e5fe25d..00000000000000 --- a/packages/react-native/React/AccessibilityResources/en.lproj/Localizable.strings +++ /dev/null @@ -1,26 +0,0 @@ -/* - Localizable.strings - React -*/ -"alert"="alert"; -"checkbox"="checkbox"; -"combobox"="combo box"; -"menu"="menu"; -"menubar"="menu bar"; -"menuitem"="menu item"; -"progressbar"="progress bar"; -"radio"="radio button"; -"radiogroup"="radio group"; -"scrollbar"="scroll bar"; -"spinbutton"="spin button"; -"switch"="switch"; -"tab"="tab"; -"tablist"="tab list"; -"timer"="timer"; -"toolbar"="tool bar"; -"checked"="checked"; -"unchecked"="not checked"; -"busy"="busy"; -"expanded"="expanded"; -"collapsed"="collapsed"; -"mixed"="mixed"; diff --git a/packages/react-native/React/Base/RCTBridgeProxy.h b/packages/react-native/React/Base/RCTBridgeProxy.h new file mode 100644 index 00000000000000..b1c70287b1cc35 --- /dev/null +++ b/packages/react-native/React/Base/RCTBridgeProxy.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#import + +#import "RCTBridgeModule.h" + +@class RCTBundleManager; +@class RCTCallableJSModules; +@class RCTModuleRegistry; +@class RCTViewRegistry; + +@interface RCTBridgeProxy : NSProxy +- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry + moduleRegistry:(RCTModuleRegistry *)moduleRegistry + bundleManager:(RCTBundleManager *)bundleManager + callableJSModules:(RCTCallableJSModules *)callableJSModules + dispatchToJSThread:(void (^)(dispatch_block_t))dispatchToJSThread NS_DESIGNATED_INITIALIZER; + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; +- (void)forwardInvocation:(NSInvocation *)invocation; + +- (void)logWarning:(NSString *)message cmd:(SEL)cmd; +- (void)logError:(NSString *)message cmd:(SEL)cmd; + +/** + * Methods required for RCTBridge class extensions + */ +- (id)moduleForClass:(Class)moduleClass; +- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad; +@end diff --git a/packages/react-native/React/Base/RCTBridgeProxy.mm b/packages/react-native/React/Base/RCTBridgeProxy.mm new file mode 100644 index 00000000000000..c91a0fe9667400 --- /dev/null +++ b/packages/react-native/React/Base/RCTBridgeProxy.mm @@ -0,0 +1,447 @@ +/* + * 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. + */ + +#import "RCTBridgeProxy.h" +#import +#import +#import +#import +#import + +using namespace facebook; + +@interface RCTUIManagerProxy : NSProxy +- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry NS_DESIGNATED_INITIALIZER; + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; +- (void)forwardInvocation:(NSInvocation *)invocation; +@end + +@implementation RCTBridgeProxy { + RCTUIManagerProxy *_uiManagerProxy; + RCTModuleRegistry *_moduleRegistry; + RCTBundleManager *_bundleManager; + RCTCallableJSModules *_callableJSModules; + void (^_dispatchToJSThread)(dispatch_block_t); +} + +- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry + moduleRegistry:(RCTModuleRegistry *)moduleRegistry + bundleManager:(RCTBundleManager *)bundleManager + callableJSModules:(RCTCallableJSModules *)callableJSModules + dispatchToJSThread:(void (^)(dispatch_block_t))dispatchToJSThread +{ + self = [super self]; + if (self) { + self->_uiManagerProxy = [[RCTUIManagerProxy alloc] initWithViewRegistry:viewRegistry]; + self->_moduleRegistry = moduleRegistry; + self->_bundleManager = bundleManager; + self->_callableJSModules = callableJSModules; + self->_dispatchToJSThread = dispatchToJSThread; + } + return self; +} + +- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue +{ + [self logWarning:@"Please migrate to dispatchToJSThread: @synthesize dispatchToJSThread = _dispatchToJSThread" + cmd:_cmd]; + + if (queue == RCTJSThread) { + _dispatchToJSThread(block); + } else if (queue) { + dispatch_async(queue, block); + } +} + +/** + * Used By: + * - RCTDevSettings + */ +- (Class)executorClass +{ + [self logWarning:@"This method is unsupported. Returning nil." cmd:_cmd]; + return nil; +} + +/** + * Used By: + * - RCTBlobCollector + */ +- (jsi::Runtime *)runtime +{ + [self logWarning:@"This method is unsupported. Returning nullptr." cmd:_cmd]; + return nullptr; +} + +/** + * RCTModuleRegistry + */ +- (id)moduleForName:(NSString *)moduleName +{ + [self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd]; + return [_moduleRegistry moduleForName:[moduleName UTF8String]]; +} + +- (id)moduleForName:(NSString *)moduleName lazilyLoadIfNecessary:(BOOL)lazilyLoad +{ + [self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd]; + return [_moduleRegistry moduleForName:[moduleName UTF8String] lazilyLoadIfNecessary:lazilyLoad]; +} + +- (id)moduleForClass:(Class)moduleClass +{ + [self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd]; + NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); + return [_moduleRegistry moduleForName:[moduleName UTF8String] lazilyLoadIfNecessary:YES]; +} + +- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol +{ + [self logError:@"The TurboModule system cannot load modules by protocol. Returning an empty NSArray*." cmd:_cmd]; + return @[]; +} + +- (BOOL)moduleIsInitialized:(Class)moduleClass +{ + [self logWarning:@"Please migrate to RCTModuleRegistry: @synthesize moduleRegistry = _moduleRegistry." cmd:_cmd]; + return [_moduleRegistry moduleIsInitialized:moduleClass]; +} + +- (NSArray *)moduleClasses +{ + [self logError:@"The TurboModuleManager does not implement this method. Returning an empty NSArray*." cmd:_cmd]; + return @[]; +} + +/** + * RCTBundleManager + */ +- (void)setBundleURL:(NSURL *)bundleURL +{ + [self logWarning:@"Please migrate to RCTBundleManager: @synthesize bundleManager = _bundleManager." cmd:_cmd]; + [_bundleManager setBundleURL:bundleURL]; +} + +- (NSURL *)bundleURL +{ + [self logWarning:@"Please migrate to RCTBundleManager: @synthesize bundleManager = _bundleManager." cmd:_cmd]; + return [_bundleManager bundleURL]; +} + +/** + * RCTCallableJSModules + */ +- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args +{ + [self logWarning:@"Please migrate to RCTCallableJSModules: @synthesize callableJSModules = _callableJSModules." + cmd:_cmd]; + + NSArray *ids = [moduleDotMethod componentsSeparatedByString:@"."]; + NSString *module = ids[0]; + NSString *method = ids[1]; + [_callableJSModules invokeModule:module method:method withArgs:args]; +} + +- (void)enqueueJSCall:(NSString *)module + method:(NSString *)method + args:(NSArray *)args + completion:(dispatch_block_t)completion +{ + [self logWarning:@"Please migrate to RCTCallableJSModules: @synthesize callableJSModules = _callableJSModules." + cmd:_cmd]; + [_callableJSModules invokeModule:module method:method withArgs:args onComplete:completion]; +} + +- (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path +{ + [self logError:@"Please migrate to RCTHost registerSegmentWithId. Nooping" cmd:_cmd]; +} + +- (id)delegate +{ + [self logError:@"This method is unsupported. Returning nil." cmd:_cmd]; + return nil; +} + +- (NSDictionary *)launchOptions +{ + [self logError:@"Bridgeless mode doesn't support launchOptions. Returning nil." cmd:_cmd]; + return nil; +} + +- (BOOL)loading +{ + [self logWarning:@"This method is not implemented. Returning NO." cmd:_cmd]; + return NO; +} + +- (BOOL)valid +{ + [self logWarning:@"This method is not implemented. Returning NO." cmd:_cmd]; + return NO; +} + +- (RCTPerformanceLogger *)performanceLogger +{ + [self logWarning:@"Bridgeless mode does not support RCTPerformanceLogger. Returning nil." cmd:_cmd]; + return nil; +} + +- (void)reload +{ + [self logError:@"Please use RCTReloadCommand instead. Nooping." cmd:_cmd]; +} + +- (void)reloadWithReason:(NSString *)reason +{ + [self logError:@"Please use RCTReloadCommand instead. Nooping." cmd:_cmd]; +} + +- (void)onFastRefresh +{ + [[NSNotificationCenter defaultCenter] postNotificationName:RCTBridgeFastRefreshNotification object:self]; +} + +- (void)requestReload __deprecated_msg("Use RCTReloadCommand instead") +{ + [self logError:@"Please use RCTReloadCommand instead. Nooping." cmd:_cmd]; +} + +- (BOOL)isBatchActive +{ + [self logWarning:@"Bridgeless mode does not support batching. Returning NO." cmd:_cmd]; + return NO; +} + +/** + * RCTBridge () + */ + +- (NSString *)bridgeDescription +{ + [self logWarning:@"Bridgeless mode does not support bridgeDescription. Returning \"BridgeProxy\"." cmd:_cmd]; + return @"BridgeProxy"; +} + +- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args +{ + [self logError:@"Bridgeless mode does not queuing callbacks by ids. No-oping." cmd:_cmd]; +} + +- (RCTBridge *)batchedBridge +{ + [self logWarning:@"Bridgeless mode does not support batchedBridge. Returning bridge proxy." cmd:_cmd]; + return (RCTBridge *)self; +} + +- (void)setBatchedBridge +{ + [self logError:@"Bridgeless mode does not support setBatchedBridge. No-oping." cmd:_cmd]; +} + +- (RCTBridgeModuleListProvider)moduleProvider +{ + [self logWarning:@"Bridgeless mode does not support RCTBridgeModuleListProvider. Returning empty block" cmd:_cmd]; + return ^{ + return @[]; + }; +} + +- (RCTModuleRegistry *)moduleRegistry +{ + return _moduleRegistry; +} + +/** + * RCTBridge (RCTCxxBridge) + */ + +- (RCTBridge *)parentBridge +{ + [self logWarning:@"Bridgeless mode does not support parentBridge. Returning bridge proxy." cmd:_cmd]; + return (RCTBridge *)self; +} + +- (BOOL)moduleSetupComplete +{ + [self logWarning:@"Bridgeless mode does not implement moduleSetupComplete. Returning YES." cmd:_cmd]; + return YES; +} + +- (void)start +{ + [self + logError: + @"Starting the bridge proxy does nothing. If you want to start React Native, please use RCTHost start. Nooping" + cmd:_cmd]; +} + +- (void)registerModuleForFrameUpdates:(id)module withModuleData:(RCTModuleData *)moduleData +{ + [self logError:@"Bridgeless mode does not allow custom modules to register themselves for frame updates. Nooping" + cmd:_cmd]; +} + +- (RCTModuleData *)moduleDataForName:(NSString *)moduleName +{ + [self logError:@"Bridgeless mode does not use RCTModuleData. Returning nil." cmd:_cmd]; + return nil; +} + +- (void)registerAdditionalModuleClasses:(NSArray *)newModules +{ + [self + logError: + @"This API is unsupported. Please return all module classes from your app's RCTTurboModuleManagerDelegate getModuleClassFromName:. Nooping." + cmd:_cmd]; +} + +- (void)updateModuleWithInstance:(id)instance +{ + [self logError:@"Bridgeless mode does not support module replacement. Nooping." cmd:_cmd]; +} + +- (void)startProfiling +{ + [self logWarning:@"Bridgeless mode does not support this method. Nooping." cmd:_cmd]; +} + +- (void)stopProfiling:(void (^)(NSData *))callback +{ + [self logWarning:@"Bridgeless mode does not support this method. Nooping." cmd:_cmd]; +} + +- (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:(NSArray *)params +{ + [self logError:@"Bridgeless mode does not support this method. Nooping and returning nil." cmd:_cmd]; + return nil; +} + +- (void)logMessage:(NSString *)message level:(NSString *)level +{ + [self logWarning:@"Bridgeless mode does not support this method. Nooping." cmd:_cmd]; +} + +- (void)_immediatelyCallTimer:(NSNumber *)timer +{ + [self logWarning:@"Bridgeless mode does not support this method. Nooping." cmd:_cmd]; +} + +/** + * RCTBridge (Inspector) + */ +- (BOOL)inspectable +{ + [self logWarning:@"Bridgeless mode does not support this method. Returning NO." cmd:_cmd]; + return NO; +} + +/** + * RCTBridge (RCTUIManager) + */ +- (RCTUIManager *)uiManager +{ + return (RCTUIManager *)_uiManagerProxy; +} + +/** + * NSProxy setup + */ +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; +{ + return [RCTCxxBridge instanceMethodSignatureForSelector:sel]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + [self logError:@"This method is unsupported." cmd:invocation.selector]; +} + +/** + * Logging + * TODO(155977839): Add a means to configure/disable these logs, so people do not ignore all LogBoxes + */ +- (void)logWarning:(NSString *)message cmd:(SEL)cmd +{ + RCTLogWarn(@"RCTBridgeProxy: Calling [bridge %@]. %@", NSStringFromSelector(cmd), message); +} + +- (void)logError:(NSString *)message cmd:(SEL)cmd +{ + RCTLogError(@"RCTBridgeProxy: Calling [bridge %@]. %@", NSStringFromSelector(cmd), message); +} + +@end + +@implementation RCTUIManagerProxy { + RCTViewRegistry *_viewRegistry; +} +- (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry +{ + self = [super self]; + if (self) { + _viewRegistry = viewRegistry; + } + return self; +} + +/** + * RCTViewRegistry + */ +- (UIView *)viewForReactTag:(NSNumber *)reactTag +{ + [self logWarning:@"Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED." + cmd:_cmd]; + return [_viewRegistry viewForReactTag:reactTag]; +} + +- (void)addUIBlock:(RCTViewManagerUIBlock)block +{ + [self + logWarning: + @"This method isn't implemented faithfully: the viewRegistry passed to RCTViewManagerUIBlock is nil. Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED." + cmd:_cmd]; + __weak __typeof(self) weakSelf = self; + RCTExecuteOnMainQueue(^{ + __typeof(self) strongSelf = weakSelf; + if (strongSelf) { + block((RCTUIManager *)strongSelf, nil); + } + }); +} + +/** + * NSProxy setup + */ +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel +{ + return [RCTUIManager instanceMethodSignatureForSelector:sel]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + [self logError:@"This methid is unsupported." cmd:invocation.selector]; +} + +/** + * Logging + * TODO(155977839): Add a means to configure/disable these logs, so people do not ignore all LogBoxes + */ +- (void)logWarning:(NSString *)message cmd:(SEL)cmd +{ + RCTLogWarn( + @"RCTBridgeProxy (RCTUIManagerProxy): Calling [bridge.uiManager %@]. %@", NSStringFromSelector(cmd), message); +} + +- (void)logError:(NSString *)message cmd:(SEL)cmd +{ + RCTLogError( + @"RCTBridgeProxy (RCTUIManagerProxy): Calling [bridge.uiManager %@]. %@", NSStringFromSelector(cmd), message); +} + +@end diff --git a/packages/react-native/React/Base/RCTEventDispatcher.m b/packages/react-native/React/Base/RCTEventDispatcher.m index 00f6046b4eb535..d779006e7e90b0 100644 --- a/packages/react-native/React/Base/RCTEventDispatcher.m +++ b/packages/react-native/React/Base/RCTEventDispatcher.m @@ -28,3 +28,12 @@ @implementation RCTBridge (RCTEventDispatcher) } @end + +@implementation RCTBridgeProxy (RCTEventDispatcher) + +- (id)eventDispatcher +{ + return [self moduleForName:@"EventDispatcher" lazilyLoadIfNecessary:YES]; +} + +@end diff --git a/packages/react-native/React/Base/RCTEventDispatcherProtocol.h b/packages/react-native/React/Base/RCTEventDispatcherProtocol.h index 4125e9829443a0..170135bb64b7b9 100644 --- a/packages/react-native/React/Base/RCTEventDispatcherProtocol.h +++ b/packages/react-native/React/Base/RCTEventDispatcherProtocol.h @@ -8,6 +8,7 @@ #import #import +#import /** * The threshold at which text inputs will start warning that the JS thread @@ -132,3 +133,9 @@ typedef NS_ENUM(NSInteger, RCTTextEventType) { - (id)eventDispatcher; @end + +@interface RCTBridgeProxy (RCTEventDispatcher) + +- (id)eventDispatcher; + +@end diff --git a/packages/react-native/React/CoreModules/RCTAccessibilityManager.h b/packages/react-native/React/CoreModules/RCTAccessibilityManager.h index 707d5766dd4c53..01af72e81420ff 100644 --- a/packages/react-native/React/CoreModules/RCTAccessibilityManager.h +++ b/packages/react-native/React/CoreModules/RCTAccessibilityManager.h @@ -9,6 +9,7 @@ #import #import +#import extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; // posted when multiplier is changed @@ -34,3 +35,9 @@ extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; / @property (nonatomic, readonly) RCTAccessibilityManager *accessibilityManager; @end + +@interface RCTBridgeProxy (RCTAccessibilityManager) + +@property (nonatomic, readonly) RCTAccessibilityManager *accessibilityManager; + +@end diff --git a/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm b/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm index d91b2a1980e80c..4aaabcaba30be7 100644 --- a/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm +++ b/packages/react-native/React/CoreModules/RCTAccessibilityManager.mm @@ -405,6 +405,15 @@ - (RCTAccessibilityManager *)accessibilityManager @end +@implementation RCTBridgeProxy (RCTAccessibilityManager) + +- (RCTAccessibilityManager *)accessibilityManager +{ + return [self moduleForClass:[RCTAccessibilityManager class]]; +} + +@end + Class RCTAccessibilityManagerCls(void) { return RCTAccessibilityManager.class; diff --git a/packages/react-native/React/CoreModules/RCTAppState.mm b/packages/react-native/React/CoreModules/RCTAppState.mm index 389aa6d642dfd0..0aa63fc6b53390 100644 --- a/packages/react-native/React/CoreModules/RCTAppState.mm +++ b/packages/react-native/React/CoreModules/RCTAppState.mm @@ -20,7 +20,11 @@ static NSDictionary *states; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - states = @{@(UIApplicationStateActive) : @"active", @(UIApplicationStateBackground) : @"background"}; + states = @{ + @(UIApplicationStateActive) : @"active", + @(UIApplicationStateBackground) : @"background", + @(UIApplicationStateInactive) : @"inactive" + }; }); if (RCTRunningInAppExtension()) { diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.h b/packages/react-native/React/CoreModules/RCTDevMenu.h index eedfff15688171..b9b76f63e6e385 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.h +++ b/packages/react-native/React/CoreModules/RCTDevMenu.h @@ -9,6 +9,7 @@ #import #import +#import #import #if RCT_DEV_MENU @@ -108,3 +109,9 @@ typedef NSString * (^RCTDevMenuItemTitleBlock)(void); @property (nonatomic, readonly) RCTDevMenu *devMenu; @end + +@interface RCTBridgeProxy (RCTDevMenu) + +@property (nonatomic, readonly) RCTDevMenu *devMenu; + +@end diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.mm b/packages/react-native/React/CoreModules/RCTDevMenu.mm index f40cf4febf25af..1a8b81ead4d23b 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.mm +++ b/packages/react-native/React/CoreModules/RCTDevMenu.mm @@ -563,6 +563,19 @@ - (RCTDevMenu *)devMenu @end +@implementation RCTBridgeProxy (RCTDevMenu) + +- (RCTDevMenu *)devMenu +{ +#if RCT_DEV_MENU + return [self moduleForClass:[RCTDevMenu class]]; +#else + return nil; +#endif +} + +@end + Class RCTDevMenuCls(void) { return RCTDevMenu.class; diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.h b/packages/react-native/React/CoreModules/RCTDevSettings.h index c576e372f4b037..35a24b5d9191b2 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.h +++ b/packages/react-native/React/CoreModules/RCTDevSettings.h @@ -6,6 +6,7 @@ */ #import +#import #import #import #import @@ -114,6 +115,12 @@ @end +@interface RCTBridgeProxy (RCTDevSettings) + +@property (nonatomic, readonly) RCTDevSettings *devSettings; + +@end + // In debug builds, the dev menu is enabled by default but it is further customizable using this method. // However, this method only has an effect in builds where the dev menu is actually compiled in. // (i.e. RCT_DEV or RCT_DEV_MENU is set) diff --git a/packages/react-native/React/CoreModules/RCTDevSettings.mm b/packages/react-native/React/CoreModules/RCTDevSettings.mm index d9b03ed38744b2..f95e963fb29d5b 100644 --- a/packages/react-native/React/CoreModules/RCTDevSettings.mm +++ b/packages/react-native/React/CoreModules/RCTDevSettings.mm @@ -633,6 +633,21 @@ - (RCTDevSettings *)devSettings @end +@implementation RCTBridgeProxy (RCTDevSettings) + +- (RCTDevSettings *)devSettings +{ +#if RCT_REMOTE_PROFILE + return [self moduleForClass:[RCTDevSettings class]]; +#elif RCT_DEV_MENU + return devSettingsMenuEnabled ? [self moduleForClass:[RCTDevSettings class]] : nil; +#else + return nil; +#endif +} + +@end + Class RCTDevSettingsCls(void) { return RCTDevSettings.class; diff --git a/packages/react-native/React/CoreModules/RCTRedBox.h b/packages/react-native/React/CoreModules/RCTRedBox.h index 5d67a2a57a914c..e4eed8732c40e9 100644 --- a/packages/react-native/React/CoreModules/RCTRedBox.h +++ b/packages/react-native/React/CoreModules/RCTRedBox.h @@ -9,6 +9,7 @@ #import #import +#import #import @class RCTJSStackFrame; @@ -57,3 +58,9 @@ typedef void (^RCTRedBoxButtonPressHandler)(void); @property (nonatomic, readonly) RCTRedBox *redBox; @end + +@interface RCTBridgeProxy (RCTRedBox) + +@property (nonatomic, readonly) RCTRedBox *redBox; + +@end diff --git a/packages/react-native/React/CoreModules/RCTRedBox.mm b/packages/react-native/React/CoreModules/RCTRedBox.mm index c80731cea7d636..dc58594a80702c 100644 --- a/packages/react-native/React/CoreModules/RCTRedBox.mm +++ b/packages/react-native/React/CoreModules/RCTRedBox.mm @@ -702,6 +702,15 @@ - (RCTRedBox *)redBox @end +@implementation RCTBridgeProxy (RCTRedBox) + +- (RCTRedBox *)redBox +{ + return RCTRedBoxGetEnabled() ? [self moduleForClass:[RCTRedBox class]] : nil; +} + +@end + #else // Disabled @interface RCTRedBox () @@ -787,6 +796,15 @@ - (RCTRedBox *)redBox @end +@implementation RCTBridgeProxy (RCTRedBox) + +- (RCTRedBox *)redBox +{ + return nil; +} + +@end + #endif Class RCTRedBoxCls(void) diff --git a/packages/react-native/React/CoreModules/RCTWebSocketModule.h b/packages/react-native/React/CoreModules/RCTWebSocketModule.h index 0e39e18f990c50..f92bfc42e3aea3 100644 --- a/packages/react-native/React/CoreModules/RCTWebSocketModule.h +++ b/packages/react-native/React/CoreModules/RCTWebSocketModule.h @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +#import #import NS_ASSUME_NONNULL_BEGIN @@ -32,4 +33,10 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface RCTBridgeProxy (RCTWebSocketModule) + +- (RCTWebSocketModule *)webSocketModule; + +@end + NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/CoreModules/RCTWebSocketModule.mm b/packages/react-native/React/CoreModules/RCTWebSocketModule.mm index 4451b9460e5bac..3fd7238911f559 100644 --- a/packages/react-native/React/CoreModules/RCTWebSocketModule.mm +++ b/packages/react-native/React/CoreModules/RCTWebSocketModule.mm @@ -207,6 +207,15 @@ - (RCTWebSocketModule *)webSocketModule @end +@implementation RCTBridgeProxy (RCTWebSocketModule) + +- (RCTWebSocketModule *)webSocketModule +{ + return [self moduleForClass:[RCTWebSocketModule class]]; +} + +@end + Class RCTWebSocketModuleCls(void) { return RCTWebSocketModule.class; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 9e41f9d3b5f20b..17a38ca7858d07 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -181,6 +181,13 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _backedTextInputView.passwordRules = RCTUITextInputPasswordRulesFromString(newTextInputProps.traits.passwordRules); } + if (newTextInputProps.traits.smartInsertDelete != oldTextInputProps.traits.smartInsertDelete) { + if (@available(iOS 11.0, *)) { + _backedTextInputView.smartInsertDeleteType = + RCTUITextSmartInsertDeleteTypeFromOptionalBool(newTextInputProps.traits.smartInsertDelete); + } + } + // Traits `blurOnSubmit`, `clearTextOnFocus`, and `selectTextOnFocus` were omitted intentionally here // because they are being checked on-demand. diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h index 2a89399964a4d5..70c55719262be0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h @@ -39,4 +39,6 @@ UITextContentType RCTUITextContentTypeFromString(std::string const &contentType) UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(std::string const &passwordRules); +UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional smartInsertDelete); + NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 532c29e59e3bdd..a79ddff152fa91 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -44,6 +44,10 @@ void RCTCopyBackedTextInput( toTextInput.keyboardType = fromTextInput.keyboardType; toTextInput.textContentType = fromTextInput.textContentType; + if (@available(iOS 11.0, *)) { + toTextInput.smartInsertDeleteType = fromTextInput.smartInsertDeleteType; + } + toTextInput.passwordRules = fromTextInput.passwordRules; [toTextInput setSelectedTextRange:fromTextInput.selectedTextRange notifyDelegate:NO]; @@ -226,3 +230,10 @@ UITextContentType RCTUITextContentTypeFromString(std::string const &contentType) { return [UITextInputPasswordRules passwordRulesWithDescriptor:RCTNSStringFromStringNilIfEmpty(passwordRules)]; } + +UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional smartInsertDelete) +{ + return smartInsertDelete.has_value() + ? (*smartInsertDelete ? UITextSmartInsertDeleteTypeYes : UITextSmartInsertDeleteTypeNo) + : UITextSmartInsertDeleteTypeDefault; +} diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 97feafc4c10fe1..33da37d1f0a65a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -14,6 +14,7 @@ #import #import #import +#import #import #import #import @@ -731,28 +732,38 @@ - (NSString *)accessibilityValue // these to screenreader users. (They should already be familiar with them // from using web). if ([roleString isEqualToString:@"checkbox"]) { - [valueComponents addObject:@"checkbox"]; + [valueComponents addObject:RCTLocalizedString("checkbox", "checkable interactive control")]; } if ([roleString isEqualToString:@"radio"]) { - [valueComponents addObject:@"radio button"]; + [valueComponents + addObject: + RCTLocalizedString( + "radio button", + "a checkable input that when associated with other radio buttons, only one of which can be checked at a time")]; } // Handle states which haven't already been handled. if (props.accessibilityState.checked == AccessibilityState::Checked) { - [valueComponents addObject:@"checked"]; + [valueComponents + addObject:RCTLocalizedString("checked", "a checkbox, radio button, or other widget which is checked")]; } if (props.accessibilityState.checked == AccessibilityState::Unchecked) { - [valueComponents addObject:@"unchecked"]; + [valueComponents + addObject:RCTLocalizedString("unchecked", "a checkbox, radio button, or other widget which is unchecked")]; } if (props.accessibilityState.checked == AccessibilityState::Mixed) { - [valueComponents addObject:@"mixed"]; + [valueComponents + addObject:RCTLocalizedString( + "mixed", "a checkbox, radio button, or other widget which is both checked and unchecked")]; } if (props.accessibilityState.expanded) { - [valueComponents addObject:@"expanded"]; + [valueComponents + addObject:RCTLocalizedString("expanded", "a menu, dialog, accordian panel, or other widget which is expanded")]; } + if (props.accessibilityState.busy) { - [valueComponents addObject:@"busy"]; + [valueComponents addObject:RCTLocalizedString("busy", "an element currently being updated or modified")]; } if (valueComponents.count > 0) { diff --git a/packages/react-native/React/I18n/.clang-format-ignore b/packages/react-native/React/I18n/.clang-format-ignore new file mode 100644 index 00000000000000..63b7f47b0fb0a2 --- /dev/null +++ b/packages/react-native/React/I18n/.clang-format-ignore @@ -0,0 +1,2 @@ +# Synced externally +FBXXHashUtils.h diff --git a/packages/react-native/React/I18n/FBXXHashUtils.h b/packages/react-native/React/I18n/FBXXHashUtils.h new file mode 100644 index 00000000000000..abfa11a8c5f3af --- /dev/null +++ b/packages/react-native/React/I18n/FBXXHashUtils.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015 Daniel Kirchner + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Based on implementation: https://github.com/ekpyron/xxhashct/blob/master/xxh64.hpp +// To generate string hash at compile time, the original file has been slightly modified by i18n team. + +#import + +NS_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif + +/// magic constants :-) +static const uint64_t PRIME1 = 11400714785074694791ULL; +static const uint64_t PRIME2 = 14029467366897019727ULL; +static const uint64_t PRIME3 = 1609587929392839161ULL; +static const uint64_t PRIME4 = 9650029242287828579ULL; +static const uint64_t PRIME5 = 2870177450012600261ULL; + +NS_INLINE uint64_t rotl(uint64_t x, int r) +{ + return ((x << r) | (x >> (64 - r))); +} + +NS_INLINE uint64_t mix1(const uint64_t h, const uint64_t prime, int rshift) +{ + return (h ^ (h >> rshift)) * prime; +} + +NS_INLINE uint64_t mix2(const uint64_t p, const uint64_t v) +{ + return rotl(v + p * PRIME2, 31) * PRIME1; +} + +NS_INLINE uint64_t mix3(const uint64_t h, const uint64_t v) +{ + return (h ^ mix2(v, 0)) * PRIME1 + PRIME4; +} + +NS_INLINE uint32_t endian32(const char *v) +{ + return (uint32_t)((uint8_t)(v[0])) | ((uint32_t)((uint8_t)(v[1])) << 8) + | ((uint32_t)((uint8_t)(v[2])) << 16) | ((uint32_t)((uint8_t)(v[3])) << 24); +} + +NS_INLINE uint64_t endian64(const char *v) +{ + return (uint64_t)((uint8_t)(v[0])) | ((uint64_t)((uint8_t)(v[1])) << 8) + | ((uint64_t)((uint8_t)(v[2])) << 16) | ((uint64_t)((uint8_t)(v[3])) << 24) + | ((uint64_t)((uint8_t)(v[4])) << 32) | ((uint64_t)((uint8_t)(v[5])) << 40) + | ((uint64_t)((uint8_t)(v[6])) << 48) | ((uint64_t)((uint8_t)(v[7])) << 56); +} + +NS_INLINE uint64_t fetch64(const char *p, const uint64_t v) +{ + return mix2(endian64(p), v); +} + +NS_INLINE uint64_t fetch32(const char *p) +{ + return (uint64_t)(endian32(p)) * PRIME1; +} + +NS_INLINE uint64_t fetch8(const char *p) +{ + return (uint8_t)(*p) * PRIME5; +} + +NS_INLINE uint64_t finalize(const uint64_t h, const char *p, uint64_t len) +{ + return (len >= 8) ? (finalize(rotl(h ^ fetch64(p, 0), 27) * PRIME1 + PRIME4, p + 8, len - 8)) + : ((len >= 4) ? (finalize(rotl(h ^ fetch32(p), 23) * PRIME2 + PRIME3, p + 4, len - 4)) + : ((len > 0) ? (finalize(rotl(h ^ fetch8(p), 11) * PRIME1, p + 1, len - 1)) + : (mix1(mix1(mix1(h, PRIME2, 33), PRIME3, 29), 1, 32)))); +} + +NS_INLINE uint64_t h32bytes_compute(const char *p, uint64_t len, const uint64_t v1, const uint64_t v2, const uint64_t v3, const uint64_t v4) +{ + return (len >= 32) ? h32bytes_compute(p + 32, len - 32, fetch64(p, v1), fetch64(p + 8, v2), fetch64(p + 16, v3), fetch64(p + 24, v4)) + : mix3(mix3(mix3(mix3(rotl(v1, 1) + rotl(v2, 7) + rotl(v3, 12) + rotl(v4, 18), v1), v2), v3), v4); +} + +NS_INLINE uint64_t h32bytes(const char *p, uint64_t len, const uint64_t seed) +{ + return h32bytes_compute(p, len, seed + PRIME1 + PRIME2, seed + PRIME2, seed, seed - PRIME1); +} + +NS_INLINE uint64_t FBxxHash64(const char *p, uint64_t len, uint64_t seed) +{ + return finalize((len >= 32 ? h32bytes(p, len, seed) : seed + PRIME5) + len, p + (len & ~0x1F), len & 0x1F); +} + +#ifdef __cplusplus +} +#endif + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/I18n/RCTLocalizedString.h b/packages/react-native/React/I18n/RCTLocalizedString.h new file mode 100644 index 00000000000000..68c48bd6775927 --- /dev/null +++ b/packages/react-native/React/I18n/RCTLocalizedString.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#if WITH_FBI18N + +#import +#define RCTLocalizedString(string, description) FBT(string, description) + +#else + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +uint64_t FBcoreLocalexxHash48(const char *input, uint64_t length, uint64_t seed); +NSString *RCTLocalizedStringFromKey(uint64_t key, NSString *defaultValue); + +#define RCTLocalizedStringKey(string, description) \ + FBcoreLocalexxHash48(string "|" description, strlen(string) + strlen(description) + 1, 0) + +#define RCTLocalizedString(string, description) \ + RCTLocalizedStringFromKey(RCTLocalizedStringKey(string, description), @"" string) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/packages/react-native/React/I18n/RCTLocalizedString.mm b/packages/react-native/React/I18n/RCTLocalizedString.mm new file mode 100644 index 00000000000000..6a09613b77a0ac --- /dev/null +++ b/packages/react-native/React/I18n/RCTLocalizedString.mm @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#import "RCTLocalizedString.h" + +#if !defined(WITH_FBI18N) || !(WITH_FBI18N) + +extern "C" { + +static NSString *FBTStringByConvertingIntegerToBase64(uint64_t number) +{ + const NSUInteger base = 64; + const char *symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; + char converted[9]; // output will take at most 8 symbols + char *p = converted + sizeof(converted) - 1; + *p = 0; + do { + *--p = symbols[number % base]; + number = number / base; + } while (number > 0); + return [[NSString alloc] initWithCString:p encoding:NSASCIIStringEncoding]; +} + +__attribute__((noinline)) uint64_t FBcoreLocalexxHash48(const char *input, uint64_t length, uint64_t seed) +{ + const uint64_t k48BitsMask = 0xffffffffffffL; + return FBxxHash64(input, length, seed) & k48BitsMask; +} + +NSString *RCTLocalizedStringFromKey(uint64_t key, NSString *defaultValue) +{ + static NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"RCTI18nStrings" + ofType:@"bundle"]]; + if (bundle == nil) { + return defaultValue; + } else { + return [bundle localizedStringForKey:FBTStringByConvertingIntegerToBase64(key) value:defaultValue table:nil]; + } +} +} + +#endif diff --git a/packages/react-native/React/I18n/strings/ar.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/ar.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/ar.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/cs.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/cs.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/cs.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/da.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/da.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/da.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/de.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/de.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/de.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/el.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/el.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/el.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/en-GB.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/en-GB.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/en.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/en.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/en.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/es-ES.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/es-ES.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/es-ES.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/es.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/es.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/es.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/fi.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/fi.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/fi.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/fr.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/fr.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/fr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/he.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/he.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/he.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/hi.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/hi.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/hi.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/hr.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/hr.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/hr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/hu.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/hu.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/hu.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/id.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/id.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/id.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/it.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/it.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/it.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/ja.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/ja.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/ja.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/ko.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/ko.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/ko.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/ms.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/ms.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/ms.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/nb.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/nb.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/nb.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/nl.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/nl.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/nl.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/pl.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/pl.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/pl.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/pt-PT.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/pt-PT.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/pt.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/pt.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/pt.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/ro.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/ro.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/ro.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/ru.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/ru.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/ru.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/sk.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/sk.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/sk.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/sv.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/sv.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/sv.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/th.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/th.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/th.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/tr.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/tr.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/tr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/uk.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/uk.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/uk.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/vi.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/vi.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/vi.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/zh-Hans.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/zh-Hant-HK.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/zh-Hant-HK.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/zh-Hant-HK.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/zh-Hant.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/zu.lproj/Localizable.strings b/packages/react-native/React/I18n/strings/zu.lproj/Localizable.strings new file mode 100644 index 00000000000000..f5e9186bea23f2 --- /dev/null +++ b/packages/react-native/React/I18n/strings/zu.lproj/Localizable.strings @@ -0,0 +1,2 @@ +// @generated SignedSource<<6c4053ad9f2da7cd9f9d1bc0789bd1e5>> + diff --git a/packages/react-native/React/I18n/strings/zu.lproj/fbt_language_pack.bin b/packages/react-native/React/I18n/strings/zu.lproj/fbt_language_pack.bin new file mode 100644 index 00000000000000..1bafad6fb4657e Binary files /dev/null and b/packages/react-native/React/I18n/strings/zu.lproj/fbt_language_pack.bin differ diff --git a/packages/react-native/React/Modules/RCTSurfacePresenterStub.h b/packages/react-native/React/Modules/RCTSurfacePresenterStub.h index 70510c181279cc..9cf334854744ca 100644 --- a/packages/react-native/React/Modules/RCTSurfacePresenterStub.h +++ b/packages/react-native/React/Modules/RCTSurfacePresenterStub.h @@ -8,6 +8,7 @@ #import #import +#import @protocol RCTSurfaceProtocol; @@ -44,4 +45,11 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface RCTBridgeProxy (RCTSurfacePresenterStub) + +- (id)surfacePresenter; +- (void)setSurfacePresenter:(id)presenter; + +@end + NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Modules/RCTSurfacePresenterStub.m b/packages/react-native/React/Modules/RCTSurfacePresenterStub.m index e4e0ec7404be59..07524d583ea422 100644 --- a/packages/react-native/React/Modules/RCTSurfacePresenterStub.m +++ b/packages/react-native/React/Modules/RCTSurfacePresenterStub.m @@ -20,3 +20,17 @@ - (void)setSurfacePresenter:(id)surfacePresenter } @end + +@implementation RCTBridgeProxy (RCTSurfacePresenterStub) + +- (id)surfacePresenter +{ + return objc_getAssociatedObject(self, @selector(surfacePresenter)); +} + +- (void)setSurfacePresenter:(id)surfacePresenter +{ + objc_setAssociatedObject(self, @selector(surfacePresenter), surfacePresenter, OBJC_ASSOCIATION_RETAIN); +} + +@end diff --git a/packages/react-native/React/React-RCTFabric.podspec b/packages/react-native/React/React-RCTFabric.podspec index 543f01691cb084..1c1ac9f67937b7 100644 --- a/packages/react-native/React/React-RCTFabric.podspec +++ b/packages/react-native/React/React-RCTFabric.podspec @@ -44,6 +44,7 @@ if ENV['USE_FRAMEWORKS'] header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-ImageManager/React_ImageManager.framework/Headers\"" header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\"" header_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\"" + header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"" end Pod::Spec.new do |s| @@ -81,6 +82,7 @@ Pod::Spec.new do |s| s.dependency "React-RCTText" s.dependency "React-FabricImage" s.dependency "React-debug" + s.dependency "React-utils" if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1" s.dependency "hermes-engine" diff --git a/packages/react-native/React/Views/RCTView.m b/packages/react-native/React/Views/RCTView.m index 66c6c5978f8a40..5464e06124d7e6 100644 --- a/packages/react-native/React/Views/RCTView.m +++ b/packages/react-native/React/Views/RCTView.m @@ -14,6 +14,7 @@ #import "RCTBorderCurve.h" #import "RCTBorderDrawing.h" #import "RCTI18nUtil.h" +#import "RCTLocalizedString.h" #import "RCTLog.h" #import "RCTViewUtils.h" #import "UIView+React.h" @@ -282,41 +283,44 @@ - (NSString *)accessibilityValue static NSDictionary *rolesAndStatesDescription = nil; dispatch_once(&onceToken, ^{ - NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"AccessibilityResources" ofType:@"bundle"]; - NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; - - if (bundle) { - NSURL *url = [bundle URLForResource:@"Localizable" withExtension:@"strings"]; - rolesAndStatesDescription = [NSDictionary dictionaryWithContentsOfURL:url error:nil]; - } - if (rolesAndStatesDescription == nil) { - // Falling back to hardcoded English list. - NSLog(@"Cannot load localized accessibility strings."); - rolesAndStatesDescription = @{ - @"alert" : @"alert", - @"checkbox" : @"checkbox", - @"combobox" : @"combo box", - @"menu" : @"menu", - @"menubar" : @"menu bar", - @"menuitem" : @"menu item", - @"progressbar" : @"progress bar", - @"radio" : @"radio button", - @"radiogroup" : @"radio group", - @"scrollbar" : @"scroll bar", - @"spinbutton" : @"spin button", - @"switch" : @"switch", - @"tab" : @"tab", - @"tablist" : @"tab list", - @"timer" : @"timer", - @"toolbar" : @"tool bar", - @"checked" : @"checked", - @"unchecked" : @"unchecked", - @"busy" : @"busy", - @"expanded" : @"expanded", - @"collapsed" : @"collapsed", - @"mixed" : @"mixed", - }; - } + rolesAndStatesDescription = @{ + @"alert" : RCTLocalizedString("alert", "important, and usually time-sensitive, information"), + @"busy" : RCTLocalizedString("busy", "an element currently being updated or modified"), + @"checkbox" : RCTLocalizedString("checkbox", "checkable interactive control"), + @"combobox" : RCTLocalizedString( + "combo box", + "input that controls another element that can pop up to help the user set the value of that input"), + @"menu" : RCTLocalizedString("menu", "offers a list of choices to the user"), + @"menubar" : RCTLocalizedString( + "menu bar", "presentation of menu that usually remains visible and is usually presented horizontally"), + @"menuitem" : RCTLocalizedString("menu item", "an option in a set of choices contained by a menu or menubar"), + @"progressbar" : + RCTLocalizedString("progress bar", "displays the progress status for tasks that take a long time"), + @"radio" : RCTLocalizedString( + "radio button", + "a checkable input that when associated with other radio buttons, only one of which can be checked at a time"), + @"radiogroup" : RCTLocalizedString("radio group", "a group of radio buttons"), + @"scrollbar" : RCTLocalizedString("scroll bar", "controls the scrolling of content within a viewing area"), + @"spinbutton" : RCTLocalizedString( + "spin button", "defines a type of range that expects the user to select a value from among discrete choices"), + @"switch" : RCTLocalizedString("switch", "represents the states 'on' and 'off'"), + @"tab" : RCTLocalizedString("tab", "an interactive element inside a tablist"), + @"tablist" : RCTLocalizedString("tab list", "container for a set of tabs"), + @"timer" : RCTLocalizedString( + "timer", + "a numerical counter listing the amount of elapsed time from a starting point or the remaining time until an end point"), + @"toolbar" : RCTLocalizedString( + "tool bar", + "a collection of commonly used function buttons or controls represented in a compact visual form"), + @"checked" : RCTLocalizedString("checked", "a checkbox, radio button, or other widget which is checked"), + @"unchecked" : RCTLocalizedString("unchecked", "a checkbox, radio button, or other widget which is unchecked"), + @"expanded" : + RCTLocalizedString("expanded", "a menu, dialog, accordian panel, or other widget which is expanded"), + @"collapsed" : + RCTLocalizedString("collapsed", "a menu, dialog, accordian panel, or other widget which is collapsed"), + @"mixed" : + RCTLocalizedString("mixed", "a checkbox, radio button, or other widget which is both checked and unchecked"), + }; }); // Handle Switch. diff --git a/packages/react-native/ReactAndroid/src/debug/AndroidManifest.xml b/packages/react-native/ReactAndroid/src/debug/AndroidManifest.xml new file mode 100644 index 00000000000000..ac05d07ff6274a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/debug/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 4ca3aa3a180c3d..79805825767cb2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -302,6 +302,7 @@ private void stopAnimationsForNode(AnimatedNode animatedNode) { // Invoke animation end callback with {finished: false} WritableMap endCallbackResponse = Arguments.createMap(); endCallbackResponse.putBoolean("finished", false); + endCallbackResponse.putDouble("value", animation.mAnimatedValue.mValue); animation.mEndCallback.invoke(endCallbackResponse); } else if (mReactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op @@ -310,6 +311,7 @@ private void stopAnimationsForNode(AnimatedNode animatedNode) { WritableMap params = Arguments.createMap(); params.putInt("animationId", animation.mId); params.putBoolean("finished", false); + params.putDouble("value", animation.mAnimatedValue.mValue); mReactApplicationContext.emitDeviceEvent( "onNativeAnimatedModuleAnimationFinished", params); } @@ -332,6 +334,7 @@ public void stopAnimation(int animationId) { // Invoke animation end callback with {finished: false} WritableMap endCallbackResponse = Arguments.createMap(); endCallbackResponse.putBoolean("finished", false); + endCallbackResponse.putDouble("value", animation.mAnimatedValue.mValue); animation.mEndCallback.invoke(endCallbackResponse); } else if (mReactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op @@ -340,6 +343,7 @@ public void stopAnimation(int animationId) { WritableMap params = Arguments.createMap(); params.putInt("animationId", animation.mId); params.putBoolean("finished", false); + params.putDouble("value", animation.mAnimatedValue.mValue); mReactApplicationContext.emitDeviceEvent( "onNativeAnimatedModuleAnimationFinished", params); } @@ -645,6 +649,7 @@ public void runUpdates(long frameTimeNanos) { if (animation.mEndCallback != null) { WritableMap endCallbackResponse = Arguments.createMap(); endCallbackResponse.putBoolean("finished", true); + endCallbackResponse.putDouble("value", animation.mAnimatedValue.mValue); animation.mEndCallback.invoke(endCallbackResponse); } else if (mReactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op @@ -653,6 +658,7 @@ public void runUpdates(long frameTimeNanos) { WritableMap params = Arguments.createMap(); params.putInt("animationId", animation.mId); params.putBoolean("finished", true); + params.putDouble("value", animation.mAnimatedValue.mValue); mReactApplicationContext.emitDeviceEvent( "onNativeAnimatedModuleAnimationFinished", params); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BridgelessReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BridgelessReactContext.java index b8ee4f666babaa..bf65d685dfb443 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BridgelessReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BridgelessReactContext.java @@ -115,7 +115,7 @@ public BridgelessJSModuleInvocationHandler( } @Override - public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) { NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray(); mReactHost.callFunctionOnModule( JavaScriptModuleRegistry.getJSModuleName(mJSModuleInterface), method.getName(), jsArgs); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java index 0a6b5edfca0769..88dd19ed42968f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java @@ -196,30 +196,30 @@ public LifecycleState getLifecycleState() { * errors occur during initialization, and will be cancelled if ReactHost.destroy() is called * before it completes. */ - public Task preload() { + public Task start() { if (ReactFeatureFlags.enableBridgelessArchitectureNewCreateReloadDestroy) { - return new_preload(); + return newStart(); } - return old_preload(); + return oldStart(); } @ThreadConfined("ReactHost") - private @Nullable Task mPreloadTask = null; + private @Nullable Task mStartTask = null; - private Task old_preload() { - final String method = "old_preload()"; + private Task oldStart() { + final String method = "oldPreload()"; return Task.call( () -> { - if (mPreloadTask == null) { + if (mStartTask == null) { log(method, "Schedule"); - mPreloadTask = + mStartTask = getOrCreateReactInstanceTask() .continueWithTask( task -> { if (task.isFaulted()) { destroy( - "old_preload() failure: " + task.getError().getMessage(), + "oldPreload() failure: " + task.getError().getMessage(), task.getError()); mReactHostDelegate.handleInstanceException(task.getError()); } @@ -229,27 +229,27 @@ private Task old_preload() { mBGExecutor) .makeVoid(); } - return mPreloadTask; + return mStartTask; }, mBGExecutor) .continueWithTask(Task::getResult); } - private Task new_preload() { - final String method = "new_preload()"; + private Task newStart() { + final String method = "newPreload()"; return Task.call( () -> { - if (mPreloadTask == null) { + if (mStartTask == null) { log(method, "Schedule"); - mPreloadTask = - waitThen_new_getOrCreateReactInstanceTask() + mStartTask = + waitThenCallNewGetOrCreateReactInstanceTask() .continueWithTask( (task) -> { if (task.isFaulted()) { mReactHostDelegate.handleInstanceException(task.getError()); // Wait for destroy to finish - return new_getOrCreateDestroyTask( - "new_preload() failure: " + task.getError().getMessage(), + return newGetOrCreateDestroyTask( + "newPreload() failure: " + task.getError().getMessage(), task.getError()) .continueWithTask(destroyTask -> Task.forError(task.getError())) .makeVoid(); @@ -258,7 +258,7 @@ private Task new_preload() { }, mBGExecutor); } - return mPreloadTask; + return mStartTask; }, mBGExecutor) .continueWithTask(Task::getResult); @@ -452,7 +452,7 @@ FabricUIManager getUIManager() { return reactInstance.getUIManager(); } - public boolean hasNativeModule(Class nativeModuleInterface) { + /* package */ boolean hasNativeModule(Class nativeModuleInterface) { final ReactInstance reactInstance = mReactInstanceTaskRef.get().getResult(); if (reactInstance != null) { return reactInstance.hasNativeModule(nativeModuleInterface); @@ -460,7 +460,7 @@ public boolean hasNativeModule(Class nativeModuleInt return false; } - public Collection getNativeModules() { + /* package */ Collection getNativeModules() { final ReactInstance reactInstance = mReactInstanceTaskRef.get().getResult(); if (reactInstance != null) { return reactInstance.getNativeModules(); @@ -468,7 +468,8 @@ public Collection getNativeModules() { return new ArrayList<>(); } - public @Nullable T getNativeModule(Class nativeModuleInterface) { + /* package */ @Nullable + T getNativeModule(Class nativeModuleInterface) { if (nativeModuleInterface == UIManagerModule.class) { ReactSoftExceptionLogger.logSoftExceptionVerbose( TAG, @@ -714,22 +715,22 @@ private BridgelessReactContext getOrCreateReactContext() { */ private Task getOrCreateReactInstanceTask() { if (ReactFeatureFlags.enableBridgelessArchitectureNewCreateReloadDestroy) { - return Task.call(this::waitThen_new_getOrCreateReactInstanceTask, mBGExecutor) + return Task.call(this::waitThenCallNewGetOrCreateReactInstanceTask, mBGExecutor) .continueWithTask(Task::getResult); } - return old_getOrCreateReactInstanceTask(); + return oldGetOrCreateReactInstanceTask(); } @ThreadConfined("ReactHost") - private Task waitThen_new_getOrCreateReactInstanceTask() { - return waitThen_new_getOrCreateReactInstanceTaskWithRetries(0, 4); + private Task waitThenCallNewGetOrCreateReactInstanceTask() { + return waitThenCallNewGetOrCreateReactInstanceTaskWithRetries(0, 4); } @ThreadConfined("ReactHost") - private Task waitThen_new_getOrCreateReactInstanceTaskWithRetries( + private Task waitThenCallNewGetOrCreateReactInstanceTaskWithRetries( int tryNum, int maxTries) { - final String method = "waitThen_new_getOrCreateReactInstanceTaskWithRetries"; + final String method = "waitThenCallNewGetOrCreateReactInstanceTaskWithRetries"; if (mReloadTask != null) { log(method, "React Native is reloading. Return reload task."); return mReloadTask; @@ -745,7 +746,7 @@ private Task waitThen_new_getOrCreateReactInstanceTaskWithRetries + tryNum + ")."); return mDestroyTask.onSuccessTask( - (task) -> waitThen_new_getOrCreateReactInstanceTaskWithRetries(tryNum + 1, maxTries), + (task) -> waitThenCallNewGetOrCreateReactInstanceTaskWithRetries(tryNum + 1, maxTries), mBGExecutor); } @@ -754,12 +755,12 @@ private Task waitThen_new_getOrCreateReactInstanceTaskWithRetries "React Native is tearing down. Not wait for teardown to finish: reached max retries."); } - return new_getOrCreateReactInstanceTask(); + return newGetOrCreateReactInstanceTask(); } @ThreadConfined("ReactHost") - private Task new_getOrCreateReactInstanceTask() { - final String method = "new_getOrCreateReactInstanceTask()"; + private Task newGetOrCreateReactInstanceTask() { + final String method = "newGetOrCreateReactInstanceTask()"; log(method); return mReactInstanceTaskRef.getOrCreate( @@ -852,8 +853,8 @@ class Result { }); } - private Task old_getOrCreateReactInstanceTask() { - final String method = "old_getOrCreateReactInstanceTask()"; + private Task oldGetOrCreateReactInstanceTask() { + final String method = "oldGetOrCreateReactInstanceTask()"; log(method); return mReactInstanceTaskRef.getOrCreate( @@ -1031,17 +1032,17 @@ public Task reload(String reason) { method, "Destroying React Native. Waiting for destroy to finish, before reloading React Native."); return mDestroyTask - .continueWithTask(task -> new_getOrCreateReloadTask(reason), mBGExecutor) + .continueWithTask(task -> newGetOrCreateReloadTask(reason), mBGExecutor) .makeVoid(); } - return new_getOrCreateReloadTask(reason).makeVoid(); + return newGetOrCreateReloadTask(reason).makeVoid(); }, mBGExecutor) .continueWithTask(Task::getResult); } - return old_reload(reason); + return oldReload(reason); } @ThreadConfined("ReactHost") @@ -1056,8 +1057,8 @@ public Task reload(String reason) { * ReactInstance task work throws an exception. */ @ThreadConfined("ReactHost") - private Task new_getOrCreateReloadTask(String reason) { - final String method = "new_getOrCreateReloadTask()"; + private Task newGetOrCreateReloadTask(String reason) { + final String method = "newGetOrCreateReloadTask()"; log(method); // Log how React Native is destroyed @@ -1159,10 +1160,10 @@ private Task new_getOrCreateReloadTask(String reason) { mReactInstanceTaskRef.reset(); log(method, "Resetting preload task ref"); - mPreloadTask = null; + mStartTask = null; // Kickstart a new ReactInstance create - return new_getOrCreateReactInstanceTask(); + return newGetOrCreateReactInstanceTask(); }, mBGExecutor) .onSuccess( @@ -1227,15 +1228,15 @@ public Task destroy(String reason, @Nullable Exception ex) { method, "Reloading React Native. Waiting for reload to finish before destroying React Native."); return mReloadTask.continueWithTask( - task -> new_getOrCreateDestroyTask(reason, ex), mBGExecutor); + task -> newGetOrCreateDestroyTask(reason, ex), mBGExecutor); } - return new_getOrCreateDestroyTask(reason, ex); + return newGetOrCreateDestroyTask(reason, ex); }, mBGExecutor) .continueWithTask(Task::getResult); } - old_destroy(reason, ex); + oldDestroy(reason, ex); return Task.forResult(nullsafeFIXME(null, "Empty Destroy Task")); } @@ -1251,8 +1252,8 @@ public Task destroy(String reason, @Nullable Exception ex) { * ReactInstance task work throws an exception. */ @ThreadConfined("ReactHost") - private Task new_getOrCreateDestroyTask(final String reason, @Nullable Exception ex) { - final String method = "new_getOrCreateDestroyTask()"; + private Task newGetOrCreateDestroyTask(final String reason, @Nullable Exception ex) { + final String method = "newGetOrCreateDestroyTask()"; log(method); // Log how React Native is destroyed @@ -1335,7 +1336,7 @@ private Task new_getOrCreateDestroyTask(final String reason, @Nullable Exc mReactInstanceTaskRef.reset(); log(method, "Resetting Preload task ref"); - mPreloadTask = null; + mStartTask = null; log(method, "Resetting destroy task ref"); mDestroyTask = null; @@ -1348,8 +1349,8 @@ private Task new_getOrCreateDestroyTask(final String reason, @Nullable Exc } /** Destroy and recreate the ReactInstance and context. */ - private Task old_reload(String reason) { - final String method = "old_reload()"; + private Task oldReload(String reason) { + final String method = "oldReload()"; log(method); // Log how React Native is destroyed @@ -1358,7 +1359,7 @@ private Task old_reload(String reason) { synchronized (mReactInstanceTaskRef) { mMemoryPressureRouter.removeMemoryPressureListener(mMemoryPressureListener); - old_destroyReactInstanceAndContext(method, reason); + oldDestroyReactInstanceAndContext(method, reason); return callAfterGetOrCreateReactInstance( method, @@ -1375,8 +1376,8 @@ private Task old_reload(String reason) { } /** Destroy the specified instance and context. */ - private void old_destroy(String reason, @Nullable Exception ex) { - final String method = "old_destroy()"; + private void oldDestroy(String reason, @Nullable Exception ex) { + final String method = "oldDestroy()"; log(method); // Log how React Native is destroyed @@ -1391,7 +1392,7 @@ private void old_destroy(String reason, @Nullable Exception ex) { mMemoryPressureRouter.destroy(reactContext); } - old_destroyReactInstanceAndContext(method, reason); + oldDestroyReactInstanceAndContext(method, reason); // Remove all attached surfaces log(method, "Clearing attached surfaces"); @@ -1409,8 +1410,8 @@ private void old_destroy(String reason, @Nullable Exception ex) { } } - private void old_destroyReactInstanceAndContext(final String callingMethod, final String reason) { - final String method = "old_destroyReactInstanceAndContext(" + callingMethod + ")"; + private void oldDestroyReactInstanceAndContext(final String callingMethod, final String reason) { + final String method = "oldDestroyReactInstanceAndContext(" + callingMethod + ")"; log(method); synchronized (mReactInstanceTaskRef) { @@ -1454,7 +1455,7 @@ private void old_destroyReactInstanceAndContext(final String callingMethod, fina // Re-enable preloads log(method, "Resetting Preload task ref"); - mPreloadTask = null; + mStartTask = null; }); } else { raiseSoftException( @@ -1469,7 +1470,7 @@ private void old_destroyReactInstanceAndContext(final String callingMethod, fina mBGExecutor.execute( () -> { log(method, "Resetting Preload task ref"); - mPreloadTask = null; + mStartTask = null; }); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java index 6b57090f41cbe5..9b3a7d7486ac34 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java @@ -57,11 +57,6 @@ * instance manager recreates it (through {@link #onNewReactContextCreated). Also, instance manager * is responsible for enabling/disabling dev support in case when app is backgrounded or when all * the views has been detached from the instance (through {@link #setDevSupportEnabled} method). - * - * IMPORTANT: In order for developer support to work correctly it is required that the - * manifest of your application contain the following entries: - * {@code } - * {@code } */ public final class BridgeDevSupportManager extends DevSupportManagerBase { private boolean mIsSamplingProfilerEnabled = false; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java index 75b5865371759b..5f535b7044d6fa 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java @@ -333,6 +333,6 @@ public BundleStatus() { } public interface BundleStatusProvider { - public BundleStatus getBundleStatus(); + BundleStatus getBundleStatus(); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java index 5380de4a32df4e..84c756550cfed8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java @@ -111,7 +111,7 @@ public interface DevSupportManager extends JSExceptionHandler { * determined right before loading the packager. Your customizer must call |callback|, as loading * will be blocked waiting for it to resolve. */ - public interface PackagerLocationCustomizer { + interface PackagerLocationCustomizer { void run(Runnable callback); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java index 180bfec3635464..75255ca79a8acc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java @@ -7,16 +7,10 @@ package com.facebook.react.modules.network; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.text.TextUtils; import android.webkit.CookieManager; -import android.webkit.ValueCallback; import androidx.annotation.Nullable; import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.GuardedAsyncTask; import com.facebook.react.bridge.ReactContext; import java.io.IOException; import java.net.CookieHandler; @@ -36,13 +30,11 @@ public class ForwardingCookieHandler extends CookieHandler { private static final String VERSION_ONE_HEADER = "Set-cookie2"; private static final String COOKIE_HEADER = "Cookie"; - private final CookieSaver mCookieSaver; private final ReactContext mContext; private @Nullable CookieManager mCookieManager; public ForwardingCookieHandler(ReactContext context) { mContext = context; - mCookieSaver = new CookieSaver(); } @Override @@ -71,20 +63,9 @@ public void put(URI uri, Map> headers) throws IOException { } public void clearCookies(final Callback callback) { - clearCookiesAsync(callback); - } - - private void clearCookiesAsync(final Callback callback) { CookieManager cookieManager = getCookieManager(); if (cookieManager != null) { - cookieManager.removeAllCookies( - new ValueCallback() { - @Override - public void onReceiveValue(Boolean value) { - mCookieSaver.onCookiesModified(); - callback.invoke(value); - } - }); + cookieManager.removeAllCookies(value -> callback.invoke(value)); } } @@ -98,7 +79,6 @@ public void addCookies(final String url, final List cookies) { addCookieAsync(url, cookie); } cookieManager.flush(); - mCookieSaver.onCookiesModified(); } private void addCookieAsync(String url, String cookie) { @@ -112,22 +92,12 @@ private static boolean isCookieHeader(String name) { return name.equalsIgnoreCase(VERSION_ZERO_HEADER) || name.equalsIgnoreCase(VERSION_ONE_HEADER); } - private void runInBackground(final Runnable runnable) { - new GuardedAsyncTask(mContext) { - @Override - protected void doInBackgroundGuarded(Void... params) { - runnable.run(); - } - }.execute(); - } - /** * Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily * to make sure it's done on a background thread as needed. */ private @Nullable CookieManager getCookieManager() { if (mCookieManager == null) { - possiblyWorkaroundSyncManager(mContext); try { mCookieManager = CookieManager.getInstance(); } catch (IllegalArgumentException ex) { @@ -151,55 +121,4 @@ protected void doInBackgroundGuarded(Void... params) { return mCookieManager; } - - private static void possiblyWorkaroundSyncManager(Context context) {} - - /** - * Responsible for flushing cookies to disk. Flushes to disk with a maximum delay of 30 seconds. - * This class is only active if we are on API < 21. - */ - private class CookieSaver { - private static final int MSG_PERSIST_COOKIES = 1; - - private static final int TIMEOUT = 30 * 1000; // 30 seconds - - private final Handler mHandler; - - public CookieSaver() { - mHandler = - new Handler( - Looper.getMainLooper(), - new Handler.Callback() { - @Override - public boolean handleMessage(Message msg) { - if (msg.what == MSG_PERSIST_COOKIES) { - persistCookies(); - return true; - } else { - return false; - } - } - }); - } - - public void onCookiesModified() {} - - public void persistCookies() { - mHandler.removeMessages(MSG_PERSIST_COOKIES); - runInBackground( - new Runnable() { - @Override - public void run() { - flush(); - } - }); - } - - private void flush() { - CookieManager cookieManager = getCookieManager(); - if (cookieManager != null) { - cookieManager.flush(); - } - } - } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java index 4a56e92a88796a..7c0d07dde5b06e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java @@ -30,7 +30,6 @@ import java.util.zip.GZIPOutputStream; import okhttp3.MediaType; import okhttp3.RequestBody; -import okhttp3.internal.Util; import okio.BufferedSink; import okio.ByteString; import okio.Okio; @@ -128,6 +127,21 @@ private static InputStream getDownloadFileInputStream(Context context, Uri uri) return RequestBody.create(mediaType, gzipByteArrayOutputStream.toByteArray()); } + /** + * Reference: + * https://github.com/square/okhttp/blob/8c8c3dbcfa91e28de2e13975ec414e07f153fde4/okhttp/src/commonMain/kotlin/okhttp3/internal/-UtilCommon.kt#L281-L288 + * Checked exceptions will be ignored + */ + private static void closeQuietly(Source source) { + try { + source.close(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + // noop. + } + } + /** Creates a RequestBody from a mediaType and inputStream given. */ public static RequestBody create(final MediaType mediaType, final InputStream inputStream) { return new RequestBody() { @@ -152,7 +166,7 @@ public void writeTo(BufferedSink sink) throws IOException { source = Okio.source(inputStream); sink.writeAll(source); } finally { - Util.closeQuietly(source); + closeQuietly(source); } } }; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java index 13701c36f65756..eb970dfbfb443f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java @@ -89,8 +89,10 @@ private MotionEvent convertMotionToRootFrame(View childView, MotionEvent childMo private void updatePreviousStateFromEvent(MotionEvent event, PointerEventState eventState) { // Caching the event state so we have a new "last" - mLastHitPathByPointerId = eventState.getHitPathByPointerId(); - mLastEventCoordinatesByPointerId = eventState.getEventCoordinatesByPointerId(); + // note: we need to make copies here as the eventState may be accessed later and we don't want + // mutations of these instance vars to affect it + mLastHitPathByPointerId = new HashMap<>(eventState.getHitPathByPointerId()); + mLastEventCoordinatesByPointerId = new HashMap<>(eventState.getEventCoordinatesByPointerId()); mLastButtonState = event.getButtonState(); // Clean up any stale pointerIds @@ -613,7 +615,7 @@ private void dispatchCancelEventForTarget( int activePointerId = eventState.getActivePointerId(); List activeHitPath = eventState.getHitPathByPointerId().get(activePointerId); - if (!activeHitPath.isEmpty()) { + if (!activeHitPath.isEmpty() && targetView != null) { boolean listeningForCancel = isAnyoneListeningForBubblingEvent(activeHitPath, EVENT.CANCEL, EVENT.CANCEL_CAPTURE); if (listeningForCancel) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/common/ViewUtils.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/common/ViewUtils.java new file mode 100644 index 00000000000000..a75abc5865e8e3 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/common/ViewUtils.java @@ -0,0 +1,34 @@ +/* + * 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. + */ + +package com.facebook.react.views.common; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.R; + +/** Class containing static methods involving manipulations of Views */ +public class ViewUtils { + + /** + * Returns value of testId for the given view, if present + * + * @param view View to get the testId value for + * @return the value of testId if defined for the view, otherwise null + */ + public static @Nullable String getTestId(@Nullable View view) { + if (view == null) { + return null; + } + Object tag = view.getTag(R.id.react_test_id); + if (tag instanceof String) { + return (String) tag; + } else { + return null; + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java index f19a1b0425888d..84dc328edab3af 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.java @@ -42,12 +42,14 @@ public static WritableArray getFontMetrics( X_HEIGHT_MEASUREMENT_TEXT, 0, X_HEIGHT_MEASUREMENT_TEXT.length(), xHeightBounds); double xHeight = xHeightBounds.height() / AMPLIFICATION_FACTOR / dm.density; for (int i = 0; i < layout.getLineCount(); i++) { + boolean endsWithNewLine = text.length() > 0 && text.charAt(layout.getLineEnd(i) - 1) == '\n'; + float lineWidth = endsWithNewLine ? layout.getLineMax(i) : layout.getLineWidth(i); Rect bounds = new Rect(); layout.getLineBounds(i, bounds); WritableMap line = Arguments.createMap(); line.putDouble("x", layout.getLineLeft(i) / dm.density); line.putDouble("y", bounds.top / dm.density); - line.putDouble("width", layout.getLineWidth(i) / dm.density); + line.putDouble("width", lineWidth / dm.density); line.putDouble("height", bounds.height() / dm.density); line.putDouble("descender", layout.getLineDescent(i) / dm.density); line.putDouble("ascender", -layout.getLineAscent(i) / dm.density); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 2e237f01970fb5..b69452d9384fcf 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -135,7 +135,10 @@ public long measure( layoutWidth = width; } else { for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) { - float lineWidth = layout.getLineWidth(lineIndex); + boolean endsWithNewLine = + text.length() > 0 && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + float lineWidth = + endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex); if (lineWidth > layoutWidth) { layoutWidth = lineWidth; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 43ffc0d31dc2a7..4af57295d9aac6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -267,11 +267,14 @@ protected void onLayout( // the last offset in the layout will result in an endless loop. Work around // this bug by avoiding getPrimaryHorizontal in that case. if (start == text.length() - 1) { + boolean endsWithNewLine = + text.length() > 0 && text.charAt(layout.getLineEnd(line) - 1) == '\n'; + float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderHorizontalPosition = isRtlParagraph // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns incorrect // values when the paragraph is RTL and `setSingleLine(true)`. - ? textViewWidth - (int) layout.getLineWidth(line) + ? textViewWidth - (int) lineWidth : (int) layout.getLineRight(line) - width; } else { // The direction of the paragraph may not be exactly the direction the string is heading diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java index 25bfe983255cc7..ffd5b2f1a5c4c2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java @@ -407,7 +407,10 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - float lineWidth = layout.getLineWidth(lineIndex); + boolean endsWithNewLine = + text.length() > 0 && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + float lineWidth = + endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex); if (lineWidth > calculatedWidth) { calculatedWidth = lineWidth; } @@ -462,11 +465,14 @@ public static long measureText( // the last offset in the layout will result in an endless loop. Work around // this bug by avoiding getPrimaryHorizontal in that case. if (start == text.length() - 1) { + boolean endsWithNewLine = + text.length() > 0 && text.charAt(layout.getLineEnd(line) - 1) == '\n'; + float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderLeftPosition = isRtlParagraph // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns incorrect // values when the paragraph is RTL and `setSingleLine(true)`. - ? calculatedWidth - layout.getLineWidth(line) + ? calculatedWidth - lineWidth : layout.getLineRight(line) - placeholderWidth; } else { // The direction of the paragraph may not be exactly the direction the string is heading diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java index 71703755597285..8cd576494a1c24 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java @@ -429,7 +429,10 @@ public static long measureText( calculatedWidth = width; } else { for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) { - float lineWidth = layout.getLineWidth(lineIndex); + boolean endsWithNewLine = + text.length() > 0 && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n'; + float lineWidth = + endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex); if (lineWidth > calculatedWidth) { calculatedWidth = lineWidth; } @@ -484,12 +487,15 @@ public static long measureText( // the last offset in the layout will result in an endless loop. Work around // this bug by avoiding getPrimaryHorizontal in that case. if (start == text.length() - 1) { + boolean endsWithNewLine = + text.length() > 0 && text.charAt(layout.getLineEnd(line) - 1) == '\n'; + float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line); placeholderLeftPosition = isRtlParagraph // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns // incorrect // values when the paragraph is RTL and `setSingleLine(true)`. - ? calculatedWidth - layout.getLineWidth(line) + ? calculatedWidth - lineWidth : layout.getLineRight(line) - placeholderWidth; } else { // The direction of the paragraph may not be exactly the direction the string is diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java index c2a1b9d29ded8f..110bf7527da06d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java @@ -564,19 +564,28 @@ private void updatePath() { int colorRight = getBorderColor(Spacing.RIGHT); int colorBottom = getBorderColor(Spacing.BOTTOM); int borderColor = getBorderColor(Spacing.ALL); + int colorBlock = getBorderColor(Spacing.BLOCK); int colorBlockStart = getBorderColor(Spacing.BLOCK_START); int colorBlockEnd = getBorderColor(Spacing.BLOCK_END); + if (isBorderColorDefined(Spacing.BLOCK)) { + colorBottom = colorBlock; + colorTop = colorBlock; + } + if (isBorderColorDefined(Spacing.BLOCK_END)) { + colorBottom = colorBlockEnd; + } + if (isBorderColorDefined(Spacing.BLOCK_START)) { + colorTop = colorBlockStart; + } + // Clip border ONLY if its color is non transparent if (Color.alpha(colorLeft) != 0 && Color.alpha(colorTop) != 0 && Color.alpha(colorRight) != 0 && Color.alpha(colorBottom) != 0 - && Color.alpha(borderColor) != 0 - && Color.alpha(colorBlock) != 0 - && Color.alpha(colorBlockStart) != 0 - && Color.alpha(colorBlockEnd) != 0) { + && Color.alpha(borderColor) != 0) { mInnerClipTempRectForBorderRadius.top += borderWidth.top; mInnerClipTempRectForBorderRadius.bottom -= borderWidth.bottom; @@ -882,7 +891,7 @@ private void updatePath() { /** Compute mInnerTopLeftCorner */ mInnerTopLeftCorner.x = mInnerClipTempRectForBorderRadius.left; - mInnerTopLeftCorner.y = mInnerClipTempRectForBorderRadius.top * 2; + mInnerTopLeftCorner.y = mInnerClipTempRectForBorderRadius.top; getEllipseIntersectionWithLine( // Ellipse Bounds @@ -908,10 +917,7 @@ private void updatePath() { } mInnerBottomLeftCorner.x = mInnerClipTempRectForBorderRadius.left; - mInnerBottomLeftCorner.y = - borderWidth.bottom != 0 - ? mInnerClipTempRectForBorderRadius.bottom * -2 - : mInnerClipTempRectForBorderRadius.bottom; + mInnerBottomLeftCorner.y = mInnerClipTempRectForBorderRadius.bottom; getEllipseIntersectionWithLine( // Ellipse Bounds @@ -937,7 +943,7 @@ private void updatePath() { } mInnerTopRightCorner.x = mInnerClipTempRectForBorderRadius.right; - mInnerTopRightCorner.y = mInnerClipTempRectForBorderRadius.top * 2; + mInnerTopRightCorner.y = mInnerClipTempRectForBorderRadius.top; getEllipseIntersectionWithLine( // Ellipse Bounds @@ -963,10 +969,7 @@ private void updatePath() { } mInnerBottomRightCorner.x = mInnerClipTempRectForBorderRadius.right; - mInnerBottomRightCorner.y = - borderWidth.bottom != 0 - ? mInnerClipTempRectForBorderRadius.bottom * -2 - : mInnerClipTempRectForBorderRadius.bottom; + mInnerBottomRightCorner.y = mInnerClipTempRectForBorderRadius.bottom; getEllipseIntersectionWithLine( // Ellipse Bounds diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp index 713f8c8c18381f..3d880580d5cc46 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp @@ -440,7 +440,7 @@ void Binding::installFabricUIManager( toolbox.contextContainer = contextContainer; toolbox.componentRegistryFactory = componentsRegistry->buildRegistryFunction; - // TODO: (T130208323) runtimeExecutor should execute lambdas after + // TODO: (T132338609) runtimeExecutor should execute lambdas after // main bundle eval, and bindingsInstallExecutor should execute before. toolbox.bridgelessBindingsExecutor = std::nullopt; toolbox.runtimeExecutor = runtimeExecutor; diff --git a/packages/react-native/ReactAndroid/src/main/res/devsupport/values-bs/strings.xml b/packages/react-native/ReactAndroid/src/main/res/devsupport/values-bs/strings.xml index 6360c5c0799ee6..488326305ad906 100644 --- a/packages/react-native/ReactAndroid/src/main/res/devsupport/values-bs/strings.xml +++ b/packages/react-native/ReactAndroid/src/main/res/devsupport/values-bs/strings.xml @@ -3,4 +3,5 @@ + Prikazuje %1$s diff --git a/packages/react-native/ReactAndroid/src/main/res/devsupport/values-fa/strings.xml b/packages/react-native/ReactAndroid/src/main/res/devsupport/values-fa/strings.xml index 9717fb6ab20f78..922b0dbdb4b48a 100644 --- a/packages/react-native/ReactAndroid/src/main/res/devsupport/values-fa/strings.xml +++ b/packages/react-native/ReactAndroid/src/main/res/devsupport/values-fa/strings.xml @@ -3,5 +3,6 @@ + واکنش منوی (%1$s) انحراف محلی اجرای %1$s diff --git a/packages/react-native/ReactAndroid/src/main/res/devsupport/values-lv/strings.xml b/packages/react-native/ReactAndroid/src/main/res/devsupport/values-lv/strings.xml index a66be9d1d970c9..336698f59082c1 100644 --- a/packages/react-native/ReactAndroid/src/main/res/devsupport/values-lv/strings.xml +++ b/packages/react-native/ReactAndroid/src/main/res/devsupport/values-lv/strings.xml @@ -3,4 +3,5 @@ + Darbojas %1$s diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-af/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-af/strings.xml new file mode 100644 index 00000000000000..24e1b69fe6b4ef --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-af/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ar/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ar/strings.xml new file mode 100644 index 00000000000000..9ee5b1218296dd --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ar/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-as/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-as/strings.xml new file mode 100644 index 00000000000000..7224dbc28d87b8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-as/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-az/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-az/strings.xml new file mode 100644 index 00000000000000..8d1df4784531c0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-az/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-be/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-be/strings.xml new file mode 100644 index 00000000000000..ce84b2af7e3b2d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-be/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bg/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bg/strings.xml new file mode 100644 index 00000000000000..09b7b31c671dab --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bg/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bn/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bn/strings.xml new file mode 100644 index 00000000000000..b5d516623ef92f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bn/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bs/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bs/strings.xml new file mode 100644 index 00000000000000..6360c5c0799ee6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-bs/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ca/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ca/strings.xml new file mode 100644 index 00000000000000..d839c0b739be71 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ca/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-cb/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-cb/strings.xml new file mode 100644 index 00000000000000..89de99e2b6bf58 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-cb/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-cs/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-cs/strings.xml new file mode 100644 index 00000000000000..b556b85f3bc390 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-cs/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-da/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-da/strings.xml new file mode 100644 index 00000000000000..1fd6723b3bae1f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-da/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-de/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-de/strings.xml new file mode 100644 index 00000000000000..b4629c93404814 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-de/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-el/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-el/strings.xml new file mode 100644 index 00000000000000..0604fa12ac1e99 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-el/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-en-rGB/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-en-rGB/strings.xml new file mode 100644 index 00000000000000..49147780ee63b5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-en-rGB/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-es-rES/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-es-rES/strings.xml new file mode 100644 index 00000000000000..025a080a25a9f8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-es-rES/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-es/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-es/strings.xml new file mode 100644 index 00000000000000..bfb1514177a8c5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-es/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-et/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-et/strings.xml new file mode 100644 index 00000000000000..e17476d14d8823 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-et/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fa/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fa/strings.xml new file mode 100644 index 00000000000000..72270d97102a7c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fa/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fb-rLS/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fb-rLS/strings.xml new file mode 100644 index 00000000000000..b36e207a5eff90 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fb-rLS/strings.xml @@ -0,0 +1,26 @@ + + + + + + Image \@\@\@ + Button, Image\@\@\@\@\@\@ + Heading \@\@\@ \@\@\@ + Combo Box \@\@\@ + Menu \@\@\@ + Menu Bar \@\@\@ + Menu Item\@\@\@\@\@\@ + Progress Bar\@\@\@\@\@\@ + Radio Group\@\@\@\@\@\@\@\@\@ + Scroll Bar\@\@\@\@\@\@\@\@\@ + Spin Button \@\@\@ \@\@\@ + Tab List\@\@\@ + Timer \@\@\@ + Tool Bar\@\@\@\@\@\@ + Summary\@\@\@\@\@\@ + expanded \@\@\@ \@\@\@ + collapsed \@\@\@ \@\@\@ \@\@\@ + unselected\@\@\@ + off \@\@\@ + mixed\@\@\@ + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fb/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fb/strings.xml new file mode 100644 index 00000000000000..42769e25d7f3f7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fb/strings.xml @@ -0,0 +1,31 @@ + + + + + + [Link#a31d26668a679e44a0843994a6744274:1] + [Image#d8aa55c15c7118a635092b092bcf1d33:1] + [Button, Image#2b5b423bf3daff8f8b188f5d5733ec14:1] + [Heading#03d19d231fd31bc5dae409f337e4fea3:1] + [Alert#f230077c00a8397fcc7138dfd8b37405:1] + [Combo Box#c399e5a30d2d764efcced424465cacc4:1] + [Menu#a2c49a9b7c94c31d6f53f5f59329a558:1] + [Menu Bar#9ede63b4972815fe4810781f1f15770c:1] + [Menu Item#ba3a1d642e7bfae7a60f57181ff5a91f:1] + [Progress Bar#fc1ee5dacc16eb35a88ca3aff9355683:1] + [Radio Group#382586bc34074311334bc929c63f69e4:1] + [Scroll Bar#8c5cb8c1bbbac0ed64bb63c240b9a415:1] + [Spin Button#8af18737c08f4adab8e7103876d29955:1] + [Tab#60c56bd22945b4d1532f474bfc568a66:1] + [Tab List#8daaf7f9316191f0e35f3ba31ed11aec:1] + [Timer#2f3aa4d3a05064a96945540d06072632:1] + [Tool Bar#13851684ac57a6612b478471e4894082:1] + [Summary#12f17d5316cde76d270ad4534e1d9f35:1] + [busy#c38a44fbc4bbf985a9e846c05e60b947:1] + [expanded#45ee23081f04143c5be90131a7b0e3c5:1] + [collapsed#850739bd3f1cb1a9b82c3a6f68399233:1] + [unselected#815cb61a1bc6c57ed0a65707d8c0c2d2:1] + [on#86ddae449e2a1cb3b1fc5fed82bfc660:1] + [off#f1ca5b210c9adef0fe7f14fe85ba521d:1] + [mixed#30d19e1c3c088ba70e30538143bad3a8:1] + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fi/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fi/strings.xml new file mode 100644 index 00000000000000..7c49d51816ed44 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fi/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fr-rCA/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fr-rCA/strings.xml new file mode 100644 index 00000000000000..d5fe3fb4a959d0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fr-rCA/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fr/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fr/strings.xml new file mode 100644 index 00000000000000..14e76329cc9db8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-fr/strings.xml @@ -0,0 +1,10 @@ + + + + + + opération en cours + agrandi + réduit + mixte + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-gu/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-gu/strings.xml new file mode 100644 index 00000000000000..2a2946b617eb63 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-gu/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ha/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ha/strings.xml new file mode 100644 index 00000000000000..084b1b6aac0de0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ha/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hi/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hi/strings.xml new file mode 100644 index 00000000000000..3bca368f01586b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hi/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hr/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hr/strings.xml new file mode 100644 index 00000000000000..97250a3e1f6a4c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hr/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hu/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hu/strings.xml new file mode 100644 index 00000000000000..7c28e052110cd6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hu/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hy/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hy/strings.xml new file mode 100644 index 00000000000000..9fc19db9a6a210 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-hy/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-in/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-in/strings.xml new file mode 100644 index 00000000000000..6d32bd7238156f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-in/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-is/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-is/strings.xml new file mode 100644 index 00000000000000..e4a30caa711f08 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-is/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-it/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-it/strings.xml new file mode 100644 index 00000000000000..89dcf449cfb41d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-it/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-iw/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-iw/strings.xml new file mode 100644 index 00000000000000..dcca08e175b5d0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-iw/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ja/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ja/strings.xml new file mode 100644 index 00000000000000..437a25fe2bea8f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ja/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-jv/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-jv/strings.xml new file mode 100644 index 00000000000000..5bc93b9a564110 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-jv/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ka/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ka/strings.xml new file mode 100644 index 00000000000000..1c2a251fabd607 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ka/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-kk/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-kk/strings.xml new file mode 100644 index 00000000000000..f2afa0349f18f9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-kk/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-km/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-km/strings.xml new file mode 100644 index 00000000000000..d6ed5fadf8cf0f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-km/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-kn/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-kn/strings.xml new file mode 100644 index 00000000000000..b4a93b39ff0be2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-kn/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ko/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ko/strings.xml new file mode 100644 index 00000000000000..ba310011f0d554 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ko/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ku/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ku/strings.xml new file mode 100644 index 00000000000000..60ec646388c582 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ku/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ky/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ky/strings.xml new file mode 100644 index 00000000000000..8bdeee47c0d67f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ky/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lo/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lo/strings.xml new file mode 100644 index 00000000000000..d35f4bd7efb9eb --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lo/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lt/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lt/strings.xml new file mode 100644 index 00000000000000..c3177bbf23299d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lt/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lv/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lv/strings.xml new file mode 100644 index 00000000000000..a66be9d1d970c9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-lv/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mk/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mk/strings.xml new file mode 100644 index 00000000000000..1c76e1bb07c64e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mk/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ml/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ml/strings.xml new file mode 100644 index 00000000000000..5d568f33232987 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ml/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mn/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mn/strings.xml new file mode 100644 index 00000000000000..2f27b04d30db45 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mn/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mr/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mr/strings.xml new file mode 100644 index 00000000000000..ad239221c0e924 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-mr/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ms/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ms/strings.xml new file mode 100644 index 00000000000000..443eca7a57e1f8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ms/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-my/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-my/strings.xml new file mode 100644 index 00000000000000..65abfdbf7bd192 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-my/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-nb/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-nb/strings.xml new file mode 100644 index 00000000000000..873742c208ac05 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-nb/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ne/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ne/strings.xml new file mode 100644 index 00000000000000..47a8e78d014c40 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ne/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-nl/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-nl/strings.xml new file mode 100644 index 00000000000000..75355cf16bd979 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-nl/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pa/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pa/strings.xml new file mode 100644 index 00000000000000..ea45e686429267 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pa/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pl/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pl/strings.xml new file mode 100644 index 00000000000000..4f53d3ec705bb8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pl/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ps/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ps/strings.xml new file mode 100644 index 00000000000000..d86dbade5fa188 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ps/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pt-rPT/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pt-rPT/strings.xml new file mode 100644 index 00000000000000..a0d4564f7f0932 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pt-rPT/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pt/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pt/strings.xml new file mode 100644 index 00000000000000..3a24466f7006cd --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-pt/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-qz/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-qz/strings.xml new file mode 100644 index 00000000000000..57089df8cbebff --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-qz/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ro/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ro/strings.xml new file mode 100644 index 00000000000000..445f1af124553e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ro/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ru/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ru/strings.xml new file mode 100644 index 00000000000000..1105ec010ea037 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ru/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-si/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-si/strings.xml new file mode 100644 index 00000000000000..2153d88a0669fb --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-si/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sk/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sk/strings.xml new file mode 100644 index 00000000000000..2d1768d211d54d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sk/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sl/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sl/strings.xml new file mode 100644 index 00000000000000..a8d2a680928b58 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sl/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sn/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sn/strings.xml new file mode 100644 index 00000000000000..ed69640542f946 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sn/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-so/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-so/strings.xml new file mode 100644 index 00000000000000..bf64cbaf06b979 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-so/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sq/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sq/strings.xml new file mode 100644 index 00000000000000..a225539656b409 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sq/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sr/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sr/strings.xml new file mode 100644 index 00000000000000..47782d7cf1000e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sr/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sv/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sv/strings.xml new file mode 100644 index 00000000000000..2eed9288127946 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sv/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sw/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sw/strings.xml new file mode 100644 index 00000000000000..6dea5b85bb9a65 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-sw/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ta/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ta/strings.xml new file mode 100644 index 00000000000000..f8bfd1c350e5e0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ta/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-te/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-te/strings.xml new file mode 100644 index 00000000000000..241b213130fe0a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-te/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tg/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tg/strings.xml new file mode 100644 index 00000000000000..330b5d77cf1e83 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tg/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-th/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-th/strings.xml new file mode 100644 index 00000000000000..a4206503879525 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-th/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tk/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tk/strings.xml new file mode 100644 index 00000000000000..abee0e9ebcd3f8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tk/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tl/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tl/strings.xml new file mode 100644 index 00000000000000..dadacb703c930c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tl/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tr/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tr/strings.xml new file mode 100644 index 00000000000000..755cf862c5eb9e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-tr/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-uk/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-uk/strings.xml new file mode 100644 index 00000000000000..bf3cc46d1cb490 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-uk/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ur/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ur/strings.xml new file mode 100644 index 00000000000000..6d76f390ea068a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-ur/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-uz/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-uz/strings.xml new file mode 100644 index 00000000000000..9d4667fa9877e6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-uz/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-vi/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-vi/strings.xml new file mode 100644 index 00000000000000..706622a3be9832 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-vi/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-wo/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-wo/strings.xml new file mode 100644 index 00000000000000..b7de40359f547d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-wo/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rCN/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rCN/strings.xml new file mode 100644 index 00000000000000..c50c06c1a71bf2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rHK/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rHK/strings.xml new file mode 100644 index 00000000000000..067865f00e1e61 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rHK/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rTW/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rTW/strings.xml new file mode 100644 index 00000000000000..c56baa9ccc27f0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zh-rTW/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zu/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zu/strings.xml new file mode 100644 index 00000000000000..44cf81d27ef227 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values-zu/strings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/strings.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/strings.xml new file mode 100644 index 00000000000000..a2f6ad5263150a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/strings.xml @@ -0,0 +1,103 @@ + + + Link + Image + Button, Image + Heading + Alert + Combo Box + Menu + Menu Bar + Menu Item + Progress Bar + Radio Group + Scroll Bar + Spin Button + Tab + Tab List + Timer + Tool Bar + Summary + busy + expanded + collapsed + unselected + on + off + mixed + diff --git a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/strings_unlocalized.xml b/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/strings_unlocalized.xml deleted file mode 100644 index d06603f611372a..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/res/views/uimanager/values/strings_unlocalized.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - Link - Image - Button, Image - Heading - Alert - Combo Box - Menu - Menu Bar - Menu Item - Progress Bar - Radio Group - Scroll Bar - Spin Button - Tab - Tab List - Timer - Tool Bar - Summary - busy - expanded - collapsed - unselected - on - off - mixed - diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.java deleted file mode 100644 index abfc9546daf129..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.java +++ /dev/null @@ -1,138 +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. - */ - -package com.facebook.common.logging; - -import androidx.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -public final class FakeLoggingDelegate implements LoggingDelegate { - - public static final class LogLine { - public final int priority; - public final String tag; - public final String msg; - public final @Nullable Throwable tr; - - private LogLine(int priority, String tag, String msg, @Nullable Throwable tr) { - - this.priority = priority; - this.tag = tag; - this.msg = msg; - this.tr = tr; - } - } - - public static final int ASSERT = FLog.ASSERT; - public static final int DEBUG = FLog.DEBUG; - public static final int ERROR = FLog.ERROR; - public static final int INFO = FLog.INFO; - public static final int VERBOSE = FLog.VERBOSE; - public static final int WARN = FLog.WARN; - - /** - * There is no log level for Terrible Failures (we emit them at the Error Log-level), but to test - * that WTF errors are being logged, we are making up a new log level here, guaranteed to be - * larger than any of the other log levels. - */ - public static final int WTF = - 1 + Collections.max(Arrays.asList(ASSERT, DEBUG, ERROR, INFO, VERBOSE, WARN)); - - private int mMinLogLevel = FLog.VERBOSE; - private final ArrayList mLogs = new ArrayList<>(); - - /** Test Harness */ - private static boolean matchLogQuery( - int priority, String tag, @Nullable String throwMsg, LogLine line) { - return priority == line.priority - && tag.equals(line.tag) - && (throwMsg == null || throwMsg.equals(line.tr.getMessage())); - } - - public boolean logContains(int priority, String tag, String throwMsg) { - for (FakeLoggingDelegate.LogLine line : mLogs) { - if (matchLogQuery(priority, tag, throwMsg, line)) { - return true; - } - } - - return false; - } - - /** LoggingDelegate API */ - public int getMinimumLoggingLevel() { - return mMinLogLevel; - } - - public void setMinimumLoggingLevel(int level) { - mMinLogLevel = level; - } - - public boolean isLoggable(int level) { - return level >= mMinLogLevel; - } - - private void logImpl(int priority, String tag, String msg, Throwable tr) { - if (isLoggable(priority)) { - mLogs.add(new LogLine(priority, tag, msg, tr)); - } - } - - public void log(int priority, String tag, String msg) { - logImpl(priority, tag, msg, null); - } - - public void d(String tag, String msg, Throwable tr) { - logImpl(DEBUG, tag, msg, tr); - } - - public void d(String tag, String msg) { - logImpl(DEBUG, tag, msg, null); - } - - public void e(String tag, String msg, Throwable tr) { - logImpl(ERROR, tag, msg, tr); - } - - public void e(String tag, String msg) { - logImpl(ERROR, tag, msg, null); - } - - public void i(String tag, String msg, Throwable tr) { - logImpl(INFO, tag, msg, tr); - } - - public void i(String tag, String msg) { - logImpl(INFO, tag, msg, null); - } - - public void v(String tag, String msg, Throwable tr) { - logImpl(VERBOSE, tag, msg, tr); - } - - public void v(String tag, String msg) { - logImpl(VERBOSE, tag, msg, null); - } - - public void w(String tag, String msg, Throwable tr) { - logImpl(WARN, tag, msg, tr); - } - - public void w(String tag, String msg) { - logImpl(WARN, tag, msg, null); - } - - public void wtf(String tag, String msg, Throwable tr) { - logImpl(WTF, tag, msg, tr); - } - - public void wtf(String tag, String msg) { - logImpl(WTF, tag, msg, null); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.kt new file mode 100644 index 00000000000000..0bf7706b3f69aa --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.kt @@ -0,0 +1,94 @@ +/* + * 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. + */ + +package com.facebook.common.logging + +import java.util.* + +class FakeLoggingDelegate : LoggingDelegate { + class LogLine(val priority: Int, val tag: String, val msg: String, val tr: Throwable? = null) + + private var minLogLevel = FLog.VERBOSE + private val logs = ArrayList() + + fun logContains(priority: Int, tag: String, throwMsg: String?): Boolean { + for (line in logs) { + if (matchLogQuery(priority, tag, throwMsg, line)) { + return true + } + } + return false + } + + /** LoggingDelegate API */ + override fun getMinimumLoggingLevel(): Int = minLogLevel + + override fun setMinimumLoggingLevel(level: Int) { + minLogLevel = level + } + + override fun isLoggable(level: Int): Boolean = level >= minLogLevel + + private fun logImpl(priority: Int, tag: String, msg: String, tr: Throwable? = null) { + if (isLoggable(priority)) { + logs.add(LogLine(priority, tag, msg, tr)) + } + } + + override fun log(priority: Int, tag: String, msg: String) = logImpl(priority, tag, msg, null) + + override fun d(tag: String, msg: String, tr: Throwable) = logImpl(DEBUG, tag, msg, tr) + + override fun d(tag: String, msg: String) = logImpl(DEBUG, tag, msg, null) + + override fun e(tag: String, msg: String, tr: Throwable) = logImpl(ERROR, tag, msg, tr) + + override fun e(tag: String, msg: String) = logImpl(ERROR, tag, msg, null) + + override fun i(tag: String, msg: String, tr: Throwable) = logImpl(INFO, tag, msg, tr) + + override fun i(tag: String, msg: String) = logImpl(INFO, tag, msg, null) + + override fun v(tag: String, msg: String, tr: Throwable) = logImpl(VERBOSE, tag, msg, tr) + + override fun v(tag: String, msg: String) = logImpl(VERBOSE, tag, msg, null) + + override fun w(tag: String, msg: String, tr: Throwable) = logImpl(WARN, tag, msg, tr) + + override fun w(tag: String, msg: String) = logImpl(WARN, tag, msg, null) + + override fun wtf(tag: String, msg: String, tr: Throwable) = logImpl(WTF, tag, msg, tr) + + override fun wtf(tag: String, msg: String) = logImpl(WTF, tag, msg, null) + + companion object { + const val ASSERT = FLog.ASSERT + const val DEBUG = FLog.DEBUG + const val ERROR = FLog.ERROR + const val INFO = FLog.INFO + const val VERBOSE = FLog.VERBOSE + const val WARN = FLog.WARN + + /** + * There is no log level for Terrible Failures (we emit them at the Error Log-level), but to + * test that WTF errors are being logged, we are making up a new log level here, guaranteed to + * be larger than any of the other log levels. + */ + @JvmField val WTF = 1 + (listOf(ASSERT, DEBUG, ERROR, INFO, VERBOSE, WARN).maxOrNull() ?: 0) + + /** Test Harness */ + private fun matchLogQuery( + priority: Int, + tag: String, + throwMsg: String?, + line: LogLine + ): Boolean = + priority == line.priority && + tag == line.tag && + (throwMsg == null || throwMsg == line.tr?.message) + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedInterpolationTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedInterpolationTest.java deleted file mode 100644 index 5fdb2e9bc72edf..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedInterpolationTest.java +++ /dev/null @@ -1,161 +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. - */ - -package com.facebook.react.animated; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -/** - * Tests method used by {@link InterpolationAnimatedNode} to interpolate value of the input nodes. - */ -@RunWith(RobolectricTestRunner.class) -public class NativeAnimatedInterpolationTest { - - private double simpleInterpolation(double value, double[] input, double[] output) { - return InterpolationAnimatedNode.interpolate( - value, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_EXTEND, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_EXTEND); - } - - @Test - public void testSimpleOneToOneMapping() { - double[] input = new double[] {0d, 1d}; - double[] output = new double[] {0d, 1d}; - assertThat(simpleInterpolation(0, input, output)).isEqualTo(0); - assertThat(simpleInterpolation(0.5, input, output)).isEqualTo(0.5); - assertThat(simpleInterpolation(0.8, input, output)).isEqualTo(0.8); - assertThat(simpleInterpolation(1, input, output)).isEqualTo(1); - } - - @Test - public void testWiderOutputRange() { - double[] input = new double[] {0d, 1d}; - double[] output = new double[] {100d, 200d}; - assertThat(simpleInterpolation(0, input, output)).isEqualTo(100); - assertThat(simpleInterpolation(0.5, input, output)).isEqualTo(150); - assertThat(simpleInterpolation(0.8, input, output)).isEqualTo(180); - assertThat(simpleInterpolation(1, input, output)).isEqualTo(200); - } - - @Test - public void testWiderInputRange() { - double[] input = new double[] {2000d, 3000d}; - double[] output = new double[] {1d, 2d}; - assertThat(simpleInterpolation(2000, input, output)).isEqualTo(1); - assertThat(simpleInterpolation(2250, input, output)).isEqualTo(1.25); - assertThat(simpleInterpolation(2800, input, output)).isEqualTo(1.8); - assertThat(simpleInterpolation(3000, input, output)).isEqualTo(2); - } - - @Test - public void testManySegments() { - double[] input = new double[] {-1d, 1d, 5d}; - double[] output = new double[] {0, 10d, 20d}; - assertThat(simpleInterpolation(-1, input, output)).isEqualTo(0); - assertThat(simpleInterpolation(0, input, output)).isEqualTo(5); - assertThat(simpleInterpolation(1, input, output)).isEqualTo(10); - assertThat(simpleInterpolation(2, input, output)).isEqualTo(12.5); - assertThat(simpleInterpolation(5, input, output)).isEqualTo(20); - } - - @Test - public void testExtendExtrapolate() { - double[] input = new double[] {10d, 20d}; - double[] output = new double[] {0d, 1d}; - assertThat(simpleInterpolation(30d, input, output)).isEqualTo(2); - assertThat(simpleInterpolation(5d, input, output)).isEqualTo(-0.5); - } - - @Test - public void testClampExtrapolate() { - double[] input = new double[] {10d, 20d}; - double[] output = new double[] {0d, 1d}; - assertThat( - InterpolationAnimatedNode.interpolate( - 30d, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP)) - .isEqualTo(1); - assertThat( - InterpolationAnimatedNode.interpolate( - 5d, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP)) - .isEqualTo(0); - } - - @Test - public void testIdentityExtrapolate() { - double[] input = new double[] {10d, 20d}; - double[] output = new double[] {0d, 1d}; - assertThat( - InterpolationAnimatedNode.interpolate( - 30d, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) - .isEqualTo(30); - assertThat( - InterpolationAnimatedNode.interpolate( - 5d, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) - .isEqualTo(5); - } - - @Test - public void testInterpolateColor() { - double[] input = new double[] {0, 1}; - int[] output = new int[] {0xFF000000, 0xFFFF0000}; - assertThat(InterpolationAnimatedNode.interpolateColor(0, input, output)).isEqualTo(0xFF000000); - assertThat(InterpolationAnimatedNode.interpolateColor(0.5, input, output)) - .isEqualTo(0xFF7F0000); - } - - @Test - public void testInterpolateString() { - double[] input = new double[] {0, 1}; - double[][] output = - new double[][] { - new double[] {20, 20, 20, 80, 80, 80, 80, 20}, - new double[] {40, 40, 33, 60, 60, 60, 65, 40}, - }; - String pattern = "M20,20L20,80L80,80L80,20Z"; - assertThat( - InterpolationAnimatedNode.interpolateString( - pattern, - 0, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) - .isEqualTo("M20,20L20,80L80,80L80,20Z"); - assertThat( - InterpolationAnimatedNode.interpolateString( - pattern, - 0.5, - input, - output, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, - InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) - .isEqualTo("M30,30L26.5,70L70,70L72.5,30Z"); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedInterpolationTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedInterpolationTest.kt new file mode 100644 index 00000000000000..32355ae5fb650d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedInterpolationTest.kt @@ -0,0 +1,157 @@ +/* + * 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. + */ + +package com.facebook.react.animated + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +/** Tests method used by [InterpolationAnimatedNode] to interpolate value of the input nodes. */ +@RunWith(RobolectricTestRunner::class) +class NativeAnimatedInterpolationTest { + + @Test + fun testSimpleOneToOneMapping() { + val input = doubleArrayOf(0.0, 1.0) + val output = doubleArrayOf(0.0, 1.0) + assertThat(simpleInterpolation(0.0, input, output)).isEqualTo(0.0) + assertThat(simpleInterpolation(0.5, input, output)).isEqualTo(0.5) + assertThat(simpleInterpolation(0.8, input, output)).isEqualTo(0.8) + assertThat(simpleInterpolation(1.0, input, output)).isEqualTo(1.0) + } + + @Test + fun testWiderOutputRange() { + val input = doubleArrayOf(0.0, 1.0) + val output = doubleArrayOf(100.0, 200.0) + assertThat(simpleInterpolation(0.0, input, output)).isEqualTo(100.0) + assertThat(simpleInterpolation(0.5, input, output)).isEqualTo(150.0) + assertThat(simpleInterpolation(0.8, input, output)).isEqualTo(180.0) + assertThat(simpleInterpolation(1.0, input, output)).isEqualTo(200.0) + } + + @Test + fun testWiderInputRange() { + val input = doubleArrayOf(2000.0, 3000.0) + val output = doubleArrayOf(1.0, 2.0) + assertThat(simpleInterpolation(2000.0, input, output)).isEqualTo(1.0) + assertThat(simpleInterpolation(2250.0, input, output)).isEqualTo(1.25) + assertThat(simpleInterpolation(2800.0, input, output)).isEqualTo(1.8) + assertThat(simpleInterpolation(3000.0, input, output)).isEqualTo(2.0) + } + + @Test + fun testManySegments() { + val input = doubleArrayOf(-1.0, 1.0, 5.0) + val output = doubleArrayOf(0.0, 10.0, 20.0) + assertThat(simpleInterpolation(-1.0, input, output)).isEqualTo(0.0) + assertThat(simpleInterpolation(0.0, input, output)).isEqualTo(5.0) + assertThat(simpleInterpolation(1.0, input, output)).isEqualTo(10.0) + assertThat(simpleInterpolation(2.0, input, output)).isEqualTo(12.5) + assertThat(simpleInterpolation(5.0, input, output)).isEqualTo(20.0) + } + + @Test + fun testExtendExtrapolate() { + val input = doubleArrayOf(10.0, 20.0) + val output = doubleArrayOf(0.0, 1.0) + assertThat(simpleInterpolation(30.0, input, output)).isEqualTo(2.0) + assertThat(simpleInterpolation(5.0, input, output)).isEqualTo(-0.5) + } + + @Test + fun testClampExtrapolate() { + val input = doubleArrayOf(10.0, 20.0) + val output = doubleArrayOf(0.0, 1.0) + assertThat( + InterpolationAnimatedNode.interpolate( + 30.0, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP)) + .isEqualTo(1.0) + assertThat( + InterpolationAnimatedNode.interpolate( + 5.0, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_CLAMP)) + .isEqualTo(0.0) + } + + @Test + fun testIdentityExtrapolate() { + val input = doubleArrayOf(10.0, 20.0) + val output = doubleArrayOf(0.0, 1.0) + assertThat( + InterpolationAnimatedNode.interpolate( + 30.0, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) + .isEqualTo(30.0) + assertThat( + InterpolationAnimatedNode.interpolate( + 5.0, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) + .isEqualTo(5.0) + } + + @Test + fun testInterpolateColor() { + val input = doubleArrayOf(0.0, 1.0) + val output = intArrayOf(0xFF000000.toInt(), 0xFFFF0000.toInt()) + assertThat(InterpolationAnimatedNode.interpolateColor(0.0, input, output)) + .isEqualTo(0xFF000000.toInt()) + assertThat(InterpolationAnimatedNode.interpolateColor(0.5, input, output)) + .isEqualTo(0xFF7F0000.toInt()) + } + + @Test + fun testInterpolateString() { + val input = doubleArrayOf(0.0, 1.0) + val output = + arrayOf( + doubleArrayOf(20.0, 20.0, 20.0, 80.0, 80.0, 80.0, 80.0, 20.0), + doubleArrayOf(40.0, 40.0, 33.0, 60.0, 60.0, 60.0, 65.0, 40.0)) + val pattern = "M20,20L20,80L80,80L80,20Z" + assertThat( + InterpolationAnimatedNode.interpolateString( + pattern, + 0.0, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) + .isEqualTo("M20,20L20,80L80,80L80,20Z") + assertThat( + InterpolationAnimatedNode.interpolateString( + pattern, + 0.5, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_IDENTITY)) + .isEqualTo("M30,30L26.5,70L70,70L72.5,30Z") + } + + private fun simpleInterpolation(value: Double, input: DoubleArray, output: DoubleArray): Double = + InterpolationAnimatedNode.interpolate( + value, + input, + output, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_EXTEND, + InterpolationAnimatedNode.EXTRAPOLATE_TYPE_EXTEND) +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaOnlyArrayTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaOnlyArrayTest.java deleted file mode 100644 index 6421ae2a5985da..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaOnlyArrayTest.java +++ /dev/null @@ -1,37 +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. - */ - -package com.facebook.react.bridge; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; - -/** Tests for {@link JavaOnlyArray} */ -public class JavaOnlyArrayTest { - - @Test - public void testGetType() throws Exception { - JavaOnlyArray values = - JavaOnlyArray.of(1, 2f, 3., "4", false, JavaOnlyArray.of(), JavaOnlyMap.of(), null); - ReadableType[] expectedTypes = - new ReadableType[] { - ReadableType.Number, - ReadableType.Number, - ReadableType.Number, - ReadableType.String, - ReadableType.Boolean, - ReadableType.Array, - ReadableType.Map, - ReadableType.Null - }; - - for (int i = 0; i < values.size(); i++) { - assertThat(values.getType(i)).isEqualTo(expectedTypes[i]); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaOnlyArrayTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaOnlyArrayTest.kt new file mode 100644 index 00000000000000..46fbb1f67668d5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaOnlyArrayTest.kt @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package com.facebook.react.bridge + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +/** Tests for [JavaOnlyArray] */ +class JavaOnlyArrayTest { + @Test + fun testGetType() { + val values = + JavaOnlyArray.of(1, 2f, 3.0, "4", false, JavaOnlyArray.of(), JavaOnlyMap.of(), null) + + assertThat(values.getType(0)).isEqualTo(ReadableType.Number) + assertThat(values.getType(1)).isEqualTo(ReadableType.Number) + assertThat(values.getType(2)).isEqualTo(ReadableType.Number) + assertThat(values.getType(3)).isEqualTo(ReadableType.String) + assertThat(values.getType(4)).isEqualTo(ReadableType.Boolean) + assertThat(values.getType(5)).isEqualTo(ReadableType.Array) + assertThat(values.getType(6)).isEqualTo(ReadableType.Map) + assertThat(values.getType(7)).isEqualTo(ReadableType.Null) + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java deleted file mode 100644 index 717b689a2b04af..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java +++ /dev/null @@ -1,59 +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. - */ - -package com.facebook.react.bridge; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.facebook.react.bridge.queue.MessageQueueThreadSpec; -import com.facebook.react.bridge.queue.QueueThreadExceptionHandler; -import com.facebook.react.bridge.queue.ReactQueueConfiguration; -import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl; -import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; -import com.facebook.react.uimanager.UIManagerModule; -import org.robolectric.RuntimeEnvironment; - -/** Utility for creating pre-configured instances of core react components for tests. */ -public class ReactTestHelper { - - /** - * @return a ReactApplicationContext that has a CatalystInstance mock returned by {@link - * #createMockCatalystInstance} - */ - public static ReactApplicationContext createCatalystContextForTest() { - ReactApplicationContext context = new ReactApplicationContext(RuntimeEnvironment.application); - context.initializeWithInstance(createMockCatalystInstance()); - return context; - } - - /** @return a CatalystInstance mock that has a default working ReactQueueConfiguration. */ - public static CatalystInstance createMockCatalystInstance() { - ReactQueueConfigurationSpec spec = - ReactQueueConfigurationSpec.builder() - .setJSQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec()) - .setNativeModulesQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec()) - .build(); - ReactQueueConfiguration ReactQueueConfiguration = - ReactQueueConfigurationImpl.create( - spec, - new QueueThreadExceptionHandler() { - @Override - public void handleException(Exception e) { - throw new RuntimeException(e); - } - }); - - CatalystInstance reactInstance = mock(CatalystInstance.class); - when(reactInstance.getReactQueueConfiguration()).thenReturn(ReactQueueConfiguration); - when(reactInstance.getNativeModule(UIManagerModule.class)) - .thenReturn(mock(UIManagerModule.class)); - when(reactInstance.isDestroyed()).thenReturn(false); - - return reactInstance; - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.kt new file mode 100644 index 00000000000000..18a49fb04a003c --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.kt @@ -0,0 +1,48 @@ +/* + * 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. + */ + +package com.facebook.react.bridge + +import com.facebook.react.bridge.queue.MessageQueueThreadSpec +import com.facebook.react.bridge.queue.ReactQueueConfiguration +import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl +import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec +import com.facebook.react.uimanager.UIManagerModule +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever +import org.robolectric.RuntimeEnvironment + +/** Utility for creating pre-configured instances of core react components for tests. */ +object ReactTestHelper { + /** + * @return a ReactApplicationContext that has a CatalystInstance mock returned by + * [createMockCatalystInstance] + */ + @JvmStatic + fun createCatalystContextForTest(): ReactApplicationContext = + ReactApplicationContext(RuntimeEnvironment.application).apply { + initializeWithInstance(createMockCatalystInstance()) + } + + /** @return a CatalystInstance mock that has a default working ReactQueueConfiguration. */ + @JvmStatic + fun createMockCatalystInstance(): CatalystInstance { + val spec: ReactQueueConfigurationSpec = + ReactQueueConfigurationSpec.builder() + .setJSQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec()) + .setNativeModulesQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec()) + .build() + val reactQueueConfiguration: ReactQueueConfiguration = + ReactQueueConfigurationImpl.create(spec) { e -> throw RuntimeException(e) } + val reactInstance: CatalystInstance = mock(CatalystInstance::class.java) + whenever(reactInstance.getReactQueueConfiguration()).thenReturn(reactQueueConfiguration) + whenever(reactInstance.getNativeModule(UIManagerModule::class.java)) + .thenReturn(mock(UIManagerModule::class.java)) + whenever(reactInstance.isDestroyed()).thenReturn(false) + return reactInstance + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactHostTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactHostTest.java index 8c0218e07a2924..21950e7c54c1ea 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactHostTest.java +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactHostTest.java @@ -119,7 +119,7 @@ public void testPreload() throws Exception { assertThat(mReactHost.isInstanceInitialized()).isFalse(); - mReactHost.preload().waitForCompletion(); + mReactHost.start().waitForCompletion(); assertThat(mReactHost.isInstanceInitialized()).isTrue(); assertThat(mReactHost.getCurrentReactContext()).isNotNull(); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.java deleted file mode 100644 index 63612bc66d5ff4..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.java +++ /dev/null @@ -1,151 +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. - */ - -package com.facebook.react.devsupport; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.util.Map; -import okio.Buffer; -import okio.ByteString; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class MultipartStreamReaderTest { - - class CallCountTrackingChunkCallback implements MultipartStreamReader.ChunkListener { - private int mCount = 0; - - @Override - public void onChunkComplete(Map headers, Buffer body, boolean done) - throws IOException { - mCount++; - } - - @Override - public void onChunkProgress(Map headers, long loaded, long total) - throws IOException {} - - public int getCallCount() { - return mCount; - } - } - - @Test - public void testSimpleCase() throws IOException { - ByteString response = - ByteString.encodeUtf8( - "preable, should be ignored\r\n" - + "--sample_boundary\r\n" - + "Content-Type: application/json; charset=utf-8\r\n" - + "Content-Length: 2\r\n\r\n" - + "{}\r\n" - + "--sample_boundary--\r\n" - + "epilogue, should be ignored"); - - Buffer source = new Buffer(); - source.write(response); - - MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); - - CallCountTrackingChunkCallback callback = - new CallCountTrackingChunkCallback() { - @Override - public void onChunkComplete(Map headers, Buffer body, boolean done) - throws IOException { - super.onChunkComplete(headers, body, done); - - assertThat(done).isTrue(); - assertThat(headers.get("Content-Type")).isEqualTo("application/json; charset=utf-8"); - assertThat(body.readUtf8()).isEqualTo("{}"); - } - }; - boolean success = reader.readAllParts(callback); - - assertThat(callback.getCallCount()).isEqualTo(1); - assertThat(success).isTrue(); - } - - @Test - public void testMultipleParts() throws IOException { - ByteString response = - ByteString.encodeUtf8( - "preable, should be ignored\r\n" - + "--sample_boundary\r\n" - + "1\r\n" - + "--sample_boundary\r\n" - + "2\r\n" - + "--sample_boundary\r\n" - + "3\r\n" - + "--sample_boundary--\r\n" - + "epilogue, should be ignored"); - - Buffer source = new Buffer(); - source.write(response); - - MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); - - CallCountTrackingChunkCallback callback = - new CallCountTrackingChunkCallback() { - @Override - public void onChunkComplete(Map headers, Buffer body, boolean done) - throws IOException { - super.onChunkComplete(headers, body, done); - - assertThat(done).isEqualTo(getCallCount() == 3); - assertThat(body.readUtf8()).isEqualTo(String.valueOf(getCallCount())); - } - }; - boolean success = reader.readAllParts(callback); - - assertThat(callback.getCallCount()).isEqualTo(3); - assertThat(success).isTrue(); - } - - @Test - public void testNoDelimiter() throws IOException { - ByteString response = ByteString.encodeUtf8("Yolo"); - - Buffer source = new Buffer(); - source.write(response); - - MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); - - CallCountTrackingChunkCallback callback = new CallCountTrackingChunkCallback(); - boolean success = reader.readAllParts(callback); - - assertThat(callback.getCallCount()).isEqualTo(0); - assertThat(success).isFalse(); - } - - @Test - public void testNoCloseDelimiter() throws IOException { - ByteString response = - ByteString.encodeUtf8( - "preable, should be ignored\r\n" - + "--sample_boundary\r\n" - + "Content-Type: application/json; charset=utf-8\r\n" - + "Content-Length: 2\r\n\r\n" - + "{}\r\n" - + "--sample_boundary\r\n" - + "incomplete message..."); - - Buffer source = new Buffer(); - source.write(response); - - MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); - - CallCountTrackingChunkCallback callback = new CallCountTrackingChunkCallback(); - boolean success = reader.readAllParts(callback); - - assertThat(callback.getCallCount()).isEqualTo(1); - assertThat(success).isFalse(); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt new file mode 100644 index 00000000000000..87e37867cf749d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.kt @@ -0,0 +1,140 @@ +/* + * 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. + */ + +package com.facebook.react.devsupport + +import okio.Buffer +import okio.ByteString +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class MultipartStreamReaderTest { + + @Test + fun testSimpleCase() { + val response: ByteString = + encodeUtf8( + "preamble, should be ignored\r\n" + + "--sample_boundary\r\n" + + "Content-Type: application/json; charset=utf-8\r\n" + + "Content-Length: 2\r\n\r\n" + + "{}\r\n" + + "--sample_boundary--\r\n" + + "epilogue, should be ignored") + + val source = Buffer() + source.write(response) + + val reader = MultipartStreamReader(source, "sample_boundary") + + val callback: CallCountTrackingChunkCallback = + object : CallCountTrackingChunkCallback() { + override fun onChunkComplete(headers: Map?, body: Buffer, done: Boolean) { + super.onChunkComplete(headers, body, done) + + assertThat(done).isTrue + assertThat(headers!!["Content-Type"]).isEqualTo("application/json; charset=utf-8") + assertThat(body.readUtf8()).isEqualTo("{}") + } + } + val success = reader.readAllParts(callback) + + assertThat(callback.callCount).isEqualTo(1) + assertThat(success).isTrue + } + + @Test + fun testMultipleParts() { + val response: ByteString = + encodeUtf8( + "preamble, should be ignored\r\n" + + "--sample_boundary\r\n" + + "1\r\n" + + "--sample_boundary\r\n" + + "2\r\n" + + "--sample_boundary\r\n" + + "3\r\n" + + "--sample_boundary--\r\n" + + "epilogue, should be ignored") + + val source = Buffer() + source.write(response) + + val reader = MultipartStreamReader(source, "sample_boundary") + + val callback: CallCountTrackingChunkCallback = + object : CallCountTrackingChunkCallback() { + override fun onChunkComplete(headers: Map?, body: Buffer, done: Boolean) { + super.onChunkComplete(headers, body, done) + + assertThat(done).isEqualTo(callCount == 3) + assertThat(body.readUtf8()).isEqualTo("$callCount") + } + } + val success = reader.readAllParts(callback) + + assertThat(callback.callCount).isEqualTo(3) + assertThat(success).isTrue + } + + @Test + fun testNoDelimiter() { + val response: ByteString = encodeUtf8("Yolo") + + val source = Buffer() + source.write(response) + + val reader = MultipartStreamReader(source, "sample_boundary") + + val callback = CallCountTrackingChunkCallback() + val success = reader.readAllParts(callback) + + assertThat(callback.callCount).isEqualTo(0) + assertThat(success).isFalse + } + + @Test + fun testNoCloseDelimiter() { + val response: ByteString = + encodeUtf8( + "preamble, should be ignored\r\n" + + "--sample_boundary\r\n" + + "Content-Type: application/json; charset=utf-8\r\n" + + "Content-Length: 2\r\n\r\n" + + "{}\r\n" + + "--sample_boundary\r\n" + + "incomplete message...") + + val source = Buffer() + source.write(response) + + val reader = MultipartStreamReader(source, "sample_boundary") + + val callback = CallCountTrackingChunkCallback() + val success = reader.readAllParts(callback) + + assertThat(callback.callCount).isEqualTo(1) + assertThat(success).isFalse + } + + internal open class CallCountTrackingChunkCallback : MultipartStreamReader.ChunkListener { + var callCount = 0 + private set + + override fun onChunkComplete(headers: Map?, body: Buffer, done: Boolean) { + callCount++ + } + + override fun onChunkProgress(headers: Map, loaded: Long, total: Long) {} + } + + private fun encodeUtf8(input: String): ByteString = + ByteString.of(*input.toByteArray(Charsets.UTF_8)) +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java deleted file mode 100644 index 03ab6bc78100df..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java +++ /dev/null @@ -1,612 +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. - */ - -package com.facebook.react.fabric.events; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.times; - -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.JavaOnlyArray; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.fabric.FabricUIManager; -import com.facebook.react.uimanager.DisplayMetricsHolder; -import com.facebook.react.uimanager.events.TouchEvent; -import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; -import com.facebook.react.uimanager.events.TouchEventType; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - -@PrepareForTest({Arguments.class, FabricUIManager.class}) -@SuppressStaticInitializationFor("com.facebook.react.fabric.FabricUIManager") -@RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -public class TouchEventDispatchTest { - - private static final int SURFACE_ID = 121; - private static final int TARGET_VIEW_ID = 42; - private static final int GESTURE_START_TIME = 1; - - @Rule public PowerMockRule rule = new PowerMockRule(); - - private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper = - new TouchEventCoalescingKeyHelper(); - - /** Events (1 pointer): START -> MOVE -> MOVE -> UP */ - private final TouchEvent[] mStartMoveEndSequence = - new TouchEvent[] { - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_DOWN, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 2f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 3f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_UP, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 3f)}) - }; - - /** Expected values for {@link #mStartMoveEndSequence} */ - private final List mStartMoveEndExpectedSequence = - listOf( - /* - * START event for touch 1: - * { - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 1f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), - /* - * MOVE event for touch 1: - * { - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0))), - /* - * MOVE event for touch 1: - * { - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 3f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0))), - /* - * END event for touch 1: - * { - * touches: [], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 3f, - GESTURE_START_TIME, - 0, - Collections.emptyList(), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)))); - - /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> UP 2st -> UP 1st */ - private final TouchEvent[] mStartPointerMoveUpSequence = - new TouchEvent[] { - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_DOWN, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_DOWN, - 1, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 1f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_UP, - 1, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_UP, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 2f)}) - }; - - /** Expected values for {@link #mStartPointerMoveUpSequence} */ - private final List mStartPointerMoveUpExpectedSequence = - listOf( - /* - * START event for touch 1: - * { - * touch: 0, - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 1f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), - /* - * START event for touch 2: - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * MOVE event for touch 1: - * { - * touch: 0, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * UP event pointer 1: - * { - * touch: 1, - * touches: [touch0], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * UP event pointer 0: - * { - * touch: 0, - * touches: [], - * changed: [touch0] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - Collections.emptyList(), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)))); - - /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> CANCEL */ - private final TouchEvent[] mStartMoveCancelSequence = - new TouchEvent[] { - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_DOWN, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_DOWN, - 1, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 1f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_CANCEL, - 0, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 3f), pointerCoords(2f, 1f)}) - }; - - /** Expected values for {@link #mStartMoveCancelSequence} */ - private final List mStartMoveCancelExpectedSequence = - listOf( - /* - * START event for touch 1: - * { - * touch: 0, - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 1f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), - /* - * START event for touch 2: - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * MOVE event for touch 1: - * { - * touch: 0, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * CANCEL event: - * { - * touch: 0, - * touches: [], - * changed: [touch0, touch1] - * } - * { - * touch: 1, - * touches: [], - * changed: [touch0, touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 3f, - GESTURE_START_TIME, - 0, - Collections.emptyList(), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - Collections.emptyList(), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)))); - - List mDispatchedEvents; - FabricEventEmitter mEventEmitter; - - FabricUIManager mUIManager; - - @Before - public void setUp() { - PowerMockito.mockStatic(Arguments.class); - PowerMockito.mockStatic(FabricUIManager.class); - PowerMockito.when(Arguments.createArray()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - return new JavaOnlyArray(); - } - }); - PowerMockito.when(Arguments.createMap()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - return new JavaOnlyMap(); - } - }); - - DisplayMetrics metrics = new DisplayMetrics(); - metrics.xdpi = 1f; - metrics.ydpi = 1f; - metrics.density = 1f; - DisplayMetricsHolder.setWindowDisplayMetrics(metrics); - - mUIManager = Mockito.mock(FabricUIManager.class); - mEventEmitter = new FabricEventEmitter(mUIManager); - } - - @Test - public void testFabric_startMoveEnd() { - for (TouchEvent event : mStartMoveEndSequence) { - event.dispatchModern(mEventEmitter); - } - ArgumentCaptor argument = ArgumentCaptor.forClass(WritableMap.class); - verify(mUIManager, times(4)) - .receiveEvent( - anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()); - - assertEquals(mStartMoveEndExpectedSequence, argument.getAllValues()); - } - - @Test - public void testFabric_startMoveCancel() { - for (TouchEvent event : mStartMoveCancelSequence) { - event.dispatchModern(mEventEmitter); - } - ArgumentCaptor argument = ArgumentCaptor.forClass(WritableMap.class); - verify(mUIManager, times(6)) - .receiveEvent( - anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()); - - assertEquals(mStartMoveCancelExpectedSequence, argument.getAllValues()); - } - - @Test - public void testFabric_startPointerUpCancel() { - for (TouchEvent event : mStartPointerMoveUpSequence) { - event.dispatchModern(mEventEmitter); - } - ArgumentCaptor argument = ArgumentCaptor.forClass(WritableMap.class); - verify(mUIManager, times(6)) - .receiveEvent( - anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()); - - assertEquals(mStartPointerMoveUpExpectedSequence, argument.getAllValues()); - } - - private TouchEvent createTouchEvent( - int gestureTime, int action, int pointerId, int[] pointerIds, PointerCoords[] pointerCoords) { - mTouchEventCoalescingKeyHelper.addCoalescingKey(gestureTime); - action |= pointerId << MotionEvent.ACTION_POINTER_INDEX_SHIFT; - return TouchEvent.obtain( - SURFACE_ID, - TARGET_VIEW_ID, - getType(action), - MotionEvent.obtain( - gestureTime, - gestureTime, - action, - pointerIds.length, - pointerIds, - pointerCoords, - 0, - 0f, - 0f, - 0, - 0, - 0, - 0), - gestureTime, - pointerCoords[0].x, - pointerCoords[0].y, - mTouchEventCoalescingKeyHelper); - } - - private static TouchEventType getType(int action) { - action &= ~MotionEvent.ACTION_POINTER_INDEX_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - return TouchEventType.START; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - return TouchEventType.END; - case MotionEvent.ACTION_MOVE: - return TouchEventType.MOVE; - case MotionEvent.ACTION_CANCEL: - return TouchEventType.CANCEL; - } - - return TouchEventType.START; - } - - private static ReadableMap buildGestureEvent( - int surfaceId, - int viewTag, - float locationX, - float locationY, - int time, - int pointerId, - List touches, - List changedTouches) { - WritableMap gesture = buildGesture(surfaceId, viewTag, locationX, locationY, time, pointerId); - gesture.putArray("changedTouches", JavaOnlyArray.from(changedTouches)); - gesture.putArray("touches", JavaOnlyArray.from(touches)); - return gesture; - } - - private static WritableMap buildGesture( - int surfaceId, int viewTag, float locationX, float locationY, int time, int pointerId) { - WritableMap map = new JavaOnlyMap(); - map.putInt("targetSurface", surfaceId); - map.putInt("target", viewTag); - map.putDouble("locationX", locationX); - map.putDouble("locationY", locationY); - map.putDouble("pageX", locationX); - map.putDouble("pageY", locationY); - map.putDouble("identifier", pointerId); - map.putDouble("timestamp", time); - return map; - } - - @SafeVarargs - private static List listOf(E... args) { - return Arrays.asList(args); - } - - private static PointerCoords pointerCoords(float x, float y) { - PointerCoords pointerCoords = new PointerCoords(); - pointerCoords.x = x; - pointerCoords.y = y; - return pointerCoords; - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt new file mode 100644 index 00000000000000..950ddd5ee40d03 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt @@ -0,0 +1,600 @@ +/* + * 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. + */ + +package com.facebook.react.fabric.events + +import android.util.DisplayMetrics +import android.view.MotionEvent +import com.facebook.react.bridge.* +import com.facebook.react.fabric.FabricUIManager +import com.facebook.react.uimanager.DisplayMetricsHolder +import com.facebook.react.uimanager.events.TouchEvent +import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper +import com.facebook.react.uimanager.events.TouchEventType +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.* +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.powermock.api.mockito.PowerMockito.mockStatic +import org.powermock.api.mockito.PowerMockito.`when` as whenever +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor +import org.powermock.modules.junit4.rule.PowerMockRule +import org.robolectric.RobolectricTestRunner + +@PrepareForTest(Arguments::class, FabricUIManager::class) +@SuppressStaticInitializationFor("com.facebook.react.fabric.FabricUIManager") +@RunWith(RobolectricTestRunner::class) +@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*") +class TouchEventDispatchTest { + @get:Rule var rule = PowerMockRule() + private val touchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper() + + /** Events (1 pointer): START -> MOVE -> MOVE -> UP */ + private val startMoveEndSequence = + listOf( + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_DOWN, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 2f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 3f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_UP, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 3f)))) + + /** Expected values for [startMoveEndSequence] */ + private val startMoveEndExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * MOVE event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0))), + /* + * MOVE event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 3f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0))), + /* + * END event for touch 1: + * { + * touches: [], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 3f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = emptyList(), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)))) + + /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> UP 2st -> UP 1st */ + private val startPointerMoveUpSequence = + listOf( + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_DOWN, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_DOWN, + pointerId = 1, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 1f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 2f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_UP, + pointerId = 1, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 2f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_UP, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 2f)))) + + /** Expected values for [startPointerMoveUpSequence] */ + private val startPointerMoveUpExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touch: 0, + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * START event for touch 2: + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * MOVE event for touch 1: + * { + * touch: 0, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * UP event pointer 1: + * { + * touch: 1, + * touches: [touch0], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * UP event pointer 0: + * { + * touch: 0, + * touches: [], + * changed: [touch0] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = emptyList(), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)))) + + /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> CANCEL */ + private val startMoveCancelSequence = + listOf( + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_DOWN, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_DOWN, + pointerId = 1, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 1f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 2f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_CANCEL, + pointerId = 0, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 3f), pointerCoords(2f, 1f)))) + + /** Expected values for [startMoveCancelSequence] */ + private val startMoveCancelExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touch: 0, + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * START event for touch 2: + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * MOVE event for touch 1: + * { + * touch: 0, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * CANCEL event: + * { + * touch: 0, + * touches: [], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 3f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = emptyList(), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = emptyList(), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)))) + private var dispatchedEvents: List = emptyList() + private lateinit var eventEmitter: FabricEventEmitter + private lateinit var uiManager: FabricUIManager + + @Before + fun setUp() { + mockStatic(Arguments::class.java) + mockStatic(FabricUIManager::class.java) + whenever(Arguments.createArray()).thenAnswer { JavaOnlyArray() } + whenever(Arguments.createMap()).thenAnswer { JavaOnlyMap() } + val metrics = DisplayMetrics() + metrics.xdpi = 1f + metrics.ydpi = 1f + metrics.density = 1f + DisplayMetricsHolder.setWindowDisplayMetrics(metrics) + uiManager = mock(FabricUIManager::class.java) + eventEmitter = FabricEventEmitter(uiManager) + } + + @Test + fun testFabric_startMoveEnd() { + for (event in startMoveEndSequence) { + event.dispatchModern(eventEmitter) + } + val argument = ArgumentCaptor.forClass(WritableMap::class.java) + verify(uiManager, times(4)) + .receiveEvent( + anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()) + Assert.assertEquals(startMoveEndExpectedSequence, argument.allValues) + } + + @Test + fun testFabric_startMoveCancel() { + for (event in startMoveCancelSequence) { + event.dispatchModern(eventEmitter) + } + val argument = ArgumentCaptor.forClass(WritableMap::class.java) + verify(uiManager, times(6)) + .receiveEvent( + anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()) + Assert.assertEquals(startMoveCancelExpectedSequence, argument.allValues) + } + + @Test + fun testFabric_startPointerUpCancel() { + for (event in startPointerMoveUpSequence) { + event.dispatchModern(eventEmitter) + } + val argument = ArgumentCaptor.forClass(WritableMap::class.java) + verify(uiManager, times(6)) + .receiveEvent( + anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()) + Assert.assertEquals(startPointerMoveUpExpectedSequence, argument.allValues) + } + + private fun createTouchEvent( + gestureTime: Int, + action: Int, + pointerId: Int, + pointerIds: IntArray, + pointerCoords: Array + ): TouchEvent { + touchEventCoalescingKeyHelper.addCoalescingKey(gestureTime.toLong()) + val action = action or (pointerId shl MotionEvent.ACTION_POINTER_INDEX_SHIFT) + return TouchEvent.obtain( + SURFACE_ID, + TARGET_VIEW_ID, + getType(action), + MotionEvent.obtain( + gestureTime.toLong(), + gestureTime.toLong(), + action, + pointerIds.size, + pointerIds, + pointerCoords, + 0, + 0f, + 0f, + 0, + 0, + 0, + 0), + gestureTime.toLong(), + pointerCoords[0].x, + pointerCoords[0].y, + touchEventCoalescingKeyHelper) + } + + companion object { + private const val SURFACE_ID = 121 + private const val TARGET_VIEW_ID = 42 + private const val GESTURE_START_TIME = 1 + + private fun getType(action: Int): TouchEventType { + val action = action and MotionEvent.ACTION_POINTER_INDEX_MASK.inv() + when (action) { + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_POINTER_DOWN -> return TouchEventType.START + MotionEvent.ACTION_UP, + MotionEvent.ACTION_POINTER_UP -> return TouchEventType.END + MotionEvent.ACTION_MOVE -> return TouchEventType.MOVE + MotionEvent.ACTION_CANCEL -> return TouchEventType.CANCEL + } + return TouchEventType.START + } + + private fun buildGestureEvent( + surfaceId: Int, + viewTag: Int, + locationX: Float, + locationY: Float, + time: Int, + pointerId: Int, + touches: List, + changedTouches: List + ): ReadableMap = + buildGesture(surfaceId, viewTag, locationX, locationY, time, pointerId).apply { + putArray("changedTouches", JavaOnlyArray.from(changedTouches)) + putArray("touches", JavaOnlyArray.from(touches)) + } + + private fun buildGesture( + surfaceId: Int, + viewTag: Int, + locationX: Float, + locationY: Float, + time: Int, + pointerId: Int + ): WritableMap = + JavaOnlyMap().apply { + putInt("targetSurface", surfaceId) + putInt("target", viewTag) + putDouble("locationX", locationX.toDouble()) + putDouble("locationY", locationY.toDouble()) + putDouble("pageX", locationX.toDouble()) + putDouble("pageY", locationY.toDouble()) + putDouble("identifier", pointerId.toDouble()) + putDouble("timestamp", time.toDouble()) + } + + private fun pointerCoords(x: Float, y: Float): MotionEvent.PointerCoords = + MotionEvent.PointerCoords().apply { + this.x = x + this.y = y + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java deleted file mode 100644 index 63540fc745283a..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.java +++ /dev/null @@ -1,81 +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. - */ - -package com.facebook.react.modules.camera; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.Mockito.mock; - -import android.util.Base64; -import android.util.Base64InputStream; -import com.facebook.react.bridge.ReactApplicationContext; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -public class ImageStoreManagerTest { - - @Test - public void itDoesNotAddLineBreaks_whenBasicStringProvided() throws IOException { - byte[] exampleString = "test".getBytes(); - assertEquals("dGVzdA==", invokeConversion(new ByteArrayInputStream(exampleString))); - } - - @Test - public void itDoesNotAddLineBreaks_whenEmptyStringProvided() throws IOException { - byte[] exampleString = "".getBytes(); - assertEquals("", invokeConversion(new ByteArrayInputStream(exampleString))); - } - - @Test - public void itDoesNotAddLineBreaks_whenStringWithSpecialCharsProvided() throws IOException { - byte[] exampleString = "sdfsdf\nasdfsdfsdfsd\r\nasdas".getBytes(); - ByteArrayInputStream inputStream = new ByteArrayInputStream(exampleString); - assertFalse(invokeConversion(inputStream).contains("\n")); - } - - /** - * This test tries to test the conversion when going beyond the current buffer size (8192 bytes) - */ - @Test - public void itDoesNotAddLineBreaks_whenStringBiggerThanBuffer() throws IOException { - ByteArrayInputStream inputStream = new ByteArrayInputStream(generateRandomByteString(10000)); - assertFalse(invokeConversion(inputStream).contains("\n")); - } - - /** Just to test if using the ByteArrayInputStream isn't missing something */ - @Test - public void itDoesNotAddLineBreaks_whenBase64InputStream() throws IOException { - byte[] exampleString = "dGVzdA==".getBytes(); - Base64InputStream inputStream = - new Base64InputStream(new ByteArrayInputStream(exampleString), Base64.NO_WRAP); - assertEquals("dGVzdA==", invokeConversion(inputStream)); - } - - private String invokeConversion(InputStream inputStream) throws IOException { - return new ImageStoreManager(mock(ReactApplicationContext.class)) - .convertInputStreamToBase64OutputStream(inputStream); - } - - private byte[] generateRandomByteString(final int length) { - Random r = new Random(); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - char c = (char) (r.nextInt((int) (Character.MAX_VALUE))); - sb.append(c); - } - return sb.toString().getBytes(); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.kt new file mode 100644 index 00000000000000..eda789eb9021f5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/camera/ImageStoreManagerTest.kt @@ -0,0 +1,89 @@ +/* + * 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. + */ + +package com.facebook.react.modules.camera + +import android.util.Base64 +import android.util.Base64InputStream +import com.facebook.react.bridge.ReactApplicationContext +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.util.* +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.* +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*") +class ImageStoreManagerTest { + + private lateinit var reactApplicationContext: ReactApplicationContext + private lateinit var imageStoreManager: ImageStoreManager + + @Before + fun setUp() { + reactApplicationContext = mock(ReactApplicationContext::class.java) + imageStoreManager = ImageStoreManager(reactApplicationContext) + } + + @Test + fun itDoesNotAddLineBreaks_whenBasicStringProvided() { + val exampleString = "test".toByteArray() + Assert.assertEquals("dGVzdA==", invokeConversion(ByteArrayInputStream(exampleString))) + } + + @Test + fun itDoesNotAddLineBreaks_whenEmptyStringProvided() { + val exampleString = "".toByteArray() + Assert.assertEquals("", invokeConversion(ByteArrayInputStream(exampleString))) + } + + @Test + fun itDoesNotAddLineBreaks_whenStringWithSpecialCharsProvided() { + val exampleString = "sdfsdf\nasdfsdfsdfsd\r\nasdas".toByteArray() + val inputStream = ByteArrayInputStream(exampleString) + val converted = invokeConversion(inputStream) + Assert.assertFalse(converted.contains("\n")) + } + + /** + * This test tries to test the conversion when going beyond the current buffer size (8192 bytes) + */ + @Test + fun itDoesNotAddLineBreaks_whenStringBiggerThanBuffer() { + val inputStream = ByteArrayInputStream(generateRandomByteString(10000)) + val converted = invokeConversion(inputStream) + Assert.assertFalse(converted.contains("\n")) + } + + /** Just to test if using the ByteArrayInputStream isn't missing something */ + @Test + fun itDoesNotAddLineBreaks_whenBase64InputStream() { + val exampleString = "dGVzdA==".toByteArray() + val inputStream = Base64InputStream(ByteArrayInputStream(exampleString), Base64.NO_WRAP) + Assert.assertEquals("dGVzdA==", invokeConversion(inputStream)) + } + + private fun invokeConversion(inputStream: InputStream): String { + return ImageStoreManager(reactApplicationContext) + .convertInputStreamToBase64OutputStream(inputStream) + } + + private fun generateRandomByteString(length: Int): ByteArray { + val r = Random() + val sb = StringBuilder() + repeat(length) { + val c = r.nextInt(Char.MAX_VALUE.code).toChar() + sb.append(c) + } + return sb.toString().toByteArray() + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java deleted file mode 100644 index 4f5e3223f0b360..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java +++ /dev/null @@ -1,57 +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. - */ - -package com.facebook.react.modules.clipboard; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.text.ClipboardManager; -import com.facebook.react.bridge.ReactApplicationContext; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@SuppressLint({"ClipboardManager", "DeprecatedClass"}) -@RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -public class ClipboardModuleTest { - - private static final String TEST_CONTENT = "test"; - - private ClipboardModule mClipboardModule; - private ClipboardManager mClipboardManager; - - @Before - public void setUp() { - mClipboardModule = - new ClipboardModule(new ReactApplicationContext(RuntimeEnvironment.application)); - mClipboardManager = - (ClipboardManager) - RuntimeEnvironment.application.getSystemService(Context.CLIPBOARD_SERVICE); - } - - @Test - public void testSetString() { - mClipboardModule.setString(TEST_CONTENT); - assertTrue(mClipboardManager.getText().equals(TEST_CONTENT)); - - mClipboardModule.setString(null); - assertFalse(mClipboardManager.hasText()); - - mClipboardModule.setString(""); - assertFalse(mClipboardManager.hasText()); - - mClipboardModule.setString(" "); - assertTrue(mClipboardManager.hasText()); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt new file mode 100644 index 00000000000000..e76120e912596d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package com.facebook.react.modules.clipboard + +import android.annotation.SuppressLint +import android.content.ClipboardManager +import android.content.Context +import com.facebook.react.bridge.ReactApplicationContext +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@Suppress("DEPRECATION") +@SuppressLint("ClipboardManager", "DeprecatedClass") +@RunWith(RobolectricTestRunner::class) +class ClipboardModuleTest { + private lateinit var clipboardModule: ClipboardModule + private lateinit var clipboardManager: ClipboardManager + + @Before + fun setUp() { + clipboardModule = ClipboardModule(ReactApplicationContext(RuntimeEnvironment.application)) + clipboardManager = + RuntimeEnvironment.application.getSystemService(Context.CLIPBOARD_SERVICE) + as ClipboardManager + } + + @Test + fun testSetString() { + clipboardModule.setString(TEST_CONTENT) + assertTrue(clipboardManager.text == TEST_CONTENT) + clipboardModule.setString(null) + assertFalse(clipboardManager.hasText()) + clipboardModule.setString("") + assertFalse(clipboardManager.hasText()) + clipboardModule.setString(" ") + assertTrue(clipboardManager.hasText()) + } + + companion object { + private const val TEST_CONTENT = "test" + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.java deleted file mode 100644 index 62890cbfe8d64e..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.java +++ /dev/null @@ -1,163 +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. - */ - -package com.facebook.react.modules.deviceinfo; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.powermock.api.mockito.PowerMockito.mockStatic; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.CatalystInstance; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactTestHelper; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.uimanager.DisplayMetricsHolder; -import java.util.Arrays; -import java.util.List; -import junit.framework.TestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -@PrepareForTest({Arguments.class, DisplayMetricsHolder.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -public class DeviceInfoModuleTest extends TestCase { - - @Rule public PowerMockRule rule = new PowerMockRule(); - - private DeviceInfoModule mDeviceInfoModule; - - private WritableMap fakePortraitDisplayMetrics; - private WritableMap fakeLandscapeDisplayMetrics; - - private ReactApplicationContext mContext; - - @Before - public void setUp() { - initTestData(); - - mockStatic(DisplayMetricsHolder.class); - mContext = spy(new ReactApplicationContext(RuntimeEnvironment.application)); - CatalystInstance catalystInstanceMock = ReactTestHelper.createMockCatalystInstance(); - mContext.initializeWithInstance(catalystInstanceMock); - - mDeviceInfoModule = new DeviceInfoModule(mContext); - } - - @After - public void teardown() { - DisplayMetricsHolder.setWindowDisplayMetrics(null); - DisplayMetricsHolder.setScreenDisplayMetrics(null); - } - - @Test - public void test_itDoesNotEmitAnEvent_whenDisplayMetricsNotChanged() { - givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics); - - mDeviceInfoModule.getTypedExportedConstants(); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - - verify(mContext, times(0)).emitDeviceEvent(anyString(), any()); - } - - @Test - public void test_itEmitsOneEvent_whenDisplayMetricsChangedOnce() { - givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics); - - mDeviceInfoModule.getTypedExportedConstants(); - givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - - verifyUpdateDimensionsEventsEmitted(mContext, fakeLandscapeDisplayMetrics); - } - - @Test - public void test_itEmitsJustOneEvent_whenUpdateRequestedMultipleTimes() { - givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics); - mDeviceInfoModule.getTypedExportedConstants(); - givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - - verifyUpdateDimensionsEventsEmitted(mContext, fakeLandscapeDisplayMetrics); - } - - @Test - public void test_itEmitsMultipleEvents_whenDisplayMetricsChangedBetweenUpdates() { - givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics); - - mDeviceInfoModule.getTypedExportedConstants(); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics); - mDeviceInfoModule.emitUpdateDimensionsEvent(); - - verifyUpdateDimensionsEventsEmitted( - mContext, - fakeLandscapeDisplayMetrics, - fakePortraitDisplayMetrics, - fakeLandscapeDisplayMetrics); - } - - private static void givenDisplayMetricsHolderContains(final WritableMap fakeDisplayMetrics) { - when(DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)) - .thenAnswer(invocation -> fakeDisplayMetrics); - } - - private static void verifyUpdateDimensionsEventsEmitted( - ReactApplicationContext context, WritableMap... expectedEvents) { - List expectedEventList = Arrays.asList(expectedEvents); - ArgumentCaptor captor = ArgumentCaptor.forClass(WritableMap.class); - verify(context, times(expectedEventList.size())) - .emitDeviceEvent(eq("didUpdateDimensions"), captor.capture()); - - List actualEvents = captor.getAllValues(); - assertThat(actualEvents).isEqualTo(expectedEventList); - } - - private void initTestData() { - mockStatic(Arguments.class); - when(Arguments.createMap()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - return new JavaOnlyMap(); - } - }); - - fakePortraitDisplayMetrics = Arguments.createMap(); - fakePortraitDisplayMetrics.putInt("width", 100); - fakePortraitDisplayMetrics.putInt("height", 200); - - fakeLandscapeDisplayMetrics = Arguments.createMap(); - fakeLandscapeDisplayMetrics.putInt("width", 200); - fakeLandscapeDisplayMetrics.putInt("height", 100); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt new file mode 100644 index 00000000000000..252a8a26478d4e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt @@ -0,0 +1,138 @@ +/* + * 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. + */ + +package com.facebook.react.modules.deviceinfo + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.ReactTestHelper +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.DisplayMetricsHolder +import junit.framework.TestCase +import org.assertj.core.api.Assertions +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.* +import org.powermock.api.mockito.PowerMockito +import org.powermock.api.mockito.PowerMockito.`when` as whenever +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.rule.PowerMockRule +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +@PrepareForTest(Arguments::class, DisplayMetricsHolder::class) +@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*") +class DeviceInfoModuleTest : TestCase() { + @get:Rule var rule = PowerMockRule() + + private lateinit var deviceInfoModule: DeviceInfoModule + private lateinit var fakePortraitDisplayMetrics: WritableMap + private lateinit var fakeLandscapeDisplayMetrics: WritableMap + private lateinit var reactContext: ReactApplicationContext + + @Before + public override fun setUp() { + initTestData() + PowerMockito.mockStatic(DisplayMetricsHolder::class.java) + reactContext = spy(ReactApplicationContext(RuntimeEnvironment.application)) + val catalystInstanceMock = ReactTestHelper.createMockCatalystInstance() + reactContext.initializeWithInstance(catalystInstanceMock) + deviceInfoModule = DeviceInfoModule(reactContext) + } + + @After + fun teardown() { + DisplayMetricsHolder.setWindowDisplayMetrics(null) + DisplayMetricsHolder.setScreenDisplayMetrics(null) + } + + @Test + fun test_itDoesNotEmitAnEvent_whenDisplayMetricsNotChanged() { + givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics) + deviceInfoModule.typedExportedConstants + deviceInfoModule.emitUpdateDimensionsEvent() + verify(reactContext, times(0)) + ?.emitDeviceEvent(ArgumentMatchers.anyString(), ArgumentMatchers.any()) + } + + @Test + fun test_itEmitsOneEvent_whenDisplayMetricsChangedOnce() { + givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics) + deviceInfoModule.typedExportedConstants + givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics) + deviceInfoModule.emitUpdateDimensionsEvent() + verifyUpdateDimensionsEventsEmitted(reactContext, fakeLandscapeDisplayMetrics) + } + + @Test + fun test_itEmitsJustOneEvent_whenUpdateRequestedMultipleTimes() { + givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics) + deviceInfoModule.typedExportedConstants + givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics) + deviceInfoModule.emitUpdateDimensionsEvent() + deviceInfoModule.emitUpdateDimensionsEvent() + verifyUpdateDimensionsEventsEmitted(reactContext, fakeLandscapeDisplayMetrics) + } + + @Test + fun test_itEmitsMultipleEvents_whenDisplayMetricsChangedBetweenUpdates() { + givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics) + deviceInfoModule.typedExportedConstants + deviceInfoModule.emitUpdateDimensionsEvent() + givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics) + deviceInfoModule.emitUpdateDimensionsEvent() + givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics) + deviceInfoModule.emitUpdateDimensionsEvent() + givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics) + deviceInfoModule.emitUpdateDimensionsEvent() + verifyUpdateDimensionsEventsEmitted( + reactContext, + fakeLandscapeDisplayMetrics, + fakePortraitDisplayMetrics, + fakeLandscapeDisplayMetrics) + } + + private fun initTestData() { + PowerMockito.mockStatic(Arguments::class.java) + whenever(Arguments.createMap()).thenAnswer { JavaOnlyMap() } + fakePortraitDisplayMetrics = Arguments.createMap() + fakePortraitDisplayMetrics.putInt("width", 100) + fakePortraitDisplayMetrics.putInt("height", 200) + fakeLandscapeDisplayMetrics = Arguments.createMap() + fakeLandscapeDisplayMetrics.putInt("width", 200) + fakeLandscapeDisplayMetrics.putInt("height", 100) + } + + companion object { + private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) { + whenever(DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)).thenAnswer { + fakeDisplayMetrics + } + } + + private fun verifyUpdateDimensionsEventsEmitted( + context: ReactContext?, + vararg expectedEvents: WritableMap + ) { + val expectedEventList = listOf(*expectedEvents) + val captor = ArgumentCaptor.forClass(WritableMap::class.java) + verify(context, times(expectedEventList.size)) + ?.emitDeviceEvent(ArgumentMatchers.eq("didUpdateDimensions"), captor.capture()) + val actualEvents = captor.allValues + Assertions.assertThat(actualEvents).isEqualTo(expectedEventList) + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java deleted file mode 100644 index d93357166e415d..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java +++ /dev/null @@ -1,176 +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. - */ - -package com.facebook.react.modules.dialog; - -import static android.os.Looper.getMainLooper; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.when; -import static org.robolectric.Shadows.shadowOf; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import androidx.fragment.app.FragmentActivity; -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReactApplicationContext; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.android.controller.ActivityController; - -@RunWith(RobolectricTestRunner.class) -public class DialogModuleTest { - - private ActivityController mActivityController; - private FragmentActivity mActivity; - private DialogModule mDialogModule; - - static final class SimpleCallback implements Callback { - private Object[] mArgs; - private int mCalls; - - @Override - public void invoke(Object... args) { - mCalls++; - mArgs = args; - } - - public int getCalls() { - return mCalls; - } - - public Object[] getArgs() { - return mArgs; - } - } - - @Before - public void setUp() throws Exception { - mActivityController = Robolectric.buildActivity(FragmentActivity.class); - mActivity = mActivityController.create().start().resume().get(); - - final ReactApplicationContext context = Mockito.mock(ReactApplicationContext.class); - when(context.hasActiveReactInstance()).thenReturn(true); - when(context.getCurrentActivity()).thenReturn(mActivity); - - mDialogModule = new DialogModule(context); - mDialogModule.onHostResume(); - } - - @After - public void tearDown() { - mActivityController.pause().stop().destroy(); - - mActivityController = null; - mDialogModule = null; - } - - @Test - public void testAllOptions() { - final JavaOnlyMap options = new JavaOnlyMap(); - options.putString("title", "Title"); - options.putString("message", "Message"); - options.putString("buttonPositive", "OK"); - options.putString("buttonNegative", "Cancel"); - options.putString("buttonNeutral", "Later"); - options.putBoolean("cancelable", false); - - mDialogModule.showAlert(options, null, null); - shadowOf(getMainLooper()).idle(); - - final AlertFragment fragment = getFragment(); - - assertNotNull("Fragment was not displayed", fragment); - assertFalse(fragment.isCancelable()); - - final AlertDialog dialog = (AlertDialog) fragment.getDialog(); - assertEquals("OK", dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText().toString()); - assertEquals("Cancel", dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText().toString()); - assertEquals("Later", dialog.getButton(DialogInterface.BUTTON_NEUTRAL).getText().toString()); - } - - @Test - public void testCallbackPositive() { - final JavaOnlyMap options = new JavaOnlyMap(); - options.putString("buttonPositive", "OK"); - - final SimpleCallback actionCallback = new SimpleCallback(); - mDialogModule.showAlert(options, null, actionCallback); - shadowOf(getMainLooper()).idle(); - - final AlertDialog dialog = (AlertDialog) getFragment().getDialog(); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); - shadowOf(getMainLooper()).idle(); - - assertEquals(1, actionCallback.getCalls()); - assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]); - assertEquals(DialogInterface.BUTTON_POSITIVE, actionCallback.getArgs()[1]); - } - - @Test - public void testCallbackNegative() { - final JavaOnlyMap options = new JavaOnlyMap(); - options.putString("buttonNegative", "Cancel"); - - final SimpleCallback actionCallback = new SimpleCallback(); - mDialogModule.showAlert(options, null, actionCallback); - shadowOf(getMainLooper()).idle(); - - final AlertDialog dialog = (AlertDialog) getFragment().getDialog(); - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick(); - shadowOf(getMainLooper()).idle(); - - assertEquals(1, actionCallback.getCalls()); - assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]); - assertEquals(DialogInterface.BUTTON_NEGATIVE, actionCallback.getArgs()[1]); - } - - @Test - public void testCallbackNeutral() { - final JavaOnlyMap options = new JavaOnlyMap(); - options.putString("buttonNeutral", "Later"); - - final SimpleCallback actionCallback = new SimpleCallback(); - mDialogModule.showAlert(options, null, actionCallback); - shadowOf(getMainLooper()).idle(); - - final AlertDialog dialog = (AlertDialog) getFragment().getDialog(); - dialog.getButton(DialogInterface.BUTTON_NEUTRAL).performClick(); - shadowOf(getMainLooper()).idle(); - - assertEquals(1, actionCallback.getCalls()); - assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]); - assertEquals(DialogInterface.BUTTON_NEUTRAL, actionCallback.getArgs()[1]); - } - - @Test - public void testCallbackDismiss() { - final JavaOnlyMap options = new JavaOnlyMap(); - - final SimpleCallback actionCallback = new SimpleCallback(); - mDialogModule.showAlert(options, null, actionCallback); - shadowOf(getMainLooper()).idle(); - - getFragment().getDialog().dismiss(); - shadowOf(getMainLooper()).idle(); - - assertEquals(1, actionCallback.getCalls()); - assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.getArgs()[0]); - } - - private AlertFragment getFragment() { - return (AlertFragment) - mActivity.getSupportFragmentManager().findFragmentByTag(DialogModule.FRAGMENT_TAG); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.kt new file mode 100644 index 00000000000000..94d4d63e664646 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.kt @@ -0,0 +1,161 @@ +/* + * 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. + */ + +package com.facebook.react.modules.dialog + +import android.app.AlertDialog +import android.content.DialogInterface +import android.os.Looper.getMainLooper +import androidx.fragment.app.FragmentActivity +import com.facebook.react.bridge.Callback +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReactApplicationContext +import org.junit.* +import org.junit.Assert.* +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.powermock.api.mockito.PowerMockito.`when` as whenever +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.android.controller.ActivityController + +@RunWith(RobolectricTestRunner::class) +class DialogModuleTest { + + private lateinit var activityController: ActivityController + private lateinit var activity: FragmentActivity + private lateinit var dialogModule: DialogModule + + class SimpleCallback : Callback { + var args: Array? = null + private set + + var calls: Int = 0 + private set + + override fun invoke(vararg args: Any?) { + this.calls++ + this.args = args + } + } + + @Before + fun setUp() { + activityController = Robolectric.buildActivity(FragmentActivity::class.java) + activity = activityController.create().start().resume().get() + + val context: ReactApplicationContext = mock(ReactApplicationContext::class.java) + whenever(context.hasActiveReactInstance()).thenReturn(true) + whenever(context.currentActivity).thenReturn(activity) + + dialogModule = DialogModule(context) + dialogModule.onHostResume() + } + + @After + fun tearDown() { + activityController.pause().stop().destroy() + } + + @Test + fun testAllOptions() { + val options = + JavaOnlyMap().apply { + putString("title", "Title") + putString("message", "Message") + putString("buttonPositive", "OK") + putString("buttonNegative", "Cancel") + putString("buttonNeutral", "Later") + putBoolean("cancelable", false) + } + + dialogModule.showAlert(options, null, null) + shadowOf(getMainLooper()).idle() + + val fragment = getFragment() + + assertNotNull("Fragment was not displayed", fragment) + assertFalse(fragment!!.isCancelable) + + val dialog = fragment.dialog as AlertDialog + assertEquals("OK", dialog.getButton(DialogInterface.BUTTON_POSITIVE).text.toString()) + assertEquals("Cancel", dialog.getButton(DialogInterface.BUTTON_NEGATIVE).text.toString()) + assertEquals("Later", dialog.getButton(DialogInterface.BUTTON_NEUTRAL).text.toString()) + } + + @Test + fun testCallbackPositive() { + val options = JavaOnlyMap().apply { putString("buttonPositive", "OK") } + + val actionCallback = SimpleCallback() + dialogModule.showAlert(options, null, actionCallback) + shadowOf(getMainLooper()).idle() + + val dialog = getFragment()!!.dialog as AlertDialog + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick() + shadowOf(getMainLooper()).idle() + + assertEquals(1, actionCallback.calls) + assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.args!![0]) + assertEquals(DialogInterface.BUTTON_POSITIVE, actionCallback.args!![1]) + } + + @Test + fun testCallbackNegative() { + val options = JavaOnlyMap().apply { putString("buttonNegative", "Cancel") } + + val actionCallback = SimpleCallback() + dialogModule.showAlert(options, null, actionCallback) + shadowOf(getMainLooper()).idle() + + val dialog = getFragment()!!.dialog as AlertDialog + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick() + shadowOf(getMainLooper()).idle() + + assertEquals(1, actionCallback.calls) + assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.args!![0]) + assertEquals(DialogInterface.BUTTON_NEGATIVE, actionCallback.args!![1]) + } + + @Test + fun testCallbackNeutral() { + val options = JavaOnlyMap().apply { putString("buttonNeutral", "Later") } + + val actionCallback = SimpleCallback() + dialogModule.showAlert(options, null, actionCallback) + shadowOf(getMainLooper()).idle() + + val dialog = getFragment()!!.dialog as AlertDialog + dialog.getButton(DialogInterface.BUTTON_NEUTRAL).performClick() + shadowOf(getMainLooper()).idle() + + assertEquals(1, actionCallback.calls) + assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.args!![0]) + assertEquals(DialogInterface.BUTTON_NEUTRAL, actionCallback.args!![1]) + } + + @Test + fun testCallbackDismiss() { + val options = JavaOnlyMap() + + val actionCallback = SimpleCallback() + dialogModule.showAlert(options, null, actionCallback) + shadowOf(getMainLooper()).idle() + + getFragment()!!.dialog!!.dismiss() + shadowOf(getMainLooper()).idle() + + assertEquals(1, actionCallback.calls) + assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.args!![0]) + } + + private fun getFragment(): AlertFragment? { + return activity.supportFragmentManager.findFragmentByTag(DialogModule.FRAGMENT_TAG) + as? AlertFragment + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/HeaderUtilTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/HeaderUtilTest.java deleted file mode 100644 index 35c17cfbc21e98..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/HeaderUtilTest.java +++ /dev/null @@ -1,73 +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. - */ - -package com.facebook.react.modules.network; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class HeaderUtilTest { - public static final String TABULATION_TEST = "\teyJhbGciOiJS\t"; - public static final String TABULATION_STRIP_EXPECTED = "eyJhbGciOiJS"; - public static final String NUMBERS_TEST = "0123456789"; - public static final String SPECIALS_TEST = "!@#$%^&*()-=_+{}[]\\|;:'\",.<>/?"; - public static final String ALPHABET_TEST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWHYZ"; - public static final String VALUE_BANNED_SYMBOLS_TEST = "���name�����������\u007f\u001f"; - public static final String NAME_BANNED_SYMBOLS_TEST = "���name�����������\u007f\u0020\u001f"; - public static final String BANNED_TEST_EXPECTED = "name"; - - @Test - public void nameStripKeepsLetters() { - assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderName(ALPHABET_TEST)); - } - - @Test - public void valueStripKeepsLetters() { - assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderValue(ALPHABET_TEST)); - } - - @Test - public void nameStripKeepsNumbers() { - assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderName(NUMBERS_TEST)); - } - - @Test - public void valueStripKeepsNumbers() { - assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderValue(NUMBERS_TEST)); - } - - @Test - public void valueStripKeepsSpecials() { - assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderValue(SPECIALS_TEST)); - } - - @Test - public void nameStripKeepsSpecials() { - assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderName(SPECIALS_TEST)); - } - - @Test - public void valueStripKeepsTabs() { - assertEquals(TABULATION_TEST, HeaderUtil.stripHeaderValue(TABULATION_TEST)); - } - - @Test - public void nameStripDeletesTabs() { - assertEquals(TABULATION_STRIP_EXPECTED, HeaderUtil.stripHeaderName(TABULATION_TEST)); - } - - @Test - public void valueStripRemovesExtraSymbols() { - assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderValue(VALUE_BANNED_SYMBOLS_TEST)); - } - - @Test - public void nameStripRemovesExtraSymbols() { - assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderName(NAME_BANNED_SYMBOLS_TEST)); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/HeaderUtilTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/HeaderUtilTest.kt new file mode 100644 index 00000000000000..7696779d7d66c9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/HeaderUtilTest.kt @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package com.facebook.react.modules.network + +import org.junit.Assert.assertEquals +import org.junit.Test + +class HeaderUtilTest { + companion object { + const val TABULATION_TEST = "\teyJhbGciOiJS\t" + const val TABULATION_STRIP_EXPECTED = "eyJhbGciOiJS" + const val NUMBERS_TEST = "0123456789" + const val SPECIALS_TEST = "!@#$%^&*()-=_+{}[]\\|;:'\",.<>/?" + const val ALPHABET_TEST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWHYZ" + const val VALUE_BANNED_SYMBOLS_TEST = "���name�����������\u007f\u001f" + const val NAME_BANNED_SYMBOLS_TEST = "���name�����������\u007f\u0020\u001f" + const val BANNED_TEST_EXPECTED = "name" + } + + @Test + fun nameStripKeepsLetters() { + assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderName(ALPHABET_TEST)) + } + + @Test + fun valueStripKeepsLetters() { + assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderValue(ALPHABET_TEST)) + } + + @Test + fun nameStripKeepsNumbers() { + assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderName(NUMBERS_TEST)) + } + + @Test + fun valueStripKeepsNumbers() { + assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderValue(NUMBERS_TEST)) + } + + @Test + fun valueStripKeepsSpecials() { + assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderValue(SPECIALS_TEST)) + } + + @Test + fun nameStripKeepsSpecials() { + assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderName(SPECIALS_TEST)) + } + + @Test + fun valueStripKeepsTabs() { + assertEquals(TABULATION_TEST, HeaderUtil.stripHeaderValue(TABULATION_TEST)) + } + + @Test + fun nameStripDeletesTabs() { + assertEquals(TABULATION_STRIP_EXPECTED, HeaderUtil.stripHeaderName(TABULATION_TEST)) + } + + @Test + fun valueStripRemovesExtraSymbols() { + assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderValue(VALUE_BANNED_SYMBOLS_TEST)) + } + + @Test + fun nameStripRemovesExtraSymbols() { + assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderName(NAME_BANNED_SYMBOLS_TEST)) + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/ProgressiveStringDecoderTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/ProgressiveStringDecoderTest.java deleted file mode 100644 index 3078bcbfa64942..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/ProgressiveStringDecoderTest.java +++ /dev/null @@ -1,138 +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. - */ - -package com.facebook.react.modules.network; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class ProgressiveStringDecoderTest { - - private static String TEST_DATA_1_BYTE = - "Lorem ipsum dolor sit amet, ea ius viris laoreet gloriatur, ea enim illud mel. Ea eligendi erroribus inciderint sea, id nemore sensibus contentiones qui. Eos et nulla abhorreant, noluisse adipiscing reprehendunt an sit. Harum iriure meliore ne nec, clita semper voluptaria at sea. Ius civibus vituperata reprehendunt ut.\n" - + "\n" - + "Sed nisl postea maiorum ex, mea eros verterem ea. Ne usu brute debitis appareat. Ad quem reprimique dissentias duo. Sit an labitur eleifend, illud zril audiam nam ex, epicuri luptatum ne usu. Lorem mundi utinam vix ea.\n" - + "\n" - + "Te eam nominati qualisque. Ut praesent consetetur pro. Soleat vivendum vim ea. Altera dolores eam in. Eum at praesent complectitur. Nec ea inani definitiones, tantas vivendum mei an, mea an ubique omnium latine. Has mundi ocurreret ei, nam ea iuvaret gloriatur.\n" - + "\n" - + "Ad omnes malorum vim, no latine facilisi mel, dicant salutandi conclusionemque ei est. Nam cu partem alterum minimum. Et quo iriure deleniti accommodare, ad impetus perfecto liberavisse pri. Instructior necessitatibus ut mel, ex cum sumo atqui comprehensam, ei nullam oporteat sed. Ius meliore placerat cu.\n" - + "\n" - + "Eum in ferri nobis, eam eu verear facilisis referrentur. Veniam epicuri referrentur at nam. Vel congue diceret fabulas te, ei fabellas temporibus mei. Nemore corrumpit quo ex, et vis soluta reprehendunt. Et eos eripuit atomorum.\n" - + "\n" - + "Eum no novum tantas decore. Indoctum definiebas intellegam ut vel. Cu per ipsum graeco, in nam dico dolore, usu id ludus consulatu. Vis an clita commune, cu quot quaeque cum. In eos semper aperiri. Ne mea probo inermis, no vis audiam volutpat.\n" - + "\n" - + "Cu quaeque scaevola vis. Civibus commune scriptorem vim an, vim ea vocent petentium consequuntur, meis propriae invidunt eam ex. Pro et ponderum recusabo sapientem. Vel legere possim ornatus ne, saepe commodo scaevola an quo. An scaevola repudiandae sed. Eam ei veri nemore.\n" - + "\n" - + "Ullum deleniti cum at. An has soleat docendi, epicuri erroribus inciderint pro ea. Noluisse invidunt splendide quo in, eam odio invenire ea. Eu hinc definiebas scripserit duo, has cu equidem ponderum expetenda, eum vulputate intellegat id. Pri eu natum semper pertinax, ei vel inani aliquip habemus, sit an facer dicam. Et graeci abhorreant contentiones duo, et summo partiendo conclusionemque per.\n" - + "\n" - + "Sed ei etiam iudico abhorreant. Pri an regione fastidii, clita discere eu nec. Torquatos percipitur inciderint eos in, id per prompta blandit. Sit et epicuri deleniti. Per labores corpora no.\n" - + "\n" - + "Quodsi melius facilis pri ei, has adhuc recusabo reprimique ut. Laoreet definitionem cum cu, amet nonumes ut vis, qui ut sonet ancillae. Vim no doctus efficiantur, ancillae indoctum ex sea, vel eu fabulas volumus argumentum. Ex eum aeque commune placerat, nam choro tamquam luptatum et. Ne sea vero idque liberavisse"; - - private static String TEST_DATA_2_BYTES = - "Лорем ипсум долор сит амет, доминг дисцере ад вих, велит игнота ратионибус мел цу. Не вирис малорум яуаеяуе хас, еу либрис доцтус хис. Моллис садипсцинг ан цум, семпер молестие репрехендунт усу те. Цасе аетерно оффендит ан еос. При ан толлит опортере оцурререт, ан яуот мутат трацтатос вих.\n" - + "\n" - + "Нец фалли харум ратионибус еа. Магна адмодум ат нам, яуи еа рецусабо мандамус, аццусам цонсеяуунтур цу хис. Импедит цотидиеяуе улламцорпер еа мел, усу ет долорес аргументум. Веро торяуатос ех нам, цибо либерависсе ест еи. Вис долор омниум сплендиде ад, велит рецусабо цонсететур иус цу.\n" - + "\n" - + "Еи дуо меис атоморум сигниферумяуе, аугуе аццусам мел ет. Ут ностро легендос хонестатис пер, ут яуас мовет сеа. Меа цу продессет аппеллантур. Вис еа яуод оффендит, дебет видерер ет нам.\n" - + "\n" - + "Еам еа дебитис иудицабит, не хас иллуд цивибус. Усу ет алии уллум утамур. Поссит цонституто те яуи, хас ет лаудем аудире, нам еи епицури салутанди. Лудус делицатиссими цум еу, либер адиписцинг еи нец. Ид ерипуит лобортис антиопам хис, санцтус елигенди неглегентур сед ут, вел сентентиае инструцтиор еи. Ан про унум яуалисяуе.\n" - + "\n" - + "Ат еррор алтера сит, пер еу яуот номинави. Пертинах репудиаре цум еу. Еа фуиссет антиопам вим, пробатус реферрентур ут иус. Еум ад модус утрояуе диспутандо.\n" - + "\n" - + "Ехерци бландит ут меа. Солет импедит сед ад. Дуо порро тимеам аудире не, алии ерант номинави цу нец, сит ферри веритус адиписци те. Те меи синт адверсариум, ад феугаит инвидунт луцилиус сед, дицунт нумяуам нам те. Еум дицант елеифенд цонсецтетуер ет, суммо вереар епицуреи не про. Не лудус сцрипта опортере вим, еи дуо идяуе алияуам сигниферумяуе. Цум еу лабитур инвенире, про ессе губергрен темпорибус еи, ад хис минимум пертинах.\n" - + "\n" - + "Дуо ад вери евертитур интеллегат, демоцритум еффициенди дуо ет. Нец но доценди демоцритум сцрипторем, витуперата цонституам нецесситатибус ут вим. Яуи виде санцтус мандамус ан, нонумес принципес вел ат, ех дуо инани нулла. Петентиум маиестатис еам ин, те ерант дебитис еурипидис вис. Но вел антиопам цотидиеяуе еффициантур, сеа еи нибх нонумы инцидеринт.\n" - + "\n" - + "Одио омнес но яуо, популо ноструд иус ад. Инани хонестатис но вис. Хис еу лудус партем персиус, пурто малис витуперата при ан, еи елаборарет ассуеверит вим. Цу бруте утинам тинцидунт вих, цум ад дицтас лобортис лаборамус. Нец хабемус рецусабо ат, ех фацилис денияуе ест. При те велит алияуам аццусамус, юсто утамур антиопам но нам.\n" - + "\n" - + "Про не еррем иудицо мелиоре, еи цибо ерудити санцтус хас. Яуод еяуидем еу вис, вих яуидам легимус ад, ид сеа солум легере мандамус. Аеяуе детрахит ех иус, суас вертерем еум цу. Еи вим алиа ехерци пхаедрум, хас не лаборес цоррумпит. Ат граеци сцрипта вим.\n" - + "\n" - + "Иус ат менандри персеяуерис. Про модус дицта еу, ин граеци доценди фиерент при, еи хас аугуе мандамус дефинитионем. Ет путент интерпретарис сит, перицула сентентиае ат ест. При ут сумо видит волуптатибус, нобис деленити еа."; - private static String TEST_DATA_3_BYTES = - "案のづよド捕毎エオ文疑ろめた今宮レ秋像とが供持属ょー真場中ホサヒ不箱らご著質ーぼンろ保6年読さ系蔵べるル緩参フシセタ鮮県フずッ歳民ナセ楽飲匹恒桜ぱ。要電ネソメ嘉負向ス援中ぜく界党フネ属平ぎ象越容レ書95争効99争効7翌テ売約わこよッ紙点発事9入そさ補綱のラず他亭匠ぞ。\n" - + "\n" - + "天レ供内ソ愛7読でぽせ回書ほごしな浅月企設潟せぐり裂個ホヌヤ局題制エ柏央ざぽ。外くにさ下格か終所あ硬当ワ着少選とけリへ康件終にぎ季規らおず給測トユテ考毎サトス事版にーご文8忙チ深暮タヲムラ度6応しぞぎぐ装速て続際ぞ発准揮包孤てい。制はたちき合南む乙甲ゅさと捕4球任条こでン頭広セスモウ月夜エス面陽ヨネ力京ウリ紙聞ト印2火映ラ基頭スフ点愛伎協ねド。\n" - + "\n" - + "属と共代みむもず以監すい者新ス田政家ヱス使校音刑トホ則上ゅぐ一未ヌ意40芸標んは学必強ゅ帝歯没牧具もか。58新イシレ正米ニユ負皇っぐせの必容キソタコ公3容ーつぶべ年然検ざ整賞ニチ注興ぐ放約えあ野夜磨やゃフよ。柳ソシアテ申1科ル舗紀深むぜ競供とび室全ハネ測高エラク権暮ヲクオト館暮ヌ黒杯クリぴぽ火竹ねる種4帰替やあい北問クルゃン登壌粉つどべ。"; - - private static final String TEST_DATA_4_BYTES = - "\uD800\uDE55\uD800\uDE55\uD800\uDE55 \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55" - + "\uD800\uDE55\uD800\uDE55\uD800\uDE55 \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80" - + "\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80" - + "\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80" - + "\uD800\uDE80\uD800\uDE80\uD800\uDE80"; - - @Test - public void testUTF8SingleByteSymbols() { - chunkString(TEST_DATA_1_BYTE, StandardCharsets.UTF_8, 64); - } - - @Test - public void testUTF8twoBytesSymbols() { - chunkString(TEST_DATA_2_BYTES, StandardCharsets.UTF_8, 63); - } - - @Test - public void testUTF8ThreeBytesSymbols() throws Exception { - chunkString(TEST_DATA_3_BYTES, StandardCharsets.UTF_8, 64); - } - - @Test - public void testUTF8FourBytesSymbols() throws Exception { - chunkString(TEST_DATA_4_BYTES, StandardCharsets.UTF_8, 111); - } - - @Test - public void testUTF16LEStandard() throws Exception { - chunkString(TEST_DATA_3_BYTES, StandardCharsets.UTF_16LE, 47); - } - - @Test - public void testUTF16LESurrogates() throws Exception { - // 4 bytes UTF-8 symbols are encoded as two 2 byte surrogate symbols in UTF-16 - chunkString(TEST_DATA_4_BYTES, StandardCharsets.UTF_16LE, 47); - } - - @Test - public void testUTF16BEStandard() throws Exception { - chunkString(TEST_DATA_3_BYTES, StandardCharsets.UTF_16BE, 47); - } - - @Test - public void testUTF16BESurrogates() throws Exception { - // 4 bytes UTF-8 symbols are encoded as two 2 byte surrogate symbols in UTF-16 - chunkString(TEST_DATA_4_BYTES, StandardCharsets.UTF_16BE, 47); - } - - @Test - public void testUTF32() throws Exception { - // UTF-32 data symbols always 4 bytes - chunkString(TEST_DATA_4_BYTES, Charset.forName("UTF-32"), 65); - } - - private void chunkString(String originalString, Charset charset, int chunkSize) { - byte data[] = originalString.getBytes(charset); - - StringBuilder builder = new StringBuilder(); - ProgressiveStringDecoder collector = new ProgressiveStringDecoder(charset); - byte[] buffer = new byte[chunkSize]; - for (int i = 0; i < data.length; i += chunkSize) { - int bytesRead = Math.min(chunkSize, data.length - i); - System.arraycopy(data, i, buffer, 0, bytesRead); - builder.append(collector.decodeNext(buffer, bytesRead)); - } - - String actualString = builder.toString(); - Assert.assertEquals(originalString, actualString); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/ProgressiveStringDecoderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/ProgressiveStringDecoderTest.kt new file mode 100644 index 00000000000000..c58ca1a3ce66c2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/network/ProgressiveStringDecoderTest.kt @@ -0,0 +1,140 @@ +/* + * 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. + */ + +package com.facebook.react.modules.network + +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import kotlin.math.min +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ProgressiveStringDecoderTest { + + private val TEST_DATA_1_BYTE = + """Lorem ipsum dolor sit amet, ea ius viris laoreet gloriatur, ea enim illud mel. Ea eligendi erroribus inciderint sea, id nemore sensibus contentiones qui. Eos et nulla abhorreant, noluisse adipiscing reprehendunt an sit. Harum iriure meliore ne nec, clita semper voluptaria at sea. Ius civibus vituperata reprehendunt ut. + + Sed nisl postea maiorum ex, mea eros verterem ea. Ne usu brute debitis appareat. Ad quem reprimique dissentias duo. Sit an labitur eleifend, illud zril audiam nam ex, epicuri luptatum ne usu. Lorem mundi utinam vix ea. + + Te eam nominati qualisque. Ut praesent consetetur pro. Soleat vivendum vim ea. Altera dolores eam in. Eum at praesent complectitur. Nec ea inani definitiones, tantas vivendum mei an, mea an ubique omnium latine. Has mundi ocurreret ei, nam ea iuvaret gloriatur. + + Ad omnes malorum vim, no latine facilisi mel, dicant salutandi conclusionemque ei est. Nam cu partem alterum minimum. Et quo iriure deleniti accommodare, ad impetus perfecto liberavisse pri. Instructior necessitatibus ut mel, ex cum sumo atqui comprehensam, ei nullam oporteat sed. Ius meliore placerat cu. + + Eum in ferri nobis, eam eu verear facilisis referrentur. Veniam epicuri referrentur at nam. Vel congue diceret fabulas te, ei fabellas temporibus mei. Nemore corrumpit quo ex, et vis soluta reprehendunt. Et eos eripuit atomorum. + + Eum no novum tantas decore. Indoctum definiebas intellegam ut vel. Cu per ipsum graeco, in nam dico dolore, usu id ludus consulatu. Vis an clita commune, cu quot quaeque cum. In eos semper aperiri. Ne mea probo inermis, no vis audiam volutpat. + + Cu quaeque scaevola vis. Civibus commune scriptorem vim an, vim ea vocent petentium consequuntur, meis propriae invidunt eam ex. Pro et ponderum recusabo sapientem. Vel legere possim ornatus ne, saepe commodo scaevola an quo. An scaevola repudiandae sed. Eam ei veri nemore. + + Ullum deleniti cum at. An has soleat docendi, epicuri erroribus inciderint pro ea. Noluisse invidunt splendide quo in, eam odio invenire ea. Eu hinc definiebas scripserit duo, has cu equidem ponderum expetenda, eum vulputate intellegat id. Pri eu natum semper pertinax, ei vel inani aliquip habemus, sit an facer dicam. Et graeci abhorreant contentiones duo, et summo partiendo conclusionemque per. + + Sed ei etiam iudico abhorreant. Pri an regione fastidii, clita discere eu nec. Torquatos percipitur inciderint eos in, id per prompta blandit. Sit et epicuri deleniti. Per labores corpora no. + + Quodsi melius facilis pri ei, has adhuc recusabo reprimique ut. Laoreet definitionem cum cu, amet nonumes ut vis, qui ut sonet ancillae. Vim no doctus efficiantur, ancillae indoctum ex sea, vel eu fabulas volumus argumentum. Ex eum aeque commune placerat, nam choro tamquam luptatum et. Ne sea vero idque liberavisse""" + + private val TEST_DATA_2_BYTES = + """Лорем ипсум долор сит амет, доминг дисцере ад вих, велит игнота ратионибус мел цу. Не вирис малорум яуаеяуе хас, еу либрис доцтус хис. Моллис садипсцинг ан цум, семпер молестие репрехендунт усу те. Цасе аетерно оффендит ан еос. При ан толлит опортере оцурререт, ан яуот мутат трацтатос вих. + + Нец фалли харум ратионибус еа. Магна адмодум ат нам, яуи еа рецусабо мандамус, аццусам цонсеяуунтур цу хис. Импедит цотидиеяуе улламцорпер еа мел, усу ет долорес аргументум. Веро торяуатос ех нам, цибо либерависсе ест еи. Вис долор омниум сплендиде ад, велит рецусабо цонсететур иус цу. + + Еи дуо меис атоморум сигниферумяуе, аугуе аццусам мел ет. Ут ностро легендос хонестатис пер, ут яуас мовет сеа. Меа цу продессет аппеллантур. Вис еа яуод оффендит, дебет видерер ет нам. + + Еам еа дебитис иудицабит, не хас иллуд цивибус. Усу ет алии уллум утамур. Поссит цонституто те яуи, хас ет лаудем аудире, нам еи епицури салутанди. Лудус делицатиссими цум еу, либер адиписцинг еи нец. Ид ерипуит лобортис антиопам хис, санцтус елигенди неглегентур сед ут, вел сентентиае инструцтиор еи. Ан про унум яуалисяуе. + + Ат еррор алтера сит, пер еу яуот номинави. Пертинах репудиаре цум еу. Еа фуиссет антиопам вим, пробатус реферрентур ут иус. Еум ад модус утрояуе диспутандо. + + Ехерци бландит ут меа. Солет импедит сед ад. Дуо порро тимеам аудире не, алии ерант номинави цу нец, сит ферри веритус адиписци те. Те меи синт адверсариум, ад феугаит инвидунт луцилиус сед, дицунт нумяуам нам те. Еум дицант елеифенд цонсецтетуер ет, суммо вереар епицуреи не про. Не лудус сцрипта опортере вим, еи дуо идяуе алияуам сигниферумяуе. Цум еу лабитур инвенире, про ессе губергрен темпорибус еи, ад хис минимум пертинах. + + Дуо ад вери евертитур интеллегат, демоцритум еффициенди дуо ет. Нец но доценди демоцритум сцрипторем, витуперата цонституам нецесситатибус ут вим. Яуи виде санцтус мандамус ан, нонумес принципес вел ат, ех дуо инани нулла. Петентиум маиестатис еам ин, те ерант дебитис еурипидис вис. Но вел антиопам цотидиеяуе еффициантур, сеа еи нибх нонумы инцидеринт. + + Одио омнес но яуо, популо ноструд иус ад. Инани хонестатис но вис. Хис еу лудус партем персиус, пурто малис витуперата при ан, еи елаборарет ассуеверит вим. Цу бруте утинам тинцидунт вих, цум ад дицтас лобортис лаборамус. Нец хабемус рецусабо ат, ех фацилис денияуе ест. При те велит алияуам аццусамус, юсто утамур антиопам но нам. + + Про не еррем иудицо мелиоре, еи цибо ерудити санцтус хас. Яуод еяуидем еу вис, вих яуидам легимус ад, ид сеа солум легере мандамус. Аеяуе детрахит ех иус, суас вертерем еум цу. Еи вим алиа ехерци пхаедрум, хас не лаборес цоррумпит. Ат граеци сцрипта вим. + + Иус ат менандри персеяуерис. Про модус дицта еу, ин граеци доценди фиерент при, еи хас аугуе мандамус дефинитионем. Ет путент интерпретарис сит, перицула сентентиае ат ест. При ут сумо видит волуптатибус, нобис деленити еа.""" + + private val TEST_DATA_3_BYTES = + """案のづよド捕毎エオ文疑ろめた今宮レ秋像とが供持属ょー真場中ホサヒ不箱らご著質ーぼンろ保6年読さ系蔵べるル緩参フシセタ鮮県フずッ歳民ナセ楽飲匹恒桜ぱ。要電ネソメ嘉負向ス援中ぜく界党フネ属平ぎ象越容レ書95争効99争効7翌テ売約わこよッ紙点発事9入そさ補綱のラず他亭匠ぞ。 + + 天レ供内ソ愛7読でぽせ回書ほごしな浅月企設潟せぐり裂個ホヌヤ局題制エ柏央ざぽ。外くにさ下格か終所あ硬当ワ着少選とけリへ康件終にぎ季規らおず給測トユテ考毎サトス事版にーご文8忙チ深暮タヲムラ度6応しぞぎぐ装速て続際ぞ発准揮包孤てい。制はたちき合南む乙甲ゅさと捕4球任条こでン頭広セスモウ月夜エス面陽ヨネ力京ウリ紙聞ト印2火映ラ基頭スフ点愛伎協ねド。 + + 属と共代みむもず以監すい者新ス田政家ヱス使校音刑トホ則上ゅぐ一未ヌ意40芸標んは学必強ゅ帝歯没牧具もか。58新イシレ正米ニユ負皇っぐせの必容キソタコ公3容ーつぶべ年然検ざ整賞ニチ注興ぐ放約えあ野夜磨やゃフよ。柳ソシアテ申1科ル舗紀深むぜ競供とび室全ハネ測高エラク権暮ヲクオト館暮ヌ黒杯クリぴぽ火竹ねる種4帰替やあい北問クルゃン登壌粉つどべ。""" + + private val TEST_DATA_4_BYTES = + """\uD800\uDE55\uD800\uDE55\uD800\uDE55 \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55 + \uD800\uDE55\uD800\uDE55\uD800\uDE55 \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80 + \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80 + \uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE55\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80\uD800\uDE80 + \uD800\uDE80\uD800\uDE80\uD800\uDE80""" + + @Test + fun testUTF8SingleByteSymbols() { + chunkString(TEST_DATA_1_BYTE, StandardCharsets.UTF_8, 64) + } + + @Test + fun testUTF8twoBytesSymbols() { + chunkString(TEST_DATA_2_BYTES, StandardCharsets.UTF_8, 63) + } + + @Test + fun testUTF8ThreeBytesSymbols() { + chunkString(TEST_DATA_3_BYTES, StandardCharsets.UTF_8, 64) + } + + @Test + fun testUTF8FourBytesSymbols() { + chunkString(TEST_DATA_4_BYTES, StandardCharsets.UTF_8, 111) + } + + @Test + fun testUTF16LEStandard() { + chunkString(TEST_DATA_3_BYTES, StandardCharsets.UTF_16LE, 47) + } + + @Test + fun testUTF16LESurrogates() { + // 4 bytes UTF-8 symbols are encoded as two 2 byte surrogate symbols in UTF-16 + chunkString(TEST_DATA_4_BYTES, StandardCharsets.UTF_16LE, 47) + } + + @Test + fun testUTF16BEStandard() { + chunkString(TEST_DATA_3_BYTES, StandardCharsets.UTF_16BE, 47) + } + + @Test + fun testUTF16BESurrogates() { + // 4 bytes UTF-8 symbols are encoded as two 2 byte surrogate symbols in UTF-16 + chunkString(TEST_DATA_4_BYTES, StandardCharsets.UTF_16BE, 47) + } + + @Test + fun testUTF32() { + // UTF-32 data symbols always 4 bytes + chunkString(TEST_DATA_4_BYTES, Charset.forName("UTF-32"), 65) + } + + private fun chunkString(originalString: String, charset: Charset, chunkSize: Int) { + val data = originalString.toByteArray(charset) + val builder = StringBuilder() + val collector = ProgressiveStringDecoder(charset) + val buffer = ByteArray(chunkSize) + var i = 0 + while (i < data.size) { + val bytesRead = min(chunkSize, data.size - i) + System.arraycopy(data, i, buffer, 0, bytesRead) + builder.append(collector.decodeNext(buffer, bytesRead)) + i += chunkSize + } + val actualString = builder.toString() + Assert.assertEquals(originalString, actualString) + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java deleted file mode 100644 index 613fb81773b0a1..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java +++ /dev/null @@ -1,307 +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. - */ - -package com.facebook.react.modules.timing; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.CatalystInstance; -import com.facebook.react.bridge.JavaOnlyArray; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.common.SystemClock; -import com.facebook.react.devsupport.interfaces.DevSupportManager; -import com.facebook.react.modules.core.ChoreographerCompat; -import com.facebook.react.modules.core.JSTimers; -import com.facebook.react.modules.core.ReactChoreographer; -import com.facebook.react.modules.core.TimingModule; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - -/** Tests for {@link TimingModule}. */ -// DISABLED, BROKEN https://circleci.com/gh/facebook/react-native/12068 -@PrepareForTest({Arguments.class, SystemClock.class, ReactChoreographer.class}) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -@RunWith(RobolectricTestRunner.class) -@Ignore // TODO T13905097 -public class TimingModuleTest { - - private static final long FRAME_TIME_NS = 17 * 1000 * 1000; // 17 ms - - private TimingModule mTimingModule; - private ReactChoreographer mReactChoreographerMock; - private PostFrameCallbackHandler mPostFrameCallbackHandler; - private PostFrameIdleCallbackHandler mIdlePostFrameCallbackHandler; - private long mCurrentTimeNs; - private JSTimers mJSTimersMock; - - @Rule public PowerMockRule rule = new PowerMockRule(); - - @Before - public void prepareModules() { - PowerMockito.mockStatic(Arguments.class); - when(Arguments.createArray()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - return new JavaOnlyArray(); - } - }); - - PowerMockito.mockStatic(SystemClock.class); - when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000); - when(SystemClock.currentTimeMillis()).thenReturn(mCurrentTimeNs / 1000000); - when(SystemClock.nanoTime()).thenReturn(mCurrentTimeNs); - - mReactChoreographerMock = mock(ReactChoreographer.class); - PowerMockito.mockStatic(ReactChoreographer.class); - when(ReactChoreographer.getInstance()).thenReturn(mReactChoreographerMock); - - CatalystInstance reactInstance = mock(CatalystInstance.class); - ReactApplicationContext reactContext = mock(ReactApplicationContext.class); - when(reactContext.getCatalystInstance()).thenReturn(reactInstance); - when(reactContext.hasActiveReactInstance()).thenReturn(true); - - mCurrentTimeNs = 0; - mPostFrameCallbackHandler = new PostFrameCallbackHandler(); - mIdlePostFrameCallbackHandler = new PostFrameIdleCallbackHandler(); - - doAnswer(mPostFrameCallbackHandler) - .when(mReactChoreographerMock) - .postFrameCallback( - eq(ReactChoreographer.CallbackType.TIMERS_EVENTS), - any(ChoreographerCompat.FrameCallback.class)); - - doAnswer(mIdlePostFrameCallbackHandler) - .when(mReactChoreographerMock) - .postFrameCallback( - eq(ReactChoreographer.CallbackType.IDLE_EVENT), - any(ChoreographerCompat.FrameCallback.class)); - - mTimingModule = new TimingModule(reactContext, mock(DevSupportManager.class)); - mJSTimersMock = mock(JSTimers.class); - when(reactContext.getJSModule(JSTimers.class)).thenReturn(mJSTimersMock); - - doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - ((Runnable) invocation.getArguments()[0]).run(); - return null; - } - }) - .when(reactContext) - .runOnJSQueueThread(any(Runnable.class)); - - mTimingModule.initialize(); - } - - private void stepChoreographerFrame() { - ChoreographerCompat.FrameCallback callback = - mPostFrameCallbackHandler.getAndResetFrameCallback(); - ChoreographerCompat.FrameCallback idleCallback = - mIdlePostFrameCallbackHandler.getAndResetFrameCallback(); - - mCurrentTimeNs += FRAME_TIME_NS; - when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000); - if (callback != null) { - callback.doFrame(mCurrentTimeNs); - } - - if (idleCallback != null) { - idleCallback.doFrame(mCurrentTimeNs); - } - } - - @Test - public void testSimpleTimer() { - mTimingModule.onHostResume(); - mTimingModule.createTimer(1, 1, 0, false); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1.0)); - reset(mJSTimersMock); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testSimpleRecurringTimer() { - mTimingModule.createTimer(100, 1, 0, true); - mTimingModule.onHostResume(); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); - - reset(mJSTimersMock); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); - } - - @Test - public void testCancelRecurringTimer() { - mTimingModule.onHostResume(); - mTimingModule.createTimer(105, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105.0)); - - reset(mJSTimersMock); - mTimingModule.deleteTimer(105); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testPausingAndResuming() { - mTimingModule.onHostResume(); - mTimingModule.createTimer(41, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostPause(); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - - reset(mJSTimersMock); - mTimingModule.onHostResume(); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - } - - @Test - public void testHeadlessJsTaskInBackground() { - mTimingModule.onHostPause(); - mTimingModule.onHeadlessJsTaskStart(42); - mTimingModule.createTimer(41, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHeadlessJsTaskFinish(42); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testHeadlessJsTaskInForeground() { - mTimingModule.onHostResume(); - mTimingModule.onHeadlessJsTaskStart(42); - mTimingModule.createTimer(41, 1, 0, true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHeadlessJsTaskFinish(42); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostPause(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testHeadlessJsTaskIntertwine() { - mTimingModule.onHostResume(); - mTimingModule.onHeadlessJsTaskStart(42); - mTimingModule.createTimer(41, 1, 0, true); - mTimingModule.onHostPause(); - - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostResume(); - mTimingModule.onHeadlessJsTaskFinish(42); - stepChoreographerFrame(); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41.0)); - - reset(mJSTimersMock); - mTimingModule.onHostPause(); - stepChoreographerFrame(); - verifyNoMoreInteractions(mJSTimersMock); - } - - @Test - public void testSetTimeoutZero() { - mTimingModule.createTimer(100, 0, 0, false); - verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100.0)); - } - - @Test - public void testIdleCallback() { - mTimingModule.onHostResume(); - mTimingModule.setSendIdleEvents(true); - - stepChoreographerFrame(); - verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis()); - } - - @Test - public void testActiveTimersInRange() { - mTimingModule.onHostResume(); - assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); - - mTimingModule.createTimer(41, 1, 0, true); - assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); // Repeating - - mTimingModule.createTimer(42, 150, 0, false); - assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); // Out of range - assertThat(mTimingModule.hasActiveTimersInRange(200)).isTrue(); // In range - } - - private static class PostFrameIdleCallbackHandler implements Answer { - - private ChoreographerCompat.FrameCallback mFrameCallback; - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - mFrameCallback = (ChoreographerCompat.FrameCallback) args[1]; - return null; - } - - public ChoreographerCompat.FrameCallback getAndResetFrameCallback() { - ChoreographerCompat.FrameCallback callback = mFrameCallback; - mFrameCallback = null; - return callback; - } - } - - private static class PostFrameCallbackHandler implements Answer { - - private ChoreographerCompat.FrameCallback mFrameCallback; - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - mFrameCallback = (ChoreographerCompat.FrameCallback) args[1]; - return null; - } - - public ChoreographerCompat.FrameCallback getAndResetFrameCallback() { - ChoreographerCompat.FrameCallback callback = mFrameCallback; - mFrameCallback = null; - return callback; - } - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt new file mode 100644 index 00000000000000..86f295da5786b4 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.kt @@ -0,0 +1,261 @@ +/* + * 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. + */ + +package com.facebook.react.modules.timing + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.CatalystInstance +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.common.SystemClock +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.modules.core.ChoreographerCompat.FrameCallback +import com.facebook.react.modules.core.JSTimers +import com.facebook.react.modules.core.ReactChoreographer +import com.facebook.react.modules.core.ReactChoreographer.CallbackType +import com.facebook.react.modules.core.TimingModule +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` as whenever +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.rule.PowerMockRule +import org.robolectric.RobolectricTestRunner + +@PrepareForTest(Arguments::class, SystemClock::class, ReactChoreographer::class) +@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*") +@RunWith(RobolectricTestRunner::class) +class TimingModuleTest { + companion object { + const val FRAME_TIME_NS = 17 * 1000 * 1000 + } + + private lateinit var timingModule: TimingModule + private lateinit var reactChoreographerMock: ReactChoreographer + private lateinit var postFrameCallbackHandler: PostFrameCallbackHandler + private lateinit var idlePostFrameCallbackHandler: PostFrameCallbackHandler + private var currentTimeNs = 0L + private lateinit var jSTimersMock: JSTimers + + @get:Rule val powerMockRule = PowerMockRule() + + @Before + fun prepareModules() { + PowerMockito.mockStatic(Arguments::class.java) + whenever(Arguments.createArray()).thenAnswer { + return@thenAnswer JavaOnlyArray() + } + + PowerMockito.mockStatic(SystemClock::class.java) + whenever(SystemClock.uptimeMillis()).thenAnswer { + return@thenAnswer currentTimeNs / 1000000 + } + whenever(SystemClock.currentTimeMillis()).thenAnswer { + return@thenAnswer currentTimeNs / 1000000 + } + whenever(SystemClock.nanoTime()).thenAnswer { + return@thenAnswer currentTimeNs + } + + reactChoreographerMock = mock(ReactChoreographer::class.java) + PowerMockito.mockStatic(ReactChoreographer::class.java) + whenever(ReactChoreographer.getInstance()).thenAnswer { reactChoreographerMock } + + val reactInstance = mock(CatalystInstance::class.java) + val reactContext = mock(ReactApplicationContext::class.java) + whenever(reactContext.catalystInstance).thenReturn(reactInstance) + whenever(reactContext.hasActiveReactInstance()).thenReturn(true) + + postFrameCallbackHandler = PostFrameCallbackHandler() + idlePostFrameCallbackHandler = PostFrameCallbackHandler() + + whenever( + reactChoreographerMock.postFrameCallback( + eq(CallbackType.TIMERS_EVENTS), any(FrameCallback::class.java))) + .thenAnswer { + return@thenAnswer postFrameCallbackHandler.answer(it) + } + + whenever( + reactChoreographerMock.postFrameCallback( + eq(CallbackType.IDLE_EVENT), any(FrameCallback::class.java))) + .thenAnswer { + return@thenAnswer idlePostFrameCallbackHandler.answer(it) + } + + timingModule = TimingModule(reactContext, mock(DevSupportManager::class.java)) + jSTimersMock = mock(JSTimers::class.java) + whenever(reactContext.getJSModule(JSTimers::class.java)).thenReturn(jSTimersMock) + whenever(reactContext.runOnJSQueueThread(any(Runnable::class.java))).thenAnswer { invocation -> + (invocation.arguments[0] as Runnable).run() + return@thenAnswer true + } + + timingModule.initialize() + } + + private fun stepChoreographerFrame() { + val callback = postFrameCallbackHandler.getAndResetFrameCallback() + val idleCallback = idlePostFrameCallbackHandler.getAndResetFrameCallback() + currentTimeNs += FRAME_TIME_NS + whenever(SystemClock.uptimeMillis()).thenAnswer { + return@thenAnswer currentTimeNs / 1000000 + } + callback?.doFrame(currentTimeNs) + idleCallback?.doFrame(currentTimeNs) + } + + @Test + fun testSimpleTimer() { + timingModule.onHostResume() + timingModule.createTimer(1.0, 1.0, 0.0, false) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(1.0)) + reset(jSTimersMock) + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testSimpleRecurringTimer() { + timingModule.createTimer(100.0, 1.0, 0.0, true) + timingModule.onHostResume() + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0)) + reset(jSTimersMock) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0)) + } + + @Test + fun testCancelRecurringTimer() { + timingModule.onHostResume() + timingModule.createTimer(105.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(105.0)) + reset(jSTimersMock) + timingModule.deleteTimer(105.0) + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testPausingAndResuming() { + timingModule.onHostResume() + timingModule.createTimer(41.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostPause() + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + reset(jSTimersMock) + timingModule.onHostResume() + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + } + + @Test + fun testHeadlessJsTaskInBackground() { + timingModule.onHostPause() + timingModule.onHeadlessJsTaskStart(42) + timingModule.createTimer(41.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHeadlessJsTaskFinish(42) + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testHeadlessJsTaskInForeground() { + timingModule.onHostResume() + timingModule.onHeadlessJsTaskStart(42) + timingModule.createTimer(41.0, 1.0, 0.0, true) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHeadlessJsTaskFinish(42) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostPause() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testHeadlessJsTaskIntertwine() { + timingModule.onHostResume() + timingModule.onHeadlessJsTaskStart(42) + timingModule.createTimer(41.0, 1.0, 0.0, true) + timingModule.onHostPause() + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostResume() + timingModule.onHeadlessJsTaskFinish(42) + stepChoreographerFrame() + verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0)) + reset(jSTimersMock) + timingModule.onHostPause() + stepChoreographerFrame() + verifyNoMoreInteractions(jSTimersMock) + } + + @Test + fun testSetTimeoutZero() { + timingModule.createTimer(100.0, 0.0, 0.0, false) + verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0)) + } + + @Test + fun testActiveTimersInRange() { + timingModule.onHostResume() + assertThat(timingModule.hasActiveTimersInRange(100)).isFalse + timingModule.createTimer(41.0, 1.0, 0.0, true) + assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Repeating + timingModule.createTimer(42.0, 150.0, 0.0, false) + assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Out of range + assertThat(timingModule.hasActiveTimersInRange(200)).isTrue // In range + } + + @Test + fun testIdleCallback() { + timingModule.setSendIdleEvents(true) + timingModule.onHostResume() + stepChoreographerFrame() + verify(jSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis().toDouble()) + } + + private class PostFrameCallbackHandler : Answer { + + private var frameCallback: FrameCallback? = null + + override fun answer(invocation: InvocationOnMock) { + invocation.arguments[1]?.let { frameCallback = it as FrameCallback } + } + + fun getAndResetFrameCallback(): FrameCallback? { + val callback = frameCallback + frameCallback = null + return callback + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/JSPackagerClientTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/JSPackagerClientTest.kt index bb329c89c42f16..4f70c85137cadd 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/JSPackagerClientTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/packagerconnection/JSPackagerClientTest.kt @@ -10,7 +10,6 @@ package com.facebook.react.packagerconnection import com.facebook.react.packagerconnection.ReconnectingWebSocket.ConnectionCallback import java.io.IOException import okio.ByteString -import okio.ByteString.encodeUtf8 import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -147,4 +146,7 @@ class JSPackagerClientTest { action: String, handler: RequestHandler ): Map = mapOf(action to handler) + + private fun encodeUtf8(input: String): ByteString = + ByteString.of(*input.toByteArray(Charsets.UTF_8)) } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelperTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelperTest.java deleted file mode 100644 index 78ba487e519c91..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelperTest.java +++ /dev/null @@ -1,89 +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. - */ - -package com.facebook.react.uimanager; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import com.facebook.react.common.MapBuilder; -import java.util.Map; -import org.junit.Test; - -public class UIManagerModuleConstantsHelperTest { - - @Test - public void normalizeEventTypes_withNull_doesNothing() { - UIManagerModuleConstantsHelper.normalizeEventTypes(null); - } - - @Test - public void normalizeEventTypes_withEmptyMap_doesNothing() { - Map emptyMap = MapBuilder.builder().build(); - - UIManagerModuleConstantsHelper.normalizeEventTypes(emptyMap); - - assertTrue(emptyMap.isEmpty()); - } - - @Test - public void normalizeEventTypes_withOnEvent_doesNormalize() { - Map onClickMap = MapBuilder.builder().put("onClick", "¯\\_(ツ)_/¯").build(); - - UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap); - - assertTrue(onClickMap.containsKey("topOnClick")); - assertTrue(onClickMap.containsKey("onClick")); - } - - @Test - public void normalizeEventTypes_withTopEvent_doesNormalize() { - Map onClickMap = MapBuilder.builder().put("topOnClick", "¯\\_(ツ)_/¯").build(); - - UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap); - - assertTrue(onClickMap.containsKey("topOnClick")); - assertFalse(onClickMap.containsKey("onClick")); - } - - @SuppressWarnings("unchecked") - @Test - public void normalizeEventTypes_withNestedObjects_doesNotLoseThem() { - Map nestedObjects = - MapBuilder.builder() - .put( - "onColorChanged", - MapBuilder.of( - "phasedRegistrationNames", - MapBuilder.of( - "bubbled", "onColorChanged", "captured", "onColorChangedCapture"))) - .build(); - - UIManagerModuleConstantsHelper.normalizeEventTypes(nestedObjects); - - assertTrue(nestedObjects.containsKey("topOnColorChanged")); - Map innerMap = (Map) nestedObjects.get("topOnColorChanged"); - assertNotNull(innerMap); - assertTrue(innerMap.containsKey("phasedRegistrationNames")); - Map innerInnerMap = - (Map) innerMap.get("phasedRegistrationNames"); - assertNotNull(innerInnerMap); - assertEquals("onColorChanged", innerInnerMap.get("bubbled")); - assertEquals("onColorChangedCapture", innerInnerMap.get("captured")); - - assertTrue(nestedObjects.containsKey("onColorChanged")); - innerMap = (Map) nestedObjects.get("topOnColorChanged"); - assertNotNull(innerMap); - assertTrue(innerMap.containsKey("phasedRegistrationNames")); - innerInnerMap = (Map) innerMap.get("phasedRegistrationNames"); - assertNotNull(innerInnerMap); - assertEquals("onColorChanged", innerInnerMap.get("bubbled")); - assertEquals("onColorChangedCapture", innerInnerMap.get("captured")); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelperTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelperTest.kt new file mode 100644 index 00000000000000..c4e004242858cc --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelperTest.kt @@ -0,0 +1,78 @@ +/* + * 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. + */ + +package com.facebook.react.uimanager + +import com.facebook.react.common.MapBuilder +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test + +class UIManagerModuleConstantsHelperTest { + @Test + fun normalizeEventTypes_withNull_doesNothing() { + UIManagerModuleConstantsHelper.normalizeEventTypes(null) + } + + @Test + fun normalizeEventTypes_withEmptyMap_doesNothing() { + val emptyMap: Map = MapBuilder.builder().build() + UIManagerModuleConstantsHelper.normalizeEventTypes(emptyMap) + assertTrue(emptyMap.isEmpty()) + } + + @Test + fun normalizeEventTypes_withOnEvent_doesNormalize() { + val onClickMap: Map = + MapBuilder.builder().put("onClick", "¯\\_(ツ)_/¯").build() + UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap) + assertTrue(onClickMap.containsKey("topOnClick")) + assertTrue(onClickMap.containsKey("onClick")) + } + + @Test + fun normalizeEventTypes_withTopEvent_doesNormalize() { + val onClickMap: Map = + MapBuilder.builder().put("topOnClick", "¯\\_(ツ)_/¯").build() + UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap) + assertTrue(onClickMap.containsKey("topOnClick")) + assertFalse(onClickMap.containsKey("onClick")) + } + + @Suppress("UNCHECKED_CAST") + @Test + fun normalizeEventTypes_withNestedObjects_doesNotLoseThem() { + val nestedObjects: Map = + MapBuilder.builder() + .put( + "onColorChanged", + MapBuilder.of( + "phasedRegistrationNames", + MapBuilder.of( + "bubbled", "onColorChanged", "captured", "onColorChangedCapture"))) + .build() + UIManagerModuleConstantsHelper.normalizeEventTypes(nestedObjects) + assertTrue(nestedObjects.containsKey("topOnColorChanged")) + var innerMap = nestedObjects["topOnColorChanged"] as? Map + assertNotNull(innerMap) + assertTrue(innerMap!!.containsKey("phasedRegistrationNames")) + var innerInnerMap = innerMap.get("phasedRegistrationNames") as? Map + assertNotNull(innerInnerMap) + assertEquals("onColorChanged", innerInnerMap!!.get("bubbled")) + assertEquals("onColorChangedCapture", innerInnerMap.get("captured")) + assertTrue(nestedObjects.containsKey("onColorChanged")) + innerMap = nestedObjects.get("topOnColorChanged") as? Map + assertNotNull(innerMap) + assertTrue(innerMap!!.containsKey("phasedRegistrationNames")) + innerInnerMap = innerMap.get("phasedRegistrationNames") as? Map + assertNotNull(innerInnerMap) + assertEquals("onColorChanged", innerInnerMap!!.get("bubbled")) + assertEquals("onColorChangedCapture", innerInnerMap.get("captured")) + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/util/JSStackTraceTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/util/JSStackTraceTest.kt similarity index 71% rename from packages/react-native/ReactAndroid/src/test/java/com/facebook/react/util/JSStackTraceTest.java rename to packages/react-native/ReactAndroid/src/test/java/com/facebook/react/util/JSStackTraceTest.kt index d2657ad80d4100..95e6f330303d54 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/util/JSStackTraceTest.java +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/util/JSStackTraceTest.kt @@ -5,18 +5,18 @@ * LICENSE file in the root directory of this source tree. */ -package com.facebook.react.util; +package com.facebook.react.util -import com.facebook.react.bridge.JavaOnlyArray; -import com.facebook.react.bridge.JavaOnlyMap; -import org.junit.Assert; -import org.junit.Test; +import com.facebook.react.bridge.JavaOnlyArray +import com.facebook.react.bridge.JavaOnlyMap +import org.junit.Assert +import org.junit.Test -public class JSStackTraceTest { +class JSStackTraceTest { @Test - public void testSymbolication() { - JavaOnlyArray values = + fun testSymbolication() { + val values = JavaOnlyArray.of( JavaOnlyMap.of( "methodName", @@ -80,17 +80,21 @@ public void testSymbolication() { "lineNumber", 10, "file", - "address at seg-3_198.js")); - String message = JSStackTrace.format("Error", values); + "address at seg-3_198.js")) + val message = JSStackTrace.format("Error", values) Assert.assertEquals( message, - "Error, stack:\n" - + "method_from_bundle@7:11\n" - + "method_from_ram_bundle@199.js:18:13\n" - + "method_from_ram_bundle_with_address@199.js:18:13\n" - + "method_from_segment@seg-1.js:9:18\n" - + "method_from_segment_with_address@seg-1.js:9:18\n" - + "method_from_ram_segment@seg-3_198.js:10:20\n" - + "method_from_ram_segment_with_address@seg-3_198.js:10:20\n"); + """ + Error, stack: + method_from_bundle@7:11 + method_from_ram_bundle@199.js:18:13 + method_from_ram_bundle_with_address@199.js:18:13 + method_from_segment@seg-1.js:9:18 + method_from_segment_with_address@seg-1.js:9:18 + method_from_ram_segment@seg-3_198.js:10:20 + method_from_ram_segment_with_address@seg-3_198.js:10:20 + + """ + .trimIndent()) } } diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 96cef2b9e79049..1f5a9b955fd5ea 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -52,7 +52,7 @@ Pod::Spec.new do |s| s.dependency "DoubleConversion" s.dependency "React-Core" s.dependency "React-debug" - # s.dependency "React-utils" + s.dependency "React-utils" # s.dependency "React-runtimescheduler" s.dependency "React-cxxreact" @@ -324,10 +324,4 @@ Pod::Spec.new do |s| ss.header_dir = "react/renderer/runtimescheduler" ss.pod_target_xcconfig = { "GCC_WARN_PEDANTIC" => "YES" } end - - s.subspec "utils" do |ss| - ss.source_files = "react/utils/*.{m,mm,cpp,h}" - ss.header_dir = "react/utils" - end - end diff --git a/packages/react-native/ReactCommon/hermes/React-hermes.podspec b/packages/react-native/ReactCommon/hermes/React-hermes.podspec index e89b85829f663c..300d1571ad931a 100644 --- a/packages/react-native/ReactCommon/hermes/React-hermes.podspec +++ b/packages/react-native/ReactCommon/hermes/React-hermes.podspec @@ -5,9 +5,6 @@ require "json" -# Whether Hermes is built for Release or Debug is determined by the PRODUCTION envvar. -build_type = ENV['PRODUCTION'] == "1" ? :release : :debug - # package.json package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json"))) version = package['version'] @@ -42,7 +39,7 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"${PODS_ROOT}/hermes-engine/destroot/include\" \"$(PODS_TARGET_SRCROOT)/..\" \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/libevent/include\"", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" - }.merge!(build_type == :debug ? { "GCC_PREPROCESSOR_DEFINITIONS" => "HERMES_ENABLE_DEBUGGER=1" } : {}) + } s.header_dir = "reacthermes" s.dependency "React-cxxreact", version s.dependency "React-jsiexecutor", version diff --git a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp index fea1d0bc2d33c7..90a11df160a106 100644 --- a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp +++ b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp @@ -398,6 +398,13 @@ JSCRuntime::JSCRuntime(JSGlobalContextRef ctx) stringCounter_(0) #endif { +#ifndef NDEBUG +#ifdef TARGET_OS_MAC + if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) { + JSGlobalContextSetInspectable(ctx_, true); + } +#endif +#endif } JSCRuntime::~JSCRuntime() { diff --git a/packages/react-native/ReactCommon/react/bridgeless/iostests/RCTHostTests.mm b/packages/react-native/ReactCommon/react/bridgeless/iostests/RCTHostTests.mm new file mode 100644 index 00000000000000..606d2d9dabd1f1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridgeless/iostests/RCTHostTests.mm @@ -0,0 +1,158 @@ +/* + * 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. + */ + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +#import + +RCT_MOCK_REF(RCTHost, _RCTLogNativeInternal); + +RCTLogLevel gLogLevel; +int gLogCalledTimes = 0; +NSString *gLogMessage = nil; +static void RCTLogNativeInternalMock(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...) +{ + gLogLevel = level; + gLogCalledTimes++; + + va_list args; + va_start(args, format); + gLogMessage = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); +} + +@interface RCTHostTests : XCTestCase +@end + +@implementation RCTHostTests { + RCTHost *_subject; + id _mockHostDelegate; +} + +static ShimRCTInstance *shimmedRCTInstance; + +- (void)setUp +{ + [super setUp]; + + RCTAutoReleasePoolPush(); + + shimmedRCTInstance = [ShimRCTInstance new]; + + _mockHostDelegate = OCMProtocolMock(@protocol(RCTHostDelegate)); + _subject = [[RCTHost alloc] initWithBundleURL:OCMClassMock([NSURL class]) + hostDelegate:_mockHostDelegate + turboModuleManagerDelegate:OCMProtocolMock(@protocol(RCTTurboModuleManagerDelegate)) + jsEngineProvider:^std::shared_ptr() { + return std::make_shared(); + }]; +} + +- (void)tearDown +{ + RCTAutoReleasePoolPop(); + + _subject = nil; + XCTAssertEqual(RCTGetRetainCount(_subject), 0); + + _mockHostDelegate = nil; + XCTAssertEqual(RCTGetRetainCount(_mockHostDelegate), 0); + + [shimmedRCTInstance reset]; + gLogCalledTimes = 0; + gLogMessage = nil; + + [super tearDown]; +} + +- (void)testStart +{ + RCT_MOCK_SET(RCTHost, _RCTLogNativeInternal, RCTLogNativeInternalMock); + + XCTAssertEqual(shimmedRCTInstance.initCount, 0); + [_subject start]; + OCMVerify(OCMTimes(1), [_mockHostDelegate hostDidStart:_subject]); + XCTAssertEqual(shimmedRCTInstance.initCount, 1); + XCTAssertEqual(gLogCalledTimes, 0); + + XCTAssertEqual(shimmedRCTInstance.invalidateCount, 0); + [_subject start]; + XCTAssertEqual(shimmedRCTInstance.initCount, 2); + XCTAssertEqual(shimmedRCTInstance.invalidateCount, 1); + OCMVerify(OCMTimes(2), [_mockHostDelegate hostDidStart:_subject]); + XCTAssertEqual(gLogLevel, RCTLogLevelWarning); + XCTAssertEqual(gLogCalledTimes, 1); + XCTAssertEqualObjects( + gLogMessage, + @"RCTHost should not be creating a new instance if one already exists. This implies there is a bug with how/when this method is being called."); + + RCT_MOCK_RESET(RCTHost, _RCTLogNativeInternal); +} + +- (void)testCallFunctionOnJSModule +{ + [_subject start]; + + NSArray *args = @[ @"hi", @(5), @(NO) ]; + [_subject callFunctionOnJSModule:@"jsModule" method:@"method" args:args]; + + XCTAssertEqualObjects(shimmedRCTInstance.jsModuleName, @"jsModule"); + XCTAssertEqualObjects(shimmedRCTInstance.method, @"method"); + XCTAssertEqualObjects(shimmedRCTInstance.args, args); +} + +- (void)testDidReceiveErrorStack +{ + id instanceDelegate = (id)_subject; + + NSMutableArray *> *stack = [NSMutableArray array]; + + NSMutableDictionary *stackFrame0 = [NSMutableDictionary dictionary]; + stackFrame0[@"linenumber"] = @(3); + stackFrame0[@"column"] = @(4); + stackFrame0[@"methodname"] = @"method1"; + stackFrame0[@"file"] = @"file1.js"; + [stack addObject:stackFrame0]; + + NSMutableDictionary *stackFrame1 = [NSMutableDictionary dictionary]; + stackFrame0[@"linenumber"] = @(63); + stackFrame0[@"column"] = @(44); + stackFrame0[@"methodname"] = @"method2"; + stackFrame0[@"file"] = @"file2.js"; + [stack addObject:stackFrame1]; + + [instanceDelegate instance:[OCMArg any] didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]; + + OCMVerify( + OCMTimes(1), + [_mockHostDelegate host:_subject didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]); +} + +- (void)testDidInitializeRuntime +{ + id mockRuntimeDelegate = OCMProtocolMock(@protocol(RCTHostRuntimeDelegate)); + _subject.runtimeDelegate = mockRuntimeDelegate; + + auto hermesRuntime = facebook::hermes::makeHermesRuntime(); + facebook::jsi::Runtime *rt = hermesRuntime.get(); + + id instanceDelegate = (id)_subject; + [instanceDelegate instance:[OCMArg any] didInitializeRuntime:*rt]; + + OCMVerify(OCMTimes(1), [mockRuntimeDelegate host:_subject didInitializeRuntime:*rt]); +} + +@end diff --git a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTHost.mm b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTHost.mm index d7d8c274af17a5..fc91d96e5420cb 100644 --- a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTHost.mm @@ -15,9 +15,13 @@ #import #import #import +#import #import #import +RCT_MOCK_DEF(RCTHost, _RCTLogNativeInternal); +#define _RCTLogNativeInternal RCT_MOCK_USE(RCTHost, _RCTLogNativeInternal) + using namespace facebook::react; @interface RCTHost () @@ -230,28 +234,13 @@ - (void)dealloc #pragma mark - RCTInstanceDelegate -- (void)instance:(RCTInstance *)instance didReceiveErrorMap:(facebook::react::MapBuffer)errorMap +- (void)instance:(RCTInstance *)instance + didReceiveJSErrorStack:(NSArray *> *)stack + message:(NSString *)message + exceptionId:(NSUInteger)exceptionId + isFatal:(BOOL)isFatal { - NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str() - encoding:[NSString defaultCStringEncoding]]; - std::vector frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); - NSMutableArray *> *stack = [NSMutableArray new]; - for (facebook::react::MapBuffer const &mapBuffer : frames) { - NSDictionary *frame = @{ - @"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str() - encoding:[NSString defaultCStringEncoding]], - @"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str() - encoding:[NSString defaultCStringEncoding]], - @"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)], - @"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)], - }; - [stack addObject:frame]; - } - [_hostDelegate host:self - didReceiveJSErrorStack:stack - message:message - exceptionId:errorMap.getInt(JSErrorHandlerKey::kExceptionId) - isFatal:errorMap.getBool(JSErrorHandlerKey::kIsFatal)]; + [_hostDelegate host:self didReceiveJSErrorStack:stack message:message exceptionId:exceptionId isFatal:isFatal]; } - (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime diff --git a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.h b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.h index 80c10803316f76..5445ca8b7c247d 100644 --- a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.h +++ b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.h @@ -38,7 +38,12 @@ FB_RUNTIME_PROTOCOL @protocol RCTInstanceDelegate -- (void)instance:(RCTInstance *)instance didReceiveErrorMap:(facebook::react::MapBuffer)errorMap; +- (void)instance:(RCTInstance *)instance + didReceiveJSErrorStack:(NSArray *> *)stack + message:(NSString *)message + exceptionId:(NSUInteger)exceptionId + isFatal:(BOOL)isFatal; + - (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime; @end diff --git a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm index 5e23e46836c287..2258edae930bdb 100644 --- a/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/bridgeless/platform/ios/Core/RCTInstance.mm @@ -6,11 +6,13 @@ */ #import "RCTInstance.h" +#import #import #import #import +#import #import #import #import @@ -229,6 +231,21 @@ - (void)_start delegate:self jsInvoker:std::make_shared(bufferedRuntimeExecutor)]; + if (RCTTurboModuleInteropEnabled() && RCTTurboModuleInteropBridgeProxyEnabled()) { + RCTBridgeProxy *bridgeProxy = [[RCTBridgeProxy alloc] + initWithViewRegistry:_bridgeModuleDecorator.viewRegistry_DEPRECATED + moduleRegistry:_bridgeModuleDecorator.moduleRegistry + bundleManager:_bridgeModuleDecorator.bundleManager + callableJSModules:_bridgeModuleDecorator.callableJSModules + dispatchToJSThread:^(dispatch_block_t block) { + __strong __typeof(self) strongSelf = weakSelf; + if (strongSelf && strongSelf->_valid) { + strongSelf->_reactInstance->getBufferedRuntimeExecutor()([=](jsi::Runtime &runtime) { block(); }); + } + }]; + [_turboModuleManager setBridgeProxy:bridgeProxy]; + } + // Initialize RCTModuleRegistry so that TurboModules can require other TurboModules. [_bridgeModuleDecorator.moduleRegistry setTurboModuleRegistry:_turboModuleManager]; @@ -413,7 +430,26 @@ - (void)_notifyEventDispatcherObserversOfEvent_DEPRECATED:(NSNotification *)noti - (void)_handleJSErrorMap:(facebook::react::MapBuffer)errorMap { - [_delegate instance:self didReceiveErrorMap:std::move(errorMap)]; + NSString *message = [NSString stringWithCString:errorMap.getString(JSErrorHandlerKey::kErrorMessage).c_str() + encoding:[NSString defaultCStringEncoding]]; + std::vector frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + NSMutableArray *> *stack = [NSMutableArray new]; + for (facebook::react::MapBuffer const &mapBuffer : frames) { + NSDictionary *frame = @{ + @"file" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameFileName).c_str() + encoding:[NSString defaultCStringEncoding]], + @"methodName" : [NSString stringWithCString:mapBuffer.getString(JSErrorHandlerKey::kFrameMethodName).c_str() + encoding:[NSString defaultCStringEncoding]], + @"lineNumber" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameLineNumber)], + @"column" : [NSNumber numberWithInt:mapBuffer.getInt(JSErrorHandlerKey::kFrameColumnNumber)], + }; + [stack addObject:frame]; + } + [_delegate instance:self + didReceiveJSErrorStack:stack + message:message + exceptionId:errorMap.getInt(JSErrorHandlerKey::kExceptionId) + isFatal:errorMap.getBool(JSErrorHandlerKey::kIsFatal)]; } @end diff --git a/packages/react-native/ReactCommon/react/bridgeless/tests/hermes/ReactInstanceTest.cpp b/packages/react-native/ReactCommon/react/bridgeless/tests/cxx/ReactInstanceTest.cpp similarity index 100% rename from packages/react-native/ReactCommon/react/bridgeless/tests/hermes/ReactInstanceTest.cpp rename to packages/react-native/ReactCommon/react/bridgeless/tests/cxx/ReactInstanceTest.cpp diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h index 34535f8c381073..4d0872fbab4a69 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h @@ -9,6 +9,7 @@ #import +#import #import #import #import @@ -65,6 +66,9 @@ RCT_EXTERN void RCTTurboModuleSetBindingMode(facebook::react::TurboModuleBinding */ - (void)installJSBindingWithRuntimeExecutor:(facebook::react::RuntimeExecutor &)runtimeExecutor; +// TODO: Should we move this into the initializer? +- (void)setBridgeProxy:(RCTBridgeProxy *)bridgeProxy; + - (void)invalidate; @end diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm index 9b629d8d72fc44..efb232fa13420e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm @@ -17,6 +17,7 @@ #import #import +#import #import #import #import @@ -198,6 +199,8 @@ @implementation RCTTurboModuleManager { NSDictionary> *_legacyEagerlyInitializedModules; NSDictionary *_legacyEagerlyRegisteredModuleClasses; + + RCTBridgeProxy *_bridgeProxy; } - (instancetype)initWithBridge:(RCTBridge *)bridge @@ -243,6 +246,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge return self; } +- (void)setBridgeProxy:(RCTBridgeProxy *)bridgeProxy +{ + _bridgeProxy = bridgeProxy; +} + - (void)notifyAboutTurboModuleSetup:(const char *)name { NSString *moduleName = [[NSString alloc] initWithUTF8String:name]; @@ -441,6 +449,15 @@ - (BOOL)_isLegacyModule:(const char *)moduleName } Class moduleClass = [self _getModuleClassFromName:moduleName]; + return [self _isLegacyModuleClass:moduleClass]; +} + +- (BOOL)_isLegacyModuleClass:(Class)moduleClass +{ + if (RCTTurboModuleInteropForAllTurboModulesEnabled()) { + return YES; + } + return moduleClass != nil && (!RCT_IS_TURBO_MODULE_CLASS(moduleClass) || [moduleClass isSubclassOfClass:RCTCxxModule.class]); } @@ -617,7 +634,7 @@ - (BOOL)_shouldCreateObjCModule:(Class)moduleClass * this method exists to know if we can safely set the bridge to the * NativeModule. */ - if ([module respondsToSelector:@selector(bridge)] && _bridge) { + if ([module respondsToSelector:@selector(bridge)] && (_bridge || _bridgeProxy)) { /** * Just because a NativeModule has the `bridge` method, it doesn't mean * that it has synthesized the bridge in its implementation. Therefore, @@ -634,7 +651,11 @@ - (BOOL)_shouldCreateObjCModule:(Class)moduleClass * generated, so we have have to rely on the KVC API of ObjC to set * the bridge property of these NativeModules. */ - [(id)module setValue:_bridge forKey:@"bridge"]; + if (_bridge) { + [(id)module setValue:_bridge forKey:@"bridge"]; + } else if (_bridgeProxy && [self _isLegacyModuleClass:[module class]]) { + [(id)module setValue:_bridgeProxy forKey:@"bridge"]; + } } @catch (NSException *exception) { RCTLogError( @"%@ has no setter or ivar for its bridge, which is not " diff --git a/packages/react-native/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp b/packages/react-native/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp index 53fe2d986c43a9..85775d70ad5681 100644 --- a/packages/react-native/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp @@ -56,8 +56,6 @@ static void testShadowNodeTreeLifeCycleLayoutAnimations( ViewComponentDescriptor(componentDescriptorParameters); auto rootComponentDescriptor = RootComponentDescriptor(componentDescriptorParameters); - auto noopEventEmitter = - std::make_shared(nullptr, -1, eventDispatcher); PropsParserContext parserContext{-1, *contextContainer}; @@ -95,7 +93,7 @@ static void testShadowNodeTreeLifeCycleLayoutAnimations( auto surfaceId = SurfaceId(surfaceIdInt); auto family = rootComponentDescriptor.createFamily( - {Tag(surfaceIdInt), surfaceId, nullptr}, nullptr); + {Tag(surfaceIdInt), surfaceId, nullptr}); // Creating an initial root shadow node. auto emptyRootNode = std::const_pointer_cast( diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp index 892464bc9cc122..f6db0dd8a9d7d0 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp @@ -126,12 +126,13 @@ ShadowNode::Shared ComponentDescriptorRegistry::createNode( std::string const &viewName, SurfaceId surfaceId, folly::dynamic const &propsDynamic, - SharedEventTarget const &eventTarget) const { + InstanceHandle::Shared const &instanceHandle) const { auto unifiedComponentName = componentNameByReactViewName(viewName); auto const &componentDescriptor = this->at(unifiedComponentName); - auto const fragment = ShadowNodeFamilyFragment{tag, surfaceId, nullptr}; - auto family = componentDescriptor.createFamily(fragment, eventTarget); + auto const fragment = + ShadowNodeFamilyFragment{tag, surfaceId, instanceHandle}; + auto family = componentDescriptor.createFamily(fragment); auto const props = componentDescriptor.cloneProps( PropsParserContext{surfaceId, *contextContainer_.get()}, nullptr, diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h index 8c5cfe31009169..93f18c1168a1ce 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h @@ -14,6 +14,7 @@ #include #include +#include #include namespace facebook::react { @@ -59,7 +60,7 @@ class ComponentDescriptorRegistry { std::string const &viewName, SurfaceId surfaceId, folly::dynamic const &props, - SharedEventTarget const &eventTarget) const; + InstanceHandle::Shared const &instanceHandle) const; void setFallbackComponentDescriptor( const SharedComponentDescriptor &descriptor); diff --git a/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h index be233e807b1898..55d56df694b3e3 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,7 @@ class ImageShadowNode final : public ConcreteViewShadowNode< static ImageState initialStateData( Props::Shared const &props, - ShadowNodeFamilyFragment const &familyFragment, + ShadowNodeFamily::Shared const & /*family*/, ComponentDescriptor const &componentDescriptor) { auto imageSource = ImageSource{ImageSource::Type::Invalid}; return {imageSource, {imageSource, nullptr}, 0}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp index 7fe5a502133b0b..d47ec1355861e3 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp @@ -50,7 +50,7 @@ void ScrollViewShadowNode::updateScrollContentOffsetIfNeeded() { ScrollViewState ScrollViewShadowNode::initialStateData( Props::Shared const &props, - const ShadowNodeFamilyFragment & /*familyFragment*/, + const ShadowNodeFamily::Shared & /*family*/, const ComponentDescriptor & /*componentDescriptor*/) { return {static_cast(*props).contentOffset, {}, 0}; } diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.h index 3d2447fe3d9035..387a22687198d5 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace facebook::react { @@ -30,7 +31,7 @@ class ScrollViewShadowNode final : public ConcreteViewShadowNode< static ScrollViewState initialStateData( Props::Shared const &props, - ShadowNodeFamilyFragment const &familyFragment, + ShadowNodeFamily::Shared const &family, ComponentDescriptor const &componentDescriptor); #pragma mark - LayoutableShadowNode diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp index c4a5e886c70d66..41720767d228f4 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphLayoutManager.cpp @@ -58,7 +58,7 @@ bool ParagraphLayoutManager::shoudMeasureString( bool hasMaximumSizeChanged = layoutConstraints.maximumSize.width != lastAvailableWidth_; - Float threshold = 0.01; + Float threshold = 0.01f; bool doesMaximumSizeMatchLastMeasurement = std::abs( layoutConstraints.maximumSize.width - diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h index 9fadc3d23ed959..664c0ccf0496ac 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h @@ -222,6 +222,15 @@ class TextInputTraits final { * Default value: `` (no rules). */ std::string passwordRules{}; + + /* + * If `false`, the iOS system will not insert an extra space after a paste + * operation neither delete one or two spaces after a cut or delete operation. + * iOS-only (inherently iOS-specific) + * Can be empty (`null` in JavaScript) which means `default`. + * Default value: `empty` (`null`). + */ + std::optional smartInsertDelete{}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h index f498ed385191fe..885d9e50d11c80 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h @@ -141,6 +141,12 @@ static TextInputTraits convertRawProp( "passwordRules", sourceTraits.passwordRules, defaultTraits.passwordRules); + traits.smartInsertDelete = convertRawProp( + context, + rawProps, + "smartInsertDelete", + sourceTraits.smartInsertDelete, + defaultTraits.smartInsertDelete); return traits; } diff --git a/packages/react-native/ReactCommon/react/renderer/core/ComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/core/ComponentDescriptor.h index c7ced77e41b652..8b3fabe9e2e27d 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ComponentDescriptor.h @@ -8,6 +8,8 @@ #pragma once #include +#include +#include #include #include #include @@ -136,8 +138,13 @@ class ComponentDescriptor { * Creates a shadow node family for particular node. */ virtual ShadowNodeFamily::Shared createFamily( - ShadowNodeFamilyFragment const &fragment, - SharedEventTarget eventTarget) const = 0; + ShadowNodeFamilyFragment const &fragment) const = 0; + + /* + * Creates an event emitter for particular node. + */ + virtual SharedEventEmitter createEventEmitter( + InstanceHandle::Shared const &instanceHandle) const = 0; protected: EventDispatcher::Weak eventDispatcher_; diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index 6a063b6265016d..3315e745ab82d1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -160,8 +160,7 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { return std::make_shared( std::make_shared( - ConcreteShadowNode::initialStateData( - props, ShadowNodeFamilyFragment::build(*family), *this)), + ConcreteShadowNode::initialStateData(props, family, *this)), family); } @@ -181,17 +180,20 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { } ShadowNodeFamily::Shared createFamily( - ShadowNodeFamilyFragment const &fragment, - SharedEventTarget eventTarget) const override { - auto eventEmitter = std::make_shared( - std::move(eventTarget), fragment.tag, eventDispatcher_); + ShadowNodeFamilyFragment const &fragment) const override { return std::make_shared( ShadowNodeFamilyFragment{ - fragment.tag, fragment.surfaceId, eventEmitter}, + fragment.tag, fragment.surfaceId, fragment.instanceHandle}, eventDispatcher_, *this); } + SharedEventEmitter createEventEmitter( + InstanceHandle::Shared const &instanceHandle) const override { + return std::make_shared( + std::make_shared(instanceHandle), eventDispatcher_); + } + protected: /* * Called immediately after `ShadowNode` is created or cloned. diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h index b55d30fc4b51c9..e23a9ae3877d86 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace facebook::react { @@ -87,8 +88,8 @@ class ConcreteShadowNode : public BaseShadowNodeT { static ConcreteStateData initialStateData( Props::Shared const & /*props*/, - ShadowNodeFamilyFragment const &familyFragment, - ComponentDescriptor const &componentDescriptor) { + ShadowNodeFamily::Shared const & /*family*/, + ComponentDescriptor const & /*componentDescriptor*/) { return {}; } diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp index 2426cea3ca23ce..22a12a2ffb43fe 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp @@ -43,7 +43,6 @@ ValueFactory EventEmitter::defaultPayloadFactory() { EventEmitter::EventEmitter( SharedEventTarget eventTarget, - Tag /*tag*/, EventDispatcher::Weak eventDispatcher) : eventTarget_(std::move(eventTarget)), eventDispatcher_(std::move(eventDispatcher)) {} @@ -131,8 +130,4 @@ void EventEmitter::setEnabled(bool enabled) const { } } -const SharedEventTarget &EventEmitter::getEventTarget() const { - return eventTarget_; -} - } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h index 36eae1ad1ffead..c02d605b43b77d 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h @@ -38,7 +38,6 @@ class EventEmitter { EventEmitter( SharedEventTarget eventTarget, - Tag tag, EventDispatcher::Weak eventDispatcher); virtual ~EventEmitter() = default; @@ -55,8 +54,6 @@ class EventEmitter { */ void setEnabled(bool enabled) const; - SharedEventTarget const &getEventTarget() const; - protected: #ifdef ANDROID // We need this temporarily due to lack of Java-counterparts for particular diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventTarget.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventTarget.cpp index 38e78e500f84b9..82118ceee8b89e 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventTarget.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventTarget.cpp @@ -13,14 +13,9 @@ namespace facebook::react { using Tag = EventTarget::Tag; -EventTarget::EventTarget( - jsi::Runtime &runtime, - jsi::Value const &instanceHandle, - Tag tag) - : weakInstanceHandle_( - jsi::WeakObject(runtime, instanceHandle.asObject(runtime))), - strongInstanceHandle_(jsi::Value::null()), - tag_(tag) {} +EventTarget::EventTarget(InstanceHandle::Shared instanceHandle) + : instanceHandle_(std::move(instanceHandle)), + strongInstanceHandle_(jsi::Value::null()) {} void EventTarget::setEnabled(bool enabled) const { enabled_ = enabled; @@ -31,7 +26,7 @@ void EventTarget::retain(jsi::Runtime &runtime) const { return; } - strongInstanceHandle_ = weakInstanceHandle_.lock(runtime); + strongInstanceHandle_ = instanceHandle_->getInstanceHandle(runtime); // Having a `null` or `undefined` object here indicates that // `weakInstanceHandle_` was already deallocated. This should *not* happen by @@ -62,7 +57,7 @@ jsi::Value EventTarget::getInstanceHandle(jsi::Runtime &runtime) const { } Tag EventTarget::getTag() const { - return tag_; + return instanceHandle_->getTag(); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventTarget.h b/packages/react-native/ReactCommon/react/renderer/core/EventTarget.h index f6b29947805b64..1b023dc65105e8 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventTarget.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventTarget.h @@ -7,9 +7,9 @@ #pragma once -#include - #include +#include +#include namespace facebook::react { @@ -34,7 +34,7 @@ class EventTarget { /* * Constructs an EventTarget from a weak instance handler and a tag. */ - EventTarget(jsi::Runtime &runtime, jsi::Value const &instanceHandle, Tag tag); + explicit EventTarget(InstanceHandle::Shared instanceHandle); /* * Sets the `enabled` flag that allows creating a strong instance handle from @@ -65,10 +65,9 @@ class EventTarget { Tag getTag() const; private: + const InstanceHandle::Shared instanceHandle_; mutable bool enabled_{false}; // Protected by `EventEmitter::DispatchMutex()`. - mutable jsi::WeakObject weakInstanceHandle_; // Protected by `jsi::Runtime &`. mutable jsi::Value strongInstanceHandle_; // Protected by `jsi::Runtime &`. - Tag tag_; }; using SharedEventTarget = std::shared_ptr; diff --git a/packages/react-native/ReactCommon/react/renderer/core/InstanceHandle.cpp b/packages/react-native/ReactCommon/react/renderer/core/InstanceHandle.cpp new file mode 100644 index 00000000000000..9d7ccef277e57a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/core/InstanceHandle.cpp @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include "InstanceHandle.h" + +namespace facebook::react { + +InstanceHandle::InstanceHandle( + jsi::Runtime &runtime, + jsi::Value const &instanceHandle, + Tag tag) + : weakInstanceHandle_( + jsi::WeakObject(runtime, instanceHandle.asObject(runtime))), + tag_(tag) {} + +jsi::Value InstanceHandle::getInstanceHandle(jsi::Runtime &runtime) const { + return weakInstanceHandle_.lock(runtime); +} + +Tag InstanceHandle::getTag() const { + return tag_; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/InstanceHandle.h b/packages/react-native/ReactCommon/react/renderer/core/InstanceHandle.h new file mode 100644 index 00000000000000..171d03011e975a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/core/InstanceHandle.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +class InstanceHandle { + public: + using Shared = std::shared_ptr; + + InstanceHandle( + jsi::Runtime &runtime, + jsi::Value const &instanceHandle, + Tag tag); + + /* + * Creates and returns the `instanceHandle`. + * Returns `null` if the `instanceHandle` is not retained at this moment. + */ + jsi::Value getInstanceHandle(jsi::Runtime &runtime) const; + + /* + * Deprecated. Do not use. + */ + Tag getTag() const; + + private: + const jsi::WeakObject weakInstanceHandle_; // Protected by `jsi::Runtime &`. + const Tag tag_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h b/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h index d77ac5971329bf..93cadde558d1b5 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ReactPrimitives.h @@ -17,8 +17,6 @@ namespace facebook::react { * `Tag` and `InstanceHandle` are used to address React Native components. */ using Tag = int32_t; -using InstanceHandle = struct InstanceHandleDummyStruct { -} *; /* * An id of a running Surface instance that is used to refer to the instance. diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index 6068eb3e822aec..ba8da5d6f1eda8 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -174,6 +174,15 @@ const SharedEventEmitter &ShadowNode::getEventEmitter() const { return family_->eventEmitter_; } +jsi::Value ShadowNode::getInstanceHandle(jsi::Runtime &runtime) const { + auto instanceHandle = family_->instanceHandle_; + if (instanceHandle == nullptr) { + return jsi::Value::null(); + } + + return instanceHandle->getInstanceHandle(runtime); +} + Tag ShadowNode::getTag() const { return family_->tag_; } diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h index 265dc9a921e56c..4e428b25997985 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h @@ -123,6 +123,7 @@ class ShadowNode : public Sealable, Props::Shared const &getProps() const; ListOfShared const &getChildren() const; SharedEventEmitter const &getEventEmitter() const; + jsi::Value getInstanceHandle(jsi::Runtime &runtime) const; Tag getTag() const; SurfaceId getSurfaceId() const; diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp index 2c86a2555e6c50..5035656acebbd5 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.cpp @@ -25,7 +25,9 @@ ShadowNodeFamily::ShadowNodeFamily( : eventDispatcher_(std::move(eventDispatcher)), tag_(fragment.tag), surfaceId_(fragment.surfaceId), - eventEmitter_(fragment.eventEmitter), + instanceHandle_(fragment.instanceHandle), + eventEmitter_( + componentDescriptor.createEventEmitter(fragment.instanceHandle)), componentDescriptor_(componentDescriptor), componentHandle_(componentDescriptor.getComponentHandle()), componentName_(componentDescriptor.getComponentName()) {} @@ -48,6 +50,10 @@ SurfaceId ShadowNodeFamily::getSurfaceId() const { return surfaceId_; } +SharedEventEmitter ShadowNodeFamily::getEventEmitter() const { + return eventEmitter_; +} + ComponentName ShadowNodeFamily::getComponentName() const { return componentName_; } diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h index 32c7eb3033241e..401686bc806568 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamily.h @@ -13,8 +13,8 @@ #include #include +#include #include -#include namespace facebook::react { @@ -22,6 +22,24 @@ class ComponentDescriptor; class ShadowNode; class State; +/* + * This is a collection of fields serving as a specification to create new + * `ShadowNodeFamily` instances. + * + * Do not use this class as a general purpose container to share information + * about a `ShadowNodeFamily`. Pelase define specific purpose containers in + * those cases. + * + * Note: All of the fields are `const &` references (essentially just raw + * pointers) which means that the Fragment does not copy/store them nor + * retain ownership of them. + */ +struct ShadowNodeFamilyFragment { + Tag const tag; + SurfaceId const surfaceId; + InstanceHandle::Shared const &instanceHandle; +}; + /* * Represents all things that shadow nodes from the same family have in common. * To be used inside `ShadowNode` class *only*. @@ -73,6 +91,8 @@ class ShadowNodeFamily final { SurfaceId getSurfaceId() const; + SharedEventEmitter getEventEmitter() const; + /* * Sets and gets the most recent state. */ @@ -94,7 +114,6 @@ class ShadowNodeFamily final { private: friend ShadowNode; - friend ShadowNodeFamilyFragment; friend State; /* @@ -119,6 +138,11 @@ class ShadowNodeFamily final { */ SurfaceId const surfaceId_; + /* + * Weak reference to the React instance handle + */ + InstanceHandle::Shared const instanceHandle_; + /* * `EventEmitter` associated with all nodes of the family. */ diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamilyFragment.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamilyFragment.cpp deleted file mode 100644 index 3f27ab9bfefee2..00000000000000 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamilyFragment.cpp +++ /dev/null @@ -1,34 +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. - */ - -#include "ShadowNodeFamilyFragment.h" - -#include - -namespace facebook::react { - -ShadowNodeFamilyFragment ShadowNodeFamilyFragment::build( - ShadowNodeFamily const &family) { - return { - family.tag_, - family.surfaceId_, - family.eventEmitter_, - }; -} - -using Value = ShadowNodeFamilyFragment::Value; - -Value::Value(ShadowNodeFamilyFragment const &fragment) - : tag(fragment.tag), - surfaceId(fragment.surfaceId), - eventEmitter(fragment.eventEmitter) {} - -Value::operator ShadowNodeFamilyFragment() const { - return ShadowNodeFamilyFragment{tag, surfaceId, eventEmitter}; -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamilyFragment.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamilyFragment.h deleted file mode 100644 index 0de0e60ebe3bb4..00000000000000 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeFamilyFragment.h +++ /dev/null @@ -1,53 +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. - */ - -#pragma once - -#include -#include - -namespace facebook::react { - -class ShadowNodeFamily; - -/* - * Note: All of the fields are `const &` references (essentially just raw - * pointers) which means that the Fragment does not copy/store them nor - * retain ownership of them. - */ -class ShadowNodeFamilyFragment final { - public: - static ShadowNodeFamilyFragment build(ShadowNodeFamily const &family); - - Tag const tag; - SurfaceId const surfaceId; - EventEmitter::Shared const &eventEmitter; - - /* - * `ShadowNodeFamilyFragment` is not owning data-structure, it only stores raw - * pointers to the data. `ShadowNodeFamilyFragment::Value` is a convenient - * owning counterpart of that. - */ - class Value final { - public: - /* - * Creates an object with given `ShadowNodeFragment`. - */ - Value(ShadowNodeFamilyFragment const &fragment); - - /* - * Creates a `ShadowNodeFragment` from the object. - */ - explicit operator ShadowNodeFamilyFragment() const; - - Tag tag; - SurfaceId surfaceId; - EventEmitter::Shared eventEmitter; - }; -}; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/ComponentDescriptorTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/ComponentDescriptorTest.cpp index 33bd463ab3f2eb..021f62b4f58a8e 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/ComponentDescriptorTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/ComponentDescriptorTest.cpp @@ -29,13 +29,11 @@ TEST(ComponentDescriptorTest, createShadowNode) { const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc")); Props::Shared props = descriptor->cloneProps(parserContext, nullptr, raw); - auto family = descriptor->createFamily( - ShadowNodeFamilyFragment{ - /* .tag = */ 9, - /* .surfaceId = */ 1, - /* .eventEmitter = */ nullptr, - }, - nullptr); + auto family = descriptor->createFamily(ShadowNodeFamilyFragment{ + /* .tag = */ 9, + /* .surfaceId = */ 1, + /* .instanceHandle = */ nullptr, + }); ShadowNode::Shared node = descriptor->createShadowNode( ShadowNodeFragment{ @@ -62,13 +60,11 @@ TEST(ComponentDescriptorTest, cloneShadowNode) { const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc")); Props::Shared props = descriptor->cloneProps(parserContext, nullptr, raw); - auto family = descriptor->createFamily( - ShadowNodeFamilyFragment{ - /* .tag = */ 9, - /* .surfaceId = */ 1, - /* .eventEmitter = */ nullptr, - }, - nullptr); + auto family = descriptor->createFamily(ShadowNodeFamilyFragment{ + /* .tag = */ 9, + /* .surfaceId = */ 1, + /* .instanceHandle = */ nullptr, + }); ShadowNode::Shared node = descriptor->createShadowNode( ShadowNodeFragment{ /* .props = */ props, @@ -97,37 +93,31 @@ TEST(ComponentDescriptorTest, appendChild) { const auto &raw = RawProps(folly::dynamic::object("nativeID", "abc")); Props::Shared props = descriptor->cloneProps(parserContext, nullptr, raw); - auto family1 = descriptor->createFamily( - ShadowNodeFamilyFragment{ - /* .tag = */ 1, - /* .surfaceId = */ 1, - /* .eventEmitter = */ nullptr, - }, - nullptr); + auto family1 = descriptor->createFamily(ShadowNodeFamilyFragment{ + /* .tag = */ 1, + /* .surfaceId = */ 1, + /* .instanceHandle = */ nullptr, + }); ShadowNode::Shared node1 = descriptor->createShadowNode( ShadowNodeFragment{ /* .props = */ props, }, family1); - auto family2 = descriptor->createFamily( - ShadowNodeFamilyFragment{ - /* .tag = */ 2, - /* .surfaceId = */ 1, - /* .eventEmitter = */ nullptr, - }, - nullptr); + auto family2 = descriptor->createFamily(ShadowNodeFamilyFragment{ + /* .tag = */ 2, + /* .surfaceId = */ 1, + /* .instanceHandle = */ nullptr, + }); ShadowNode::Shared node2 = descriptor->createShadowNode( ShadowNodeFragment{ /* .props = */ props, }, family2); - auto family3 = descriptor->createFamily( - ShadowNodeFamilyFragment{ - /* .tag = */ 3, - /* .surfaceId = */ 1, - /* .eventEmitter = */ nullptr, - }, - nullptr); + auto family3 = descriptor->createFamily(ShadowNodeFamilyFragment{ + /* .tag = */ 3, + /* .surfaceId = */ 1, + /* .instanceHandle = */ nullptr, + }); ShadowNode::Shared node3 = descriptor->createShadowNode( ShadowNodeFragment{ /* .props = */ props, diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp index 65e6705cb582a5..2513d84a5d5db9 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/ShadowNodeTest.cpp @@ -41,7 +41,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 11, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -57,7 +57,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 12, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -73,7 +73,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 13, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -92,7 +92,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 15, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -108,7 +108,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 16, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -127,7 +127,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 17, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -143,7 +143,7 @@ class ShadowNodeTest : public ::testing::Test { ShadowNodeFamilyFragment{ /* .tag = */ 18, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); @@ -174,7 +174,7 @@ TEST_F(ShadowNodeTest, handleShadowNodeCreation) { EXPECT_STREQ(nodeZ_->getComponentName(), "Test"); EXPECT_EQ(nodeZ_->getTag(), 18); EXPECT_EQ(nodeZ_->getSurfaceId(), surfaceId_); - EXPECT_EQ(nodeZ_->getEventEmitter(), nullptr); + EXPECT_NE(nodeZ_->getEventEmitter(), nullptr); EXPECT_EQ(nodeZ_->getChildren().size(), 0); } @@ -238,7 +238,7 @@ TEST_F(ShadowNodeTest, handleState) { ShadowNodeFamilyFragment{ /* .tag = */ 9, /* .surfaceId = */ surfaceId_, - /* .eventEmitter = */ nullptr, + /* .instanceHandle = */ nullptr, }, eventDispatcher_, componentDescriptor_); diff --git a/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.cpp index 35973c49a2b3b2..10d061cb858008 100644 --- a/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.cpp @@ -26,10 +26,8 @@ ShadowNode::Unshared ComponentBuilder::build( children.push_back(build(childFragment)); } - auto family = componentDescriptor.createFamily( - ShadowNodeFamilyFragment{ - elementFragment.tag, elementFragment.surfaceId, nullptr}, - nullptr); + auto family = componentDescriptor.createFamily(ShadowNodeFamilyFragment{ + elementFragment.tag, elementFragment.surfaceId, nullptr}); auto initialState = componentDescriptor.createInitialState(elementFragment.props, family); diff --git a/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.h b/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.h index 6a0251fab9f82e..ae082320a84520 100644 --- a/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.h +++ b/packages/react-native/ReactCommon/react/renderer/element/ComponentBuilder.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec index a5f2bd21334955..5dd7f9ef452028 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec @@ -48,7 +48,8 @@ Pod::Spec.new do |s| "\"$(PODS_ROOT)/DoubleConversion\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\"", - "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\"" + "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"" ] end @@ -63,5 +64,6 @@ Pod::Spec.new do |s| s.dependency "React-Core/Default" s.dependency "React-RCTImage" s.dependency "React-debug" + s.dependency "React-utils" s.dependency "glog" end diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index 0b9106bbb5d2b0..f504c986b5c418 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -225,9 +225,6 @@ ShadowTree::ShadowTree( ShadowTreeDelegate const &delegate, ContextContainer const &contextContainer) : surfaceId_(surfaceId), delegate_(delegate) { - const auto noopEventEmitter = std::make_shared( - nullptr, -1, std::shared_ptr()); - static auto globalRootComponentDescriptor = std::make_unique( ComponentDescriptorParameters{ @@ -239,9 +236,8 @@ ShadowTree::ShadowTree( layoutConstraints, layoutContext); - auto const fragment = - ShadowNodeFamilyFragment{surfaceId, surfaceId, noopEventEmitter}; - auto family = globalRootComponentDescriptor->createFamily(fragment, nullptr); + auto const fragment = ShadowNodeFamilyFragment{surfaceId, surfaceId, nullptr}; + auto family = globalRootComponentDescriptor->createFamily(fragment); auto rootShadowNode = std::static_pointer_cast( globalRootComponentDescriptor->createShadowNode( diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp index 75945738a5af8a..2ecd5413846d68 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp @@ -53,7 +53,7 @@ static ShadowNode::Shared makeNode( return componentDescriptor.createShadowNode( ShadowNodeFragment{ props, std::make_shared(children)}, - componentDescriptor.createFamily({tag, SurfaceId(1), nullptr}, nullptr)); + componentDescriptor.createFamily({tag, SurfaceId(1), nullptr})); } /** @@ -79,8 +79,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { auto rootComponentDescriptor = RootComponentDescriptor(componentDescriptorParameters); - auto rootFamily = rootComponentDescriptor.createFamily( - {Tag(1), SurfaceId(1), nullptr}, nullptr); + auto rootFamily = + rootComponentDescriptor.createFamily({Tag(1), SurfaceId(1), nullptr}); // Creating an initial root shadow node. auto emptyRootNode = std::const_pointer_cast( @@ -110,8 +110,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { auto childJ = makeNode(viewComponentDescriptor, 109, {}); auto childK = makeNode(viewComponentDescriptor, 110, {}); - auto family = viewComponentDescriptor.createFamily( - {10, SurfaceId(1), nullptr}, nullptr); + auto family = + viewComponentDescriptor.createFamily({10, SurfaceId(1), nullptr}); // Construct "identical" shadow nodes: they differ only in children. auto shadowNodeV1 = viewComponentDescriptor.createShadowNode( @@ -390,8 +390,8 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) { auto rootComponentDescriptor = RootComponentDescriptor(componentDescriptorParameters); - auto rootFamily = rootComponentDescriptor.createFamily( - {Tag(1), SurfaceId(1), nullptr}, nullptr); + auto rootFamily = + rootComponentDescriptor.createFamily({Tag(1), SurfaceId(1), nullptr}); // Creating an initial root shadow node. auto emptyRootNode = std::const_pointer_cast( @@ -422,8 +422,8 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) { auto childJ = makeNode(viewComponentDescriptor, 109, {}); auto childK = makeNode(viewComponentDescriptor, 110, {}); - auto family = viewComponentDescriptor.createFamily( - {10, SurfaceId(1), nullptr}, nullptr); + auto family = + viewComponentDescriptor.createFamily({10, SurfaceId(1), nullptr}); auto reparentedViewA = makeNode( viewComponentDescriptor, diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp index 0e53a7ad9b70d8..02607035ad4e50 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp @@ -42,8 +42,6 @@ static void testShadowNodeTreeLifeCycle( ViewComponentDescriptor(componentDescriptorParameters); auto rootComponentDescriptor = RootComponentDescriptor(componentDescriptorParameters); - auto noopEventEmitter = - std::make_shared(nullptr, -1, eventDispatcher); PropsParserContext parserContext{-1, *contextContainer}; @@ -52,8 +50,8 @@ static void testShadowNodeTreeLifeCycle( for (int i = 0; i < repeats; i++) { allNodes.clear(); - auto family = rootComponentDescriptor.createFamily( - {Tag(1), SurfaceId(1), nullptr}, nullptr); + auto family = + rootComponentDescriptor.createFamily({Tag(1), SurfaceId(1), nullptr}); // Creating an initial root shadow node. auto emptyRootNode = std::const_pointer_cast( @@ -195,8 +193,6 @@ static void testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening( ViewComponentDescriptor(componentDescriptorParameters); auto rootComponentDescriptor = RootComponentDescriptor(componentDescriptorParameters); - auto noopEventEmitter = - std::make_shared(nullptr, -1, eventDispatcher); PropsParserContext parserContext{-1, *contextContainer}; @@ -205,8 +201,8 @@ static void testShadowNodeTreeLifeCycleExtensiveFlatteningUnflattening( for (int i = 0; i < repeats; i++) { allNodes.clear(); - auto family = rootComponentDescriptor.createFamily( - {Tag(1), SurfaceId(1), nullptr}, nullptr); + auto family = + rootComponentDescriptor.createFamily({Tag(1), SurfaceId(1), nullptr}); // Creating an initial root shadow node. auto emptyRootNode = std::const_pointer_cast( diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp index 6a1cbc8bb3e1b8..ebfeb2b0860f77 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp @@ -108,7 +108,9 @@ void SurfaceHandler::stop() const noexcept { // mounted views, so we need to commit an empty tree to trigger all // side-effects (including destroying and removing mounted views). react_native_assert(shadowTree && "`shadowTree` must not be null."); - shadowTree->commitEmptyTree(); + if (shadowTree) { + shadowTree->commitEmptyTree(); + } } void SurfaceHandler::setDisplayMode(DisplayMode displayMode) const noexcept { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 610f4dc12563a2..355a3a1dad82fb 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -68,7 +68,7 @@ ShadowNode::Shared UIManager::createNode( std::string const &name, SurfaceId surfaceId, const RawProps &rawProps, - SharedEventTarget eventTarget) const { + const InstanceHandle::Shared &instanceHandle) const { SystraceSection s("UIManager::createNode"); auto &componentDescriptor = componentDescriptorRegistry_->at(name); @@ -77,9 +77,9 @@ ShadowNode::Shared UIManager::createNode( PropsParserContext propsParserContext{surfaceId, *contextContainer_.get()}; - auto const fragment = ShadowNodeFamilyFragment{tag, surfaceId, nullptr}; - auto family = - componentDescriptor.createFamily(fragment, std::move(eventTarget)); + auto const fragment = + ShadowNodeFamilyFragment{tag, surfaceId, instanceHandle}; + auto family = componentDescriptor.createFamily(fragment); auto const props = componentDescriptor.cloneProps(propsParserContext, nullptr, rawProps); auto const state = componentDescriptor.createInitialState(props, family); @@ -231,19 +231,19 @@ ShadowTree::Unique UIManager::stopSurface(SurfaceId surfaceId) const { // Waiting for all concurrent commits to be finished and unregistering the // `ShadowTree`. auto shadowTree = getShadowTreeRegistry().remove(surfaceId); + if (shadowTree) { + // We execute JavaScript/React part of the process at the very end to + // minimize any visible side-effects of stopping the Surface. Any possible + // commits from the JavaScript side will not be able to reference a + // `ShadowTree` and will fail silently. + runtimeExecutor_([=](jsi::Runtime &runtime) { + SurfaceRegistryBinding::stopSurface(runtime, surfaceId); + }); - // We execute JavaScript/React part of the process at the very end to minimize - // any visible side-effects of stopping the Surface. Any possible commits from - // the JavaScript side will not be able to reference a `ShadowTree` and will - // fail silently. - runtimeExecutor_([=](jsi::Runtime &runtime) { - SurfaceRegistryBinding::stopSurface(runtime, surfaceId); - }); - - if (leakChecker_) { - leakChecker_->stopSurface(surfaceId); + if (leakChecker_) { + leakChecker_->stopSurface(surfaceId); + } } - return shadowTree; } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 97db13c7927742..31db46be2c0eee 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -134,7 +135,7 @@ class UIManager final : public ShadowTreeDelegate { std::string const &componentName, SurfaceId surfaceId, const RawProps &props, - SharedEventTarget eventTarget) const; + const InstanceHandle::Shared &instanceHandle) const; ShadowNode::Shared cloneNode( ShadowNode const &shadowNode, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index f9c0580a228553..2e4db8f6e19b27 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -140,6 +140,19 @@ void UIManagerBinding::invalidate() const { uiManager_->setDelegate(nullptr); } +static void validateArgumentCount( + jsi::Runtime &runtime, + std::string const &methodName, + size_t expected, + size_t actual) { + if (actual < expected) { + throw jsi::JSError( + runtime, + methodName + " requires " + std::to_string(expected) + + " arguments, but only " + std::to_string(actual) + " were passed"); + } +} + jsi::Value UIManagerBinding::get( jsi::Runtime &runtime, jsi::PropNameID const &name) { @@ -177,21 +190,25 @@ jsi::Value UIManagerBinding::get( // Semantic: Creates a new node with given pieces. if (methodName == "createNode") { + auto paramCount = 5; return jsi::Function::createFromHostFunction( runtime, name, - 5, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { - auto eventTarget = - eventTargetFromValue(runtime, arguments[4], arguments[0]); - if (!eventTarget) { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + auto instanceHandle = + instanceHandleFromValue(runtime, arguments[4], arguments[0]); + if (!instanceHandle) { react_native_assert(false); return jsi::Value::undefined(); } + return valueFromShadowNode( runtime, uiManager->createNode( @@ -199,21 +216,24 @@ jsi::Value UIManagerBinding::get( stringFromValue(runtime, arguments[1]), surfaceIdFromValue(runtime, arguments[2]), RawProps(runtime, arguments[3]), - eventTarget)); + instanceHandle)); }); } // Semantic: Clones the node with *same* props and *same* children. if (methodName == "cloneNode") { + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + return valueFromShadowNode( runtime, uiManager->cloneNode( @@ -222,15 +242,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "setIsJSResponder") { + auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + uiManager->setIsJSResponder( shadowNodeFromValue(runtime, arguments[0]), arguments[1].getBool(), @@ -241,15 +264,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "findNodeAtPoint") { + auto paramCount = 4; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) { + size_t count) { + validateArgumentCount(runtime, methodName, paramCount, count); + auto node = shadowNodeFromValue(runtime, arguments[0]); auto locationX = (Float)arguments[1].getNumber(); auto locationY = (Float)arguments[2].getNumber(); @@ -272,15 +298,18 @@ jsi::Value UIManagerBinding::get( // Semantic: Clones the node with *same* props and *empty* children. if (methodName == "cloneNodeWithNewChildren") { + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + return valueFromShadowNode( runtime, uiManager->cloneNode( @@ -291,15 +320,18 @@ jsi::Value UIManagerBinding::get( // Semantic: Clones the node with *given* props and *same* children. if (methodName == "cloneNodeWithNewProps") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto const &rawProps = RawProps(runtime, arguments[1]); return valueFromShadowNode( runtime, @@ -312,15 +344,18 @@ jsi::Value UIManagerBinding::get( // Semantic: Clones the node with *given* props and *empty* children. if (methodName == "cloneNodeWithNewChildrenAndProps") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto const &rawProps = RawProps(runtime, arguments[1]); return valueFromShadowNode( runtime, @@ -332,15 +367,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "appendChild") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + uiManager->appendChild( shadowNodeFromValue(runtime, arguments[0]), shadowNodeFromValue(runtime, arguments[1])); @@ -352,11 +390,11 @@ jsi::Value UIManagerBinding::get( return jsi::Function::createFromHostFunction( runtime, name, - 1, + 0, [](jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const * /*arguments*/, - size_t /*count*/) noexcept -> jsi::Value { + size_t /*count*/) -> jsi::Value { auto shadowNodeList = std::make_shared( ShadowNode::ListOfShared({})); return valueFromShadowNodeList(runtime, shadowNodeList); @@ -364,14 +402,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "appendChildToSet") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [](jsi::Runtime &runtime, - jsi::Value const & /*thisValue*/, - jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + paramCount, + [methodName, paramCount]( + jsi::Runtime &runtime, + jsi::Value const & /*thisValue*/, + jsi::Value const *arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[0]); auto shadowNode = shadowNodeFromValue(runtime, arguments[1]); shadowNodeList->push_back(shadowNode); @@ -380,18 +422,21 @@ jsi::Value UIManagerBinding::get( } if (methodName == "completeRoot") { + auto paramCount = 2; std::weak_ptr weakUIManager = uiManager_; // Enhanced version of the method that uses `backgroundExecutor` and // captures a shared pointer to `UIManager`. return jsi::Function::createFromHostFunction( runtime, name, - 2, - [weakUIManager, uiManager]( + paramCount, + [weakUIManager, uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto runtimeSchedulerBinding = RuntimeSchedulerBinding::getBinding(runtime); auto surfaceId = surfaceIdFromValue(runtime, arguments[0]); @@ -448,15 +493,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "registerEventHandler") { + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [this]( + paramCount, + [this, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto eventHandler = arguments[0].getObject(runtime).getFunction(runtime); eventHandler_ = @@ -466,15 +514,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "getRelativeLayoutMetrics") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( *shadowNodeFromValue(runtime, arguments[0]), shadowNodeFromValue(runtime, arguments[1]).get(), @@ -490,15 +541,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "dispatchCommand") { + auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, - 3, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); if (shadowNode) { uiManager->dispatchCommand( @@ -511,15 +565,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "setNativeProps") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *arguments, - size_t) -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + uiManager->setNativeProps_DEPRECATED( shadowNodeFromValue(runtime, arguments[0]), RawProps(runtime, arguments[1])); @@ -530,15 +587,18 @@ jsi::Value UIManagerBinding::get( // Legacy API if (methodName == "measureLayout") { + auto paramCount = 4; return jsi::Function::createFromHostFunction( runtime, name, - 4, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) { + size_t count) { + validateArgumentCount(runtime, methodName, paramCount, count); + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( *shadowNodeFromValue(runtime, arguments[0]), shadowNodeFromValue(runtime, arguments[1]).get(), @@ -566,15 +626,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "measure") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) { + size_t count) { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto layoutMetrics = uiManager->getRelativeLayoutMetrics( *shadowNode, nullptr, {/* .includeTransform = */ true}); @@ -608,15 +671,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "measureInWindow") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) { + size_t count) { + validateArgumentCount(runtime, methodName, paramCount, count); + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( *shadowNodeFromValue(runtime, arguments[0]), nullptr, @@ -643,15 +709,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "sendAccessibilityEvent") { + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 2, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + uiManager->sendAccessibilityEvent( shadowNodeFromValue(runtime, arguments[0]), stringFromValue(runtime, arguments[1])); @@ -661,15 +730,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "configureNextLayoutAnimation") { + auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, - 3, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + uiManager->configureNextLayoutAnimation( runtime, // TODO: pass in JSI value instead of folly::dynamic to RawValue @@ -686,10 +758,10 @@ jsi::Value UIManagerBinding::get( name, 0, [this]( - jsi::Runtime &, - jsi::Value const &, - jsi::Value const *, - size_t) noexcept -> jsi::Value { + jsi::Runtime & /*runtime*/, + jsi::Value const & /*thisValue*/, + jsi::Value const * /*arguments*/, + size_t /*count*/) -> jsi::Value { return {serialize(currentEventPriority_)}; }); } @@ -703,15 +775,18 @@ jsi::Value UIManagerBinding::get( } if (methodName == "findShadowNodeByTag_DEPRECATED") { + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const &, jsi::Value const *arguments, - size_t) -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = uiManager->findShadowNodeByTag_DEPRECATED( tagFromValue(arguments[0])); @@ -742,15 +817,18 @@ jsi::Value UIManagerBinding::get( // /* width: */ number, // /* height: */ number // ] + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( *shadowNodeFromValue(runtime, arguments[0]), nullptr, @@ -780,15 +858,18 @@ jsi::Value UIManagerBinding::get( // Otherwise, it returns null. // getParent(shadowNode: ShadowNode): ?InstanceHandle + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto parentShadowNode = uiManager->getNewestParentOfShadowNode(*shadowNode); @@ -798,7 +879,7 @@ jsi::Value UIManagerBinding::get( return jsi::Value::null(); } - return getInstanceHandleFromShadowNode(parentShadowNode, runtime); + return (*parentShadowNode).getInstanceHandle(runtime); }); } @@ -811,15 +892,18 @@ jsi::Value UIManagerBinding::get( // children. Otherwise, it returns an empty array. // getChildren(shadowNode: ShadowNode): Array + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto newestCloneOfShadowNode = @@ -844,15 +928,18 @@ jsi::Value UIManagerBinding::get( // the current revision of an active shadow tree. // isConnected(shadowNode: ShadowNode): boolean + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto newestCloneOfShadowNode = @@ -873,15 +960,18 @@ jsi::Value UIManagerBinding::get( // compareDocumentPosition(shadowNode: ShadowNode, otherShadowNode: // ShadowNode): number + auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto otherShadowNode = shadowNodeFromValue(runtime, arguments[1]); @@ -907,15 +997,18 @@ jsi::Value UIManagerBinding::get( // not need any traversal. // getTextContent(shadowNode: ShadowNode): string + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto textContent = @@ -949,15 +1042,18 @@ jsi::Value UIManagerBinding::get( // /* top: */ number, // /* left: */ number, // ] + auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, - 1, - [uiManager]( + paramCount, + [uiManager, methodName, paramCount]( jsi::Runtime &runtime, jsi::Value const & /*thisValue*/, jsi::Value const *arguments, - size_t /*count*/) noexcept -> jsi::Value { + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto newestCloneOfShadowNode = @@ -1006,13 +1102,80 @@ jsi::Value UIManagerBinding::get( return jsi::Array::createWithElements( runtime, - getInstanceHandleFromShadowNode( - newestParentOfShadowNode, runtime), + (*newestParentOfShadowNode).getInstanceHandle(runtime), jsi::Value{runtime, (double)offsetTop}, jsi::Value{runtime, (double)offsetLeft}); }); } + if (methodName == "getScrollPosition") { + // This is a method to access scroll information for React Native nodes, to + // implement these methods: + // * `Element.prototype.scrollLeft`: see + // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft. + // * `Element.prototype.scrollTop`: see + // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop. + + // It uses the version of the shadow node that is present in the current + // revision of the shadow tree. If the node is not present or is not + // displayed (because any of its ancestors or itself have 'display: none'), + // it returns undefined. Otherwise, it returns the scroll position. + + // getScrollPosition(shadowNode: ShadowNode): + // ?[ + // /* scrollLeft: */ number, + // /* scrollTop: */ number, + // ] + auto paramCount = 1; + return jsi::Function::createFromHostFunction( + runtime, + name, + paramCount, + [uiManager, methodName, paramCount]( + jsi::Runtime &runtime, + jsi::Value const & /*thisValue*/, + jsi::Value const *arguments, + size_t count) -> jsi::Value { + validateArgumentCount(runtime, methodName, paramCount, count); + + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); + + auto newestCloneOfShadowNode = + uiManager->getNewestCloneOfShadowNode(*shadowNode); + // The node is no longer part of an active shadow tree, or it is the + // root node + if (newestCloneOfShadowNode == nullptr) { + return jsi::Value::undefined(); + } + + // If the node is not displayed (itself or any of its ancestors has + // "display: none", it returns an empty layout metrics object. + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( + *shadowNode, nullptr, {/* .includeTransform = */ true}); + if (layoutMetrics == EmptyLayoutMetrics) { + return jsi::Value::undefined(); + } + + auto layoutableShadowNode = traitCast( + newestCloneOfShadowNode.get()); + // This should never happen + if (layoutableShadowNode == nullptr) { + return jsi::Value::undefined(); + } + + auto scrollPosition = layoutableShadowNode->getContentOriginOffset(); + + return jsi::Array::createWithElements( + runtime, + jsi::Value{ + runtime, + scrollPosition.x == 0 ? 0 : (double)-scrollPosition.x}, + jsi::Value{ + runtime, + scrollPosition.y == 0 ? 0 : (double)-scrollPosition.y}); + }); + } + return jsi::Value::undefined(); } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h b/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h index 6c4893dccf58f6..629a37b658934a 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h @@ -142,16 +142,16 @@ inline static Tag tagFromValue(jsi::Value const &value) { return (Tag)value.getNumber(); } -inline static SharedEventTarget eventTargetFromValue( +inline static InstanceHandle::Shared instanceHandleFromValue( jsi::Runtime &runtime, - jsi::Value const &eventTargetValue, + jsi::Value const &instanceHandleValue, jsi::Value const &tagValue) { - react_native_assert(!eventTargetValue.isNull()); - if (eventTargetValue.isNull()) { + react_native_assert(!instanceHandleValue.isNull()); + if (instanceHandleValue.isNull()) { return nullptr; } - return std::make_shared( - runtime, eventTargetValue, tagFromValue(tagValue)); + return std::make_shared( + runtime, instanceHandleValue, tagFromValue(tagValue)); } inline static SurfaceId surfaceIdFromValue( @@ -185,21 +185,6 @@ inline static folly::dynamic commandArgsFromValue( return jsi::dynamicFromValue(runtime, value); } -inline static jsi::Value getInstanceHandleFromShadowNode( - ShadowNode::Shared shadowNode, - jsi::Runtime &runtime) { - auto eventTarget = shadowNode->getEventEmitter()->getEventTarget(); - // shadowNode is probably a RootShadowNode and they don't have - // event targets. - if (eventTarget == nullptr) { - return jsi::Value::null(); - } - eventTarget->retain(runtime); - auto instanceHandle = eventTarget->getInstanceHandle(runtime); - eventTarget->release(runtime); - return instanceHandle; -} - inline static jsi::Value getArrayOfInstanceHandlesFromShadowNodes( ShadowNode::ListOfShared const &nodes, jsi::Runtime &runtime) { @@ -209,7 +194,7 @@ inline static jsi::Value getArrayOfInstanceHandlesFromShadowNodes( std::vector nonNullInstanceHandles; nonNullInstanceHandles.reserve(nodes.size()); for (auto const &shadowNode : nodes) { - auto instanceHandle = getInstanceHandleFromShadowNode(shadowNode, runtime); + auto instanceHandle = (*shadowNode).getInstanceHandle(runtime); if (!instanceHandle.isNull()) { nonNullInstanceHandles.push_back(std::move(instanceHandle)); } diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/Memory/RCTMemoryUtils.h b/packages/react-native/ReactCommon/react/test_utils/ios/Memory/RCTMemoryUtils.h new file mode 100644 index 00000000000000..e9110fa59e5456 --- /dev/null +++ b/packages/react-native/ReactCommon/react/test_utils/ios/Memory/RCTMemoryUtils.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#import + +#import + +RCT_EXTERN_C_BEGIN + +int RCTGetRetainCount(id _Nullable object); + +void RCTAutoReleasePoolPush(void); +void RCTAutoReleasePoolPop(void); + +RCT_EXTERN_C_END diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/Memory/RCTMemoryUtils.m b/packages/react-native/ReactCommon/react/test_utils/ios/Memory/RCTMemoryUtils.m new file mode 100644 index 00000000000000..22f41fec29fea8 --- /dev/null +++ b/packages/react-native/ReactCommon/react/test_utils/ios/Memory/RCTMemoryUtils.m @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#import "RCTMemoryUtils.h" + +int RCTGetRetainCount(id _Nullable object) +{ + return object != nil ? CFGetRetainCount((__bridge CFTypeRef)object) - 1 : 0; +} + +OBJC_EXPORT +void *objc_autoreleasePoolPush(void) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + +OBJC_EXPORT +void objc_autoreleasePoolPop(void *context) __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + +static NSString *const kAutoreleasePoolContextStackKey = @"autorelease_pool_context_stack"; + +void RCTAutoReleasePoolPush(void) +{ + assert([NSThread isMainThread]); + NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary]; + void *context = objc_autoreleasePoolPush(); + NSMutableArray *contextStack = dictionary[kAutoreleasePoolContextStackKey]; + if (!contextStack) { + contextStack = [NSMutableArray array]; + dictionary[kAutoreleasePoolContextStackKey] = contextStack; + } + [contextStack addObject:[NSValue valueWithPointer:context]]; +} + +void RCTAutoReleasePoolPop(void) +{ + assert([NSThread isMainThread]); + NSMutableDictionary *dictionary = [[NSThread currentThread] threadDictionary]; + NSMutableArray *contextStack = dictionary[kAutoreleasePoolContextStackKey]; + assert(contextStack.count > 0); + NSValue *lastContext = contextStack.lastObject; + [contextStack removeLastObject]; + objc_autoreleasePoolPop(lastContext.pointerValue); +} diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/RCTSwizzleHelpers.h b/packages/react-native/ReactCommon/react/test_utils/ios/RCTSwizzleHelpers.h new file mode 100644 index 00000000000000..79ca15d2a6558d --- /dev/null +++ b/packages/react-native/ReactCommon/react/test_utils/ios/RCTSwizzleHelpers.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#import + +#import + +RCT_EXTERN_C_BEGIN + +void RCTSwizzleInstanceSelector( + Class targetClass, + Class swizzleClass, + SEL selector); + +RCT_EXTERN_C_END diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/RCTSwizzleHelpers.m b/packages/react-native/ReactCommon/react/test_utils/ios/RCTSwizzleHelpers.m new file mode 100644 index 00000000000000..989a3474b73b27 --- /dev/null +++ b/packages/react-native/ReactCommon/react/test_utils/ios/RCTSwizzleHelpers.m @@ -0,0 +1,17 @@ +/* + * 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. + */ + +#import "RCTSwizzleHelpers.h" + +#import + +void RCTSwizzleInstanceSelector(Class targetClass, Class swizzleClass, SEL selector) +{ + Method originalMethod = class_getInstanceMethod(targetClass, selector); + Method swizzleMethod = class_getInstanceMethod(swizzleClass, selector); + method_exchangeImplementations(originalMethod, swizzleMethod); +} diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.h b/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.h new file mode 100644 index 00000000000000..e11e88c560a72a --- /dev/null +++ b/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#import + +@interface ShimRCTInstance : NSObject + +@property int initCount; +@property int invalidateCount; + +@property NSString *jsModuleName; +@property NSString *method; +@property NSArray *args; + +- (void)reset; + +@end diff --git a/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm b/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm new file mode 100644 index 00000000000000..77476f9dfd3d52 --- /dev/null +++ b/packages/react-native/ReactCommon/react/test_utils/ios/Shims/ShimRCTInstance.mm @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#import "ShimRCTInstance.h" + +#import + +#import "RCTSwizzleHelpers.h" + +static __weak ShimRCTInstance *weakShim = nil; + +@implementation ShimRCTInstance + +- (instancetype)init +{ + if (self = [super init]) { + _initCount = 0; + RCTSwizzleInstanceSelector( + [RCTInstance class], + [ShimRCTInstance class], + @selector(initWithDelegate: + jsEngineInstance:bundleManager:turboModuleManagerDelegate:onInitialBundleLoad:moduleRegistry:)); + RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate)); + RCTSwizzleInstanceSelector( + [RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:)); + weakShim = self; + } + return self; +} + +- (void)reset +{ + RCTSwizzleInstanceSelector( + [RCTInstance class], + [ShimRCTInstance class], + @selector(initWithDelegate: + jsEngineInstance:bundleManager:turboModuleManagerDelegate:onInitialBundleLoad:moduleRegistry:)); + RCTSwizzleInstanceSelector([RCTInstance class], [ShimRCTInstance class], @selector(invalidate)); + RCTSwizzleInstanceSelector( + [RCTInstance class], [ShimRCTInstance class], @selector(callFunctionOnJSModule:method:args:)); + _initCount = 0; + _invalidateCount = 0; +} + +- (instancetype)initWithDelegate:(id)delegate + jsEngineInstance:(std::shared_ptr)jsEngineInstance + bundleManager:(RCTBundleManager *)bundleManager + turboModuleManagerDelegate:(id)tmmDelegate + onInitialBundleLoad:(RCTInstanceInitialBundleLoadCompletionBlock)onInitialBundleLoad + moduleRegistry:(RCTModuleRegistry *)moduleRegistry +{ + weakShim.initCount++; + return self; +} + +- (void)invalidate +{ + weakShim.invalidateCount++; +} + +- (void)callFunctionOnJSModule:(NSString *)moduleName method:(NSString *)method args:(NSArray *)args +{ + weakShim.jsModuleName = moduleName; + weakShim.method = method; + weakShim.args = [args copy]; +} + +@end diff --git a/packages/react-native/ReactCommon/react/test_utils/shadowTreeGeneration.h b/packages/react-native/ReactCommon/react/test_utils/shadowTreeGeneration.h index 65307403d8e649..da8aa09e1682bc 100644 --- a/packages/react-native/ReactCommon/react/test_utils/shadowTreeGeneration.h +++ b/packages/react-native/ReactCommon/react/test_utils/shadowTreeGeneration.h @@ -290,7 +290,7 @@ static inline ShadowNode::Shared generateShadowNodeTree( int deviation = 3) { if (size <= 1) { auto family = componentDescriptor.createFamily( - {generateReactTag(), SurfaceId(1), nullptr}, nullptr); + {generateReactTag(), SurfaceId(1), nullptr}); return componentDescriptor.createShadowNode( ShadowNodeFragment{generateDefaultProps(componentDescriptor)}, family); } @@ -306,7 +306,7 @@ static inline ShadowNode::Shared generateShadowNodeTree( } auto family = componentDescriptor.createFamily( - {generateReactTag(), SurfaceId(1), nullptr}, nullptr); + {generateReactTag(), SurfaceId(1), nullptr}); return componentDescriptor.createShadowNode( ShadowNodeFragment{ generateDefaultProps(componentDescriptor), diff --git a/packages/react-native/ReactCommon/react/utils/React-utils.podspec b/packages/react-native/ReactCommon/react/utils/React-utils.podspec new file mode 100644 index 00000000000000..d262c559241052 --- /dev/null +++ b/packages/react-native/ReactCommon/react/utils/React-utils.podspec @@ -0,0 +1,57 @@ +# 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. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' +folly_version = '2021.07.22.00' + +header_search_paths = [ + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"$(PODS_TARGET_SRCROOT)\"", + "\"$(PODS_TARGET_SRCROOT)/ReactCommon\"", +] + +if ENV["USE_FRAMEWORKS"] + header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\"" +end + +Pod::Spec.new do |s| + s.name = "React-utils" + s.version = version + s.summary = "-" # TODO + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = { :ios => min_ios_version_supported } + s.source = source + s.source_files = "**/*.{cpp,h,mm}" + s.compiler_flags = folly_compiler_flags + s.header_dir = "react/utils" + s.exclude_files = "tests" + s.pod_target_xcconfig = { + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + "HEADER_SEARCH_PATHS" => header_search_paths.join(' ')} + + if ENV['USE_FRAMEWORKS'] + s.module_name = "React_utils" + s.header_mappings_dir = "../.." + end + + s.dependency "RCT-Folly", folly_version + s.dependency "React-debug" + s.dependency "glog" +end diff --git a/packages/react-native/cli.js b/packages/react-native/cli.js index 301a51241fbeae..451c0860fc0bf0 100755 --- a/packages/react-native/cli.js +++ b/packages/react-native/cli.js @@ -56,10 +56,10 @@ async function main() { if (latest !== currentVersion) { const msg = ` ${chalk.bold.yellow('WARNING:')} You should run ${chalk.white.bold( - 'npx react-native@latest', - )} to ensure you're always using the most current version of the CLI. NPX has cached version (${chalk.bold.yellow( - currentVersion, - )}) != current release (${chalk.bold.green(latest)}) + 'npx react-native@latest', + )} to ensure you're always using the most current version of the CLI. NPX has cached version (${chalk.bold.yellow( + currentVersion, + )}) != current release (${chalk.bold.green(latest)}) `; console.warn(msg); } diff --git a/packages/react-native/flow/jest.js b/packages/react-native/flow/jest.js index 5f42ea1358f404..505dd3fc1638e2 100644 --- a/packages/react-native/flow/jest.js +++ b/packages/react-native/flow/jest.js @@ -38,6 +38,10 @@ type JestMockFn, TReturn> = { * instantiated from this mock function. */ instances: Array, + /** + * An array that contains the contexts for all calls of the mock function. + */ + contexts: Array, /** * An array that contains all the object results that have been * returned by this mock function call diff --git a/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb b/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb index b738548d5cf945..297ed18bca59bb 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/codegen_utils-test.rb @@ -578,7 +578,7 @@ def get_podspec_fabric_and_script_phases(script_phases) 'React-rncore': [], 'React-Fabric': [], 'React-debug': [], - + 'React-utils': [], }) specs[:'script_phases'] = script_phases @@ -590,12 +590,13 @@ def get_podspec_when_use_frameworks specs = get_podspec_no_fabric_no_script() specs["pod_target_xcconfig"]["FRAMEWORK_SEARCH_PATHS"].concat([]) - specs["pod_target_xcconfig"]["HEADER_SEARCH_PATHS"].concat(" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_TARGET_SRCROOT)\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-Fabric/React_Fabric.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-FabricImage/React_FabricImage.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-graphics/React_graphics.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"$(PODS_CONFIGURATION_BUILD_DIR)/ReactCommon/ReactCommon.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-RCTFabric/RCTFabric.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\"") + specs["pod_target_xcconfig"]["HEADER_SEARCH_PATHS"].concat(" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_TARGET_SRCROOT)\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-Fabric/React_Fabric.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-FabricImage/React_FabricImage.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-graphics/React_graphics.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"$(PODS_CONFIGURATION_BUILD_DIR)/ReactCommon/ReactCommon.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-RCTFabric/RCTFabric.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"") specs[:dependencies].merge!({ 'React-graphics': [], 'React-Fabric': [], 'React-debug': [], + 'React-utils': [], }) return specs diff --git a/packages/react-native/scripts/cocoapods/__tests__/flipper-test.rb b/packages/react-native/scripts/cocoapods/__tests__/flipper-test.rb index 2c0659ecc4cd13..12de576beb3681 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/flipper-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/flipper-test.rb @@ -22,7 +22,7 @@ def test_installFlipperDependencies_installDependencies # Assert assert_equal($podInvocationCount, 1) - assert_equal($podInvocation['React-Core/DevSupport'][:path], "../../" ) + assert_equal($podInvocation['React-Core/DevSupport'][:path], "../../") end # ======================= # diff --git a/packages/react-native/scripts/cocoapods/__tests__/new_architecture-test.rb b/packages/react-native/scripts/cocoapods/__tests__/new_architecture-test.rb index 702d02982c2c4b..1f0c04c216a168 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/new_architecture-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/new_architecture-test.rb @@ -129,7 +129,7 @@ def test_installModulesDependencies_whenNewArchEnabledAndNewArchAndNoSearchPaths # Assert assert_equal(spec.compiler_flags, NewArchitectureHelper.folly_compiler_flags) - assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\"") + assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"") assert_equal(spec.pod_target_xcconfig["CLANG_CXX_LANGUAGE_STANDARD"], "c++17") assert_equal(spec.pod_target_xcconfig["OTHER_CPLUSPLUSFLAGS"], "$(inherited) -DRCT_NEW_ARCH_ENABLED=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1") assert_equal( @@ -149,6 +149,7 @@ def test_installModulesDependencies_whenNewArchEnabledAndNewArchAndNoSearchPaths { :dependency_name => "React-Fabric" }, { :dependency_name => "React-graphics" }, { :dependency_name => "React-debug" }, + { :dependency_name => "React-utils" }, { :dependency_name => "hermes-engine" } ]) end @@ -167,7 +168,7 @@ def test_installModulesDependencies_whenNewArchDisabledAndSearchPathsAndCompiler # Assert assert_equal(spec.compiler_flags, "-Wno-nullability-completeness #{NewArchitectureHelper.folly_compiler_flags}") - assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "#{other_flags} \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\"") + assert_equal(spec.pod_target_xcconfig["HEADER_SEARCH_PATHS"], "#{other_flags} \"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Private/Yoga\" \"$(PODS_ROOT)/DoubleConversion\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-FabricImage/React_FabricImage.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"") assert_equal(spec.pod_target_xcconfig["CLANG_CXX_LANGUAGE_STANDARD"], "c++17") assert_equal( spec.dependencies, diff --git a/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb b/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb index 954630097e25a0..82209f2dc1d49e 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb @@ -174,6 +174,47 @@ def test_hasPod_whenInstallerHasPod_returnTrue assert_equal(result, true) end + # ======================================================== # + # Test - Set GCC Preprocessor Definition for React-hermes # + # ======================================================== # + + def test_SetGCCPreprocessorDefinitionForHermes_itSetsThePreprocessorForDebug + # Arrange + react_hermes_name = "React-hermes" + react_core_name = "React-Core" + hermes_engine_name = "hermes-engine" + react_hermes_debug_config = BuildConfigurationMock.new("Debug") + react_hermes_release_config = BuildConfigurationMock.new("Release") + react_core_debug_config = BuildConfigurationMock.new("Debug") + react_core_release_config = BuildConfigurationMock.new("Release") + hermes_engine_debug_config = BuildConfigurationMock.new("Debug") + hermes_engine_release_config = BuildConfigurationMock.new("Release") + react_hermes_target = TargetMock.new(react_hermes_name, [react_hermes_debug_config, react_hermes_release_config]) + react_core_target = TargetMock.new(react_core_name, [react_core_debug_config, react_core_release_config]) + hermes_engine_target = TargetMock.new(hermes_engine_name, [hermes_engine_debug_config, hermes_engine_release_config]) + + installer = InstallerMock.new( + :pod_target_installation_results => { + react_hermes_name => TargetInstallationResultMock.new(react_hermes_target, react_hermes_target), + react_core_name => TargetInstallationResultMock.new(react_core_target, react_core_target), + hermes_engine_name => TargetInstallationResultMock.new(hermes_engine_target, hermes_engine_target), + } + ) + + # Act + ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer) + + # Assert + build_setting = "GCC_PREPROCESSOR_DEFINITIONS" + expected_value = "$(inherited) HERMES_ENABLE_DEBUGGER=1" + assert_equal(expected_value, react_hermes_debug_config.build_settings[build_setting]) + assert_nil(react_hermes_release_config.build_settings[build_setting]) + assert_nil(react_core_debug_config.build_settings[build_setting]) + assert_nil(react_core_release_config.build_settings[build_setting]) + assert_equal(expected_value, hermes_engine_debug_config.build_settings[build_setting]) + assert_nil(hermes_engine_release_config.build_settings[build_setting]) + end + # ============================ # # Test - Exclude Architectures # # ============================ # @@ -434,6 +475,60 @@ def test_applyMacCatalystPatches_correctlyAppliesNecessaryPatches assert_equal(user_project_mock.save_invocation_count, 1) end + # ================================= # + # Test - Apply Xcode 15 Patch # + # ================================= # + + def test_applyXcode15Patch_correctlyAppliesNecessaryPatch + # Arrange + first_target = prepare_target("FirstTarget") + second_target = prepare_target("SecondTarget") + third_target = TargetMock.new("ThirdTarget", [ + BuildConfigurationMock.new("Debug", { + "GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" ' + }), + BuildConfigurationMock.new("Release", { + "GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" ' + }), + ], nil) + + user_project_mock = UserProjectMock.new("a/path", [ + prepare_config("Debug"), + prepare_config("Release"), + ], + :native_targets => [ + first_target, + second_target + ] + ) + pods_projects_mock = PodsProjectMock.new([], {"hermes-engine" => {}}, :native_targets => [ + third_target + ]) + installer = InstallerMock.new(pods_projects_mock, [ + AggregatedProjectMock.new(user_project_mock) + ]) + + # Act + ReactNativePodsUtils.apply_xcode_15_patch(installer) + + # Assert + first_target.build_configurations.each do |config| + assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip, + '$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"' + ) + end + second_target.build_configurations.each do |config| + assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip, + '$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"' + ) + end + third_target.build_configurations.each do |config| + assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip, + '$(inherited) "SomeFlag=1" "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"' + ) + end + end + # ==================================== # # Test - Set Node_Modules User Setting # # ==================================== # diff --git a/packages/react-native/scripts/cocoapods/codegen_utils.rb b/packages/react-native/scripts/cocoapods/codegen_utils.rb index 97715206cf5e17..1ea549fa094961 100644 --- a/packages/react-native/scripts/cocoapods/codegen_utils.rb +++ b/packages/react-native/scripts/cocoapods/codegen_utils.rb @@ -101,6 +101,7 @@ def get_react_codegen_spec(package_json_file, folly_version: '2021.07.22.00', fa "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-NativeModulesApple/React_NativeModulesApple.framework/Headers\"", "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-RCTFabric/RCTFabric.framework/Headers\"", "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"" ]) end @@ -142,6 +143,7 @@ def get_react_codegen_spec(package_json_file, folly_version: '2021.07.22.00', fa 'React-graphics': [], 'React-Fabric': [], 'React-debug': [], + 'React-utils': [], }); end diff --git a/packages/react-native/scripts/cocoapods/flipper.rb b/packages/react-native/scripts/cocoapods/flipper.rb index 73371c1df603b1..7206161e568560 100644 --- a/packages/react-native/scripts/cocoapods/flipper.rb +++ b/packages/react-native/scripts/cocoapods/flipper.rb @@ -19,7 +19,6 @@ # This function installs the `React-Core/DevSupport` subpods # when the dependencies are installed for a non production app. # -# @parameter production: a boolean that indicates whether we are in production or not. # @parameter pathToReactNative: the path to the React Native installation def install_flipper_dependencies(pathToReactNative) pod 'React-Core/DevSupport', :path => "#{pathToReactNative}/" diff --git a/packages/react-native/scripts/cocoapods/new_architecture.rb b/packages/react-native/scripts/cocoapods/new_architecture.rb index 3b128b01d29d3d..b9a51834c91269 100644 --- a/packages/react-native/scripts/cocoapods/new_architecture.rb +++ b/packages/react-native/scripts/cocoapods/new_architecture.rb @@ -110,6 +110,7 @@ def self.install_modules_dependencies(spec, new_arch_enabled, folly_version) header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\"" header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\"" header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-debug/React_debug.framework/Headers\"" + header_search_paths << "\"${PODS_CONFIGURATION_BUILD_DIR}/React-utils/React_utils.framework/Headers\"" end header_search_paths_string = header_search_paths.join(" ") spec.compiler_flags = compiler_flags.empty? ? @@folly_compiler_flags : "#{compiler_flags} #{@@folly_compiler_flags}" @@ -137,6 +138,7 @@ def self.install_modules_dependencies(spec, new_arch_enabled, folly_version) spec.dependency "React-Fabric" spec.dependency "React-graphics" spec.dependency "React-debug" + spec.dependency "React-utils" if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1" spec.dependency "hermes-engine" diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index 2bed5260603e25..fc823b54e41393 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -39,6 +39,11 @@ def self.has_pod(installer, name) installer.pods_project.pod_group(name) != nil end + def self.set_gcc_preprocessor_definition_for_React_hermes(installer) + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", "Debug") + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "hermes-engine", "Debug") + end + def self.turn_off_resource_bundle_react_core(installer) # this is needed for Xcode 14, see more details here https://github.com/facebook/react-native/issues/34673 # we should be able to remove this once CocoaPods catches up to it, see more details here https://github.com/CocoaPods/CocoaPods/issues/11402 @@ -121,6 +126,18 @@ def self.apply_mac_catalyst_patches(installer) end end + def self.apply_xcode_15_patch(installer) + installer.target_installation_results.pod_target_installation_results + .each do |pod_name, target_installation_result| + target_installation_result.native_target.build_configurations.each do |config| + # unary_function and binary_function are no longer provided in C++17 and newer standard modes as part of Xcode 15. They can be re-enabled with setting _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION + # Ref: https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Deprecations + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= '$(inherited) ' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '"_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION" ' + end + end + end + def self.apply_flags_for_fabric(installer, fabric_enabled: false) fabric_flag = "-DRN_FABRIC_ENABLED" if fabric_enabled @@ -132,6 +149,19 @@ def self.apply_flags_for_fabric(installer, fabric_enabled: false) private + def self.add_build_settings_to_pod(installer, settings_name, settings_value, target_pod_name, configuration) + installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result| + if pod_name.to_s == target_pod_name + target_installation_result.native_target.build_configurations.each do |config| + if configuration == nil || (configuration != nil && configuration == config.name) + config.build_settings[settings_name] ||= '$(inherited) ' + config.build_settings[settings_name] << settings_value + end + end + end + end + end + def self.fix_library_search_path(config) lib_search_paths = config.build_settings["LIBRARY_SEARCH_PATHS"] diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index cba10765767e47..40d5ee3b2b2579 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -57,7 +57,7 @@ def prepare_react_native_project! # - path: path to react_native installation. # - fabric_enabled: whether fabric should be enabled or not. # - new_arch_enabled: whether the new architecture should be enabled or not. -# - production: whether the dependencies must be installed to target a Debug or a Release build. +# - :production [DEPRECATED] whether the dependencies must be installed to target a Debug or a Release build. # - hermes_enabled: whether Hermes should be enabled or not. # - flipper_configuration: The configuration to use for flipper. # - app_path: path to the React Native app. Required by the New Architecture. @@ -67,7 +67,7 @@ def use_react_native! ( path: "../node_modules/react-native", fabric_enabled: false, new_arch_enabled: ENV['RCT_NEW_ARCH_ENABLED'] == '1', - production: ENV['PRODUCTION'] == '1', + production: false, # deprecated hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true, flipper_configuration: FlipperConfiguration.disabled, app_path: '..', @@ -118,6 +118,7 @@ def use_react_native! ( pod 'React-rncore', :path => "#{prefix}/ReactCommon" pod 'React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact" pod 'React-debug', :path => "#{prefix}/ReactCommon/react/debug" + pod 'React-utils', :path => "#{prefix}/ReactCommon/react/utils" if hermes_enabled setup_hermes!(:react_native_path => prefix, :fabric_enabled => fabric_enabled) @@ -164,11 +165,8 @@ def use_react_native! ( build_codegen!(prefix, relative_installation_root) end - # CocoaPods `configurations` option ensures that the target is copied only for the specified configurations, - # but those dependencies are still built. - # Flipper doesn't currently compile for release https://github.com/facebook/react-native/issues/33764 - # Setting the production flag to true when build for production make sure that we don't install Flipper in the app in the first place. - if flipper_configuration.flipper_enabled && !production + # Flipper now build in Release mode but it is not linked to the Release binary (as specified by the Configuration option) + if flipper_configuration.flipper_enabled install_flipper_dependencies(prefix) use_flipper_pods(flipper_configuration.versions, :configurations => flipper_configuration.configurations) end @@ -237,11 +235,16 @@ def react_native_post_install( fabric_enabled = ReactNativePodsUtils.has_pod(installer, 'React-Fabric') + if ReactNativePodsUtils.has_pod(installer, "React-hermes") + ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer) + end + ReactNativePodsUtils.exclude_i386_architecture_while_using_hermes(installer) ReactNativePodsUtils.fix_library_search_paths(installer) ReactNativePodsUtils.update_search_paths(installer) ReactNativePodsUtils.set_node_modules_user_settings(installer, react_native_path) ReactNativePodsUtils.apply_flags_for_fabric(installer, fabric_enabled: fabric_enabled) + ReactNativePodsUtils.apply_xcode_15_patch(installer) NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer) is_new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == "1" diff --git a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec index 81112367d81f2f..681cb2c4d234a6 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec +++ b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec @@ -8,9 +8,6 @@ require_relative "./hermes-utils.rb" react_native_path = File.join(__dir__, "..", "..") -# Whether Hermes is built for Release or Debug is determined by the PRODUCTION envvar. -build_type = ENV['PRODUCTION'] == "1" ? :release : :debug - # package.json package = JSON.parse(File.read(File.join(react_native_path, "package.json"))) version = package['version'] @@ -23,7 +20,7 @@ git = "https://github.com/facebook/hermes.git" abort_if_invalid_tarball_provided! -source = compute_hermes_source(build_from_source, hermestag_file, git, version, build_type, react_native_path) +source = compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path) Pod::Spec.new do |spec| spec.name = "hermes-engine" @@ -42,7 +39,7 @@ Pod::Spec.new do |spec| spec.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", "CLANG_CXX_LIBRARY" => "compiler-default" - }.merge!(build_type == :debug ? { "GCC_PREPROCESSOR_DEFINITIONS" => "HERMES_ENABLE_DEBUGGER=1" } : {}) + } spec.ios.vendored_frameworks = "destroot/Library/Frameworks/ios/hermes.framework" spec.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework" @@ -50,7 +47,7 @@ Pod::Spec.new do |spec| if source[:http] then spec.subspec 'Pre-built' do |ss| - ss.preserve_paths = ["destroot/bin/*"].concat(build_type == :debug ? ["**/*.{h,c,cpp}"] : []) + ss.preserve_paths = ["destroot/bin/*"].concat(["**/*.{h,c,cpp}"]) ss.source_files = "destroot/include/**/*.h" ss.exclude_files = ["destroot/include/jsi/jsi/JSIDynamic.{h,cpp}", "destroot/include/jsi/jsi/jsilib-*.{h,cpp}"] ss.header_mappings_dir = "destroot/include" @@ -58,6 +55,24 @@ Pod::Spec.new do |spec| ss.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework" end + + # Right now, even reinstalling pods with the PRODUCTION flag turned on, does not change the version of hermes that is downloaded + # To remove the PRODUCTION flag, we want to download the right version of hermes on the flight + # we do so in a pre-build script we invoke from the Xcode build pipeline + # We use this only for Apps created using the template. RNTester and Nightlies should not be used to build for Release. + # We ignore this if we provide a specific tarball: the assumption here is that if you are providing a tarball, is because you want to + # test something specific for that tarball. + if source[:http].include?('https://repo1.maven.org/') + spec.script_phase = { + :name => "[Hermes] Replace Hermes for the right configuration, if needed", + :execution_position => :before_compile, + :script => <<-EOS + . "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh" + "$NODE_BINARY" "$REACT_NATIVE_PATH/sdks/hermes-engine/utils/replace_hermes_version.js" -c "$CONFIGURATION" -r "#{version}" -p "$REACT_NATIVE_PATH" + EOS + } + end + elsif source[:git] then spec.subspec 'Hermes' do |ss| @@ -90,13 +105,13 @@ Pod::Spec.new do |spec| spec.prepare_command = ". #{react_native_path}/sdks/hermes-engine/utils/create-dummy-hermes-xcframework.sh" - CMAKE_BINARY = %x(command -v cmake | tr -d '\n') + CMAKE_BINARY = Pod::Executable::which!('cmake') # NOTE: Script phases are sorted alphabetically inside Xcode project spec.script_phases = [ { :name => '[RN] [1] Build Hermesc', :script => <<-EOS - . ${PODS_ROOT}/../.xcode.env + . "${REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh" export CMAKE_BINARY=${CMAKE_BINARY:-#{CMAKE_BINARY}} . ${REACT_NATIVE_PATH}/sdks/hermes-engine/utils/build-hermesc-xcode.sh #{hermesc_path} EOS @@ -104,7 +119,7 @@ Pod::Spec.new do |spec| { :name => '[RN] [2] Build Hermes', :script => <<-EOS - . ${PODS_ROOT}/../.xcode.env + . "${REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh" export CMAKE_BINARY=${CMAKE_BINARY:-#{CMAKE_BINARY}} . ${REACT_NATIVE_PATH}/sdks/hermes-engine/utils/build-hermes-xcode.sh #{version} #{hermesc_path}/ImportHermesc.cmake EOS diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index 26abc7b70761fb..1d3f501d9d3910 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -30,7 +30,7 @@ def abort_if_invalid_tarball_provided!() # - react_native_path: path to react native # # Returns: a properly configured source object -def compute_hermes_source(build_from_source, hermestag_file, git, version, build_type, react_native_path) +def compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path) source = {} if ENV.has_key?('HERMES_ENGINE_TARBALL_PATH') @@ -43,8 +43,10 @@ def compute_hermes_source(build_from_source, hermestag_file, git, version, build else build_hermes_from_source(source, git) end - elsif hermes_artifact_exists(release_tarball_url(version, build_type)) - use_release_tarball(source, version, build_type) + elsif hermes_artifact_exists(release_tarball_url(version, :debug)) + use_release_tarball(source, version, :debug) + download_stable_hermes(react_native_path, version, :debug) + download_stable_hermes(react_native_path, version, :release) elsif hermes_artifact_exists(nightly_tarball_url(version).gsub("\\", "")) use_nightly_tarball(source, react_native_path, version) else @@ -100,6 +102,25 @@ def putsIfPodPresent(message, level = 'warning') end end +def download_stable_hermes(react_native_path, version, configuration) + tarball_url = release_tarball_url(version, configuration) + download_hermes_tarball(react_native_path, tarball_url, version, configuration) +end + +def download_hermes_tarball(react_native_path, tarball_url, version, configuration) + destination_folder = "#{react_native_path}/sdks/downloads" + destination_path = configuration == nil ? + "#{destination_folder}/hermes-ios-#{version}.tar.gz" : + "#{destination_folder}/hermes-ios-#{version}-#{configuration}.tar.gz" + + unless File.exist?(destination_path) + # Download to a temporary file first so we don't cache incomplete downloads. + tmp_file = "#{destination_folder}/hermes-ios.download" + `mkdir -p "#{destination_folder}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` + end + return destination_path +end + # This function downloads the nightly prebuilt version of Hermes based on the passed version # and save it in the node_module/react_native/sdks/downloads folder # It then returns the path to the hermes tarball @@ -110,16 +131,7 @@ def putsIfPodPresent(message, level = 'warning') # Returns: the path to the downloaded Hermes tarball def download_nightly_hermes(react_native_path, version) tarball_url = nightly_tarball_url(version) - - destination_folder = "#{react_native_path}/sdks/downloads" - destination_path = "#{destination_folder}/hermes-ios-#{version}.tar.gz" - - unless File.exist?(destination_path) - # Download to a temporary file first so we don't cache incomplete downloads. - tmp_file = "#{destination_folder}/hermes-ios.download" - `mkdir -p "#{destination_folder}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` - end - return destination_path + return download_stable_hermes(react_native_path, tarball_url, version, nil) end def nightly_tarball_url(version) diff --git a/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh b/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh index aab75e92ebfb22..429707c356c9f0 100755 --- a/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh +++ b/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh @@ -52,13 +52,8 @@ function build_host_hermesc { # Utility function to configure an Apple framework function configure_apple_framework { - local enable_bitcode enable_debugger cmake_build_type + local enable_debugger cmake_build_type - if [[ $1 == iphoneos || $1 == catalyst ]]; then - enable_bitcode="true" - else - enable_bitcode="false" - fi if [[ $BUILD_TYPE == "Debug" ]]; then enable_debugger="true" else @@ -82,7 +77,7 @@ function configure_apple_framework { -DHERMES_ENABLE_LIBFUZZER:BOOLEAN=false \ -DHERMES_ENABLE_FUZZILLI:BOOLEAN=false \ -DHERMES_ENABLE_TEST_SUITE:BOOLEAN=false \ - -DHERMES_ENABLE_BITCODE:BOOLEAN="$enable_bitcode" \ + -DHERMES_ENABLE_BITCODE:BOOLEAN=false \ -DHERMES_BUILD_APPLE_FRAMEWORK:BOOLEAN=true \ -DHERMES_BUILD_APPLE_DSYM:BOOLEAN=true \ -DIMPORT_HERMESC:PATH="$IMPORT_HERMESC_PATH" \ diff --git a/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js b/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js new file mode 100644 index 00000000000000..ce1bb48f542bd8 --- /dev/null +++ b/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js @@ -0,0 +1,106 @@ +/** + * 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 yargs = require('yargs'); +const fs = require('fs'); +const {execSync} = require('child_process'); + +const LAST_BUILD_FILENAME = '.last_build_configuration'; + +function validateBuildConfiguration(configuration) { + if (!['Debug', 'Release'].includes(configuration)) { + throw new Error(`Invalid configuration ${configuration}`); + } +} + +function validateVersion(version) { + if (version == null || version === '') { + throw new Error('Version cannot be empty'); + } +} + +function shouldReplaceHermesConfiguration(configuration) { + const fileExists = fs.existsSync(LAST_BUILD_FILENAME); + + if (fileExists) { + console.log(`Found ${LAST_BUILD_FILENAME} file`); + const oldConfiguration = fs.readFileSync(LAST_BUILD_FILENAME).toString(); + if (oldConfiguration === configuration) { + console.log( + 'Same config of the previous build. No need to replace Hermes engine', + ); + return false; + } + } + + // Assumption: if there is no stored last build, we assume that it was build for debug. + if (!fileExists && configuration === 'Debug') { + console.log( + 'No previous build detected, but Debug Configuration. No need to replace Hermes engine', + ); + return false; + } + + return true; +} + +function replaceHermesConfiguration(configuration, version, reactNativePath) { + const tarballURLPath = `${reactNativePath}/sdks/downloads/hermes-ios-${version}-${configuration}.tar.gz`; + + const finalLocation = 'hermes-engine'; + console.log('Preparing the final location'); + fs.rmSync(finalLocation, {force: true, recursive: true}); + fs.mkdirSync(finalLocation, {recursive: true}); + + console.log('Extracting the tarball'); + execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`); +} + +function updateLastBuildConfiguration(configuration) { + fs.writeFileSync(LAST_BUILD_FILENAME, configuration); +} + +function main(configuration, version, reactNativePath) { + validateBuildConfiguration(configuration); + validateVersion(version); + + if (!shouldReplaceHermesConfiguration(configuration)) { + return; + } + + replaceHermesConfiguration(configuration, version, reactNativePath); + updateLastBuildConfiguration(configuration); + console.log('Done replacing hermes-engine'); +} + +// This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok +const argv = yargs + .option('c', { + alias: 'configuration', + description: + 'Configuration to use to download the right Hermes version. Allowed values are "Debug" and "Release".', + }) + .option('r', { + alias: 'reactNativeVersion', + description: + 'The Version of React Native associated with the Hermes tarball.', + }) + .option('p', { + alias: 'reactNativePath', + description: 'The path to the React Native root folder', + }) + .usage('Usage: $0 -c Debug -r -p ').argv; + +const configuration = argv.configuration; +const version = argv.reactNativeVersion; +const reactNativePath = argv.reactNativePath; + +main(configuration, version, reactNativePath); diff --git a/packages/react-native/template/README.md b/packages/react-native/template/README.md new file mode 100644 index 00000000000000..12470c30ecb5a2 --- /dev/null +++ b/packages/react-native/template/README.md @@ -0,0 +1,79 @@ +This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). + +# Getting Started + +>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. + +## Step 1: Start the Metro Server + +First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. + +To start Metro, run the following command from the _root_ of your React Native project: + +```bash +# using npm +npm start + +# OR using Yarn +yarn start +``` + +## Step 2: Start your Application + +Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: + +### For Android + +```bash +# using npm +npm run android + +# OR using Yarn +yarn android +``` + +### For iOS + +```bash +# using npm +npm run ios + +# OR using Yarn +yarn ios +``` + +If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. + +This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. + +## Step 3: Modifying your App + +Now that you have successfully run the app, let's modify it. + +1. Open `App.tsx` in your text editor of choice and edit some lines. +2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! + + For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! + +## Congratulations! :tada: + +You've successfully run and modified your React Native App. :partying_face: + +### Now what? + +- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). +- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). + +# Troubleshooting + +If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. + +# Learn More + +To learn more about React Native, take a look at the following resources: + +- [React Native Website](https://reactnative.dev) - learn more about React Native. +- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. +- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. +- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. +- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. diff --git a/packages/react-native/template/android/app/src/debug/AndroidManifest.xml b/packages/react-native/template/android/app/src/debug/AndroidManifest.xml index 4b185bc1597eb7..eb98c01afd79a6 100644 --- a/packages/react-native/template/android/app/src/debug/AndroidManifest.xml +++ b/packages/react-native/template/android/app/src/debug/AndroidManifest.xml @@ -2,12 +2,8 @@ - - - - + tools:ignore="GoogleAppIndexingWarning"/> diff --git a/packages/rn-tester/Podfile b/packages/rn-tester/Podfile index e4e35975def76c..cf6b7f05d24d6d 100644 --- a/packages/rn-tester/Podfile +++ b/packages/rn-tester/Podfile @@ -38,7 +38,7 @@ def pods(target_name, options = {}, use_flipper: !IN_CI && !USE_FRAMEWORKS) flipper_configuration: use_flipper ? FlipperConfiguration.enabled : FlipperConfiguration.disabled, app_path: "#{Dir.pwd}", config_file_dir: "#{Dir.pwd}/node_modules", - production: !ENV['PRODUCTION'].nil?, + production: false, #deprecated ios_folder: '.', ) pod 'ReactCommon-Samples', :path => "#{@prefix_path}/ReactCommon/react/nativemodule/samples" diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index a9132e6b3dd66c..b595f3ccdc18c0 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -137,6 +137,7 @@ PODS: - React-jsiexecutor - React-NativeModulesApple - React-rncore + - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - React-Core (1000.0.0): @@ -149,6 +150,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/CoreModulesHeaders (1000.0.0): @@ -161,6 +163,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/Default (1000.0.0): @@ -172,6 +175,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/DevSupport (1000.0.0): @@ -186,6 +190,7 @@ PODS: - React-jsiexecutor (= 1000.0.0) - React-jsinspector (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTActionSheetHeaders (1000.0.0): @@ -198,6 +203,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTAnimationHeaders (1000.0.0): @@ -210,6 +216,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTBlobHeaders (1000.0.0): @@ -222,6 +229,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTImageHeaders (1000.0.0): @@ -234,6 +242,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTLinkingHeaders (1000.0.0): @@ -246,6 +255,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTNetworkHeaders (1000.0.0): @@ -258,6 +268,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTPushNotificationHeaders (1000.0.0): @@ -270,6 +281,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTSettingsHeaders (1000.0.0): @@ -282,6 +294,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTTextHeaders (1000.0.0): @@ -294,6 +307,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTVibrationHeaders (1000.0.0): @@ -306,6 +320,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-Core/RCTWebSocket (1000.0.0): @@ -318,6 +333,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-perflogger (= 1000.0.0) + - React-utils - SocketRocket (= 0.6.0) - Yoga - React-CoreModules (1000.0.0): @@ -372,11 +388,11 @@ PODS: - React-Fabric/templateprocessor (= 1000.0.0) - React-Fabric/textlayoutmanager (= 1000.0.0) - React-Fabric/uimanager (= 1000.0.0) - - React-Fabric/utils (= 1000.0.0) - React-graphics (= 1000.0.0) - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/animations (1000.0.0): - DoubleConversion @@ -392,6 +408,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/attributedstring (1000.0.0): - DoubleConversion @@ -407,6 +424,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/butter (1000.0.0): - DoubleConversion @@ -422,6 +440,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/componentregistry (1000.0.0): - DoubleConversion @@ -437,6 +456,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/componentregistrynative (1000.0.0): - DoubleConversion @@ -452,6 +472,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components (1000.0.0): - DoubleConversion @@ -478,6 +499,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/inputaccessory (1000.0.0): - DoubleConversion @@ -493,6 +515,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/legacyviewmanagerinterop (1000.0.0): - DoubleConversion @@ -508,6 +531,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/modal (1000.0.0): - DoubleConversion @@ -523,6 +547,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/rncore (1000.0.0): - DoubleConversion @@ -538,6 +563,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/root (1000.0.0): - DoubleConversion @@ -553,6 +579,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/safeareaview (1000.0.0): - DoubleConversion @@ -568,6 +595,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/scrollview (1000.0.0): - DoubleConversion @@ -583,6 +611,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/text (1000.0.0): - DoubleConversion @@ -598,6 +627,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/textinput (1000.0.0): - DoubleConversion @@ -613,6 +643,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/unimplementedview (1000.0.0): - DoubleConversion @@ -628,6 +659,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/components/view (1000.0.0): - DoubleConversion @@ -643,6 +675,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - Yoga - React-Fabric/config (1000.0.0): @@ -659,6 +692,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/core (1000.0.0): - DoubleConversion @@ -674,6 +708,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/debug_renderer (1000.0.0): - DoubleConversion @@ -689,6 +724,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/imagemanager (1000.0.0): - DoubleConversion @@ -704,6 +740,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/leakchecker (1000.0.0): - DoubleConversion @@ -719,6 +756,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/mapbuffer (1000.0.0): - DoubleConversion @@ -734,6 +772,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/mounting (1000.0.0): - DoubleConversion @@ -749,6 +788,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/runtimescheduler (1000.0.0): - DoubleConversion @@ -764,6 +804,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/scheduler (1000.0.0): - DoubleConversion @@ -779,6 +820,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/telemetry (1000.0.0): - DoubleConversion @@ -794,6 +836,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/templateprocessor (1000.0.0): - DoubleConversion @@ -809,6 +852,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/textlayoutmanager (1000.0.0): - DoubleConversion @@ -825,6 +869,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-Fabric/uimanager (1000.0.0): - DoubleConversion @@ -840,21 +885,7 @@ PODS: - React-jsi (= 1000.0.0) - React-jsiexecutor (= 1000.0.0) - React-logger - - ReactCommon/turbomodule/core (= 1000.0.0) - - React-Fabric/utils (1000.0.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly/Fabric (= 2021.07.22.00) - - RCTRequired (= 1000.0.0) - - RCTTypeSafety (= 1000.0.0) - - React-Core - - React-cxxreact - - React-debug - - React-graphics (= 1000.0.0) - - React-jsi (= 1000.0.0) - - React-jsiexecutor (= 1000.0.0) - - React-logger + - React-utils - ReactCommon/turbomodule/core (= 1000.0.0) - React-FabricImage (1000.0.0): - DoubleConversion @@ -893,6 +924,7 @@ PODS: - React-debug - React-Fabric - React-RCTImage + - React-utils - React-jsi (1000.0.0): - boost (= 1.76.0) - DoubleConversion @@ -962,6 +994,7 @@ PODS: - React-ImageManager - React-RCTImage (= 1000.0.0) - React-RCTText + - React-utils - Yoga - React-RCTImage (1000.0.0): - RCT-Folly (= 2021.07.22.00) @@ -1013,6 +1046,10 @@ PODS: - React-rncore (1000.0.0) - React-runtimeexecutor (1000.0.0): - React-jsi (= 1000.0.0) + - React-utils (1000.0.0): + - glog + - RCT-Folly (= 2021.07.22.00) + - React-debug - ReactCommon-Samples (1000.0.0): - DoubleConversion - hermes-engine @@ -1120,6 +1157,7 @@ DEPENDENCIES: - React-RCTVibration (from `../react-native/Libraries/Vibration`) - React-rncore (from `../react-native/ReactCommon`) - React-runtimeexecutor (from `../react-native/ReactCommon/runtimeexecutor`) + - React-utils (from `../react-native/ReactCommon/react/utils`) - ReactCommon-Samples (from `../react-native/ReactCommon/react/nativemodule/samples`) - ReactCommon/turbomodule/core (from `../react-native/ReactCommon`) - ScreenshotManager (from `NativeModuleExample`) @@ -1229,6 +1267,8 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon" React-runtimeexecutor: :path: "../react-native/ReactCommon/runtimeexecutor" + React-utils: + :path: "../react-native/ReactCommon/react/utils" ReactCommon: :path: "../react-native/ReactCommon" ReactCommon-Samples: @@ -1242,8 +1282,8 @@ SPEC CHECKSUMS: boost: 57d2868c099736d80fcd648bf211b4431e51a558 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 - FBLazyVector: 9c79ec2238e065a949c9bb7aebdf7cebd6203015 - FBReactNativeSpec: 66b1b6348a3f6c3133e6e437ad50b46f4fef812f + FBLazyVector: f4492a543c5a8fa1502d3a5867e3f7252497cfe8 + FBReactNativeSpec: 7a256eec25705f77ac6d6c6187ec2d89a180fa6c Flipper: 6edb735e6c3e332975d1b17956bcc584eccf5818 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 @@ -1254,53 +1294,54 @@ SPEC CHECKSUMS: FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 99bd064df01718db56b8f75e6b5ea3051c7dad0a - hermes-engine: 6085d07261e8a8bfe708e4b0dcd0f3eae72a8e4d + hermes-engine: 3d4707423e276e19d41573fc74ac39cf57c56b17 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OCMock: 9491e4bec59e0b267d52a9184ff5605995e74be8 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: b0d1393cb3763d71efca99db314c65f0072eb0fe - RCTRequired: 40ffd795b32a630f147fcc15247920e5161b6a4e - RCTTypeSafety: fd9d941c329580ea280cad3565bb2e1656969f41 - React: 47363de0e2c161a347eef1cb57cba33f1a4b7377 - React-callinvoker: 28e99e6254975d5be67eafeedbcc3a45bb1ace94 - React-Codegen: 9ecf53f804f4d1d2a5d29bb2154e22a30c381f81 - React-Core: f2a04589df942502db2f3ef84ddc83a35a7086d3 - React-CoreModules: 4fe81e5b80d5daa3e81151dfc9e516ae1a0e575f - React-cxxreact: 1235a3b3d8ea0639f95741cc907ddbb41da4bea3 - React-debug: 9c6d0fd7867cc4a8f435d528e2c5fa8465e9c054 - React-Fabric: b429ac248d619825f97ceb607d1a2d556a49c47c - React-FabricImage: 8136841d81a7005ac3c294623f87497f6bbf8f09 - React-graphics: ed6de536e153c7bc852f9c773a3c9b21c2ee22da - React-hermes: b494c9c833faa6217b2f666d13aa52623c34c2e9 - React-ImageManager: 3e990b6ffbc7634556ddf5b90e5bb1025c397a4c - React-jsi: 00d3c30ce021ff1a074315b824b22108db01131f - React-jsiexecutor: 473c386e219c256066689a334663e8813cdc65ef - React-jsinspector: 25164fdf93d5be275ba455d916e96adb49fa407c - React-logger: a2165169d9c1897c1a139c6b158f6f79090ee174 - React-NativeModulesApple: 5ca2a6989b4549ecf557958c72d514bda071922d - React-perflogger: b5ecf879c705577c80e5299ae678b9375fe2f998 - React-RCTActionSheet: 4a241f444d11926261dd904449a31f35157d4eee - React-RCTAnimation: cf7c9f502e597ee97ce18d1bc4eb8a5836ecf21d - React-RCTAppDelegate: da46fbaa608f201a832d1310bd2e8ce4234642b1 - React-RCTBlob: 48eae02e3f75777da97e9282a5861d04471624a9 - React-RCTFabric: 4b122d0e96f6a581046b8cb950e3277036a5d45a - React-RCTImage: 3efab57255aeb986f89ddc8719fbffd4559954ad - React-RCTLinking: 708dbd9b0aa0845c735e5d6b0e9f7f268008a830 - React-RCTNetwork: d88896f8b7adf85ba366168c5bf1e03d7c235493 - React-RCTPushNotification: dbacfd181137a62deebc62fd386419e707cdcf95 - React-RCTSettings: 17cb468bba42b17b7b0e40ea971d6bd8a78baa3f - React-RCTTest: d7142acb2544c01f53deb2b0543d63df6e214ac2 - React-RCTText: 8cb3ebda2675a02de0cb363a765fafb7d6c31cc1 - React-RCTVibration: c40f0f3e0114354bbf4b418e381c2c96d1d22d48 - React-rncore: 11d8412fb1e339cd1be30aa33cb3b265b46a542b - React-runtimeexecutor: c810e5b7d7500a9ad3ed66b834503e412684936d - ReactCommon: a7972e80473d6ba9bcf3ee53bcc310ac6b944a49 - ReactCommon-Samples: 93c677b30087b6aa82bbccb4458a2028cd6ebd10 + RCTRequired: 82c56a03b3efd524bfdb581a906add903f78f978 + RCTTypeSafety: 034ade4e3b36be976b8378f825ccadbe104fa852 + React: cb6dc75e09f32aeddb4d8fb58a394a67219a92fe + React-callinvoker: bae59cbd6affd712bbfc703839dad868ff35069d + React-Codegen: 42dae0c7801d765934f76cfbaefcb5742524cc98 + React-Core: b04375fb8581bb80b6c87c25061a1998c61b0006 + React-CoreModules: fa9b32bbc7818672a7ca91eeef4867e133b566ec + React-cxxreact: 21b73aa1e245d6c701e62150312c3748756bbf42 + React-debug: d1cd203242675d48eecec6c2553933c0e0a3874f + React-Fabric: e85af38574589ce0d1a353623ecda0f94435da16 + React-FabricImage: 8552a7e0919bc2ab09c6869a7507ad7a46e552d9 + React-graphics: a85048af7e210ec4fed185ef201726f7b4825cc0 + React-hermes: fa4837e1d1e55f462ad3e485794056189b495d7e + React-ImageManager: 528eb68fe16f08d4c76cd32949e6bcd9e4aeae4b + React-jsi: ae20bc6ced4999f64acc5163cbfa67f878f346f4 + React-jsiexecutor: 754993beb8627912e5b25344cad02ed11a616d9f + React-jsinspector: bede0a6ac88f2463eafc1301239fe943adf06fa7 + React-logger: c20eb15d006d5c303cf6bfbb11243c8d579d9f56 + React-NativeModulesApple: 518f3f3d2d9e4944f99df30e601f8774d1fa1663 + React-perflogger: c294d51cfc18b90caa1604ef3a0fe2dd76b9e15e + React-RCTActionSheet: 943bd5f540f3af1e5a149c13c4de81858edf718a + React-RCTAnimation: a430a8c32e7947b7b014f7bd1ef6825168ad4841 + React-RCTAppDelegate: c847ea72bc6fd48b7b6693755c232a3cecbbc970 + React-RCTBlob: 9de0f88a876429c31b96b63975173c60978b5586 + React-RCTFabric: 911e88ea1b9f0b083b1502fb8dabda0a68142d42 + React-RCTImage: 8addd5fae983149d4506fbf8b36be30585adadf4 + React-RCTLinking: aec004e7f55b71be0f68913b1d993964fc8013e1 + React-RCTNetwork: 67229afd0642c55d4493cad5129238a7a1599441 + React-RCTPushNotification: 9e4bba7bb3a4682281216a81f3342caecf84cef7 + React-RCTSettings: 9b6f5a70aa3b859b2686794c3441e278b4f6e0a6 + React-RCTTest: d4004e03f9e5ca2607eb05bee5a0618b189a127a + React-RCTText: 6d0a9927391dc26325c2edf60ef7d36f637e709c + React-RCTVibration: ae65884c71d67f356396d6fcc44eec48b5afef70 + React-rncore: fe8c75a4beb121d0f923f0a45a17910083ccb681 + React-runtimeexecutor: e1c32bc249dd3cf3919cb4664fd8dc84ef70cff7 + React-utils: 2c3b06a36a63d6fce240ac5cb1de003b95222810 + ReactCommon: de6e7a92ad50207b08bcf696a61d9b509876e131 + ReactCommon-Samples: 13b7118480fb9abeee8a98bc1cceff06984cfde4 ScreenshotManager: d39b964a374e5012e2b8c143e29ead86b1da6a3c SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 - Yoga: b82f2e3cbeb3d926329278b24d54d798215d4fa3 + Yoga: 239f77be94241af2a02e7018fe6165a715bc25f1 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: bdab6add69d555774de227d7119a8f5ae02a670e +PODFILE CHECKSUM: e220946495183a79874329aff76ec197027be224 -COCOAPODS: 1.12.0 +COCOAPODS: 1.12.1 diff --git a/packages/rn-tester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m b/packages/rn-tester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m index 09682c19e9b433..7e0c3fcefdddf6 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m +++ b/packages/rn-tester/RNTesterUnitTests/RCTNativeAnimatedNodesManagerTests.m @@ -486,7 +486,8 @@ - (void)testAnimationCallbackFinish RCTResponseSenderBlock endCallback = ^(NSArray *response) { endCallbackCalls++; - XCTAssertEqualObjects(response, @[ @{@"finished" : @YES} ]); + NSArray *expected = @[ @{@"finished" : @YES, @"value" : @1} ]; + XCTAssertEqualObjects(response, expected); }; [_nodesManager startAnimatingNode:@1 @@ -714,7 +715,8 @@ - (void)testHandleStoppingAnimation RCTResponseSenderBlock endCallback = ^(NSArray *response) { endCallbackCalled = YES; - XCTAssertEqualObjects(response, @[ @{@"finished" : @NO} ]); + XCTAssertEqual(response.count, 1); + XCTAssertEqualObjects(response[0][@"finished"], @NO); }; [_nodesManager startAnimatingNode:@404 diff --git a/packages/rn-tester/android/app/src/main/AndroidManifest.xml b/packages/rn-tester/android/app/src/main/AndroidManifest.xml index 14257864c5fbc6..7707683c667d0b 100644 --- a/packages/rn-tester/android/app/src/main/AndroidManifest.xml +++ b/packages/rn-tester/android/app/src/main/AndroidManifest.xml @@ -60,7 +60,6 @@ - { - assert_equals( - eventType, - expectedEventType, - 'Event.type should be ' + expectedEventType, - ); - }, pointerTestName + "'s type should be " + expectedEventType); + harness.test( + ({assert_equals}) => { + assert_equals( + eventType, + expectedEventType, + 'Event.type should be ' + expectedEventType, + ); + }, + pointerTestName + "'s type should be " + expectedEventType, + ); // Test button and buttons if (eventType === 'pointerdown') { diff --git a/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventAttributesNoHoverPointers.js b/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventAttributesNoHoverPointers.js index bedfc48181a8a9..c9894ae6cf3763 100644 --- a/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventAttributesNoHoverPointers.js +++ b/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventAttributesNoHoverPointers.js @@ -66,13 +66,16 @@ function PointerEventAttributesNoHoverPointersTestCase( testNamePrefix + ' ' + expectedPointerType + ' ' + expectedEventType; detected_pointertypes[event.nativeEvent.pointerType] = true; - harness.test(({assert_equals}) => { - assert_equals( - eventType, - expectedEventType, - 'Event.type should be ' + expectedEventType, - ); - }, pointerTestName + "'s type should be " + expectedEventType); + harness.test( + ({assert_equals}) => { + assert_equals( + eventType, + expectedEventType, + 'Event.type should be ' + expectedEventType, + ); + }, + pointerTestName + "'s type should be " + expectedEventType, + ); // Test button and buttons harness.test(({assert_equals}) => { diff --git a/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventCaptureMouse.js b/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventCaptureMouse.js new file mode 100644 index 00000000000000..7ce1b6bf894f66 --- /dev/null +++ b/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventCaptureMouse.js @@ -0,0 +1,213 @@ +/** + * 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 + * @flow + */ + +import type {PlatformTestComponentBaseProps} from '../PlatformTest/RNTesterPlatformTestTypes'; +import type {PointerEvent} from 'react-native/Libraries/Types/CoreEventTypes'; + +import * as React from 'react'; +import {useCallback, useRef} from 'react'; +import {StyleSheet, View, Text} from 'react-native'; +import RNTesterPlatformTest from '../PlatformTest/RNTesterPlatformTest'; +import type {ElementRef} from 'react'; + +// adapted from https://github.com/web-platform-tests/wpt/blob/master/pointerevents/pointerevent_capture_mouse.html +function PointerEventCaptureMouseTestCase( + props: PlatformTestComponentBaseProps, +) { + const {harness} = props; + + const target0Ref = useRef | null>(null); + const target1Ref = useRef | null>(null); + + const isPointerCaptureRef = useRef(false); + const pointermoveNoCaptureGot0Ref = useRef(false); + const pointermoveCaptureGot0Ref = useRef(false); + const pointermoveNoCaptureGot1Ref = useRef(false); + const ownEventForTheCapturedTargetGotRef = useRef(false); + + // const testGotPointerCapture = harness.useAsyncTest( + // 'gotpointercapture event received"', + // ); + // const testLostPointerCapture = harness.useAsyncTest( + // 'lostpointercapture event received"', + // ); + + const handleCaptureButtonDown = useCallback((evt: PointerEvent) => { + const target0 = target0Ref.current; + if (target0 != null && isPointerCaptureRef.current === false) { + isPointerCaptureRef.current = true; + try { + // $FlowFixMe[prop-missing] + target0.setPointerCapture(evt.nativeEvent.pointerId); + } catch (e) {} + } + }, []); + + // const handleTarget0GotPointerCapture = useCallback( + // (evt: PointerEvent) => { + // testGotPointerCapture.done(); + // }, + // [testGotPointerCapture], + // ); + + // const handleTarget0LostPointerCapture = useCallback( + // (evt: PointerEvent) => { + // testLostPointerCapture.done(); + // isPointerCaptureRef.current = false; + // }, + // [testLostPointerCapture], + // ); + + const testPointerMove0 = harness.useAsyncTest( + 'pointerover event for black rectangle received', + ); + const testPointerMove1 = harness.useAsyncTest( + 'pointerover event for purple rectangle received', + ); + + const handleTarget0PointerMove = useCallback( + (evt: PointerEvent) => { + const target0 = target0Ref.current; + if (!pointermoveNoCaptureGot0Ref.current) { + testPointerMove0.done(); + pointermoveNoCaptureGot0Ref.current = true; + } + if (isPointerCaptureRef.current && target0 != null) { + const {clientX, clientY} = evt.nativeEvent; + const {left, right, top, bottom} = + // $FlowFixMe[prop-missing] + target0.getBoundingClientRect(); + + if (!pointermoveCaptureGot0Ref.current) { + harness.test( + ({assert_equals}) => { + assert_equals( + evt.nativeEvent.relatedTarget, + null, + 'relatedTarget is null when the capture is set', + ); + }, + 'relatedTarget is null when the capture is set.', + {skip: true}, + ); + harness.test(({assert_true}) => { + assert_true( + clientX < left || + clientX > right || + clientY < top || + clientY > bottom, + 'pointermove received for captured element while out of it', + ); + }, 'pointermove received for captured element while out of it'); + pointermoveCaptureGot0Ref.current = true; + } + if ( + clientX > left && + clientX < right && + clientY > top && + clientY < bottom && + !ownEventForTheCapturedTargetGotRef.current + ) { + harness.test(({assert_true}) => { + assert_true( + true, + 'pointermove received for captured element while inside of it', + ); + }, 'pointermove received for captured element while inside of it'); + ownEventForTheCapturedTargetGotRef.current = true; + } + } + }, + [harness, testPointerMove0], + ); + + const handleTarget1PointerMove = useCallback( + (evt: PointerEvent) => { + harness.test(({assert_equals}) => { + assert_equals( + isPointerCaptureRef.current, + false, + "pointermove shouldn't trigger for this target when capture is enabled", + ); + }, "pointermove shouldn't trigger for the purple rectangle while the black rectangle has capture"); + + if (!pointermoveNoCaptureGot1Ref.current) { + testPointerMove1.done(); + pointermoveNoCaptureGot1Ref.current = true; + } + }, + [harness, testPointerMove1], + ); + + return ( + + + + + Set Capture + + + ); +} + +const styles = StyleSheet.create({ + captureButton: { + alignSelf: 'flex-start', + backgroundColor: 'lightblue', + paddingHorizontal: 32, + paddingVertical: 16, + borderRadius: 8, + }, + container: {}, + target0: { + backgroundColor: 'black', + padding: 32, + marginBottom: 16, + }, + target1: { + backgroundColor: 'purple', + padding: 32, + marginBottom: 16, + }, +}); + +type Props = $ReadOnly<{}>; +export default function PointerEventCaptureMouse( + props: Props, +): React.MixedElement { + return ( + + ); +} diff --git a/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventPrimaryTouchPointer.js b/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventPrimaryTouchPointer.js index 140f1da72861fb..cd643c420353e7 100644 --- a/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventPrimaryTouchPointer.js +++ b/packages/rn-tester/js/examples/Experimental/W3CPointerEventPlatformTests/PointerEventPrimaryTouchPointer.js @@ -62,23 +62,28 @@ function PointerEventPrimaryTouchPointerTestCase( expectedOrder[Object.keys(detected_events).length]; detected_events[pointerEventIdentifier] = true; - harness.test(({assert_equals}) => { - assert_equals( - boxLabel, - expectedBoxLabel, - 'event should be coming from the correct box', - ); - assert_equals( - eventType, - expectedEventType.toLowerCase(), - 'event should have the right type', - ); - assert_equals( - isPrimary, - expectedIsPrimary, - 'event should be correctly primary', - ); - }, `${expectedBoxLabel} box's ${expectedEventType} should${!expectedIsPrimary ? ' not' : ''} be marked as the primary pointer`); + harness.test( + ({assert_equals}) => { + assert_equals( + boxLabel, + expectedBoxLabel, + 'event should be coming from the correct box', + ); + assert_equals( + eventType, + expectedEventType.toLowerCase(), + 'event should have the right type', + ); + assert_equals( + isPrimary, + expectedIsPrimary, + 'event should be correctly primary', + ); + }, + `${expectedBoxLabel} box's ${expectedEventType} should${ + !expectedIsPrimary ? ' not' : '' + } be marked as the primary pointer`, + ); }, [harness], ); diff --git a/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js b/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js index c21e32a029ebe4..8891b4b29bc5d1 100644 --- a/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js +++ b/packages/rn-tester/js/examples/Experimental/W3CPointerEventsExample.js @@ -26,6 +26,7 @@ import PointerEventLayoutChangeShouldFirePointerOver from './W3CPointerEventPlat import PointerEventPointerCancelTouch from './W3CPointerEventPlatformTests/PointerEventPointerCancelTouch'; import PointerEventClickTouch from './W3CPointerEventPlatformTests/PointerEventClickTouch'; import PointerEventClickTouchHierarchy from './W3CPointerEventPlatformTests/PointerEventClickTouchHierarchy'; +import PointerEventCaptureMouse from './W3CPointerEventPlatformTests/PointerEventCaptureMouse'; import EventfulView from './W3CPointerEventsEventfulView'; import ManyPointersPropertiesExample from './Compatibility/ManyPointersPropertiesExample'; import PointerEventClickTouchHierarchyPointerEvents from './W3CPointerEventPlatformTests/PointerEventClickTouchHierarchyPointerEvents'; @@ -240,6 +241,13 @@ export default { return ; }, }, + { + name: 'pointerevent_caapture_mouse', + title: 'WPT 12: Pointer Events capture test', + render(): React.Node { + return ; + }, + }, { name: 'pointerevent_click_touch', title: 'Pointer Events: basic click test', diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index b0563e0d2f384d..9daea74eb4b796 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -906,4 +906,23 @@ exports.examples = ([ ); }, }, + { + title: 'iOS autoformatting behaviors', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, ]: Array); diff --git a/packages/virtualized-lists/Lists/FillRateHelper.js b/packages/virtualized-lists/Lists/FillRateHelper.js index 87482e73f6be3e..c5bfadf962ca3c 100644 --- a/packages/virtualized-lists/Lists/FillRateHelper.js +++ b/packages/virtualized-lists/Lists/FillRateHelper.js @@ -10,7 +10,8 @@ 'use strict'; -import type {FrameMetricProps} from './VirtualizedListProps'; +import type {CellMetricProps} from './ListMetricsAggregator'; +import ListMetricsAggregator from './ListMetricsAggregator'; export type FillRateInfo = Info; @@ -27,13 +28,6 @@ class Info { sample_count: number = 0; } -type FrameMetrics = { - inLayout?: boolean, - length: number, - offset: number, - ... -}; - const DEBUG = false; let _listeners: Array<(Info) => void> = []; @@ -51,7 +45,7 @@ let _sampleRate = DEBUG ? 1 : null; class FillRateHelper { _anyBlankStartTime: ?number = null; _enabled = false; - _getFrameMetrics: (index: number, props: FrameMetricProps) => ?FrameMetrics; + _listMetrics: ListMetricsAggregator; _info: Info = new Info(); _mostlyBlankStartTime: ?number = null; _samplesStartTime: ?number = null; @@ -79,10 +73,8 @@ class FillRateHelper { _minSampleCount = minSampleCount; } - constructor( - getFrameMetrics: (index: number, props: FrameMetricProps) => ?FrameMetrics, - ) { - this._getFrameMetrics = getFrameMetrics; + constructor(listMetrics: ListMetricsAggregator) { + this._listMetrics = listMetrics; this._enabled = (_sampleRate || 0) > Math.random(); this._resetData(); } @@ -139,7 +131,7 @@ class FillRateHelper { computeBlankness( props: { - ...FrameMetricProps, + ...CellMetricProps, initialNumToRender?: ?number, ... }, @@ -186,12 +178,12 @@ class FillRateHelper { let blankTop = 0; let first = cellsAroundViewport.first; - let firstFrame = this._getFrameMetrics(first, props); + let firstFrame = this._listMetrics.getCellMetrics(first, props); while ( first <= cellsAroundViewport.last && - (!firstFrame || !firstFrame.inLayout) + (!firstFrame || !firstFrame.isMounted) ) { - firstFrame = this._getFrameMetrics(first, props); + firstFrame = this._listMetrics.getCellMetrics(first, props); first++; } // Only count blankTop if we aren't rendering the first item, otherwise we will count the header @@ -204,12 +196,12 @@ class FillRateHelper { } let blankBottom = 0; let last = cellsAroundViewport.last; - let lastFrame = this._getFrameMetrics(last, props); + let lastFrame = this._listMetrics.getCellMetrics(last, props); while ( last >= cellsAroundViewport.first && - (!lastFrame || !lastFrame.inLayout) + (!lastFrame || !lastFrame.isMounted) ) { - lastFrame = this._getFrameMetrics(last, props); + lastFrame = this._listMetrics.getCellMetrics(last, props); last--; } // Only count blankBottom if we aren't rendering the last item, otherwise we will count the diff --git a/packages/virtualized-lists/Lists/ListMetricsAggregator.js b/packages/virtualized-lists/Lists/ListMetricsAggregator.js new file mode 100644 index 00000000000000..3fcb051525114b --- /dev/null +++ b/packages/virtualized-lists/Lists/ListMetricsAggregator.js @@ -0,0 +1,179 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {Props as VirtualizedListProps} from './VirtualizedListProps'; +import {keyExtractor as defaultKeyExtractor} from './VirtualizeUtils'; + +import invariant from 'invariant'; + +export type CellMetrics = { + /** + * Index of the item in the list + */ + index: number, + /** + * Length of the cell along the scrolling axis + */ + length: number, + /** + * Offset to the cell along the scrolling axis + */ + offset: number, + /** + * Whether the cell is last known to be mounted + */ + isMounted: boolean, +}; + +/** + * Subset of VirtualizedList props needed to calculate cell metrics + */ +export type CellMetricProps = { + data: VirtualizedListProps['data'], + getItemCount: VirtualizedListProps['getItemCount'], + getItem: VirtualizedListProps['getItem'], + getItemLayout?: VirtualizedListProps['getItemLayout'], + keyExtractor?: VirtualizedListProps['keyExtractor'], + ... +}; + +/** + * Provides an interface to query information about the metrics of a list and its cells. + */ +export default class ListMetricsAggregator { + _averageCellLength = 0; + _frames: {[string]: CellMetrics} = {}; + _highestMeasuredCellIndex = 0; + _totalCellLength = 0; + _totalCellsMeasured = 0; + + /** + * Notify the ListMetricsAggregator that a cell has been laid out. + * + * @returns whether the cell layout has changed since last notification + */ + notifyCellLayout( + cellKey: string, + index: number, + length: number, + offset: number, + ): boolean { + const next: CellMetrics = { + offset, + length, + index, + isMounted: true, + }; + const curr = this._frames[cellKey]; + if ( + !curr || + next.offset !== curr.offset || + next.length !== curr.length || + index !== curr.index + ) { + this._totalCellLength += next.length - (curr ? curr.length : 0); + this._totalCellsMeasured += curr ? 0 : 1; + this._averageCellLength = + this._totalCellLength / this._totalCellsMeasured; + this._frames[cellKey] = next; + this._highestMeasuredCellIndex = Math.max( + this._highestMeasuredCellIndex, + index, + ); + return true; + } else { + this._frames[cellKey].isMounted = true; + return false; + } + } + + /** + * Notify ListMetricsAggregator that a cell has been unmounted. + */ + notifyCellUnmounted(cellKey: string): void { + const curr = this._frames[cellKey]; + if (curr) { + this._frames[cellKey] = {...curr, isMounted: false}; + } + } + + /** + * Return the average length of the cells which have been measured + */ + getAverageCellLength(): number { + return this._averageCellLength; + } + + /** + * Return the highest measured cell index + */ + getHighestMeasuredCellIndex(): number { + return this._highestMeasuredCellIndex; + } + + /** + * Returns the exact metrics of a cell if it has already been laid out, + * otherwise an estimate based on the average length of previously measured + * cells + */ + getCellMetricsApprox(index: number, props: CellMetricProps): CellMetrics { + const frame = this.getCellMetrics(index, props); + if (frame && frame.index === index) { + // check for invalid frames due to row re-ordering + return frame; + } else { + const {data, getItemCount} = props; + invariant( + index >= 0 && index < getItemCount(data), + 'Tried to get frame for out of range index ' + index, + ); + return { + length: this._averageCellLength, + offset: this._averageCellLength * index, + index, + isMounted: false, + }; + } + } + + /** + * Returns the exact metrics of a cell if it has already been laid out + */ + getCellMetrics(index: number, props: CellMetricProps): ?CellMetrics { + const {data, getItem, getItemCount, getItemLayout} = props; + invariant( + index >= 0 && index < getItemCount(data), + 'Tried to get frame for out of range index ' + index, + ); + const keyExtractor = props.keyExtractor ?? defaultKeyExtractor; + const frame = this._frames[keyExtractor(getItem(data, index), index)]; + if (!frame || frame.index !== index) { + if (getItemLayout) { + const {length, offset} = getItemLayout(data, index); + return {index, length, offset, isMounted: true}; + } + } + return frame; + } + + /** + * Gets an approximate offset to an item at a given index. Supports + * fractional indices. + */ + getCellOffsetApprox(index: number, props: CellMetricProps): number { + if (Number.isInteger(index)) { + return this.getCellMetricsApprox(index, props).offset; + } else { + const frameMetrics = this.getCellMetricsApprox(Math.floor(index), props); + const remainder = index - Math.floor(index); + return frameMetrics.offset + remainder * frameMetrics.length; + } + } +} diff --git a/packages/virtualized-lists/Lists/ViewabilityHelper.js b/packages/virtualized-lists/Lists/ViewabilityHelper.js index 33a9811825affd..cae7c0bd685572 100644 --- a/packages/virtualized-lists/Lists/ViewabilityHelper.js +++ b/packages/virtualized-lists/Lists/ViewabilityHelper.js @@ -10,7 +10,8 @@ 'use strict'; -import type {FrameMetricProps} from './VirtualizedListProps'; +import type {CellMetricProps} from './ListMetricsAggregator'; +import ListMetricsAggregator from './ListMetricsAggregator'; const invariant = require('invariant'); @@ -101,17 +102,10 @@ class ViewabilityHelper { * Determines which items are viewable based on the current metrics and config. */ computeViewableItems( - props: FrameMetricProps, + props: CellMetricProps, scrollOffset: number, viewportHeight: number, - getFrameMetrics: ( - index: number, - props: FrameMetricProps, - ) => ?{ - length: number, - offset: number, - ... - }, + listMetrics: ListMetricsAggregator, // Optional optimization to reduce the scan size renderRange?: { first: number, @@ -146,7 +140,7 @@ class ViewabilityHelper { return []; } for (let idx = first; idx <= last; idx++) { - const metrics = getFrameMetrics(idx, props); + const metrics = listMetrics.getCellMetrics(idx, props); if (!metrics) { continue; } @@ -178,21 +172,14 @@ class ViewabilityHelper { * `onViewableItemsChanged` as appropriate. */ onUpdate( - props: FrameMetricProps, + props: CellMetricProps, scrollOffset: number, viewportHeight: number, - getFrameMetrics: ( - index: number, - props: FrameMetricProps, - ) => ?{ - length: number, - offset: number, - ... - }, + listMetrics: ListMetricsAggregator, createViewToken: ( index: number, isViewable: boolean, - props: FrameMetricProps, + props: CellMetricProps, ) => ViewToken, onViewableItemsChanged: ({ viewableItems: Array, @@ -210,7 +197,7 @@ class ViewabilityHelper { if ( (this._config.waitForInteraction && !this._hasInteracted) || itemCount === 0 || - !getFrameMetrics(0, props) + !listMetrics.getCellMetrics(0, props) ) { return; } @@ -220,7 +207,7 @@ class ViewabilityHelper { props, scrollOffset, viewportHeight, - getFrameMetrics, + listMetrics, renderRange, ); } @@ -275,7 +262,7 @@ class ViewabilityHelper { } _onUpdateSync( - props: FrameMetricProps, + props: CellMetricProps, viewableIndicesToCheck: Array, onViewableItemsChanged: ({ changed: Array, @@ -285,7 +272,7 @@ class ViewabilityHelper { createViewToken: ( index: number, isViewable: boolean, - props: FrameMetricProps, + props: CellMetricProps, ) => ViewToken, ) { // Filter out indices that have gone out of view since this call was scheduled. diff --git a/packages/virtualized-lists/Lists/VirtualizeUtils.js b/packages/virtualized-lists/Lists/VirtualizeUtils.js index 3a70d9f683091e..3dd88d607590bf 100644 --- a/packages/virtualized-lists/Lists/VirtualizeUtils.js +++ b/packages/virtualized-lists/Lists/VirtualizeUtils.js @@ -10,7 +10,8 @@ 'use strict'; -import type {FrameMetricProps} from './VirtualizedListProps'; +import type {CellMetricProps} from './ListMetricsAggregator'; +import ListMetricsAggregator from './ListMetricsAggregator'; /** * Used to find the indices of the frames that overlap the given offsets. Useful for finding the @@ -19,15 +20,8 @@ import type {FrameMetricProps} from './VirtualizedListProps'; */ export function elementsThatOverlapOffsets( offsets: Array, - props: FrameMetricProps, - getFrameMetrics: ( - index: number, - props: FrameMetricProps, - ) => { - length: number, - offset: number, - ... - }, + props: CellMetricProps, + listMetrics: ListMetricsAggregator, zoomScale: number = 1, ): Array { const itemCount = props.getItemCount(props.data); @@ -38,9 +32,8 @@ export function elementsThatOverlapOffsets( let right = itemCount - 1; while (left <= right) { - // eslint-disable-next-line no-bitwise - const mid = left + ((right - left) >>> 1); - const frame = getFrameMetrics(mid, props); + const mid = left + Math.floor((right - left) / 2); + const frame = listMetrics.getCellMetricsApprox(mid, props); const scaledOffsetStart = frame.offset * zoomScale; const scaledOffsetEnd = (frame.offset + frame.length) * zoomScale; @@ -99,21 +92,14 @@ export function newRangeCount( * biased in the direction of scroll. */ export function computeWindowedRenderLimits( - props: FrameMetricProps, + props: CellMetricProps, maxToRenderPerBatch: number, windowSize: number, prev: { first: number, last: number, }, - getFrameMetricsApprox: ( - index: number, - props: FrameMetricProps, - ) => { - length: number, - offset: number, - ... - }, + listMetrics: ListMetricsAggregator, scrollMetrics: { dt: number, offset: number, @@ -152,7 +138,7 @@ export function computeWindowedRenderLimits( const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength); const lastItemOffset = - getFrameMetricsApprox(itemCount - 1, props).offset * zoomScale; + listMetrics.getCellMetricsApprox(itemCount - 1, props).offset * zoomScale; if (lastItemOffset < overscanBegin) { // Entire list is before our overscan window return { @@ -165,7 +151,7 @@ export function computeWindowedRenderLimits( let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets( [overscanBegin, visibleBegin, visibleEnd, overscanEnd], props, - getFrameMetricsApprox, + listMetrics, zoomScale, ); overscanFirst = overscanFirst == null ? 0 : overscanFirst; diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index 8a23e81cbef1a6..0c078e085957c3 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -16,13 +16,13 @@ import type { } from 'react-native/Libraries/Types/CoreEventTypes'; import type {ViewToken} from './ViewabilityHelper'; import type { - FrameMetricProps, Item, Props, RenderItemProps, RenderItemType, Separators, } from './VirtualizedListProps'; +import type {CellMetricProps} from './ListMetricsAggregator'; import { RefreshControl, @@ -37,6 +37,7 @@ import infoLog from '../Utilities/infoLog'; import {CellRenderMask} from './CellRenderMask'; import ChildListCollection from './ChildListCollection'; import FillRateHelper from './FillRateHelper'; +import ListMetricsAggregator from './ListMetricsAggregator'; import StateSafePureComponent from './StateSafePureComponent'; import ViewabilityHelper from './ViewabilityHelper'; import CellRenderer from './VirtualizedListCellRenderer'; @@ -176,7 +177,7 @@ class VirtualizedList extends StateSafePureComponent { if (veryLast < 0) { return; } - const frame = this.__getFrameMetricsApprox(veryLast, this.props); + const frame = this._listMetrics.getCellMetricsApprox(veryLast, this.props); const offset = Math.max( 0, frame.offset + @@ -237,24 +238,31 @@ class VirtualizedList extends StateSafePureComponent { getItemCount(data) - 1 }`, ); - if (!getItemLayout && index > this._highestMeasuredFrameIndex) { + if ( + !getItemLayout && + index > this._listMetrics.getHighestMeasuredCellIndex() + ) { invariant( !!onScrollToIndexFailed, 'scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, ' + 'otherwise there is no way to know the location of offscreen indices or handle failures.', ); onScrollToIndexFailed({ - averageItemLength: this._averageCellLength, - highestMeasuredFrameIndex: this._highestMeasuredFrameIndex, + averageItemLength: this._listMetrics.getAverageCellLength(), + highestMeasuredFrameIndex: + this._listMetrics.getHighestMeasuredCellIndex(), index, }); return; } - const frame = this.__getFrameMetricsApprox(Math.floor(index), this.props); + const frame = this._listMetrics.getCellMetricsApprox( + Math.floor(index), + this.props, + ); const offset = Math.max( 0, - this._getOffsetApprox(index, this.props) - + this._listMetrics.getCellOffsetApprox(index, this.props) - (viewPosition || 0) * (this._scrollMetrics.visibleLength - frame.length), ) - (viewOffset || 0); @@ -427,7 +435,7 @@ class VirtualizedList extends StateSafePureComponent { super(props); this._checkProps(props); - this._fillRateHelper = new FillRateHelper(this._getFrameMetrics); + this._fillRateHelper = new FillRateHelper(this._listMetrics); this._updateCellsToRenderBatcher = new Batchinator( this._updateCellsToRender, this.props.updateCellsBatchingPeriod ?? 50, @@ -683,7 +691,7 @@ class VirtualizedList extends StateSafePureComponent { maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch), windowSizeOrDefault(props.windowSize), cellsAroundViewport, - this.__getFrameMetricsApprox, + this._listMetrics, this._scrollMetrics, ); invariant( @@ -1037,15 +1045,18 @@ class VirtualizedList extends StateSafePureComponent { ? clamp( section.first - 1, section.last, - this._highestMeasuredFrameIndex, + this._listMetrics.getHighestMeasuredCellIndex(), ) : section.last; - const firstMetrics = this.__getFrameMetricsApprox( + const firstMetrics = this._listMetrics.getCellMetricsApprox( section.first, this.props, ); - const lastMetrics = this.__getFrameMetricsApprox(last, this.props); + const lastMetrics = this._listMetrics.getCellMetricsApprox( + last, + this.props, + ); const spacerSize = lastMetrics.offset + lastMetrics.length - firstMetrics.offset; cells.push( @@ -1223,17 +1234,9 @@ class VirtualizedList extends StateSafePureComponent { } } - _averageCellLength = 0; _cellRefs: {[string]: null | CellRenderer} = {}; _fillRateHelper: FillRateHelper; - _frames: { - [string]: { - inLayout?: boolean, - index: number, - length: number, - offset: number, - }, - } = {}; + _listMetrics: ListMetricsAggregator = new ListMetricsAggregator(); _footerLength = 0; // Used for preventing scrollToIndex from being called multiple times for initialScrollIndex _hasTriggeredInitialScrollToIndex = false; @@ -1242,7 +1245,6 @@ class VirtualizedList extends StateSafePureComponent { _hasWarned: {[string]: boolean} = {}; _headerLength = 0; _hiPriInProgress: boolean = false; // flag to prevent infinite hiPri cell limit update - _highestMeasuredFrameIndex = 0; _indicesToKeys: Map = new Map(); _lastFocusedCellKey: ?string = null; _nestedChildLists: ChildListCollection = @@ -1263,8 +1265,6 @@ class VirtualizedList extends StateSafePureComponent { _scrollRef: ?React.ElementRef = null; _sentStartForContentLength = 0; _sentEndForContentLength = 0; - _totalCellLength = 0; - _totalCellsMeasured = 0; _updateCellsToRenderBatcher: Batchinator; _viewabilityTuples: Array = []; @@ -1324,31 +1324,17 @@ class VirtualizedList extends StateSafePureComponent { _onCellLayout = (e: LayoutEvent, cellKey: string, index: number): void => { const layout = e.nativeEvent.layout; - const next = { - offset: this._selectOffset(layout), - length: this._selectLength(layout), + const offset = this._selectOffset(layout); + const length = this._selectLength(layout); + + const layoutHasChanged = this._listMetrics.notifyCellLayout( + cellKey, index, - inLayout: true, - }; - const curr = this._frames[cellKey]; - if ( - !curr || - next.offset !== curr.offset || - next.length !== curr.length || - index !== curr.index - ) { - this._totalCellLength += next.length - (curr ? curr.length : 0); - this._totalCellsMeasured += curr ? 0 : 1; - this._averageCellLength = - this._totalCellLength / this._totalCellsMeasured; - this._frames[cellKey] = next; - this._highestMeasuredFrameIndex = Math.max( - this._highestMeasuredFrameIndex, - index, - ); + length, + offset, + ); + if (layoutHasChanged) { this._scheduleCellsToRenderUpdate(); - } else { - this._frames[cellKey].inLayout = true; } this._triggerRemeasureForChildListsInCell(cellKey); @@ -1364,10 +1350,7 @@ class VirtualizedList extends StateSafePureComponent { _onCellUnmount = (cellKey: string) => { delete this._cellRefs[cellKey]; - const curr = this._frames[cellKey]; - if (curr) { - this._frames[cellKey] = {...curr, inLayout: false}; - } + this._listMetrics.notifyCellUnmounted(cellKey); }; _triggerRemeasureForChildListsInCell(cellKey: string): void { @@ -1467,19 +1450,16 @@ class VirtualizedList extends StateSafePureComponent { const framesInLayout = []; const itemCount = this.props.getItemCount(this.props.data); for (let ii = 0; ii < itemCount; ii++) { - const frame = this.__getFrameMetricsApprox(ii, this.props); - /* $FlowFixMe[prop-missing] (>=0.68.0 site=react_native_fb) This comment - * suppresses an error found when Flow v0.68 was deployed. To see the - * error delete this comment and run Flow. */ - if (frame.inLayout) { + const frame = this._listMetrics.getCellMetricsApprox(ii, this.props); + if (frame.isMounted) { framesInLayout.push(frame); } } - const windowTop = this.__getFrameMetricsApprox( + const windowTop = this._listMetrics.getCellMetricsApprox( this.state.cellsAroundViewport.first, this.props, ).offset; - const frameLast = this.__getFrameMetricsApprox( + const frameLast = this._listMetrics.getCellMetricsApprox( this.state.cellsAroundViewport.last, this.props, ); @@ -1774,7 +1754,8 @@ class VirtualizedList extends StateSafePureComponent { // But only if there are items before the first rendered item if (first > 0) { const distTop = - offset - this.__getFrameMetricsApprox(first, this.props).offset; + offset - + this._listMetrics.getCellMetricsApprox(first, this.props).offset; hiPri = distTop < 0 || (velocity < -2 && @@ -1785,7 +1766,7 @@ class VirtualizedList extends StateSafePureComponent { // But only if there are items after the last rendered item if (!hiPri && last >= 0 && last < itemCount - 1) { const distBottom = - this.__getFrameMetricsApprox(last, this.props).offset - + this._listMetrics.getCellMetricsApprox(last, this.props).offset - (offset + visibleLength); hiPri = distBottom < 0 || @@ -1802,7 +1783,7 @@ class VirtualizedList extends StateSafePureComponent { // We shouldn't do another hipri cellToRenderUpdate if ( hiPri && - (this._averageCellLength || this.props.getItemLayout) && + (this._listMetrics.getAverageCellLength() || this.props.getItemLayout) && !this._hiPriInProgress ) { this._hiPriInProgress = true; @@ -1885,7 +1866,7 @@ class VirtualizedList extends StateSafePureComponent { _createViewToken = ( index: number, isViewable: boolean, - props: FrameMetricProps, + props: CellMetricProps, // $FlowFixMe[missing-local-annot] ) => { const {data, getItem} = props; @@ -1898,81 +1879,12 @@ class VirtualizedList extends StateSafePureComponent { }; }; - /** - * Gets an approximate offset to an item at a given index. Supports - * fractional indices. - */ - _getOffsetApprox = (index: number, props: FrameMetricProps): number => { - if (Number.isInteger(index)) { - return this.__getFrameMetricsApprox(index, props).offset; - } else { - const frameMetrics = this.__getFrameMetricsApprox( - Math.floor(index), - props, - ); - const remainder = index - Math.floor(index); - return frameMetrics.offset + remainder * frameMetrics.length; - } - }; - - __getFrameMetricsApprox: ( - index: number, - props: FrameMetricProps, - ) => { - length: number, - offset: number, - ... - } = (index, props) => { - const frame = this._getFrameMetrics(index, props); - if (frame && frame.index === index) { - // check for invalid frames due to row re-ordering - return frame; - } else { - const {data, getItemCount, getItemLayout} = props; - invariant( - index >= 0 && index < getItemCount(data), - 'Tried to get frame for out of range index ' + index, - ); - invariant( - !getItemLayout, - 'Should not have to estimate frames when a measurement metrics function is provided', - ); - return { - length: this._averageCellLength, - offset: this._averageCellLength * index, - }; - } - }; - - _getFrameMetrics = ( - index: number, - props: FrameMetricProps, - ): ?{ - length: number, - offset: number, - index: number, - inLayout?: boolean, - ... - } => { - const {data, getItemCount, getItemLayout} = props; - invariant( - index >= 0 && index < getItemCount(data), - 'Tried to get frame for out of range index ' + index, - ); - const frame = this._frames[VirtualizedList._getItemKey(props, index)]; - if (!frame || frame.index !== index) { - if (getItemLayout) { - /* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment - * suppresses an error found when Flow v0.63 was deployed. To see the error - * delete this comment and run Flow. */ - return getItemLayout(data, index); - } - } - return frame; - }; + __getListMetrics(): ListMetricsAggregator { + return this._listMetrics; + } _getNonViewportRenderRegions = ( - props: FrameMetricProps, + props: CellMetricProps, ): $ReadOnlyArray<{ first: number, last: number, @@ -2008,7 +1920,7 @@ class VirtualizedList extends StateSafePureComponent { i-- ) { first--; - heightOfCellsBeforeFocused += this.__getFrameMetricsApprox( + heightOfCellsBeforeFocused += this._listMetrics.getCellMetricsApprox( i, props, ).length; @@ -2023,7 +1935,7 @@ class VirtualizedList extends StateSafePureComponent { i++ ) { last++; - heightOfCellsAfterFocused += this.__getFrameMetricsApprox( + heightOfCellsAfterFocused += this._listMetrics.getCellMetricsApprox( i, props, ).length; @@ -2033,7 +1945,7 @@ class VirtualizedList extends StateSafePureComponent { }; _updateViewableItems( - props: FrameMetricProps, + props: CellMetricProps, cellsAroundViewport: {first: number, last: number}, ) { // If we have any pending scroll updates it means that the scroll metrics @@ -2046,7 +1958,7 @@ class VirtualizedList extends StateSafePureComponent { props, this._scrollMetrics.offset, this._scrollMetrics.visibleLength, - this._getFrameMetrics, + this._listMetrics, this._createViewToken, tuple.onViewableItemsChanged, cellsAroundViewport, diff --git a/packages/virtualized-lists/Lists/VirtualizedListContext.js b/packages/virtualized-lists/Lists/VirtualizedListContext.js index bca5724498a356..b40128f2dd2b26 100644 --- a/packages/virtualized-lists/Lists/VirtualizedListContext.js +++ b/packages/virtualized-lists/Lists/VirtualizedListContext.js @@ -31,9 +31,7 @@ type Context = $ReadOnly<{ cellKey: string, ref: React.ElementRef, }) => void, - unregisterAsNestedChild: ({ - ref: React.ElementRef, - }) => void, + unregisterAsNestedChild: ({ref: React.ElementRef}) => void, }>; export const VirtualizedListContext: React.Context = diff --git a/packages/virtualized-lists/Lists/VirtualizedListProps.js b/packages/virtualized-lists/Lists/VirtualizedListProps.js index 3e196a9a9bac6a..d1ec579b3a09df 100644 --- a/packages/virtualized-lists/Lists/VirtualizedListProps.js +++ b/packages/virtualized-lists/Lists/VirtualizedListProps.js @@ -292,15 +292,3 @@ export type Props = {| ...RequiredProps, ...OptionalProps, |}; - -/** - * Subset of properties needed to calculate frame metrics - */ -export type FrameMetricProps = { - data: RequiredProps['data'], - getItemCount: RequiredProps['getItemCount'], - getItem: RequiredProps['getItem'], - getItemLayout?: OptionalProps['getItemLayout'], - keyExtractor?: OptionalProps['keyExtractor'], - ... -}; diff --git a/packages/virtualized-lists/Lists/VirtualizedSectionList.js b/packages/virtualized-lists/Lists/VirtualizedSectionList.js index d217bcde8c5259..0f6b2a360b33bf 100644 --- a/packages/virtualized-lists/Lists/VirtualizedSectionList.js +++ b/packages/virtualized-lists/Lists/VirtualizedSectionList.js @@ -138,11 +138,11 @@ class VirtualizedSectionList< if (this._listRef == null) { return; } + const listRef = this._listRef; if (params.itemIndex > 0 && this.props.stickySectionHeadersEnabled) { - const frame = this._listRef.__getFrameMetricsApprox( - index - params.itemIndex, - this._listRef.props, - ); + const frame = listRef + .__getListMetrics() + .getCellMetricsApprox(index - params.itemIndex, listRef.props); viewOffset += frame.length; } const toIndexParams = { diff --git a/packages/virtualized-lists/Lists/__tests__/FillRateHelper-test.js b/packages/virtualized-lists/Lists/__tests__/FillRateHelper-test.js index c74915a20ee547..58d47ba68d8c90 100644 --- a/packages/virtualized-lists/Lists/__tests__/FillRateHelper-test.js +++ b/packages/virtualized-lists/Lists/__tests__/FillRateHelper-test.js @@ -21,9 +21,9 @@ const dataGlobal = [ {key: 'd'}, {key: 'footer'}, ]; -function getFrameMetrics(index: number) { +function getCellMetrics(index: number) { const frame = rowFramesGlobal[dataGlobal[index].key]; - return {length: frame.height, offset: frame.y, inLayout: frame.inLayout}; + return {length: frame.height, offset: frame.y, isMounted: frame.isMounted}; } function computeResult({helper, props, state, scroll}): number { @@ -47,11 +47,11 @@ describe('computeBlankness', function () { }); it('computes correct blankness of viewport', function () { - const helper = new FillRateHelper(getFrameMetrics); + const helper = new FillRateHelper({getCellMetrics}); rowFramesGlobal = { - header: {y: 0, height: 0, inLayout: true}, - a: {y: 0, height: 50, inLayout: true}, - b: {y: 50, height: 50, inLayout: true}, + header: {y: 0, height: 0, isMounted: true}, + a: {y: 0, height: 50, isMounted: true}, + b: {y: 50, height: 50, isMounted: true}, }; let blankness = computeResult({helper}); expect(blankness).toBe(0); @@ -66,32 +66,32 @@ describe('computeBlankness', function () { }); it('skips frames that are not in layout', function () { - const helper = new FillRateHelper(getFrameMetrics); + const helper = new FillRateHelper({getCellMetrics}); rowFramesGlobal = { - header: {y: 0, height: 0, inLayout: false}, - a: {y: 0, height: 10, inLayout: false}, - b: {y: 10, height: 30, inLayout: true}, - c: {y: 40, height: 40, inLayout: true}, - d: {y: 80, height: 20, inLayout: false}, - footer: {y: 100, height: 0, inLayout: false}, + header: {y: 0, height: 0, isMounted: false}, + a: {y: 0, height: 10, isMounted: false}, + b: {y: 10, height: 30, isMounted: true}, + c: {y: 40, height: 40, isMounted: true}, + d: {y: 80, height: 20, isMounted: false}, + footer: {y: 100, height: 0, isMounted: false}, }; const blankness = computeResult({helper, state: {last: 4}}); expect(blankness).toBe(0.3); }); it('sampling rate can disable', function () { - let helper = new FillRateHelper(getFrameMetrics); + let helper = new FillRateHelper({getCellMetrics}); rowFramesGlobal = { - header: {y: 0, height: 0, inLayout: true}, - a: {y: 0, height: 40, inLayout: true}, - b: {y: 40, height: 40, inLayout: true}, + header: {y: 0, height: 0, isMounted: true}, + a: {y: 0, height: 40, isMounted: true}, + b: {y: 40, height: 40, isMounted: true}, }; let blankness = computeResult({helper}); expect(blankness).toBe(0.2); FillRateHelper.setSampleRate(0); - helper = new FillRateHelper(getFrameMetrics); + helper = new FillRateHelper({getCellMetrics}); blankness = computeResult({helper}); expect(blankness).toBe(0); }); @@ -102,11 +102,11 @@ describe('computeBlankness', function () { FillRateHelper.addListener(listener), ); subscriptions[1].remove(); - const helper = new FillRateHelper(getFrameMetrics); + const helper = new FillRateHelper({getCellMetrics}); rowFramesGlobal = { - header: {y: 0, height: 0, inLayout: true}, - a: {y: 0, height: 40, inLayout: true}, - b: {y: 40, height: 40, inLayout: true}, + header: {y: 0, height: 0, isMounted: true}, + a: {y: 0, height: 40, isMounted: true}, + b: {y: 40, height: 40, isMounted: true}, }; const blankness = computeResult({helper}); expect(blankness).toBe(0.2); diff --git a/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js b/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js index 1d755eac39b8a5..08423c52cc275a 100644 --- a/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js +++ b/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js @@ -18,7 +18,7 @@ const props = { data, getItemCount: () => data.length, }; -function getFrameMetrics(index: number) { +function getCellMetrics(index: number) { const frame = rowFrames[data[index].key]; return {length: frame.height, offset: frame.y}; } @@ -38,9 +38,9 @@ describe('computeViewableItems', function () { d: {y: 150, height: 50}, }; data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}]; - expect(helper.computeViewableItems(props, 0, 200, getFrameMetrics)).toEqual( - [0, 1, 2, 3], - ); + expect( + helper.computeViewableItems(props, 0, 200, {getCellMetrics}), + ).toEqual([0, 1, 2, 3]); }); it('returns top 2 rows as viewable (1. entirely visible and 2. majority)', function () { @@ -54,9 +54,9 @@ describe('computeViewableItems', function () { d: {y: 250, height: 50}, }; data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}]; - expect(helper.computeViewableItems(props, 0, 200, getFrameMetrics)).toEqual( - [0, 1], - ); + expect( + helper.computeViewableItems(props, 0, 200, {getCellMetrics}), + ).toEqual([0, 1]); }); it('returns only 2nd row as viewable (majority)', function () { @@ -71,7 +71,7 @@ describe('computeViewableItems', function () { }; data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}]; expect( - helper.computeViewableItems(props, 25, 200, getFrameMetrics), + helper.computeViewableItems(props, 25, 200, {getCellMetrics}), ).toEqual([1]); }); @@ -81,9 +81,9 @@ describe('computeViewableItems', function () { }); rowFrames = {}; data = []; - expect(helper.computeViewableItems(props, 0, 200, getFrameMetrics)).toEqual( - [], - ); + expect( + helper.computeViewableItems(props, 0, 200, {getCellMetrics}), + ).toEqual([]); }); it('handles different view area coverage percent thresholds', function () { @@ -96,39 +96,39 @@ describe('computeViewableItems', function () { data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}]; let helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 0}); - expect(helper.computeViewableItems(props, 0, 50, getFrameMetrics)).toEqual([ - 0, - ]); - expect(helper.computeViewableItems(props, 1, 50, getFrameMetrics)).toEqual([ - 0, 1, - ]); + expect(helper.computeViewableItems(props, 0, 50, {getCellMetrics})).toEqual( + [0], + ); + expect(helper.computeViewableItems(props, 1, 50, {getCellMetrics})).toEqual( + [0, 1], + ); expect( - helper.computeViewableItems(props, 199, 50, getFrameMetrics), + helper.computeViewableItems(props, 199, 50, {getCellMetrics}), ).toEqual([1, 2]); expect( - helper.computeViewableItems(props, 250, 50, getFrameMetrics), + helper.computeViewableItems(props, 250, 50, {getCellMetrics}), ).toEqual([2]); helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 100}); - expect(helper.computeViewableItems(props, 0, 200, getFrameMetrics)).toEqual( - [0, 1], - ); - expect(helper.computeViewableItems(props, 1, 200, getFrameMetrics)).toEqual( - [1], - ); expect( - helper.computeViewableItems(props, 400, 200, getFrameMetrics), + helper.computeViewableItems(props, 0, 200, {getCellMetrics}), + ).toEqual([0, 1]); + expect( + helper.computeViewableItems(props, 1, 200, {getCellMetrics}), + ).toEqual([1]); + expect( + helper.computeViewableItems(props, 400, 200, {getCellMetrics}), ).toEqual([2]); expect( - helper.computeViewableItems(props, 600, 200, getFrameMetrics), + helper.computeViewableItems(props, 600, 200, {getCellMetrics}), ).toEqual([3]); helper = new ViewabilityHelper({viewAreaCoveragePercentThreshold: 10}); expect( - helper.computeViewableItems(props, 30, 200, getFrameMetrics), + helper.computeViewableItems(props, 30, 200, {getCellMetrics}), ).toEqual([0, 1, 2]); expect( - helper.computeViewableItems(props, 31, 200, getFrameMetrics), + helper.computeViewableItems(props, 31, 200, {getCellMetrics}), ).toEqual([1, 2]); }); @@ -141,30 +141,30 @@ describe('computeViewableItems', function () { }; data = [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}]; let helper = new ViewabilityHelper({itemVisiblePercentThreshold: 0}); - expect(helper.computeViewableItems(props, 0, 50, getFrameMetrics)).toEqual([ - 0, - ]); - expect(helper.computeViewableItems(props, 1, 50, getFrameMetrics)).toEqual([ - 0, 1, - ]); - - helper = new ViewabilityHelper({itemVisiblePercentThreshold: 100}); - expect(helper.computeViewableItems(props, 0, 250, getFrameMetrics)).toEqual( - [0, 1, 2], + expect(helper.computeViewableItems(props, 0, 50, {getCellMetrics})).toEqual( + [0], ); - expect(helper.computeViewableItems(props, 1, 250, getFrameMetrics)).toEqual( - [1, 2], + expect(helper.computeViewableItems(props, 1, 50, {getCellMetrics})).toEqual( + [0, 1], ); + helper = new ViewabilityHelper({itemVisiblePercentThreshold: 100}); + expect( + helper.computeViewableItems(props, 0, 250, {getCellMetrics}), + ).toEqual([0, 1, 2]); + expect( + helper.computeViewableItems(props, 1, 250, {getCellMetrics}), + ).toEqual([1, 2]); + helper = new ViewabilityHelper({itemVisiblePercentThreshold: 10}); expect( - helper.computeViewableItems(props, 184, 20, getFrameMetrics), + helper.computeViewableItems(props, 184, 20, {getCellMetrics}), ).toEqual([1]); expect( - helper.computeViewableItems(props, 185, 20, getFrameMetrics), + helper.computeViewableItems(props, 185, 20, {getCellMetrics}), ).toEqual([1, 2]); expect( - helper.computeViewableItems(props, 186, 20, getFrameMetrics), + helper.computeViewableItems(props, 186, 20, {getCellMetrics}), ).toEqual([2]); }); }); @@ -181,7 +181,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -195,7 +195,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -204,7 +204,7 @@ describe('onUpdate', function () { props, 100, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -228,7 +228,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -242,7 +242,7 @@ describe('onUpdate', function () { props, 100, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -260,7 +260,7 @@ describe('onUpdate', function () { props, 200, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -287,7 +287,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -321,7 +321,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -329,7 +329,7 @@ describe('onUpdate', function () { props, 300, // scroll past item 'a' 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -362,7 +362,7 @@ describe('onUpdate', function () { props, 0, 100, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -374,7 +374,7 @@ describe('onUpdate', function () { props, 20, 100, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -401,7 +401,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); @@ -426,7 +426,7 @@ describe('onUpdate', function () { props, 0, 200, - getFrameMetrics, + {getCellMetrics}, createViewToken, onViewableItemsChanged, ); diff --git a/packages/virtualized-lists/Lists/__tests__/VirtualizeUtils-test.js b/packages/virtualized-lists/Lists/__tests__/VirtualizeUtils-test.js index 29f169eb63048f..9af659dac5ba9d 100644 --- a/packages/virtualized-lists/Lists/__tests__/VirtualizeUtils-test.js +++ b/packages/virtualized-lists/Lists/__tests__/VirtualizeUtils-test.js @@ -42,14 +42,19 @@ describe('newRangeCount', function () { describe('elementsThatOverlapOffsets', function () { it('handles fixed length', function () { const offsets = [0, 250, 350, 450]; - function getFrameMetrics(index: number) { + function getCellMetricsApprox(index: number) { return { length: 100, offset: 100 * index, }; } expect( - elementsThatOverlapOffsets(offsets, fakeProps(100), getFrameMetrics, 1), + elementsThatOverlapOffsets( + offsets, + fakeProps(100), + {getCellMetricsApprox}, + 1, + ), ).toEqual([0, 2, 3, 4]); }); it('handles variable length', function () { @@ -65,21 +70,26 @@ describe('elementsThatOverlapOffsets', function () { elementsThatOverlapOffsets( offsets, fakeProps(frames.length), - ii => frames[ii], + {getCellMetricsApprox: ii => frames[ii]}, 1, ), ).toEqual([1, 1, 3]); }); it('handles frame boundaries', function () { const offsets = [0, 100, 200, 300]; - function getFrameMetrics(index: number) { + function getCellMetricsApprox(index: number) { return { length: 100, offset: 100 * index, }; } expect( - elementsThatOverlapOffsets(offsets, fakeProps(100), getFrameMetrics, 1), + elementsThatOverlapOffsets( + offsets, + fakeProps(100), + {getCellMetricsApprox}, + 1, + ), ).toEqual([0, 0, 1, 2]); }); it('handles out of bounds', function () { @@ -93,7 +103,7 @@ describe('elementsThatOverlapOffsets', function () { elementsThatOverlapOffsets( offsets, fakeProps(frames.length), - ii => frames[ii], + {getCellMetricsApprox: ii => frames[ii]}, 1, ), ).toEqual([undefined, 1]); diff --git a/scripts/__tests__/npm-utils-test.js b/scripts/__tests__/npm-utils-test.js index bcf10031df3a85..b8013fcebd3e74 100644 --- a/scripts/__tests__/npm-utils-test.js +++ b/scripts/__tests__/npm-utils-test.js @@ -7,7 +7,11 @@ * @format */ -const {getPackageVersionStrByTag, publishPackage} = require('../npm-utils'); +const { + applyPackageVersions, + getPackageVersionStrByTag, + publishPackage, +} = require('../npm-utils'); const execMock = jest.fn(); jest.mock('shelljs', () => ({ @@ -20,6 +24,47 @@ describe('npm-utils', () => { jest.resetAllMocks(); }); + describe('applyPackageVersions', () => { + it('should replace package.json with dependencies', () => { + const originalPackageJson = { + name: 'my-package', + dependencies: { + 'my-dependency-a': 'nightly', + 'my-dependency-b': '^1.2.3', + }, + devDependencies: { + 'my-dev-dependency-a': 'nightly', + 'my-dev-dependency-b': '^1.2.3', + }, + someOtherField: { + 'my-dependency-a': 'should-be-untouched', + }, + }; + + const dependencies = { + 'my-dependency-a': '0.72.0-nightly-shortcommit', + 'my-dev-dependency-a': 'updated-version', + 'my-non-existant-dep': 'some-version', + }; + + const package = applyPackageVersions(originalPackageJson, dependencies); + expect(package).toEqual({ + name: 'my-package', + dependencies: { + 'my-dependency-a': '0.72.0-nightly-shortcommit', + 'my-dependency-b': '^1.2.3', + }, + devDependencies: { + 'my-dev-dependency-a': 'updated-version', + 'my-dev-dependency-b': '^1.2.3', + }, + someOtherField: { + 'my-dependency-a': 'should-be-untouched', + }, + }); + }); + }); + describe('getPackageVersionStrByTag', () => { it('should return package version string', () => { execMock.mockImplementationOnce(() => ({code: 0, stdout: '0.34.2 \n'})); diff --git a/scripts/__tests__/publish-npm-test.js b/scripts/__tests__/publish-npm-test.js index 32bc613ff98628..3af1fa8bb3473e 100644 --- a/scripts/__tests__/publish-npm-test.js +++ b/scripts/__tests__/publish-npm-test.js @@ -10,7 +10,9 @@ const execMock = jest.fn(); const echoMock = jest.fn(); const exitMock = jest.fn(); +const consoleErrorMock = jest.fn(); const isTaggedLatestMock = jest.fn(); +const setReactNativeVersionMock = jest.fn(); const publishAndroidArtifactsToMavenMock = jest.fn(); const env = process.env; @@ -33,11 +35,13 @@ jest generateAndroidArtifacts: jest.fn(), publishAndroidArtifactsToMaven: publishAndroidArtifactsToMavenMock, })) - .mock('../monorepo/publish-nightly-for-each-changed-package'); + .mock('./../set-rn-version', () => setReactNativeVersionMock) + .mock('../monorepo/get-and-update-nightlies'); const date = new Date('2023-04-20T23:52:39.543Z'); const publishNpm = require('../publish-npm'); +let consoleError; describe('publish-npm', () => { beforeAll(() => { @@ -47,8 +51,14 @@ describe('publish-npm', () => { afterAll(() => { jest.useRealTimers(); }); + beforeEach(() => { + consoleError = console.error; + console.error = consoleErrorMock; + }); + afterEach(() => { process.env = env; + console.error = consoleError; }); afterEach(() => { @@ -58,8 +68,6 @@ describe('publish-npm', () => { describe('dry-run', () => { it('should set version and not publish', () => { - execMock.mockReturnValueOnce({code: 0}); - publishNpm('dry-run'); expect(exitMock).toHaveBeenCalledWith(0); @@ -67,10 +75,11 @@ describe('publish-npm', () => { expect(echoMock).toHaveBeenCalledWith( 'Skipping `npm publish` because --dry-run is set.', ); - expect(execMock).toHaveBeenCalledWith( - 'node scripts/set-rn-version.js --to-version 1000.0.0-currentco --build-type dry-run', + expect(setReactNativeVersionMock).toBeCalledWith( + '1000.0.0-currentco', + null, + 'dry-run', ); - expect(execMock.mock.calls).toHaveLength(1); }); }); @@ -78,7 +87,6 @@ describe('publish-npm', () => { it('should publish', () => { execMock .mockReturnValueOnce({stdout: '0.81.0-rc.1\n', code: 0}) - .mockReturnValueOnce({code: 0}) .mockReturnValueOnce({code: 0}); const expectedVersion = '0.82.0-nightly-20230420-currentco'; @@ -91,22 +99,19 @@ describe('publish-npm', () => { expect(execMock.mock.calls[0][0]).toBe( `npm view react-native@next version`, ); - expect(execMock.mock.calls[1][0]).toBe( - `node scripts/set-rn-version.js --to-version ${expectedVersion} --build-type nightly`, - ); - expect(execMock.mock.calls[2][0]).toBe('npm publish --tag nightly'); + expect(execMock.mock.calls[1][0]).toBe('npm publish --tag nightly'); expect(echoMock).toHaveBeenCalledWith( `Published to npm ${expectedVersion}`, ); expect(exitMock).toHaveBeenCalledWith(0); - expect(execMock.mock.calls).toHaveLength(3); }); it('should fail to set version', () => { - execMock - .mockReturnValueOnce({stdout: '0.81.0-rc.1\n', code: 0}) - .mockReturnValueOnce({code: 1}); + execMock.mockReturnValueOnce({stdout: '0.81.0-rc.1\n', code: 0}); const expectedVersion = '0.82.0-nightly-20230420-currentco'; + setReactNativeVersionMock.mockImplementation(() => { + throw new Error('something went wrong'); + }); publishNpm('nightly'); @@ -114,14 +119,10 @@ describe('publish-npm', () => { expect(execMock.mock.calls[0][0]).toBe( `npm view react-native@next version`, ); - expect(execMock.mock.calls[1][0]).toBe( - `node scripts/set-rn-version.js --to-version ${expectedVersion} --build-type nightly`, - ); - expect(echoMock).toHaveBeenCalledWith( + expect(consoleErrorMock).toHaveBeenCalledWith( `Failed to set version number to ${expectedVersion}`, ); expect(exitMock).toHaveBeenCalledWith(1); - expect(execMock.mock.calls).toHaveLength(2); }); }); diff --git a/scripts/__tests__/set-rn-version-test.js b/scripts/__tests__/set-rn-version-test.js index b913e4638b9a3f..d01ee595905939 100644 --- a/scripts/__tests__/set-rn-version-test.js +++ b/scripts/__tests__/set-rn-version-test.js @@ -7,32 +7,23 @@ * @format */ -const execMock = jest.fn(); const echoMock = jest.fn(); -const exitMock = jest.fn(); const catMock = jest.fn(); const sedMock = jest.fn(); const writeFileSyncMock = jest.fn(); +const updateTemplatePackageMock = jest.fn(); jest .mock('shelljs', () => ({ - exec: execMock, echo: echoMock, - exit: exitMock, cat: catMock, sed: sedMock, })) - .mock('./../scm-utils', () => ({ - saveFiles: jest.fn(), - })) - .mock('path', () => ({ - join: () => '../packages/react-native', - })) + .mock('./../update-template-package', () => updateTemplatePackageMock) .mock('fs', () => ({ writeFileSync: writeFileSyncMock, mkdtempSync: () => './rn-set-version/', - })) - .mock('os'); + })); const setReactNativeVersion = require('../set-rn-version'); @@ -45,7 +36,7 @@ describe('set-rn-version', () => { it('should set nightly version', () => { catMock.mockImplementation(path => { if (path === 'packages/react-native/package.json') { - return '{"name": "myPackage", "version": 2}'; + return '{"name": "myPackage", "version": 2, "dependencies": {"@react-native/package-a": "nightly", "@react-native/package-b": "^0.73.0"}}'; } else if ( path === 'scripts/versiontemplates/ReactNativeVersion.java.template' || path === 'scripts/versiontemplates/RCTVersion.m.template' || @@ -58,13 +49,13 @@ describe('set-rn-version', () => { } }); - execMock - .mockReturnValueOnce({code: 0}) - .mockReturnValueOnce({stdout: 'line1\nline2\nline3\n'}); sedMock.mockReturnValueOnce({code: 0}); const version = '0.81.0-nightly-29282302-abcd1234'; - setReactNativeVersion(version, 'nightly'); + const nightlyVersions = { + '@react-native/package-a': version, + }; + setReactNativeVersion(version, nightlyVersions, 'nightly'); expect(sedMock).toHaveBeenCalledWith( '-i', @@ -91,14 +82,19 @@ describe('set-rn-version', () => { expect(writeFileSyncMock.mock.calls[4][0]).toBe( 'packages/react-native/package.json', ); - expect(writeFileSyncMock.mock.calls[4][1]).toBe( - `{\n "name": "myPackage",\n "version": "${version}"\n}`, - ); - - expect(exitMock.mock.calls[0][0]).toBe(0); - expect(execMock.mock.calls[0][0]).toBe( - `node scripts/set-rn-template-version.js ${version}`, - ); + expect(writeFileSyncMock.mock.calls[4][1]).toBe(`{ + "name": "myPackage", + "version": "${version}", + "dependencies": { + "@react-native/package-a": "0.81.0-nightly-29282302-abcd1234", + "@react-native/package-b": "^0.73.0" + } +}`); + + expect(updateTemplatePackageMock).toHaveBeenCalledWith({ + '@react-native/package-a': '0.81.0-nightly-29282302-abcd1234', + 'react-native': version, + }); }); it('should set release version', () => { @@ -109,13 +105,10 @@ describe('set-rn-version', () => { return 'exports.version = {major: ${major}, minor: ${minor}, patch: ${patch}, prerelease: ${prerelease}}'; }); - execMock - .mockReturnValueOnce({code: 0}) - .mockReturnValueOnce({stdout: 'line1\nline2\nline3\n'}); sedMock.mockReturnValueOnce({code: 0}); const version = '0.81.0'; - setReactNativeVersion(version, 'release'); + setReactNativeVersion(version, null, 'release'); expect(sedMock).toHaveBeenCalledWith( '-i', @@ -137,46 +130,8 @@ describe('set-rn-version', () => { `{\n "name": "myPackage",\n "version": "${version}"\n}`, ); - expect(exitMock.mock.calls[0][0]).toBe(0); - expect(execMock.mock.calls[0][0]).toBe( - `node scripts/set-rn-template-version.js ${version}`, - ); - expect(execMock.mock.calls[1][0]).toBe( - `diff -r ./rn-set-version/ . | grep '^[>]' | grep -c ${version} `, - ); - }); - - it('should fail validation', () => { - catMock.mockReturnValue('{}'); - - execMock - .mockReturnValueOnce({code: 0}) - .mockReturnValueOnce({stdout: 'line1\nline2\n'}); - sedMock.mockReturnValueOnce({code: 0}); - const filesToValidate = [ - 'packages/react-native/package.json', - 'packages/react-native/ReactAndroid/gradle.properties', - 'packages/react-native/template/package.json', - ]; - - const version = '0.81.0'; - setReactNativeVersion(version, 'release'); - - expect(exitMock).toHaveBeenCalledWith(0); - expect(echoMock).toHaveBeenNthCalledWith( - 1, - 'The tmp versioning folder is ./rn-set-version/', - ); - - expect(echoMock).toHaveBeenNthCalledWith(2, 'WARNING:'); - - expect(echoMock.mock.calls[2][0]).toBe( - `Failed to update all the files: [${filesToValidate.join( - ', ', - )}] must have versions in them`, - ); - expect(echoMock.mock.calls[3][0]).toBe( - `These files already had version ${version} set.`, - ); + expect(updateTemplatePackageMock).toHaveBeenCalledWith({ + 'react-native': version, + }); }); }); diff --git a/scripts/monorepo/__tests__/publish-nightly-for-each-changed-package-test.js b/scripts/monorepo/__tests__/get-and-update-nightlies-test.js similarity index 54% rename from scripts/monorepo/__tests__/publish-nightly-for-each-changed-package-test.js rename to scripts/monorepo/__tests__/get-and-update-nightlies-test.js index b7d420809bfec6..5453b0c7344997 100644 --- a/scripts/monorepo/__tests__/publish-nightly-for-each-changed-package-test.js +++ b/scripts/monorepo/__tests__/get-and-update-nightlies-test.js @@ -7,11 +7,12 @@ * @format */ -const publishNightlyForEachChangedPackage = require('../publish-nightly-for-each-changed-package'); +const getAndUpdateNightlies = require('../get-and-update-nightlies'); +const NPM_NIGHTLY_VERSION = 'published-nightly-version'; const mockPackages = [ { - packageManifest: {name: 'packageA', version: 'local-version'}, + packageManifest: {name: '@react-native/packageA', version: 'local-version'}, packageAbsolutePath: '/some/place/packageA', packageRelativePathFromRoot: './place/packageA', }, @@ -44,13 +45,13 @@ jest restore: jest.fn(), })) .mock('../../npm-utils', () => ({ - getPackageVersionStrByTag: () => 'published-nightly-version', + getPackageVersionStrByTag: () => NPM_NIGHTLY_VERSION, diffPackages: diffPackagesMock, publishPackage: publishPackageMock, pack: jest.fn(), })); -describe('publishNightlyForEachChangedPackage', () => { +describe('getAndUpdateNightlies', () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -60,16 +61,16 @@ describe('publishNightlyForEachChangedPackage', () => { publishPackageMock.mockImplementationOnce(() => ({code: 0})); diffPackagesMock.mockImplementationOnce(() => 'some-file-name.js\n'); - publishNightlyForEachChangedPackage(nightlyVersion); + const latestNightlies = getAndUpdateNightlies(nightlyVersion); // ensure we set the version of the last published nightly (for diffing) expect(writeFileSyncMock.mock.calls[0][1]).toBe( - '{\n "name": "packageA",\n "version": "published-nightly-version"\n}\n', + '{\n "name": "@react-native/packageA",\n "version": "published-nightly-version"\n}\n', ); expect(diffPackagesMock).toBeCalledWith( - 'packageA@nightly', - 'packageA-published-nightly-version.tgz', + '@react-native/packageA@nightly', + 'react-native-packageA-published-nightly-version.tgz', { cwd: '/some/place/packageA', }, @@ -77,25 +78,60 @@ describe('publishNightlyForEachChangedPackage', () => { // when determining that we DO want to publish, ensure we update the version to the provded nightly version we want to use expect(writeFileSyncMock.mock.calls[1][1]).toBe( - `{\n "name": "packageA",\n "version": "${nightlyVersion}"\n}\n`, + `{\n "name": "@react-native/packageA",\n "version": "${nightlyVersion}"\n}\n`, ); expect(publishPackageMock).toBeCalled(); + + // Expect the map returned to accurately list the latest nightly version + expect(latestNightlies).toEqual({ + '@react-native/packageA': '0.73.0-nightly-202108-shortcommit', + }); + }); + describe('fails to publish', () => { + let consoleError; + beforeEach(() => { + consoleError = console.error; + console.error = jest.fn(); + }); + + afterEach(() => { + console.error = consoleError; + }); + + it('doesnt update nightly version when fails to publish', () => { + const nightlyVersion = '0.73.0-nightly-202108-shortcommit'; + publishPackageMock.mockImplementationOnce(() => ({ + code: 1, + stderr: 'Some error about it failing to publish', + })); + diffPackagesMock.mockImplementationOnce(() => 'some-file-name.js\n'); + + const latestNightlies = getAndUpdateNightlies(nightlyVersion); + + // Expect the map returned to accurately list the latest nightly version + expect(latestNightlies).toEqual({}); + }); }); it('doesnt publish because no changes', () => { const nightlyVersion = '0.73.0-nightly-202108-shortcommit'; diffPackagesMock.mockImplementationOnce(() => '\n'); - publishNightlyForEachChangedPackage(nightlyVersion); + const latestNightlies = getAndUpdateNightlies(nightlyVersion); expect(writeFileSyncMock.mock.calls[0][1]).toBe( - '{\n "name": "packageA",\n "version": "published-nightly-version"\n}\n', + '{\n "name": "@react-native/packageA",\n "version": "published-nightly-version"\n}\n', ); // in this test, we expect there to be no differences between last published nightly and local // so we never update the version and we don't publish expect(writeFileSyncMock.mock.calls.length).toBe(1); expect(publishPackageMock).not.toBeCalled(); + + // Since we don't update, we expect the map to list the latest nightly on npm + expect(latestNightlies).toEqual({ + '@react-native/packageA': 'published-nightly-version', + }); }); }); diff --git a/scripts/monorepo/publish-nightly-for-each-changed-package.js b/scripts/monorepo/get-and-update-nightlies.js similarity index 88% rename from scripts/monorepo/publish-nightly-for-each-changed-package.js rename to scripts/monorepo/get-and-update-nightlies.js index 38ee594e7e4075..c38c79a705351d 100644 --- a/scripts/monorepo/publish-nightly-for-each-changed-package.js +++ b/scripts/monorepo/get-and-update-nightlies.js @@ -61,12 +61,15 @@ function hasChanges( } /** - * Publish nightlies for monorepo packages that changed since last publish. - * This is called by `react-native`'s nightly job. + * Get the latest nightly version of each monorepo package and publishes a new nightly if there have been updates. + * Returns a map of monorepo packages and its latest nightly version. * + * This is called by `react-native`'s nightly job. * Note: This does not publish `package/react-native`'s nightly. That is handled in `publish-npm`. */ -function publishNightlyForEachChangedPackage(nightlyVersion) { +function getAndUpdateNightlies(nightlyVersion) { + const nightlyVersions = {}; + forEachPackage( (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { if (packageManifest.private) { @@ -103,6 +106,7 @@ function publishNightlyForEachChangedPackage(nightlyVersion) { console.log( `Detected no changes in ${packageManifest.name}@nightly since last publish. Skipping.`, ); + nightlyVersions[packageManifest.name] = lastPublishedNightlyVersion; return; } @@ -127,9 +131,11 @@ function publishNightlyForEachChangedPackage(nightlyVersion) { console.log( `\u2705 Successfully published new version of ${packageManifest.name}`, ); + nightlyVersions[packageManifest.name] = nightlyVersion; } }, ); + return nightlyVersions; } -module.exports = publishNightlyForEachChangedPackage; +module.exports = getAndUpdateNightlies; diff --git a/scripts/npm-utils.js b/scripts/npm-utils.js index 5586d7e3372952..d92de469014203 100644 --- a/scripts/npm-utils.js +++ b/scripts/npm-utils.js @@ -56,7 +56,35 @@ function pack(packagePath) { } } +/** + * `package` is an object form of package.json + * `dependencies` is a map of dependency to version string + * + * This replaces both dependencies and devDependencies in package.json + */ +function applyPackageVersions(originalPackageJson, packageVersions) { + const packageJson = {...originalPackageJson}; + + for (const name of Object.keys(packageVersions)) { + if ( + packageJson.dependencies != null && + packageJson.dependencies[name] != null + ) { + packageJson.dependencies[name] = packageVersions[name]; + } + + if ( + packageJson.devDependencies != null && + packageJson.devDependencies[name] != null + ) { + packageJson.devDependencies[name] = packageVersions[name]; + } + } + return packageJson; +} + module.exports = { + applyPackageVersions, getPackageVersionStrByTag, publishPackage, diffPackages, diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index 653a3782c56925..b6cb1a12525a8c 100755 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -9,7 +9,7 @@ 'use strict'; -const {exec, echo, exit} = require('shelljs'); +const {echo, exit} = require('shelljs'); const {parseVersion} = require('./version-utils'); const {getPackageVersionStrByTag, publishPackage} = require('./npm-utils'); const { @@ -17,7 +17,8 @@ const { getCurrentCommit, isTaggedLatest, } = require('./scm-utils'); -const publishNightlyForEachChangedPackage = require('./monorepo/publish-nightly-for-each-changed-package'); +const getAndUpdateNightlies = require('./monorepo/get-and-update-nightlies'); +const setReactNativeVersion = require('./set-rn-version'); const { generateAndroidArtifacts, publishAndroidArtifactsToMaven, @@ -134,25 +135,24 @@ function getNpmInfo(buildType) { function publishNpm(buildType) { const {version, tag} = getNpmInfo(buildType); - // Set version number in various files (package.json, gradle.properties etc) - // For non-nightly, non-dry-run, CircleCI job `prepare_package_for_release` does this + // Here we update the react-native package and template package with the right versions + // For releases, CircleCI job `prepare_package_for_release` handles this if (buildType === 'nightly' || buildType === 'dry-run') { - // Sets the version for package/react-native - if ( - exec( - `node scripts/set-rn-version.js --to-version ${version} --build-type ${buildType}`, - ).code - ) { - echo(`Failed to set version number to ${version}`); + // Publish monorepo nightlies if there are updates, returns nightly versions for each + const monorepoNightlyVersions = + buildType === 'nightly' ? getAndUpdateNightlies(version) : null; + + try { + // Update the react-native and template packages with the react-native version + // and nightly versions of monorepo deps + setReactNativeVersion(version, monorepoNightlyVersions, buildType); + } catch (e) { + console.error(`Failed to set version number to ${version}`); + console.error(e); return exit(1); } } - // set and publish the relevant monorepo packages - if (buildType === 'nightly') { - publishNightlyForEachChangedPackage(version); - } - generateAndroidArtifacts(version); // Write version number to the build folder diff --git a/scripts/run-ci-e2e-tests.js b/scripts/run-ci-e2e-tests.js index 050577579e4895..fbab0b3bc71160 100644 --- a/scripts/run-ci-e2e-tests.js +++ b/scripts/run-ci-e2e-tests.js @@ -64,7 +64,11 @@ try { } describe('Create react-native package'); - if (exec('node ./scripts/set-rn-version.js --version 1000.0.0').code) { + if ( + exec( + 'node ./scripts/set-rn-version.js --to-version 1000.0.0 --build-type dry-run', + ).code + ) { echo('Failed to set version and update package.json ready for release'); exitCode = 1; throw Error(exitCode); diff --git a/scripts/set-rn-template-version.js b/scripts/set-rn-template-version.js deleted file mode 100755 index de8e00f745537f..00000000000000 --- a/scripts/set-rn-template-version.js +++ /dev/null @@ -1,33 +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 fs = require('fs'); -const path = require('path'); - -const version = process.argv[2]; - -if (!version) { - console.error('Please provide a react-native version.'); - process.exit(1); -} - -const jsonPath = path.join( - __dirname, - '../packages/react-native/template/package.json', -); - -let templatePackageJson = require(jsonPath); -templatePackageJson.dependencies['react-native'] = version; -fs.writeFileSync( - jsonPath, - JSON.stringify(templatePackageJson, null, 2) + '\n', - 'utf-8', -); diff --git a/scripts/set-rn-version.js b/scripts/set-rn-version.js index a5f99213740ab0..836fa7d96f319d 100755 --- a/scripts/set-rn-version.js +++ b/scripts/set-rn-version.js @@ -10,12 +10,11 @@ 'use strict'; const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const {cat, echo, exec, exit, sed} = require('shelljs'); +const {cat, echo, exit, sed} = require('shelljs'); const yargs = require('yargs'); const {parseVersion, validateBuildType} = require('./version-utils'); -const {saveFiles} = require('./scm-utils'); +const updateTemplatePackage = require('./update-template-package'); +const {applyPackageVersions} = require('./npm-utils'); /** * This script updates relevant React Native files with supplied version: @@ -31,13 +30,32 @@ if (require.main === module) { type: 'string', required: true, }) + .option('d', { + alias: 'dependency-versions', + type: 'string', + describe: + 'JSON string of package versions. Ex. "{"react-native":"0.64.1"}"', + default: null, + }) + .coerce('d', dependencyVersions => { + if (dependencyVersions == null) { + return null; + } + return JSON.parse(dependencyVersions); + }) .option('b', { alias: 'build-type', type: 'string', + choices: ['dry-run', 'nightly', 'release'], required: true, }).argv; - setReactNativeVersion(argv.toVersion, argv.buildType); + setReactNativeVersion( + argv.toVersion, + argv.dependencyVersions, + argv.buildType, + ); + exit(0); } function setSource({major, minor, patch, prerelease}) { @@ -108,8 +126,15 @@ function setGradle({version}) { } } -function setPackage({version}) { - const packageJson = JSON.parse(cat('packages/react-native/package.json')); +function setPackage({version}, dependencyVersions) { + const originalPackageJson = JSON.parse( + cat('packages/react-native/package.json'), + ); + const packageJson = + dependencyVersions != null + ? applyPackageVersions(originalPackageJson, dependencyVersions) + : originalPackageJson; + packageJson.version = version; fs.writeFileSync( @@ -119,58 +144,22 @@ function setPackage({version}) { ); } -function setTemplatePackage({version}) { - const result = exec(`node scripts/set-rn-template-version.js ${version}`); - if (result.code) { - echo("Failed to update React Native template's version of React Native"); - throw result.stderr; - } -} - -function setReactNativeVersion(argVersion, buildType) { +function setReactNativeVersion(argVersion, dependencyVersions, buildType) { validateBuildType(buildType); const version = parseVersion(argVersion, buildType); - // Create tmp folder for copies of files to verify files have changed - const filesToValidate = [ - 'packages/react-native/package.json', - 'packages/react-native/ReactAndroid/gradle.properties', - 'packages/react-native/template/package.json', - ]; - const tmpVersioningFolder = fs.mkdtempSync( - path.join(os.tmpdir(), 'rn-set-version'), - ); - echo(`The tmp versioning folder is ${tmpVersioningFolder}`); - saveFiles(tmpVersioningFolder); - setSource(version); - setPackage(version); - setTemplatePackage(version); - setGradle(version); + setPackage(version, dependencyVersions); - // Validate changes - // We just do a git diff and check how many times version is added across files - const numberOfChangedLinesWithNewVersion = exec( - `diff -r ${tmpVersioningFolder} . | grep '^[>]' | grep -c ${version.version} `, - {silent: true}, - ).stdout.trim(); - - if (+numberOfChangedLinesWithNewVersion !== filesToValidate.length) { - // TODO: the logic that checks whether all the changes have been applied - // is missing several files. For example, it is not checking Ruby version nor that - // the Objecive-C files, the codegen and other files are properly updated. - // We are going to work on this in another PR. - echo('WARNING:'); - echo( - `Failed to update all the files: [${filesToValidate.join( - ', ', - )}] must have versions in them`, - ); - echo(`These files already had version ${version.version} set.`); - } + const templateDependencyVersions = { + 'react-native': version.version, + ...(dependencyVersions != null ? dependencyVersions : {}), + }; + updateTemplatePackage(templateDependencyVersions); - return exit(0); + setGradle(version); + return; } module.exports = setReactNativeVersion; diff --git a/scripts/test-e2e-local.js b/scripts/test-e2e-local.js index a6a891df5ac62b..6a8f679f29d249 100644 --- a/scripts/test-e2e-local.js +++ b/scripts/test-e2e-local.js @@ -17,11 +17,12 @@ */ const {exec, exit, pushd, popd, pwd, cd, cp} = require('shelljs'); +const updateTemplatePackage = require('../scripts/update-template-package'); const yargs = require('yargs'); const fs = require('fs'); const { - launchAndroidEmulator, + maybeLaunchAndroidEmulator, isPackagerRunning, launchPackagerInSeparateWindow, } = require('./testing-utils'); @@ -106,7 +107,7 @@ if (argv.target === 'RNTester') { } else { // we do the android path here - launchAndroidEmulator(); + maybeLaunchAndroidEmulator(); console.info( `We're going to test the ${ @@ -208,7 +209,9 @@ if (argv.target === 'RNTester') { ); const localNodeTGZPath = `${reactNativePackagePath}/react-native-${releaseVersion}.tgz`; - exec(`node scripts/set-rn-template-version.js "file:${localNodeTGZPath}"`); + updateTemplatePackage({ + 'react-native': `file:${localNodeTGZPath}`, + }); // create locally the node module exec('npm pack', {cwd: reactNativePackagePath}); diff --git a/scripts/testing-utils.js b/scripts/testing-utils.js index a7d48b7d8b73a2..c3ce4f78dde413 100644 --- a/scripts/testing-utils.js +++ b/scripts/testing-utils.js @@ -60,7 +60,20 @@ function tryLaunchEmulator() { }; } -function launchAndroidEmulator() { +function hasConnectedDevice() { + const physicalDevices = exec('adb devices | grep -v emulator', {silent: true}) + .stdout.trim() + .split('\n') + .slice(1); + return physicalDevices.length > 0; +} + +function maybeLaunchAndroidEmulator() { + if (hasConnectedDevice) { + console.info('Already have a device connected. Skip launching emulator.'); + return; + } + const result = tryLaunchEmulator(); if (result.success) { console.info('Successfully launched emulator.'); @@ -103,7 +116,7 @@ function launchPackagerInSeparateWindow(folderPath) { } module.exports = { - launchAndroidEmulator, + maybeLaunchAndroidEmulator, isPackagerRunning, launchPackagerInSeparateWindow, }; diff --git a/scripts/update-template-package.js b/scripts/update-template-package.js new file mode 100755 index 00000000000000..9443707db562e8 --- /dev/null +++ b/scripts/update-template-package.js @@ -0,0 +1,54 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const {applyPackageVersions} = require('./npm-utils'); + +/** + * Updates the react-native template package.json with + * dependencies in `dependencyMap`. + * + * `dependencyMap` is a dict of package name to its version + * ex. {"react-native": "0.23.0", "other-dep": "nightly"} + */ +function updateTemplatePackage(dependencyMap) { + const jsonPath = path.join( + __dirname, + '../packages/react-native/template/package.json', + ); + const templatePackageJson = require(jsonPath); + + const updatedPackageJson = applyPackageVersions( + templatePackageJson, + dependencyMap, + ); + + fs.writeFileSync( + jsonPath, + JSON.stringify(updatedPackageJson, null, 2) + '\n', + 'utf-8', + ); +} + +if (require.main === module) { + const dependencyMapStr = process.argv[2]; + if (!dependencyMapStr) { + console.error( + 'Please provide a json string of package name and their version. Ex. \'{"packageName":"0.23.0"}\'', + ); + process.exit(1); + } + + updateTemplatePackage(JSON.parse(dependencyMapStr)); +} + +module.exports = updateTemplatePackage; diff --git a/yarn.lock b/yarn.lock index a969b06ec24bcd..42a07e676c5766 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4925,10 +4925,10 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== -flow-bin@^0.207.0: - version "0.207.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.207.0.tgz#71c6a21214c57f3e36e03b32033dd0358452499b" - integrity sha512-jAet+lhdrvpk4kjonZ6r/v09rkyaPQX+E5gio+HesEBvsOsCWfnuadBRQZihTM4+szjJPWEkITfrCurI/aCqEQ== +flow-bin@^0.209.0: + version "0.209.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.209.0.tgz#616a002b94ef35e1b083add9a6d27dbe387bf4f0" + integrity sha512-HRc6+bKE8AN23SnuKaxdUjcQcjaIp6pksrGJ6pltFO5tIEvZmPrbT99P7Yb3ybqwcKU/Ry8yfJbuW92Ed2V3nw== flow-enums-runtime@^0.0.6: version "0.0.6" @@ -5311,6 +5311,11 @@ hermes-estree@0.12.0: resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.12.0.tgz#8a289f9aee854854422345e6995a48613bac2ca8" integrity sha512-+e8xR6SCen0wyAKrMT3UD0ZCCLymKhRgjEB5sS28rKiFir/fXgLoeRilRUssFCILmGHb+OvHDUlhxs0+IEyvQw== +hermes-estree@0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.12.1.tgz#74901ee351387fecbf3c683c90b1fa7d22f1c6f0" + integrity sha512-IWnP3rEZnuEq64IGM/sNsp+QCQcCAAu5TMallJ7bpUw0YUfk5q6cA7tvBGo/D0kGyo5jASc4Yp/CQCsLSSMfGQ== + hermes-estree@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.8.0.tgz#530be27243ca49f008381c1f3e8b18fb26bf9ec0" @@ -5323,6 +5328,13 @@ hermes-parser@0.12.0: dependencies: hermes-estree "0.12.0" +hermes-parser@0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.12.1.tgz#e60595f05ecd13026c614ca5d9db2eaefe971301" + integrity sha512-53aep6osCq1GiSIlbe7ltPD9v0GeAUtGlaMhgKexGjePoI66GnalLR5aPeuIZbExBQAb+af/kiXT3yxBweuXUA== + dependencies: + hermes-estree "0.12.1" + hermes-parser@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.8.0.tgz#116dceaba32e45b16d6aefb5c4c830eaeba2d257" @@ -7589,6 +7601,14 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" +prettier-plugin-hermes-parser@0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/prettier-plugin-hermes-parser/-/prettier-plugin-hermes-parser-0.12.1.tgz#5c1ec4c18cea9c4b88478fa27f172e3f849b40b6" + integrity sha512-Letp5jjP/lpw/zpqKmtuDytQ2fP5M5dExcdUqIZknOwf+cXLirHaDddxz3gqzUJrpU4WzuaC0x3nfcH1Hi0Tng== + dependencies: + hermes-estree "0.12.1" + hermes-parser "0.12.1" + prettier@2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"