Compare commits

..
Author SHA1 Message Date
Rob Hogan a19fd24132 Scripts: remove checked-in debugger; statements
Summary:
These snuck in, presumably accidentally, via https://github.com/facebook/react-native/pull/49164.

This interferes with running test suites with a debugger connected (e.g, when debugging Jest itself).

(Aside: we should probably enable [`eslint/no-debugger`](https://eslint.org/docs/latest/rules/no-debugger) to catch these)

Changelog: [Internal]

Differential Revision: D69377992
2025-02-09 16:47:39 -08:00
David Vacca b45a3e5cd8 Introduce new BuildConfig to determine if the new architecture is fully enabled into an Android app (#49283)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49283

In this diff I'm introducing a new BuildConfig called UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE to determine if the new architecture is fully enabled into an Android app at build time, fully enabled means:
- no interop
- all view managers migrated to new API
- all native modules migrated to new API
- legacy architecture can be stripped

This BuildConfig is different from ReactNativeFeatureFlags.enableBridgelessArchitecture() because the latter is controlled at runtime, BuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE is used at the build system level and it can be accessed from proguard to optimize code that's unused when the app is fully running in the new architecture. Additionally we will use the BuildConfig to assert that some classes and methods are not loaded or executed.

changelog: [Android][Changed] Introduces BuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE to determine if the new architecture is fully enabled into an Android app

Reviewed By: cortinico

Differential Revision: D69206248

fbshipit-source-id: f60a059be8333d3051eb7d2efac79939a479f6f8
2025-02-09 02:17:38 -08:00
Nick Gerleman 7b7c45030b Filter function parsing (#49281)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49281

Adds `CSSFilterFunction`, decomposing to the various filter types, alongside `CSSFilterList`.

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D69212763

fbshipit-source-id: 8aade5ef4725aaad2548b6ef30d8aa1298803cd5
2025-02-07 18:14:42 -08:00
Nick Gerleman 8bd01c7d01 Fix incorrect tokenization of non-exponential numbers ending with "E" (#49280)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49280

We were incorrectly consuming an `E` at the end of number tokens, even if not followed by a digit, which breaks dimension tokens where the unit starts with "E", like `em`. Follow the spec the right way:

https://www.w3.org/TR/css-syntax-3/#consume-number

> If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) or U+0065 LATIN SMALL LETTER E (e), optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+), followed by a digit, then...

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D69330975

fbshipit-source-id: a9bd5bceac9efbf02c1b7fb60659093774bb7228
2025-02-07 18:14:42 -08:00
Nick Gerleman f40d69f06d Reduce transform parsing duplication (#49279)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49279

I ended up using this same pattern for filter parsing where the logic betweeen functions is very similar. Let's deduplicate the logic for transform parsing a bit. This also separates `rotate()` and `rotateZ()` types, to be handled the same at a different layer.

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D69326443

fbshipit-source-id: 9bf910c6d4e07748ff032433167576f9d58cd8d6
2025-02-07 18:14:42 -08:00
Sam Zhou 722f5ba786 Remove global React$ type references (#49276)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49276

This diff replaces the remaining `React$` global types in the codebase, in preparation for their removal in Flow.

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D69322418

fbshipit-source-id: 058a2489ce8e6bf59df2ec4e61e9708f63561671
2025-02-07 18:01:43 -08:00
Thomas Nardone 94b5d4b53f Flip SurfaceMountingManager null view state SoftException to not crash (#49271)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49271

This error is somewhat expected, so causing the red box error popup is a bit too disruptive.  Flip it to a no-crash exception.

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D69125274

fbshipit-source-id: 0dc7ac59ac8637bdabde25bd8886b1aebf175395
2025-02-07 15:10:36 -08:00
Peter Abbondanzo 58e163c74e Replace magic number for unset child view id (#49277)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49277

Replaces the `-1` magic number representing unset children with a named constant

Changelog: [Internal]

Reviewed By: zeyap

Differential Revision: D69324509

fbshipit-source-id: 64fb6c920a7715f5d15d3955564a8bf2b6ce404a
2025-02-07 15:10:23 -08:00
David Vacca 847f8902ff Update non-codegen ViewManagerInterfaces to extend ViewManagerWithGeneratedInterface (#49274)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49274

In this diff I'm updating all the non-codegen ViewManagerInterfaces to extend ViewManagerWithGeneratedInterface to make it consistent with codenerated ViewManagerInterfaces

changelog: [internal] internal

Reviewed By: javache

Differential Revision: D69206247

fbshipit-source-id: 6a577d9ee7410be990a03e78847333b61b429e88
2025-02-07 14:28:12 -08:00
David Vacca 6e0e72df71 Update documentation for OSSReleaseStageValue
Summary:
Update documentation for OSSReleaseStageValue

changelog: [internal] internal

Reviewed By: cortinico, alanleedev

Differential Revision: D69268799

fbshipit-source-id: 9f2fa5fc8363c5afd159f9a785d9b613548d8734
2025-02-07 13:46:51 -08:00
Thomas Nardone 83fd1742da Lazily create args in ResponseUtil (#49270)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49270

Changelog: [Internal]

Reviewed By: Abbondanzo

Differential Revision: D69316855

fbshipit-source-id: 1c7f58cb9364540c5bf7309b6c2f81162efcc178
2025-02-07 12:39:11 -08:00
Rob Hogan fc15260f1c Update monorepo Jest to 29.7.0 (#49261)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49261

Update the version of Jest used in React Native and Metro's own tests from `^29.6.3` to `^29.7.0`

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D69307514

fbshipit-source-id: 686935ed4ba1334d445217fd2f8a303b774b6c4a
2025-02-07 11:27:18 -08:00
Samuel Susla 14540e6abf refactor Point.h and add unit tests (#49249)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49249

changelog: [internal]

- Make all Point methods inline as per [C++ core guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f5-if-a-function-is-very-small-and-time-critical-declare-it-inline).
- Add unit tests for Point.
- Use default == and != operator.

Reviewed By: javache

Differential Revision: D69239807

fbshipit-source-id: c5926d587a7888f54895cb7b1a62a23dc26242a3
2025-02-07 09:37:55 -08:00
Samuel Susla 947d9c3897 introduce Fabric View Culling (#49198)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49198

changelog: [internal]

The work done on the main thread should scale with what is on the screen. React Native shouldn’t block the main thread for off screen elements that do not affect what is shown to the end user. When React schedules a commit, only views needed to achieve a screen full of content should be materialised and added to the host platform’s view hierarchy.

With Fabric View Culling, views that do not contribute pixels to the screen will not materialize and updates to them will be skipped. React Native will focus system resources on what is visible to the end user.

Fabric View Culling maximises benefits from view recycling. Each UI element such as text, image, or video is recycled individually. As soon as an item goes off screen, it can be reused anywhere in the UI and pieced together with other items to create new UI elements. Such recycling reduces the need of having multiple view types and improves memory usage and scroll performance.

In the example bellow, view B will not be mounted because the user can't see it.
 {F1974949953}

The difference in number of allocated views:
Please note, the screenshots below are from Xcode View Hierarchy debugger. To show how many views are allocated in memory, I disabled [removeClippedSubviews](https://reactnative.dev/docs/scrollview#removeclippedsubviews) flag globally.
|Before|After:
| {F1974949979}| {F1974949981}

# Disclaimer, this is not a complete implementation
This implementation is not complete and it is missing to handle edge cases.
Things that are missing:
- Transform style is not taken into account.
- removeClippedSubviews is not respected. Fabric View Culling happens unconditionally for every scroll view.
- Fabric View Culling does not respect when ScrollView has overflow set to visible.
- Fabric View Culling is only performant enough on iOS.
- [enableSynchronousStateUpdates](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js#L248) must be enabled for Fabric View Culling to work correctly.

Reviewed By: javache

Differential Revision: D63458372

fbshipit-source-id: c93ec434081f2be8a446212e2c0681f8ae4e90f9
2025-02-07 09:37:55 -08:00
Samuel Susla 68a17f2651 add getOverflowInsetFrame to LayoutMetrics (#49197)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49197

changelog: [internal]

Adds new method to LayoutMetrics that calculates frame adjusted for overflow inset.

For example, for the following view hierarchy. it would produce a frame that would fully contain view A and view B.
```
┌─────────────┐
│<View A />   │
│     ┌───────┴─────┐
└─────┤<View B />   │
      │             │
      └─────────────┘
```

See tests for more details

Reviewed By: javache, lenaic

Differential Revision: D68775683

fbshipit-source-id: b8f7c42cfca7dba8dcae75cae5e6944bd1082957
2025-02-07 09:37:55 -08:00
Samuel Susla 4fa2064905 introduce feature flag for View Culling (#49196)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49196

changelog: [internal]

introduces feature flag for Fabric View Culling. Not used anywhere yet.

Reviewed By: christophpurrer

Differential Revision: D68775684

fbshipit-source-id: 612362dd142cea48f38e00e65e942bcfd8580da2
2025-02-07 09:37:55 -08:00
Alex Hunt f96f1a6e11 Add signedsource and generated header to build-types (#49259)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49259

Also exclude `types_generated/` under ESLint + Prettier — paired with the removed `format` annotation.

Changelog: [Internal]

Reviewed By: j-piasecki

Differential Revision: D69306215

fbshipit-source-id: f2bfbeb3ce691ebf86b63fc498ae3847873c83ee
2025-02-07 08:40:09 -08:00
Alex Hunt 0d7379b9fe Tree-shake non-type imports (#49258)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49258

Updates dependency resolution in `yarn build-types` to happen after the `translateFlowToFlowDef` step. This means that we prune all non-type imports, massively reducing the input files of the program when building types only.

Changelog: [Internal]

Reviewed By: j-piasecki

Differential Revision: D69302812

fbshipit-source-id: aa80bea17cb584b747cb31c003e87fe00afd1e16
2025-02-07 08:40:09 -08:00
Alex Hunt a7a513fc96 Add module resolution to build-types (#49257)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49257

Adds minimal dependency resolution to `yarn build-types`.

- This enables us to opt in React Native APIs by entry point, with the build script resolving all necessary dependencies. Improves correctness and removes concern of globbing paths manually.

Other notes:

- The `ActionSheetIOS.js` entry point is temporarily disabled as input; needs further work.

Changelog: [Internal]

Reviewed By: j-piasecki

Differential Revision: D69255015

fbshipit-source-id: 2d99c014b50e41e4695549f46ca874a2b546f545
2025-02-07 08:40:09 -08:00
Tim Yung 5635d5c0a3 RN: Avoid Rejections in InteractionManagerStub (#49241)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49241

In auditing differences between `InteractionManager` and `InteractionManagerStub` (used to evaluate to entirely remove the former all together), I noticed a behavioral disparity with how errors are handled.

In `InteractionManager`, the promise that's returned is never rejected, whereas `InteractionManagerStub` propagates errors by rejecting the promise that's returned. This changes `InteractionManagerStub` to behave like `InteractionManager` for the purpose of comparing apples-to-apples.

Changelog:
[Internal]

Reviewed By: javache

Differential Revision: D69275495

fbshipit-source-id: 05439a0cadc1f76b34a3f1457f7db31d6bda2a90
2025-02-07 08:18:42 -08:00
Rob Hogan 57c291bbc4 Fantom: Use hierarchical resolution within node_modules in Metro config
Summary:
Fantom was disabling Metro hierarchical lookup in all cases when `JS_DIR` is set. The intention is that `node_modules` folders other than the configured `JS_DIR/public/node_modules` are not used.

However, this leads to incorrect resolution where a transitive dependency is not hoisted. If the origin of the resolution is already inside `node_modules`, we must perform a hierarchical lookup to avoid picking up just whichever version happens to be hoisted to `node_modules` root.

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D69303559

fbshipit-source-id: 12068fb0bebb8c2f81b64c23b952a623cb6fd792
2025-02-07 07:52:43 -08:00
Rubén Norte 10e47e69aa ] Add Fantom test placeholder for LogBox (#49252)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49252

Changelog: [internal]

This adds a Fantom test placeholder for LogBox, which shows what type of tests we could be writing for this.

It also adds a few ids in LogBox components so we can inspect them in tests and make assertions on them.

Reviewed By: javache

Differential Revision: D69301572

fbshipit-source-id: 89a332a47c300c1dc18937cd91206ce6d820b6aa
2025-02-07 07:47:32 -08:00
Rubén Norte b006080949 Expose rootTag in Root (#49251)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49251

Changelog: [internal]

Components like `AppContainer` require passing the rootTag as a prop, but we don't have access to it from Fantom unless we render something in the root and access it via the RootTagContext. This exposes the rootTag of the Root as a method so we can use it in initial render too.

Reviewed By: javache

Differential Revision: D69301571

fbshipit-source-id: 429fb56d937d3dffeb3c17a70d136ba4925ece8e
2025-02-07 07:47:32 -08:00
Rubén Norte 9a1dadf799 Introduce Fantom.dispatchNativeEvent utility (#49254)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49254

Changelog: [internal]

This is just a convenience method to do:

```
Fantom.dispatchNativeEvent(node, 'click');
```

Instead of:

```
runOnUIThread(() => {
  enqueueNativeEvent(node, 'click');
});

runWorkLoop();
```

Which is too verbose and people rarely need this level of granularity in tests.

Note that, in Fabric, we have methods called `dispatchEvent` that don't match 1:1 with this API. In that case, `dispatchEvent` is more aligned with Fantom's `enqueueNativeEvent`.

Reviewed By: javache

Differential Revision: D69302382

fbshipit-source-id: 6f71a5ace11c81f551df2c2837881dbc6f48e7ba
2025-02-07 07:47:32 -08:00
Rubén Norte 03c7316ab0 Rename dispatchNativeEvent as enqueueNativeEvent (#49253)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49253

Changelog: [internal]

Renaming this low level method as `enqueue` is more accurate in this case than `dispatch`, which is misleading because it actually doesn't dispatch it to JS.

We should also rename this in Fabric, but that's a larger and breaking change, so just making the change in Fantom for now. This is a trade-off between convenience/ergonomics of the testing API vs. alignment with the internal nomenclature. In this case we favor the first.

Reviewed By: javache

Differential Revision: D69302383

fbshipit-source-id: 7e163920ace709503367bf68baab5e9f2bf8ae3f
2025-02-07 07:47:32 -08:00
Dawid Małecki 09740c9001 Add Share path to build-types script and align Flow with TS types (#49167)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49167

Changelog:
[Internal]

Reviewed By: huntie

Differential Revision: D69119294

fbshipit-source-id: 5d7a10fee47da8d8ffa07f8481060d0176618ce9
2025-02-07 04:56:26 -08:00
Mateo Guzmán 2e7c84ba00 Make MultiPostprocessor internal (#49237)
Summary:
As part of the initiative to reduce the public API surface, this class can be internalized. I've checked there are [no relevant OSS usages](https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+com.facebook.react.views.image.MultiPostprocessor).

## Changelog:

[INTERNAL] - Make com.facebook.react.views.image.MultiPostprocessor internal

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

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

Reviewed By: cipolleschi

Differential Revision: D69293117

Pulled By: cortinico

fbshipit-source-id: 7e2713520a5a4fe492fb9d25cdd92c46746ded40
2025-02-07 04:01:37 -08:00
Nicola Corti 5ab6e7ad3f Remove unnecessary public modifiers from ComponentNameResolver (#49231)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49231

Those public modifier have no meaning as the class is internal. I'm removing them.

Changelog:
[Internal] [Changed] -

Reviewed By: javache

Differential Revision: D69252048

fbshipit-source-id: b51e5ac20338a01291d6cd04ee4c990cc8a6a755
2025-02-07 02:39:12 -08:00
Pieter De Baets f25e35ae4a Revert visibility change of ReactCookieJarContainer (#49247)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49247

This was incorrectly made internal in https://www.internalfb.com/diff/D66724567

Changelog: [Android][Removed] Made ReactCookieJarContainer internal.

Reviewed By: cortinico, andrewdacenko

Differential Revision: D69254203

fbshipit-source-id: 5c4ba9b4f9a8e53002df25b55f0c8762874e6736
2025-02-07 02:39:01 -08:00
Nicola Corti 2f784ce9a5 Undo a breaking change on ReactOverflowView (#49229)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49229

This interface was converted to Kotlin, but the single method should have been converted to a `val`.
People kotlin consumers could call ReactOverflowView.overflow; now they need to call getOverflow().

Changelog:
[Internal] [Changed] - Undo a breaking change on ReactOverflowView

Reviewed By: NickGerleman

Differential Revision: D69250226

fbshipit-source-id: 5c7cca8c83f5c76a9cc1d254f8aa51409150c356
2025-02-07 02:10:38 -08:00
Nicola Corti 5a4962a0c1 Undo breaking change in ReactPointerEventsView due to Kotlin conversion (#49233)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49233

I'm converting the function inside ReactPointerEventsView from `fun` to `val`.
This Kotlin conversion resulted in a breakign change for Kotlin consumer which I believe can be prevented
if we do this change instead.

Changelog:
[Internal] [Changed] -

Reviewed By: alanleedev

Differential Revision: D69252562

fbshipit-source-id: b277c6720f3156ed532bf5f2253d54cd72e38050
2025-02-07 02:07:24 -08:00
Janic Duplessis 2aed264695 Fix exclude .d.ts test in GenerateCodegenSchemaTaskTest (#49238)
Summary:
I wanted to test exclusion of .d.ts files in https://github.com/facebook/react-native/pull/49227, but it also has node_modules so it will not test that condition correctly.

## Changelog:

[INTERNAL] [FIXED] - Fix exclude .d.ts test in GenerateCodegenSchemaTaskTest

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

Test Plan: Run tests

Reviewed By: cipolleschi

Differential Revision: D69291695

Pulled By: cortinico

fbshipit-source-id: 46b9367f3466b9cd49232a0565e5778a06b43990
2025-02-07 01:55:34 -08:00
Nick Gerleman b34e63539d Disallow invalid unitless lengths in filters (#49242)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49242

Same bug as in D68740553, copy/pasted. Unitless numbers are not valid <length> apart from `0`.

Changelog:
[General][Breaking] - Disallow invalid unitless lengths in filters

Reviewed By: javache

Differential Revision: D69210768

fbshipit-source-id: c20a3aa1e9dbc84f636235a70c58e4d96dbe86b9
2025-02-06 21:02:24 -08:00
Nick Gerleman e2a776f322 transformOrigin Parsing (#49216)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49216

Should be able to replace `processTransformOrigin`.

As part of this, I discovered `processTransformOrigin` has a bug where it does not correctly support `center left` or `center right` syntax since it assumes first occurrence of `center` is for the horizontal

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D69204030

fbshipit-source-id: 8001ef4f0b54fcbe93855920260e077b89669f6d
2025-02-06 21:02:24 -08:00
Nick Gerleman 36adaf4c0b Transform parsing (#49189)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49189

Allow parsing the set of currently supported transform functions, and lists of them, using `CSSTranformFunction` (which may decompose to e.g. `CSSScaleX`), and `CSSTransformList`.

A bit more duplication than I would like here, but a lot of these have subtle differences.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D69153280

fbshipit-source-id: ef8e93c8a49a7f1b98bd7c57614aa1c84417120d
2025-02-06 21:02:24 -08:00
Peter Abbondanzo 99212cf6f3 Backout of "[xplat/js][RN][metro][socialvr] Update monorepo Jest to 29.7.0" (#49239)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49239

Changelog: [Internal]

Reverts https://github.com/facebook/react-native/pull/49213 which is causing some internal test failures

Reviewed By: makovkastar

Differential Revision: D69266554

fbshipit-source-id: bc70286c049b90813ee7ff641adabdf98ea890e0
2025-02-06 19:20:10 -08:00
Riccardo Cipolleschi 5d4f9467d9 Add changelog for 0.75.5 (#49234)
Summary:
Add changelog for 0.75.5

## Changelog:
[Internal] - Add changelog for 0.75.5

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

Test Plan: N/A

Reviewed By: cortinico

Differential Revision: D69256092

Pulled By: cipolleschi

fbshipit-source-id: 946d9edabe1e808d2a916892ab1a978bd7f5855f
2025-02-06 14:40:18 -08:00
Nicola Corti 442a368af5 Remove unnecessary public modifiers from FrescoBasedReactTextInlineImageViewManager (#49230)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49230

Those public modifier have no meaning as the class is internal. I'm removing them.

Changelog:
[Internal] [Changed] -

Reviewed By: tdn120

Differential Revision: D69251378

fbshipit-source-id: 4c3747510d18330dcdb8a0798e92736c4ab65a03
2025-02-06 13:32:53 -08:00
shubhamguptadream11 ea876054cf feat: added new workflow for issue monitoring in react native (#49225)
Summary:
While triaging issues in the React Native repository, we face two major challenges:

- Missing Issues: The large volume of issues makes it difficult to ensure that none are overlooked.
- Ownership: There is no structured process to determine who should handle which issue.

To address these challenges, we are setting up this action.

## Changelog:

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

Pick one each for the category and type tags:

[GENERAL] [ADDED] - Added a new workflow to monitor new issue in repo.

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

Here we are using this github action: https://github.com/react-native-community/repo-monitor to monitor new issues and then notify it on specific discord server to notify someone.

Currently this action runs every 6 hours.

Requirements:
- We need following inputs to make this workflow run:
   -  `DISCORD_WEBHOOK_URL` to be added in secrets [Needed for posting message in specific channels]
   -  `role_id`: To notify a group.

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

Test Plan: NA

Reviewed By: cipolleschi

Differential Revision: D69254130

Pulled By: cortinico

fbshipit-source-id: 43a57f8f3bf161042a9432d02f292896ea8f7622
2025-02-06 13:15:29 -08:00
Janic Duplessis e9e0d8c2f7 Improve input files for codegen gradle task (#49227)
Summary:
In some projects we have conventions of using .tsx extension even for files without react components, we had issues where codegen wasn't updated properly.

I debugged the files included in a large project and made some improvements:

- Include tsx and jsx files
- exclude nested node_modules
- exclude ts type def files

## Changelog:

[ANDROID] [FIXED] - Improve input files for codegen gradle task

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

Test Plan: Tested in a large app using codegen. I inspected the files that are included in the task inputs and made sure it works with first party and 3rd party modules.

Reviewed By: cipolleschi

Differential Revision: D69254204

Pulled By: cortinico

fbshipit-source-id: 368408e9719e9b5c9839dd873430b86ae4a062c7
2025-02-06 11:52:25 -08:00
Nicola Corti a5d9044158 Fix minor typo in 0.78 changelog (#49232)
Summary:
Just a minor typo in the changelog for 0.78

## Changelog:

[Internal] [Changed] -

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

Test Plan: ¯\\_(ツ)_/¯

Reviewed By: cipolleschi

Differential Revision: D69252846

Pulled By: cortinico

fbshipit-source-id: 97010601482199c87d7a9da06e5e32a4bfa8a552
2025-02-06 11:40:10 -08:00
zhongwuzw 1e9ac296a5 Added custom load js block in bridge mode (#48845)
Summary:
`loadSourceForBridge` is broken after we refactor the appdelegate. So let's add it back.

## Changelog:

[IOS] [FIXED] - Added custom load js block in bridge mode

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

Test Plan: Custom Appdelegate's `loadSourceForBridge` can be called in bridge mode.

Reviewed By: robhogan

Differential Revision: D68832046

Pulled By: cipolleschi

fbshipit-source-id: dcea791e6d8243fdb2f45a33af175aee1a4e1223
2025-02-06 08:55:33 -08:00
Alex Hunt b54efb8d0d Update multi-platform handling in build-types, add debug logs (#49224)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49224

Refactor / quality pass.

- Remove `micromatch`, replace with glob ignores.
- Move and simplify platfom-specific file logic: mutate `files` as a single `Set`, reduce iterations.
    - This is reconfigured so that the input file path globs need only match `*.js` sources.
- Introduce `debug` logs and expose convenience `--debug` script flag.
- Move output error detection into inner function implementation.

Changelog: [Internal]

Metro changelog: Internal

Reviewed By: j-piasecki

Differential Revision: D69240543

fbshipit-source-id: c2faef8212a2995936362b3d33d189c405bd879d
2025-02-06 08:49:29 -08:00
Mateo Guzmán a1b05c5b86 Make SynchronousEventReceiver internal (#49218)
Summary:
As part of the initiative to reduce the public API surface, this class can be internalized. I've checked there are [no relevant OSS usages](https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+com.facebook.react.uimanager.events.SynchronousEventReceiver).

## Changelog:

[INTERNAL] - Make com.facebook.react.uimanager.events.SynchronousEventReceiver internal

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

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

Reviewed By: javache

Differential Revision: D69242334

Pulled By: cortinico

fbshipit-source-id: 8ac7ff7d5bed43fff72233c3faa5ad9bded81ef1
2025-02-06 08:31:27 -08:00
Andrew Datsenko 1d5cdf10fc Add .nthCalledWith and .toHaveBeenNthCalledWith (#49221)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49221

Changelog: [Internal]
Add missing jest expect apis

Reviewed By: cortinico

Differential Revision: D69133125

fbshipit-source-id: fe4e54cbb3646c154108cee6ead9a64c3e75c1b7
2025-02-06 08:02:32 -08:00
Mateo Guzmán 5506441df9 Remove unused ViewUtils object (#49219)
Summary:
As part of the initiative to reduce the public API surface, I found that this object is unused and can be removed. I've also checked there are [no relevant OSS usages](https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+com.facebook.react.views.common.ViewUtils).

## Changelog:

[INTERNAL] - Remove unused com.facebook.react.views.common.ViewUtils object

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

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

Reviewed By: cortinico, fabriziocucci

Differential Revision: D69239590

Pulled By: javache

fbshipit-source-id: 14881f68903c8fa87966f35e6d5627e8580c9cc2
2025-02-06 07:19:07 -08:00
Mateo Guzmán 62c9ff6264 Kotlinify MessageQueueThreadPerfStats, ReactQueueConfiguration and QueueThreadExceptionHandler (#49215)
Summary:
Migrating a class holder and two remaining interfaces from com.facebook.react.bridge.queue to Kotlin

## Changelog:

[INTERNAL] - Kotlinify MessageQueueThreadPerfStats, ReactQueueConfiguration and QueueThreadExceptionHandler

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

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

Reviewed By: cipolleschi

Differential Revision: D69236832

Pulled By: cortinico

fbshipit-source-id: 765632740ce5023229d8dd2ddec4c069840c1d33
2025-02-06 07:06:17 -08:00
Rubén Norte c169250a36 Implement test.only in benchmarks (#49222)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49222

Changelog: [internal]

This implements `test.only` in Fantom benchmarks, so we can focus on a specific case to speed up iteration.

Reviewed By: sammy-SC

Differential Revision: D69241220

fbshipit-source-id: 42b02fcb4d693988da4fa15a0c6bd7e90e473b9f
2025-02-06 05:50:19 -08:00
Rubén Norte 1902c3c4d5 Rename add as test in benchmarking API (#49223)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49223

Changelog: [internal]

Renames `suite.add()` as `suite.test()` for symmetry with Jest.

We'll also allow `test.only` in a following change for quick iteration.

Reviewed By: rshest

Differential Revision: D69241221

fbshipit-source-id: d141f80dc0c8e51b419ce233bca68bf0755fd356
2025-02-06 05:50:19 -08:00
Nicola Corti e96396bd18 Fix @react-native/popup-menu-android not building for 3rd party developers (#49212)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49212

Currently, developers can't use `popup-menu-android` at all because the Gradle file we publish is referencing
internal machinery.

I'm adding a pre-publish script that manipulates the Gradle. This is the easiest solution without having to do
crazy setup inside RNGP or having duplicated version codes around in the monorepo.

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

Changelog:
[Android] [Fixed] - Fix react-native/popup-menu-android not building for 3rd party developers

Reviewed By: cipolleschi

Differential Revision: D69192874

fbshipit-source-id: 9f9e8a0a6e76308e598a09f4c70dbc659c238b00
2025-02-06 05:47:37 -08:00
Oskar Kwaśniewski ecad90ad8b fix: move view flattening props to cross platform type interface (#49220)
Summary:
Hey!

Since new architecture introduced View Flattening on iOS, props responsible for disabling this feature on specific views should be defined in cross platform interface.

Reference: https://github.com/reactwg/react-native-new-architecture/discussions/110

## Changelog:

[GENERAL] [CHANGED] - move view flattening props to cross platform type interface

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

Test Plan: N/A

Reviewed By: fabriziocucci

Differential Revision: D69239454

Pulled By: javache

fbshipit-source-id: a89cb9fbaec63bbcb7691df067d5d3a375a8a66e
2025-02-06 04:12:56 -08:00
Jakub Piasecki eae7d3c6a1 Update types exposed by Alert and the module structure (#49157)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49157

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D69044715

fbshipit-source-id: 05486d9c9be161a3604ef535d260f525d7c9e9d1
2025-02-06 03:59:28 -08:00
Riccardo Cipolleschi 9d4c4b2741 Fix typo in verifyReleaseOnNPM-test file (#49207)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49207

Renamed `verifyReleaseOnNPM-test` to `verifyRNReleaseOnNpm-test`.

## Changelog:
[Internal] - rename a test file for CI script

Reviewed By: huntie

Differential Revision: D69183614

fbshipit-source-id: 7a2c2804617f380758a53438598bb6fe0e27e68d
2025-02-06 03:39:15 -08:00
Dawid Małecki 5f110c416b Add Settings path to build-types script and align Flow with TS types (#49175)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49175

Changelog:
[Internal]

Reviewed By: huntie

Differential Revision: D69121991

fbshipit-source-id: 022d56e823aae884ce10fd24cc9701b02db19e9f
2025-02-06 03:02:42 -08:00
Nicola Corti 091f8cf506 Add changelog for 0.76.7 (#49214)
Summary:
Add changelog for 0.76.7

## Changelog:

[Internal] [Changed] -

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

Test Plan: N/A

Reviewed By: cipolleschi

Differential Revision: D69198059

Pulled By: cortinico

fbshipit-source-id: 0f9c22ad8d175d1afab4f9eb59753834b079d756
2025-02-06 02:49:48 -08:00
Rob Hogan d5c1647a29 Update monorepo Jest to 29.7.0 (#49213)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49213

Update the version of Jest used in React Native and Metro's own tests from `^29.6.3` to `^29.7.0`

Changelog: [Internal]

Reviewed By: yungsters

Differential Revision: D69188217

fbshipit-source-id: 0748db5428e422c048454b7d129cbdd4dab6d687
2025-02-05 21:42:48 -08:00
Nick Lefever 04279cea78 Remove RSNRU feature flag after holdout cleanup (#49146)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49146

Runtime Reference ShadowNode Update is enabled by default and no longer referenced in the RN holdout. This diff:
- removes the feature flag
- removes all references to the feature flag, enabling it
- updates the unit test for RSNRU

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D69061234

fbshipit-source-id: 0dab0b5cae99e83297f34645dee58ae9b3c0dc5f
2025-02-05 18:55:46 -08:00
Thomas Nardone ca5ce205f7 Add separate flags for recycling View, Text components (#49211)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49211

Add more control over view recycling behavior by splitting out each component that currently supports it.

Changelog:[Android][Added] Feature flags for recycling View, Text components separately

Reviewed By: sammy-SC, mdvacca

Differential Revision: D69190841

fbshipit-source-id: 6d85fee7103bf928e4f5bf6946bab3ff4cae4053
2025-02-05 18:36:06 -08:00
Tim Yung fb8a6a5bb0 Animated: Avoid In-Band State Update (#49184)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49184

D65645985 shipped a refactor to `Animated`, so that it would use a custom `useAnimatedPropsMemo` instead of `useMemo`. This significantly improved update performance by no longer invalidating the `AnimatedProps` on effectively every update to `Animated` components.

However, this was measured to increase memory usage. After a few experiments, we identified that use of the in-band state update was responsible for the memory regression. While this requires further root cause investigation, this diff attempts to mitigate the memory regression.

This diff introduces a feature flag that enables an implementation that minimizes duplicated work, such as unnecessarily computing `compositeKey` or creating new instances of `AnimatedProps`. In addition, this implementation strives to do so without significantly degrading when an update is interrupted by a concurrent update.

Changelog:
[General][Changed] - Introduced a feature flag to test an optimization in `Animated` to reduce memory usage.

Reviewed By: rickhanlonii

Differential Revision: D69135223

fbshipit-source-id: a2699a314625e7570698bc41455b139711cfd7e3
2025-02-05 16:24:37 -08:00
David Vacca bdb394f754 Extend ReactNativeFeatureFlags to support prereleaseChannels (#49141)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49141

This diff extends ReactNativeFeatureFlags to support prereleaseChannels, the goal is to be able to configure what release channel each feature flag will be enabled / disabled

changelog: [internal] internal

Reviewed By: rubennorte

Differential Revision: D68583324

fbshipit-source-id: 09fde8511dcf5dff63821f15afe0a2530a0845fd
2025-02-05 13:36:21 -08:00
Mateo Guzmán dab8f6097e Make ProcessorBase internal (#49181)
Summary:
As part of the initiative to reduce the public API surface, this class can be internalized. I've checked there are [no relevant OSS usages](https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+com.facebook.annotationprocessors.common.ProcessorBase).

## Changelog:

[INTERNAL] - Make com.facebook.annotationprocessors.common.ProcessorBase internal

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

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

Reviewed By: mdvacca

Differential Revision: D69178806

Pulled By: cortinico

fbshipit-source-id: 3f4b211fcdfdb5e0614b7d25f29879984b7ae255
2025-02-05 12:53:49 -08:00
Andrew Datsenko 2080f64f03 Add .lastCalledWith and .toHaveBeenLastCalledWith (#49210)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49210

Changelog: [Internal]
Add missing jest expect apis

Reviewed By: rubennorte

Differential Revision: D69131600

fbshipit-source-id: bf7a740a4e830c5ce3403d8489bc3439428ee7b4
2025-02-05 12:52:21 -08:00
Peter Abbondanzo 6c87b748f3 Remove loadVectorDrawablesOnImages feature flag (#49183)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49183

The feature has been released to all for quite some time now and the holdout group has finally been unlinked. This removes all references to the feature flag and a few indicators that the feature is enabled/disabled from RNTester

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D69146787

fbshipit-source-id: 8a7f01016a715e61541910630d8c3ceb84ec5c82
2025-02-05 11:10:29 -08:00
Andrew Datsenko b30a5f8ab2 Add .toBeCalledWith and .toHaveBeenCalledWith (#49177)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49177

Changelog: [Internal]
Add missing jest expect apis

Reviewed By: rubennorte

Differential Revision: D69123826

fbshipit-source-id: 53c550970d1cd2434f52a400ad18c8611f187176
2025-02-05 10:51:08 -08:00
Riccardo Cipolleschi 3033aaaef1 Remove last remnant of CircleCI from comments (#49203)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49203

There are some leftover references to CircleCI in some comments. Let's remove it.

## Changelog:
[Internal] - Remove remaining CircleCI references from comments

Reviewed By: huntie

Differential Revision: D69182573

fbshipit-source-id: ea6cfe98422527d094ad4410cdd2a1a87dd61ddb
2025-02-05 09:55:00 -08:00
Riccardo Cipolleschi 07699e5838 Remove last remnant of CircleCI from npm-utils (#49202)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49202

There are some leftover references to CircleCI in these scripts. Let's remove it.

## Changelog:
[Internal] - Remove remaining CircleCI references from npm-utils scripts

Reviewed By: huntie

Differential Revision: D69182550

fbshipit-source-id: d8707abba3e01c26c8d7170522333dcbc039c19d
2025-02-05 09:55:00 -08:00
Riccardo Cipolleschi 1dd464d84e Remove last remnant of CircleCI from CI scripts (#49201)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49201

There are some leftover references to CircleCI in these scripts. Let's remove it.

## Changelog:
[Internal] - Remove remaining CircleCI references from CI scripts

Reviewed By: huntie

Differential Revision: D69182535

fbshipit-source-id: 4e825b65b5f5ca6ce16f5c7ac2f79088cf2d1ace
2025-02-05 09:55:00 -08:00
Andrew Datsenko 33ff0c4789 Update toBeCalled and toBeCalledTimes aliases (#49200)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49200

Changelog: [Internal]
Update to `toBeCalled` and `toBeCalledTimes` aliases - forward them using prototype so number of frames matches when thrown.

Reviewed By: rubennorte

Differential Revision: D69182276

fbshipit-source-id: c20469959dc2e0f5c3686c90e27cd80117ad5fb7
2025-02-05 08:26:50 -08:00
Jakub Piasecki 289bdb6b1b Enable TypeScript generation for ToastAndroid (#49095)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49095

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D68958757

fbshipit-source-id: 3e06201936e2beda4d6a0e591dcd4a619b169795
2025-02-05 08:15:17 -08:00
Jakub Piasecki 43f07ae9a5 Update common interface shadowing for type generation (#49205)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49205

Changelog: [Internal]

Updates how name shadowing works for the TS type generation prototype to align more with how Flow does it - `.js.flow` files shadow every other file with the same name, then `.js` file (if exists) is treated as the common interface.

The script still uses `.flow.js` for common interface, which will be changed in another diff.

Reviewed By: huntie

Differential Revision: D68958772

fbshipit-source-id: caa390711f2bcd7666d875703fc316d874500a0d
2025-02-05 08:15:17 -08:00
Andrew Datsenko 87bdfda020 Add .toContain and .toContainEqual (#49178)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49178

Changelog: [Internal]
Add missing jest expect apis

Reviewed By: rubennorte

Differential Revision: D69130272

fbshipit-source-id: 77f45c501444700b0839a5aa7fcff807ba70641c
2025-02-05 07:43:50 -08:00
Nicola Corti c4822419c4 Add retry to yarn-install step (#49199)
Summary:
`yarn install` is failing sporadically with a 500. This should mitigate this flakyness.

## Changelog:

[INTERNAL] -

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

Test Plan: CI

Reviewed By: rubennorte

Differential Revision: D69180877

Pulled By: cortinico

fbshipit-source-id: 5276e2744c73df896b4bcadfecf3db61d57d198c
2025-02-05 06:43:39 -08:00
Riccardo Cipolleschi a52f5514ed Automate the check for the Release on NPM (#49164)
Summary:
One of the steps we perform when doing a release is to run `npm view react-native` to verify that the release has been published and it is available with the right tag.
As of today, we check this manually.

This change aims at automating this check so that we don't have to do it manually ourselves.

## Changelog:
[Internal] - Releases: automate the npm view check

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

Test Plan:
Created a veriftyReleaseOnNPM-tests.js jest test to verify that the script works fine.

<img width="667" alt="Screenshot 2025-02-04 at 15 18 24" src="https://github.com/user-attachments/assets/cf08155f-80da-4e15-a922-5c16f3fd806e" />

Reviewed By: cortinico

Differential Revision: D69118622

Pulled By: cipolleschi

fbshipit-source-id: a8d40cd2fcb164d8f7174de680b340510f3e8551
2025-02-05 06:33:57 -08:00
Nicola Corti bf4c887e1d Cleanup public modifiers inside LayoutDirectionUtil (#49172)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49172

Those public keywords are unnecessary, let's remove them.

Changelog:
[Internal] [Changed] -

Reviewed By: NickGerleman

Differential Revision: D69120493

fbshipit-source-id: dbc46f340f33c54c1986813eb6a51f5cfec4790d
2025-02-05 06:28:29 -08:00
Nicola Corti 8b8b05cd1e Make WindowUtil internal (#49171)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49171

This makes the `WindowUtil` class internal. I've verified that there are no meaningful usages in OSS:
https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+com.facebook.react.views.view.WindowUtil

Changelog:
[Internal] [Changed] -

Reviewed By: tdn120

Differential Revision: D69120492

fbshipit-source-id: ac6fe5d6a799f5eb0e572844464dc2139b5a63c9
2025-02-05 06:28:29 -08:00
Alex Hunt eacd793930 Back out "Remove internal XHRInterceptor API" (#49195)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49195

Reverts https://github.com/facebook/react-native/pull/49132. Turns out this is still load bearing / hard to extract.

Changelog: [Internal]

Reviewed By: cipolleschi

Differential Revision: D69177405

fbshipit-source-id: 2de0270974fa8a72a006ef16b3e472287d035065
2025-02-05 06:25:26 -08:00
Mateo Guzmán 628002205c Make AndroidChoreographerProvider internal (#49182)
Summary:
As part of the initiative to reduce the public API surface, this class can be internalized. I've checked there are [no relevant OSS usages](https://github.com/search?type=code&q=NOT+is%3Afork+NOT+org%3Afacebook+NOT+repo%3Areact-native-tvos%2Freact-native-tvos+NOT+repo%3Anuagoz%2Freact-native+NOT+repo%3A2lambda123%2Freact-native+NOT+repo%3Abeanchips%2Ffacebookreactnative+NOT+repo%3AfabOnReact%2Freact-native-notes+NOT+user%3Ahuntie+com.facebook.react.internal.AndroidChoreographerProvider).

## Changelog:

[INTERNAL] - Make com.facebook.react.internal.AndroidChoreographerProvider internal

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

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

Reviewed By: javache

Differential Revision: D69176905

Pulled By: cortinico

fbshipit-source-id: 63edb5a88279a5c68f9cd5afe48bbaf88a9684ff
2025-02-05 06:23:22 -08:00
Rubén Norte 6554a99c0d Add option to enable test only mode in benchmarks (#49193)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49193

Changelog: [internal]

Adds an option to force test-only mode in Fantom benchmarks.

Reviewed By: javache

Differential Revision: D69176982

fbshipit-source-id: 97b23604961eb4ee8747b278a8439d0b0075dc07
2025-02-05 04:29:19 -08:00
Rubén Norte 95160c1e6b Fix testOnly behavior in CI (#49192)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49192

Changelog: [internal]

We refactored the public API of Fantom benchmarks in https://github.com/facebook/react-native/pull/49014 but that refactor broke test only mode, as we started overriding the options after setting them. This fixes that.

Reviewed By: javache

Differential Revision: D69176983

fbshipit-source-id: 9afc2d2f27fb2ee0aa452d4b02c28531acf40b8e
2025-02-05 04:29:19 -08:00
Nick Gerleman 1def9fdbc9 Allow parsing lists of compound data types (#49188)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49188

This allows creating lists of a compound data type, storing each element as a variant of the possible types, instead of as the specified type.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D69142157

fbshipit-source-id: d742d81a6517b24f24827727cd777550f2ad274f
2025-02-04 21:51:21 -08:00
Nick Gerleman 62ea6e891c react/utils/toLower (#49187)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49187

`tolower` is not `constexpr`. Share some quick utilities for char to lowercase, and case insensitive comparision that does not create new string.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D69134770

fbshipit-source-id: 57a84f2d1a441e5a4c07c0db96cb6c133770fb51
2025-02-04 21:51:21 -08:00
Nick Gerleman 75097b2599 CSSCompoundDataType (#49186)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49186

Next up for transforms, and for some future cases, it is convenient to be able to export a single marker type like `CSSTransform`, that can expand to a variant of multiple possible types of different shape (e.g. `CSSMatrix3D` vs `CSSScale`).

It is also best (for code size) to only have a single representation of compound types (e.g. `<CSSLength, CSSPercentage>` generates a separate copy of code compared to `<CSSPercentage, CSSLength>`).

This diff introduces `CSSCompoundDataTypes` which allows composing types, which are then flattened out to discrete types during parsing. For simplicity, `CSSCompoundDataType` cannot currently be nested inside of other `CSSCompoundDataType`, though this could be added in the future.

```
/**
 * Marker for the <length-percentage> data type
 * https://drafts.csswg.org/css-values/#mixed-percentages
 */
using CSSLengthPercentage = CSSCompoundDataType<CSSLength, CSSPercentage>;
```

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D69089416

fbshipit-source-id: 8645009f06eb14b1ac4437a4fc4dd6b9ad3f88a2
2025-02-04 21:51:21 -08:00
Nicola Corti b345cecaaa Centralize yarn install to use actions/yarn-install (#49174)
Summary:
This centralizes the invocation of yarn install to be via the `actions/yarn-install`.
It will make it easier to add a retry if we want for all the `yarn install` steps in all the workflows.

## Changelog:

[INTERNAL] -

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

Test Plan: CI

Reviewed By: NickGerleman

Differential Revision: D69121525

Pulled By: cortinico

fbshipit-source-id: 135da2e172cdf95b2a0ef8fd3d25996ab9317167
2025-02-04 21:12:19 -08:00
Andrew Datsenko b0501a5be2 Add .toStrictEqual (#49176)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49176

Changelog: [Internal]
Add missing jest expect apis

Reviewed By: rubennorte

Differential Revision: D69123117

fbshipit-source-id: 11c326f7ac4df80852409a1c9d72c911ceff21f7
2025-02-04 13:52:57 -08:00
Nick Gerleman 6a683cb268 Cleanup CSSKeyword (#49155)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49155

This does some code cleanup for CSS keywords to reduce boilerplate, duplication, better isolate namespace, fix a typo, and ensure we get a warning (unused variable) if we miss handling a defined keyword.

We technically don't need `CSSKeyword` at all anymore, and don't need to overlay each keywords values to be the same, though having a pattern where each keyword set uses ordinal values from CSSKeyword forces folks to look and add the enum to the list, and include the header defining the data types for keyword sets, instead of each set looking a little magic.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D69083181

fbshipit-source-id: b2764e87c2a127d73f816327c4edd45151ea8d82
2025-02-04 12:16:40 -08:00
Nick Gerleman 0cea462113 Font Variant Parsing (#49151)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49151

This diff is... maybe an argument against a global list of interned keywords (it works better in some other contexts though), and this structure is likely to change later when we reintroduce what was previously `CSSPropertyDescriptor` (a list of allowed keywords per property).

But... we're going to roll with this for now to replace the ViewConfig processor (which string splits) in the most over-engineered way possible.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D68851566

fbshipit-source-id: 71022f051b112adc03bd182d433e3d890e6023f2
2025-02-04 12:16:40 -08:00
Nick Gerleman b985831702 CSSWhitespaceSeparatedList (#49152)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49152

For parsing a variable number of whitespace separated data types.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D68849561

fbshipit-source-id: be3314990d9e7c202c02deba463d79e50985c0b7
2025-02-04 12:16:40 -08:00
Nick Gerleman 68946780d0 Support parsing <shadow> (#48991)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48991

This adds support for parsing the `<shadow>` data type. In combination with `CSSCommaSeparatedList`, we can now parse box shadow expressions.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D68744811

fbshipit-source-id: bac7be0faf8cd8eee04f21651180151edeef7294
2025-02-04 12:16:40 -08:00
Nick Gerleman 1624cdb29e Fix missing handling of "inset" CSS Keyword (#48990)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48990

tsia

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D68743950

fbshipit-source-id: 4c4814e3e83be92aea56992cc7d2c830edca99dd
2025-02-04 12:16:40 -08:00
Nick Gerleman f77fced5e2 CSSCommaSeparatedList (#48987)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48987

Adds a data type parser for a variable number of values of a given single data type (at least 1).

E.g. `CSSCommaSeparatedList<CSSShadow>` will represent the syntax of `<shadow>#` (ie the value produced by box-shadow).

Changelog: [internal]

Reviewed By: lenaic

Differential Revision: D68738165

fbshipit-source-id: 6dd17b3da24b1c24808e49834a29a237c0115fab
2025-02-04 12:16:40 -08:00
Nick Gerleman 6482204523 CSSDataTypeParser consume() function (#48986)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48986

This adds a new `consume()` function to data type parsers which passes a raw parser. This can be used for types which are compounds of other data types, where we may want to accept more than the first token.

This will be used for shadow parsing, but also fixes a hypothetical future bug with ratios. E.g. `calc(foo) / calc(bar)` may be a valid ratio, not starting with a token. We instead just want to try to parse a number data type from the stream.

The form of parsing a preserved token + rest is removed, with the assumption that anything parsing more than a single token should use compound parsing.

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D68735370

fbshipit-source-id: 660e0b4a496136c8a559f4ba47bc1bd8d17aa116
2025-02-04 12:16:40 -08:00
Nick Gerleman 966f2a2983 Do not consume component value on visitor failure (#48985)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48985

This reverts some of the behavior I added in D68357624, since peeking a component value is non-obviously more expensive than manually copying the parser, and needing to peek will be a pain for flat lists of values (like for box-shadow).

Changelog: [internal]

Reviewed By: lenaic

Differential Revision: D68733518

fbshipit-source-id: 7b4a061d1649019274441ae0e82609f771dd2916
2025-02-04 12:16:40 -08:00
182 changed files with 8398 additions and 1812 deletions
+1
View File
@@ -3,6 +3,7 @@
docs/generatedComponentApiDocs.js
packages/react-native/flow/
packages/react-native/sdks/
packages/react-native/types_generated/
packages/react-native/ReactAndroid/build
packages/react-native/ReactAndroid/hermes-engine/build/
packages/react-native/Libraries/Renderer/*
+2 -3
View File
@@ -15,9 +15,8 @@ runs:
uses: ./.github/actions/setup-node
with:
node-version: ${{ inputs.node-version }}
- name: Yarn install
shell: bash
run: yarn install --non-interactive --frozen-lockfile
- name: Run yarn install
uses: ./.github/actions/yarn-install
- name: Run linters against modified files (analysis-bot)
shell: bash
run: yarn lint-ci
+2 -2
View File
@@ -42,12 +42,12 @@ runs:
with:
java-version: '17'
distribution: 'zulu'
- name: Run yarn install
uses: ./.github/actions/yarn-install
- name: Start Metro in Debug
shell: bash
if: ${{ inputs.flavor == 'Debug' }}
run: |
yarn install
# build codegen or we will see a redbox
./packages/react-native-codegen/scripts/oss/build.sh
+14 -1
View File
@@ -4,4 +4,17 @@ runs:
steps:
- name: Install dependencies
shell: bash
run: yarn install --non-interactive --frozen-lockfile
run: |
MAX_ATTEMPTS=2
ATTEMPT=0
WAIT_TIME=20
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
yarn install --non-interactive --frozen-lockfile && break
echo "yarn install failed. Retrying in $WAIT_TIME seconds..."
sleep $WAIT_TIME
ATTEMPT=$((ATTEMPT + 1))
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "All attempts to invoke yarn install failed - Aborting the workflow"
exit 1
fi
@@ -15,12 +15,14 @@ const {
const mockRun = jest.fn();
const mockSleep = jest.fn();
const mockGetNpmPackageInfo = jest.fn();
const mockVerifyPublishedPackage = jest.fn();
const silence = () => {};
jest.mock('../utils.js', () => ({
log: silence,
run: mockRun,
sleep: mockSleep,
verifyPublishedPackage: mockVerifyPublishedPackage,
getNpmPackageInfo: mockGetNpmPackageInfo,
}));
@@ -82,77 +84,43 @@ describe('#verifyPublishedTemplate', () => {
it("waits on npm updating for version and not 'latest'", async () => {
const NOT_LATEST = false;
mockGetNpmPackageInfo
// template@<version>
.mockReturnValueOnce(Promise.reject('mock http/404'))
.mockReturnValueOnce(Promise.resolve());
mockSleep.mockReturnValueOnce(Promise.resolve()).mockImplementation(() => {
throw new Error('Should not be called again!');
});
const version = '0.77.0';
await verifyPublishedTemplate(version, NOT_LATEST);
expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith(
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'@react-native-community/template',
version,
null,
18,
);
});
it('waits on npm updating version and latest tag', async () => {
const IS_LATEST = true;
const version = '0.77.0';
mockGetNpmPackageInfo
// template@latest → unknown tag
.mockReturnValueOnce(Promise.reject('mock http/404'))
// template@latest != version → old tag
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
// template@latest == version → correct tag
.mockReturnValueOnce(Promise.resolve({version}));
mockSleep
.mockReturnValueOnce(Promise.resolve())
.mockReturnValueOnce(Promise.resolve())
.mockImplementation(() => {
throw new Error('Should not be called again!');
});
await verifyPublishedTemplate(version, IS_LATEST);
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'@react-native-community/template',
version,
'latest',
18,
);
});
describe('timeouts', () => {
let mockProcess;
beforeEach(() => {
mockProcess = jest.spyOn(process, 'exit').mockImplementation(code => {
throw new Error(`process.exit(${code}) called!`);
});
});
afterEach(() => mockProcess.mockRestore());
describe('retries', () => {
it('will timeout if npm does not update package version after a set number of retries', async () => {
const RETRIES = 2;
mockGetNpmPackageInfo.mockReturnValue(Promise.reject('mock http/404'));
mockSleep.mockReturnValue(Promise.resolve());
await expect(() =>
verifyPublishedTemplate('0.77.0', true, RETRIES),
).rejects.toThrowError('process.exit(1) called!');
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
});
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
const RETRIES = 7;
const IS_LATEST = true;
mockGetNpmPackageInfo.mockReturnValue(
Promise.resolve({version: '0.76.5'}),
);
mockSleep.mockReturnValue(Promise.resolve());
await expect(async () => {
await verifyPublishedTemplate('0.77.0', IS_LATEST, RETRIES);
}).rejects.toThrowError('process.exit(1) called!');
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
await verifyPublishedTemplate('0.77.0', true, RETRIES),
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'@react-native-community/template',
'0.77.0',
'latest',
2,
);
});
});
});
@@ -0,0 +1,135 @@
/**
* 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
*/
const {verifyPublishedPackage} = require('../verifyPublishedPackage');
const mockRun = jest.fn();
const mockSleep = jest.fn();
const mockGetNpmPackageInfo = jest.fn();
const silence = () => {};
const REACT_NATIVE_PACKAGE = 'react-native';
jest.mock('../utils.js', () => ({
log: silence,
run: mockRun,
sleep: mockSleep,
getNpmPackageInfo: mockGetNpmPackageInfo,
}));
describe('#verifyPublishedPackage', () => {
beforeEach(jest.clearAllMocks);
it("waits on npm updating for version and not 'latest'", async () => {
mockGetNpmPackageInfo
// template@<version>
.mockReturnValueOnce(Promise.reject('mock http/404'))
.mockReturnValueOnce(Promise.resolve());
mockSleep.mockReturnValueOnce(Promise.resolve()).mockImplementation(() => {
throw new Error('Should not be called again!');
});
const version = '0.78.0';
await verifyPublishedPackage(REACT_NATIVE_PACKAGE, version, null);
expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith(
REACT_NATIVE_PACKAGE,
version,
);
});
it('waits on npm updating version and latest tag', async () => {
const version = '0.78.0';
mockGetNpmPackageInfo
// template@latest → unknown tag
.mockReturnValueOnce(Promise.reject('mock http/404'))
// template@latest != version → old tag
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
// template@latest == version → correct tag
.mockReturnValueOnce(Promise.resolve({version}));
mockSleep
.mockReturnValueOnce(Promise.resolve())
.mockReturnValueOnce(Promise.resolve())
.mockImplementation(() => {
throw new Error('Should not be called again!');
});
await verifyPublishedPackage(REACT_NATIVE_PACKAGE, version, 'latest');
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
REACT_NATIVE_PACKAGE,
'latest',
);
});
it('waits on npm updating version and next tag', async () => {
const version = '0.78.0-rc.0';
mockGetNpmPackageInfo
// template@latest → unknown tag
.mockReturnValueOnce(Promise.reject('mock http/404'))
// template@latest != version → old tag
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
// template@latest == version → correct tag
.mockReturnValueOnce(Promise.resolve({version}));
mockSleep
.mockReturnValueOnce(Promise.resolve())
.mockReturnValueOnce(Promise.resolve())
.mockImplementation(() => {
throw new Error('Should not be called again!');
});
await verifyPublishedPackage(REACT_NATIVE_PACKAGE, version, 'next');
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
REACT_NATIVE_PACKAGE,
'next',
);
});
describe('timeouts', () => {
let mockProcess;
beforeEach(() => {
mockProcess = jest.spyOn(process, 'exit').mockImplementation(code => {
throw new Error(`process.exit(${code}) called!`);
});
});
afterEach(() => mockProcess.mockRestore());
it('will timeout if npm does not update package version after a set number of retries', async () => {
const RETRIES = 2;
mockGetNpmPackageInfo.mockReturnValue(Promise.reject('mock http/404'));
mockSleep.mockReturnValue(Promise.resolve());
await expect(() =>
verifyPublishedPackage(
REACT_NATIVE_PACKAGE,
'0.77.0',
'latest',
RETRIES,
),
).rejects.toThrowError('process.exit(1) called!');
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
});
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
const RETRIES = 7;
const IS_LATEST = true;
mockGetNpmPackageInfo.mockReturnValue(
Promise.resolve({version: '0.76.5'}),
);
mockSleep.mockReturnValue(Promise.resolve());
await expect(async () => {
await verifyPublishedPackage(
REACT_NATIVE_PACKAGE,
'0.77.0',
'latest',
RETRIES,
);
}).rejects.toThrowError('process.exit(1) called!');
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
});
});
});
@@ -0,0 +1,104 @@
/**
* 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
*/
const {verifyReleaseOnNpm} = require('../verifyReleaseOnNpm');
const mockVerifyPublishedPackage = jest.fn();
const silence = () => {};
jest.mock('../utils.js', () => ({
verifyPublishedPackage: mockVerifyPublishedPackage,
}));
describe('#verifyReleaseOnNPM', () => {
beforeEach(jest.clearAllMocks);
it("waits on npm updating for version and not 'latest'", async () => {
const NOT_LATEST = false;
const version = '0.78.0';
await verifyReleaseOnNpm(version, NOT_LATEST);
expect(mockVerifyPublishedPackage).toHaveBeenLastCalledWith(
'react-native',
version,
null,
18,
);
});
it('waits on npm updating version and latest tag', async () => {
const IS_LATEST = true;
const version = '0.78.0';
await verifyReleaseOnNpm(version, IS_LATEST);
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'react-native',
version,
'latest',
18,
);
});
it('waits on npm updating version, not latest and next tag', async () => {
const IS_LATEST = false;
const version = '0.78.0-rc.0';
await verifyReleaseOnNpm(version, IS_LATEST);
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'react-native',
version,
'next',
18,
);
});
it('waits on npm updating version, latest and next tag', async () => {
const IS_LATEST = true;
const version = '0.78.0-rc.0';
await verifyReleaseOnNpm(version, IS_LATEST);
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'react-native',
version,
'next',
18,
);
});
describe('timeouts', () => {
it('will timeout if npm does not update package version after a set number of retries', async () => {
const RETRIES = 2;
await verifyReleaseOnNpm('0.77.0', true, RETRIES),
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'react-native',
'0.77.0',
'latest',
2,
);
});
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
const RETRIES = 7;
const IS_LATEST = true;
await verifyReleaseOnNpm('0.77.0', IS_LATEST, RETRIES);
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
'react-native',
'0.77.0',
'latest',
7,
);
});
});
});
+2 -2
View File
@@ -4,8 +4,8 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
GITHUB_OWNER=${CIRCLE_PROJECT_USERNAME:-facebook}
GITHUB_REPO=${CIRCLE_PROJECT_REPONAME:-react-native}
GITHUB_OWNER=-facebook
GITHUB_REPO=-react-native
export GITHUB_OWNER
export GITHUB_REPO
+7 -21
View File
@@ -4,34 +4,20 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
GITHUB_OWNER=${CIRCLE_PROJECT_USERNAME:-facebook}
GITHUB_REPO=${CIRCLE_PROJECT_REPONAME:-react-native}
GITHUB_OWNER=-facebook
GITHUB_REPO=-react-native
export GITHUB_OWNER
export GITHUB_REPO
if [ -x "$(command -v shellcheck)" ]; then
IFS=$'\n'
if [ -n "$CIRCLE_CI" ]; then
results=( "$(find . -type f -not -path "*node_modules*" -not -path "*third-party*" -name '*.sh' -exec sh -c 'shellcheck "$1" -f json' -- {} \;)" )
cat <(echo shellcheck; printf '%s\n' "${results[@]}" | jq .,[] | jq -s . | jq --compact-output --raw-output '[ (.[] | .[] | . ) ]') | GITHUB_PR_NUMBER="$GITHUB_PR_NUMBER" node packages/react-native-bots/code-analysis-bot.js
# check status
STATUS=$?
if [ $STATUS == 0 ]; then
echo "Shell scripts analyzed successfully."
else
echo "Shell script analysis failed, error status $STATUS."
fi
else
find . \
-type f \
-not -path "*node_modules*" \
-not -path "*third-party*" \
-name '*.sh' \
find . \
-type f \
-not -path "*node_modules*" \
-not -path "*third-party*" \
-name '*.sh' \
-exec sh -c 'shellcheck "$1"' -- {} \;
fi
else
echo 'shellcheck is not installed. See https://github.com/facebook/react-native/wiki/Development-Dependencies#shellcheck for instructions.'
+12 -34
View File
@@ -7,7 +7,7 @@
* @format
*/
const {run, sleep, getNpmPackageInfo, log} = require('./utils.js');
const {run, sleep, log, verifyPublishedPackage} = require('./utils.js');
const TAG_AS_LATEST_REGEX = /#publish-packages-to-npm&latest/;
@@ -53,8 +53,7 @@ module.exports.publishTemplate = async (github, version, dryRun = true) => {
});
};
const SLEEP_S = 10;
const MAX_RETRIES = 3 * 6; // 3 minutes
const MAX_RETRIES = 3 * 6; // 18 attempts. Waiting between attempt: 10 s. Total time: 3 mins.
const TEMPLATE_NPM_PKG = '@react-native-community/template';
/**
@@ -68,36 +67,15 @@ module.exports.verifyPublishedTemplate = async (
latest = false,
retries = MAX_RETRIES,
) => {
log(`🔍 Is ${TEMPLATE_NPM_PKG}@${version} on npm?`);
let count = retries;
while (count-- > 0) {
try {
const json = await getNpmPackageInfo(
TEMPLATE_NPM_PKG,
latest ? 'latest' : version,
);
log(`🎉 Found ${TEMPLATE_NPM_PKG}@${version} on npm`);
if (!latest) {
return;
}
if (json.version === version) {
log(`🎉 ${TEMPLATE_NPM_PKG}@latest → ${version} on npm`);
return;
}
log(
`🐌 ${TEMPLATE_NPM_PKG}@latest → ${pkg.version} on npm and not ${version} as expected, retrying...`,
);
} catch (e) {
log(`Nope, fetch failed: ${e.message}`);
}
await sleep(SLEEP_S);
try {
await verifyPublishedPackage(
TEMPLATE_NPM_PKG,
version,
latest ? 'latest' : null,
retries,
);
} catch (e) {
console.error(e.message);
process.exit(1);
}
let msg = `🚨 Timed out when trying to verify ${TEMPLATE_NPM_PKG}@${version} on npm`;
if (latest) {
msg += ' and latest tag points to this version.';
}
log(msg);
process.exit(1);
};
+8 -4
View File
@@ -12,18 +12,22 @@ const {execSync} = require('child_process');
function run(cmd) {
return execSync(cmd, 'utf8').toString().trim();
}
module.exports.run = run;
async function sleep(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
module.exports.sleep = sleep;
async function getNpmPackageInfo(pkg, versionOrTag) {
return fetch(`https://registry.npmjs.org/${pkg}/${versionOrTag}`).then(resp =>
resp.json(),
);
}
module.exports.getNpmPackageInfo = getNpmPackageInfo;
module.exports.log = (...args) => console.log(...args);
const log = (...args) => console.log(...args);
module.exports = {
log,
getNpmPackageInfo,
sleep,
run,
};
@@ -0,0 +1,63 @@
/**
* 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
*/
const {log, getNpmPackageInfo, sleep} = require('./utils');
const SLEEP_S = 10;
const MAX_RETRIES = 3 * 6; // 18 attempts. Waiting between attempt: 10 s. Total time: 3 mins.
async function verifyPublishedPackage(
packageName,
version,
tag = null,
retries = MAX_RETRIES,
) {
log(`🔍 Is ${packageName}@${version} on npm?`);
let count = retries;
while (count-- > 0) {
try {
const json = await getNpmPackageInfo(packageName, tag ? tag : version);
log(`🎉 Found ${packageName}@${version} on npm`);
if (!tag) {
return;
}
// check for next tag
if (tag === 'next' && json.version === version) {
log(`🎉 ${packageName}@next → ${version} on npm`);
return;
}
// Check for latest tag
if (tag === 'latest' && json.version === version) {
log(`🎉 ${packageName}@latest → ${version} on npm`);
return;
}
log(
`🐌 ${packageName}@${tag}${pkg.version} on npm and not ${version} as expected, retrying...`,
);
} catch (e) {
log(`Nope, fetch failed: ${e.message}`);
}
await sleep(SLEEP_S);
}
let msg = `🚨 Timed out when trying to verify ${packageName}@${version} on npm`;
if (tag) {
msg += ` and ${tag} tag points to this version.`;
}
log(msg);
process.exit(1);
}
module.exports = {
verifyPublishedPackage,
};
@@ -0,0 +1,26 @@
/**
* 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
*/
const {run, sleep, log, verifyPublishedPackage} = require('./utils.js');
const REACT_NATIVE_NPM_PKG = 'react-native';
const MAX_RETRIES = 3 * 6; // 18 attempts. Waiting between attempt: 10 s. Total time: 3 mins.
/**
* Will verify that @latest, @next and the @<version> have been published.
*
* NOTE: This will infinitely query each step until successful, make sure the
* calling job has a timeout.
*/
module.exports.verifyReleaseOnNpm = async (
version,
latest = false,
retries = MAX_RETRIES,
) => {
const tag = version.includes('-rc.') ? 'next' : latest ? 'latest' : null;
await verifyPublishedPackage(REACT_NATIVE_NPM_PKG, version, tag, retries);
};
+2 -3
View File
@@ -18,9 +18,8 @@ jobs:
if: github.repository == 'facebook/react-native'
steps:
- uses: actions/checkout@v4
- name: Run Yarn Install on Root
run: yarn install
working-directory: .
- name: Run yarn install
uses: ./.github/actions/yarn-install
- name: Danger
run: yarn danger ci --use-github-checks --failOnErrors
working-directory: packages/react-native-bots
+27
View File
@@ -0,0 +1,27 @@
name: Monitor React Native New Issues
on:
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
jobs:
monitor-issues:
runs-on: ubuntu-latest
steps:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Monitor New Issues
uses: react-native-community/repo-monitor@v1.0.0
with:
task: "monitor-issues"
git_secret: ${{ secrets.GITHUB_TOKEN }}
notifier: "discord"
fetch_data_interval: 6
repo_owner: "facebook"
repo_name: "react-native"
discord_webhook_url: "${{ secrets.DISCORD_WEBHOOK_URL }}"
discord_id_type: "role"
discord_ids: "1295340673779630141"
+10
View File
@@ -214,3 +214,13 @@ jobs:
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \
-d "{\"event_type\": \"publish\", \"client_payload\": { \"version\": \"${{ github.ref_name }}\" }}"
- name: Verify Release is on NPM
timeout-minutes: 3
uses: actions/github-script@v6
with:
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
script: |
const {verifyReleaseOnNpm} = require('./.github/workflow-scripts/verifyReleaseOnNpm.js');
const {isLatest()} = require('./.github/workflow-scripts/publishTemplate.js');
const version = "${{ github.ref_name }}";
await verifyReleaseOnNpm(version, isLatest());
+1
View File
@@ -7,5 +7,6 @@
packages/*/dist
vendor
packages/react-native/types_generated/
packages/react-native-codegen/e2e/__test_fixtures__/modules/NativeEnumTurboModule.js
+61 -1
View File
@@ -49,7 +49,7 @@
#### Android specific
- Changed visibility of FrescoBasedReactTextInlineImageViewManager to internal ([d5f33c19cb](https://github.com/facebook/react-native/commit/d5f33c19cb33e2f2c7d2470cc90872c1f065f20d) by [@alanleedev](https://github.com/alanleedev))
- Mikgrating pointerEvents API breaks compatibility for kotlin usages of this api as a val ([45e4a3afce](https://github.com/facebook/react-native/commit/45e4a3afceb4be3047cd01a60ec2c9f806ed30fe) by [@mdvacca](https://github.com/mdvacca))
- Migrating pointerEvents API breaks compatibility for kotlin usages of this api as a val ([45e4a3afce](https://github.com/facebook/react-native/commit/45e4a3afceb4be3047cd01a60ec2c9f806ed30fe) by [@mdvacca](https://github.com/mdvacca))
- Convert RootView to Kotlin ([21c9491926](https://github.com/facebook/react-native/commit/21c94919260a68409f82081740169d0409e78933) by [@fabriziocucci](https://github.com/fabriziocucci))
- Delete unused abstract class GuardedResultAsyncTask ([67bff8734f](https://github.com/facebook/react-native/commit/67bff8734f4b92fe399910eecad5b67511a749c1) by [@mdvacca](https://github.com/mdvacca))
- Delete deprecated class FabricViewStateManager ([b25b65ba19](https://github.com/facebook/react-native/commit/b25b65ba19f3c674fd2efe5c01123ccc0ae55cbf) by [@mdvacca](https://github.com/mdvacca))
@@ -547,6 +547,32 @@ github.com/robhogan))
- **TextInput:** Workaround for Mac Catalyst TextInput crash due to serialization attempt of WeakEventEmitter ([e04738b7ec](https://github.com/facebook/react-native/commit/e04738b7ecec9e7da3aab49bb24a6336b9496b94) by [@rozele](https://github.com/rozele))
- **TextInput:** Fix `maxLength` not working in old arch ([4b3ef3b00c](https://github.com/facebook/react-native/commit/4b3ef3b00ce0026c0d1e1f2a5546fcec249255d8) by [@mateoguzmana](https://github.com/mateoguzmana))
## v0.76.7
### Changed
#### iOS specific
- **Deps:** Pin 'concurrent-ruby' to a working version ([198adb47af](https://github.com/facebook/react-native/commit/198adb47af3676c85b35adb308c110c1d87120c8) by [@cipolleschi](https://github.com/cipolleschi))
### Fixed
- **Text** Fix `maxFontSizeMultiplier` prop on `Text` and `TextInput` components in Fabric / New Architecture ([ea49d4d1b01107a5ecbbbd4904f1d935e51d6b32](https://github.com/facebook/react-native/commit/ea49d4d1b01107a5ecbbbd4904f1d935e51d6b32) by [@RickardZrinski](https://github.com/RickardZrinski))
- **Appearance:** Fix `Appearance.setColorScheme(null)` not resetting color scheme value ([7d63235086](https://github.com/facebook/react-native/commit/7d63235086352d8c424d634c7039551f0a5025dc) by [@sangonz193](https://github.com/sangonz193))
#### Android specific
- **Deps:** Add missing `invariant` dependency ([ee8088b615](https://github.com/facebook/react-native/commit/ee8088b6157837c239db47ac5bd3a8603ceefc3c) by [@tido64](https://github.com/tido64))
- **Turbomodule** Fix execution of early InteropEvents ([4ed2b35bf6](https://github.com/facebook/react-native/commit/4ed2b35bf61426c81c9f8b30a142d77b44988fdb) by [@mdvacca](https://github.com/mdvacca))
- **Deps:** Bump Kotlin to 1.9.25 to mitigate #49115 ([f8857ba3b5](https://github.com/facebook/react-native/commit/f8857ba3b51f26871d0a0b82b9581a0c35b6273d) by [@cortinico](https://github.com/cortinico))
#### iOS specific
- **runtime:** `RCTSurfaceHostingProxyRootView` no longer has different behavior (whether it calls `start` on the provided *surface*) depending on which initializer is used. Call `start` yourself on the *surface* instead. ([13b93cfdda](https://github.com/facebook/react-native/commit/13b93cfddaa559697968ac1c19e55f7aaa053070) by Nolan O'Brien)
- Be less strict with method parsing of TurboModule Interop Layer
- **Turbomodule:** Avoid crashing the app when the InteropLayer can't find some methods in the native implementation. ([3bd3f101b9](https://github.com/facebook/react-native/commit/3bd3f101b9dcff8551a2f8259ddeed9843fd69b8) by [@cipolleschi](https://github.com/cipolleschi))
- **Runtime:** Fix applicationDidEnterBackground not being called ([adaceba546](https://github.com/facebook/react-native/commit/adaceba5462b4ad8676745f34e0be2bf5bb25166) by [@alextoudic](https://github.com/alextoudic))
## v0.76.6
### Fixed
@@ -952,6 +978,40 @@ created on the mqt_native thread. ([c4a6bbc8fd](https://github.com/facebook/reac
- **infra:** Update ws from 7.5.1 to 7.5.10 (CVE-2024-37890) ([13f1b9e10f](https://github.com/facebook/react-native/commit/13f1b9e10f6045421808714f7e62aa17bfb3e891) by [@GijsWeterings](https://github.com/GijsWeterings))
- **infra:** Update ws from 6.2.2 to 6.2.3 (CVE-2024-37890) ([80cfacef78](https://github.com/facebook/react-native/commit/80cfacef78f34d3786d955084a8bf4d42ea37f1b) by [@GijsWeterings](https://github.com/GijsWeterings))
## v0.75.5
### Added
- **Hermes:** Implement more missing methods on WithRuntimeDecorator ([80f67ca03c](https://github.com/facebook/react-native/commit/80f67ca03c99c688e2a3127e9b3dddd02625848e) by [@neildhar](https://github.com/neildhar))
### Changed
#### Android specific
- **Deps:** Bump Kotlin to 1.9.25 to mitigate [#49115](https://github.com/facebook/react-native/issues/49115) ([25e76a2717](https://github.com/facebook/react-native/commit/25e76a271781b3ffe8002108d8b12aa3d47442b5) by [@riteshshukla04](https://github.com/riteshshukla04))
#### iOS specific
- **Deps:** Pin Xcodeproj to < 1.26.0 ([2922af2e7e](https://github.com/facebook/react-native/commit/2922af2e7e8527a93c7956b10ddb314f25c334fa) by [@cipolleschi](https://github.com/cipolleschi))
- **Deps:** Pin concurrent-ruby to <= 1.3.4 ([794bf34e60](https://github.com/facebook/react-native/commit/794bf34e60cea8146aebad1fefe051d4140fc28b) by [@cipolleschi](https://github.com/cipolleschi))
### Fixed
- **FormData:** fix: FormData filename in content-disposition ([78ef1e2bc2](https://github.com/facebook/react-native/commit/78ef1e2bc2ed30321745e2505713915b9015d920) by [@foyarash](https://github.com/@foyarash))
#### Android specific
- **TextInput:** Set TextInput selection correctly when attached to window in Android ([1656394bae](https://github.com/facebook/react-native/commit/1656394bae16cc54fb38687d38bcbf85138c98a2) by [@QichenZhu](https://github.com/QichenZhu))
#### iOS specific
- **Animation:** Fabric: Fixes animations strict weak ordering sorted check failed ([ea0bc54115](https://github.com/facebook/react-native/commit/ea0bc541155700e0973d960c94d01918d6b28c6b) by [@zhongwuzw](https://github.com/zhongwuzw))
- **Hermes** Exclude dSYM from the archive ([fdb2631b5e](https://github.com/facebook/react-native/commit/fdb2631b5ea27765663046b94f84956d30ebaaeb) by [@cipolleschi](https://github.com/cipolleschi))
- **Image** Fix images not displayed when extension is implicit ([b6ed0d351e](https://github.com/facebook/react-native/commit/b6ed0d351e246c431bdc88a6c3d154ba35220c25) by [@cipolleschi](https://github.com/cipolleschi))
- **Xcode:** Fix the generation of .xcode.env.local ([dbffbf72d7](https://github.com/facebook/react-native/commit/dbffbf72d7287e021e965b6639e455e8555bbf2e) by [@cipolleschi](https://github.com/cipolleschi))
## v0.75.4
### Fixed
+5 -1
View File
@@ -12,5 +12,9 @@
// https://www.npmjs.com/package/debug
declare module 'debug' {
declare module.exports: (namespace: string) => (...Array<mixed>) => void;
declare module.exports: {
(namespace: string): (...Array<mixed>) => void,
enable(match: string): void,
disable(): void,
};
}
+43 -42
View File
@@ -1,29 +1,28 @@
// flow-typed signature: e556c06e721548417501c08b01fec911
// flow-typed version: ad3adf2de8/react-dom_v17.x.x/flow_>=v0.127.x
declare module 'react-dom' {
import type {Component} from 'react';
declare var version: string;
declare function findDOMNode(
componentOrElement: Element | ?React$Component<any, any>
componentOrElement: Element | ?Component<any, any>
): null | Element | Text;
declare function render<ElementType: React$ElementType>(
element: React$Element<ElementType>,
declare function render<ElementType: React.ElementType>(
element: ExactReactElement_DEPRECATED<ElementType>,
container: Element,
callback?: () => void
): React$ElementRef<ElementType>;
): React.ElementRef<ElementType>;
declare function hydrate<ElementType: React$ElementType>(
element: React$Element<ElementType>,
declare function hydrate<ElementType: React.ElementType>(
element: ExactReactElement_DEPRECATED<ElementType>,
container: Element,
callback?: () => void
): React$ElementRef<ElementType>;
): React.ElementRef<ElementType>;
declare function createPortal(
node: React$Node,
node: React.Node,
container: Element
): React$Portal;
): React.Portal;
declare function unmountComponentAtNode(container: any): boolean;
@@ -37,30 +36,32 @@ declare module 'react-dom' {
): void;
declare function unstable_renderSubtreeIntoContainer<
ElementType: React$ElementType
ElementType: React.ElementType
>(
parentComponent: React$Component<any, any>,
nextElement: React$Element<ElementType>,
parentComponent: Component<any, any>,
nextElement: ExactReactElement_DEPRECATED<ElementType>,
container: any,
callback?: () => void
): React$ElementRef<ElementType>;
): React.ElementRef<ElementType>;
}
declare module 'react-dom/server' {
declare var version: string;
declare function renderToString(element: React$Node): string;
declare function renderToString(element: React.Node): string;
declare function renderToStaticMarkup(element: React$Node): string;
declare function renderToStaticMarkup(element: React.Node): string;
declare function renderToNodeStream(element: React$Node): stream$Readable;
declare function renderToNodeStream(element: React.Node): stream$Readable;
declare function renderToStaticNodeStream(
element: React$Node
element: React.Node
): stream$Readable;
}
declare module 'react-dom/test-utils' {
import type {Component} from 'react';
declare interface Thenable {
then(resolve: () => mixed, reject?: () => mixed): mixed,
}
@@ -74,66 +75,66 @@ declare module 'react-dom/test-utils' {
};
declare function renderIntoDocument(
instance: React$Element<any>
): React$Component<any, any>;
instance: React.MixedElement
): Component<any, any>;
declare function mockComponent(
componentClass: React$ElementType,
componentClass: React.ElementType,
mockTagName?: string
): { [key: string]: mixed, ... };
declare function isElement(element: React$Element<any>): boolean;
declare function isElement(element: React.MixedElement): boolean;
declare function isElementOfType(
element: React$Element<any>,
componentClass: React$ElementType
element: React.MixedElement,
componentClass: React.ElementType
): boolean;
declare function isDOMComponent(instance: any): boolean;
declare function isCompositeComponent(
instance: React$Component<any, any>
instance: Component<any, any>
): boolean;
declare function isCompositeComponentWithType(
instance: React$Component<any, any>,
componentClass: React$ElementType
instance: Component<any, any>,
componentClass: React.ElementType
): boolean;
declare function findAllInRenderedTree(
tree: React$Component<any, any>,
test: (child: React$Component<any, any>) => boolean
): Array<React$Component<any, any>>;
tree: Component<any, any>,
test: (child: Component<any, any>) => boolean
): Array<Component<any, any>>;
declare function scryRenderedDOMComponentsWithClass(
tree: React$Component<any, any>,
tree: Component<any, any>,
className: string
): Array<Element>;
declare function findRenderedDOMComponentWithClass(
tree: React$Component<any, any>,
tree: Component<any, any>,
className: string
): ?Element;
declare function scryRenderedDOMComponentsWithTag(
tree: React$Component<any, any>,
tree: Component<any, any>,
tagName: string
): Array<Element>;
declare function findRenderedDOMComponentWithTag(
tree: React$Component<any, any>,
tree: Component<any, any>,
tagName: string
): ?Element;
declare function scryRenderedComponentsWithType(
tree: React$Component<any, any>,
componentClass: React$ElementType
): Array<React$Component<any, any>>;
tree: Component<any, any>,
componentClass: React.ElementType
): Array<Component<any, any>>;
declare function findRenderedComponentWithType(
tree: React$Component<any, any>,
componentClass: React$ElementType
): ?React$Component<any, any>;
tree: Component<any, any>,
componentClass: React.ElementType
): ?Component<any, any>;
declare function act(callback: () => void | Thenable): Thenable;
}
+45 -41
View File
@@ -1,50 +1,52 @@
// Type definitions for react-test-renderer 16.x.x
// Ported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer
type ReactComponentInstance = React$Component<any>;
type ReactTestRendererJSON = {
type: string,
props: { [propName: string]: any, ... },
children: null | ReactTestRendererJSON[],
...
};
type ReactTestRendererTree = ReactTestRendererJSON & {
nodeType: "component" | "host",
instance: ?ReactComponentInstance,
rendered: null | ReactTestRendererTree,
...
};
type ReactTestInstance = {
instance: ?ReactComponentInstance,
type: string,
props: { [propName: string]: any, ... },
parent: null | ReactTestInstance,
children: Array<ReactTestInstance | string>,
find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance,
findByType(type: React$ElementType): ReactTestInstance,
findByProps(props: { [propName: string]: any, ... }): ReactTestInstance,
findAll(
predicate: (node: ReactTestInstance) => boolean,
options?: { deep: boolean, ... }
): ReactTestInstance[],
findAllByType(
type: React$ElementType,
options?: { deep: boolean, ... }
): ReactTestInstance[],
findAllByProps(
props: { [propName: string]: any, ... },
options?: { deep: boolean, ... }
): ReactTestInstance[],
...
};
type TestRendererOptions = { createNodeMock(element: React.MixedElement): any, ... };
declare module "react-test-renderer" {
declare export type ReactTestRenderer = {
import type {Component as ReactComponent} from 'react';
type ReactComponentInstance = ReactComponent<any>;
export type ReactTestRendererJSON = {
type: string,
props: { [propName: string]: any, ... },
children: null | ReactTestRendererJSON[],
...
};
export type ReactTestRendererTree = ReactTestRendererJSON & {
nodeType: "component" | "host",
instance: ?ReactComponentInstance,
rendered: null | ReactTestRendererTree,
...
};
export type ReactTestInstance = {
instance: ?ReactComponentInstance,
type: string,
props: { [propName: string]: any, ... },
parent: null | ReactTestInstance,
children: Array<ReactTestInstance | string>,
find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance,
findByType(type: React.ElementType): ReactTestInstance,
findByProps(props: { [propName: string]: any, ... }): ReactTestInstance,
findAll(
predicate: (node: ReactTestInstance) => boolean,
options?: { deep: boolean, ... }
): ReactTestInstance[],
findAllByType(
type: React.ElementType,
options?: { deep: boolean, ... }
): ReactTestInstance[],
findAllByProps(
props: { [propName: string]: any, ... },
options?: { deep: boolean, ... }
): ReactTestInstance[],
...
};
export type ReactTestRenderer = {
toJSON(): null | ReactTestRendererJSON,
toTree(): null | ReactTestRendererTree,
unmount(nextElement?: React.MixedElement): void,
@@ -65,6 +67,8 @@ declare module "react-test-renderer" {
}
declare module "react-test-renderer/shallow" {
import type {ReactTestInstance} from 'react-test-renderer';
declare export default class ShallowRenderer {
static createRenderer(): ShallowRenderer;
getMountedInstance(): ReactTestInstance;
+3 -2
View File
@@ -49,7 +49,7 @@
"@babel/preset-env": "^7.25.3",
"@babel/preset-flow": "^7.24.7",
"@definitelytyped/dtslint": "^0.0.127",
"@jest/create-cache-key-function": "^29.6.3",
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/metro-babel-transformer": "0.79.0-main",
"@react-native/metro-config": "0.79.0-main",
"@tsconfig/node18": "1.0.1",
@@ -63,6 +63,7 @@
"chalk": "^4.0.0",
"clang-format": "^1.8.0",
"connect": "^3.6.5",
"debug": "^2.2.0",
"deep-equal": "1.1.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0",
@@ -83,7 +84,7 @@
"hermes-eslint": "0.25.1",
"hermes-transform": "0.25.1",
"inquirer": "^7.1.0",
"jest": "^29.6.3",
"jest": "^29.7.0",
"jest-diff": "^29.7.0",
"jest-junit": "^10.0.0",
"jest-snapshot": "^29.7.0",
@@ -32,11 +32,16 @@ abstract class GenerateCodegenSchemaTask : Exec() {
@get:InputFiles
val jsInputFiles =
project.fileTree(jsRootDir) {
it.include("**/*.js")
it.include("**/*.ts")
project.fileTree(jsRootDir) { tree ->
tree.include("**/*.js")
tree.include("**/*.jsx")
tree.include("**/*.ts")
tree.include("**/*.tsx")
tree.exclude("node_modules/**/*")
tree.exclude("**/*.d.ts")
// We want to exclude the build directory, to don't pick them up for execution avoidance.
it.exclude("**/build/**/*")
tree.exclude("**/build/**/*")
}
@get:OutputFile
@@ -27,16 +27,23 @@ class GenerateCodegenSchemaTaskTest {
val jsRootDir =
tempFolder.newFolder("js").apply {
File(this, "file.js").createNewFile()
File(this, "file.jsx").createNewFile()
File(this, "file.ts").createNewFile()
File(this, "file.tsx").createNewFile()
File(this, "ignore.txt").createNewFile()
}
val task = createTestTask<GenerateCodegenSchemaTask> { it.jsRootDir.set(jsRootDir) }
assertThat(task.jsInputFiles.dir).isEqualTo(jsRootDir)
assertThat(task.jsInputFiles.includes).isEqualTo(setOf("**/*.js", "**/*.ts"))
assertThat(task.jsInputFiles.includes)
.isEqualTo(setOf("**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"))
assertThat(task.jsInputFiles.files)
.containsExactlyInAnyOrder(File(jsRootDir, "file.js"), File(jsRootDir, "file.ts"))
.containsExactlyInAnyOrder(
File(jsRootDir, "file.js"),
File(jsRootDir, "file.jsx"),
File(jsRootDir, "file.ts"),
File(jsRootDir, "file.tsx"))
}
@Test
@@ -60,12 +67,15 @@ class GenerateCodegenSchemaTaskTest {
.createFileAndPath()
File(this, "afolder/build/intermediates/sourcemaps/react/anotherfolder/excludedfile.js")
.createFileAndPath()
File(this, "node_modules/excludedfile.js").createFileAndPath()
File(this, "afolder/excludedfile.d.ts").createFileAndPath()
}
val task = createTestTask<GenerateCodegenSchemaTask> { it.jsRootDir.set(jsRootDir) }
assertThat(task.jsInputFiles.dir).isEqualTo(jsRootDir)
assertThat(task.jsInputFiles.excludes).isEqualTo(setOf("**/build/**/*"))
assertThat(task.jsInputFiles.excludes)
.isEqualTo(setOf("node_modules/**/*", "**/*.d.ts", "**/build/**/*"))
assertThat(task.jsInputFiles.files).containsExactly(File(jsRootDir, "afolder/includedfile.js"))
}
+1 -1
View File
@@ -26,7 +26,7 @@
"chalk": "^4.1.2",
"commander": "^12.0.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"jest": "^29.7.0",
"listr2": "^8.2.1",
"react-test-renderer": "19.0.0",
"rxjs": "^7.8.1"
-7
View File
@@ -198,13 +198,6 @@ async function sendReview(
return;
}
if (process.env.CIRCLE_CI) {
console.error(
'Code analysis found issues, but the review cannot be posted to GitHub without an access token.',
);
process.exit(1);
}
let results = body + '\n';
comments.forEach(comment => {
results +=
@@ -33,7 +33,7 @@
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@types/jest": "^29.5.3",
"jest": "^29.6.3",
"jest": "^29.7.0",
"rimraf": "^3.0.2"
},
"peerDependencies": {
@@ -20,6 +20,7 @@ const rnTesterConfig = getDefaultConfig(
const JS_DIR = process.env.JS_DIR
? path.resolve(process.cwd(), process.env.JS_DIR)
: null;
const NODE_MODULES = path.sep + 'node_modules' + path.sep;
const config = {
projectRoot: path.resolve(__dirname, '../../..'),
@@ -28,12 +29,22 @@ const config = {
},
resolver: {
blockList: /\/RendererProxy\.fb\.js$/, // Disable dependency injection for the renderer
disableHierarchicalLookup: !!JS_DIR,
sourceExts: ['fb.js', ...rnTesterConfig.resolver.sourceExts],
nodeModulesPaths: JS_DIR
? [path.join(JS_DIR, 'public', 'node_modules')]
: [],
hasteImplModulePath: path.resolve(__dirname, 'hasteImpl.js'),
resolveRequest: JS_DIR
? (ctx, dep, platform) =>
ctx.originModulePath.includes(NODE_MODULES)
? ctx.resolveRequest(ctx, dep, platform)
: // Disable hierarchical node_modules lookup from 1P code.
ctx.resolveRequest(
{...ctx, disableHierarchicalLookup: true},
dep,
platform,
)
: null,
},
transformer: {
// We need to wrap the default transformer so we can run it from source
@@ -43,8 +54,7 @@ const config = {
watchFolders: JS_DIR
? [
path.join(JS_DIR, 'RKJSModules', 'vendor', 'react'),
path.join(JS_DIR, 'tools', 'metro'),
path.join(JS_DIR, 'node_modules'),
path.join(JS_DIR, 'tools', 'metro', 'packages', 'metro-runtime'),
path.join(JS_DIR, 'public', 'node_modules'),
]
: [],
+128 -14
View File
@@ -230,10 +230,7 @@ class Expect {
}
}
toBeCalled(): void {
return this.toHaveBeenCalled();
}
toBeCalled: () => void;
toHaveBeenCalled(): void {
const mock = this.#requireMock();
const pass = mock.calls.length > 0;
@@ -244,10 +241,7 @@ class Expect {
}
}
toBeCalledTimes(times: number): void {
return this.toHaveBeenCalledTimes(times);
}
toBeCalledTimes: (times: number) => void;
toHaveBeenCalledTimes(times: number): void {
const mock = this.#requireMock();
const pass = mock.calls.length === times;
@@ -258,22 +252,63 @@ class Expect {
}
}
toBeCalledWith(...args: mixed[]): void {
return this.toHaveBeenCalledWith(...args);
}
toHaveBeenCalledWith(...args: mixed[]): void {
toBeCalledWith: (...args: Array<mixed>) => void;
toHaveBeenCalledWith(...args: Array<mixed>): void {
const mock = this.#requireMock();
const pass = mock.calls.some(callArgs =>
deepEqual(callArgs, args, {strict: true}),
);
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called with ${stringify(args)}, but it was called with ${stringify(mock.calls)}`,
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called with ${stringify(
args,
)}, but it was called with ${stringify(mock.calls)}`,
).blameToPreviousFrame();
}
}
lastCalledWith: (...args: Array<mixed>) => void;
toHaveBeenLastCalledWith(...args: mixed[]): void {
const mock = this.#requireMock();
if (mock.calls.length === 0) {
if (this.#isNot) {
return;
}
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)} to have been last called with ${stringify(args)}, but it was not called a single time.`,
);
}
const pass = deepEqual(mock.lastCall, args, {strict: true});
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been last called with ${stringify(args)}, but it was last called with ${stringify(mock.lastCall)}.`,
);
}
}
nthCalledWith: (index: number, ...args: mixed[]) => void;
toHaveBeenNthCalledWith(index: number, ...args: mixed[]): void {
if (index < 1) {
throw new ErrorWithCustomBlame(
`Expected index to be positive number, got ${index}.`,
).blameToPreviousFrame();
}
const mock = this.#requireMock();
if (this.#isNot && mock.calls.length < index) {
return;
}
const pass = deepEqual(mock.calls[index - 1], args, {strict: true});
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been nth(${index}) called with ${stringify(args)}, but it was called with ${stringify(mock.calls[index - 1])}.`,
);
}
}
toBeGreaterThan(expected: number): void {
if (typeof this.#received !== 'number') {
throw new ErrorWithCustomBlame(
@@ -358,6 +393,70 @@ class Expect {
}
}
toContain(item: mixed): void {
if (typeof this.#received === 'string') {
if (typeof item !== 'string') {
throw new ErrorWithCustomBlame(
`Expected ${String(item)} to be a string but it was a ${typeof item}`,
).blameToPreviousFrame();
}
const pass = this.#received.includes(item);
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to contain ${item}`,
).blameToPreviousFrame();
}
return;
}
if (!Array.isArray(this.#received)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)} to be an array`,
).blameToPreviousFrame();
}
const pass = this.#received.includes(item);
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to contain ${String(item)}`,
).blameToPreviousFrame();
}
}
toContainEqual(item: mixed): void {
if (typeof this.#received === 'string') {
if (typeof item !== 'string') {
throw new ErrorWithCustomBlame(
`Expected ${String(item)} to be a string but it was a ${typeof item}`,
).blameToPreviousFrame();
}
const pass = this.#received.includes(item);
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to contain ${item}`,
).blameToPreviousFrame();
}
return;
}
if (!Array.isArray(this.#received)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)} to be an array`,
).blameToPreviousFrame();
}
const pass = this.#received.some(value =>
deepEqual(value, item, {strict: true}),
);
if (!this.#isExpectedResult(pass)) {
throw new ErrorWithCustomBlame(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to contain item equal to ${String(item)}`,
).blameToPreviousFrame();
}
}
toMatchSnapshot(expected?: string): void {
if (this.#isNot) {
throw new ErrorWithCustomBlame(
@@ -399,6 +498,21 @@ class Expect {
}
}
/**
* Base methods can't be implemented as an arrow function because they
* will not be added to the prototype.
*/
// $FlowExpectedError[method-unbinding]
Expect.prototype.toBeCalled = Expect.prototype.toHaveBeenCalled;
// $FlowExpectedError[method-unbinding]
Expect.prototype.toBeCalledTimes = Expect.prototype.toHaveBeenCalledTimes;
// $FlowExpectedError[method-unbinding]
Expect.prototype.toBeCalledWith = Expect.prototype.toHaveBeenCalledWith;
// $FlowExpectedError[method-unbinding]
Expect.prototype.lastCalledWith = Expect.prototype.toHaveBeenLastCalledWith;
// $FlowExpectedError[method-unbinding]
Expect.prototype.nthCalledWith = Expect.prototype.toHaveBeenNthCalledWith;
const expect: mixed => Expect = (received: mixed) => new Expect(received);
export default expect;
+62 -24
View File
@@ -25,12 +25,23 @@ type SuiteOptions = $ReadOnly<{
minWarmupDuration?: number,
minWarmupIterations?: number,
disableOptimizedBuildCheck?: boolean,
testOnly?: boolean,
}>;
type TestOptions = $ReadOnly<{
...FnOptions,
only?: boolean,
}>;
type SuiteResults = Array<$ReadOnly<TaskResult>>;
interface TestFunction {
(name: string, fn: () => void, options?: FnOptions): SuiteAPI;
only: (name: string, fn: () => void, options?: FnOptions) => SuiteAPI;
}
interface SuiteAPI {
add(name: string, fn: () => void, options?: FnOptions): SuiteAPI;
+test: TestFunction;
verify(fn: (results: SuiteResults) => void): SuiteAPI;
}
@@ -41,7 +52,7 @@ export function suite(
const tasks: Array<{
name: string,
fn: () => void,
options: FnOptions | void,
options: TestOptions | void,
}> = [];
const verifyFns = [];
@@ -56,12 +67,13 @@ export function suite(
// no point in running the benchmark.
// We still run a single iteration of each test just to make sure that the
// logic in the benchmark doesn't break.
const isTestOnly = isRunningFromCI && verifyFns.length === 0;
const isTestOnly =
suiteOptions.testOnly === true ||
(isRunningFromCI && verifyFns.length === 0);
const benchOptions: BenchOptions = isTestOnly
? {
warmupIterations: 1,
warmupTime: 0,
warmup: false,
iterations: 1,
time: 0,
}
@@ -71,30 +83,39 @@ export function suite(
benchOptions.throws = true;
benchOptions.now = () => NativeCPUTime.getCPUTimeNanos() / 1000000;
if (suiteOptions.minIterations != null) {
benchOptions.iterations = suiteOptions.minIterations;
}
if (!isTestOnly) {
if (suiteOptions.minIterations != null) {
benchOptions.iterations = suiteOptions.minIterations;
}
if (suiteOptions.minDuration != null) {
benchOptions.time = suiteOptions.minDuration;
}
if (suiteOptions.minDuration != null) {
benchOptions.time = suiteOptions.minDuration;
}
if (suiteOptions.warmup != null) {
benchOptions.warmup = suiteOptions.warmup;
}
if (suiteOptions.warmup != null) {
benchOptions.warmup = suiteOptions.warmup;
}
if (suiteOptions.minWarmupDuration != null) {
benchOptions.warmupTime = suiteOptions.minWarmupDuration;
}
if (suiteOptions.minWarmupDuration != null) {
benchOptions.warmupTime = suiteOptions.minWarmupDuration;
}
if (suiteOptions.minWarmupIterations != null) {
benchOptions.warmupIterations = suiteOptions.minWarmupIterations;
if (suiteOptions.minWarmupIterations != null) {
benchOptions.warmupIterations = suiteOptions.minWarmupIterations;
}
}
const bench = new Bench(benchOptions);
const isFocused = tasks.find(task => task.options?.only === true) != null;
for (const task of tasks) {
bench.add(task.name, task.fn, task.options);
if (isFocused && task.options?.only !== true) {
continue;
}
const {only, ...options} = task.options ?? {};
bench.add(task.name, task.fn, options);
}
bench.runSync();
@@ -116,13 +137,30 @@ export function suite(
if (__DEV__ && suiteOptions.disableOptimizedBuildCheck !== true) {
throw new Error('Benchmarks should not be run in development mode');
}
if (isFocused) {
throw new Error(
'Failing focused test to prevent it from being committed',
);
}
});
const test = (
name: string,
fn: () => void,
options?: FnOptions,
): SuiteAPI => {
tasks.push({name, fn, options});
return suiteAPI;
};
test.only = (name: string, fn: () => void, options?: FnOptions): SuiteAPI => {
tasks.push({name, fn, options: {...options, only: true}});
return suiteAPI;
};
const suiteAPI = {
add(name: string, fn: () => void, options?: FnOptions): SuiteAPI {
tasks.push({name, fn, options});
return suiteAPI;
},
test,
verify(fn: (results: SuiteResults) => void): SuiteAPI {
verifyFns.push(fn);
return suiteAPI;
+33 -5
View File
@@ -381,7 +381,7 @@ describe('Fantom', () => {
});
});
describe('runOnUIThread + dispatchNativeEvent', () => {
describe('runOnUIThread + enqueueNativeEvent', () => {
it('sends event without payload', () => {
const root = Fantom.createRoot();
let maybeNode;
@@ -404,7 +404,7 @@ describe('Fantom', () => {
expect(focusEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'focus');
Fantom.enqueueNativeEvent(element, 'focus');
});
// The tasks have not run.
@@ -437,7 +437,7 @@ describe('Fantom', () => {
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'change', {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
});
});
@@ -470,13 +470,13 @@ describe('Fantom', () => {
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'scroll', {
Fantom.enqueueNativeEvent(element, 'scroll', {
contentOffset: {
x: 0,
y: 1,
},
});
Fantom.dispatchNativeEvent(
Fantom.enqueueNativeEvent(
element,
'scroll',
{
@@ -501,6 +501,34 @@ describe('Fantom', () => {
});
});
describe('dispatchNativeEvent', () => {
it('flushes the event and runs the work loop', () => {
const root = Fantom.createRoot();
let maybeNode;
let focusEvent = jest.fn();
Fantom.runTask(() => {
root.render(
<TextInput
onFocus={focusEvent}
ref={node => {
maybeNode = node;
}}
/>,
);
});
const element = ensureInstance(maybeNode, ReactNativeElement);
expect(focusEvent).toHaveBeenCalledTimes(0);
Fantom.dispatchNativeEvent(element, 'focus');
expect(focusEvent).toHaveBeenCalledTimes(1);
});
});
describe('scrollTo', () => {
it('throws error if called on node that is not scroll view', () => {
const root = Fantom.createRoot();
@@ -0,0 +1,35 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import Fantom from '../..';
let runs = 0;
// We need to use `afterAll` because the benchmark API defines tests in Jest,
// and we can't call it from within other tests.
afterAll(() => {
expect(runs).toBe(1);
});
Fantom.unstable_benchmark
.suite('Benchmark test', {
testOnly: true,
// Ignores warmup, iterations and duration
warmup: true,
minWarmupIterations: 10,
minIterations: 10,
minDuration: 1000,
minWarmupDuration: 1000,
})
.test('test', () => {
runs++;
});
@@ -358,6 +358,100 @@ describe('expect', () => {
}),
);
['lastCalledWith', 'toHaveBeenLastCalledWith'].map(
toHaveBeenLastCalledWithAlias =>
test(toHaveBeenLastCalledWithAlias, () => {
const fn = jest.fn();
expect(fn).not[toHaveBeenLastCalledWithAlias]();
expect(fn).not[toHaveBeenLastCalledWithAlias]({});
expect(() => {
expect(fn)[toHaveBeenLastCalledWithAlias]();
}).toThrow();
fn('happy');
expect(fn)[toHaveBeenLastCalledWithAlias]('happy');
expect(fn).not[toHaveBeenLastCalledWithAlias]();
fn();
expect(fn)[toHaveBeenLastCalledWithAlias]();
expect(fn).not[toHaveBeenLastCalledWithAlias]('happy');
fn({a: 1}, 2);
expect(fn)[toHaveBeenLastCalledWithAlias]({a: 1}, 2);
expect(fn).not[toHaveBeenLastCalledWithAlias]();
expect(fn).not[toHaveBeenLastCalledWithAlias]({a: 1});
expect(fn).not[toHaveBeenLastCalledWithAlias]({a: 2}, 2);
expect(fn).not[toHaveBeenLastCalledWithAlias]({a: 1}, 2, undefined);
expect(() => {
expect(fn).not[toHaveBeenLastCalledWithAlias]({a: 1}, 2);
}).toThrow();
expect(() => {
expect(fn)[toHaveBeenLastCalledWithAlias](1);
}).toThrow();
// Passing functions that aren't mocks should always fail
expect(() => {
expect(() => {})[toHaveBeenLastCalledWithAlias]();
}).toThrow();
expect(() => {
expect(() => {}).not[toHaveBeenLastCalledWithAlias]();
}).toThrow();
}),
);
['nthCalledWith', 'toHaveBeenNthCalledWith'].map(
toHaveBeenNthCalledWithAlias =>
test(toHaveBeenNthCalledWithAlias, () => {
const fn = jest.fn();
expect(fn).not[toHaveBeenNthCalledWithAlias](1);
expect(fn).not[toHaveBeenNthCalledWithAlias](1, {});
expect(() => {
expect(fn)[toHaveBeenNthCalledWithAlias](0);
}).toThrow();
expect(() => {
expect(fn)[toHaveBeenNthCalledWithAlias](1);
}).toThrow();
fn('happy');
fn();
fn({a: 1}, 2);
expect(fn)[toHaveBeenNthCalledWithAlias](1, 'happy');
expect(fn)[toHaveBeenNthCalledWithAlias](2);
expect(fn)[toHaveBeenNthCalledWithAlias](3, {a: 1}, 2);
expect(fn).not[toHaveBeenNthCalledWithAlias](1);
expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 1});
expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 2}, 2);
expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 1}, 2, undefined);
expect(() => {
expect(fn).not[toHaveBeenNthCalledWithAlias](3, {a: 1}, 2);
}).toThrow();
expect(() => {
expect(fn)[toHaveBeenNthCalledWithAlias](1);
}).toThrow();
// Passing functions that aren't mocks should always fail
expect(() => {
expect(() => {})[toHaveBeenNthCalledWithAlias](1);
}).toThrow();
expect(() => {
expect(() => {}).not[toHaveBeenNthCalledWithAlias](1);
}).toThrow();
}),
);
describe('jest.fn()', () => {
it('tracks execution of functions without implementations', () => {
const fn = jest.fn();
@@ -661,6 +755,46 @@ describe('expect', () => {
}).toThrow();
});
test('toContain', () => {
expect('hello').toContain('he');
expect('hello').not.toContain('lol');
expect([1, 2, 3]).toContain(1);
expect([1, 2, 3]).not.toContain(4);
const obj = {a: 1};
expect([obj, {a: 2}, {a: 3}]).toContain(obj);
expect([obj]).not.toContain({a: 1});
expect(() => {
expect([]).toContain(obj);
}).toThrow();
expect(() => {
expect('hello').not.toContain('e');
}).toThrow();
});
test('toContainEqual', () => {
expect('hello').toContainEqual('he');
expect('hello').not.toContainEqual('lol');
expect([1, 2, 3]).toContainEqual(1);
expect([1, 2, 3]).not.toContainEqual(4);
const obj = {a: 1};
expect([obj, {a: 2}, {a: 3}]).toContainEqual(obj);
expect([obj]).toContainEqual({a: 1});
expect([[obj]]).toContainEqual([{a: 1}]);
expect([obj]).not.toContainEqual({a: 2});
expect(() => {
expect([]).toContainEqual(obj);
}).toThrow();
expect(() => {
expect([{a: 1}]).not.toContainEqual({a: 1});
}).toThrow();
});
describe('toMatchSnapshot()', () => {
test('primitive types', () => {
expect(undefined).toMatchSnapshot();
+29 -2
View File
@@ -13,10 +13,12 @@ import type {
RenderOutputConfig,
} from './getFantomRenderedOutput';
import type {MixedElement} from 'react';
import type {RootTag} from 'react-native/Libraries/ReactNative/RootTag';
import ReactNativeElement from '../../react-native/src/private/webapis/dom/nodes/ReadOnlyNode';
import * as Benchmark from './Benchmark';
import getFantomRenderedOutput from './getFantomRenderedOutput';
import {createRootTag} from 'react-native/Libraries/ReactNative/RootTag';
import ReactFabric from 'react-native/Libraries/Renderer/shims/ReactFabric';
import NativeFantom, {
NativeEventCategory,
@@ -91,6 +93,10 @@ class Root {
return getFantomRenderedOutput(this.#surfaceId, config);
}
getRootTag(): RootTag {
return createRootTag(this.#surfaceId);
}
// TODO: add an API to check if all surfaces were deallocated when tests are finished.
}
@@ -166,14 +172,21 @@ function createRoot(rootConfig?: RootConfig): Root {
return new Root(rootConfig);
}
function dispatchNativeEvent(
/**
* This is a low level method to enqueue a native event to a node.
* It does not wait for it to be flushed in the UI thread or for it to be
* processed by JS.
*
* For a higher level API, use `dispatchNativeEvent`.
*/
function enqueueNativeEvent(
node: ReactNativeElement,
type: string,
payload?: {[key: string]: mixed},
options?: {category?: NativeEventCategory, isUnique?: boolean},
) {
const shadowNode = getNativeNodeReference(node);
NativeFantom.dispatchNativeEvent(
NativeFantom.enqueueNativeEvent(
shadowNode,
type,
payload,
@@ -182,6 +195,19 @@ function dispatchNativeEvent(
);
}
function dispatchNativeEvent(
node: ReactNativeElement,
type: string,
payload?: {[key: string]: mixed},
options?: {category?: NativeEventCategory, isUnique?: boolean},
) {
runOnUIThread(() => {
enqueueNativeEvent(node, type, payload, options);
});
runWorkLoop();
}
function scrollTo(
node: ReactNativeElement,
options: {x: number, y: number, zoomScale?: number},
@@ -287,6 +313,7 @@ export default {
runWorkLoop,
createRoot,
dispatchNativeEvent,
enqueueNativeEvent,
flushAllNativeEvents,
unstable_benchmark: Benchmark,
scrollTo,
@@ -7,8 +7,8 @@
plugins {
id("com.facebook.react")
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
@@ -12,8 +12,9 @@ package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface AndroidPopupMenuManagerInterface<T extends View> {
public interface AndroidPopupMenuManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setMenuItems(T view, @Nullable ReadableArray value);
void show(T view);
}
@@ -6,6 +6,7 @@
"files": [
"js",
"android",
"react-native.config.js",
"!android/build",
"!**/__tests__",
"!**/__fixtures__",
@@ -15,6 +16,9 @@
"react-native",
"android"
],
"scripts": {
"prepublishOnly": "node ./scripts/prepublish-popup-menu-android.js"
},
"license": "MIT",
"devDependencies": {
"@react-native/codegen": "0.79.0-main"
@@ -0,0 +1,46 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
* This script is used to update the android/build.gradle.kts file
* with the versions from the libs.versions.toml file.
*
* This is needed because this package is consumed from source from
* external users and we don't want to have several SDK version around to
* maintain.
*
* It's invoked as a prepublish script for this package.
*/
function extractVersion(tomlContent, regex) {
const match = tomlContent.match(regex);
return match && match[1] ? match[1] : null;
}
const fs = require('fs');
const buildGradleKtsPath = 'android/build.gradle.kts';
const libsVersionsTomlPath = '../react-native/gradle/libs.versions.toml';
console.log(`Updating ${buildGradleKtsPath} with versions from ${libsVersionsTomlPath}...`);
let gradleContent = fs.readFileSync(buildGradleKtsPath, 'utf8');
const tomlContent = fs.readFileSync(libsVersionsTomlPath, 'utf8');
const compileSdk = extractVersion(tomlContent, /compileSdk\s*=\s*"(\d+)"/);
const minSdk = extractVersion(tomlContent, /minSdk\s*=\s*"(\d+)"/);
const buildTools = extractVersion(tomlContent, /buildTools\s*=\s*"([\d.]+)"/);
gradleContent = gradleContent
.replace('libs.versions.compileSdk.get().toInt()', compileSdk)
.replace('libs.versions.minSdk.get().toInt()', minSdk)
.replace('libs.versions.buildTools.get()', `"${buildTools}"`)
.replace('project(":packages:react-native:ReactAndroid")', '"com.facebook.react:react-android"');
fs.writeFileSync(buildGradleKtsPath, gradleContent);
console.log('Done!');
@@ -12,8 +12,9 @@ package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface SampleNativeComponentManagerInterface<T extends View> {
public interface SampleNativeComponentManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setOpacity(T view, float value);
void setValues(T view, @Nullable ReadableArray value);
void changeBackgroundColor(T view, String color);
@@ -1,62 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
export type AlertType =
| 'default'
| 'plain-text'
| 'secure-text'
| 'login-password';
export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
export type AlertButton = {
text?: string,
onPress?: ?((value?: string) => any) | ?Function,
isPreferred?: boolean,
style?: AlertButtonStyle,
...
};
export type Buttons = Array<AlertButton>;
export type AlertOptions = {
/** @platform android */
cancelable?: ?boolean,
userInterfaceStyle?: 'unspecified' | 'light' | 'dark',
/** @platform android */
onDismiss?: ?() => void,
...
};
/**
* Launches an alert dialog with the specified title and message.
*
* See https://reactnative.dev/docs/alert
*/
declare class Alert {
static alert(
title: ?string,
message?: ?string,
buttons?: Buttons,
options?: AlertOptions,
): void;
static prompt(
title: ?string,
message?: ?string,
callbackOrButtons?: ?(((text: string) => void) | Buttons),
type?: ?AlertType,
defaultValue?: string,
keyboardType?: string,
options?: AlertOptions,
): void;
}
export default Alert;
+51 -5
View File
@@ -9,18 +9,61 @@
*/
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
import type {AlertOptions, AlertType, Buttons} from './Alert.flow';
import Platform from '../Utilities/Platform';
import RCTAlertManager from './RCTAlertManager';
export type * from './Alert.flow';
/**
* @platform ios
*/
export type AlertType =
| 'default'
| 'plain-text'
| 'secure-text'
| 'login-password';
/**
* @platform ios
*/
export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
export type AlertButton = {
text?: string,
onPress?: ?((value?: string) => any) | ?Function,
isPreferred?: boolean,
style?: AlertButtonStyle,
...
};
export type AlertButtons = Array<AlertButton>;
export type AlertOptions = {
/** @platform android */
cancelable?: ?boolean,
userInterfaceStyle?: 'unspecified' | 'light' | 'dark',
/** @platform android */
onDismiss?: ?() => void,
...
};
/**
* Launches an alert dialog with the specified title and message.
*
* Optionally provide a list of buttons. Tapping any button will fire the
* respective onPress callback and dismiss the alert. By default, the only
* button will be an 'OK' button.
*
* This is an API that works both on iOS and Android and can show static
* alerts. On iOS, you can show an alert that prompts the user to enter
* some information.
*
* See https://reactnative.dev/docs/alert
*/
class Alert {
static alert(
title: ?string,
message?: ?string,
buttons?: Buttons,
buttons?: AlertButtons,
options?: AlertOptions,
): void {
if (Platform.OS === 'ios') {
@@ -53,7 +96,7 @@ class Alert {
// At most three buttons (neutral, negative, positive). Ignore rest.
// The text 'OK' should be probably localized. iOS Alert does that in native.
const defaultPositiveText = 'OK';
const validButtons: Buttons = buttons
const validButtons: AlertButtons = buttons
? buttons.slice(0, 3)
: [{text: defaultPositiveText}];
const buttonPositive = validButtons.pop();
@@ -93,10 +136,13 @@ class Alert {
}
}
/**
* @platform ios
*/
static prompt(
title: ?string,
message?: ?string,
callbackOrButtons?: ?(((text: string) => void) | Buttons),
callbackOrButtons?: ?(((text: string) => void) | AlertButtons),
type?: ?AlertType = 'plain-text',
defaultValue?: string,
keyboardType?: string,
@@ -1,20 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {Args} from './NativeAlertManager';
declare const RCTAlertManager: {
alertWithArgs(
args: Args,
callback: (id: number, value: string) => void,
): void,
};
export default RCTAlertManager;
@@ -228,6 +228,21 @@ using namespace facebook::react;
};
}
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) {
configuration.loadSourceForBridgeWithProgress =
^(RCTBridge *_Nonnull bridge,
RCTSourceLoadProgressBlock _Nonnull onProgress,
RCTSourceLoadBlock _Nonnull loadCallback) {
[weakSelf.delegate loadSourceForBridge:bridge onProgress:onProgress onComplete:loadCallback];
};
}
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) {
configuration.loadSourceForBridge = ^(RCTBridge *_Nonnull bridge, RCTSourceLoadBlock _Nonnull loadCallback) {
[weakSelf.delegate loadSourceForBridge:bridge withBlock:loadCallback];
};
}
return [[RCTRootViewFactory alloc] initWithTurboModuleDelegate:self hostDelegate:self configuration:configuration];
}
@@ -31,6 +31,11 @@ typedef NSURL *_Nullable (^RCTBundleURLBlock)(void);
typedef NSArray<id<RCTBridgeModule>> *_Nonnull (^RCTExtraModulesForBridgeBlock)(RCTBridge *bridge);
typedef NSDictionary<NSString *, Class> *_Nonnull (^RCTExtraLazyModuleClassesForBridge)(RCTBridge *bridge);
typedef BOOL (^RCTBridgeDidNotFindModuleBlock)(RCTBridge *bridge, NSString *moduleName);
typedef void (^RCTLoadSourceForBridgeWithProgressBlock)(
RCTBridge *bridge,
RCTSourceLoadProgressBlock onProgress,
RCTSourceLoadBlock loadCallback);
typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBlock loadCallback);
#pragma mark - RCTRootViewFactory Configuration
@interface RCTRootViewFactoryConfiguration : NSObject
@@ -145,6 +150,19 @@ typedef BOOL (^RCTBridgeDidNotFindModuleBlock)(RCTBridge *bridge, NSString *modu
*/
@property (nonatomic, nullable) RCTBridgeDidNotFindModuleBlock bridgeDidNotFindModule;
/**
* The bridge will automatically attempt to load the JS source code from the
* location specified by the `sourceURLForBridge:` method, however, if you want
* to handle loading the JS yourself, you can do so by setting this property.
*/
@property (nonatomic, nullable) RCTLoadSourceForBridgeWithProgressBlock loadSourceForBridgeWithProgress;
/**
* Similar to loadSourceForBridgeWithProgress but without progress
* reporting.
*/
@property (nonatomic, nullable) RCTLoadSourceForBridgeBlock loadSourceForBridge;
@end
#pragma mark - RCTRootViewFactory
@@ -302,6 +302,22 @@
return NO;
}
- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback
{
if (_configuration.loadSourceForBridge != nil) {
_configuration.loadSourceForBridge(bridge, loadCallback);
}
}
- (void)loadSourceForBridge:(RCTBridge *)bridge
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback
{
if (_configuration.loadSourceForBridgeWithProgress != nil) {
_configuration.loadSourceForBridgeWithProgress(bridge, onProgress, loadCallback);
}
}
- (NSURL *)bundleURL
{
return self->_configuration.bundleURLBlock();
@@ -39,7 +39,7 @@ describe('onScroll', () => {
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(
Fantom.enqueueNativeEvent(
element,
'scroll',
{
@@ -85,13 +85,13 @@ describe('onScroll', () => {
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'scroll', {
Fantom.enqueueNativeEvent(element, 'scroll', {
contentOffset: {
x: 0,
y: 1,
},
});
Fantom.dispatchNativeEvent(
Fantom.enqueueNativeEvent(
element,
'scroll',
{
@@ -0,0 +1,660 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
* @fantom_flags enableAccessToHostTreeInFabric:true
* @fantom_flags enableViewCulling:true
* @fantom_flags enableSynchronousStateUpdates:true
*/
import '../../../Core/InitializeCore.js';
import ensureInstance from '../../../../src/private/utilities/ensureInstance';
import ReactNativeElement from '../../../../src/private/webapis/dom/nodes/ReactNativeElement';
import View from '../../View/View';
import ScrollView from '../ScrollView';
import Fantom from '@react-native/fantom';
import * as React from 'react';
test('basic culling', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
let maybeNode;
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
ref={node => {
maybeNode = node;
}}>
<View
nativeID={'child'}
style={{height: 10, width: 10, marginTop: 45}}
/>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 60,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Delete {type: "View", nativeID: "child"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Delete {type: "View", nativeID: (N/A)}',
'Update {type: "ScrollView", nativeID: (N/A)}',
]);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 0,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
]);
});
test('recursive culling', () => {
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
let maybeNode;
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
ref={node => {
maybeNode = node;
}}>
<View
nativeID={'element A'}
style={{height: 30, width: 30, marginTop: 25}}>
<View nativeID={'child AA'} style={{height: 10, width: 10}} />
<View
nativeID={'child AB'}
style={{height: 10, width: 10, marginTop: 5}}
/>
</View>
<View
nativeID={'element B'}
style={{height: 30, width: 30, marginTop: 195}}>
<View nativeID={'child BA'} style={{height: 10, width: 10}} />
<View
nativeID={'child BB'}
style={{height: 10, width: 10, marginTop: 5}}
/>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element A"}',
'Create {type: "View", nativeID: "child AA"}',
'Create {type: "View", nativeID: "child AB"}',
'Insert {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
'Insert {type: "View", parentNativeID: "element A", index: 1, nativeID: "child AB"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
// === Scroll down to the edge of child AA ===
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 30,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
]);
// === Scroll down past child AA ===
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 36,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
'Delete {type: "View", nativeID: "child AA"}',
]);
// === Scroll down past child AB ===
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 51,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AB"}',
'Delete {type: "View", nativeID: "child AB"}',
]);
// === Scroll down past element A ===
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 56,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Delete {type: "View", nativeID: "element A"}',
]);
// Scroll element B into viewport. Just child BA should be created.
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 155,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element B"}',
'Create {type: "View", nativeID: "child BA"}',
'Insert {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
]);
// Scroll child BA into viewport.
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 165,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child BB"}',
'Insert {type: "View", parentNativeID: "element B", index: 1, nativeID: "child BB"}',
]);
// Scroll back to start
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 0,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: "element B", index: 1, nativeID: "child BB"}',
'Remove {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
'Delete {type: "View", nativeID: "child BA"}',
'Delete {type: "View", nativeID: "child BB"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
'Delete {type: "View", nativeID: "element B"}',
'Create {type: "View", nativeID: "element A"}',
'Create {type: "View", nativeID: "child AA"}',
'Create {type: "View", nativeID: "child AB"}',
'Insert {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
'Insert {type: "View", parentNativeID: "element A", index: 1, nativeID: "child AB"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
]);
// Scroll past element A
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 85,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: "element A", index: 1, nativeID: "child AB"}',
'Remove {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
'Delete {type: "View", nativeID: "child AA"}',
'Delete {type: "View", nativeID: "child AB"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Delete {type: "View", nativeID: "element A"}',
]);
});
test('recursive culling when initial offset is negative', () => {
const root = Fantom.createRoot({viewportHeight: 874, viewportWidth: 402});
let maybeNode;
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 874, width: 402}}
contentOffset={{x: 0, y: -10000}}
ref={node => {
maybeNode = node;
}}>
<View
nativeID={'child A'}
style={{height: 100, width: 100, marginTop: 235}}
/>
<View
nativeID={'child B'}
style={{height: 100, width: 100, marginTop: 235}}>
<View nativeID={'child BA'} style={{height: 17, width: 100}} />
<View
nativeID={'child BB'}
style={{height: 17, width: 100, marginTop: 60}}
/>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 0,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child A"}',
'Create {type: "View", nativeID: "child B"}',
'Create {type: "View", nativeID: "child BA"}',
'Create {type: "View", nativeID: "child BB"}',
'Insert {type: "View", parentNativeID: "child B", index: 0, nativeID: "child BA"}',
'Insert {type: "View", parentNativeID: "child B", index: 1, nativeID: "child BB"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 1, nativeID: "child B"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
]);
});
test('deep nesting', () => {
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
let maybeNode;
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
ref={node => {
maybeNode = node;
}}>
<View
nativeID={'element A'}
style={{height: 10, width: 100, marginTop: 30}}
/>
<View
nativeID={'element B'}
style={{height: 50, width: 100, marginTop: 85}}>
<View
nativeID={'child BA'}
style={{height: 30, width: 80, marginTop: 10, marginLeft: 10}}>
<View
nativeID={'child BAA'}
style={{height: 10, width: 75, marginTop: 5, marginLeft: 5}}
/>
<View
nativeID={'child BAB'}
style={{height: 10, width: 75, marginTop: 15, marginLeft: 5}}
/>
</View>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 40,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element B"}',
'Create {type: "View", nativeID: "child BA"}',
'Create {type: "View", nativeID: "child BAA"}',
'Insert {type: "View", parentNativeID: "child BA", index: 0, nativeID: "child BAA"}',
'Insert {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
'Insert {type: "View", parentNativeID: (N/A), index: 1, nativeID: "element B"}',
]);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 150,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Delete {type: "View", nativeID: "element A"}',
'Create {type: "View", nativeID: "child BAB"}',
'Insert {type: "View", parentNativeID: "child BA", index: 1, nativeID: "child BAB"}',
]);
});
test('adding new item into area that is not culled', () => {
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
Fantom.runTask(() => {
root.render(
<ScrollView style={{height: 100, width: 100}}>
<View
nativeID={'element A'}
style={{height: 20, width: 20, marginTop: 30}}
/>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
Fantom.runTask(() => {
root.render(
<ScrollView style={{height: 100, width: 100}}>
<View
nativeID={'element A'}
style={{height: 20, width: 20, marginTop: 30}}>
<View nativeID={'child AA'} style={{height: 20, width: 20}} />
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Create {type: "View", nativeID: "child AA"}',
'Insert {type: "View", parentNativeID: "element A", index: 0, nativeID: "child AA"}',
]);
});
test('adding new item into area that is culled', () => {
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
contentOffset={{x: 0, y: 45}}
style={{height: 100, width: 100}}>
<View
key="element B"
nativeID={'element B'}
style={{height: 20, width: 20, marginTop: 30}}
/>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element B"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
Fantom.runTask(() => {
root.render(
<ScrollView
contentOffset={{x: 0, y: 45}}
style={{height: 100, width: 100}}>
<View
key="element A"
nativeID={'element A'}
style={{height: 20, width: 20}}
/>
<View
key="element B"
nativeID={'element B'}
style={{height: 20, width: 20, marginTop: 10}}
/>
</ScrollView>,
);
});
// element B is updated but it should be inconsequential.
// Differentiator generates an update for it because Yoga cloned
// shadow node backing element B.
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "View", nativeID: "element B"}',
]);
});
test('initial render', () => {
let maybeNode;
const root = Fantom.createRoot({viewportHeight: 100, viewportWidth: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
contentOffset={{x: 0, y: 45}}
ref={node => {
maybeNode = node;
}}
style={{height: 100, width: 100}}>
<View nativeID={'element A'} style={{height: 50, width: 100}} />
<View
nativeID={'element B'}
style={{height: 50, width: 100, marginTop: 100}}>
<View nativeID={'child BA'} style={{height: 20, width: 100}} />
<View
nativeID={'child BB'}
style={{height: 20, width: 100, marginTop: 10}}
/>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 100,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element A"}',
'Delete {type: "View", nativeID: "element A"}',
'Create {type: "View", nativeID: "element B"}',
'Create {type: "View", nativeID: "child BA"}',
'Create {type: "View", nativeID: "child BB"}',
'Insert {type: "View", parentNativeID: "element B", index: 0, nativeID: "child BA"}',
'Insert {type: "View", parentNativeID: "element B", index: 1, nativeID: "child BB"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element B"}',
]);
});
test('unmounting culled elements', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
contentOffset={{x: 0, y: 20}}>
<View nativeID={'element 1'} style={{height: 10, width: 10}} />
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
Fantom.runTask(() => {
root.render(<></>);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Remove {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
'Delete {type: "ScrollView", nativeID: (N/A)}',
]);
});
// TODO: only elements in ScrollView are culled.
test('basic culling smaller ScrollView', () => {
let maybeNode;
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
ref={node => {
maybeNode = node;
}}
style={{height: 50, width: 50, marginTop: 25}}>
<View nativeID={'element 1'} style={{height: 10, width: 10}} />
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "element 1"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element 1"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 11,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "element 1"}',
'Delete {type: "View", nativeID: "element 1"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Delete {type: "View", nativeID: (N/A)}',
'Update {type: "ScrollView", nativeID: (N/A)}',
]);
});
test('views are not culled when outside of viewport', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<View
nativeID={'child'}
style={{height: 10, width: 10, marginTop: 101}}
/>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "View", nativeID: "child"}',
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "child"}',
]);
});
@@ -124,7 +124,7 @@ describe('focus and blur event', () => {
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'focus');
Fantom.enqueueNativeEvent(element, 'focus');
});
// The tasks have not run.
@@ -137,7 +137,7 @@ describe('focus and blur event', () => {
expect(blurEvent).toHaveBeenCalledTimes(0);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'blur');
Fantom.enqueueNativeEvent(element, 'blur');
});
Fantom.runWorkLoop();
@@ -169,7 +169,7 @@ describe('onChange', () => {
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'change', {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
});
});
@@ -202,7 +202,7 @@ describe('onChangeText', () => {
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'change', {
Fantom.enqueueNativeEvent(element, 'change', {
text: 'Hello World',
});
});
@@ -76,20 +76,6 @@ export interface ViewPropsIOS extends TVViewPropsIOS {
}
export interface ViewPropsAndroid {
/**
* Views that are only used to layout their children or otherwise don't draw anything
* may be automatically removed from the native hierarchy as an optimization.
* Set this property to false to disable this optimization and ensure that this View exists in the native view hierarchy.
*/
collapsable?: boolean | undefined;
/**
* Setting to false prevents direct children of the view from being removed
* from the native view hierarchy, similar to the effect of setting
* `collapsable={false}` on each child.
*/
collapsableChildren?: boolean | undefined;
/**
* Whether this view should render itself (and all of its children) into a single hardware texture on the GPU.
*
@@ -211,4 +197,18 @@ export interface ViewProps
* Used to reference react managed views from native code.
*/
nativeID?: string | undefined;
/**
* Views that are only used to layout their children or otherwise don't draw anything
* may be automatically removed from the native hierarchy as an optimization.
* Set this property to false to disable this optimization and ensure that this View exists in the native view hierarchy.
*/
collapsable?: boolean | undefined;
/**
* Setting to false prevents direct children of the view from being removed
* from the native view hierarchy, similar to the effect of setting
* `collapsable={false}` on each child.
*/
collapsableChildren?: boolean | undefined;
}
@@ -21,7 +21,7 @@ let thousandViews: React.MixedElement;
Fantom.unstable_benchmark
.suite('View')
.add(
.test(
'render 100 uncollapsable views',
() => {
Fantom.runTask(() => root.render(thousandViews));
@@ -51,7 +51,7 @@ Fantom.unstable_benchmark
},
},
)
.add(
.test(
'render 1000 uncollapsable views',
() => {
Fantom.runTask(() => root.render(thousandViews));
@@ -25,6 +25,14 @@ type Task =
}
| (() => void);
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
}
/**
* InteractionManager allows long-running work to be scheduled after any
* interactions/animations have completed. In particular, this allows JavaScript
@@ -97,7 +105,7 @@ const InteractionManagerStub = {
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise((resolve, reject) => {
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
@@ -19,6 +19,7 @@ import * as LogBoxStyle from './LogBoxStyle';
import * as React from 'react';
type Props = $ReadOnly<{
id?: string,
backgroundColor: $ReadOnly<{
default: string,
pressed: string,
@@ -42,6 +43,7 @@ function LogBoxButton(props: Props): React.Node {
const content = (
<View
id={props.id}
style={StyleSheet.compose(
{
backgroundColor: pressed
@@ -36,7 +36,9 @@ export default function LogBoxInspectorHeader(props: Props): React.Node {
<LogBoxInspectorHeaderSafeArea style={styles[props.level]}>
<View style={styles.header}>
<View style={styles.title}>
<Text style={styles.titleText}>Failed to compile</Text>
<Text style={styles.titleText} id="logbox_header_title_text">
Failed to compile
</Text>
</View>
</View>
</LogBoxInspectorHeaderSafeArea>
@@ -60,7 +62,9 @@ export default function LogBoxInspectorHeader(props: Props): React.Node {
onPress={() => props.onSelectIndex(prevIndex)}
/>
<View style={styles.title}>
<Text style={styles.titleText}>{titleText}</Text>
<Text style={styles.titleText} id="logbox_header_title_text">
{titleText}
</Text>
</View>
<LogBoxInspectorHeaderButton
disabled={props.total <= 1}
@@ -46,11 +46,13 @@ function LogBoxInspectorMessageHeader(props: Props): React.Node {
return (
<View style={messageStyles.body}>
<View style={messageStyles.heading}>
<Text style={[messageStyles.headingText, messageStyles[props.level]]}>
<Text
style={[messageStyles.headingText, messageStyles[props.level]]}
id="logbox_message_title_text">
{props.title}
</Text>
</View>
<Text style={messageStyles.bodyText}>
<Text style={messageStyles.bodyText} id="logbox_message_contents_text">
<LogBoxMessage
maxLength={props.collapsed ? SHOW_MORE_MESSAGE_LENGTH : Infinity}
message={props.message}
@@ -39,6 +39,7 @@ export default function LogBoxNotification(props: Props): React.Node {
return (
<View style={styles.container}>
<LogBoxButton
id={`logbox_button_${level}`}
onPress={props.onPressOpen}
style={styles.press}
backgroundColor={{
@@ -36,6 +36,7 @@ exports[`LogBoxInspectorHeader should render both buttons for two total 1`] = `
}
>
<Text
id="logbox_header_title_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -99,6 +100,7 @@ exports[`LogBoxInspectorHeader should render no buttons for one total 1`] = `
}
>
<Text
id="logbox_header_title_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -152,6 +154,7 @@ exports[`LogBoxInspectorHeader should render syntax error header 1`] = `
}
>
<Text
id="logbox_header_title_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -205,6 +208,7 @@ exports[`LogBoxInspectorHeader should render two buttons for three or more total
}
>
<Text
id="logbox_header_title_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -28,6 +28,7 @@ exports[`LogBoxInspectorMessageHeader should not render "See More" if expanded 1
}
>
<Text
id="logbox_message_title_text"
style={
Array [
Object {
@@ -47,6 +48,7 @@ exports[`LogBoxInspectorMessageHeader should not render "See More" if expanded 1
</Text>
</View>
<Text
id="logbox_message_contents_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -105,6 +107,7 @@ exports[`LogBoxInspectorMessageHeader should not render See More button for shor
}
>
<Text
id="logbox_message_title_text"
style={
Array [
Object {
@@ -124,6 +127,7 @@ exports[`LogBoxInspectorMessageHeader should not render See More button for shor
</Text>
</View>
<Text
id="logbox_message_contents_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -182,6 +186,7 @@ exports[`LogBoxInspectorMessageHeader should render "See More" if collapsed 1`]
}
>
<Text
id="logbox_message_title_text"
style={
Array [
Object {
@@ -201,6 +206,7 @@ exports[`LogBoxInspectorMessageHeader should render "See More" if collapsed 1`]
</Text>
</View>
<Text
id="logbox_message_contents_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -272,6 +278,7 @@ exports[`LogBoxInspectorMessageHeader should render error 1`] = `
}
>
<Text
id="logbox_message_title_text"
style={
Array [
Object {
@@ -291,6 +298,7 @@ exports[`LogBoxInspectorMessageHeader should render error 1`] = `
</Text>
</View>
<Text
id="logbox_message_contents_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -349,6 +357,7 @@ exports[`LogBoxInspectorMessageHeader should render fatal 1`] = `
}
>
<Text
id="logbox_message_title_text"
style={
Array [
Object {
@@ -368,6 +377,7 @@ exports[`LogBoxInspectorMessageHeader should render fatal 1`] = `
</Text>
</View>
<Text
id="logbox_message_contents_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -426,6 +436,7 @@ exports[`LogBoxInspectorMessageHeader should render syntax error 1`] = `
}
>
<Text
id="logbox_message_title_text"
style={
Array [
Object {
@@ -445,6 +456,7 @@ exports[`LogBoxInspectorMessageHeader should render syntax error 1`] = `
</Text>
</View>
<Text
id="logbox_message_contents_text"
style={
Object {
"color": "rgba(255, 255, 255, 1)",
@@ -20,6 +20,7 @@ exports[`LogBoxNotification should render log 1`] = `
"pressed": "rgba(51, 51, 51, 0.9)",
}
}
id="logbox_button_warn"
onPress={[Function]}
style={
Object {
@@ -0,0 +1,136 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
* @fantom_flags enableAccessToHostTreeInFabric:true
*/
import ensureInstance from '../../../src/private/utilities/ensureInstance';
import ReadOnlyElement from '../../../src/private/webapis/dom/nodes/ReadOnlyElement';
import View from '../../Components/View/View';
import AppContainer from '../../ReactNative/AppContainer';
import LogBoxInspectorContainer from '../LogBoxInspectorContainer';
import {
ManualConsoleError,
// $FlowExpectedError[untyped-import]
} from './__fixtures__/ReactWarningFixtures';
import Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
import * as React from 'react';
import '../../Core/InitializeCore.js';
function findById(node: ReadOnlyElement, id: string): ?ReadOnlyElement {
if (node.id === id) {
return node;
}
for (const child of node.children) {
const found = findById(child, id);
if (found) {
return found;
}
}
return null;
}
describe('LogBox', () => {
let originalConsoleError;
let originalConsoleWarn;
let mockError;
let mockWarn;
beforeAll(() => {
originalConsoleError = console.error;
originalConsoleWarn = console.warn;
});
beforeEach(() => {
mockError = jest.fn((...args) => {
originalConsoleError(...args);
});
mockWarn = jest.fn((...args) => {
originalConsoleWarn(...args);
});
// $FlowExpectedError[cannot-write]
console.error = mockError;
// $FlowExpectedError[cannot-write]
console.warn = mockWarn;
});
afterEach(() => {
// $FlowExpectedError[cannot-write]
console.error = originalConsoleError;
// $FlowExpectedError[cannot-write]
console.warn = originalConsoleWarn;
});
it('renders an empty screen if there are no errors', () => {
const logBoxRoot = Fantom.createRoot();
Fantom.runTask(() => {
logBoxRoot.render(<LogBoxInspectorContainer />);
});
expect(logBoxRoot.getRenderedOutput().toJSX()).toBe(null);
});
it('handles a manual console.error without a component stack in LogBox', () => {
let maybeViewNode;
const logBoxRoot = Fantom.createRoot();
Fantom.runTask(() => {
logBoxRoot.render(
<View
ref={node => {
maybeViewNode = node;
}}>
<LogBoxInspectorContainer />
</View>,
);
});
expect(logBoxRoot.getRenderedOutput().toJSX()).toBe(null);
const logBoxRootNode = ensureInstance(maybeViewNode, ReadOnlyElement);
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<View
ref={node => {
maybeViewNode = node;
}}>
<AppContainer rootTag={root.getRootTag()}>
<ManualConsoleError />
</AppContainer>
</View>,
);
});
expect(logBoxRoot.getRenderedOutput().toJSX()).toBe(null);
const appRootNode = ensureInstance(maybeViewNode, ReadOnlyElement);
const logBoxButton = nullthrows(
findById(appRootNode, 'logbox_button_error'),
);
Fantom.dispatchNativeEvent(logBoxButton, 'click');
const headerTitle = findById(logBoxRootNode, 'logbox_header_title_text');
const messageTitle = findById(logBoxRootNode, 'logbox_message_title_text');
const messageContents = findById(
logBoxRootNode,
'logbox_message_contents_text',
);
expect(headerTitle?.textContent).toBe('Log 1 of 1');
expect(messageTitle?.textContent).toBe('Console Error');
expect(messageContents?.textContent).toBe('Manual console error');
});
});
@@ -45,6 +45,19 @@ export type ResponseType =
| 'text';
export type Response = ?Object | string;
type XHRInterceptor = interface {
requestSent(id: number, url: string, method: string, headers: Object): void,
responseReceived(
id: number,
url: string,
status: number,
headers: Object,
): void,
dataReceived(id: number, data: string): void,
loadingFinished(id: number, encodedDataLength: number): void,
loadingFailed(id: number, error: string): void,
};
// The native blob module is optional so inject it here if available.
if (BlobManager.isAvailable) {
BlobManager.addNetworkingHandler();
@@ -120,6 +133,7 @@ class XMLHttpRequest extends EventTarget {
static LOADING: number = LOADING;
static DONE: number = DONE;
static _interceptor: ?XHRInterceptor = null;
static _profiling: boolean = false;
UNSENT: number = UNSENT;
@@ -157,6 +171,10 @@ class XMLHttpRequest extends EventTarget {
_startTime: ?number = null;
_performanceLogger: IPerformanceLogger = GlobalPerformanceLogger;
static __setInterceptor_DO_NOT_USE(interceptor: ?XHRInterceptor) {
XMLHttpRequest._interceptor = interceptor;
}
static enableProfiling(enableProfiling: boolean): void {
XMLHttpRequest._profiling = enableProfiling;
}
@@ -283,10 +301,20 @@ class XMLHttpRequest extends EventTarget {
return this._cachedResponse;
}
// exposed for testing
__didCreateRequest(requestId: number): void {
this._requestId = requestId;
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.requestSent(
requestId,
this._url || '',
this._method || 'GET',
this._headers,
);
}
// exposed for testing
__didUploadProgress(
requestId: number,
progress: number,
@@ -321,6 +349,14 @@ class XMLHttpRequest extends EventTarget {
} else {
delete this.responseURL;
}
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.responseReceived(
requestId,
responseURL || this._url || '',
status,
responseHeaders || {},
);
}
}
@@ -331,6 +367,9 @@ class XMLHttpRequest extends EventTarget {
this._response = response;
this._cachedResponse = undefined; // force lazy recomputation
this.setReadyState(this.LOADING);
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.dataReceived(requestId, response);
}
__didReceiveIncrementalData(
@@ -353,6 +392,8 @@ class XMLHttpRequest extends EventTarget {
'Track:XMLHttpRequest:Incremental Data: ' + this._getMeasureURL(),
);
}
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.dataReceived(requestId, responseText);
this.setReadyState(this.LOADING);
this.__didReceiveDataProgress(requestId, progress, total);
@@ -376,6 +417,7 @@ class XMLHttpRequest extends EventTarget {
);
}
// exposed for testing
__didCompleteResponse(
requestId: number,
error: string,
@@ -401,6 +443,16 @@ class XMLHttpRequest extends EventTarget {
end: performance.now(),
});
}
if (error) {
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.loadingFailed(requestId, error);
} else {
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.loadingFinished(
requestId,
this._response.length,
);
}
}
}
@@ -34,6 +34,19 @@ export type ResponseType =
| 'text';
export type Response = ?Object | string;
type XHRInterceptor = interface {
requestSent(id: number, url: string, method: string, headers: Object): void,
responseReceived(
id: number,
url: string,
status: number,
headers: Object,
): void,
dataReceived(id: number, data: string): void,
loadingFinished(id: number, encodedDataLength: number): void,
loadingFailed(id: number, error: string): void,
};
// The native blob module is optional so inject it here if available.
if (BlobManager.isAvailable) {
BlobManager.addNetworkingHandler();
@@ -88,6 +101,7 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
static LOADING: number = LOADING;
static DONE: number = DONE;
static _interceptor: ?XHRInterceptor = null;
static _profiling: boolean = false;
UNSENT: number = UNSENT;
@@ -135,6 +149,10 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
_startTime: ?number = null;
_performanceLogger: IPerformanceLogger = GlobalPerformanceLogger;
static __setInterceptor_DO_NOT_USE(interceptor: ?XHRInterceptor) {
XMLHttpRequest._interceptor = interceptor;
}
static enableProfiling(enableProfiling: boolean): void {
XMLHttpRequest._profiling = enableProfiling;
}
@@ -261,10 +279,20 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
return this._cachedResponse;
}
// exposed for testing
__didCreateRequest(requestId: number): void {
this._requestId = requestId;
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.requestSent(
requestId,
this._url || '',
this._method || 'GET',
this._headers,
);
}
// exposed for testing
__didUploadProgress(
requestId: number,
progress: number,
@@ -297,6 +325,14 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
} else {
delete this.responseURL;
}
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.responseReceived(
requestId,
responseURL || this._url || '',
status,
responseHeaders || {},
);
}
}
@@ -307,6 +343,9 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
this._response = response;
this._cachedResponse = undefined; // force lazy recomputation
this.setReadyState(this.LOADING);
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.dataReceived(requestId, response);
}
__didReceiveIncrementalData(
@@ -329,6 +368,8 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
'Track:XMLHttpRequest:Incremental Data: ' + this._getMeasureURL(),
);
}
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.dataReceived(requestId, responseText);
this.setReadyState(this.LOADING);
this.__didReceiveDataProgress(requestId, progress, total);
@@ -376,6 +417,16 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): typeof EventTarget) {
end: performance.now(),
});
}
if (error) {
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.loadingFailed(requestId, error);
} else {
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.loadingFinished(
requestId,
this._response.length,
);
}
}
}
+3 -3
View File
@@ -30,11 +30,11 @@ export type TaskProvider = () => Task;
type TaskCanceller = () => void;
type TaskCancelProvider = () => TaskCanceller;
export type ComponentProvider = () => React$ComponentType<any>;
export type ComponentProvider = () => React.ComponentType<any>;
export type ComponentProviderInstrumentationHook = (
component_: ComponentProvider,
scopedPerformanceLogger: IPerformanceLogger,
) => React$ComponentType<any>;
) => React.ComponentType<any>;
export type AppConfig = {
appKey: string,
component?: ComponentProvider,
@@ -59,7 +59,7 @@ export type Registry = {
};
export type WrapperComponentProvider = (
appParameters: Object,
) => React$ComponentType<any>;
) => React.ComponentType<any>;
export type RootViewStyleProvider = (appParameters: Object) => ViewStyleProp;
const runnables: Runnables = {};
@@ -36,7 +36,7 @@ const ownerDocument: ReactNativeDocument = {};
/* eslint-disable no-new */
Fantom.unstable_benchmark
.suite('ReactNativeElement vs. ReactFabricHostComponent')
.add('ReactNativeElement', () => {
.test('ReactNativeElement', () => {
new ReactNativeElement(
tag,
viewConfig,
@@ -44,6 +44,6 @@ Fantom.unstable_benchmark
ownerDocument,
);
})
.add('ReactFabricHostComponent', () => {
.test('ReactFabricHostComponent', () => {
new ReactFabricHostComponent(tag, viewConfig, internalInstanceHandle);
});
+1 -1
View File
@@ -12,7 +12,7 @@ import * as React from 'react';
export opaque type RootTag = number;
export const RootTagContext: React$Context<RootTag> =
export const RootTagContext: React.Context<RootTag> =
React.createContext<RootTag>(0);
if (__DEV__) {
@@ -42,7 +42,7 @@ describe('discrete event category', () => {
interruptRendering = false;
const element = ensureReactNativeElement(maybeTextInputNode);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(
Fantom.enqueueNativeEvent(
element,
'change',
{
@@ -161,7 +161,7 @@ describe('continuous event category', () => {
interruptRendering = false;
const element = ensureReactNativeElement(maybeTextInputNode);
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(
Fantom.enqueueNativeEvent(
element,
'selectionChange',
{
+2 -2
View File
@@ -11,7 +11,7 @@
'use strict';
const Settings = {
get(key: string): mixed {
get(key: string): any {
console.warn('Settings is not yet supported on this platform.');
return null;
},
@@ -20,7 +20,7 @@ const Settings = {
console.warn('Settings is not yet supported on this platform.');
},
watchKeys(keys: string | Array<string>, callback: Function): number {
watchKeys(keys: string | Array<string>, callback: () => void): number {
console.warn('Settings is not yet supported on this platform.');
return -1;
},
+9 -2
View File
@@ -8,6 +8,8 @@
* @flow strict-local
*/
import type {ColorValue} from '../StyleSheet/StyleSheet';
import NativeActionSheetManager from '../ActionSheetIOS/NativeActionSheetManager';
import NativeShareModule from './NativeShareModule';
@@ -29,11 +31,16 @@ export type ShareContent =
export type ShareOptions = {
dialogTitle?: string,
excludedActivityTypes?: Array<string>,
tintColor?: string,
tintColor?: ColorValue,
subject?: string,
anchor?: number,
};
export type ShareAction = {
action: 'sharedAction' | 'dismissedAction',
activityType?: string | null,
};
class Share {
/**
* Open a dialog to share text content.
@@ -73,7 +80,7 @@ class Share {
*/
static share(
content: ShareContent,
options: ShareOptions = {},
options?: ShareOptions = {},
): Promise<{action: string, activityType: ?string}> {
invariant(
typeof content === 'object' && content !== null,
@@ -112,7 +112,7 @@ describe('processFilter', () => {
});
it('string multiple filters', () => {
expect(
processFilter('brightness(0.5) opacity(0.5) blur(5) hue-rotate(90deg)'),
processFilter('brightness(0.5) opacity(0.5) blur(5px) hue-rotate(90deg)'),
).toEqual([{brightness: 0.5}, {opacity: 0.5}, {blur: 5}, {hueRotate: 90}]);
});
it('string multiple filters with newlines', () => {
@@ -124,7 +124,7 @@ describe('processFilter', () => {
});
it('string multiple filters one invalid', () => {
expect(
processFilter('brightness(0.5) opacity(0.5) blur(5) hue-rotate(90foo)'),
processFilter('brightness(0.5) opacity(0.5) blur(5px) hue-rotate(90foo)'),
).toEqual([]);
});
it('string multiple same filters', () => {
@@ -233,7 +233,7 @@ function createFilterPrimitive(
function testDropShadow() {
it('should parse string drop-shadow', () => {
expect(processFilter('drop-shadow(4px 4 10px red)')).toEqual([
expect(processFilter('drop-shadow(4px 4px 10px red)')).toEqual([
{
dropShadow: {
offsetX: 4,
@@ -246,7 +246,7 @@ function testDropShadow() {
});
it('should parse string negative offsets drop-shadow', () => {
expect(processFilter('drop-shadow(-4 -4)')).toEqual([
expect(processFilter('drop-shadow(-4px -4px)')).toEqual([
{
dropShadow: {
offsetX: -4,
@@ -258,7 +258,9 @@ function testDropShadow() {
it('should parse string multiple drop-shadows', () => {
expect(
processFilter('drop-shadow(4 4) drop-shadow(4 4) drop-shadow(4 4)'),
processFilter(
'drop-shadow(4px 4px) drop-shadow(4px 4px) drop-shadow(4px 4px)',
),
).toEqual([
{
dropShadow: {
@@ -283,7 +285,7 @@ function testDropShadow() {
it('should parse string drop-shadow with random whitespaces', () => {
expect(
processFilter(' drop-shadow(4px 4 10px red) '),
processFilter(' drop-shadow(4px 4px 10px red) '),
).toEqual([
{
dropShadow: {
@@ -299,7 +301,7 @@ function testDropShadow() {
it('should parse string drop-shadow with multiple filters', () => {
expect(
processFilter(
'drop-shadow(4px 4 10px red) brightness(0.5) brightness(0.5)',
'drop-shadow(4px 4px 10px red) brightness(0.5) brightness(0.5)',
),
).toEqual([
{
@@ -316,7 +318,7 @@ function testDropShadow() {
});
it('should parse string drop-shadow with color', () => {
expect(processFilter('drop-shadow(50 50 purple)')).toEqual([
expect(processFilter('drop-shadow(50px 50px purple)')).toEqual([
{
dropShadow: {
offsetX: 50,
@@ -328,7 +330,7 @@ function testDropShadow() {
});
it('should parse string drop-shadow with rgba color', () => {
expect(processFilter('drop-shadow(50 50 rgba(0, 0, 0, 1))')).toEqual([
expect(processFilter('drop-shadow(50px 50px rgba(0, 0, 0, 1))')).toEqual([
{
dropShadow: {
offsetX: 50,
@@ -340,7 +342,7 @@ function testDropShadow() {
});
it('should parse string with mixed case drop-shadow', () => {
expect(processFilter('DroP-sHaDOw(50 50 purple)')).toEqual([
expect(processFilter('DroP-sHaDOw(50px 50px purple)')).toEqual([
{
dropShadow: {
offsetX: 50,
@@ -359,7 +361,7 @@ function testDropShadow() {
offsetX: 4,
offsetY: 4,
color: '#FFFFFF',
standardDeviation: '10',
standardDeviation: '10px',
},
},
]),
@@ -376,7 +378,7 @@ function testDropShadow() {
});
it('should fail to parse string comma separated drop-shadow', () => {
expect(processFilter('drop-shadow(4px, 4, 10px, red)')).toEqual([]);
expect(processFilter('drop-shadow(4px, 4px, 10px, red)')).toEqual([]);
});
it('should fail to parse other symbols after args comma separated drop-shadow', () => {
@@ -384,15 +386,15 @@ function testDropShadow() {
});
it('should fail on color between lengths string drop-shadow', () => {
expect(processFilter('drop-shadow(10 red 10 10')).toEqual([]);
expect(processFilter('drop-shadow(10px red 10px 10px')).toEqual([]);
});
it('should fail on color between offset & blur string drop-shadow', () => {
expect(processFilter('drop-shadow(10 10 red 10')).toEqual([]);
expect(processFilter('drop-shadow(10px 10px red 10px')).toEqual([]);
});
it('should fail on negative blue', () => {
expect(processFilter('drop-shadow(10 10 -10')).toEqual([]);
expect(processFilter('drop-shadow(10px 10px -10px')).toEqual([]);
});
it('should fail on invalid object drop-shadow', () => {
@@ -317,5 +317,9 @@ function parseLength(length: string): ?number {
return null;
}
if (match[3] == null && match[1] !== '0') {
return null;
}
return Number(match[1]);
}
+1 -1
View File
@@ -15,7 +15,7 @@ const React = require('react');
/**
* Whether the current element is the descendant of a <Text> element.
*/
const TextAncestorContext: React$Context<boolean> = React.createContext(false);
const TextAncestorContext: React.Context<boolean> = React.createContext(false);
if (__DEV__) {
TextAncestorContext.displayName = 'TextAncestorContext';
}
@@ -53,7 +53,7 @@ declare export default typeof NativeActionSheetManager;
"
`;
exports[`public API should not change unintentionally Libraries/Alert/Alert.flow.js 1`] = `
exports[`public API should not change unintentionally Libraries/Alert/Alert.js 1`] = `
"export type AlertType =
| \\"default\\"
| \\"plain-text\\"
@@ -67,7 +67,7 @@ export type AlertButton = {
style?: AlertButtonStyle,
...
};
export type Buttons = Array<AlertButton>;
export type AlertButtons = Array<AlertButton>;
export type AlertOptions = {
cancelable?: ?boolean,
userInterfaceStyle?: \\"unspecified\\" | \\"light\\" | \\"dark\\",
@@ -78,36 +78,13 @@ declare class Alert {
static alert(
title: ?string,
message?: ?string,
buttons?: Buttons,
buttons?: AlertButtons,
options?: AlertOptions
): void;
static prompt(
title: ?string,
message?: ?string,
callbackOrButtons?: ?(((text: string) => void) | Buttons),
type?: ?AlertType,
defaultValue?: string,
keyboardType?: string,
options?: AlertOptions
): void;
}
declare export default typeof Alert;
"
`;
exports[`public API should not change unintentionally Libraries/Alert/Alert.js 1`] = `
"export type * from \\"./Alert.flow\\";
declare class Alert {
static alert(
title: ?string,
message?: ?string,
buttons?: Buttons,
options?: AlertOptions
): void;
static prompt(
title: ?string,
message?: ?string,
callbackOrButtons?: ?(((text: string) => void) | Buttons),
callbackOrButtons?: ?(((text: string) => void) | AlertButtons),
type?: ?AlertType,
defaultValue?: string,
keyboardType?: string,
@@ -124,17 +101,6 @@ declare export default typeof NativeAlertManager;
"
`;
exports[`public API should not change unintentionally Libraries/Alert/RCTAlertManager.flow.js 1`] = `
"declare const RCTAlertManager: {
alertWithArgs(
args: Args,
callback: (id: number, value: string) => void
): void,
};
declare export default typeof RCTAlertManager;
"
`;
exports[`public API should not change unintentionally Libraries/Alert/RCTAlertManager.js.flow 1`] = `
"declare export default {
alertWithArgs(
@@ -5514,6 +5480,7 @@ exports[`public API should not change unintentionally Libraries/LogBox/UI/AnsiHi
exports[`public API should not change unintentionally Libraries/LogBox/UI/LogBoxButton.js 1`] = `
"type Props = $ReadOnly<{
id?: string,
backgroundColor: $ReadOnly<{
default: string,
pressed: string,
@@ -6030,6 +5997,18 @@ export type ResponseType =
| \\"json\\"
| \\"text\\";
export type Response = ?Object | string;
type XHRInterceptor = interface {
requestSent(id: number, url: string, method: string, headers: Object): void,
responseReceived(
id: number,
url: string,
status: number,
headers: Object
): void,
dataReceived(id: number, data: string): void,
loadingFinished(id: number, encodedDataLength: number): void,
loadingFailed(id: number, error: string): void,
};
declare class XMLHttpRequestEventTarget extends EventTarget {
get onload(): EventCallback | null;
set onload(listener: ?EventCallback): void;
@@ -6112,6 +6091,18 @@ export type ResponseType =
| \\"json\\"
| \\"text\\";
export type Response = ?Object | string;
type XHRInterceptor = interface {
requestSent(id: number, url: string, method: string, headers: Object): void,
responseReceived(
id: number,
url: string,
status: number,
headers: Object
): void,
dataReceived(id: number, data: string): void,
loadingFinished(id: number, encodedDataLength: number): void,
loadingFailed(id: number, error: string): void,
};
declare class XMLHttpRequestEventTarget extends EventTarget {
onload: ?Function;
onloadstart: ?Function;
@@ -6542,11 +6533,11 @@ exports[`public API should not change unintentionally Libraries/ReactNative/AppR
export type TaskProvider = () => Task;
type TaskCanceller = () => void;
type TaskCancelProvider = () => TaskCanceller;
export type ComponentProvider = () => React$ComponentType<any>;
export type ComponentProvider = () => React.ComponentType<any>;
export type ComponentProviderInstrumentationHook = (
component_: ComponentProvider,
scopedPerformanceLogger: IPerformanceLogger
) => React$ComponentType<any>;
) => React.ComponentType<any>;
export type AppConfig = {
appKey: string,
component?: ComponentProvider,
@@ -6571,7 +6562,7 @@ export type Registry = {
};
export type WrapperComponentProvider = (
appParameters: Object
) => React$ComponentType<any>;
) => React.ComponentType<any>;
export type RootViewStyleProvider = (appParameters: Object) => ViewStyleProp;
declare const AppRegistry: {
setWrapperComponentProvider(provider: WrapperComponentProvider): void,
@@ -6887,7 +6878,7 @@ exports[`public API should not change unintentionally Libraries/ReactNative/Rend
exports[`public API should not change unintentionally Libraries/ReactNative/RootTag.js 1`] = `
"declare export opaque type RootTag;
declare export const RootTagContext: React$Context<RootTag>;
declare export const RootTagContext: React.Context<RootTag>;
declare export function createRootTag(rootTag: number | RootTag): RootTag;
"
`;
@@ -6951,9 +6942,9 @@ declare export default typeof NativeSettingsManager;
exports[`public API should not change unintentionally Libraries/Settings/Settings.js 1`] = `
"declare const Settings: {
get(key: string): mixed,
get(key: string): any,
set(settings: Object): void,
watchKeys(keys: string | Array<string>, callback: Function): number,
watchKeys(keys: string | Array<string>, callback: () => void): number,
clearWatch(watchId: number): void,
};
declare export default typeof Settings;
@@ -6981,14 +6972,18 @@ exports[`public API should not change unintentionally Libraries/Share/Share.js 1
export type ShareOptions = {
dialogTitle?: string,
excludedActivityTypes?: Array<string>,
tintColor?: string,
tintColor?: ColorValue,
subject?: string,
anchor?: number,
};
export type ShareAction = {
action: \\"sharedAction\\" | \\"dismissedAction\\",
activityType?: string | null,
};
declare class Share {
static share(
content: ShareContent,
options: ShareOptions
options?: ShareOptions
): Promise<{ action: string, activityType: ?string }>;
static sharedAction: \\"sharedAction\\";
static dismissedAction: \\"dismissedAction\\";
@@ -7684,7 +7679,8 @@ declare export default typeof Text;
`;
exports[`public API should not change unintentionally Libraries/Text/TextAncestor.js 1`] = `
"declare const TextAncestorContext: React$Context<boolean>;
"declare const React: $FlowFixMe;
declare const TextAncestorContext: React.Context<boolean>;
declare export default typeof TextAncestorContext;
"
`;
@@ -64,6 +64,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
_reactSubviews = [NSMutableArray new];
self.multipleTouchEnabled = YES;
_useCustomContainerView = NO;
_removeClippedSubviews = NO;
}
return self;
}
@@ -229,10 +230,13 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
needsInvalidateLayer = YES;
}
if (oldViewProps.removeClippedSubviews != newViewProps.removeClippedSubviews) {
_removeClippedSubviews = newViewProps.removeClippedSubviews;
if (_removeClippedSubviews && self.currentContainerView.subviews.count > 0) {
_reactSubviews = [NSMutableArray arrayWithArray:self.currentContainerView.subviews];
// Disable `removeClippedSubviews` when Fabric View Culling is enabled.
if (!ReactNativeFeatureFlags::enableViewCulling()) {
if (oldViewProps.removeClippedSubviews != newViewProps.removeClippedSubviews) {
_removeClippedSubviews = newViewProps.removeClippedSubviews;
if (_removeClippedSubviews && self.currentContainerView.subviews.count > 0) {
_reactSubviews = [NSMutableArray arrayWithArray:self.currentContainerView.subviews];
}
}
}
@@ -1387,6 +1387,7 @@ public final class com/facebook/react/bridge/ReactSoftExceptionLogger$Categories
public static final field RVG_IS_VIEW_CLIPPED Ljava/lang/String;
public static final field RVG_ON_VIEW_REMOVED Ljava/lang/String;
public static final field SOFT_ASSERTIONS Ljava/lang/String;
public static final field SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE Ljava/lang/String;
}
public abstract interface class com/facebook/react/bridge/ReactSoftExceptionLogger$ReactSoftExceptionListener {
@@ -1643,7 +1644,7 @@ public final class com/facebook/react/bridge/queue/MessageQueueThreadImpl$Compan
public final fun create (Lcom/facebook/react/bridge/queue/MessageQueueThreadSpec;Lcom/facebook/react/bridge/queue/QueueThreadExceptionHandler;)Lcom/facebook/react/bridge/queue/MessageQueueThreadImpl;
}
public class com/facebook/react/bridge/queue/MessageQueueThreadPerfStats {
public final class com/facebook/react/bridge/queue/MessageQueueThreadPerfStats {
public field cpuTime J
public field wallTime J
public fun <init> ()V
@@ -1873,6 +1874,7 @@ public final class com/facebook/react/common/build/ReactBuildConfig {
public static final field INSTANCE Lcom/facebook/react/common/build/ReactBuildConfig;
public static final field IS_INTERNAL_BUILD Z
public static final field UNSTABLE_ENABLE_FUSEBOX_RELEASE Z
public static final field UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE Z
}
public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
@@ -3370,6 +3372,14 @@ public class com/facebook/react/modules/network/ProgressResponseBody : okhttp3/R
public fun totalBytesRead ()J
}
public final class com/facebook/react/modules/network/ReactCookieJarContainer : com/facebook/react/modules/network/CookieJarContainer {
public fun <init> ()V
public fun loadForRequest (Lokhttp3/HttpUrl;)Ljava/util/List;
public fun removeCookieJar ()V
public fun saveFromResponse (Lokhttp3/HttpUrl;Ljava/util/List;)V
public fun setCookieJar (Lokhttp3/CookieJar;)V
}
public class com/facebook/react/modules/network/TLSSocketFactory : javax/net/ssl/SSLSocketFactory {
public fun <init> ()V
public fun createSocket (Ljava/lang/String;I)Ljava/net/Socket;
@@ -5585,10 +5595,6 @@ public abstract interface class com/facebook/react/uimanager/events/RCTModernEve
public abstract fun receiveTouches (Lcom/facebook/react/uimanager/events/TouchEvent;)V
}
public abstract interface class com/facebook/react/uimanager/events/SynchronousEventReceiver {
public abstract fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZ)V
}
public final class com/facebook/react/uimanager/events/TouchEvent : com/facebook/react/uimanager/events/Event {
public static final field Companion Lcom/facebook/react/uimanager/events/TouchEvent$Companion;
public static final field UNSET J
@@ -6040,11 +6046,6 @@ public final class com/facebook/react/views/common/ContextUtils {
public static final fun findContextOfType (Landroid/content/Context;Ljava/lang/Class;)Ljava/lang/Object;
}
public final class com/facebook/react/views/common/ViewUtils {
public static final field INSTANCE Lcom/facebook/react/views/common/ViewUtils;
public static final fun getTestId (Landroid/view/View;)Ljava/lang/String;
}
public final class com/facebook/react/views/debuggingoverlay/DebuggingOverlay : android/view/View {
public fun <init> (Landroid/content/Context;)V
public final fun clearElementsHighlights ()V
@@ -6240,19 +6241,6 @@ public final class com/facebook/react/views/image/ImageResizeMode {
public static final fun toTileMode (Ljava/lang/String;)Landroid/graphics/Shader$TileMode;
}
public final class com/facebook/react/views/image/MultiPostprocessor : com/facebook/imagepipeline/request/Postprocessor {
public static final field Companion Lcom/facebook/react/views/image/MultiPostprocessor$Companion;
public synthetic fun <init> (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public static final fun from (Ljava/util/List;)Lcom/facebook/imagepipeline/request/Postprocessor;
public fun getName ()Ljava/lang/String;
public fun getPostprocessorCacheKey ()Lcom/facebook/cache/common/CacheKey;
public fun process (Landroid/graphics/Bitmap;Lcom/facebook/imagepipeline/bitmaps/PlatformBitmapFactory;)Lcom/facebook/common/references/CloseableReference;
}
public final class com/facebook/react/views/image/MultiPostprocessor$Companion {
public final fun from (Ljava/util/List;)Lcom/facebook/imagepipeline/request/Postprocessor;
}
public abstract interface class com/facebook/react/views/image/ReactCallerContextFactory {
public abstract fun getOrCreateCallerContext (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
}
@@ -7654,9 +7642,3 @@ public final class com/facebook/react/views/view/ViewGroupClickEvent : com/faceb
public fun getEventName ()Ljava/lang/String;
}
public final class com/facebook/react/views/view/WindowUtilKt {
public static final fun setStatusBarTranslucency (Landroid/view/Window;Z)V
public static final fun setStatusBarVisibility (Landroid/view/Window;Z)V
public static final fun setSystemBarsTranslucency (Landroid/view/Window;Z)V
}
@@ -523,6 +523,7 @@ android {
buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
buildConfigField("boolean", "UNSTABLE_ENABLE_FUSEBOX_RELEASE", "false")
buildConfigField("boolean", "ENABLE_PERFETTO", "false")
buildConfigField("boolean", "UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE", "false")
resValue("integer", "react_native_dev_server_port", reactNativeDevServerPort())
@@ -11,9 +11,9 @@ import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.TypeElement
public abstract class ProcessorBase : AbstractProcessor() {
internal abstract class ProcessorBase : AbstractProcessor() {
public fun process(annotations: Set<TypeElement?>?, roundEnv: RoundEnvironment?): Boolean =
fun process(annotations: Set<TypeElement?>?, roundEnv: RoundEnvironment?): Boolean =
processImpl(annotations, roundEnv)
protected abstract fun processImpl(
@@ -19,6 +19,8 @@ public object ReactSoftExceptionLogger {
public const val RVG_IS_VIEW_CLIPPED: String = "ReactViewGroup.isViewClipped"
public const val RVG_ON_VIEW_REMOVED: String = "ReactViewGroup.onViewRemoved"
public const val SOFT_ASSERTIONS: String = "SoftAssertions"
public const val SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE: String =
"SurfaceMountingManager:MissingViewState"
}
// Use a list instead of a set here because we expect the number of listeners
@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge.queue;
package com.facebook.react.bridge.queue
/** This class holds perf counters' values at the beginning of an RN startup. */
public class MessageQueueThreadPerfStats {
public long wallTime;
public long cpuTime;
@JvmField public var wallTime: Long = 0
@JvmField public var cpuTime: Long = 0
}
@@ -5,13 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge.queue;
package com.facebook.react.bridge.queue
/**
* Interface for a class that knows how to handle an Exception thrown while executing a Runnable
* submitted via {@link MessageQueueThread#runOnQueue}.
* submitted via [MessageQueueThread.runOnQueue].
*/
public interface QueueThreadExceptionHandler {
void handleException(Exception e);
public fun interface QueueThreadExceptionHandler {
public fun handleException(e: Exception)
}
@@ -1,27 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge.queue;
/**
* Specifies which {@link MessageQueueThread}s must be used to run the various contexts of execution
* within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be the same
* but should be coded against as if they are different.
*
* <p>UI Queue Thread: The standard Android main UI thread and Looper. Not configurable. Native
* Modules Queue Thread: The thread and Looper that native modules are invoked on. JS Queue Thread:
* The thread and Looper that JS is executed on.
*/
public interface ReactQueueConfiguration {
MessageQueueThread getUIQueueThread();
MessageQueueThread getNativeModulesQueueThread();
MessageQueueThread getJSQueueThread();
void destroy();
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge.queue
/**
* Specifies which [MessageQueueThread]s must be used to run the various contexts of execution
* within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be the same
* but should be coded against as if they are different.
*
* UI Queue Thread: The standard Android main UI thread and Looper. Not configurable.
*
* Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
*
* JS Queue Thread: The thread and Looper that JS is executed on. thread and Looper that JS is
* executed on.
*/
public interface ReactQueueConfiguration {
public fun getUIQueueThread(): MessageQueueThread
public fun getNativeModulesQueueThread(): MessageQueueThread
public fun getJSQueueThread(): MessageQueueThread
public fun destroy()
}
@@ -33,4 +33,8 @@ public object ReactBuildConfig {
/** [Experimental] Enable React Native DevTools in release builds. */
@JvmField
public val UNSTABLE_ENABLE_FUSEBOX_RELEASE: Boolean = BuildConfig.UNSTABLE_ENABLE_FUSEBOX_RELEASE
@JvmField
public val UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE: Boolean =
BuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE
}
@@ -286,11 +286,11 @@ public class MountingManager {
* Send an accessibility eventType to a Native View. eventType is any valid `AccessibilityEvent.X`
* value.
*
* <p>Why accept {@ViewUtils.NO_SURFACE_ID}(-1) SurfaceId? Currently there are calls to
* <p>Why accept {@ViewUtil.NO_SURFACE_ID}(-1) SurfaceId? Currently there are calls to
* UIManager.sendAccessibilityEvent which is a legacy API and accepts only reactTag. We will have
* to investigate and migrate away from those calls over time.
*
* @param surfaceId {@link int} that identifies the surface or {@ViewUtils.NO_SURFACE_ID}(-1) to
* @param surfaceId {@link int} that identifies the surface or {@ViewUtil.NO_SURFACE_ID}(-1) to
* temporarily support backward compatibility.
* @param reactTag {@link int} that identifies the react Tag of the view.
* @param eventType {@link int} that identifies Android eventType. see {@link
@@ -21,6 +21,7 @@ import androidx.collection.SparseArrayCompat;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
@@ -1033,8 +1034,8 @@ public class SurfaceMountingManager {
if (viewState == null) {
ReactSoftExceptionLogger.logSoftException(
MountingManager.TAG,
new IllegalStateException(
ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
new ReactNoCrashSoftException(
"Unable to find viewState for tag: " + reactTag + " for deleteView"));
return;
}
@@ -10,7 +10,7 @@ package com.facebook.react.internal
import com.facebook.react.bridge.UiThreadUtil
/** An implementation of ChoreographerProvider that directly uses android.view.Choreographer. */
public object AndroidChoreographerProvider : ChoreographerProvider {
internal object AndroidChoreographerProvider : ChoreographerProvider {
private class AndroidChoreographer : ChoreographerProvider.Choreographer {
private val instance: android.view.Choreographer = android.view.Choreographer.getInstance()
@@ -24,9 +24,9 @@ public object AndroidChoreographerProvider : ChoreographerProvider {
}
}
@JvmStatic public fun getInstance(): AndroidChoreographerProvider = this
@JvmStatic fun getInstance(): AndroidChoreographerProvider = this
override public fun getChoreographer(): ChoreographerProvider.Choreographer {
override fun getChoreographer(): ChoreographerProvider.Choreographer {
UiThreadUtil.assertOnUiThread()
return AndroidChoreographer()
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<a73d250c74505693246cd3309ef1d08a>>
* @generated SignedSource<<ae55a0a7badfc9d80453d2737f0f87fd>>
*/
/**
@@ -166,12 +166,30 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableUIConsistency(): Boolean = accessor.enableUIConsistency()
/**
* Enables View Culling: as soon as a view goes off screen, it can be reused anywhere in the UI and pieced together with other items to create new UI elements.
*/
@JvmStatic
public fun enableViewCulling(): Boolean = accessor.enableViewCulling()
/**
* Enables View Recycling. When enabled, individual ViewManagers must still opt-in.
*/
@JvmStatic
public fun enableViewRecycling(): Boolean = accessor.enableViewRecycling()
/**
* Enables View Recycling for <Text> via ReactTextView/ReactTextViewManager.
*/
@JvmStatic
public fun enableViewRecyclingForText(): Boolean = accessor.enableViewRecyclingForText()
/**
* Enables View Recycling for <View> via ReactViewGroup/ReactViewManager.
*/
@JvmStatic
public fun enableViewRecyclingForView(): Boolean = accessor.enableViewRecyclingForView()
/**
* When enabled, rawProps in Props will not include Yoga specific props.
*/
@@ -214,12 +232,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun lazyAnimationCallbacks(): Boolean = accessor.lazyAnimationCallbacks()
/**
* Adds support for loading vector drawable assets in the Image component (only on Android)
*/
@JvmStatic
public fun loadVectorDrawablesOnImages(): Boolean = accessor.loadVectorDrawablesOnImages()
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -262,12 +274,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun useRawPropsJsiValue(): Boolean = accessor.useRawPropsJsiValue()
/**
* When enabled, cloning shadow nodes within react native will update the reference held by the current JS fiber tree.
*/
@JvmStatic
public fun useRuntimeShadowNodeReferenceUpdate(): Boolean = accessor.useRuntimeShadowNodeReferenceUpdate()
/**
* In Bridgeless mode, should legacy NativeModules use the TurboModule system?
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0effb773b4902465909432a2f5576bdf>>
* @generated SignedSource<<d7872ba2601906476aec3d08ebe1ab94>>
*/
/**
@@ -43,7 +43,10 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableReportEventPaintTimeCache: Boolean? = null
private var enableSynchronousStateUpdatesCache: Boolean? = null
private var enableUIConsistencyCache: Boolean? = null
private var enableViewCullingCache: Boolean? = null
private var enableViewRecyclingCache: Boolean? = null
private var enableViewRecyclingForTextCache: Boolean? = null
private var enableViewRecyclingForViewCache: Boolean? = null
private var excludeYogaFromRawPropsCache: Boolean? = null
private var fixDifferentiatorEmittingUpdatesWithWrongParentTagCache: Boolean? = null
private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null
@@ -51,7 +54,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var fuseboxEnabledReleaseCache: Boolean? = null
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var loadVectorDrawablesOnImagesCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null
@@ -59,7 +61,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
private var useRuntimeShadowNodeReferenceUpdateCache: Boolean? = null
private var useTurboModuleInteropCache: Boolean? = null
private var useTurboModulesCache: Boolean? = null
@@ -270,6 +271,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableViewCulling(): Boolean {
var cached = enableViewCullingCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableViewCulling()
enableViewCullingCache = cached
}
return cached
}
override fun enableViewRecycling(): Boolean {
var cached = enableViewRecyclingCache
if (cached == null) {
@@ -279,6 +289,24 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableViewRecyclingForText(): Boolean {
var cached = enableViewRecyclingForTextCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableViewRecyclingForText()
enableViewRecyclingForTextCache = cached
}
return cached
}
override fun enableViewRecyclingForView(): Boolean {
var cached = enableViewRecyclingForViewCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableViewRecyclingForView()
enableViewRecyclingForViewCache = cached
}
return cached
}
override fun excludeYogaFromRawProps(): Boolean {
var cached = excludeYogaFromRawPropsCache
if (cached == null) {
@@ -342,15 +370,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun loadVectorDrawablesOnImages(): Boolean {
var cached = loadVectorDrawablesOnImagesCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.loadVectorDrawablesOnImages()
loadVectorDrawablesOnImagesCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
if (cached == null) {
@@ -414,15 +433,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun useRuntimeShadowNodeReferenceUpdate(): Boolean {
var cached = useRuntimeShadowNodeReferenceUpdateCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.useRuntimeShadowNodeReferenceUpdate()
useRuntimeShadowNodeReferenceUpdateCache = cached
}
return cached
}
override fun useTurboModuleInterop(): Boolean {
var cached = useTurboModuleInteropCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<2a8b1617f45c251d8e6ceb7c70612104>>
* @generated SignedSource<<c616ff84eacfbdd7640bc1516e72ad8f>>
*/
/**
@@ -74,8 +74,14 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableUIConsistency(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewCulling(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewRecycling(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewRecyclingForText(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewRecyclingForView(): Boolean
@DoNotStrip @JvmStatic public external fun excludeYogaFromRawProps(): Boolean
@DoNotStrip @JvmStatic public external fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean
@@ -90,8 +96,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun lazyAnimationCallbacks(): Boolean
@DoNotStrip @JvmStatic public external fun loadVectorDrawablesOnImages(): Boolean
@DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useAlwaysAvailableJSErrorHandling(): Boolean
@@ -106,8 +110,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun useRawPropsJsiValue(): Boolean
@DoNotStrip @JvmStatic public external fun useRuntimeShadowNodeReferenceUpdate(): Boolean
@DoNotStrip @JvmStatic public external fun useTurboModuleInterop(): Boolean
@DoNotStrip @JvmStatic public external fun useTurboModules(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<115a70b8b6854f14841deb0d98e43902>>
* @generated SignedSource<<964ac42bbe930d8506dcb9d9834460bd>>
*/
/**
@@ -69,8 +69,14 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableUIConsistency(): Boolean = false
override fun enableViewCulling(): Boolean = false
override fun enableViewRecycling(): Boolean = false
override fun enableViewRecyclingForText(): Boolean = true
override fun enableViewRecyclingForView(): Boolean = true
override fun excludeYogaFromRawProps(): Boolean = false
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean = true
@@ -85,8 +91,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun lazyAnimationCallbacks(): Boolean = false
override fun loadVectorDrawablesOnImages(): Boolean = true
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean = false
override fun useAlwaysAvailableJSErrorHandling(): Boolean = false
@@ -101,8 +105,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun useRawPropsJsiValue(): Boolean = false
override fun useRuntimeShadowNodeReferenceUpdate(): Boolean = true
override fun useTurboModuleInterop(): Boolean = false
override fun useTurboModules(): Boolean = false
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<7fde7cbd79c60ae151f7c4585363f654>>
* @generated SignedSource<<39af73b5dd34ee875ac898945dc7b4e7>>
*/
/**
@@ -47,7 +47,10 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableReportEventPaintTimeCache: Boolean? = null
private var enableSynchronousStateUpdatesCache: Boolean? = null
private var enableUIConsistencyCache: Boolean? = null
private var enableViewCullingCache: Boolean? = null
private var enableViewRecyclingCache: Boolean? = null
private var enableViewRecyclingForTextCache: Boolean? = null
private var enableViewRecyclingForViewCache: Boolean? = null
private var excludeYogaFromRawPropsCache: Boolean? = null
private var fixDifferentiatorEmittingUpdatesWithWrongParentTagCache: Boolean? = null
private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null
@@ -55,7 +58,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var fuseboxEnabledReleaseCache: Boolean? = null
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var loadVectorDrawablesOnImagesCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null
@@ -63,7 +65,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
private var useRuntimeShadowNodeReferenceUpdateCache: Boolean? = null
private var useTurboModuleInteropCache: Boolean? = null
private var useTurboModulesCache: Boolean? = null
@@ -297,6 +298,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableViewCulling(): Boolean {
var cached = enableViewCullingCache
if (cached == null) {
cached = currentProvider.enableViewCulling()
accessedFeatureFlags.add("enableViewCulling")
enableViewCullingCache = cached
}
return cached
}
override fun enableViewRecycling(): Boolean {
var cached = enableViewRecyclingCache
if (cached == null) {
@@ -307,6 +318,26 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableViewRecyclingForText(): Boolean {
var cached = enableViewRecyclingForTextCache
if (cached == null) {
cached = currentProvider.enableViewRecyclingForText()
accessedFeatureFlags.add("enableViewRecyclingForText")
enableViewRecyclingForTextCache = cached
}
return cached
}
override fun enableViewRecyclingForView(): Boolean {
var cached = enableViewRecyclingForViewCache
if (cached == null) {
cached = currentProvider.enableViewRecyclingForView()
accessedFeatureFlags.add("enableViewRecyclingForView")
enableViewRecyclingForViewCache = cached
}
return cached
}
override fun excludeYogaFromRawProps(): Boolean {
var cached = excludeYogaFromRawPropsCache
if (cached == null) {
@@ -377,16 +408,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun loadVectorDrawablesOnImages(): Boolean {
var cached = loadVectorDrawablesOnImagesCache
if (cached == null) {
cached = currentProvider.loadVectorDrawablesOnImages()
accessedFeatureFlags.add("loadVectorDrawablesOnImages")
loadVectorDrawablesOnImagesCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
if (cached == null) {
@@ -457,16 +478,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun useRuntimeShadowNodeReferenceUpdate(): Boolean {
var cached = useRuntimeShadowNodeReferenceUpdateCache
if (cached == null) {
cached = currentProvider.useRuntimeShadowNodeReferenceUpdate()
accessedFeatureFlags.add("useRuntimeShadowNodeReferenceUpdate")
useRuntimeShadowNodeReferenceUpdateCache = cached
}
return cached
}
override fun useTurboModuleInterop(): Boolean {
var cached = useTurboModuleInteropCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<bf4fee68309fdc7f9270f44ab5535c14>>
* @generated SignedSource<<28634dd2fab612ceddaa3c1a39e9c617>>
*/
/**
@@ -69,8 +69,14 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableUIConsistency(): Boolean
@DoNotStrip public fun enableViewCulling(): Boolean
@DoNotStrip public fun enableViewRecycling(): Boolean
@DoNotStrip public fun enableViewRecyclingForText(): Boolean
@DoNotStrip public fun enableViewRecyclingForView(): Boolean
@DoNotStrip public fun excludeYogaFromRawProps(): Boolean
@DoNotStrip public fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean
@@ -85,8 +91,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun lazyAnimationCallbacks(): Boolean
@DoNotStrip public fun loadVectorDrawablesOnImages(): Boolean
@DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip public fun useAlwaysAvailableJSErrorHandling(): Boolean
@@ -101,8 +105,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun useRawPropsJsiValue(): Boolean
@DoNotStrip public fun useRuntimeShadowNodeReferenceUpdate(): Boolean
@DoNotStrip public fun useTurboModuleInterop(): Boolean
@DoNotStrip public fun useTurboModules(): Boolean
@@ -20,7 +20,6 @@ import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.common.ReactConstants
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.modules.common.ModuleDataCleaner
import com.facebook.react.modules.network.ForwardingCookieHandler
@@ -164,9 +163,7 @@ constructor(
.setNetworkFetcher(ReactOkHttpNetworkFetcher(client))
.setDownsampleMode(DownsampleMode.AUTO)
.setRequestListeners(requestListeners)
builder
.experiment()
.setBinaryXmlEnabled(ReactNativeFeatureFlags.loadVectorDrawablesOnImages())
builder.experiment().setBinaryXmlEnabled(true)
return builder
}
}
@@ -16,7 +16,7 @@ import okhttp3.Headers
import okhttp3.HttpUrl
/** Basic okhttp3 CookieJar container */
internal class ReactCookieJarContainer : CookieJarContainer {
public class ReactCookieJarContainer : CookieJarContainer {
private var cookieJar: CookieJar? = null
@@ -21,14 +21,13 @@ internal object ResponseUtil {
progress: Long,
total: Long
) {
val args =
reactContext?.emitDeviceEvent(
"didSendNetworkData",
Arguments.createArray().apply {
pushInt(requestId)
pushInt(progress.toInt())
pushInt(total.toInt())
}
reactContext?.emitDeviceEvent("didSendNetworkData", args)
})
}
@JvmStatic
@@ -39,15 +38,14 @@ internal object ResponseUtil {
progress: Long,
total: Long
) {
val args =
reactContext?.emitDeviceEvent(
"didReceiveNetworkIncrementalData",
Arguments.createArray().apply {
pushInt(requestId)
pushString(data)
pushInt(progress.toInt())
pushInt(total.toInt())
}
reactContext?.emitDeviceEvent("didReceiveNetworkIncrementalData", args)
})
}
@JvmStatic
@@ -57,36 +55,33 @@ internal object ResponseUtil {
progress: Long,
total: Long
) {
val args =
reactContext?.emitDeviceEvent(
"didReceiveNetworkDataProgress",
Arguments.createArray().apply {
pushInt(requestId)
pushInt(progress.toInt())
pushInt(total.toInt())
}
reactContext?.emitDeviceEvent("didReceiveNetworkDataProgress", args)
})
}
@JvmStatic
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: String?) {
val args =
reactContext?.emitDeviceEvent(
"didReceiveNetworkData",
Arguments.createArray().apply {
pushInt(requestId)
pushString(data)
}
reactContext?.emitDeviceEvent("didReceiveNetworkData", args)
})
}
@JvmStatic
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: WritableMap?) {
val args =
reactContext?.emitDeviceEvent(
"didReceiveNetworkData",
Arguments.createArray().apply {
pushInt(requestId)
pushMap(data)
}
reactContext?.emitDeviceEvent("didReceiveNetworkData", args)
})
}
@JvmStatic
@@ -96,28 +91,25 @@ internal object ResponseUtil {
error: String?,
e: Throwable?
) {
val args =
reactContext?.emitDeviceEvent(
"didCompleteNetworkResponse",
Arguments.createArray().apply {
pushInt(requestId)
pushString(error)
}
if ((e != null) && (e.javaClass == SocketTimeoutException::class.java)) {
args.pushBoolean(true) // last argument is a time out boolean
}
reactContext?.emitDeviceEvent("didCompleteNetworkResponse", args)
if (e?.javaClass == SocketTimeoutException::class.java) {
pushBoolean(true) // last argument is a time out boolean
}
})
}
@JvmStatic
fun onRequestSuccess(reactContext: ReactApplicationContext?, requestId: Int) {
val args =
reactContext?.emitDeviceEvent(
"didCompleteNetworkResponse",
Arguments.createArray().apply {
pushInt(requestId)
pushNull()
}
reactContext?.emitDeviceEvent("didCompleteNetworkResponse", args)
})
}
@JvmStatic
@@ -128,14 +120,13 @@ internal object ResponseUtil {
headers: WritableMap?,
url: String?
) {
val args =
reactContext?.emitDeviceEvent(
"didReceiveNetworkResponse",
Arguments.createArray().apply {
pushInt(requestId)
pushInt(statusCode)
pushMap(headers)
pushString(url)
}
reactContext?.emitDeviceEvent("didReceiveNetworkResponse", args)
})
}
}
@@ -12,5 +12,5 @@ import com.facebook.proguard.annotations.DoNotStripAny
@DoNotStripAny
internal interface ComponentNameResolver {
/* returns a list of all the component names that are registered in React Native. */
public val componentNames: Array<String>?
val componentNames: Array<String>?
}
@@ -37,6 +37,7 @@ import java.util.Set;
public class JSPointerDispatcher {
private static final int UNSELECTED_VIEW_TAG = -1;
private static final int UNSET_POINTER_ID = -1;
private static final int UNSET_CHILD_VIEW_ID = -1;
private static final float ONMOVE_EPSILON = 0.1f;
private static final String TAG = "PointerEvents";
@@ -45,7 +46,7 @@ public class JSPointerDispatcher {
private Map<Integer, List<ViewTarget>> mCurrentlyDownPointerIdsToHitPath;
private Set<Integer> mHoveringPointerIds = new HashSet<>();
private int mChildHandlingNativeGesture = -1;
private int mChildHandlingNativeGesture = UNSET_CHILD_VIEW_ID;
private int mPrimaryPointerId = UNSET_POINTER_ID;
private int mCoalescingKey = 0;
private int mLastButtonState = 0;
@@ -62,7 +63,7 @@ public class JSPointerDispatcher {
public void onChildStartedNativeGesture(
View childView, MotionEvent motionEvent, EventDispatcher eventDispatcher) {
if (mChildHandlingNativeGesture != -1 || childView == null) {
if (mChildHandlingNativeGesture != UNSET_CHILD_VIEW_ID || childView == null) {
// This means we previously had another child start handling this native gesture and now a
// different native parent of that child has decided to intercept the touch stream and handle
// the gesture itself. Example where this can happen: HorizontalScrollView in a ScrollView.
@@ -92,7 +93,7 @@ public class JSPointerDispatcher {
public void onChildEndedNativeGesture() {
// There should be only one child gesture at any given time. We can safely turn off the flag.
mChildHandlingNativeGesture = -1;
mChildHandlingNativeGesture = UNSET_CHILD_VIEW_ID;
}
// returns the section of the hit path shared by both lists, or an empty list if there's no such
@@ -280,7 +281,7 @@ public class JSPointerDispatcher {
public void handleMotionEvent(
MotionEvent motionEvent, EventDispatcher eventDispatcher, boolean isCapture) {
// Don't fire any pointer events if child view is handling native gesture
if (mChildHandlingNativeGesture != -1) {
if (mChildHandlingNativeGesture != UNSET_CHILD_VIEW_ID) {
return;
}
@@ -609,7 +610,7 @@ public class JSPointerDispatcher {
// expected to happen very often as it would mean some child View has decided to intercept the
// touch stream and start a native gesture only upon receiving the UP/CANCEL event.
Assertions.assertCondition(
mChildHandlingNativeGesture == -1,
mChildHandlingNativeGesture == UNSET_CHILD_VIEW_ID,
"Expected to not have already sent a cancel for this gesture");
int activePointerId = eventState.getActivePointerId();
@@ -12,7 +12,7 @@ import com.facebook.yoga.YogaDirection
internal object LayoutDirectionUtil {
@JvmStatic
public fun toAndroidFromYoga(direction: YogaDirection): Int =
fun toAndroidFromYoga(direction: YogaDirection): Int =
when (direction) {
YogaDirection.LTR -> View.LAYOUT_DIRECTION_LTR
YogaDirection.RTL -> View.LAYOUT_DIRECTION_RTL
@@ -20,7 +20,7 @@ internal object LayoutDirectionUtil {
}
@JvmStatic
public fun toYogaFromAndroid(direction: Int): YogaDirection =
fun toYogaFromAndroid(direction: Int): YogaDirection =
when (direction) {
View.LAYOUT_DIRECTION_LTR -> YogaDirection.LTR
View.LAYOUT_DIRECTION_RTL -> YogaDirection.RTL
@@ -17,5 +17,5 @@ public interface ReactOverflowView {
* Gets the overflow state of a view. If set, this should be one of [ViewProps#HIDDEN],
* [ViewProps#VISIBLE] or [ViewProps#SCROLL].
*/
public fun getOverflow(): String?
public val overflow: String?
}
@@ -13,6 +13,6 @@ package com.facebook.react.uimanager
*/
public interface ReactPointerEventsView {
/** Return the PointerEvents of the View. */
public fun getPointerEvents(): PointerEvents
/** The PointerEvents of the View. */
public val pointerEvents: PointerEvents
}
@@ -10,8 +10,8 @@ package com.facebook.react.uimanager.events
import com.facebook.react.bridge.WritableMap
@Deprecated("Experimental")
public interface SynchronousEventReceiver {
public fun receiveEvent(
internal interface SynchronousEventReceiver {
fun receiveEvent(
surfaceId: Int,
reactTag: Int,
eventName: String,
@@ -1,24 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.views.common
import android.view.View
import com.facebook.react.R
/** Class containing static methods involving manipulations of Views */
public object ViewUtils {
/**
* Returns value of testId for the given view, if present
*
* @param view View to get the testId value for
* @return the value of testId if defined for the view, otherwise null
*/
@JvmStatic
public fun getTestId(view: View?): String? = view?.getTag(R.id.react_test_id) as? String
}

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