Compare commits

..
Author SHA1 Message Date
Nicola CortiandFacebook GitHub Bot 5983edd21b Migrate ClipboardModuleTest to use BridgelessReactContext (#53131)
Summary:
This test was still using the old `BridgeReactContext`, I'm migrating it to `BridgelessReactContext`.

## Changelog:

[INTERNAL] -


Test Plan: CI

Reviewed By: mdvacca

Differential Revision: D79801564

Pulled By: cortinico
2025-08-07 09:35:26 -07:00
generatedunixname499836121andFacebook GitHub Bot bf13ecba7f Apply fixup patch to fbsource
Summary:
This is an automatically generated fixup patch to bring fbsource back into sync with
facebook/react on GitHub. Please land this patch as soon as possible, as the difference
reflected on here is already on GitHub and future changes may depend on these
changes!

<< DO NOT EDIT BELOW THIS LINE >>
diff-train-skip-merge
diff-train-source-id: 7da6014535999d256d2846aadbb7fc1373d76253

Generated by: https://www.internalfb.com/intern/sandcastle/job/18014400563434324/

GitHub Repo: facebook/react

Changelog: [Internal]

Reviewed By: jackpope

Differential Revision: D79754431

fbshipit-source-id: 027428142f36683b07cb66112335f75ecfb5dd12
2025-08-07 09:27:23 -07:00
Andrew DatsenkoandFacebook GitHub Bot 5f8807acc2 Add public API test (#53096)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53096

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79726634

fbshipit-source-id: 1474cbfb635250e06f4f898338ef0874bc488ed1
2025-08-07 09:04:25 -07:00
Nicola CortiandFacebook GitHub Bot 026e22bb8d Deprecate the DefaultDevSupportManagerFactory.create() method used for Old Arch (#53137)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53137

One of those 2 methods can be deprecated as it was used only for old architecture.
We'll be removing it at some point in the future.

Changelog:
[Android] [Deprecated] - DefaultDevSupportManagerFactory.create() method used for Old Arch

Reviewed By: rshest

Differential Revision: D79806116

fbshipit-source-id: ad2d5515f93bb85e3b7c495b369078f4c66d143b
2025-08-07 08:56:26 -07:00
Mateo GuzmánandFacebook GitHub Bot f1894393ca Initial Kotlin setup and migrate YogaConstants (#53133)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53133

# Changelog:
[Internal] -

As part of the ongoing effort to migrate the React Native codebase to Kotlin, this PR introduces the initial setup required for Kotlin support in Yoga.

- Added initial basic Kotlin configuration to the project.
- Migrated `YogaConstants` as an initial file to try out the first migration steps.

X-link: https://github.com/facebook/yoga/pull/1829

Test Plan:
- Tested the migrated class directly against facebook/react-native, see the PR [here](https://github.com/facebook/react-native/pull/52998).
- Run: `./gradlew :yoga:assembleDebug` & `./gradlew :yoga:compileDebugSources`

I am not able to run the Java tests in this repo (even before the initial Kotlin setup) – not sure if I am missing something there but any pointers are welcome – it seems like there is some missing configuration. Currently trying with `./gradlew :yoga:test`

Reviewed By: cortinico

Differential Revision: D79545992

Pulled By: rshest

fbshipit-source-id: 8257ff53e6b6f2436980be98b6c94e1ac526b207
2025-08-07 08:17:56 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot 77ee24ddce Print benchmark comparison results in a tabular format (#53097)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53097

# Changelog:
[Internal] -

This makes the benchmark results comparison printout, added in https://github.com/facebook/react-native/pull/52925, be done in a tabular form, so it's easier to read and copy/paste around.

Reviewed By: lenaic

Differential Revision: D79728695

fbshipit-source-id: 4dfc999ad4a9a8c2d67efdfce11aff75383cf645
2025-08-07 08:11:32 -07:00
Eliot FallonandFacebook GitHub Bot 327057fad5 fix: fix a typo in the react_native_pods.rb file (#53129)
Summary:
- Switches fmt_config to fast_float_config so it matches what is used in the method

When trying to use a forked version of the Pod it is not possible to set a new repo for Fast Float. This was caused by a typo in the method used to update the configuration of where to find the Pod.

## Changelog:

[IOS] [FIXED] - Fixed variable naming error in set_fast_float_config method in react_native_pods.rb

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

Test Plan: Add the line `set_fast_float_config({:git => 'some-git-repo'})` to an application's Podfile

Reviewed By: cortinico

Differential Revision: D79805939

Pulled By: cipolleschi

fbshipit-source-id: 9705e1f63e21b788362ca94b74e32bce0177a729
2025-08-07 07:48:07 -07:00
Nicola CortiandFacebook GitHub Bot cd71c9620c Do not die on collect-results after printing to Discord. (#53132)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53132

This will prevent the Firebase script from running at all. I'm removing it.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D79801691

fbshipit-source-id: 2705dff93fc9dbbcfaf97a1ba29b69d4d0a8143c
2025-08-07 07:07:10 -07:00
generatedunixname537391475639613andFacebook GitHub Bot 9bb53c02dd xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/switch/androidswitch/react/renderer/components/androidswitch/AndroidSwitchShadowNode.cpp (#53126)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53126

Reviewed By: rshest

Differential Revision: D79710434

fbshipit-source-id: da72999b76c0eff73eb62ef47dcf5ec0ed6c9e09
2025-08-07 05:47:35 -07:00
David VaccaandFacebook GitHub Bot f67078df07 Deprecate all LegacyArchitecture classes on LayoutAnimation package (#53101)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53101

Deprecate all LegacyArchitecture classes on LayoutAnimation package

changelog: [Android][Changed] Deprecate LegacyArchitecture classes from LayoutAnimation package

Reviewed By: alanleedev

Differential Revision: D79658935

fbshipit-source-id: 34ab2f674868dbee459f2018e82a7d50d0d7333a
2025-08-07 05:11:31 -07:00
Mathieu ActhernoeneandFacebook GitHub Bot 3b185e4bce Fix Dimensions window values on Android < 15 (#52738)
Summary:
This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

<img width="300" alt="Screenshot 2025-06-27 at 16 23 02" src="https://github.com/user-attachments/assets/c7d11334-9298-4f7f-a75c-590df8cc2d8a" />

Using `WindowMetricsCalculator` from AndroidX:

<img width="300" alt="Screenshot 2025-06-27 at 16 34 01" src="https://github.com/user-attachments/assets/7a4e3dc7-a83b-421b-8f6d-fd1344f5fe81" />

Fixes https://github.com/facebook/react-native/issues/47080

## Changelog:

[Android] [Fixed] Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled

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

Test Plan:
Run the example app on an Android < 15 device.

Rollback Plan:

Reviewed By: cipolleschi, Abbondanzo

Differential Revision: D78738516

Pulled By: alanleedev

fbshipit-source-id: fdb22f3cc76b0bda987db426cb015124bcacdc84
2025-08-07 02:17:14 -07:00
Alan LeeandFacebook GitHub Bot 8b2e309479 replace getWindowDisplayMetrics with getScreenDisplayMetrics (#53041)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53041

update `DisplayMetricsHolder.getWindowDisplayMetrics()` to `getScreenDisplayMetrics()`.

Where window width and height is not needed, prefer to use `screenDisplayMetrics` as with upcoming diff `windowDisplayMetrics` initialization only happen using UiContext and have potential to cause more issues if used unnecessarily.

Changelog: [Internal] Update `DisplayMetricsHolder.getWindowDisplayMetrics()` to use `.getScreenDisplayMetrics()`

Reviewed By: mlord93

Differential Revision: D79571226

fbshipit-source-id: d90fca36c119318e7a2dfa6953fc2148b35e83d4
2025-08-07 02:17:14 -07:00
David VaccaandFacebook GitHub Bot d4bf644e47 Update deprecation message for ReactNativeHost class (#53118)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53118

Update deprecation message for ReactNativeHost class

changelog: [internal] internal

Reviewed By: shwanton

Differential Revision: D79677829

fbshipit-source-id: 5013e0988a1ffbfea49a261fd23ac71af76c3313
2025-08-06 23:58:34 -07:00
Devan BuggayandFacebook GitHub Bot 2697e8aaab Remove mode from metro client-log data (#53117)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53117

Sending the mode is no longer needed as you can't opt out of legacy arch any longer, and was only used for printing a (NOBRIDGE) prefix.

Changelog: [Internal]

Reviewed By: shwanton

Differential Revision: D79762592

fbshipit-source-id: 845aabf2a8365c88808990ea481503b23597a8a0
2025-08-06 23:56:45 -07:00
Tim YungandFacebook GitHub Bot c861804325 VirtualView: Simplify Window Focus Detection
Summary:
Refactors the window focus detection feature flag logic in `VirtualView` (Android) to eliminate one instance property and instead utilize the existence of the focus listener to determine whether window focus detection is enabled.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D79743782

fbshipit-source-id: d12e70d8e52b72d546ff097c3b1bcfbd29fb9129
2025-08-06 20:49:05 -07:00
Alex HuntandFacebook GitHub Bot 2768c84445 Implement local connection for perf metrics in HostTarget (#52838)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52838

**Context**

Experimental V2 Performance Monitor prototype, beginning by bringing the [Interaction to Next Paint (INP)](https://web.dev/articles/inp) metric to React Native.

**This diff**

Wires up a client/subscriber for the `"__chromium_devtools_metrics_reporter"` runtime binding (to which we emit live metrics events since D78904748). This will be used to unpack these performance updates to send to the host platform.

- Creates a new `HostRuntimeBinding` helper, which establishes a local/private CDP session.
- Conditionally installs our perf metrics runtime binding in `HostTarget` when `perfMonitorV2Enabled` is set.
- Wires up a new `onPerfMonitorUpdate` event on `HostTargetDelegate` (unimplemented until the next diff).

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D78904766

fbshipit-source-id: 991f17a4cc69f574917750053a5da31bbc6dc0d5
2025-08-06 16:05:28 -07:00
Zeya PengandFacebook GitHub Bot be6f3c6f77 Add NativeAnimated jest test to verify node creation behavior at update (#53115)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53115

## Changelog:

[Internal] [Added] - Add NativeAnimated jest test to verify node creation behavior at update

Reviewed By: yungsters

Differential Revision: D79729178

fbshipit-source-id: 1c24c6a19ee994ee4ea7a916c47f227e151d65b4
2025-08-06 15:17:33 -07:00
David VaccaandFacebook GitHub Bot 54770cecc4 Mark JSInstance as InteropLegacyArchitecture (#53100)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53100

Mark JSInstance as InteropLegacyArchitecture

changelog: [internal] internal

Reviewed By: mlord93, cortinico

Differential Revision: D79732414

fbshipit-source-id: fc66d6cfef34b4ac66af724bf4336f4f4c38f447
2025-08-06 14:13:32 -07:00
Andrew DatsenkoandFacebook GitHub Bot 457190cc4b Add test for width, style, testID and tintColor (#53088)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53088

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79642765

fbshipit-source-id: 92862934fc72cfca2ce35c365fa7369d878d58d9
2025-08-06 09:38:29 -07:00
Maciej JastrzębskiandFacebook GitHub Bot 6965d57e75 fix(a11y): TextInput aria-label handling (#53051)
Summary:
The `aria-label` prop was ignored on `TextInput` component. Which resulted in screen reader not able to read it.

This PR forwards `aria-label` to `accessibilityLabel` in a manner similar to e.g. `View` and `Text`

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[GENERAL] [FIXED] - a11y: fix `aria-label` on `TextInput`

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

Test Plan:
Run RNTester => TextInput => Accessibility section under Accessibility Inspector or screen reader.

iOS fixed:
https://github.com/user-attachments/assets/68c3a2ef-7dfe-479c-97fc-cbe72108c45c

iOS baseline:
https://github.com/user-attachments/assets/2e8372ba-10dc-47d2-b6b1-9f664000de7d

Reviewed By: andrewdacenko

Differential Revision: D79635413

Pulled By: rshest

fbshipit-source-id: dd2f583d67c6c6c6393e02c5fe534308e1e2f921
2025-08-06 09:36:35 -07:00
Tommy NguyenandFacebook GitHub Bot bf2c3af93b fix(codegen): fix missing dependencies (#52884)
Summary:
`react-native/codegen` uses `babel/parser` and `babel/core` but does not declare dependency on them. Depending on how packages are hoisted (and especially in pnpm setups), this causes crashes during codegen.

Resolves https://github.com/facebook/react-native/issues/52883

## Changelog:

[GENERAL] [FIXED] - Add missing Babel dependencies

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

Test Plan: See https://github.com/facebook/react-native/issues/52883

Reviewed By: cortinico, christophpurrer

Differential Revision: D79103092

Pulled By: robhogan

fbshipit-source-id: ecaf690f994393a652ea7f0d4f30bbabeb23a434
2025-08-06 08:19:47 -07:00
generatedunixname537391475639613andFacebook GitHub Bot a4b958099c xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp (#53093)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53093

Reviewed By: rshest

Differential Revision: D79709943

fbshipit-source-id: b8d75b6e3b790c94dd3250654459afd782315d9f
2025-08-06 08:18:43 -07:00
Alex HuntandFacebook GitHub Bot b4164cd97c Move JInspectorNetworkReporter, add missing SoLoader call (#53055)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53055

Moves the C++ part of `InspectorNetworkReporter.kt` into the `react_devsupportjni` JNI library, and adds missing `SoLoader.loadLibrary` call.

Replaces D79568759 / https://github.com/facebook/react-native/pull/53036.

Changelog: [Internal]

Reviewed By: robhogan

Differential Revision: D79638331

fbshipit-source-id: 5df450ad80e4532d6ada1e4dab51a1de6418a4e0
2025-08-06 08:14:31 -07:00
Andrew DatsenkoandFacebook GitHub Bot f526e91fda Add tests for src and srcSet (#53062)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53062

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79642287

fbshipit-source-id: 59d7dbf7f2562ca550ac48eb9067469e6128e7c6
2025-08-06 08:08:44 -07:00
Andrew DatsenkoandFacebook GitHub Bot bb5d0df0e3 Add source tests (#53074)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53074

Changelog: [Internal]
Add tests for source prop of Image.

Reviewed By: rshest

Differential Revision: D79605165

fbshipit-source-id: 400cb37f59966634a2ced3c95425be4dcb1fd1a3
2025-08-06 08:08:44 -07:00
Andrew DatsenkoandFacebook GitHub Bot a6ef65e06b Add resizeMode tests (#53061)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53061

Changelog: [Internal]
Add tests for resizeMode prop

Reviewed By: rshest

Differential Revision: D79600137

fbshipit-source-id: d802fb375f2f57f38a51d918d2045763dd79c440
2025-08-06 08:08:44 -07:00
Andrew DatsenkoandFacebook GitHub Bot 93694e3f5d Add referrerPolicy tests (#53073)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53073

Changelog: [Internal]
Add referrerPolicty for cross origin requests.

Reviewed By: rshest

Differential Revision: D79598735

fbshipit-source-id: 96a5b092a278993ac728a9413c1a9376bfe7a69a
2025-08-06 08:08:44 -07:00
Andrew DatsenkoandFacebook GitHub Bot 96ebf5e969 Add on<Event> props (#53060)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53060

Changelog: [Internal]
Add tests for Image props that notify of image loading progress.

Reviewed By: rshest

Differential Revision: D79596580

fbshipit-source-id: 124ee1b4ac70710c4efa9b6e11a168b61fe29aed
2025-08-06 08:08:44 -07:00
Andrew DatsenkoandFacebook GitHub Bot 7bd1254eda Add tests for defaultSource and height (#53058)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53058

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79566292

fbshipit-source-id: cd0d39d914d4e17ee6472be766ba18b0c3866d95
2025-08-06 08:08:44 -07:00
Sophie LandFacebook GitHub Bot 4b8dbe7642 fix: turn off build IDs for reproducibility (#53089)
Summary:
these cause issues for apps that want to be "reproducible" (i.e. the same code leads to the same output, which can be helpful for detemining if there's been any tampering or similar). see also https://gitlab.com/IzzyOnDroid/repo/-/wikis/Reproducible-Builds/RB-Hints-for-Developers#no-funny-build-time-generated-ids

I'm not exactly sure why this was set. it seems to have been introduced in https://github.com/facebook/react-native/commit/e3830ddffd9260fe071e0c9f9df40b379d54cf26 without any (public) explanation as to why it was needed?

## Changelog:

[ANDROID] [FIXED] - Turned off build IDs for native libraries, fixing issues with reproducibility

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

Test Plan: I've successfully used similar patches in my own app for a while.

Reviewed By: cipolleschi

Differential Revision: D79718924

Pulled By: cortinico

fbshipit-source-id: 7c609fa0b5b305cb759586fb1c7f332589ca9cc7
2025-08-06 08:07:14 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot 99f1c30409 Don't populate props with default values for Text component (#53059)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53059

# Changelog:
[Internal] -

This is a re-submit of the partially reverted https://github.com/facebook/react-native/pull/52905, which should work fine now on top of the changes stack that support the RN feature flags being safely used on the modules' top level.

Reviewed By: rubennorte

Differential Revision: D79640965

fbshipit-source-id: d8bda0dba662930d1343ea9d5155791a686b653b
2025-08-06 07:54:26 -07:00
Samuel SuslaandFacebook GitHub Bot 288f6d9f48 Back out "Make sure props default value is restored when disconnected from animated" (#53084)
Summary:
changelog: [internal]

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

This is causing glitches

Reviewed By: rozele

Differential Revision: D79705318

fbshipit-source-id: 0e25909837f0db9812af7835ea88f60390c6ae70
2025-08-06 06:13:14 -07:00
Ruslan LesiutinandFacebook GitHub Bot 421a0c7077 fix: reset maxDuration only after recording TracingStartedInPage event (#53080)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53080

# Changelog: [Internal]

We were accidentally resetting this before calculating the timestampf for the TracingStartedInPage event, which is the left boundary for the timeline window.

Reviewed By: sbuggay

Differential Revision: D79670865

fbshipit-source-id: c6b5f869e185d6d3c80cb7153891c72283c82418
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot 593bdc6762 Keep tracingMode on TraceRecordingState (#53077)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53077

# Changelog: [Internal]

Stores the tracing::Mode on a TraceRecordingState and updates the logic for:
- InstanceTracingAgent to enable PerformanceTracer with a specified window size
- RuntimeTracingAgent to only enable sampling profiler for CDP-initiated sessions

Reviewed By: sbuggay

Differential Revision: D79670864

fbshipit-source-id: 2c4eacb29666b59acbc508848a0a3d859f21a403
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot b358404f44 Override background Trace Recording if user initiated a new one (#53039)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53039

# Changelog: [Internal]

We can now distinguish these 2 different modes.

Reviewed By: sbuggay

Differential Revision: D79565934

fbshipit-source-id: 780678d1f50d4c1d73d55ebc95da0ea328fa2cb3
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot f66ef53962 Create TracingMode (#53035)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53035

# Changelog: [Internal]

This adds an ability to distinguish different trace recordings, based on mode

Reviewed By: sbuggay

Differential Revision: D79557790

fbshipit-source-id: cbb216df86fa1a4692e1b82c8a14a6049b5c45ec
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot 989579c533 Stop potentially running trace recording if CDP session is destroyed (#52960)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52960

# Changelog: [Internal]

We never call `stopTracing()` on a HostTarget, if the Agent was destroyed.

This could only happen if the session was destroyed. There could me multiple sessions, so we have to keep the source of truth for recording status in a session state.

Reviewed By: sbuggay

Differential Revision: D79435494

fbshipit-source-id: db72ffdf6856974e980afa78d59dec4bf45df289
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot 8e97de473e Cleanup legacy tracing endpoints from Agents (#52965)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52965

# Changelog: [Internal]

Reviewed By: sbuggay

Differential Revision: D79434656

fbshipit-source-id: 8c6b690ac6c22df8376651d14b940f74cf23dca3
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot f618ca4872 Use new endpoints in CDP TracingAgent (#52961)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52961

# Changelog: [Internal]

Now that we've implemented all required serializers and TraceRecordedState has everything needed, we can migrate TracingAgent to use this new infra.

Reviewed By: sbuggay

Differential Revision: D79433497

fbshipit-source-id: 8c63f0faa50844786b7af8860c22fc006dd38414
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot b764966c9a Expose start and stop tracing endpoints for HostTargetController (#52962)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52962

# Changelog: [Internal]

CDP Agents should interact with Targets by using Controllers:
https://www.internalfb.com/code/fbsource/[0b20b752c43f]/xplat/js/react-native-github/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h?lines=300-303

This diff adds public endpoinst for controlling the trace recording on a `HostTargetController` that will be used by `TracingAgent` later.

Reviewed By: sbuggay

Differential Revision: D79433500

fbshipit-source-id: 8ff45b1e2380206f0b72f8ba8b879543cf6b9933
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot b954fbf8b7 Implement serializer for TraceRecordingState (#52964)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52964

# Changelog: [Internal]

Now TraceRecordingState should capture everything we need in order to display a trace on a timeline.

We just need to serialize it properly into collection of serialized Trace Events that would be sent via `Tracing.dataCollected` CDP events.

This is what this serializer is doing.

Reviewed By: sbuggay

Differential Revision: D79434655

fbshipit-source-id: 6b6858db23077eb7305781b9b0d98af8dedd68ca
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot c8f260658d Record startTime on TraceRecordingState (#52967)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52967

# Changelog: [Internal]

We would need this to correctly serialize Runtime Sampling Profiles.

In case of recording the trace in the background, this might not be the value we would want to use, we would probably add another field or calculate it dynamically, depending on the tracing mode.

This is not the case for now, will be solved separately on top of the stack.

Reviewed By: sbuggay

Differential Revision: D79433502

fbshipit-source-id: 172ff19985d325585590794fb2523a24a6860221
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot fa9b195661 Record processId on TraceRecordingState (#52969)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52969

# Changelog: [Internal]

We would need this to correctly serialize Runtime Sampling Profiles.

Reviewed By: sbuggay

Differential Revision: D79433501

fbshipit-source-id: 43d10037351669a87623ffccf2b6da60961c0157
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot 3c637b09e6 Record InstanceTracing profile on TraceRecordingState (#52963)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52963

# Changelog: [Internal]

We are deprecating previous `InstanceTarcingProfile` struct, but not removing yet, since this is still used and we will migrate in one of the diffs at the top of the stack.

The `InstanceTracingProfile` will consist only of Trace Events that were captured by PerformanceTracer, and will not store Runtime Sampling Profiles.

There are multiple reasons for this:
1. As of right now, Runtime Sampling Profiles are completely independant from Instance Profiles and do not require anythings from Instance as a Target to be represented on a timeline.
2. Although PerformanceTracer is a singleton, it should be this way. It captures events for something that can only be dispatched if there is an allocated React Instance, so in the future PerformanceTracer could become a data-member of InstanceTarget.

Reviewed By: sbuggay

Differential Revision: D79433498

fbshipit-source-id: 0fdf09517488bc5c6c8eb604aee66535cdc19ffb
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot 5144062937 Record RuntimeSamplingProfile on TraceRecordingState (#52968)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52968

# Changelog: [Internal]

Now that we've defined all actors and relationship between Agents, we can start recording profiles and store them on a recording state.

Reviewed By: sbuggay

Differential Revision: D79415396

fbshipit-source-id: 9639e59d619268d970b895da2e19dfd92c963a32
2025-08-06 05:48:35 -07:00
Ruslan LesiutinandFacebook GitHub Bot a9da64fbf7 Create a local session for Tracing (#52966)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52966

# Changelog: [Internal]

The current design for Tracing is flawed. Right now the logic is mostly scattered around CDP Agents, lifetime of which is tied to CDP session.

This diff introduces a new approach:
- The HostTarget will be the only Target that has public entrypoints to startTracing as part of the jsinspector backend.
- It will create and own TraceRecording that acts as a local session. We won't use wording session here, because it is already reserved for CDP case.
- Every Target will implement TracingAgent, lifetime of which will be limited by lifetime of either Target or TraceRecording.
- All these TracingAgent will have a reference to TraceRecordingState, which they can mutate

This approach unblocks:
- Recording traces without active CDP sessions, for example in a background.
- Recording full instance reloads and multiple profiles for Instances and Runtimes.

{F1980838032}

Reviewed By: sbuggay

Differential Revision: D79371359

fbshipit-source-id: 034e984fd7e977457bd41a980e5feb049db3339d
2025-08-06 05:48:35 -07:00
generatedunixname537391475639613andFacebook GitHub Bot fce9f68f29 xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewShadowNode.cpp (#53090)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53090

Reviewed By: rshest

Differential Revision: D79627030

fbshipit-source-id: fe36f901b2573689821ec3eacecc1f364d3c9183
2025-08-06 05:44:42 -07:00
Rubén NorteandFacebook GitHub Bot 0090333296 Add test to ensure baseline memory usage (#53072)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53072

Changelog: [internal]

This creates a test to make sure there are no changes in React Native dramatically increasing memory usage in simple scenarios.

Reviewed By: rshest

Differential Revision: D79646758

fbshipit-source-id: d7a863468adabb75de7ceb123d96131564bd0959
2025-08-06 05:40:02 -07:00
Rubén NorteandFacebook GitHub Bot 789fc57254 Improve API to take JS heap snapshots in Fantom (#53071)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53071

Changelog: [internal]

The current API to take JS heap snapshots has some problems:
1. Ergonomics: it requires you to input the filepath where you want to store the snapshot. This isn't aligned with the behavior we have for JS traces where the output path is provided to you.
2. It doesn't work in optimized builds, as it requires a specific option in Hermes.

For 1), this replaces `Fantom.saveJSMemoryHeapSnapshot(filePath)` with `Fantom.takeJSMemoryHeapSnapshot()` that outputs the snapshot in a predefined path and prints it to the console.

For 2), this adds a new environment variable to force building Hermes with memory instrumentation (`FANTOM_ENABLE_JS_MEMORY_INSTRUMENTATION`). This is exposed as an option and not set by default because it has a performance overhead at runtime that we don't want to pay (especially in benchmarks).

This option only works when using Buck in development, because we want to generate this new binary type on demand when necessary, instead of making it part of the prebuilts we do before running tests in OSS and CI.

Reviewed By: lenaic

Differential Revision: D79642314

fbshipit-source-id: a2980616a495bd6dca29c0709a9581db6fb3f2cc
2025-08-06 05:40:02 -07:00
Rubén NorteandFacebook GitHub Bot 2187f653f6 Improve filenames of JS sampling profiler traces (#53069)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53069

Changelog: [internal]

This changes the names of the JS traces from Fantom from using a unix timestamp in the file name to using the ISO date:

- From: `View-itest.js-1754406329686.cpuprofile`
- To: `View-itest.js-2025-08-05T15:05:29.686Z.cpuprofile`

Reviewed By: rshest

Differential Revision: D79646760

fbshipit-source-id: d8a654724c1abc2d3e285ee658c2d390d3241d82
2025-08-06 05:40:02 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot 743db63570 Fix logspam when there is no benchmarking results (#53091)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53091

# Changelog:
[Internal] -
This fixes some excessive printing from the benchmarking results, which happened even if there was no benchmarks ran.

Reviewed By: rubennorte

Differential Revision: D79719026

fbshipit-source-id: 0e75d97ae9c762cab25007bfab5ca8a6dc43e148
2025-08-06 05:32:11 -07:00
Vitali ZaidmanandFacebook GitHub Bot 93a052c611 Changelog for 0.81.0-rc.5 (#53086)
Summary:
Add changelog entry for 0.81.0-rc.5

## Changelog:

[INTERNAL] Changelog for 0.81.0-rc.5

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

Test Plan: N/A

Reviewed By: cortinico

Differential Revision: D79717506

Pulled By: vzaidman

fbshipit-source-id: 6086894bc5609e5e250939bcc2008a01350c2afe
2025-08-06 05:13:03 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 7d0bef2f25 Add warning if RCT_NEW_ARCH_ENABLED is set to 0 (#53070)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53070

As per title, this change adds a warning if you call `pod install` with `RCT_NEW_ARCH_ENABLED` set to 0.

## Changelog:
[iOS][Added] -  Add warning if RCT_NEW_ARCH_ENABLED is set to 0

Reviewed By: mdvacca

Differential Revision: D79655716

fbshipit-source-id: 516cc02f9b2dbddaae99c2d74bba249970641d1d
2025-08-06 04:56:19 -07:00
Ruslan LesiutinandFacebook GitHub Bot aa25ad22a2 Update debugger-frontend from 7dcbddd...9215667 (#53082)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53082

Changelog: [Internal] - Update `react-native/debugger-frontend` from 7dcbddd...9215667

Resyncs `react-native/debugger-frontend` from GitHub - see `rn-chrome-devtools-frontend` [changelog](https://github.com/facebook/react-native-devtools-frontend/compare/7dcbddd636137a9604d69a99cc69221216cd4be6...921566790e9e16d0ecace6e49b3cfaace205958c).

### Changelog

| Commit | Author | Date/Time | Subject |
| ------ | ------ | --------- | ------- |
| [921566790](https://github.com/facebook/react-native-devtools-frontend/commit/921566790) | Ruslan Lesiutin (28902667+hoxyq@users.noreply.github.com) | 2025-08-05T18:09:32+01:00 | [feat: add React Native-only event for pre-enabling multiple layers of Tracing (#199)](https://github.com/facebook/react-native-devtools-frontend/commit/921566790) |

Reviewed By: sbuggay

Differential Revision: D79661411

fbshipit-source-id: 63c13de4c6f24bd669aed6397e6d08f00510237c
2025-08-06 03:41:09 -07:00
Nicola CortiandFacebook GitHub Bot 00175bd096 Compare nightly results up to 7 days in the back (#53065)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53065

Instead of checking results from the previous day, allow to go back up to 7
days in the past to check for previous runs.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D79568067

fbshipit-source-id: 5d6fac6a06b4e26e52de6fbf6187ec8f8e44e10e
2025-08-06 03:04:02 -07:00
Nicola CortiandFacebook GitHub Bot f806851875 Store nightly outcome and compute daily broken/recovered from Firebase (#53066)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53066

This diff introduces the mechanism for us to store the result of nightly 3p
library integration on Firebase.
Having the result store, we can now query the result from the previous day and
report if the build is newly broken or recovered overnight.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D79565536

fbshipit-source-id: ba839b2950462a7ca6186a163f93f062719304fb
2025-08-06 03:04:02 -07:00
Riccardo CipolleschiandFacebook GitHub Bot aa4555eaf1 Fix dynamic framework build (#53075)
Summary:
Pure cocoapods build with dyn frameworks was broken due to some missing dependencies with the new performance metrics of cdp

## Changelog:
[iOS][Fixed] - Fix pure cocoapods dynamic framework build

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

Test Plan:
Tested locally in RNTester with
```
USE_FRAMEWORKS=dynamic bundle exec pod install
```
and then building from Xcode

Reviewed By: cortinico

Differential Revision: D79676186

Pulled By: cipolleschi

fbshipit-source-id: ae6b81138fcae66c67bedadb1e1ad9cd6c4b6c35
2025-08-06 03:02:22 -07:00
nishan (o^▽^o)andFacebook GitHub Bot 138d0eb01d Add filter to native animated allowlist (#52920)
Summary:
Add filter to native animated allowlist.

## Changelog:

[GENERAL] [ADDED] - Allow filter usage with native animated driver.

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

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

Test Plan: Tested on RNTester

Reviewed By: joevilches

Differential Revision: D79473929

Pulled By: lunaleaps

fbshipit-source-id: de6fcc0f18a1d656688dce513d2cf48a3c9d4f09
2025-08-05 20:27:16 -07:00
Peter AbbondanzoandFacebook GitHub Bot 4503068117 Make ReactAccessibilityDelegate nullsafe (#53068)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53068

Adds some assertion annotations and small null checks to `ReactAccessibilityDelegate` to comply with FB's Nullsafe annotation

Changelog: [Internal]

Reviewed By: arushikesarwani94

Differential Revision: D79645332

fbshipit-source-id: 707e4e6d4a5f09232af168c2f7c57c2fdbb1f08d
2025-08-05 16:11:34 -07:00
Sam ZhouandFacebook GitHub Bot af1bcb6d44 Mass replace $FlowIgnore with $FlowFixMe in react-native (#53076)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53076

Changelog: [Internal]

Reviewed By: marcoww6

Differential Revision: D79672242

fbshipit-source-id: 560f057d8658ed602cf7241e584bade70d8f3a99
2025-08-05 15:44:41 -07:00
Rubén NorteandFacebook GitHub Bot 5936f29d6a Add test to ensure setUpDefaultReactNativeEnvironment does not access feature flags (#53057)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53057

Changelog: [internal]

This adds a Fantom test to ensure that `setUpDefaultReactNativeEnvironment` doesn't read any feature flags. This prevents catching this as a runtime issue when the feature flag system complains that feature flags were accessed before being overridden, which always would happen if this module read any flags (as it runs before any product code that sets overrides).

Reviewed By: rshest

Differential Revision: D79639890

fbshipit-source-id: 6997609b7bf84947a6da53b58e68f9edd5654912
2025-08-05 13:58:32 -07:00
Rubén NorteandFacebook GitHub Bot 527e308a90 Add private function in feature flags to reset internal JS state for testing (#53056)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53056

Changelog: [internal]

This exposes a utility method in `ReactNativeFeatureFlagsBase` to reset its internal state for testing purposes.

This is intentionally not exposed through `ReactNativeFeatureFlags` to avoid it being used at runtime.

Reviewed By: rshest

Differential Revision: D79639889

fbshipit-source-id: adfb6125d991994c9706d5952d309915fec8f815
2025-08-05 13:58:32 -07:00
Zeya PengandFacebook GitHub Bot 1f9667effa Make sure props default value is restored when disconnected from animated (#53043)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53043

## Changelog:

[Internal] [Changed] - Make sure props default value is restored when disconnected from animated

Reviewed By: sammy-SC

Differential Revision: D79566216

fbshipit-source-id: b23b10101dfac5027cd6d0f0926f6e41b39715ba
2025-08-05 13:28:17 -07:00
Andrew DatsenkoandFacebook GitHub Bot a0f93ea879 Add tests for crossOrigin (#53045)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53045

Changelog: [Internal]
Add tests for cross origin headers.

Reviewed By: rubennorte

Differential Revision: D79562333

fbshipit-source-id: ff8705b4e59c89bc48be29767bbdefbba9328534
2025-08-05 12:06:14 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 83e6eaf693 Prevent users from opting-out of the New Architecture (#53026)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53026

This change prevents users from opting out of the New Architecure.

The change is non breaking with respect to building an app: all the functions are still there, even if they are unreachable, in case users will still call them explicitly.
We hardcoded all the values to enable the New Architecture, so there is no way to disable it.

This is a behavioral breaking change, though.

## Changelog:
[iOS][Removed] - Removed the opt-out from the New Architecture.

Reviewed By: cortinico

Differential Revision: D79090048

fbshipit-source-id: 9779bfedf50748d7adbef5f7ef038f469e30efc2
2025-08-05 11:37:24 -07:00
Sam ZhouandFacebook GitHub Bot 01d5eebdb7 Remove random variants of suppress_types that have been fully cleaned up
Summary: Changelog: [Internal]

Reviewed By: marcoww6

Differential Revision: D79652708

fbshipit-source-id: 8027082b0566cca5700d40860272b7683082b275
2025-08-05 11:02:53 -07:00
Alex HuntandFacebook GitHub Bot 141c95697a Validate max requestable IO.read size (#53063)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53063

Update `IO.read` CDP method handler to validate the received `size` parameter.

This now accepts a max value of 10MB — adding a layer of safety in front of our current Android implementation, which fails at around ~15MB due to OkHttp limits.

Changelog: [Internal]

Reviewed By: vzaidman

Differential Revision: D79646155

fbshipit-source-id: c777802105dc31cdcc7e9e960c880e689540fddd
2025-08-05 10:09:19 -07:00
Peter AbbondanzoandFacebook GitHub Bot 0f0a3cfce2 Provide ReactContext to experimental JSTouchDispatcher method (#53003)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53003

The new `onChildStartedNativeGesture` method accepts a nullable `ReactContext` value for the purpose of flushing active touch events. This change updates all callsites of that method to pass a `ReactContext`

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D79258727

fbshipit-source-id: 7b2950f514295dbe26822442c98079b8121cb3bf
2025-08-05 09:42:36 -07:00
Devan BuggayandFacebook GitHub Bot 551af31871 Update debugger-frontend from a7e4f59...7dcbddd (#53047)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53047

Changelog: [Internal] - Update `react-native/debugger-frontend` from a7e4f59...7dcbddd

Resyncs `react-native/debugger-frontend` from GitHub - see `rn-chrome-devtools-frontend` [changelog](https://github.com/facebook/react-native-devtools-frontend/compare/a7e4f59675edbda995b0cb0d40f277a59a3baebf...7dcbddd636137a9604d69a99cc69221216cd4be6).

### Changelog

| Commit | Author | Date/Time | Subject |
| ------ | ------ | --------- | ------- |
| [7dcbddd63](https://github.com/facebook/react-native-devtools-frontend/commit/7dcbddd63) | sbuggay (sbuggay@gmail.com) | 2025-08-04T13:54:27-07:00 | [Add landingView query param enabling view focus on launch (#197)](https://github.com/facebook/react-native-devtools-frontend/commit/7dcbddd63) |

Reviewed By: huntie

Differential Revision: D79590789

fbshipit-source-id: 1868506dd401361a9843f24e67ffce9c6a5ffb64
2025-08-05 08:36:40 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot 8ef39b491c Implement benchmark comparison of different feture flag configurations (#52925)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52925

# Changelog:
[Internal] -

This adds an extra "ranking" report when running Fantom benchmark vs different React Feature flag configurations.

It can be very useful when implementing some particular optimization, to streamline the before/after comparison wit this optimization enabled/disabled.

Reviewed By: andrewdacenko

Differential Revision: D79269601

fbshipit-source-id: f29e761e313d6857e5b3ac65faf2a387a84be9df
2025-08-05 08:34:35 -07:00
Rubén NorteandFacebook GitHub Bot 664f7c0dcf Add test to verify that components like View and Text are not loaded as a side-effect of environment initialization (#53053)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53053

Changelog: [internal]

This adds a Fantom test to ensure that setting up the RN environment doesn't trigger the initialization for React components like View and Text, which should be lazy loaded when necessary.

Reviewed By: rshest

Differential Revision: D79636160

fbshipit-source-id: ef1fbd6cd531eb7082dce000ba74a5eed451e259
2025-08-05 07:06:24 -07:00
Rubén NorteandFacebook GitHub Bot 14c869cee6 Reduce side-effects of AppRegistry (#53054)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53054

Changelog: [internal]

We eagerly require `AppRegistry` from environment initialization (`InitializeCore`) because it has some side-effects that are necessary for error reporting (see https://github.com/facebook/react-native/issues/34649 and https://github.com/facebook/react-native/pull/34650), but this change makes a lot of modules to be eagerly initialized.

This reduces that to avoid loading modules that not necessary for environment setup, which allows us to do things like setting up feature flags before modules like `View` and `Text` have been initialized.

Reviewed By: rshest

Differential Revision: D79636159

fbshipit-source-id: a3f1e0db3dd69112ceef3ea339167694e2457454
2025-08-05 07:06:24 -07:00
Andrew DatsenkoandFacebook GitHub Bot dc322d91d2 Add ImageSource debug convertions (#53042)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53042

Changelog: [Internal]
Add debug convertions for ImageSource.

Reviewed By: rubennorte

Differential Revision: D79561513

fbshipit-source-id: f48d640d78b34a6e72d120f41f1f32ab963d1069
2025-08-05 07:05:54 -07:00
Andrew DatsenkoandFacebook GitHub Bot e9fdb23ed2 Test blurRadius (#53037)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53037

Changelog: [Internal]
Add `blurRadius` test.

Reviewed By: rshest

Differential Revision: D79552155

fbshipit-source-id: 710b2f328857a32ecb0db00c36f3eedabe74a249
2025-08-05 07:05:54 -07:00
Andrew DatsenkoandFacebook GitHub Bot d58601b59f Add base public API tests (#53040)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53040

Changelog: [Internal]

Add base public API integration tests for Image component.

Reviewed By: rubennorte

Differential Revision: D79551685

fbshipit-source-id: 467d3573102675f4ad1e3757894795b0ad9a8413
2025-08-05 07:05:54 -07:00
Rubén NorteandFacebook GitHub Bot b903ed7940 Add support for JS sampling profiler (#52827)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52827

Changelog: [internal]

This adds **support for creating Hermes/JS sampling profiler traces in Fantom**, which is especially useful when running benchmarks.

Usage:
```
FANTOM_PROFILE_JS=1 yarn fantom Animated-benchmark
```

Output:

 {F1980642216}

After this, the trace is fully symbolicated.

Can be opened directly in Google Chrome:
{F1980642229}

Or in the built-in viewer in VSCode:

 {F1980642242} {F1980642240} {F1980642241}

When collapsing frames in the Flame Chart viewer in VSCode, we can quickly identify opportunities for optimizations.

This also supports multi-config environments. In that case, trace file names are created using a short representation of the configuration.

User guide for benchmarks in Fantom, including how to use this, will be done in a future diff.

NOTE: This still doesn't work in OSS because we don't support optimized mode there. In dev mode, there's a segmentation fault coming from this line: `hermesRuntime->sampledTraceToStreamInDevToolsFormat(fileStream)`

Reviewed By: sammy-SC

Differential Revision: D78905646

fbshipit-source-id: 382ddd5034db601309bd118cedde2fe0d57fde98
2025-08-05 05:36:26 -07:00
Rubén NorteandFacebook GitHub Bot 064750daa8 Improve type safety of constants passed from runner to runtime (#53034)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53034

Changelog: [internal]

This is just a refactor to make sure that the constants we pass from the Fantom runner to its runtime are correct by using Flow to typecheck it.

Reviewed By: andrewdacenko

Differential Revision: D79565574

fbshipit-source-id: cbbab9cdec5ef5b3c82b929b8939c76c0ef41823
2025-08-05 05:36:26 -07:00
Nicola CortiandFacebook GitHub Bot 36bdd7b9cb Remove unused ReactSafeAreaViewShadowNode (#52985)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52985

This class is essentially a wrapper of LayoutShadowNode with no extra logic added.
Let's remove it.

Changelog:
[Internal] [Changed] -

Reviewed By: alanleedev

Differential Revision: D79450688

fbshipit-source-id: 943e10e602cb9a5b77fca81e11d2333828b27813
2025-08-05 05:16:57 -07:00
Nicola CortiandFacebook GitHub Bot d5d21d0614 Remove possibility to newArchEnabled=false in 0.82 (#53025)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53025

It's now time to say goodbye to the Legacy Architecture :')

This change hardcodes the `newArchEnabled` property to true, and warns the users
if they're attempting to set it to false.

Changelog:
[Android] [Breaking] - Remove possibility to newArchEnabled=false in 0.82

Reviewed By: cipolleschi

Differential Revision: D78560296

fbshipit-source-id: ccfc45d2f7f21cc20e063cb901d76be3d41458d6
2025-08-05 05:11:12 -07:00
Rubén NorteandFacebook GitHub Bot de5093c887 Add test for MemoryInfo (#53052)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53052

Changelog: [internal]

Adds a test for `performance.memory`.

Reviewed By: hoxyq

Differential Revision: D79633246

fbshipit-source-id: 8f9df0219de6c04c8be75af4c9d03576a8164ea9
2025-08-05 03:46:35 -07:00
Sam ZhouandFacebook GitHub Bot 6b354155ed Replace $FlowFixMe(Props|State|Empty) with just $FlowFixMe (#53002)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53002

Changelog: [Internal]

Reviewed By: marcoww6

Differential Revision: D79524515

fbshipit-source-id: 18b96538a62c7ae5912b1e89d2b50c1420c7eaf5
2025-08-04 11:43:00 -07:00
Rubén NorteandFacebook GitHub Bot 21bccda26c Remove PerformanceEntryReporter::getCurrentTimeStamp (#53030)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53030

Changelog: [internal]

Removes `PerformanceEntryReporter::getCurrentTimeStamp` in favor of `HighResTimeStamp::now`, to make the source of truth more explicit.

Reviewed By: hoxyq

Differential Revision: D79560370

fbshipit-source-id: 0ccf2bf511781d3c47c6ddb4dd7f2061aab152b5
2025-08-04 11:21:03 -07:00
Rubén NorteandFacebook GitHub Bot 7c1c833ee9 Remove redundant methods to mock timers from PerformanceEntryReporter and NativePerformance (#53028)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53028

Changelog: [internal]

Now that we have mocking at a more fundamental level (`HighResTimeStamp` API) we can replace other timing mocks with that one.

This does it for `PerformanceEntryReporter` and the `NativePerformance` module.

Reviewed By: hoxyq

Differential Revision: D79557640

fbshipit-source-id: 86579b8bb586190ab7cc8721f30e60b3ef789798
2025-08-04 11:21:03 -07:00
Rubén NorteandFacebook GitHub Bot 2658e21a62 Make test for LongTasks API deterministic and re-enable on Github (#53018)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53018

Changelog: [internal]

This makes the test for LongTasks API use the new API for mocking timers in Fantom to make it deterministic, and re-enables it on Github.

Reviewed By: rshest

Differential Revision: D79554724

fbshipit-source-id: 984c66ecd7c20eb972ba1e6b19944532acb82246
2025-08-04 11:21:03 -07:00
Rubén NorteandFacebook GitHub Bot f2e72c3859 Implement HighResTimeStamp mocking in Fantom (#53019)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53019

Changelog: [internal]

This adds support for mocking `HighResTimeStamp` values in Fantom tests via a new `Fantom.installHighResTimeStampMock` function.

See new tests for more details on how it works.

Reviewed By: rshest

Differential Revision: D79554723

fbshipit-source-id: 8b0fb292948be118c7616fde1a8a84014af82de8
2025-08-04 11:21:03 -07:00
Rubén NorteandFacebook GitHub Bot f1cf4894ff Allow mocking HighResTimeStamp in debug builds (#53020)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53020

Changelog: [internal]

This adds a new feature to `HighResTimeStamp` to set a custom timestamp provider for "now" that can be useful for testing, only in debug builds to avoid potentially regressing performance.

Reviewed By: hoxyq, rshest

Differential Revision: D79554725

fbshipit-source-id: c325d05999b9e2d69f769b61f15c763446777a0a
2025-08-04 11:21:03 -07:00
Rubén NorteandFacebook GitHub Bot 8dc162a56a Small refactor of HighResTimeStamp (#53021)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53021

Changelog: [internal]

Moving the logic to set the default value for `HighResTimeStamp` to a shared function, to simplify further changes.

Reviewed By: rshest

Differential Revision: D79554726

fbshipit-source-id: cd0d4567ef63d386d28e0325203169323e97b207
2025-08-04 11:21:03 -07:00
Fabrizio CucciandFacebook GitHub Bot 893730633c Use buttonState to distinguish ACTION_DOWN and ACTION_HOVER_EXIT (#53033)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53033

This diff replaces the logic introduced in D72078450 to prevent `onPointerEnter`/`onPointerLeave` from firing when a button is pressed. The new approach trades off some complexity for reliability: instead of deferring `ACTION_HOVER_EXIT` handling to the next frame, we now suppress it immediately if any button is pressed (`buttonState != 0`). This simpler logic appears to work reliably on Quest devices, though it may behave differently on the Android emulator (something we’ll monitor).

The main reason for this change is that deferring ACTION_HOVER_EXIT introduces problems in newer Spatial React use cases, particularly when a single component hierarchy spans multiple roots. For example, when hovering between ReactSurfaceRoot and another root like VolumetricWindow, deferring ACTION_HOVER_EXIT can lead to incorrect enter/exit ordering:

* Cursor starts hovering over `ReactSurfaceRoot`
* Cursor moves to `VolumetricWindow`
* `ACTION_HOVER_EXIT` (`ReactSurfaceRoot`) — deferred
* `ACTION_HOVER_ENTER` (`VolumetricWindow`) — processed
* `ACTION_HOVER_EXIT` (`ReactSurfaceRoot`) — processed (too late)

This results in inconsistent hover state updates across roots, which this diff resolves by handling `ACTION_HOVER_EXIT` immediately when appropriate.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D79504775

fbshipit-source-id: ea97bff48ddf4d3d09caf56ca29057c202b12409
2025-08-04 11:10:10 -07:00
Mateo GuzmánandFacebook GitHub Bot d547d9e56e Kotlin: redundant unit return type [1/2] (#52993)
Summary:
Fixing some warnings from static code analysis regarding [redundant unit return type](https://www.jetbrains.com/help/inspectopedia/RedundantUnitReturnType.html).

## Changelog:

[INTERNAL] - Kotlin: redundant unit return type [1/2]

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

Test Plan:
```sh
yarn test-android
yarn android
```

Reviewed By: cortinico

Differential Revision: D79546007

Pulled By: rshest

fbshipit-source-id: 017cb3b70333fe652ecb1bdca751fa4f56f737fd
2025-08-04 11:04:51 -07:00
Alex HuntandFacebook GitHub Bot e39fd8f79c Fix mimeType parsing for CDP responses (#53027)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53027

Tweaks MIME type parsing in CDP Network messages, now matching Chrome.

This fixes response preview behaviour by the frontend for text response previews that are `base64Encoded` 🙌🏻 (we were observing these for JSON `fetch` calls on iOS).

Changelog: [Internal]

Reviewed By: vzaidman

Differential Revision: D79559495

fbshipit-source-id: 2565af7587fc6fbdd3ef6fcbb10c558341ddfbdc
2025-08-04 10:29:37 -07:00
Alex HuntandFacebook GitHub Bot fa66e314b2 Implement Network.loadingFailed (#53023)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53023

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D79555222

fbshipit-source-id: bfbe36edc867b1fc7a44d8e998489ef1d8896331
2025-08-04 10:04:02 -07:00
Alex HuntandFacebook GitHub Bot ea50245e1e Update InspectorNetworkReporter to avoid overhead for CDP-only methods (#53022)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53022

Following D77799617, D77927896, updates `InspectorNetworkReporter.kt` to check `isDebuggingEnabled` internally and avoid work/communication over the JNI layer — to minimise impact on the Android Network stack.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D78004462

fbshipit-source-id: 2bea2ee0592d68d1cb330ac82e8b3b227b54a675
2025-08-04 10:04:02 -07:00
Nolan O'BrienandFacebook GitHub Bot 323fe3a5d4 Fix exhaustive switches (#53032)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53032

## Changelog:

[General][Fixed] - Add default cases to switch statements in headers

Differential Revision: D79148595

fbshipit-source-id: e7260b5e9356b60b238b9f75ab1809fbbbbbeaf4
2025-08-04 09:42:15 -07:00
Samuel SuslaandFacebook GitHub Bot 0f912f8312 make ivars in NativeAnimatedNodesManager const (#53015)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53015

changelog: [internal]

marking a few fields as const to prevent accidental change.

Reviewed By: rshest

Differential Revision: D79443448

fbshipit-source-id: 32a44f7f0c43c240879c77058ef6672885488191
2025-08-04 08:57:06 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot d03151f561 Mitigate "Feature flags were accessed before being overridden" error (#53031)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53031

# Changelog:
[Internal] -

This partially reverts https://github.com/facebook/react-native/pull/52905, as in some configurations we don't appear to be able to use RN feature flags on the module level.

The usage is removed for now to unbreak the builds, with a follow up to resolve it in an adequate manner.

Reviewed By: hoxyq

Differential Revision: D79562056

fbshipit-source-id: 45bd896d572ff926a4c2dfa98334bf998718d86b
2025-08-04 08:47:18 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 75e04b8c41 Add documentation for updating the issue triage oncall
Summary:
This Diff simply adds some docs on how the secret must be formatted, and removes a print that would expose the triager IDs is anyone would look at the logs (not a big deal given that the ids are public on Discord, though).

## Changelog:
[Internal] -

Reviewed By: cortinico

Differential Revision: D79556988

fbshipit-source-id: 23d6e72141dff4e91242cc1d9f5b95ebaf5ca858
2025-08-04 08:28:48 -07:00
Peter AbbondanzoandFacebook GitHub Bot 1828c53f85 Emit scroll end events when fling animator completes (#52989)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52989

Adds a call to `NativeAnimatedModule`'s `userDrivenScrollEnded` method whenever a scroll event completes a smooth scroll animation. This is necessary in cases where Animated events control any layout properties of children and we need to force the shadow tree to resync with the native tree. For example, if a scroll view's child transforms its scale based on the scrollX or scrollY properties and the user triggers a `scrollToOffset` or `scrollToIndex` call, we don't update the layout of that child until the next state change.

Changelog: [Android][Fixed] - Fixed an issue where shadow tree and native tree layouts mismatch at the end of a scroll event

Reviewed By: sammy-SC

Differential Revision: D79464176

fbshipit-source-id: fee5f1c522714dbcddf8836de291c05d10e6e90e
2025-08-04 08:19:16 -07:00
Nicola CortiandFacebook GitHub Bot ea1aff455a Make ReactCxxErrorHandler internal (#53024)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53024

This class is Legacy Arch and is not used in OSS. Let's make it internal.

Changelog:
[Internal] [Changed] -

Reviewed By: RSNara

Differential Revision: D79556615

fbshipit-source-id: d157fe8f04784038d64657c6d240b0c51e41d82d
2025-08-04 07:51:30 -07:00
Ruslan LesiutinandFacebook GitHub Bot dd7ab0f833 forward fix tests after changes to PerformanceTracer (#53016)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53016

# Changelog: [Internal]

Reviewed By: vzaidman

Differential Revision: D79554532

fbshipit-source-id: 83e68f714974083238f97e2ef1affa6dee3b116f
2025-08-04 06:34:45 -07:00
Ruslan LesiutinandFacebook GitHub Bot bfd6c6d8fc fix: removed constexpr from now() (#53014)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53014

# Changelog: [Internal]

This doesn't make sense, I've probably overlooked it while applying this to other methods.

Reviewed By: rubennorte

Differential Revision: D79552990

fbshipit-source-id: a7dc428dfcc86a08a9e52655f9878795b8e58c1c
2025-08-04 06:03:41 -07:00
Rubén NorteandFacebook GitHub Bot 2016118aeb Centralize path definitions for Fantom builds and move to .out (#52829)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52829

Changelog: [internal]

Just a small refactor to move the definitions of output paths to a specific module. We'll add more to this in a latter diff.

Reviewed By: sammy-SC

Differential Revision: D78905645

fbshipit-source-id: 011e6cec13396301dad8e76400b6f2b9e13568f0
2025-08-04 05:56:51 -07:00
Rubén NorteandFacebook GitHub Bot 6760383470 Create source maps for Fantom lazily (#52786)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52786

Changelog: [internal]

It takes around 300ms to generate each source map file, and we really only need them if tests throw errors. Requesting source maps also increases contention to access the Metro server from each test worker.

This refactors the code so we only generate them in that case, which could save up to 20s in test execution time.

Reviewed By: rshest

Differential Revision: D78807672

fbshipit-source-id: af9f0f0377ddcf05014b5aca0b28db938dfb4ce2
2025-08-04 05:56:51 -07:00
Rubén NorteandFacebook GitHub Bot 5c2b9eda69 Refactor runner to use a single instance of Metro for each run (#52777)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52777

Changelog: [internal]

This significantly speeds up test execution in Fantom (around 2x in OSS and 6x at Meta) by starting a Metro server before all tests runs and reusing it across all tests to build test bundles, instead of spinning up a new Metro instance every time we run each test.

The architecture change (also considering the previous change in buck prebuilds) looks like this:
{F1980689532}

This is how is impacts execution times (compared to the baseline):
* OSS
  * Before: 62s {F1980564286}
  * After: 30s (**2x faster**) {F1980564265}

Reviewed By: andrewdacenko

Differential Revision: D78741903

fbshipit-source-id: b209f88925e49cc2a2067e8df9b7fa9a29b4c8d2
2025-08-04 05:56:51 -07:00
Rubén NorteandFacebook GitHub Bot 3ecd48fa91 Refactor Fantom runner to decouple compilation from execution to speed up test execution (#52758)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52758

Changelog: [internal]

This is a change to how Fantom tests run in Meta infra via Buck.

After this, the biggest opportunity will be optimizing how we generate bundles with Metro.

Reviewed By: christophpurrer

Differential Revision: D78672863

fbshipit-source-id: 1152907f3ba60e7d2e48bcc588f3c07aef7bb393
2025-08-04 05:56:51 -07:00
Ruslan ShestopalyukandFacebook GitHub Bot f047c9b42b Remove props that set by default for Text component on JS side (#52905)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52905

Similarly to [this PR](https://github.com/facebook/react-native/pull/51225), we do it for the Text component now: we don't set any non-default value props (most prominently, the accessibility ones) before passing this to native.

This saves bandwidth and potentially improves the prop parsing time, which existing Fantom benchmarks do confirm.

This is implemented behind a feature flag, which is false by default (will use it to run an experiment before rolling out).

**NOTE:** This implementation forks the whole text component, as suggested by rubennorte, in order to isolate the changes and with the ultimate goal of removing the old version once the experiment is concluded.

## Changelog:
[Internal] - Text no longer sets any default accessibility props, which should not result in visible changes in behaviour but may affect snapshot tests.

Reviewed By: rubennorte

Differential Revision: D79177652

fbshipit-source-id: a39430464fd5edec953b4c91be7ef9620ebd75ac
2025-08-04 05:29:19 -07:00
Nicola CortiandFacebook GitHub Bot c37f3ed8c6 Remove unnecessary ReactNoCrashBridgeNotAllowedSoftException (#52987)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52987

This class is unnecessary. Is also public for no real reason.
Instead we should use `ReactNoCrashSoftException` directly.

I'm not marking this as breaking as users hsould not be catching this class.

Changelog:
[Internal] [Changed] -

Reviewed By: mdvacca

Differential Revision: D79451567

fbshipit-source-id: 4d6f45b3006c79969fcf141002d34a72bf88901a
2025-08-04 04:50:59 -07:00
lukmccallandFacebook GitHub Bot 2f46a49b8d Fix ReactHostImpl.nativeModules always returning an empty list (#52986)
Summary:
During the Expo QA process, we discovered that `ReactContext.reactApplicationContext.nativeModules` always returns an empty list (https://github.com/expo/expo/blob/4e2bbb23edda74d0e24756fd1735b8763e38f7a7/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/ReactExtensions.kt#L12). This happens because, during object creation, the `reactInstance` is always null.

## Changelog:

[ANDROID] [FIXED] - Fix `ReactHostImpl.nativeModules` always returning an empty list

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

Test Plan: - RN tester compiles 

Reviewed By: mdvacca

Differential Revision: D79451613

Pulled By: cortinico

fbshipit-source-id: d5341bcc1193eb948db4e99f16ba32a63073a6db
2025-08-04 04:46:56 -07:00
Mateo GuzmánandFacebook GitHub Bot f273c63d37 Kotlin: obvious explicit type (#52990)
Summary:
Fixing some warnings from static code analysis regarding [obvious explicit type](https://www.jetbrains.com/help/inspectopedia/RedundantExplicitType.html#locating-this-inspection).

## Changelog:

[INTERNAL] - Kotlin: obvious explicit type

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

Test Plan:
```sh
yarn test-android
yarn android
```

Reviewed By: cortinico

Differential Revision: D79546014

Pulled By: rshest

fbshipit-source-id: 1b66edde3185911b137e2c77673779cc613fae74
2025-08-04 04:28:50 -07:00
Rubén NorteandFacebook GitHub Bot b708d2da61 Disable LongTasksAPI on Github CI (#53009)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53009

Changelog: [internal]

This disables the test for LongTasks API on Github CI as the execution speed there is unreliable and makes the current tests flaky.

This happens because the test is timing dependent, but the alternative would be to mock some core behaviors that I think might be even worse.

Reviewed By: cortinico

Differential Revision: D79510480

fbshipit-source-id: 277e42e36aa6dfebf4745d094541a667f58a0996
2025-08-04 03:54:58 -07:00
Rubén NorteandFacebook GitHub Bot 4289cff268 Add isOSS to Fantom constants (#53010)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53010

Changelog: [internal]

Exposing this value so we can disable some tests in some scenarios in a following this.

Reviewed By: cipolleschi

Differential Revision: D79510481

fbshipit-source-id: 632d9a008943ed40c24878b5561065b6ede1d689
2025-08-04 03:54:58 -07:00
Nicola CortiandFacebook GitHub Bot 691d4744b3 Mark NotThreadSafeBridgeIdleDebugListener as LegacyArchitecture (#52988)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52988

This class should be marked as `LegacyArchitecture` while it was not.

Changelog:
[Internal] [Changed] -

Reviewed By: mdvacca

Differential Revision: D79451041

fbshipit-source-id: 62c5d35821e354ea851eaccac909bf6bd9157f09
2025-08-04 03:51:08 -07:00
Alex HuntandFacebook GitHub Bot bbcafbbffe Pin Node.js version in GitHub Actions to 24.4.1 (#53013)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53013

Quick fix to restore CI on `main`. `actions/setup-node` is now pulling Node.js `24.5.0`, which introduces a bug affecting `packages/dev-middleware/src/__tests__/` Jest tests.

Changelog: [Internal]

Reviewed By: cipolleschi

Differential Revision: D79551277

fbshipit-source-id: 51951ad8ffe376a478da268b50aa54ac2d9bba03
2025-08-04 03:33:14 -07:00
Rubén NorteandFacebook GitHub Bot c535e7c1c9 Make TextInput test follow convention and add tests for all methods (#53011)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53011

Changelog: [internal]

This restructures the test for `TextInput` to follow the convention:

```
describe('<TextInput>', () => {
  describe('props', () => {
    /* ... */
  });

  describe('ref', () => {
    /* ... */
  });
});
```

It also adds tests for all methods.

Reviewed By: sammy-SC

Differential Revision: D79511032

fbshipit-source-id: 118198bdb1a86c2a9e0f41ff0b81bcd62b535d9f
2025-08-04 03:17:11 -07:00
Nicola CortiandFacebook GitHub Bot 742ef3d661 AGP to 8.12.0 (#52973)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52973

This bumps AGP to the latest stable version.

Changelog:
[Android] [Changed] - AGP to 8.12.0

Reviewed By: rshest

Differential Revision: D79436778

fbshipit-source-id: 3071c0108af064573c087aaf7b92d0b10c1adc6a
2025-08-04 02:55:55 -07:00
Nicola CortiandFacebook GitHub Bot d42335007b Update debugger_bug_report.yml (#52981)
Summary:
This label was renamed at some point and it broke the issue template. This fixes it.

## Changelog:

[INTERNAL] -

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

Test Plan: N/A

Reviewed By: mdvacca

Differential Revision: D79448155

Pulled By: cortinico

fbshipit-source-id: 2312fc5a0a82a65ca908af58dc74348141c16ca2
2025-08-04 02:30:24 -07:00
338 changed files with 7112 additions and 2021 deletions
-4
View File
@@ -76,10 +76,6 @@ module.system.haste.module_ref_prefix=m#
react.runtime=automatic
suppress_type=$FlowFixMe
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FlowFixMeEmpty
ban_spread_key_props=true
@@ -1,6 +1,6 @@
name: 🔍 Debugger - Bug Report
description: Report a bug with React Native DevTools and the New Debugger
labels: ["Needs: Triage :mag:", "Debugger"]
labels: ["Needs: Triage :mag:", "Debugging"]
body:
- type: markdown
@@ -0,0 +1,897 @@
/**
* 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 {
FirebaseClient,
compareResults,
getYesterdayDate,
getTodayDate,
} = require('../firebaseUtils');
describe('FirebaseClient', () => {
const originalFetch = global.fetch;
const originalEnv = process.env;
beforeEach(() => {
global.fetch = jest.fn();
process.env = {
...originalEnv,
FIREBASE_APP_EMAIL: 'test@example.com',
FIREBASE_APP_PASS: 'testpassword',
FIREBASE_APP_APIKEY: 'test-api-key',
FIREBASE_APP_PROJECTNAME: 'test-project',
};
jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
global.fetch = originalFetch;
process.env = originalEnv;
jest.restoreAllMocks();
});
describe('constructor', () => {
it('should initialize with environment variables', () => {
const client = new FirebaseClient();
expect(client.email).toBe('test@example.com');
expect(client.password).toBe('testpassword');
expect(client.apiKey).toBe('test-api-key');
expect(client.projectId).toBe('test-project');
expect(client.databaseUrl).toBe(
'test-project-default-rtdb.firebaseio.com',
);
expect(client.idToken).toBeNull();
});
});
describe('authenticate', () => {
it('should authenticate successfully', async () => {
const mockResponse = {
idToken: 'mock-id-token',
refreshToken: 'mock-refresh-token',
};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResponse)),
});
const client = new FirebaseClient();
await client.authenticate();
expect(client.idToken).toBe('mock-id-token');
expect(global.fetch).toHaveBeenCalledWith(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=test-api-key',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'test@example.com',
password: 'testpassword',
returnSecureToken: true,
}),
},
);
});
it('should throw error when email is missing', async () => {
delete process.env.FIREBASE_APP_EMAIL;
const client = new FirebaseClient();
await expect(client.authenticate()).rejects.toThrow(
'Firebase credentials not found in environment variables',
);
});
it('should throw error when password is missing', async () => {
delete process.env.FIREBASE_APP_PASS;
const client = new FirebaseClient();
await expect(client.authenticate()).rejects.toThrow(
'Firebase credentials not found in environment variables',
);
});
it('should handle authentication failure', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 400,
text: jest.fn().mockResolvedValueOnce(
JSON.stringify({
error: {message: 'Invalid credentials'},
}),
),
});
const client = new FirebaseClient();
await expect(client.authenticate()).rejects.toThrow(
'HTTP 400: Invalid credentials',
);
});
});
describe('makeRequest', () => {
it('should make successful GET request', async () => {
const mockData = {test: 'data'};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
});
const client = new FirebaseClient();
const result = await client.makeRequest('example.com', '/test', 'GET');
expect(result).toEqual(mockData);
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
});
it('should make successful POST request with data', async () => {
const mockData = {success: true};
const postData = {test: 'post data'};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
});
const client = new FirebaseClient();
const result = await client.makeRequest(
'example.com',
'/test',
'POST',
postData,
);
expect(result).toEqual(mockData);
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
});
it('should handle non-JSON response', async () => {
const textResponse = 'plain text response';
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(textResponse),
});
const client = new FirebaseClient();
const result = await client.makeRequest('example.com', '/test', 'GET');
expect(result).toBe(textResponse);
});
it('should handle HTTP error with JSON error message', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce(
JSON.stringify({
error: {message: 'Not found'},
}),
),
});
const client = new FirebaseClient();
await expect(
client.makeRequest('example.com', '/test', 'GET'),
).rejects.toThrow('HTTP 404: Not found');
});
it('should handle HTTP error with plain text error message', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 500,
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
});
const client = new FirebaseClient();
await expect(
client.makeRequest('example.com', '/test', 'GET'),
).rejects.toThrow('HTTP 500: Internal Server Error');
});
});
describe('makeDatabaseRequest', () => {
it('should make database request with existing token', async () => {
const mockData = {test: 'data'};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
expect(result).toEqual(mockData);
expect(global.fetch).toHaveBeenCalledWith(
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
);
});
it('should authenticate before making request if no token exists', async () => {
const authResponse = {idToken: 'new-token'};
const dataResponse = {test: 'data'};
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(dataResponse)),
});
const client = new FirebaseClient();
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
expect(result).toEqual(dataResponse);
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(client.idToken).toBe('new-token');
});
it('should make PUT request with data', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce('null'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const testData = [{library: 'test', status: 'success'}];
await client.makeDatabaseRequest('2023-12-01', 'PUT', testData);
expect(global.fetch).toHaveBeenCalledWith(
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(testData),
},
);
});
});
describe('storeResults', () => {
it('should store results successfully', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce('null'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const results = [{library: 'test', status: 'success'}];
await client.storeResults('2023-12-01', results);
expect(global.fetch).toHaveBeenCalledWith(
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(results),
},
);
expect(console.log).toHaveBeenCalledWith(
'Successfully stored results for 2023-12-01',
);
});
});
describe('getResults', () => {
it('should retrieve results successfully', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const results = await client.getResults('2023-12-01');
expect(results).toEqual(mockResults);
});
it('should return null for 404 errors', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce('Not Found'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const results = await client.getResults('2023-12-01');
expect(results).toBeNull();
});
it('should throw error for non-404 HTTP errors', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 500,
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
await expect(client.getResults('2023-12-01')).rejects.toThrow(
'HTTP 500: Internal Server Error',
);
});
});
describe('getLatestResults', () => {
it('should authenticate before making requests if no token exists', async () => {
const authResponse = {idToken: 'new-token'};
const mockResults = [{library: 'test', status: 'success'}];
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
const result = await client.getLatestResults('2023-12-15', 1);
expect(result).toEqual({
results: mockResults,
date: '2023-12-14',
});
expect(client.idToken).toBe('new-token');
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-14 (1 days back)',
);
});
it('should find results from the previous day', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-14',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-14 (1 days back)',
);
});
it('should find results from several days back', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock 404 responses for first 2 days, then success on 3rd day
global.fetch
.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce('Not Found'),
})
.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce('Not Found'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-12',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-13 (2 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-12 (3 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-12 (3 days back)',
);
});
it('should skip empty results and continue searching', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock empty array for first day, then valid results on second day
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify([])),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-13',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-13 (2 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-13 (2 days back)',
);
});
it('should return null when no results found within maxDaysBack', async () => {
// Mock 404 responses for all days
global.fetch.mockResolvedValue({
ok: false,
status: 404,
text: jest.fn().mockResolvedValue('Not Found'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 3);
expect(result).toEqual({
results: null,
date: null,
});
expect(console.log).toHaveBeenCalledWith(
'No previous results found within the last 3 days',
);
expect(global.fetch).toHaveBeenCalledTimes(3);
});
it('should use default maxDaysBack of 7 when not specified', async () => {
// Mock 404 responses for all days
global.fetch.mockResolvedValue({
ok: false,
status: 404,
text: jest.fn().mockResolvedValue('Not Found'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15');
expect(result).toEqual({
results: null,
date: null,
});
expect(console.log).toHaveBeenCalledWith(
'No previous results found within the last 7 days',
);
expect(global.fetch).toHaveBeenCalledTimes(7);
});
it('should handle non-404 errors and continue searching', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock 500 error for first day, then success on second day
global.fetch
.mockResolvedValueOnce({
ok: false,
status: 500,
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-13',
});
expect(console.log).toHaveBeenCalledWith(
'No results found for 2023-12-14: HTTP 500: Internal Server Error',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-13 (2 days back)',
);
});
it('should handle date boundaries correctly', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
// Test month boundary
const result = await client.getLatestResults('2023-12-01', 1);
expect(result).toEqual({
results: mockResults,
date: '2023-11-30',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-11-30 (1 days back)...',
);
});
it('should handle year boundary correctly', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
// Test year boundary
const result = await client.getLatestResults('2024-01-01', 1);
expect(result).toEqual({
results: mockResults,
date: '2023-12-31',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-31 (1 days back)...',
);
});
it('should handle null results and continue searching', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock null for first day, then valid results on second day
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce('null'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-13',
});
});
});
});
describe('compareResults', () => {
it('should handle null previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const result = compareResults(currentResults, null);
expect(result).toEqual({
broken: [],
recovered: [],
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
});
});
it('should handle undefined previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
];
const result = compareResults(currentResults, undefined);
expect(result).toEqual({
broken: [],
recovered: [],
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
});
});
it('should identify broken tests', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'failed',
},
]);
expect(result.recovered).toEqual([]);
});
it('should identify recovered tests', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([]);
expect(result.recovered).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'failed',
currentStatus: 'success',
},
]);
});
it('should identify both broken and recovered tests', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
{library: 'lib3', platform: 'iOS', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'failed'},
{library: 'lib3', platform: 'iOS', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'failed',
},
]);
expect(result.recovered).toEqual([
{
library: 'lib2',
platform: 'Android',
previousStatus: 'failed',
currentStatus: 'success',
},
]);
});
it('should handle tests that are not in previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'failed',
},
]);
expect(result.recovered).toEqual([]);
});
it('should handle empty current results', () => {
const currentResults = [];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([]);
expect(result.recovered).toEqual([]);
});
it('should handle empty previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
];
const previousResults = [];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([]);
expect(result.recovered).toEqual([]);
// When previousResults is an empty array (not null/undefined),
// the function doesn't return newFailures property
expect(result.newFailures).toBeUndefined();
});
it('should handle different status values', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'timeout'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'error'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'timeout',
},
]);
expect(result.recovered).toEqual([
{
library: 'lib2',
platform: 'Android',
previousStatus: 'error',
currentStatus: 'success',
},
]);
});
});
describe('getYesterdayDate', () => {
it("should return yesterday's date in YYYY-MM-DD format", () => {
const mockDate = new Date('2023-12-15T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getYesterdayDate();
expect(result).toBe('2023-12-14');
global.Date.mockRestore();
});
it('should handle month boundary correctly', () => {
const mockDate = new Date('2023-12-01T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getYesterdayDate();
expect(result).toBe('2023-11-30');
global.Date.mockRestore();
});
it('should handle year boundary correctly', () => {
const mockDate = new Date('2024-01-01T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getYesterdayDate();
expect(result).toBe('2023-12-31');
global.Date.mockRestore();
});
});
describe('getTodayDate', () => {
it("should return today's date in YYYY-MM-DD format", () => {
const mockDate = new Date('2023-12-15T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getTodayDate();
expect(result).toBe('2023-12-15');
global.Date.mockRestore();
});
it('should handle different times of day correctly', () => {
const mockDate = new Date('2023-12-15T23:59:59Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getTodayDate();
expect(result).toBe('2023-12-15');
global.Date.mockRestore();
});
});
@@ -11,8 +11,15 @@ const fs = require('fs');
const path = require('path');
const {
prepareFailurePayload,
prepareComparisonPayload,
sendMessageToDiscord,
} = require('./notifyDiscord');
const {
FirebaseClient,
compareResults,
getYesterdayDate,
getTodayDate,
} = require('./firebaseUtils');
function readOutcomes() {
const baseDir = '/tmp';
@@ -103,16 +110,62 @@ async function collectResults(discordWebHook) {
const outcomes = readOutcomes();
const failures = printFailures(outcomes);
// Send failure notification if there are current failures
if (failures.length > 0) {
if (discordWebHook) {
console.log('Sending to discord');
console.log('Sending current failures to Discord...');
await notifyDiscord(discordWebHook, failures);
} else {
console.log('Web hook not set');
console.log('Discord webhook not set');
}
process.exit(1);
}
console.log('✅ All tests passed!');
// Initialize Firebase client
const firebaseClient = new FirebaseClient();
const today = getTodayDate();
try {
// Store today's results in Firebase
console.log(`Storing results for ${today} in Firebase...`);
await firebaseClient.storeResults(today, outcomes);
// Get the most recent previous results for comparison
console.log(`Looking for most recent previous results before ${today}...`);
const {results: previousResults, date: previousDate} =
await firebaseClient.getLatestResults(today);
let broken = [];
let recovered = [];
if (previousResults) {
console.log(`Comparing with results from ${previousDate}`);
// Compare results and identify broken/recovered jobs
const comparison = compareResults(outcomes, previousResults);
broken = comparison.broken;
recovered = comparison.recovered;
console.log(
`Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`,
);
} else {
console.log(
'No previous results found for comparison - this might be the first run or no recent data available',
);
}
// Send comparison message to Discord if there are changes
if (discordWebHook && (broken.length > 0 || recovered.length > 0)) {
console.log('Sending comparison results to Discord...');
const comparisonMessage = prepareComparisonPayload(broken, recovered);
await sendMessageToDiscord(discordWebHook, comparisonMessage);
}
console.log('✅ All tests passed!');
} catch (error) {
console.error('Error in collectResults:', error);
// If Firebase fails but there are no test failures, don't fail the workflow
console.log('⚠️ Firebase operations failed, but all tests passed');
}
}
module.exports = {
@@ -25,6 +25,31 @@ function extractUsersFromScheduleAndDate(schedule, userMap, date) {
return [user1, user2];
}
/**
* You can invoke this script by doing:
* ```
* node .github/workflow-scripts/extractIssueOncalls.js $DATA
* ```
*
* the $DATA is stored in the github secrets as ONCALL_SCHEDULE variable.
* The format of the data is:
* ```
* {
* \"userMap\": {
* \"discord_handle1\": \"discord_id1\",
* \"discord_handle2\": \"discord_id2\",
* ...
* },
* \"schedule\": {
* \"2025-07-29\": [\"discord_handle1\", \"discord_handle2\"],
* \"2025-08-05\": [\"discord_handle3\", \"discord_handle4\"],
* ...
* }
* ```
*
* When uploading the secret, make sure that the JSON strings are escaped!
* The script will fail otherwise, because GitHub will remove the `"` characters.
*/
function main() {
const configuration = process.argv[2];
const {userMap, schedule} = JSON.parse(configuration);
+261
View File
@@ -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.
*
* @format
*/
// We connect to firebase using a plain HTTP request because we don't want to
// add yet another devDependency to the react-native monorepo.
class FirebaseClient {
constructor() {
this.email = process.env.FIREBASE_APP_EMAIL;
this.password = process.env.FIREBASE_APP_PASS;
this.apiKey = process.env.FIREBASE_APP_APIKEY;
this.projectId = process.env.FIREBASE_APP_PROJECTNAME;
this.databaseUrl = `${this.projectId}-default-rtdb.firebaseio.com`;
this.idToken = null;
}
async authenticate() {
if (!this.email || !this.password) {
throw new Error(
'Firebase credentials not found in environment variables',
);
}
const authData = {
email: this.email,
password: this.password,
returnSecureToken: true,
};
const response = await this.makeRequest(
'identitytoolkit.googleapis.com',
`/v1/accounts:signInWithPassword?key=${this.apiKey}`,
'POST',
authData,
);
this.idToken = response.idToken;
return;
}
/**
* Make a database request for a specific date
* @param {string} date - Date in YYYY-MM-DD format
* @param {string} method - HTTP method
* @param {*} data - Data to send (optional)
* @returns {Promise<*>} - Response data
*/
async makeDatabaseRequest(date, method, data = null) {
if (!this.idToken) {
await this.authenticate();
}
const path = `/nightly-results/${date}.json?auth=${this.idToken}`;
return this.makeRequest(this.databaseUrl, path, method, data);
}
/**
* Store test results for a specific date
* @param {string} date - Date in YYYY-MM-DD format
* @param {Array<Object>} results - Array of test results
* @returns {Promise<void>}
*/
async storeResults(date, results) {
await this.makeDatabaseRequest(date, 'PUT', results);
console.log(`Successfully stored results for ${date}`);
}
/**
* Retrieve test results for a specific date
* @param {string} date - Date in YYYY-MM-DD format
* @returns {Promise<Array<Object>|null>} - Array of test results or null if not found
*/
async getResults(date) {
try {
return await this.makeDatabaseRequest(date, 'GET');
} catch (error) {
if (error.message.includes('404')) {
return null; // No results found for this specific date.
}
throw error;
}
}
/**
* Find the most recent available job results before the given date
* @param {string} currentDate - Current date in YYYY-MM-DD format
* @param {number} maxDaysBack - Maximum number of days to look back (default: 7)
* @returns {Promise<{results: Array<Object>|null, date: string|null}>} - Most recent results and their date
*/
async getLatestResults(currentDate, maxDaysBack = 7) {
if (!this.idToken) {
await this.authenticate();
}
const currentDateObj = new Date(currentDate);
for (let daysBack = 1; daysBack <= maxDaysBack; daysBack++) {
const checkDate = new Date(currentDateObj);
checkDate.setDate(checkDate.getDate() - daysBack);
const checkDateStr = checkDate.toISOString().split('T')[0];
console.log(
`Checking for results on ${checkDateStr} (${daysBack} days back)...`,
);
try {
const results = await this.getResults(checkDateStr);
if (results && results.length > 0) {
console.log(
`Found results from ${checkDateStr} (${daysBack} days back)`,
);
return {results, date: checkDateStr};
}
} catch (error) {
console.log(`No results found for ${checkDateStr}: ${error.message}`);
continue;
}
}
console.log(
`No previous results found within the last ${maxDaysBack} days`,
);
return {results: null, date: null};
}
async makeRequest(hostname, path, method, data = null) {
const url = `https://${hostname}${path}`;
const options = {
method,
headers: {
'Content-Type': 'application/json',
},
};
if (data) {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
const responseText = await response.text();
if (!response.ok) {
let errorMessage;
try {
const parsedError = JSON.parse(responseText);
errorMessage = parsedError.error?.message || responseText;
} catch {
errorMessage = responseText;
}
throw new Error(`HTTP ${response.status}: ${errorMessage}`);
}
try {
return JSON.parse(responseText);
} catch {
return responseText;
}
}
}
/**
* Compare current results with previous day's results
* @param {Array<Object>} currentResults - Today's test results
* @param {Array<Object>} previousResults - Yesterday's test results
* @returns {Object} - Object containing broken and recovered tests
*/
function compareResults(currentResults, previousResults) {
if (!previousResults) {
return {
broken: [],
recovered: [],
newFailures: currentResults.filter(result => result.status !== 'success'),
};
}
// Create maps for easier lookup
const currentMap = new Map();
const previousMap = new Map();
currentResults.forEach(result => {
const key = `${result.library}-${result.platform}`;
currentMap.set(key, result);
});
previousResults.forEach(result => {
const key = `${result.library}-${result.platform}`;
previousMap.set(key, result);
});
const broken = [];
const recovered = [];
// Check for broken tests (was success, now failed)
for (const [key, currentResult] of currentMap) {
const previousResult = previousMap.get(key);
if (previousResult) {
if (
previousResult.status === 'success' &&
currentResult.status !== 'success'
) {
broken.push({
library: currentResult.library,
platform: currentResult.platform,
previousStatus: previousResult.status,
currentStatus: currentResult.status,
});
}
}
}
// Check for recovered tests (was failed, now success)
for (const [key, currentResult] of currentMap) {
const previousResult = previousMap.get(key);
if (previousResult) {
if (
previousResult.status !== 'success' &&
currentResult.status === 'success'
) {
recovered.push({
library: currentResult.library,
platform: currentResult.platform,
previousStatus: previousResult.status,
currentStatus: currentResult.status,
});
}
}
}
return {broken, recovered};
}
/**
* Get yesterday's date in YYYY-MM-DD format
* @returns {string} - Yesterday's date
*/
function getYesterdayDate() {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday.toISOString().split('T')[0];
}
/**
* Get today's date in YYYY-MM-DD format
* @returns {string} - Today's date
*/
function getTodayDate() {
const today = new Date();
return today.toISOString().split('T')[0];
}
module.exports = {
FirebaseClient,
compareResults,
getYesterdayDate,
getTodayDate,
};
+59 -13
View File
@@ -41,20 +41,12 @@ async function sendMessageToDiscord(webHook, message) {
}
/**
* Prepares a formatted Discord message payload from a list of failures.
* @param {Array<Object>} failures - List of failures to format
* @returns {Object} - The formatted Discord message payload
* Sorts jobs by platform first, then by library name.
* @param {Array<Object>} jobs - Array of jobs with platform and library properties
* @returns {Array<Object>} - Sorted array of jobs
*/
function prepareFailurePayload(failures) {
if (!failures || failures.length === 0) {
return {
content:
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
};
}
// Sort failures by platform and then by library name
const sortedFailures = [...failures].sort((a, b) => {
function sortResultsByPlatformAndLibrary(jobs) {
return [...jobs].sort((a, b) => {
// First sort by platform
const platformA = a.platform || 'Unknown';
const platformB = b.platform || 'Unknown';
@@ -68,6 +60,23 @@ function prepareFailurePayload(failures) {
const libraryB = b.library || 'Unknown';
return libraryA.localeCompare(libraryB);
});
}
/**
* Prepares a formatted Discord message payload from a list of failures.
* @param {Array<Object>} failures - List of failures to format
* @returns {Object} - The formatted Discord message payload
*/
function prepareFailurePayload(failures) {
if (!failures || failures.length === 0) {
return {
content:
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
};
}
// Sort failures by platform and then by library name
const sortedFailures = sortResultsByPlatformAndLibrary(failures);
// Format the failures into a message
const formattedFailures = sortedFailures
@@ -83,8 +92,45 @@ function prepareFailurePayload(failures) {
};
}
/**
* Prepares a formatted Discord message payload for broken and recovered nightly jobs.
* @param {Array<Object>} broken - List of newly broken jobs
* @param {Array<Object>} recovered - List of recovered jobs
* @returns {Object} - The formatted Discord message payload
*/
function prepareComparisonPayload(broken, recovered) {
let content = '📊 **React Native Nightly Integration Status Update** 📊\n\n';
if (broken.length === 0 && recovered.length === 0) {
content +=
'No changes from yesterday - all nightly jobs maintained their previous status.';
} else {
if (broken.length > 0) {
content += '🔴 **Newly Broken Jobs:**\n';
const sortedBroken = sortResultsByPlatformAndLibrary(broken);
sortedBroken.forEach(job => {
content += `❌ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
});
content += '\n';
}
if (recovered.length > 0) {
content += '🟢 **Recovered Jobs:**\n';
const sortedRecovered = sortResultsByPlatformAndLibrary(recovered);
sortedRecovered.forEach(job => {
content += `✅ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
});
}
}
return {content};
}
// Export the functions using CommonJS syntax
module.exports = {
prepareFailurePayload,
prepareComparisonPayload,
sendMessageToDiscord,
};
+4
View File
@@ -32,3 +32,7 @@ jobs:
needs: check-nightly
secrets:
discord_webhook_url: ${{ secrets.NIGHTLY_DISCORD_WEBHOOK }}
firebase_app_email: ${{ secrets.FIREBASE_APP_EMAIL }}
firebase_app_pass: ${{ secrets.FIREBASE_APP_PASS }}
firebase_app_projectname: ${{ secrets.FIREBASE_APP_PROJECTNAME }}
firebase_app_apikey: ${{ secrets.FIREBASE_APP_APIKEY }}
-4
View File
@@ -27,10 +27,6 @@ jobs:
ONCALL2=$(echo $ONCALLS | cut -d ' ' -f 2)
echo "oncall1=$ONCALL1" >> $GITHUB_ENV
echo "oncall2=$ONCALL2" >> $GITHUB_ENV
- name: Print oncalls
run: |
echo "oncall1: ${{ env.oncall1 }}"
echo "oncall2: ${{ env.oncall2 }}"
- name: Monitor New Issues
uses: react-native-community/repo-monitor@v1.0.1
with:
+1 -1
View File
@@ -592,7 +592,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22", "20.19.4"]
node-version: ["24.4.1", "22", "20.19.4"]
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -5,6 +5,14 @@ on:
secrets:
discord_webhook_url:
required: true
firebase_app_email:
required: true
firebase_app_pass:
required: true
firebase_app_apikey:
required: true
firebase_app_projectname:
required: true
# We use the matrix.library entry to specify the dependency we want to use
@@ -94,6 +102,11 @@ jobs:
path: /tmp
- name: Collect failures
uses: actions/github-script@v6
env:
FIREBASE_APP_EMAIL: ${{ secrets.firebase_app_email }}
FIREBASE_APP_PASS: ${{ secrets.firebase_app_pass }}
FIREBASE_APP_APIKEY: ${{ secrets.firebase_app_apikey }}
FIREBASE_APP_PROJECTNAME: ${{ secrets.firebase_app_projectname }}
with:
script: |
const {collectResults} = require('./.github/workflow-scripts/collectNightlyOutcomes.js');
+1
View File
@@ -170,6 +170,7 @@ fix_*.patch
# Jest Integration
/private/react-native-fantom/build/
/private/react-native-fantom/.out/
/private/react-native-fantom/tester/build/
# [Experimental] Generated TS type definitions
+26 -206
View File
@@ -1,218 +1,40 @@
# Changelog
## v0.81.0-rc.3
### Breaking
- Metro to ^0.83.1 ([e247be793c](https://github.com/facebook/react-native/commit/e247be793c70a374955d798d8cbbc6eba58080ec) by [@motiz88](https://github.com/motiz88))
#### Android specific
#### iOS specific
### Added
#### Android specific
#### iOS specific
### Changed
#### Android specific
#### iOS specific
### Deprecated
#### Android specific
#### iOS specific
### Removed
#### Android specific
#### iOS specific
## v0.81.0-rc.5
### Fixed
#### Android specific
- **Runtime:** Fixed `ReactHostImpl.nativeModules` always returning an empty list ([2f46a49](https://github.com/facebook/react-native/commit/2f46a49b8d8a11d5cf4342eee83c469b545c6779) by [@lukmccall](https://github.com/lukmccall))
## v0.81.0-rc.4 - Burned
## v0.81.0-rc.3
### Changed
- **Metro:** Metro to ^0.83.1 ([e247be793c](https://github.com/facebook/react-native/commit/e247be793c70a374955d798d8cbbc6eba58080ec) by [@motiz88](https://github.com/motiz88))
### Fixed
#### Android specific
- **rngp:** Fix a race condition with codegen libraries missing sources ([9013a9e666](https://github.com/facebook/react-native/commit/9013a9e66629677c47e1b69703f9fc8f4cbc1c2c) by [@cortinico](https://github.com/cortinico))
- Make accessors inside HeadlessJsTaskService open again ([7ef57163cb](https://github.com/facebook/react-native/commit/7ef57163cb016317e43e563da7ea181989f6abca) by [@cortinico](https://github.com/cortinico))
#### iOS specific
### Security
#### Android specific
#### iOS specific
### Unknown
- Release 0.81.0-rc.3 ([0e6009eecf](https://github.com/facebook/react-native/commit/0e6009eecfeac71121a045f0f7e6bae94ffc11b0) by [@react-native-bot](https://github.com/react-native-bot))
- Bump Podfile.lock ([3695258eed](https://github.com/facebook/react-native/commit/3695258eed45fed5fbff5dd6128c88275f91083c) by [@react-native-bot](https://github.com/react-native-bot))
#### Android Unknown
#### iOS Unknown
#### Failed to parse
- **API:** Make accessors inside HeadlessJsTaskService open again ([7ef57163cb](https://github.com/facebook/react-native/commit/7ef57163cb016317e43e563da7ea181989f6abca) by [@cortinico](https://github.com/cortinico))
## v0.81.0-rc.2
### Breaking
#### Android specific
#### iOS specific
### Added
#### Android specific
#### iOS specific
### Changed
- Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options. ([0508eddfe6](https://github.com/facebook/react-native/commit/0508eddfe60df60cb3bfa4074ae199bd0e492d5f) by [@yungsters](https://github.com/yungsters))
- `NewAppScreen` no longer internally handles device safe area, use optional `safeAreaInsets` prop (aligned in 0.81 template) ([732bd12dc2](https://github.com/facebook/react-native/commit/732bd12dc21460641ef01b23f2eb722f26b060d5) by [@huntie](https://github.com/huntie))
#### Android specific
#### iOS specific
### Deprecated
#### Android specific
#### iOS specific
### Removed
#### Android specific
#### iOS specific
- **API:** `NewAppScreen` no longer internally handles device safe area, use optional `safeAreaInsets` prop (aligned in 0.81 template) ([732bd12dc2](https://github.com/facebook/react-native/commit/732bd12dc21460641ef01b23f2eb722f26b060d5) by [@huntie](https://github.com/huntie))
- **Babel:** Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options. ([0508eddfe6](https://github.com/facebook/react-native/commit/0508eddfe60df60cb3bfa4074ae199bd0e492d5f) by [@yungsters](https://github.com/yungsters))
### Fixed
#### Android specific
#### iOS specific
- Fixed issue with RNDeps release/debug switch failing ([4ee2b60a1e](https://github.com/facebook/react-native/commit/4ee2b60a1eacca744d58a7ad336ca9d3714289f6) by [@chrfalch](https://github.com/chrfalch))
- Fixed missing script for resolving prebuilt xcframework when switching between release/debug ([2e55241a90](https://github.com/facebook/react-native/commit/2e55241a901b4cd95917de68ce9078928820a208) by [@chrfalch](https://github.com/chrfalch))
### Security
#### Android specific
#### iOS specific
### Unknown
- Release 0.81.0-rc.2 ([68ef746ec5](https://github.com/facebook/react-native/commit/68ef746ec5dd7d2874d190733e75bb8197034c5a) by [@react-native-bot](https://github.com/react-native-bot))
- Fix E2E test script when the ci flag is not specified ([cdd7f99581](https://github.com/facebook/react-native/commit/cdd7f995813727b630ff38abc8a910a0f8f10b37) by [@cipolleschi](https://github.com/cipolleschi))
- Fix E2E script when using CI artifacts ([d8bf94489a](https://github.com/facebook/react-native/commit/d8bf94489ab7498eba7e5f45d09dd2819fe739c3) by [@cipolleschi](https://github.com/cipolleschi))
- Bump Podfile.lock ([10b63c15b6](https://github.com/facebook/react-native/commit/10b63c15b6faaf54d555ede879f82ac565f030a9) by [@react-native-bot](https://github.com/react-native-bot))
- Release 0.81.0-rc.1 ([b06bb89ddd](https://github.com/facebook/react-native/commit/b06bb89ddd3cebddea4716036a4368b87a65f492) by [@react-native-bot](https://github.com/react-native-bot))
#### Android Unknown
#### iOS Unknown
#### Failed to parse
- **Podspec:** Fixed issue with RNDeps release/debug switch failing ([4ee2b60a1e](https://github.com/facebook/react-native/commit/4ee2b60a1eacca744d58a7ad336ca9d3714289f6) by [@chrfalch](https://github.com/chrfalch))
- **Podspec:** Fixed missing script for resolving prebuilt xcframework when switching between release/debug ([2e55241a90](https://github.com/facebook/react-native/commit/2e55241a901b4cd95917de68ce9078928820a208) by [@chrfalch](https://github.com/chrfalch))
## v0.81.0-rc.1
@@ -220,28 +42,27 @@
#### iOS specific
- Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
- Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
- Add the `ENTERPRISE_REPOSITORY` env variable to cocopaods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
- **CocoaPods** Add the `ENTERPRISE_REPOSITORY` env variable to cocoapods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
- **Prebuild:** Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
### Changed
- Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
- **Metro:** Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
#### Android specific
- Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
- Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
- Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
- **Gradle:** Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
- **Gradle:** Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
- **Legacy Arch:** Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
### Fixed
- Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
- **Yoga:** Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
- **Podspec:** Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
## v0.81.0-rc.0
@@ -1586,4 +1407,3 @@ See [CHANGELOG-0.5x](./CHANGELOG-0.5x.md#v0530)
## v0.52.0
See [CHANGELOG-0.5x](./CHANGELOG-0.5x.md#v0520)
+3 -3
View File
@@ -24,12 +24,12 @@ try {
} catch (e) {
// Fallback to lib when source doesn't exit (e.g. when installed as a dev dependency)
FlowParser =
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
TypeScriptParser =
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
}
@@ -80,7 +80,7 @@ try {
'@react-native-community/cli-server-api',
{paths: [communityCliPath]},
);
// $FlowIgnore[unsupported-syntax] dynamic import
// $FlowFixMe[unsupported-syntax] dynamic import
communityMiddlewareFallback.createDevServerMiddleware = require(
communityCliServerApiPath,
).createDevServerMiddleware as CreateDevServerMiddleware;
@@ -92,14 +92,14 @@ async function runServer(
console.info(`Starting dev server on ${devServerUrl}\n`);
if (args.assetPlugins) {
// $FlowIgnore[cannot-write] Assigning to readonly property
// $FlowFixMe[cannot-write] Assigning to readonly property
metroConfig.transformer.assetPlugins = args.assetPlugins.map(plugin =>
require.resolve(plugin),
);
}
// TODO(T214991636): Remove legacy Metro log forwarding
if (!args.clientLogs) {
// $FlowIgnore[cannot-write] Assigning to readonly property
// $FlowFixMe[cannot-write] Assigning to readonly property
metroConfig.server.forwardClientLogs = false;
}
@@ -146,7 +146,7 @@ async function runServer(
}
},
};
// $FlowIgnore[cannot-write] Assigning to readonly property
// $FlowFixMe[cannot-write] Assigning to readonly property
metroConfig.reporter = reporter;
await Metro.runServer(metroConfig, {
@@ -175,7 +175,7 @@ function getReporterImpl(
try {
// First we let require resolve it, so we can require packages in node_modules
// as expected. eg: require('my-package/reporter');
// $FlowIgnore[unsupported-syntax]
// $FlowFixMe[unsupported-syntax]
return require(customLogReporterPath);
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
@@ -183,7 +183,7 @@ function getReporterImpl(
}
// If that doesn't work, then we next try relative to the cwd, eg:
// require('./reporter');
// $FlowIgnore[unsupported-syntax]
// $FlowFixMe[unsupported-syntax]
return require(path.resolve(customLogReporterPath));
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ const FIRST = 1,
FOURTH = 4;
function getNodePackagePath(packageName: string): string {
// $FlowIgnore[prop-missing] type definition is incomplete
// $FlowFixMe[prop-missing] type definition is incomplete
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
}
+1 -1
View File
@@ -75,7 +75,7 @@ const FIRST = 1,
FIFTH = 5;
function getNodePackagePath(packageName: string): string {
// $FlowIgnore[prop-missing] type definition is incomplete
// $FlowFixMe[prop-missing] type definition is incomplete
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
}
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<74c5fb174ae5a8a3850a3c2373b3b6f5>>
Git revision: a7e4f59675edbda995b0cb0d40f277a59a3baebf
@generated SignedSource<<9252db36d4b1db907a38c08935ceeb38>>
Git revision: 921566790e9e16d0ecace6e49b3cfaace205958c
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebook/react-native-devtools-frontend
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ contextBridge.executeInMainWorld({
let didDecorateInspectorFrontendHostInstance = false;
// reactNativeDecorateInspectorFrontendHostInstance was introduced in
// https://github.com/facebook/react-native-devtools-frontend/pull/168
// $FlowIgnore[prop-missing]
// $FlowFixMe[prop-missing]
globalThis.reactNativeDecorateInspectorFrontendHostInstance = (
InspectorFrontendHostInstance: $FlowFixMe,
) => {
+2 -2
View File
@@ -18,9 +18,9 @@ declare module.exports: typeof Node;
// Because Electron doesn't support package.json `exports`, we need to
// switch at runtime.
if ('electron' in process.versions) {
// $FlowIgnore[invalid-export]
// $FlowFixMe[invalid-export]
module.exports = require('./electron');
} else {
// $FlowIgnore[invalid-export]
// $FlowFixMe[invalid-export]
module.exports = require('./node');
}
@@ -70,7 +70,7 @@ export class DebuggerAgent {
this.#ws = null;
}
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get socket(): WebSocket {
return nullthrows(this.#ws);
}
@@ -103,11 +103,11 @@ export class DebuggerMock extends DebuggerAgent {
originalHandleCallsArray === this.handle.mock.calls
? this.handle.mock.calls.slice(originalHandleCallCount)
: this.handle.mock.calls;
// $FlowIgnore[incompatible-use]
// $FlowIgnore[prop-missing]
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
const [response] = newHandleCalls.find(args => args[0].id === message.id);
// $FlowIgnore[incompatible-return]
// $FlowIgnore[incompatible-indexer]
// $FlowFixMe[incompatible-return]
// $FlowFixMe[incompatible-indexer]
return response;
}
}
@@ -90,7 +90,7 @@ export class DeviceAgent {
});
}
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get socket(): WebSocket {
return nullthrows(this.#ws);
}
@@ -64,9 +64,9 @@ export async function sendFromTargetToDebugger<Message: CdpMessageFromTarget>(
originalHandleCallsArray === debugger_.handle.mock.calls
? debugger_.handle.mock.calls.slice(originalHandleCallCount)
: debugger_.handle.mock.calls;
// $FlowIgnore[incompatible-type]
// $FlowFixMe[incompatible-type]
const [receivedMessage]: [Message] = newHandleCalls.find(
// $FlowIgnore[incompatible-call]
// $FlowFixMe[incompatible-call]
(call: [Message]) => call[0].method === message.method,
);
return receivedMessage;
@@ -97,13 +97,13 @@ export async function sendFromDebuggerToTarget<Message: CdpMessageToTarget>(
originalEventCallsArray === device.wrappedEventParsed.mock.calls
? device.wrappedEventParsed.mock.calls.slice(originalEventCallCount)
: device.wrappedEventParsed.mock.calls;
// $FlowIgnore[incompatible-use]
// $FlowFixMe[incompatible-use]
const [receivedMessage] = newEventCalls.find(
// $FlowIgnore[prop-missing]
// $FlowIgnore[incompatible-use]
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
call => call[0].wrappedEvent.id === message.id,
);
// $FlowIgnore[incompatible-return]
// $FlowFixMe[incompatible-return]
return receivedMessage.wrappedEvent;
}
@@ -141,7 +141,7 @@ export async function createAndConnectTarget(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -60,7 +60,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -119,7 +119,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -187,7 +187,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -288,7 +288,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -338,7 +338,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -108,7 +108,7 @@ describe('inspector proxy device message middleware', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -314,7 +314,7 @@ describe('inspector-proxy device socket handoff', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toEqual(
expect.arrayContaining(
@@ -55,7 +55,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -113,7 +113,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toContainEqual(
expect.objectContaining({
@@ -169,7 +169,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -222,7 +222,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toContainEqual(
expect.objectContaining({
@@ -273,7 +273,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -423,7 +423,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -459,7 +459,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toContainEqual(
expect.objectContaining({
@@ -10,7 +10,7 @@
export function withAbortSignalForEachTest(): $ReadOnly<{signal: AbortSignal}> {
const ref: {signal: AbortSignal} = {
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get signal() {
throw new Error(
'The return value of withAbortSignalForEachTest is lazily initialized and can only be accessed in tests.',
@@ -39,19 +39,19 @@ export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{
app: ConnectApp,
port: number,
} = {
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get serverBaseUrl() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get serverBaseWsUrl() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get app() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get port() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
@@ -40,7 +40,7 @@ function makeRequest(
host: ?string,
encrypted: boolean,
): http$IncomingMessage<> | http$IncomingMessage<tls$TLSSocket> {
// $FlowIgnore[incompatible-return] Partial mock of request
// $FlowFixMe[incompatible-return] Partial mock of request
return {
socket: encrypted ? {encrypted: true} : {},
headers: host != null ? {host} : {},
@@ -1,5 +1,5 @@
[versions]
agp = "8.11.0"
agp = "8.12.0"
gson = "2.8.9"
guava = "31.0.1-jre"
javapoet = "1.13.0"
@@ -28,8 +28,8 @@ import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
import com.facebook.react.utils.PropertyUtils
import com.facebook.react.utils.findPackageJsonFile
import java.io.File
import kotlin.system.exitProcess
@@ -43,6 +43,7 @@ import org.gradle.internal.jvm.Jvm
class ReactPlugin : Plugin<Project> {
override fun apply(project: Project) {
checkJvmVersion(project)
checkLegacyArchProperty(project)
val extension = project.extensions.create("react", ReactExtension::class.java, project)
// We register a private extension on the rootProject so that project wide configs
@@ -115,6 +116,30 @@ class ReactPlugin : Plugin<Project> {
}
}
private fun checkLegacyArchProperty(project: Project) {
if ((project.hasProperty(PropertyUtils.NEW_ARCH_ENABLED) &&
!project.property(PropertyUtils.NEW_ARCH_ENABLED).toString().toBoolean()) ||
(project.hasProperty(PropertyUtils.SCOPED_NEW_ARCH_ENABLED) &&
!project.property(PropertyUtils.SCOPED_NEW_ARCH_ENABLED).toString().toBoolean())) {
project.logger.error(
"""
********************************************************************************
WARNING: Setting `newArchEnabled=false` in your `gradle.properties` file is not
supported anymore since React Native 0.82.
You can remove the line from your `gradle.properties` file.
The application will run with the New Architecture enabled by default.
********************************************************************************
"""
.trimIndent())
}
}
/** This function configures Android resources - in this case just the bundle */
private fun configureResources(project: Project, reactExtension: ReactExtension) {
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl {
@@ -271,19 +296,17 @@ class ReactPlugin : Plugin<Project> {
task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
}
if (project.isNewArchEnabled(extension)) {
// For New Arch, we also need to generate code for C++ Autolinking
val generateAutolinkingNewArchitectureFilesTask =
project.tasks.register(
"generateAutolinkingNewArchitectureFiles",
GenerateAutolinkingNewArchitecturesFileTask::class.java) { task ->
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
task.generatedOutputDirectory.set(generatedAutolinkingJniDir)
}
project.tasks
.named("preBuild", Task::class.java)
.dependsOn(generateAutolinkingNewArchitectureFilesTask)
}
// We also need to generate code for C++ Autolinking
val generateAutolinkingNewArchitectureFilesTask =
project.tasks.register(
"generateAutolinkingNewArchitectureFiles",
GenerateAutolinkingNewArchitecturesFileTask::class.java) { task ->
task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
task.generatedOutputDirectory.set(generatedAutolinkingJniDir)
}
project.tasks
.named("preBuild", Task::class.java)
.dependsOn(generateAutolinkingNewArchitectureFilesTask)
// We let generateAutolinkingPackageList and generateEntryPoint depend on the preBuild task so
// it's executed before
@@ -13,7 +13,6 @@ import com.android.build.gradle.LibraryExtension
import com.facebook.react.ReactExtension
import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import java.io.File
import java.net.Inet4Address
import java.net.NetworkInterface
@@ -65,10 +64,7 @@ internal object AgpConfiguratorUtils {
.getByType(ApplicationAndroidComponentsExtension::class.java)
.finalizeDsl { ext ->
ext.buildFeatures.buildConfig = true
ext.defaultConfig.buildConfigField(
"boolean",
"IS_NEW_ARCHITECTURE_ENABLED",
project.isNewArchEnabled(extension).toString())
ext.defaultConfig.buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true")
ext.defaultConfig.buildConfigField(
"boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString())
ext.defaultConfig.buildConfigField(
@@ -11,7 +11,6 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.Variant
import com.facebook.react.ReactExtension
import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import java.io.File
import org.gradle.api.Project
@@ -21,10 +20,6 @@ internal object NdkConfiguratorUtils {
project.pluginManager.withPlugin("com.android.application") {
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl {
ext ->
if (!project.isNewArchEnabled(extension)) {
// For Old Arch, we don't need to setup the NDK
return@finalizeDsl
}
// We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine
// .aar
ext.buildFeatures.prefab = true
@@ -78,29 +73,19 @@ internal object NdkConfiguratorUtils {
extension: ReactExtension,
variant: Variant
) {
if (!project.isNewArchEnabled(extension)) {
// For Old Arch, we set a pickFirst only on libraries that we know are
// clashing with our direct dependencies (mainly FBJNI and Hermes).
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
"**/libfbjni.so",
"**/libc++_shared.so",
))
} else {
// We set some packagingOptions { pickFirst ... } for our users for libraries we own.
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
// This is the .so provided by FBJNI via prefab
"**/libfbjni.so",
// Those are prefab libraries we distribute via ReactAndroid
// Due to a bug in AGP, they fire a warning on console as both the JNI
// and the prefab .so files gets considered.
"**/libreactnative.so",
"**/libjsi.so",
// AGP will give priority of libc++_shared coming from App modules.
"**/libc++_shared.so",
))
}
// We set some packagingOptions { pickFirst ... } for our users for libraries we own.
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
// This is the .so provided by FBJNI via prefab
"**/libfbjni.so",
// Those are prefab libraries we distribute via ReactAndroid
// Due to a bug in AGP, they fire a warning on console as both the JNI
// and the prefab .so files gets considered.
"**/libreactnative.so",
"**/libjsi.so",
// AGP will give priority of libc++_shared coming from App modules.
"**/libc++_shared.so",
))
}
/**
@@ -13,11 +13,9 @@ import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat
import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED
import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.NEW_ARCH_ENABLED
import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_NEW_ARCH_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC
import com.facebook.react.utils.PropertyUtils.USE_THIRD_PARTY_JSC
@@ -28,12 +26,7 @@ internal object ProjectUtils {
const val HERMES_FALLBACK = true
internal fun Project.isNewArchEnabled(extension: ReactExtension): Boolean {
return (project.hasProperty(NEW_ARCH_ENABLED) &&
project.property(NEW_ARCH_ENABLED).toString().toBoolean()) ||
(project.hasProperty(SCOPED_NEW_ARCH_ENABLED) &&
project.property(SCOPED_NEW_ARCH_ENABLED).toString().toBoolean())
}
internal fun Project.isNewArchEnabled(): Boolean = true
internal val Project.isHermesEnabled: Boolean
get() =
@@ -27,70 +27,8 @@ class ProjectUtilsTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test
fun isNewArchEnabled_returnsFalseByDefault() {
val project = createProject()
val extension = TestReactExtension(project)
assertThat(createProject().isNewArchEnabled(extension)).isFalse()
}
@Test
fun isNewArchEnabled_withDisabled_returnsFalse() {
val project = createProject()
project.extensions.extraProperties.set("newArchEnabled", "false")
val extension = TestReactExtension(project)
assertThat(project.isNewArchEnabled(extension)).isFalse()
}
@Test
fun isNewArchEnabled_withEnabled_returnsTrue() {
val project = createProject()
project.extensions.extraProperties.set("newArchEnabled", "true")
val extension = TestReactExtension(project)
assertThat(project.isNewArchEnabled(extension)).isTrue()
}
@Test
fun isNewArchEnabled_withInvalid_returnsFalse() {
val project = createProject()
project.extensions.extraProperties.set("newArchEnabled", "¯\\_(ツ)_/¯")
val extension = TestReactExtension(project)
assertThat(project.isNewArchEnabled(extension)).isFalse()
}
@Test
fun isNewArchEnabled_withRNVersion0_returnFalse() {
val project = createProject()
val extension = TestReactExtension(project)
File(tempFolder.root, "package.json").apply {
writeText(
// language=json
"""
{
"version": "0.73.0"
}
"""
.trimIndent())
}
extension.reactNativeDir.set(tempFolder.root)
assertThat(project.isNewArchEnabled(extension)).isFalse()
}
@Test
fun isNewArchEnabled_withRNVersion1000_returnFalse() {
val project = createProject()
val extension = TestReactExtension(project)
File(tempFolder.root, "package.json").apply {
writeText(
// language=json
"""
{
"version": "1000.0.0"
}
"""
.trimIndent())
}
extension.reactNativeDir.set(tempFolder.root)
assertThat(project.isNewArchEnabled(extension)).isFalse()
fun isNewArchEnabled_alwaysReturnsTrue() {
assertThat(createProject().isNewArchEnabled()).isTrue()
}
@Test
@@ -29,6 +29,8 @@
"lib"
],
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/parser": "^7.25.3",
"glob": "^7.1.1",
"hermes-parser": "0.30.0",
"invariant": "^2.2.4",
@@ -487,7 +487,7 @@ describe('buildSchemaFromConfigType', () => {
describe('when buildModuleSchema returns null', () => {
it('throws an error', () => {
// $FlowIgnore[incompatible-call] - This is to test an invariant
// $FlowFixMe[incompatible-call] - This is to test an invariant
buildModuleSchemaMock.mockReturnValueOnce(null);
expect(() =>
@@ -10,9 +10,9 @@
'use strict';
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const flowSnaps = require('../../../../src/parsers/flow/components/__tests__/__snapshots__/component-parser-test.js.snap');
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const tsSnaps = require('../../../../src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap');
const flowFixtures = require('../../flow/components/__test_fixtures__/fixtures.js');
const tsFixtures = require('../../typescript/components/__test_fixtures__/fixtures.js');
@@ -10,9 +10,9 @@
'use strict';
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const flowSnaps = require('../../../../src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap');
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const tsSnaps = require('../../../../src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap');
const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js');
const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js');
@@ -48,6 +48,7 @@ const SUPPORTED_STYLES: {[string]: true} = {
borderStartStartRadius: true,
elevation: true,
opacity: true,
filter: true,
transform: true,
zIndex: true,
/* ios styles */
@@ -21,11 +21,11 @@ function mockQueueMicrotask() {
let queueMicrotask;
beforeEach(() => {
queueMicrotask = global.queueMicrotask;
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.queueMicrotask = process.nextTick;
});
afterEach(() => {
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.queueMicrotask = queueMicrotask;
});
}
@@ -60,12 +60,12 @@ function processColor(
}
if (isRgbaValue(color)) {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
return (color: RgbaValue);
}
let normalizedColor: ?ProcessedColorValue = normalizeColor(
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
(color: ColorValue),
);
if (normalizedColor === undefined || normalizedColor === null) {
@@ -125,7 +125,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
let value: RgbaValue | RgbaAnimatedValue | ColorValue =
valueIn ?? defaultColor;
if (isRgbaAnimatedValue(value)) {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue);
this.r = rgbaAnimatedValue.r;
this.g = rgbaAnimatedValue.g;
@@ -133,14 +133,14 @@ export default class AnimatedColor extends AnimatedWithChildren {
this.a = rgbaAnimatedValue.a;
} else {
const processedColor: RgbaValue | NativeColorValue =
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
processColor((value: ColorValue | RgbaValue)) ?? defaultColor;
let initColor: RgbaValue = defaultColor;
if (isRgbaValue(processedColor)) {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
initColor = (processedColor: RgbaValue);
} else {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
this.nativeColor = (processedColor: NativeColorValue);
}
@@ -170,7 +170,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
processColor(value) ?? defaultColor;
this._withSuspendedCallbacks(() => {
if (isRgbaValue(processedColor)) {
// $FlowIgnore[incompatible-type] - Type is verified above
// $FlowFixMe[incompatible-type] - Type is verified above
const rgbaValue: RgbaValue = processedColor;
this.r.setValue(rgbaValue.r);
this.g.setValue(rgbaValue.g);
@@ -181,7 +181,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
shouldUpdateNodeConfig = true;
}
} else {
// $FlowIgnore[incompatible-type] - Type is verified above
// $FlowFixMe[incompatible-type] - Type is verified above
const nativeColor: NativeColorValue = processedColor;
if (this.nativeColor !== nativeColor) {
this.nativeColor = nativeColor;
@@ -224,7 +224,7 @@ function createStringInterpolation(
outputRange.every(output =>
output.components.every(
(component, i) =>
// $FlowIgnoreMe[invalid-compare]
// $FlowFixMe[invalid-compare]
typeof component === 'number' || component === firstOutput[i],
),
),
@@ -235,9 +235,9 @@ function createStringInterpolation(
const numericComponents: $ReadOnlyArray<$ReadOnlyArray<number>> =
outputRange.map(output =>
isColor
? // $FlowIgnoreMe[incompatible-call]
? // $FlowFixMe[incompatible-type]
output.components
: // $FlowIgnoreMe[incompatible-call]
: // $FlowFixMe[incompatible-call]
output.components.filter(c => typeof c === 'number'),
);
const interpolations = numericComponents[0].map((_, i) =>
@@ -393,7 +393,7 @@ export default class AnimatedInterpolation<
let outputRange = this._config.outputRange;
let outputType = null;
if (typeof outputRange[0] === 'string') {
// $FlowIgnoreMe[incompatible-cast]
// $FlowFixMe[incompatible-cast]
outputRange = ((outputRange: $ReadOnlyArray<string>).map(value => {
const processedColor = processColor(value);
if (typeof processedColor === 'number') {
@@ -325,8 +325,8 @@ export default class AnimatedProps extends AnimatedNode {
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
@@ -123,7 +123,7 @@ export default class AnimatedStyle extends AnimatedWithChildren {
this._style = style;
if ((Platform.OS as string) === 'web') {
// $FlowIgnore[cannot-write] - Intentional shadowing.
// $FlowFixMe[cannot-write] - Intentional shadowing.
this.__getValueForStyle = resultStyle => [
originalStyleForWeb,
resultStyle,
@@ -249,8 +249,8 @@ export default class AnimatedStyle extends AnimatedWithChildren {
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
@@ -31,7 +31,7 @@
void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
{
RCTEnableTurboModule(turboModuleEnabled);
RCTEnableTurboModule(YES);
#if DEBUG
// Disable idle timer in dev builds to avoid putting application in background and complicating
@@ -43,15 +43,12 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
UIView *
RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary *initialProperties, BOOL fabricEnabled)
{
if (fabricEnabled) {
id<RCTSurfaceProtocol> surface = [[RCTFabricSurface alloc] initWithBridge:bridge
moduleName:moduleName
initialProperties:initialProperties];
UIView *rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
[surface start];
return rootView;
}
return [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
id<RCTSurfaceProtocol> surface = [[RCTFabricSurface alloc] initWithBridge:bridge
moduleName:moduleName
initialProperties:initialProperties];
UIView *rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
[surface start];
return rootView;
}
NSArray<NSString *> *RCTAppSetupUnstableModulesRequiringMainQueueSetup(id<RCTDependencyProvider> dependencyProvider)
@@ -57,8 +57,7 @@
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
{
BOOL enableFabric = self.fabricEnabled;
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, enableFabric);
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES);
rootView.backgroundColor = [UIColor systemBackgroundColor];
@@ -107,22 +106,22 @@
- (BOOL)newArchEnabled
{
return RCTIsNewArchEnabled();
return YES;
}
- (BOOL)bridgelessEnabled
{
return self.newArchEnabled;
return YES;
}
- (BOOL)fabricEnabled
{
return self.newArchEnabled;
return YES;
}
- (BOOL)turboModuleEnabled
{
return self.newArchEnabled;
return YES;
}
- (Class)getModuleClassFromName:(const char *)name
@@ -52,17 +52,12 @@ using namespace facebook::react;
self.delegate = delegate;
[self _setUpFeatureFlags:releaseLevel];
auto newArchEnabled = [self newArchEnabled];
auto fabricEnabled = [self fabricEnabled];
[RCTColorSpaceUtils applyDefaultColorSpace:[self defaultColorSpace]];
RCTEnableTurboModule([self turboModuleEnabled]);
RCTEnableTurboModule(YES);
self.rootViewFactory = [self createRCTRootViewFactory];
if (newArchEnabled || fabricEnabled) {
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
}
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
}
return self;
@@ -126,37 +121,22 @@ using namespace facebook::react;
- (BOOL)newArchEnabled
{
if ([_delegate respondsToSelector:@selector(newArchEnabled)]) {
return _delegate.newArchEnabled;
}
return RCTIsNewArchEnabled();
return YES;
}
- (BOOL)fabricEnabled
{
if ([_delegate respondsToSelector:@selector(fabricEnabled)]) {
return _delegate.fabricEnabled;
}
return [self newArchEnabled];
return YES;
}
- (BOOL)turboModuleEnabled
{
if ([_delegate respondsToSelector:@selector(turboModuleEnabled)]) {
return _delegate.turboModuleEnabled;
}
return [self newArchEnabled];
return YES;
}
- (BOOL)bridgelessEnabled
{
if ([_delegate respondsToSelector:@selector(bridgelessEnabled)]) {
return _delegate.bridgelessEnabled;
}
return [self newArchEnabled];
return YES;
}
#pragma mark - RCTTurboModuleManagerDelegate
@@ -250,9 +230,9 @@ using namespace facebook::react;
RCTRootViewFactoryConfiguration *configuration =
[[RCTRootViewFactoryConfiguration alloc] initWithBundleURLBlock:bundleUrlBlock
newArchEnabled:self.fabricEnabled
turboModuleEnabled:self.turboModuleEnabled
bridgelessEnabled:self.bridgelessEnabled];
newArchEnabled:YES
turboModuleEnabled:YES
bridgelessEnabled:YES];
configuration.createRootViewWithBridge = ^UIView *(RCTBridge *bridge, NSString *moduleName, NSDictionary *initProps) {
return [weakSelf.delegate createRootViewWithBridge:bridge moduleName:moduleName initProps:initProps];
@@ -334,9 +314,7 @@ using namespace facebook::react;
dispatch_once(&setupFeatureFlagsToken, ^{
switch (releaseLevel) {
case Stable:
if ([self bridgelessEnabled]) {
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSStable>());
}
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSStable>());
break;
case Canary:
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSCanary>());
@@ -73,9 +73,9 @@
{
if (self = [super init]) {
_bundleURLBlock = bundleURLBlock;
_fabricEnabled = newArchEnabled;
_turboModuleEnabled = turboModuleEnabled;
_bridgelessEnabled = bridgelessEnabled;
_fabricEnabled = YES;
_turboModuleEnabled = YES;
_bridgelessEnabled = YES;
}
return self;
}
@@ -135,17 +135,12 @@
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions
{
if (_configuration.bridgelessEnabled) {
// Enable TurboModule interop by default in Bridgeless mode
RCTEnableTurboModuleInterop(YES);
RCTEnableTurboModuleInteropBridgeProxy(YES);
// Enable TurboModule interop by default in Bridgeless mode
RCTEnableTurboModuleInterop(YES);
RCTEnableTurboModuleInteropBridgeProxy(YES);
[self createReactHostIfNeeded:launchOptions];
return;
}
[self createBridgeIfNeeded:launchOptions];
[self createBridgeAdapterIfNeeded];
[self createReactHostIfNeeded:launchOptions];
return;
}
- (UIView *)viewWithModuleName:(NSString *)moduleName
@@ -154,29 +149,17 @@
{
[self initializeReactHostWithLaunchOptions:launchOptions];
if (_configuration.bridgelessEnabled) {
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName initialProperties:initProps];
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName
initialProperties:initProps ? initProps : @{}];
RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView =
[[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView =
[[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor];
if (_configuration.customizeRootView != nil) {
_configuration.customizeRootView(surfaceHostingProxyRootView);
}
return surfaceHostingProxyRootView;
}
UIView *rootView;
if (_configuration.createRootViewWithBridge != nil) {
rootView = _configuration.createRootViewWithBridge(self.bridge, moduleName, initProps);
} else {
rootView = [self createRootViewWithBridge:self.bridge moduleName:moduleName initProps:initProps];
}
surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor];
if (_configuration.customizeRootView != nil) {
_configuration.customizeRootView(rootView);
_configuration.customizeRootView(surfaceHostingProxyRootView);
}
return rootView;
return surfaceHostingProxyRootView;
}
- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
@@ -188,8 +171,7 @@
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
{
BOOL enableFabric = _configuration.fabricEnabled;
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, enableFabric);
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES);
rootView.backgroundColor = [UIColor systemBackgroundColor];
return rootView;
}
@@ -198,19 +180,15 @@
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
_runtimeScheduler = std::make_shared<facebook::react::RuntimeScheduler>(RCTRuntimeExecutorFromBridge(bridge));
if (RCTIsNewArchEnabled()) {
std::shared_ptr<facebook::react::CallInvoker> callInvoker =
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
RCTTurboModuleManager *turboModuleManager =
[[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:_turboModuleManagerDelegate
jsInvoker:callInvoker];
_contextContainer->erase("RuntimeScheduler");
_contextContainer->insert("RuntimeScheduler", _runtimeScheduler);
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
} else {
return RCTAppSetupJsExecutorFactoryForOldArch(bridge, _runtimeScheduler);
}
std::shared_ptr<facebook::react::CallInvoker> callInvoker =
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:_turboModuleManagerDelegate
jsInvoker:callInvoker];
_contextContainer->erase("RuntimeScheduler");
_contextContainer->insert("RuntimeScheduler", _runtimeScheduler);
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
}
- (void)createBridgeIfNeeded:(NSDictionary *)launchOptions
@@ -228,7 +206,7 @@
- (void)createBridgeAdapterIfNeeded
{
if (!self->_configuration.fabricEnabled || self.bridgeAdapter) {
if (self.bridgeAdapter != nullptr) {
return;
}
@@ -618,6 +618,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
// so omitting onBlur and onFocus pressability handlers here.
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
const _accessibilityLabel =
props?.['aria-label'] ?? props?.accessibilityLabel;
let _accessibilityState;
if (
accessibilityState != null ||
@@ -681,6 +684,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
{...otherProps}
{...eventHandlers}
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessible={accessible}
submitBehavior={submitBehavior}
@@ -744,8 +748,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
{...otherProps}
{...colorProps}
{...eventHandlers}
accessibilityState={_accessibilityState}
accessibilityLabel={_accessibilityLabel}
accessibilityLabelledBy={_accessibilityLabelledBy}
accessibilityState={_accessibilityState}
accessible={accessible}
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
autoCapitalize={autoCapitalize}
@@ -10,213 +10,404 @@
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import type {TextInputInstance} from '../TextInput.flow';
import ensureInstance from '../../../../src/private/__tests__/utilities/ensureInstance';
import * as Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
import * as React from 'react';
import {createRef, useEffect, useLayoutEffect, useRef} from 'react';
import {TextInput} from 'react-native';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
describe('focus view command', () => {
it('creates view before dispatching view command from ref function', () => {
const root = Fantom.createRoot();
describe('<TextInput>', () => {
describe('props', () => {
describe('selection', () => {
it('the selection is passed to component view by command', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<TextInput
nativeID="text-input"
ref={node => {
if (node) {
node.focus();
}
}}
/>,
);
});
Fantom.runTask(() => {
root.render(
<TextInput nativeID="text-input" selection={{start: 0, end: 4}}>
hello World!
</TextInput>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
it('creates view before dispatching view command from useLayoutEffect', () => {
const root = Fantom.createRoot();
function Component() {
const textInputRef = useRef<null | React.ElementRef<typeof TextInput>>(
null,
);
useLayoutEffect(() => {
textInputRef.current?.focus();
});
return <TextInput ref={textInputRef} nativeID="text-input" />;
}
Fantom.runTask(() => {
root.render(<Component />);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
it('creates view before dispatching view command from useEffect', () => {
const root = Fantom.createRoot();
function Component() {
const textInputRef = useRef<null | React.ElementRef<typeof TextInput>>(
null,
);
useEffect(() => {
textInputRef.current?.focus();
});
return <TextInput ref={textInputRef} nativeID="text-input" />;
}
Fantom.runTask(() => {
root.render(<Component />);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
});
describe('focus and blur event', () => {
it('sends focus and blur events', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<HostInstance>();
let focusEvent = jest.fn();
let blurEvent = jest.fn();
Fantom.runTask(() => {
root.render(
<TextInput onFocus={focusEvent} onBlur={blurEvent} ref={nodeRef} />,
);
});
const element = ensureInstance(nodeRef.current, ReactNativeElement);
expect(focusEvent).toHaveBeenCalledTimes(0);
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'focus');
});
// The tasks have not run.
expect(focusEvent).toHaveBeenCalledTimes(0);
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runWorkLoop();
expect(focusEvent).toHaveBeenCalledTimes(1);
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'blur');
});
Fantom.runWorkLoop();
expect(focusEvent).toHaveBeenCalledTimes(1);
expect(blurEvent).toHaveBeenCalledTimes(1);
});
});
describe('onChange', () => {
it('delivers onChange event', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<HostInstance>();
const onChange = jest.fn();
Fantom.runTask(() => {
root.render(
<TextInput
onChange={event => {
onChange(event.nativeEvent);
}}
ref={nodeRef}
/>,
);
});
const element = ensureInstance(nodeRef.current, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "setTextAndSelection, args: [0,null,0,4]"}',
]);
});
});
Fantom.runWorkLoop();
describe('onChange', () => {
it('is called when the change native event is dispatched', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<TextInputInstance>();
const onChange = jest.fn();
expect(onChange).toHaveBeenCalledTimes(1);
const [entry] = onChange.mock.lastCall;
expect(entry.text).toEqual('Hello World');
});
});
Fantom.runTask(() => {
root.render(
<TextInput
onChange={event => {
onChange(event.nativeEvent);
}}
ref={nodeRef}
/>,
);
});
describe('onChangeText', () => {
it('delivers onChangeText event', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<HostInstance>();
const onChangeText = jest.fn();
const element = ensureInstance(nodeRef.current, ReactNativeElement);
Fantom.runTask(() => {
root.render(<TextInput onChangeText={onChangeText} ref={nodeRef} />);
});
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
});
});
const element = ensureInstance(nodeRef.current, ReactNativeElement);
Fantom.runWorkLoop();
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
expect(onChange).toHaveBeenCalledTimes(1);
const [entry] = onChange.mock.lastCall;
expect(entry.text).toEqual('Hello World');
});
});
Fantom.runWorkLoop();
describe('onChangeText', () => {
it('is called when the change native event is dispatched', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<TextInputInstance>();
const onChangeText = jest.fn();
expect(onChangeText).toHaveBeenCalledTimes(1);
const [entry] = onChangeText.mock.lastCall;
expect(entry).toEqual('Hello World');
});
});
Fantom.runTask(() => {
root.render(<TextInput onChangeText={onChangeText} ref={nodeRef} />);
});
describe('props.selection', () => {
it('the selection is passed to component view by command', () => {
const root = Fantom.createRoot();
const element = ensureInstance(nodeRef.current, ReactNativeElement);
Fantom.runTask(() => {
root.render(
<TextInput nativeID="text-input" selection={{start: 0, end: 4}}>
hello World!
</TextInput>,
);
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
});
});
Fantom.runWorkLoop();
expect(onChangeText).toHaveBeenCalledTimes(1);
const [entry] = onChangeText.mock.lastCall;
expect(entry).toEqual('Hello World');
});
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "setTextAndSelection, args: [0,null,0,4]"}',
]);
describe('onFocus', () => {
it('is called when the focus native event is dispatched', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<TextInputInstance>();
let focusEvent = jest.fn();
Fantom.runTask(() => {
root.render(<TextInput onFocus={focusEvent} ref={nodeRef} />);
});
const element = ensureInstance(nodeRef.current, ReactNativeElement);
expect(focusEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'focus');
});
// The tasks have not run.
expect(focusEvent).toHaveBeenCalledTimes(0);
Fantom.runWorkLoop();
expect(focusEvent).toHaveBeenCalledTimes(1);
});
});
describe('onBlur', () => {
it('is called when the blur native event is dispatched', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<TextInputInstance>();
let blurEvent = jest.fn();
Fantom.runTask(() => {
root.render(<TextInput onBlur={blurEvent} ref={nodeRef} />);
});
const element = ensureInstance(nodeRef.current, ReactNativeElement);
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.enqueueNativeEvent(element, 'blur');
});
// The tasks have not run.
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runWorkLoop();
expect(blurEvent).toHaveBeenCalledTimes(1);
});
});
});
describe('ref', () => {
it('is an element node', () => {
const ref = createRef<TextInputInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<TextInput ref={ref} />);
});
expect(ref.current).toBeInstanceOf(ReactNativeElement);
});
it('provides additional methods: clear, isFocused, getNativeRef, setSelection', () => {
const ref = createRef<TextInputInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<TextInput ref={ref} />);
});
const instance = nullthrows(ref.current);
expect(instance.clear).toBeInstanceOf(Function);
expect(instance.isFocused).toBeInstanceOf(Function);
expect(instance.getNativeRef).toBeInstanceOf(Function);
});
describe('focus()', () => {
it('dispatches the focus command', () => {
const root = Fantom.createRoot();
const ref = createRef<TextInputInstance>();
Fantom.runTask(() => {
root.render(<TextInput nativeID="text-input" ref={ref} />);
});
root.takeMountingManagerLogs();
const instance = nullthrows(ref.current);
Fantom.runTask(() => {
instance.focus();
});
expect(root.takeMountingManagerLogs()).toEqual([
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
it('creates view before dispatching view command from ref function', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<TextInput
nativeID="text-input"
ref={node => {
if (node) {
node.focus();
}
}}
/>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
it('creates view before dispatching view command from useLayoutEffect', () => {
const root = Fantom.createRoot();
function Component() {
const textInputRef = useRef<null | React.ElementRef<
typeof TextInput,
>>(null);
useLayoutEffect(() => {
textInputRef.current?.focus();
});
return <TextInput ref={textInputRef} nativeID="text-input" />;
}
Fantom.runTask(() => {
root.render(<Component />);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
it('creates view before dispatching view command from useEffect', () => {
const root = Fantom.createRoot();
function Component() {
const textInputRef = useRef<null | React.ElementRef<
typeof TextInput,
>>(null);
useEffect(() => {
textInputRef.current?.focus();
});
return <TextInput ref={textInputRef} nativeID="text-input" />;
}
Fantom.runTask(() => {
root.render(<Component />);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "AndroidTextInput", nativeID: "text-input"}',
'Insert {type: "AndroidTextInput", parentNativeID: (root), index: 0, nativeID: "text-input"}',
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
]);
});
});
describe('blur()', () => {
it('does NOT dispatch any commands if the input is NOT focused', () => {
const root = Fantom.createRoot();
const ref = createRef<TextInputInstance>();
Fantom.runTask(() => {
root.render(<TextInput nativeID="text-input" ref={ref} />);
});
root.takeMountingManagerLogs();
const instance = nullthrows(ref.current);
Fantom.runTask(() => {
instance.blur();
});
expect(root.takeMountingManagerLogs()).toEqual([]);
});
it('does dispatches the blur command if the input is focused', () => {
const root = Fantom.createRoot();
const ref = createRef<TextInputInstance>();
Fantom.runTask(() => {
root.render(<TextInput nativeID="text-input" ref={ref} />);
});
const instance = nullthrows(ref.current);
Fantom.runTask(() => {
instance.focus();
});
root.takeMountingManagerLogs();
Fantom.runTask(() => {
instance.blur();
});
expect(root.takeMountingManagerLogs()).toEqual([
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "blur"}',
]);
});
});
describe('clear()', () => {
it('dispatches the clear command', () => {
const root = Fantom.createRoot();
const ref = createRef<TextInputInstance>();
Fantom.runTask(() => {
root.render(
<TextInput nativeID="text-input" ref={ref} value="Some input" />,
);
});
root.takeMountingManagerLogs();
const instance = nullthrows(ref.current);
Fantom.runTask(() => {
instance.clear();
});
expect(root.takeMountingManagerLogs()).toEqual([
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "setTextAndSelection, args: [0,"",0,0]"}',
]);
});
});
describe('isFocused()', () => {
it('returns true if the input is focused', () => {
const root = Fantom.createRoot();
const ref = createRef<TextInputInstance>();
Fantom.runTask(() => {
root.render(<TextInput nativeID="text-input" ref={ref} />);
});
const instance = nullthrows(ref.current);
expect(instance.isFocused()).toBe(false);
Fantom.runTask(() => {
instance.focus();
});
expect(instance.isFocused()).toBe(true);
Fantom.runTask(() => {
instance.blur();
});
expect(instance.isFocused()).toBe(false);
});
});
describe('setSelection', () => {
it('dispatches the setTextAndSelection command', () => {
const root = Fantom.createRoot();
const ref = createRef<TextInputInstance>();
Fantom.runTask(() => {
root.render(
<TextInput nativeID="text-input" ref={ref} value="Some input" />,
);
});
root.takeMountingManagerLogs();
const instance = nullthrows(ref.current);
Fantom.runTask(() => {
instance.setSelection(2, 5);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "setTextAndSelection, args: [0,null,2,5]"}',
]);
});
});
});
});
@@ -432,6 +432,7 @@ jest.unmock('../TextInput');
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTSinglelineTextInputView
accessibilityLabel="label"
accessibilityState={
Object {
"busy": true,
@@ -477,7 +477,7 @@ function runExceptionsManagerTests() {
expect(nativeReportException).not.toBeCalled();
expect(logBoxAddConsoleLog).toBeCalledTimes(1);
expect(logBoxAddConsoleLog.mock.calls[0][0]).toBe('error');
// $FlowIgnore[incompatible-call]
// $FlowFixMe[incompatible-call]
expect(logBoxAddConsoleLog.mock.calls[0][1]).toBe(...args);
} else {
expect(logBoxAddException).not.toBeCalled();
@@ -554,7 +554,7 @@ function runExceptionsManagerTests() {
const object = {
toString: () => 'Warning: Some error may have happened',
};
// $FlowIgnore[prop-missing]
// $FlowFixMe[prop-missing]
object.cycle = object;
const args = [object];
@@ -27,12 +27,12 @@ function _setDevelopmentModeForTests(dev: mixed) {
beforeAll(() => {
originalDev = global.__DEV__;
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = dev;
});
afterAll(() => {
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = originalDev;
});
}
@@ -12,14 +12,14 @@ import {getUrlCacheBreaker, setUrlCacheBreaker} from '../AssetUtils';
describe('AssetUtils', () => {
afterEach(() => {
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = true;
jest.clearAllMocks();
});
it('should return empty string and warn once if no cacheBreaker set (DEV)', () => {
const mockWarn = jest.spyOn(console, 'warn').mockReturnValue(undefined);
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = true;
expect(getUrlCacheBreaker()).toEqual('');
expect(getUrlCacheBreaker()).toEqual('');
@@ -28,7 +28,7 @@ describe('AssetUtils', () => {
it('should return empty string if no cacheBreaker set in prod', () => {
const mockWarn = jest.spyOn(console, 'warn');
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = false;
expect(getUrlCacheBreaker()).toEqual('');
expect(mockWarn).not.toHaveBeenCalled();
@@ -0,0 +1,656 @@
/**
* 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 '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
import {Image} from 'react-native';
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
const LOGO_SOURCE = {uri: 'https://reactnative.dev/img/tiny_logo.png'};
describe('<Image>', () => {
describe('props', () => {
describe('empty props', () => {
// TODO T233552213: do not send empty source
it('renders an empty element when there are no props', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image />);
});
expect(root.getRenderedOutput().toJSX()).toEqual(
<rn-image overflow="hidden" source-scale="1" source-type="remote" />,
);
Fantom.runTask(() => {
root.render(<Image src="" />);
});
expect(root.getRenderedOutput().toJSX()).toEqual(
<rn-image overflow="hidden" source-scale="1" source-type="remote" />,
);
});
});
describe('accessibility', () => {
describe('accessible', () => {
it('indicates that image is an accessibility element', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image accessible={true} />);
});
expect(
root.getRenderedOutput({props: ['accessible']}).toJSX(),
).toEqual(<rn-image accessible="true" />);
});
});
describe('accessibilityLabel', () => {
it('provides information for screen reader', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image accessibilityLabel="React Native Logo" />);
});
expect(
root.getRenderedOutput({props: ['accessibilityLabel']}).toJSX(),
).toEqual(<rn-image accessibilityLabel="React Native Logo" />);
});
});
describe('alt', () => {
it('provides information for screen reader', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image alt="React Native Logo" />);
});
expect(root.getRenderedOutput({props: ['^access']}).toJSX()).toEqual(
<rn-image
accessible="true"
accessibilityLabel="React Native Logo"
/>,
);
});
it('can be set alongside accessibilityLabel, but accessibilityLabel has higher priority', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
alt="React Native Logo"
accessibilityLabel="React Native"
/>,
);
});
expect(root.getRenderedOutput({props: ['^access']}).toJSX()).toEqual(
<rn-image accessible="true" accessibilityLabel="React Native" />,
);
});
});
});
describe('blurRadius', () => {
it('provides blur radius for image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image blurRadius={10} />);
});
expect(root.getRenderedOutput({props: ['blurRadius']}).toJSX()).toEqual(
<rn-image blurRadius="10" />,
);
});
});
describe('crossOrigin', () => {
it('does not set any headers in anonymous mode', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-type="remote"
source-uri={LOGO_SOURCE.uri}
/>,
);
Fantom.runTask(() => {
root.render(<Image crossOrigin="anonymous" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-type="remote"
source-uri={LOGO_SOURCE.uri}
/>,
);
});
it('sets the "Access-Control-Allow-Credentials" header in "use-credentials" mode', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image crossOrigin="use-credentials" source={LOGO_SOURCE} />,
);
});
expect(
root.getRenderedOutput({props: ['source-header']}).toJSX(),
).toEqual(
<rn-image source-header-Access-Control-Allow-Credentials="true" />,
);
});
});
describe('defaultSource', () => {
it('can provide a default image to display', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
defaultSource={require('./img/img1.png')}
source={LOGO_SOURCE}
/>,
);
});
expect(
root.getRenderedOutput({props: ['defaultSource']}).toJSX(),
).toEqual(
<rn-image
defaultSource-type="remote"
defaultSource-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
/>,
);
});
});
describe('height', () => {
it('provides height for image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image height={100} source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
<rn-image height="100.000000" />,
);
});
});
describe('width', () => {
it('provides width for image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image width={100} source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['width']}).toJSX()).toEqual(
<rn-image width="100.000000" />,
);
});
});
describe('loading progress', () => {
(
[
['onError', 'fails to load'],
['onLoadStart', 'start loading'],
['onProgress', 'is loading'],
['onLoad', 'loads successfully'],
['onLoadEnd', 'ends loading'],
] as const
).forEach(([onProp, event]) => {
it(`${onProp} is called when image ${event}`, () => {
const onPropCallback = jest.fn();
const ref = createRef<HostInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
ref={ref}
source={LOGO_SOURCE}
onError={() => {
onProp === 'onError' && onPropCallback();
}}
onLoad={() => {
onProp === 'onLoad' && onPropCallback();
}}
onLoadStart={() => {
onProp === 'onLoadStart' && onPropCallback();
}}
onLoadEnd={() => {
onProp === 'onLoadEnd' && onPropCallback();
}}
onProgress={() => {
onProp === 'onProgress' && onPropCallback();
}}
/>,
);
});
expect(onPropCallback).toHaveBeenCalledTimes(0);
const image = ensureInstance(ref.current, ReactNativeElement);
Fantom.dispatchNativeEvent(image, onProp, {});
expect(onPropCallback).toHaveBeenCalledTimes(1);
});
});
});
describe('referrerPolicy', () => {
(
[
'no-referrer',
'no-referrer-when-downgrade',
'origin',
'origin-when-cross-origin',
'same-origin',
'strict-origin',
'strict-origin-when-cross-origin',
'unsafe-url',
] as const
).forEach(referrerPolicy => {
it(`${referrerPolicy} sets correct "Referrer-Policy" header`, () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image referrerPolicy={referrerPolicy} src={LOGO_SOURCE.uri} />,
);
});
expect(
root.getRenderedOutput({props: ['source-header']}).toJSX(),
).toEqual(
<rn-image source-header-Referrer-Policy={referrerPolicy} />,
);
});
});
});
describe('resizeMode', () => {
it('is set to "cover" by default', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
<rn-image />,
);
Fantom.runTask(() => {
root.render(<Image resizeMode="cover" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
<rn-image />,
);
});
(['stretch', 'contain', 'repeat', 'center'] as const).forEach(
resizeMode => {
it(`can be set to "${resizeMode}"`, () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image resizeMode={resizeMode} source={LOGO_SOURCE} />,
);
});
expect(
root.getRenderedOutput({props: ['resizeMode']}).toJSX(),
).toEqual(<rn-image resizeMode={resizeMode} />);
});
},
);
});
describe('source', () => {
it('can be set to a local image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image source={require('./img/img1.png')} />);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-size="{1, 1}"
source-type="local"
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
/>,
);
});
it('can be set to a remote image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
width: 100,
height: 100,
scale: 2,
cache: 'only-if-cached',
method: 'POST',
body: 'name=React+Native',
headers: {
Authorization: 'Basic RandomString',
},
}}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-body="name=React+Native"
source-cache="only-if-cached"
source-header-Authorization="Basic RandomString"
source-method="POST"
source-scale="2"
source-size="{100, 100}"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
it('can be set to a list of remote images', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
source={[
{
uri: 'https://reactnative.dev/img/tiny_logo.png',
scale: 1,
headers: {
Authorization: 'Basic RandomString',
},
},
{
uri: 'https://reactnative.dev/img/medium_logo.png',
scale: 2,
cache: 'only-if-cached',
},
{
uri: 'https://reactnative.dev/img/large_logo.png',
scale: 3,
method: 'POST',
},
]}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-header-Authorization="Basic RandomString"
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-cache="only-if-cached"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/medium_logo.png"
source-3x-method="POST"
source-3x-type="remote"
source-3x-uri="https://reactnative.dev/img/large_logo.png"
/>,
);
});
});
describe('src', () => {
it('can be set to a remote image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image src="https://reactnative.dev/img/tiny_logo.png" />,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
it('takes precedence over `source` prop', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
src="https://reactnative.dev/img/tiny_logo.png"
source={{uri: 'https://reactnative.dev/img/medium_logo.png'}}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
});
describe('srcSet', () => {
it('can be set to a list of remote images', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
srcSet={
'https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x'
}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
/>,
);
});
it('defaults to `1x` descriptor', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
srcSet={
'https://reactnative.dev/img/tiny_logo.png, https://reactnative.dev/img/header_logo.svg 2x'
}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
/>,
);
});
it('uses `src` for `1x` descriptor when provided', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
srcSet={
'https://reactnative.dev/img/header_logo.svg 2x, https://reactnative.dev/img/large_logo.svg 3x'
}
src="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
source-3x-type="remote"
source-3x-uri="https://reactnative.dev/img/large_logo.svg"
/>,
);
});
});
describe('style', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
style={{
width: 100,
height: 100,
resizeMode: 'contain',
}}
source={LOGO_SOURCE}
/>,
);
});
expect(root.getRenderedOutput().toJSX()).toEqual(
<rn-image
height="100.000000"
overflow="hidden"
resizeMode="contain"
width="100.000000"
source-scale="1"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
});
describe('testID', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image testID="test" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['testID']}).toJSX()).toEqual(
<rn-image testID="test" />,
);
});
});
describe('tintColor', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image tintColor="red" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['tintColor']}).toJSX()).toEqual(
<rn-image tintColor="rgba(255, 0, 0, 1)" />,
);
});
});
});
describe('ref', () => {
describe('instance', () => {
it('is an element node', () => {
const elementRef = createRef<HostInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image ref={elementRef} />);
});
expect(elementRef.current).toBeInstanceOf(ReactNativeElement);
});
it('uses the "RN:Image" tag name', () => {
const elementRef = createRef<HostInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image ref={elementRef} />);
});
const element = ensureInstance(elementRef.current, ReactNativeElement);
expect(element.tagName).toBe('RN:Image');
});
});
});
});
@@ -0,0 +1,128 @@
/**
* 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 '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
import {ImageBackground} from 'react-native';
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
describe('<ImageBackground>', () => {
describe('props', () => {
describe('ImageProps', () => {
it('can have local source', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<ImageBackground source={require('./img/img1.png')} />);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-size="{1, 1}"
source-type="local"
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
/>,
);
});
it('can have remote source', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<ImageBackground
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
width: 100,
height: 100,
scale: 2,
cache: 'only-if-cached',
method: 'POST',
body: 'name=React+Native',
headers: {
Authorization: 'Basic RandomString',
},
}}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-body="name=React+Native"
source-cache="only-if-cached"
source-header-Authorization="Basic RandomString"
source-method="POST"
source-scale="2"
source-size="{100, 100}"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
it('can have srcSet', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<ImageBackground srcSet="https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x" />,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
/>,
);
});
});
describe('style', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<ImageBackground style={{width: 100, height: 100}} />);
});
expect(
root.getRenderedOutput({props: ['width|height']}).toJSX(),
).toEqual(<rn-image width="100.000000" height="100.000000" />);
});
});
});
describe('ref', () => {
it('Allows to set a reference to the inner `Image` component', () => {
const elementRef = createRef<HostInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<ImageBackground imageRef={elementRef} />);
});
const image = ensureInstance(elementRef.current, ReactNativeElement);
expect(image.tagName).toBe('RN:Image');
});
});
});
@@ -164,7 +164,7 @@ const InteractionManagerStub = {
*/
addListener(
eventType: string,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
listener: (...args: any) => mixed,
context: mixed,
): EventSubscription {
@@ -43,7 +43,7 @@ Pod::Spec.new do |s|
s.dependency "RCTTypeSafety"
s.dependency "React-jsi"
s.dependency "React-Core/RCTNetworkHeaders"
add_dependency(s, "React-debug")
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-featureflags")
@@ -26,8 +26,6 @@ import createPerformanceLogger from '../Utilities/createPerformanceLogger';
import SceneTracker from '../Utilities/SceneTracker';
import {coerceDisplayMode} from './DisplayMode';
import HeadlessJsTaskError from './HeadlessJsTaskError';
import NativeHeadlessJsTaskSupport from './NativeHeadlessJsTaskSupport';
import renderApplication from './renderApplication';
import {unmountComponentAtNodeAndRemoveContainer} from './RendererProxy';
import invariant from 'invariant';
@@ -86,6 +84,7 @@ export function registerComponent(
): string {
const scopedPerformanceLogger = createPerformanceLogger();
runnables[appKey] = (appParameters, displayMode) => {
const renderApplication = require('./renderApplication').default;
renderApplication(
componentProviderInstrumentationHook(
componentProvider,
@@ -258,6 +257,9 @@ export function startHeadlessTask(
taskKey: string,
data: any,
): void {
const NativeHeadlessJsTaskSupport =
require('./NativeHeadlessJsTaskSupport').default;
const taskProvider = taskProviders.get(taskKey);
if (!taskProvider) {
console.warn(`No task registered for key ${taskKey}`);
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow
* @generated SignedSource<<16b364e89f43b8a47832b0dfb98af11e>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<cf323fc5ca893bab5669c7d321660412>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow strict-local
* @generated SignedSource<<1dd9e9c3f20e37ae14e485fc6ee3d9e9>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<908f5fb85384725318e261f40e49d9a6>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow
* @generated SignedSource<<e2c46705ed927302dbe9332dafba459d>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<8f46fdc9267fcc4fdc9e76842fe24066>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow strict-local
* @generated SignedSource<<e8dce0e82b831c91465d04b49fb48ab2>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<83073425aa3f71ced2c8c51f25a25938>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow strict-local
* @generated SignedSource<<556d1487de0b9e4a09cbc67dd130a884>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<52163887de05f1cff05388145cf85b3b>>
*/
'use strict';
@@ -18,9 +18,9 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
let inner: ?____ViewStyle_Internal = null;
if (props != null) {
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
outer = {};
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
inner = {};
for (const prop of Object.keys(props)) {
+517 -243
View File
@@ -14,6 +14,7 @@ import type {GestureResponderEvent} from '../Types/CoreEventTypes';
import type {NativeTextProps} from './TextNativeComponent';
import type {PressRetentionOffset, TextProps} from './TextProps';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import * as PressabilityDebug from '../Pressability/PressabilityDebug';
import usePressability from '../Pressability/usePressability';
import flattenStyle from '../StyleSheet/flattenStyle';
@@ -35,156 +36,495 @@ type TextForwardRef = React.ElementRef<
*
* @see https://reactnative.dev/docs/text
*/
const TextImpl: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = ({
ref: forwardedRef,
accessible,
accessibilityLabel,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
children,
ellipsizeMode,
disabled,
id,
nativeID,
numberOfLines,
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
selectable,
selectionColor,
suppressHighlighting,
style,
...restProps
}: {
ref?: React.RefSetter<TextForwardRef>,
...TextProps,
}) => {
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
let _accessibilityState: ?TextProps['accessibilityState'] =
accessibilityState;
if (
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
if (_accessibilityState != null) {
_accessibilityState = {
busy: ariaBusy ?? _accessibilityState.busy,
checked: ariaChecked ?? _accessibilityState.checked,
disabled: ariaDisabled ?? _accessibilityState.disabled,
expanded: ariaExpanded ?? _accessibilityState.expanded,
selected: ariaSelected ?? _accessibilityState.selected,
};
} else {
_accessibilityState = {
busy: ariaBusy,
checked: ariaChecked,
disabled: ariaDisabled,
expanded: ariaExpanded,
selected: ariaSelected,
};
let _TextImpl;
if (ReactNativeFeatureFlags.reduceDefaultPropsInText()) {
const TextImplNoDefaultProps: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = ({
ref: forwardedRef,
accessible,
accessibilityLabel,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
children,
ellipsizeMode,
disabled,
id,
nativeID,
numberOfLines,
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
selectable,
selectionColor,
suppressHighlighting,
style,
...restProps
}: {
ref?: React.RefSetter<TextForwardRef>,
...TextProps,
}) => {
const processedProps = restProps as {
...NativeTextProps,
};
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
let _accessibilityState: ?TextProps['accessibilityState'] =
accessibilityState;
if (
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
if (_accessibilityState != null) {
_accessibilityState = {
busy: ariaBusy ?? _accessibilityState.busy,
checked: ariaChecked ?? _accessibilityState.checked,
disabled: ariaDisabled ?? _accessibilityState.disabled,
expanded: ariaExpanded ?? _accessibilityState.expanded,
selected: ariaSelected ?? _accessibilityState.selected,
};
} else {
_accessibilityState = {
busy: ariaBusy,
checked: ariaChecked,
disabled: ariaDisabled,
expanded: ariaExpanded,
selected: ariaSelected,
};
}
}
}
const _accessibilityStateDisabled = _accessibilityState?.disabled;
const _disabled = disabled ?? _accessibilityStateDisabled;
const _accessibilityStateDisabled = _accessibilityState?.disabled;
const _disabled = disabled ?? _accessibilityStateDisabled;
const isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
if (__DEV__) {
if (PressabilityDebug.isEnabled() && onPress != null) {
_style = [style, {color: 'magenta'}];
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
// falsy states we need to update the accessibilityState object to use the disabled prop.
if (
_accessibilityState != null &&
_disabled !== _accessibilityStateDisabled &&
((_disabled != null && _disabled !== false) ||
(_accessibilityStateDisabled != null &&
_accessibilityStateDisabled !== false))
) {
_accessibilityState.disabled = _disabled;
}
}
let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
const _accessible = Platform.select({
ios: accessible !== false,
android:
accessible == null
? onPress != null || onLongPress != null
: accessible,
default: accessible,
});
const isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
if (__DEV__) {
console.error(
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
if (PressabilityDebug.isEnabled() && onPress != null) {
_style = [style, {color: 'magenta'}];
}
}
let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
if (__DEV__) {
console.error(
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
);
}
_numberOfLines = 0;
}
let _selectable = selectable;
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-cast]
(String(processedStyle.fontWeight): TextStyleInternal['fontWeight']);
}
if (processedStyle.userSelect != null) {
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
overrides = overrides || ({}: {...TextStyleInternal});
overrides.userSelect = undefined;
}
if (processedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
const _nativeID = id ?? nativeID;
if (_accessibilityLabel !== undefined) {
processedProps.accessibilityLabel = _accessibilityLabel;
}
if (_accessibilityState !== undefined) {
processedProps.accessibilityState = _accessibilityState;
}
if (_nativeID !== undefined) {
processedProps.nativeID = _nativeID;
}
if (_numberOfLines !== undefined) {
processedProps.numberOfLines = _numberOfLines;
}
if (_selectable !== undefined) {
processedProps.selectable = _selectable;
}
if (_style !== undefined) {
processedProps.style = _style;
}
if (_selectionColor !== undefined) {
processedProps.selectionColor = _selectionColor;
}
let textPressabilityProps: ?TextPressabilityProps;
if (isPressable) {
textPressabilityProps = {
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
};
}
const hasTextAncestor = useContext(TextAncestorContext);
if (hasTextAncestor) {
processedProps.disabled = disabled;
processedProps.children = children;
if (isPressable) {
return (
<NativePressableVirtualText
ref={forwardedRef}
textProps={processedProps}
textPressabilityProps={textPressabilityProps ?? {}}
/>
);
}
return <NativeVirtualText {...processedProps} ref={forwardedRef} />;
}
let nativeText = null;
processedProps.accessible = _accessible;
processedProps.allowFontScaling = allowFontScaling !== false;
processedProps.disabled = _disabled;
processedProps.ellipsizeMode = ellipsizeMode ?? 'tail';
processedProps.children = children;
if (isPressable) {
nativeText = (
<NativePressableText
ref={forwardedRef}
textProps={processedProps}
textPressabilityProps={textPressabilityProps ?? {}}
/>
);
} else {
nativeText = <NativeText {...processedProps} ref={forwardedRef} />;
}
if (children == null) {
return nativeText;
}
// If the children do not contain a JSX element it would not be possible to have a
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
// which has a performance overhead. Since we do this for performance reasons we need
// to keep the check simple to avoid regressing overall perf. For this reason the
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
// to capture the majority of `Text` uses but also not make this check too expensive.
if (Array.isArray(children) && children.length <= 3) {
let hasNonTextChild = false;
for (let child of children) {
if (child != null && typeof child === 'object') {
hasNonTextChild = true;
break;
}
}
if (!hasNonTextChild) {
return nativeText;
}
} else if (typeof children !== 'object') {
return nativeText;
}
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
};
_TextImpl = TextImplNoDefaultProps;
} else {
const TextImplLegacy: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = ({
ref: forwardedRef,
accessible,
accessibilityLabel,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
children,
ellipsizeMode,
disabled,
id,
nativeID,
numberOfLines,
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
selectable,
selectionColor,
suppressHighlighting,
style,
...restProps
}: {
ref?: React.RefSetter<TextForwardRef>,
...TextProps,
}) => {
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
let _accessibilityState: ?TextProps['accessibilityState'] =
accessibilityState;
if (
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
if (_accessibilityState != null) {
_accessibilityState = {
busy: ariaBusy ?? _accessibilityState.busy,
checked: ariaChecked ?? _accessibilityState.checked,
disabled: ariaDisabled ?? _accessibilityState.disabled,
expanded: ariaExpanded ?? _accessibilityState.expanded,
selected: ariaSelected ?? _accessibilityState.selected,
};
} else {
_accessibilityState = {
busy: ariaBusy,
checked: ariaChecked,
disabled: ariaDisabled,
expanded: ariaExpanded,
selected: ariaSelected,
};
}
}
const _accessibilityStateDisabled = _accessibilityState?.disabled;
const _disabled = disabled ?? _accessibilityStateDisabled;
const isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
if (__DEV__) {
if (PressabilityDebug.isEnabled() && onPress != null) {
_style = [style, {color: 'magenta'}];
}
}
let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
if (__DEV__) {
console.error(
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
);
}
_numberOfLines = 0;
}
let _selectable = selectable;
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-cast]
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
}
if (processedStyle.userSelect != null) {
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
overrides = overrides || ({}: {...TextStyleInternal});
overrides.userSelect = undefined;
}
if (processedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
const _nativeID = id ?? nativeID;
const hasTextAncestor = useContext(TextAncestorContext);
if (hasTextAncestor) {
if (isPressable) {
return (
<NativePressableVirtualText
ref={forwardedRef}
textProps={{
...restProps,
accessibilityLabel: _accessibilityLabel,
accessibilityState: _accessibilityState,
nativeID: _nativeID,
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: _style,
disabled: disabled,
children,
}}
textPressabilityProps={{
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
}}
/>
);
}
return (
<NativeVirtualText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}
disabled={disabled}>
{children}
</NativeVirtualText>
);
}
_numberOfLines = 0;
}
let _selectable = selectable;
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-cast]
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
// falsy states we need to update the accessibilityState object to use the disabled prop.
if (
_disabled !== _accessibilityStateDisabled &&
((_disabled != null && _disabled !== false) ||
(_accessibilityStateDisabled != null &&
_accessibilityStateDisabled !== false))
) {
_accessibilityState = {..._accessibilityState, disabled: _disabled};
}
if (processedStyle.userSelect != null) {
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
overrides = overrides || ({}: {...TextStyleInternal});
overrides.userSelect = undefined;
}
const _accessible = Platform.select({
ios: accessible !== false,
android:
accessible == null
? onPress != null || onLongPress != null
: accessible,
default: accessible,
});
if (processedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
const _nativeID = id ?? nativeID;
const hasTextAncestor = useContext(TextAncestorContext);
if (hasTextAncestor) {
let nativeText = null;
if (isPressable) {
return (
<NativePressableVirtualText
nativeText = (
<NativePressableText
ref={forwardedRef}
textProps={{
...restProps,
accessibilityLabel: _accessibilityLabel,
accessibilityState: _accessibilityState,
accessible: _accessible,
allowFontScaling: allowFontScaling !== false,
disabled: _disabled,
ellipsizeMode: ellipsizeMode ?? 'tail',
nativeID: _nativeID,
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: _style,
disabled: disabled,
children,
}}
textPressabilityProps={{
@@ -203,127 +543,61 @@ const TextImpl: component(
}}
/>
);
} else {
nativeText = (
<NativeText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessible={_accessible}
allowFontScaling={allowFontScaling !== false}
disabled={_disabled}
ellipsizeMode={ellipsizeMode ?? 'tail'}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}>
{children}
</NativeText>
);
}
return (
<NativeVirtualText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}
disabled={disabled}>
{children}
</NativeVirtualText>
);
}
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
// falsy states we need to update the accessibilityState object to use the disabled prop.
if (
_disabled !== _accessibilityStateDisabled &&
((_disabled != null && _disabled !== false) ||
(_accessibilityStateDisabled != null &&
_accessibilityStateDisabled !== false))
) {
_accessibilityState = {..._accessibilityState, disabled: _disabled};
}
const _accessible = Platform.select({
ios: accessible !== false,
android:
accessible == null ? onPress != null || onLongPress != null : accessible,
default: accessible,
});
let nativeText = null;
if (isPressable) {
nativeText = (
<NativePressableText
ref={forwardedRef}
textProps={{
...restProps,
accessibilityLabel: _accessibilityLabel,
accessibilityState: _accessibilityState,
accessible: _accessible,
allowFontScaling: allowFontScaling !== false,
disabled: _disabled,
ellipsizeMode: ellipsizeMode ?? 'tail',
nativeID: _nativeID,
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: _style,
children,
}}
textPressabilityProps={{
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
}}
/>
);
} else {
nativeText = (
<NativeText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessible={_accessible}
allowFontScaling={allowFontScaling !== false}
disabled={_disabled}
ellipsizeMode={ellipsizeMode ?? 'tail'}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}>
{children}
</NativeText>
);
}
if (children == null) {
return nativeText;
}
// If the children do not contain a JSX element it would not be possible to have a
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
// which has a performance overhead. Since we do this for performance reasons we need
// to keep the check simple to avoid regressing overall perf. For this reason the
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
// to capture the majority of `Text` uses but also not make this check too expensive.
if (Array.isArray(children) && children.length <= 3) {
let hasNonTextChild = false;
for (let child of children) {
if (child != null && typeof child === 'object') {
hasNonTextChild = true;
break;
}
}
if (!hasNonTextChild) {
if (children == null) {
return nativeText;
}
} else if (typeof children !== 'object') {
return nativeText;
}
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
};
// If the children do not contain a JSX element it would not be possible to have a
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
// which has a performance overhead. Since we do this for performance reasons we need
// to keep the check simple to avoid regressing overall perf. For this reason the
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
// to capture the majority of `Text` uses but also not make this check too expensive.
if (Array.isArray(children) && children.length <= 3) {
let hasNonTextChild = false;
for (let child of children) {
if (child != null && typeof child === 'object') {
hasNonTextChild = true;
break;
}
}
if (!hasNonTextChild) {
return nativeText;
}
} else if (typeof children !== 'object') {
return nativeText;
}
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
};
_TextImpl = TextImplLegacy;
}
const TextImpl: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = _TextImpl;
TextImpl.displayName = 'Text';
@@ -6,6 +6,7 @@
*
* @flow strict-local
* @format
* @fantom_flags reduceDefaultPropsInText:*
*/
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
@@ -6,6 +6,7 @@
*
* @flow strict-local
* @format
* @fantom_flags reduceDefaultPropsInText:*
*/
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
@@ -128,7 +128,6 @@ const HMRClient: HMRClientNativeInterface = {
JSON.stringify({
type: 'log',
level,
mode: global.RN$Bridgeless === true ? 'NOBRIDGE' : 'BRIDGE',
data: data.map(item =>
typeof item === 'string'
? item
@@ -43,7 +43,7 @@ function TestComponent(
}
function id(instance: HostInstance | null): string | null {
// $FlowIgnore[prop-missing] - Intentional.
// $FlowFixMe[prop-missing] - Intentional.
return instance?.props?.id ?? null;
}
@@ -108,7 +108,7 @@ test('accepts a ref object', () => {
const ledger: Array<{[string]: string | null}> = [];
const ref = {
// $FlowIgnore[unsafe-getters-setters] - Intentional.
// $FlowFixMe[unsafe-getters-setters] - Intentional.
set current(current: HostInstance | null) {
ledger.push({ref: id(current)});
},
@@ -140,7 +140,7 @@ test('invokes refs in order', () => {
ledger.push({refA: id(current)});
};
const refB = {
// $FlowIgnore[unsafe-getters-setters] - Intentional.
// $FlowFixMe[unsafe-getters-setters] - Intentional.
set current(current: HostInstance | null) {
ledger.push({refB: id(current)});
},
@@ -149,7 +149,7 @@ test('invokes refs in order', () => {
ledger.push({refC: id(current)});
};
const refD = {
// $FlowIgnore[unsafe-getters-setters] - Intentional.
// $FlowFixMe[unsafe-getters-setters] - Intentional.
set current(current: HostInstance | null) {
ledger.push({refD: id(current)});
},
@@ -141,8 +141,7 @@ NSMutableArray<NSString *> *getModulesLoadedWithOldArch(void)
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
if (RCTAreLegacyLogsEnabled() && RCTIsNewArchEnabled() &&
![getCoreModuleClasses() containsObject:[moduleClass description]]) {
if (RCTAreLegacyLogsEnabled() && ![getCoreModuleClasses() containsObject:[moduleClass description]]) {
addModuleLoadedWithOldArch([moduleClass description]);
}
static dispatch_once_t onceToken;
@@ -183,7 +182,7 @@ NSString *RCTBridgeModuleNameForClass(Class cls)
return RCTDropReactPrefixes(name);
}
static BOOL turboModuleEnabled = NO;
static const BOOL turboModuleEnabled = YES;
BOOL RCTTurboModuleEnabled(void)
{
#if RCT_DEBUG
@@ -197,7 +196,7 @@ BOOL RCTTurboModuleEnabled(void)
void RCTEnableTurboModule(BOOL enabled)
{
turboModuleEnabled = enabled;
// The new Architecture is enabled by default and we are ignoring changes to the TurboModule system.
}
static BOOL turboModuleInteropEnabled = NO;
+1 -2
View File
@@ -43,8 +43,7 @@ UIDeviceOrientation RCTDeviceOrientation(void);
// Whether the New Architecture is enabled or not
BOOL RCTIsNewArchEnabled(void)
{
NSNumber *rctNewArchEnabled = (NSNumber *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTNewArchEnabled"];
return rctNewArchEnabled == nil || rctNewArchEnabled.boolValue;
return YES;
}
void RCTSetNewArchEnabled(BOOL enabled)
{
@@ -52,7 +52,7 @@ Pod::Spec.new do |s|
s.dependency "React-RCTImage", version
s.dependency "React-jsi", version
s.dependency 'React-RCTBlob'
add_dependency(s, "React-debug")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
@@ -96,6 +96,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
@interface RCTModalHostViewComponentView () <RCTFabricModalHostViewControllerDelegate>
@property (nonatomic, weak) UIView *accessibilityFocusedView;
@end
@implementation RCTModalHostViewComponentView {
@@ -148,6 +150,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
{
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
if (shouldBePresented) {
[self saveAccessibilityFocusedView];
self.viewController.presentationController.delegate = self;
_isPresented = YES;
@@ -179,6 +182,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
if (eventEmitter) {
eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{});
}
[self restoreAccessibilityFocusedView];
}];
}
}
@@ -207,6 +212,23 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
[self ensurePresentedOnlyIfNeeded];
}
- (void)saveAccessibilityFocusedView
{
id focusedElement = UIAccessibilityFocusedElement(nil);
if (focusedElement && [focusedElement isKindOfClass:[UIView class]]) {
self.accessibilityFocusedView = (UIView *)focusedElement;
}
}
- (void)restoreAccessibilityFocusedView
{
id viewToFocus = self.accessibilityFocusedView;
if (viewToFocus) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, viewToFocus);
self.accessibilityFocusedView = nil;
}
}
#pragma mark - RCTFabricModalHostViewControllerDelegate
- (void)boundsDidChange:(CGRect)newBounds
@@ -92,6 +92,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
add_dependency(s, "React-performancecdpmetrics", :framework_name => 'React_performancecdpmetrics')
add_dependency(s, "React-renderercss")
add_dependency(s, "React-RCTFBReactNativeSpec")
@@ -49,6 +49,7 @@ Pod::Spec.new do |s|
s.dependency "React-Core"
s.dependency "React-jsi"
add_dependency(s, "React-debug")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
@@ -1140,11 +1140,6 @@ public abstract class com/facebook/react/bridge/ReactContextBaseJavaModule : com
protected final fun getCurrentActivity ()Landroid/app/Activity;
}
public final class com/facebook/react/bridge/ReactCxxErrorHandler {
public static final field INSTANCE Lcom/facebook/react/bridge/ReactCxxErrorHandler;
public static final fun setHandleErrorFunc (Ljava/lang/Object;Ljava/lang/reflect/Method;)V
}
public final class com/facebook/react/bridge/ReactMarker {
public static final field INSTANCE Lcom/facebook/react/bridge/ReactMarker;
public static final fun addFabricListener (Lcom/facebook/react/bridge/ReactMarker$FabricMarkerListener;)V
@@ -1309,12 +1304,6 @@ public abstract interface annotation class com/facebook/react/bridge/ReactMethod
public abstract interface class com/facebook/react/bridge/ReactModuleWithSpec {
}
public final class com/facebook/react/bridge/ReactNoCrashBridgeNotAllowedSoftException : com/facebook/react/bridge/ReactNoCrashSoftException {
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
public fun <init> (Ljava/lang/Throwable;)V
}
public class com/facebook/react/bridge/ReactNoCrashSoftException : java/lang/RuntimeException {
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
@@ -3359,8 +3348,10 @@ public final class com/facebook/react/uimanager/DisplayMetricsHolder {
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics;
public static final fun getWindowDisplayMetrics ()Landroid/util/DisplayMetrics;
public static final fun initDisplayMetrics (Landroid/content/Context;)V
public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun initScreenDisplayMetrics (Landroid/content/Context;)V
public static final fun initScreenDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun initWindowDisplayMetrics (Landroid/content/Context;)V
public static final fun initWindowDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
}
@@ -630,6 +630,7 @@ dependencies {
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.androidx.window)
api(libs.fbjni)
api(libs.fresco)
@@ -21,7 +21,7 @@ public class MemoryPressureRouter(context: Context) : ComponentCallbacks2 {
context.applicationContext.registerComponentCallbacks(this)
}
public fun destroy(context: Context): Unit {
public fun destroy(context: Context) {
context.applicationContext.unregisterComponentCallbacks(this)
}
@@ -14,7 +14,7 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
/** Responsible for dispatching events specific for hardware inputs. */
internal class ReactAndroidHWInputDeviceHelper() {
internal class ReactAndroidHWInputDeviceHelper {
/**
* We keep a reference to the last focused view id so that we can send it as a target for key
* events and be able to send a blur event when focus changes.
@@ -161,7 +161,7 @@ public open class ReactFragment : Fragment(), PermissionAwareActivity {
permissions: Array<String>,
requestCode: Int,
listener: PermissionListener?
): Unit {
) {
permissionListener = listener
requestPermissions(permissions, requestCode)
}
@@ -229,6 +229,9 @@ public class ReactInstanceManager {
return new ReactInstanceManagerBuilder();
}
/**
* @noinspection deprecation
*/
/* package */ ReactInstanceManager(
Context applicationContext,
@Nullable Activity currentActivity,
@@ -259,7 +262,11 @@ public class ReactInstanceManager {
FLog.d(TAG, "ReactInstanceManager.ctor()");
initializeSoLoaderIfNecessary(applicationContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(applicationContext);
if (currentActivity != null) {
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
}
// See {@code ReactInstanceManagerBuilder} for description of all flags here.
mApplicationContext = applicationContext;
@@ -924,6 +931,13 @@ public class ReactInstanceManager {
ReactContext currentReactContext = getCurrentReactContext();
if (currentReactContext != null) {
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext);
Activity currentActivity = currentReactContext.getCurrentActivity();
if (currentActivity != null) {
DisplayMetricsHolder.initWindowDisplayMetrics(currentActivity);
}
AppearanceModule appearanceModule =
currentReactContext.getNativeModule(AppearanceModule.class);
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react
import android.app.Activity
@@ -35,7 +35,8 @@ import java.util.List;
*
* @deprecated This class will be replaced by com.facebook.react.ReactHost in the New Architecture.
*/
@Deprecated
@Deprecated(
since = "This class is part of Legacy Architecture and will be removed in a future release")
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Nullsafe(Nullsafe.Mode.LOCAL)
public abstract class ReactNativeHost {
@@ -14,7 +14,7 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
@Deprecated("This class is deprecated and will be removed in the next major release.")
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal interface ReactPackageLogger {
fun startProcessPackage(): Unit
fun startProcessPackage()
fun endProcessPackage(): Unit
fun endProcessPackage()
}
@@ -136,9 +136,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
setClipChildren(false);
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
}
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
}
@Override
@@ -205,10 +204,15 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
return;
}
@Nullable ReactContext reactContext = getCurrentReactContext();
if (reactContext == null) {
return;
}
EventDispatcher eventDispatcher =
UIManagerHelper.getEventDispatcher(getCurrentReactContext(), getUIManagerType());
UIManagerHelper.getEventDispatcher(reactContext, getUIManagerType());
if (eventDispatcher != null) {
mJSTouchDispatcher.onChildStartedNativeGesture(ev, eventDispatcher);
mJSTouchDispatcher.onChildStartedNativeGesture(ev, eventDispatcher, reactContext);
if (childView != null && mJSPointerDispatcher != null) {
mJSPointerDispatcher.onChildStartedNativeGesture(childView, ev, eventDispatcher);
}
@@ -878,7 +882,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private int mDeviceRotation = 0;
/* package */ CustomGlobalLayoutListener() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(getContext());
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(getContext());
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
@@ -1001,7 +1006,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
return;
}
mDeviceRotation = rotation;
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
emitOrientationChanged(rotation);
}
@@ -27,7 +27,7 @@ public abstract class AnimatedNode {
@JvmField internal var BFSColor: Int = INITIAL_BFS_COLOR
@JvmField internal var tag: Int = -1
internal fun addChild(child: AnimatedNode): Unit {
internal fun addChild(child: AnimatedNode) {
val currentChildren =
children
?: ArrayList<AnimatedNode>(DEFAULT_ANIMATED_NODE_CHILD_COUNT).also { children = it }
@@ -28,7 +28,7 @@ internal class DecayAnimation(config: ReadableMap) : AnimationDriver() {
resetConfig(config)
}
override fun resetConfig(config: ReadableMap): Unit {
override fun resetConfig(config: ReadableMap) {
velocity = config.getDouble("velocity")
deceleration = config.getDouble("deceleration")
startFrameTimeMillis = -1
@@ -261,7 +261,7 @@ internal class InterpolationAnimatedNode(config: ReadableMap) : ValueAnimatedNod
}
private fun findRangeIndex(value: Double, ranges: DoubleArray): Int {
var index: Int = 1
var index = 1
while (index < ranges.size - 1) {
if (ranges[index] >= value) {
break
@@ -219,7 +219,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
*
* @param viewTag The tag of the scroll view that has stopped scrolling
*/
public fun userDrivenScrollEnded(viewTag: Int): Unit {
public fun userDrivenScrollEnded(viewTag: Int) {
// ask to the Node Manager for all the native nodes listening to OnScroll event
val nodeManager = nodesManagerRef.get() ?: return
@@ -72,7 +72,7 @@ public class NativeAnimatedNodesManager(
*
* @param uiManagerType
*/
public fun initializeEventListenerForUIManagerType(@UIManagerType uiManagerType: Int): Unit {
public fun initializeEventListenerForUIManagerType(@UIManagerType uiManagerType: Int) {
val isEventListenerInitialized =
when (uiManagerType) {
UIManagerType.FABRIC -> eventListenerInitializedForFabric
@@ -100,7 +100,7 @@ public class NativeAnimatedNodesManager(
public fun hasActiveAnimations(): Boolean = activeAnimations.size() > 0 || updatedNodes.size() > 0
@UiThread
public fun createAnimatedNode(tag: Int, config: ReadableMap): Unit {
public fun createAnimatedNode(tag: Int, config: ReadableMap) {
if (animatedNodes.get(tag) != null) {
throw JSApplicationIllegalArgumentException(
"createAnimatedNode: Animated node [$tag] already exists")
@@ -129,7 +129,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun updateAnimatedNodeConfig(tag: Int, config: ReadableMap?): Unit {
public fun updateAnimatedNodeConfig(tag: Int, config: ReadableMap?) {
val node =
animatedNodes.get(tag)
?: throw JSApplicationIllegalArgumentException(
@@ -143,16 +143,13 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun dropAnimatedNode(tag: Int): Unit {
public fun dropAnimatedNode(tag: Int) {
animatedNodes.remove(tag)
updatedNodes.remove(tag)
}
@UiThread
public fun startListeningToAnimatedNodeValue(
tag: Int,
listener: AnimatedNodeValueListener?
): Unit {
public fun startListeningToAnimatedNodeValue(tag: Int, listener: AnimatedNodeValueListener?) {
val node = animatedNodes[tag]
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -162,7 +159,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun stopListeningToAnimatedNodeValue(tag: Int): Unit {
public fun stopListeningToAnimatedNodeValue(tag: Int) {
val node = animatedNodes.get(tag)
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -172,7 +169,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun setAnimatedNodeValue(tag: Int, value: Double): Unit {
public fun setAnimatedNodeValue(tag: Int, value: Double) {
val node = animatedNodes.get(tag)
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -184,7 +181,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun setAnimatedNodeOffset(tag: Int, offset: Double): Unit {
public fun setAnimatedNodeOffset(tag: Int, offset: Double) {
val node = animatedNodes.get(tag)
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -195,7 +192,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun flattenAnimatedNodeOffset(tag: Int): Unit {
public fun flattenAnimatedNodeOffset(tag: Int) {
val node = animatedNodes.get(tag)
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -205,7 +202,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun extractAnimatedNodeOffset(tag: Int): Unit {
public fun extractAnimatedNodeOffset(tag: Int) {
val node = animatedNodes.get(tag)
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -220,7 +217,7 @@ public class NativeAnimatedNodesManager(
animatedNodeTag: Int,
animationConfig: ReadableMap,
endCallback: Callback?
): Unit {
) {
val node =
animatedNodes.get(animatedNodeTag)
?: throw JSApplicationIllegalArgumentException(
@@ -298,7 +295,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun stopAnimation(animationId: Int): Unit {
public fun stopAnimation(animationId: Int) {
// in most of the cases there should never be more than a few active animations running at the
// same time. Therefore it does not make much sense to create an animationId -> animation
// object map that would require additional memory just to support the use-case of stopping
@@ -342,7 +339,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun connectAnimatedNodes(parentNodeTag: Int, childNodeTag: Int): Unit {
public fun connectAnimatedNodes(parentNodeTag: Int, childNodeTag: Int) {
val parentNode =
animatedNodes.get(parentNodeTag)
?: throw JSApplicationIllegalArgumentException(
@@ -355,7 +352,7 @@ public class NativeAnimatedNodesManager(
updatedNodes.put(childNodeTag, childNode)
}
public fun disconnectAnimatedNodes(parentNodeTag: Int, childNodeTag: Int): Unit {
public fun disconnectAnimatedNodes(parentNodeTag: Int, childNodeTag: Int) {
val parentNode =
animatedNodes.get(parentNodeTag)
?: throw JSApplicationIllegalArgumentException(
@@ -369,7 +366,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun connectAnimatedNodeToView(animatedNodeTag: Int, viewTag: Int): Unit {
public fun connectAnimatedNodeToView(animatedNodeTag: Int, viewTag: Int) {
val node =
animatedNodes.get(animatedNodeTag)
?: throw JSApplicationIllegalArgumentException(
@@ -396,7 +393,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun disconnectAnimatedNodeFromView(animatedNodeTag: Int, viewTag: Int): Unit {
public fun disconnectAnimatedNodeFromView(animatedNodeTag: Int, viewTag: Int) {
val node =
animatedNodes.get(animatedNodeTag)
?: throw JSApplicationIllegalArgumentException(
@@ -409,7 +406,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun getValue(tag: Int, callback: Callback?): Unit {
public fun getValue(tag: Int, callback: Callback?) {
val node = animatedNodes.get(tag)
if (node == null || node !is ValueAnimatedNode) {
throw JSApplicationIllegalArgumentException(
@@ -436,7 +433,7 @@ public class NativeAnimatedNodesManager(
}
@UiThread
public fun restoreDefaultValues(animatedNodeTag: Int): Unit {
public fun restoreDefaultValues(animatedNodeTag: Int) {
val node = animatedNodes.get(animatedNodeTag) ?: return
// Restoring default values needs to happen before UIManager operations so it is
// possible the node hasn't been created yet if it is being connected and
@@ -454,7 +451,7 @@ public class NativeAnimatedNodesManager(
viewTag: Int,
eventHandlerName: String,
eventMapping: ReadableMap
): Unit {
) {
val nodeTag = eventMapping.getInt("animatedValueTag")
val node =
animatedNodes.get(nodeTag)
@@ -487,7 +484,7 @@ public class NativeAnimatedNodesManager(
viewTag: Int,
eventHandlerName: String,
animatedValueTag: Int
): Unit {
) {
val eventName = normalizeEventName(eventHandlerName)
eventDrivers
@@ -550,7 +547,7 @@ public class NativeAnimatedNodesManager(
* have already been visited.
*/
@UiThread
public fun runUpdates(frameTimeNanos: Long): Unit {
public fun runUpdates(frameTimeNanos: Long) {
UiThreadUtil.assertOnUiThread()
var hasFinishedAnimations = false
@@ -27,21 +27,21 @@ internal open class ValueAnimatedNode(config: ReadableMap? = null) : AnimatedNod
open fun getAnimatedObject(): Any? = null
fun flattenOffset(): Unit {
fun flattenOffset() {
nodeValue += offset
offset = 0.0
}
fun extractOffset(): Unit {
fun extractOffset() {
offset += nodeValue
nodeValue = 0.0
}
fun onValueUpdate(): Unit {
fun onValueUpdate() {
valueListener?.onValueUpdate(getValue() - offset, offset)
}
fun setValueListener(listener: AnimatedNodeValueListener?): Unit {
fun setValueListener(listener: AnimatedNodeValueListener?) {
valueListener = listener
}

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