mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e5b03474 | ||
|
|
3efbe33ce0 | ||
|
|
4134b1c60d | ||
|
|
87ec0965a2 | ||
|
|
c6f12254d1 | ||
|
|
e9f279117e | ||
|
|
7a81fd7a8a | ||
|
|
be9b076087 | ||
|
|
db70b791ba | ||
|
|
da6d089305 | ||
|
|
1243679fe2 | ||
|
|
18ebea533d | ||
|
|
e750059d98 | ||
|
|
e7b9d70e0a | ||
|
|
13900d75b8 | ||
|
|
2f0977d8e4 | ||
|
|
00d5caee99 | ||
|
|
ea84bc666c | ||
|
|
ca908c0681 | ||
|
|
eee5d2ec3c | ||
|
|
efd57d681c | ||
|
|
bc0b5ca5df | ||
|
|
f6aae38e52 | ||
|
|
2e7e065d09 | ||
|
|
b487e65869 | ||
|
|
ff934cd249 | ||
|
|
118c18f3bb | ||
|
|
e7f943de2f | ||
|
|
8aac234ce2 | ||
|
|
f402ed17fa | ||
|
|
734730df75 | ||
|
|
50d0157f0c | ||
|
|
2e444d2b40 | ||
|
|
366270e742 | ||
|
|
469978f170 | ||
|
|
7ee7e69cdf | ||
|
|
efcfe5dcd6 | ||
|
|
b5b9e032c2 | ||
|
|
4c62d46525 | ||
|
|
0217d7e19c | ||
|
|
74f3ab7d40 | ||
|
|
949d229b5f | ||
|
|
52f09276cc | ||
|
|
5e7eb9f3a6 | ||
|
|
725527885a | ||
|
|
d19f5f97b8 | ||
|
|
9aa21b5e87 | ||
|
|
9800c8e47e | ||
|
|
34901d4861 | ||
|
|
21c9491926 | ||
|
|
9df20d414e | ||
|
|
d6f286a4a0 | ||
|
|
318db8eedf | ||
|
|
071d223ee0 | ||
|
|
67bff8734f | ||
|
|
b25b65ba19 | ||
|
|
a4849cb3d6 | ||
|
|
385b9f4265 | ||
|
|
bfc8b3391e | ||
|
|
2df7552fa2 | ||
|
|
e1623b7525 | ||
|
|
fa8a25eb6b | ||
|
|
96c2be8567 | ||
|
|
53bc661138 | ||
|
|
69ecaef068 | ||
|
|
215b0a50cf | ||
|
|
11c214d0c9 | ||
|
|
dc88683798 | ||
|
|
1a9780f0e3 | ||
|
|
72bb2f4089 | ||
|
|
70a957452c | ||
|
|
a4310ede9c | ||
|
|
0580e88aa5 | ||
|
|
762389f775 | ||
|
|
4bced9099a | ||
|
|
21cc745d51 | ||
|
|
9147b0753a | ||
|
|
2480de6ffb | ||
|
|
b6f2b148f4 | ||
|
|
60a4d22307 | ||
|
|
09f6d165ec | ||
|
|
88c9a42fca | ||
|
|
28ced2e558 | ||
|
|
b8095f4692 | ||
|
|
44b04b6d42 | ||
|
|
fa03840e68 | ||
|
|
490db92562 | ||
|
|
e996b3f346 | ||
|
|
f791fb9e66 | ||
|
|
b886bc4db9 | ||
|
|
91e217ff54 | ||
|
|
5ff59b448b | ||
|
|
de30f408e5 | ||
|
|
212a743fdf | ||
|
|
b27bd00a38 | ||
|
|
2aa79979d3 | ||
|
|
2fcf7b1f49 | ||
|
|
7ccc5934d0 | ||
|
|
bc9e4db9e9 |
@@ -43,9 +43,6 @@ runs:
|
||||
shell: powershell
|
||||
run: |
|
||||
if (-not(Test-Path -Path $Env:HERMES_WS_DIR\win64-bin\hermesc.exe)) {
|
||||
choco install --no-progress cmake --version 3.14.7 --allow-downgrade
|
||||
if (-not $?) { throw "Failed to install CMake" }
|
||||
|
||||
cd $Env:HERMES_WS_DIR\icu
|
||||
# If Invoke-WebRequest shows a progress bar, it will fail with
|
||||
# Win32 internal error "Access is denied" 0x5 occurred [...]
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
name: Trigger E2E Tests on Comment
|
||||
# This workflow is used to automatically trigger E2E tests when a comment is made
|
||||
# containing the text "/run-e2e-tests".
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
trigger-e2e-tests:
|
||||
name: Trigger E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/test-e2e')
|
||||
steps:
|
||||
# This is needed because of https://github.com/actions/runner-images/issues/6283
|
||||
# TL;DR: brew is not in the PATH anymore.
|
||||
- name: Setup Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Install jq
|
||||
run: brew install jq
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
# Github does not provide the branch of a PR when a comment on a PR is made
|
||||
# So, given the issue number, which is the PR number, we can retrieve the branch with
|
||||
# a quick API call
|
||||
echo "Retrieving branch"
|
||||
BRANCH=$(curl -L \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/facebook/react-native/pulls/$PR_NUMBER | jq -r '.head.ref')
|
||||
|
||||
echo "Trigger Test All workflow for branch $BRANCH"
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/facebook/react-native/actions/workflows/test-all.yml/dispatches \
|
||||
-d "{\"ref\": \"$BRANCH\", \"inputs\": {\"run-e2e-tests\": \"true\"}}"
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.issue.number }}
|
||||
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## v0.77.0-rc.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Replace Object.hasOwn usages to fix Animated on JSC ([e996b3f346](https://github.com/facebook/react-native/commit/e996b3f346462a394012a722ce19990cdf9c3d9a) by [@robhogan](https://github.com/robhogan))
|
||||
- Remove non compliant `filename*` attribute in a FormData `content-disposition` header ([f791fb9e66](https://github.com/facebook/react-native/commit/f791fb9e660fe15bccf55029045c48f4bbcbc5cb) by [@foyarash](https://github.com/foyarash))
|
||||
- Fix "punycode is deprecated" warning by replacing `node-fetch` with native `fetch` ([881d8a720f](https://github.com/facebook/react-native/commit/881d8a720fb24241d7b2127273ca6116833bf176) by [@jbroma](https://github.com/jbroma))
|
||||
|
||||
#### Android specific
|
||||
|
||||
- Reverted removal of TurboReactPackage ([70a957452c](https://github.com/facebook/react-native/commit/70a957452c438a74787f4f752b2c274360cb2edd) by [@javache](https://github.com/javache))
|
||||
- Fix IOException in `BuildCodegenCLITask` ([9147b0753a](https://github.com/facebook/react-native/commit/9147b0753a6c3afb2480b079f91614cd7189a28a) by [@vonovak](https://github.com/vonovak))
|
||||
|
||||
## v0.77.0-rc.0
|
||||
|
||||
### Breaking
|
||||
|
||||
@@ -67,7 +67,7 @@ React Native is developed and supported by many companies and individual core co
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
React Native apps may target iOS 13.4 and Android 6.0 (API 23) or newer. You may use Windows, macOS, or Linux as your development operating system, though building and running iOS apps is limited to macOS. Tools like [Expo](https://expo.dev) can be used to work around this.
|
||||
React Native apps may target iOS 15.1 and Android 7.0 (API 24) or newer. You may use Windows, macOS, or Linux as your development operating system, though building and running iOS apps is limited to macOS. Tools like [Expo](https://expo.dev) can be used to work around this.
|
||||
|
||||
## 🎉 Building your first React Native app
|
||||
|
||||
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
*
|
||||
* @flow strict
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
declare module 'jest-diff' {
|
||||
import type {CompareKeys} from 'pretty-format';
|
||||
|
||||
declare export type DiffOptionsColor = (arg: string) => string; // subset of Chalk type
|
||||
|
||||
declare export type DiffOptions = {
|
||||
aAnnotation?: string,
|
||||
aColor?: DiffOptionsColor,
|
||||
aIndicator?: string,
|
||||
bAnnotation?: string,
|
||||
bColor?: DiffOptionsColor,
|
||||
bIndicator?: string,
|
||||
changeColor?: DiffOptionsColor,
|
||||
changeLineTrailingSpaceColor?: DiffOptionsColor,
|
||||
commonColor?: DiffOptionsColor,
|
||||
commonIndicator?: string,
|
||||
commonLineTrailingSpaceColor?: DiffOptionsColor,
|
||||
contextLines?: number,
|
||||
emptyFirstOrLastLinePlaceholder?: string,
|
||||
expand?: boolean,
|
||||
includeChangeCounts?: boolean,
|
||||
omitAnnotationLines?: boolean,
|
||||
patchColor?: DiffOptionsColor,
|
||||
compareKeys?: CompareKeys,
|
||||
};
|
||||
|
||||
declare export function diff(
|
||||
a: mixed,
|
||||
b: mixed,
|
||||
options?: DiffOptions,
|
||||
): string | null;
|
||||
}
|
||||
+4
-1
@@ -19,7 +19,6 @@ declare type Colors = {
|
||||
tag: {close: string, open: string},
|
||||
value: {close: string, open: string},
|
||||
};
|
||||
declare type CompareKeys = ((a: string, b: string) => number) | null | void;
|
||||
|
||||
declare type PrettyFormatPlugin =
|
||||
| {
|
||||
@@ -38,6 +37,10 @@ declare type PrettyFormatPlugin =
|
||||
};
|
||||
|
||||
declare module 'pretty-format' {
|
||||
declare export type CompareKeys =
|
||||
| ((a: string, b: string) => number)
|
||||
| null
|
||||
| void;
|
||||
declare export function format(
|
||||
value: mixed,
|
||||
options?: ?{
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -25,4 +25,5 @@ module.exports = {
|
||||
transformIgnorePatterns: ['.*'],
|
||||
testRunner: './jest/integration/runner/index.js',
|
||||
watchPathIgnorePatterns: ['<rootDir>/jest/integration/build/'],
|
||||
globalSetup: './jest/integration/runner/warmup/index.js',
|
||||
};
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {FantomTestConfigJsOnlyFeatureFlags} from './getFantomTestConfig';
|
||||
|
||||
module.exports = function entrypointTemplate({
|
||||
testPath,
|
||||
setupModulePath,
|
||||
featureFlagsModulePath,
|
||||
featureFlags,
|
||||
}: {
|
||||
testPath: string,
|
||||
setupModulePath: string,
|
||||
featureFlagsModulePath: string,
|
||||
featureFlags: FantomTestConfigJsOnlyFeatureFlags,
|
||||
}): string {
|
||||
return `/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
@@ -29,6 +35,17 @@ module.exports = function entrypointTemplate({
|
||||
*/
|
||||
|
||||
import {registerTest} from '${setupModulePath}';
|
||||
${
|
||||
Object.keys(featureFlags).length > 0
|
||||
? `import * as ReactNativeFeatureFlags from '${featureFlagsModulePath}';
|
||||
|
||||
ReactNativeFeatureFlags.override({
|
||||
${Object.entries(featureFlags)
|
||||
.map(([name, value]) => ` ${name}: () => ${JSON.stringify(value)},`)
|
||||
.join('\n')}
|
||||
});`
|
||||
: ''
|
||||
}
|
||||
|
||||
registerTest(() => require('${testPath}'));
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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 ReactNativeFeatureFlags from '../../../packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {extract, parse} from 'jest-docblock';
|
||||
|
||||
type CommonFeatureFlags = (typeof ReactNativeFeatureFlags)['common'];
|
||||
type JsOnlyFeatureFlags = (typeof ReactNativeFeatureFlags)['jsOnly'];
|
||||
|
||||
type DocblockPragmas = {[key: string]: string | string[]};
|
||||
|
||||
export type FantomTestConfigMode = 'dev' | 'opt';
|
||||
|
||||
export type FantomTestConfigCommonFeatureFlags = Partial<{
|
||||
[key in keyof CommonFeatureFlags]: CommonFeatureFlags[key]['defaultValue'],
|
||||
}>;
|
||||
|
||||
export type FantomTestConfigJsOnlyFeatureFlags = Partial<{
|
||||
[key in keyof JsOnlyFeatureFlags]: JsOnlyFeatureFlags[key]['defaultValue'],
|
||||
}>;
|
||||
|
||||
export type FantomTestConfig = {
|
||||
mode: FantomTestConfigMode,
|
||||
flags: {
|
||||
common: FantomTestConfigCommonFeatureFlags,
|
||||
jsOnly: FantomTestConfigJsOnlyFeatureFlags,
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_MODE: FantomTestConfigMode = 'dev';
|
||||
|
||||
const FANTOM_FLAG_FORMAT = /^(\w+):(\w+)$/;
|
||||
|
||||
/**
|
||||
* Extracts the Fantom configuration from the test file, specified as part of
|
||||
* the docblock comment. E.g.:
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @flow strict-local
|
||||
* * @fantom_mode opt
|
||||
* * @fantom_flags commonTestFlag:true
|
||||
* * @fantom_flags jsOnlyTestFlag:true
|
||||
* *
|
||||
* ```
|
||||
*
|
||||
* The supported options are:
|
||||
* - `fantom_mode`: specifies the level of optimization to compile the test
|
||||
* with. Valid values are `dev` and `opt`.
|
||||
* - `fantom_flags`: specifies the configuration for common and JS-only feature
|
||||
* flags. They can be specified in the same pragma or in different ones, and
|
||||
* the format is `<flag_name>:<value>`.
|
||||
*/
|
||||
export default function getFantomTestConfig(
|
||||
testPath: string,
|
||||
): FantomTestConfig {
|
||||
const docblock = extract(fs.readFileSync(testPath, 'utf8'));
|
||||
const pragmas = parse(docblock) as DocblockPragmas;
|
||||
|
||||
const config: FantomTestConfig = {
|
||||
mode: DEFAULT_MODE,
|
||||
flags: {
|
||||
common: {},
|
||||
jsOnly: {},
|
||||
},
|
||||
};
|
||||
|
||||
const maybeMode = pragmas.fantom_mode;
|
||||
|
||||
if (maybeMode != null) {
|
||||
if (Array.isArray(maybeMode)) {
|
||||
throw new Error('Expected a single value for @fantom_mode');
|
||||
}
|
||||
|
||||
const mode = maybeMode;
|
||||
|
||||
if (mode === 'dev' || mode === 'opt') {
|
||||
config.mode = mode;
|
||||
} else {
|
||||
throw new Error(`Invalid Fantom mode: ${mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
const maybeRawFlagConfig = pragmas.fantom_flags;
|
||||
|
||||
if (maybeRawFlagConfig != null) {
|
||||
const rawFlagConfigs = (
|
||||
Array.isArray(maybeRawFlagConfig)
|
||||
? maybeRawFlagConfig
|
||||
: [maybeRawFlagConfig]
|
||||
).flatMap(value => value.split(/\s+/g));
|
||||
|
||||
for (const rawFlagConfig of rawFlagConfigs) {
|
||||
const matches = FANTOM_FLAG_FORMAT.exec(rawFlagConfig);
|
||||
if (matches == null) {
|
||||
throw new Error(
|
||||
`Invalid format for Fantom feature flag: ${rawFlagConfig}. Expected <flag_name>:<value>`,
|
||||
);
|
||||
}
|
||||
|
||||
const [, name, rawValue] = matches;
|
||||
|
||||
if (ReactNativeFeatureFlags.common[name]) {
|
||||
const flagConfig = ReactNativeFeatureFlags.common[name];
|
||||
const value = parseFeatureFlagValue(flagConfig.defaultValue, rawValue);
|
||||
config.flags.common[name] = value;
|
||||
} else if (ReactNativeFeatureFlags.jsOnly[name]) {
|
||||
const flagConfig = ReactNativeFeatureFlags.jsOnly[name];
|
||||
const value = parseFeatureFlagValue(flagConfig.defaultValue, rawValue);
|
||||
config.flags.jsOnly[name] = value;
|
||||
} else {
|
||||
const validKeys = Object.keys(ReactNativeFeatureFlags.common)
|
||||
.concat(Object.keys(ReactNativeFeatureFlags.jsOnly))
|
||||
.join(', ');
|
||||
|
||||
throw new Error(
|
||||
`Invalid Fantom feature flag: ${name}. Valid flags are: ${validKeys}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function parseFeatureFlagValue<T: boolean | number | string>(
|
||||
defaultValue: T,
|
||||
value: string,
|
||||
): T {
|
||||
switch (typeof defaultValue) {
|
||||
case 'boolean':
|
||||
if (value === 'true') {
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a boolean
|
||||
return true;
|
||||
} else if (value === 'false') {
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a boolean
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(`Invalid value for boolean flag: ${value}`);
|
||||
}
|
||||
case 'number':
|
||||
const parsed = Number(value);
|
||||
|
||||
if (Number.isNaN(parsed)) {
|
||||
throw new Error(`Invalid value for number flag: ${value}`);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a number
|
||||
return parsed;
|
||||
case 'string':
|
||||
// $FlowExpectedError[incompatible-return] at this point we know T is a string
|
||||
return value;
|
||||
default:
|
||||
throw new Error(`Unsupported feature flag type: ${typeof defaultValue}`);
|
||||
}
|
||||
}
|
||||
@@ -12,25 +12,29 @@
|
||||
import type {TestSuiteResult} from '../runtime/setup';
|
||||
|
||||
import entrypointTemplate from './entrypoint-template';
|
||||
import {spawnSync} from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import getFantomTestConfig from './getFantomTestConfig';
|
||||
import {
|
||||
getBuckModeForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
getShortHash,
|
||||
runBuck2,
|
||||
symbolicateStackTrace,
|
||||
} from './utils';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {formatResultsErrors} from 'jest-message-util';
|
||||
import Metro from 'metro';
|
||||
import nullthrows from 'nullthrows';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const BUILD_OUTPUT_PATH = path.resolve(__dirname, '..', 'build');
|
||||
|
||||
const ENABLE_OPTIMIZED_MODE: false = false;
|
||||
const PRINT_FANTOM_OUTPUT: false = false;
|
||||
|
||||
function parseRNTesterCommandResult(
|
||||
commandArgs: $ReadOnlyArray<string>,
|
||||
result: ReturnType<typeof spawnSync>,
|
||||
): {logs: string, testResult: TestSuiteResult} {
|
||||
function parseRNTesterCommandResult(result: ReturnType<typeof runBuck2>): {
|
||||
logs: string,
|
||||
testResult: TestSuiteResult,
|
||||
} {
|
||||
const stdout = result.stdout.toString();
|
||||
|
||||
const outputArray = stdout
|
||||
@@ -46,51 +50,26 @@ function parseRNTesterCommandResult(
|
||||
testResult = JSON.parse(nullthrows(testResultJSON));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
[
|
||||
'Failed to parse test results from RN tester binary result. Full output:',
|
||||
'buck2 ' + commandArgs.join(' '),
|
||||
'stdout:',
|
||||
stdout,
|
||||
'stderr:',
|
||||
result.stderr.toString(),
|
||||
].join('\n'),
|
||||
'Failed to parse test results from RN tester binary result.\n' +
|
||||
getDebugInfoFromCommandResult(result),
|
||||
);
|
||||
}
|
||||
|
||||
return {logs: outputArray.join('\n'), testResult};
|
||||
}
|
||||
|
||||
function getBuckModeForPlatform() {
|
||||
const mode = ENABLE_OPTIMIZED_MODE ? 'opt' : 'dev';
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'linux':
|
||||
return `@//arvr/mode/linux/${mode}`;
|
||||
case 'darwin':
|
||||
return os.arch() === 'arm64'
|
||||
? `@//arvr/mode/mac-arm/${mode}`
|
||||
: `@//arvr/mode/mac/${mode}`;
|
||||
case 'win32':
|
||||
return `@//arvr/mode/win/${mode}`;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${os.platform()}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getShortHash(contents: string): string {
|
||||
return crypto.createHash('md5').update(contents).digest('hex').slice(0, 8);
|
||||
}
|
||||
|
||||
function generateBytecodeBundle({
|
||||
sourcePath,
|
||||
bytecodePath,
|
||||
isOptimizedMode,
|
||||
}: {
|
||||
sourcePath: string,
|
||||
bytecodePath: string,
|
||||
isOptimizedMode: boolean,
|
||||
}): void {
|
||||
const hermesCompilerCommandArgs = [
|
||||
const hermesCompilerCommandResult = runBuck2([
|
||||
'run',
|
||||
getBuckModeForPlatform(),
|
||||
getBuckModeForPlatform(isOptimizedMode),
|
||||
'//xplat/hermes/tools/hermesc:hermesc',
|
||||
'--',
|
||||
'-emit-binary',
|
||||
@@ -100,33 +79,10 @@ function generateBytecodeBundle({
|
||||
'-out',
|
||||
bytecodePath,
|
||||
sourcePath,
|
||||
];
|
||||
|
||||
const hermesCompilerCommandResult = spawnSync(
|
||||
'buck2',
|
||||
hermesCompilerCommandArgs,
|
||||
{
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
]);
|
||||
|
||||
if (hermesCompilerCommandResult.status !== 0) {
|
||||
throw new Error(
|
||||
[
|
||||
'Failed to run Hermes compiler. Full output:',
|
||||
'buck2 ' + hermesCompilerCommandArgs.join(' '),
|
||||
'stdout:',
|
||||
hermesCompilerCommandResult.stdout,
|
||||
'stderr:',
|
||||
hermesCompilerCommandResult.stderr,
|
||||
'error:',
|
||||
hermesCompilerCommandResult.error,
|
||||
].join('\n'),
|
||||
);
|
||||
throw new Error(getDebugInfoFromCommandResult(hermesCompilerCommandResult));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,17 +95,25 @@ module.exports = async function runTest(
|
||||
): mixed {
|
||||
const startTime = Date.now();
|
||||
|
||||
const isOptimizedMode = ENABLE_OPTIMIZED_MODE;
|
||||
const testConfig = getFantomTestConfig(testPath);
|
||||
|
||||
const isOptimizedMode = testConfig.mode === 'opt';
|
||||
|
||||
const metroConfig = await Metro.loadConfig({
|
||||
config: path.resolve(__dirname, '..', 'config', 'metro.config.js'),
|
||||
});
|
||||
|
||||
const setupModulePath = path.resolve(__dirname, '../runtime/setup.js');
|
||||
const featureFlagsModulePath = path.resolve(
|
||||
__dirname,
|
||||
'../../../packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js',
|
||||
);
|
||||
|
||||
const entrypointContents = entrypointTemplate({
|
||||
testPath: `${path.relative(BUILD_OUTPUT_PATH, testPath)}`,
|
||||
setupModulePath: `${path.relative(BUILD_OUTPUT_PATH, setupModulePath)}`,
|
||||
featureFlagsModulePath: `${path.relative(BUILD_OUTPUT_PATH, featureFlagsModulePath)}`,
|
||||
featureFlags: testConfig.flags.jsOnly,
|
||||
});
|
||||
|
||||
const entrypointPath = path.join(
|
||||
@@ -163,76 +127,56 @@ module.exports = async function runTest(
|
||||
fs.mkdirSync(path.dirname(entrypointPath), {recursive: true});
|
||||
fs.writeFileSync(entrypointPath, entrypointContents, 'utf8');
|
||||
|
||||
const sourceMapPath = path.join(
|
||||
path.dirname(testJSBundlePath),
|
||||
path.basename(testJSBundlePath, '.js') + '.map',
|
||||
);
|
||||
|
||||
await Metro.runBuild(metroConfig, {
|
||||
entry: entrypointPath,
|
||||
out: testJSBundlePath,
|
||||
platform: 'android',
|
||||
minify: isOptimizedMode,
|
||||
dev: !isOptimizedMode,
|
||||
sourceMap: true,
|
||||
sourceMapUrl: sourceMapPath,
|
||||
});
|
||||
|
||||
if (isOptimizedMode) {
|
||||
generateBytecodeBundle({
|
||||
sourcePath: testJSBundlePath,
|
||||
bytecodePath: testBytecodeBundlePath,
|
||||
isOptimizedMode,
|
||||
});
|
||||
}
|
||||
|
||||
const rnTesterCommandArgs = [
|
||||
const rnTesterCommandResult = runBuck2([
|
||||
'run',
|
||||
getBuckModeForPlatform(),
|
||||
getBuckModeForPlatform(isOptimizedMode),
|
||||
'//xplat/ReactNative/react-native-cxx/samples/tester:tester',
|
||||
'--',
|
||||
'--bundlePath',
|
||||
testBundlePath,
|
||||
];
|
||||
const rnTesterCommandResult = spawnSync('buck2', rnTesterCommandArgs, {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
|
||||
},
|
||||
});
|
||||
'--featureFlags',
|
||||
JSON.stringify(testConfig.flags.common),
|
||||
]);
|
||||
|
||||
if (rnTesterCommandResult.status !== 0) {
|
||||
throw new Error(
|
||||
[
|
||||
'Failed to run test in RN tester binary. Full output:',
|
||||
'buck2 ' + rnTesterCommandArgs.join(' '),
|
||||
'stdout:',
|
||||
rnTesterCommandResult.stdout,
|
||||
'stderr:',
|
||||
rnTesterCommandResult.stderr,
|
||||
'error:',
|
||||
rnTesterCommandResult.error,
|
||||
].join('\n'),
|
||||
);
|
||||
throw new Error(getDebugInfoFromCommandResult(rnTesterCommandResult));
|
||||
}
|
||||
|
||||
if (PRINT_FANTOM_OUTPUT) {
|
||||
console.log(
|
||||
[
|
||||
'RN tester binary. Full output:',
|
||||
'buck2 ' + rnTesterCommandArgs.join(' '),
|
||||
'stdout:',
|
||||
rnTesterCommandResult.stdout,
|
||||
'stderr:',
|
||||
rnTesterCommandResult.stderr,
|
||||
'error:',
|
||||
rnTesterCommandResult.error,
|
||||
].join('\n'),
|
||||
);
|
||||
console.log(getDebugInfoFromCommandResult(rnTesterCommandResult));
|
||||
}
|
||||
|
||||
const rnTesterParsedOutput = parseRNTesterCommandResult(
|
||||
rnTesterCommandArgs,
|
||||
rnTesterCommandResult,
|
||||
);
|
||||
|
||||
const testResultError = rnTesterParsedOutput.testResult.error;
|
||||
if (testResultError) {
|
||||
const error = new Error(testResultError.message);
|
||||
error.stack = testResultError.stack;
|
||||
error.stack = symbolicateStackTrace(sourceMapPath, testResultError.stack);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -248,6 +192,9 @@ module.exports = async function runTest(
|
||||
failureDetails: [] as Array<string>,
|
||||
testFilePath: testPath,
|
||||
...testResult,
|
||||
failureMessages: testResult.failureMessages.map(maybeStackTrace =>
|
||||
symbolicateStackTrace(sourceMapPath, maybeStackTrace),
|
||||
),
|
||||
})) ?? [];
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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 {spawnSync} from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {SourceMapConsumer} from 'source-map';
|
||||
|
||||
export function getBuckModeForPlatform(enableRelease: boolean = false): string {
|
||||
const mode = enableRelease ? 'opt' : 'dev';
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'linux':
|
||||
return `@//arvr/mode/linux/${mode}`;
|
||||
case 'darwin':
|
||||
return os.arch() === 'arm64'
|
||||
? `@//arvr/mode/mac-arm/${mode}`
|
||||
: `@//arvr/mode/mac/${mode}`;
|
||||
case 'win32':
|
||||
return `@//arvr/mode/win/${mode}`;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${os.platform()}`);
|
||||
}
|
||||
}
|
||||
|
||||
type SpawnResultWithOriginalCommand = {
|
||||
...ReturnType<typeof spawnSync>,
|
||||
originalCommand: string,
|
||||
...
|
||||
};
|
||||
|
||||
export function runBuck2(args: Array<string>): SpawnResultWithOriginalCommand {
|
||||
const result = spawnSync('buck2', args, {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
originalCommand: `buck2 ${args.join(' ')}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDebugInfoFromCommandResult(
|
||||
commandResult: SpawnResultWithOriginalCommand,
|
||||
): string {
|
||||
const logLines = [
|
||||
`Command ${commandResult.status === 0 ? 'succeeded' : 'failed'}: ${commandResult.originalCommand}`,
|
||||
'',
|
||||
'stdout:',
|
||||
commandResult.stdout,
|
||||
'',
|
||||
'stderr:',
|
||||
commandResult.stderr,
|
||||
];
|
||||
|
||||
if (commandResult.error) {
|
||||
logLines.push('', 'error:', String(commandResult.error));
|
||||
}
|
||||
|
||||
return logLines.join('\n');
|
||||
}
|
||||
|
||||
export function getShortHash(contents: string): string {
|
||||
return crypto.createHash('md5').update(contents).digest('hex').slice(0, 8);
|
||||
}
|
||||
|
||||
export function symbolicateStackTrace(
|
||||
sourceMapPath: string,
|
||||
stackTrace: string,
|
||||
): string {
|
||||
const sourceMapData = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8'));
|
||||
const consumer = new SourceMapConsumer(sourceMapData);
|
||||
|
||||
return stackTrace
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/at (.*) \((.*):(\d+):(\d+)\)/);
|
||||
if (match) {
|
||||
const functionName = match[1];
|
||||
// const fileName = match[2];
|
||||
const lineNumber = parseInt(match[3], 10);
|
||||
const columnNumber = parseInt(match[4], 10);
|
||||
// Get the original position
|
||||
const originalPosition = consumer.originalPositionFor({
|
||||
line: lineNumber,
|
||||
column: columnNumber,
|
||||
});
|
||||
return `at ${originalPosition.name ?? functionName} (${originalPosition.source}:${originalPosition.line}:${originalPosition.column})`;
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 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
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
require('../../../../scripts/build/babel-register').registerForMonorepo();
|
||||
|
||||
module.exports = require('./warmup');
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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 {
|
||||
getBuckModeForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
runBuck2,
|
||||
} from '../utils';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import fs from 'fs';
|
||||
import Metro from 'metro';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
export default async function warmUp(
|
||||
globalConfig: {...},
|
||||
projectConfig: {...},
|
||||
): Promise<void> {
|
||||
try {
|
||||
warmUpHermesCompiler();
|
||||
warmUpRNTesterCLI();
|
||||
await warmUpMetro();
|
||||
} catch (e) {
|
||||
// Sandcastle fails to parse the test output if we log stuff to stdout/stderr.
|
||||
if ((process.env.SANDCASTLE ?? '') !== '') {
|
||||
console.error(
|
||||
'Global warmup failed. Tests will continue to run but will likely fail. Details:\n',
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function warmUpMetro(): Promise<void> {
|
||||
const metroConfig = await Metro.loadConfig({
|
||||
config: path.resolve(__dirname, '..', '..', 'config', 'metro.config.js'),
|
||||
});
|
||||
|
||||
const entrypointPath = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'runtime',
|
||||
'WarmUpEntryPoint.js',
|
||||
);
|
||||
|
||||
const bundlePath = path.join(
|
||||
os.tmpdir(),
|
||||
`fantom-warmup-bundle-${Date.now()}.js`,
|
||||
);
|
||||
|
||||
await Metro.runBuild(metroConfig, {
|
||||
entry: entrypointPath,
|
||||
out: bundlePath,
|
||||
platform: 'android',
|
||||
minify: false,
|
||||
dev: true,
|
||||
});
|
||||
|
||||
try {
|
||||
fs.unlinkSync(bundlePath);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function warmUpHermesCompiler(): void {
|
||||
const buildHermesCompilerCommandResult = runBuck2([
|
||||
'build',
|
||||
getBuckModeForPlatform(),
|
||||
'//xplat/hermes/tools/hermesc:hermesc',
|
||||
]);
|
||||
|
||||
if (buildHermesCompilerCommandResult.status !== 0) {
|
||||
throw new Error(
|
||||
getDebugInfoFromCommandResult(buildHermesCompilerCommandResult),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function warmUpRNTesterCLI(): void {
|
||||
const buildRNTesterCommandResult = runBuck2([
|
||||
'build',
|
||||
getBuckModeForPlatform(),
|
||||
'//xplat/ReactNative/react-native-cxx/samples/tester:tester',
|
||||
]);
|
||||
|
||||
if (buildRNTesterCommandResult.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(buildRNTesterCommandResult));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is just an entrypoint to warm up the Metro cache before the tests run.
|
||||
*/
|
||||
|
||||
import 'react-native/Libraries/Core/InitializeCore.js';
|
||||
import 'react-native/src/private/__tests__/ReactNativeTester';
|
||||
import './setup';
|
||||
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* 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 {ensureMockFunction} from './mocks';
|
||||
import deepEqual from 'deep-equal';
|
||||
import {diff} from 'jest-diff';
|
||||
|
||||
class ErrorWithCustomBlame extends Error {
|
||||
// Initially 5 to ignore all the frames from Babel helpers to instantiate this
|
||||
// custom error class.
|
||||
#ignoredFrameCount: number = 5;
|
||||
#cachedProcessedStack: ?string;
|
||||
#customStack: ?string;
|
||||
|
||||
blameToPreviousFrame(): this {
|
||||
this.#cachedProcessedStack = null;
|
||||
this.#ignoredFrameCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
get stack(): string {
|
||||
if (this.#cachedProcessedStack == null) {
|
||||
const originalStack = this.#customStack ?? super.stack;
|
||||
|
||||
const lines = originalStack.split('\n');
|
||||
const index = lines.findIndex(line =>
|
||||
/at (.*) \((.*):(\d+):(\d+)\)/.test(line),
|
||||
);
|
||||
lines.splice(index > -1 ? index : 1, this.#ignoredFrameCount);
|
||||
this.#cachedProcessedStack = lines.join('\n');
|
||||
}
|
||||
|
||||
return this.#cachedProcessedStack;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
set stack(value: string) {
|
||||
this.#cachedProcessedStack = null;
|
||||
this.#customStack = value;
|
||||
}
|
||||
|
||||
static fromError(error: Error): ErrorWithCustomBlame {
|
||||
const errorWithCustomBlame = new ErrorWithCustomBlame(error.message);
|
||||
// In this case we're inheriting the error and we don't know if the stack
|
||||
// contains helpers that we need to ignore.
|
||||
errorWithCustomBlame.#ignoredFrameCount = 0;
|
||||
errorWithCustomBlame.stack = error.stack;
|
||||
return errorWithCustomBlame;
|
||||
}
|
||||
}
|
||||
|
||||
class Expect {
|
||||
#received: mixed;
|
||||
#isNot: boolean = false;
|
||||
|
||||
constructor(received: mixed) {
|
||||
this.#received = received;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
get not(): this {
|
||||
this.#isNot = !this.#isNot;
|
||||
return this;
|
||||
}
|
||||
|
||||
toEqual(expected: mixed): void {
|
||||
const pass = deepEqual(this.#received, expected, {strict: true});
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} to equal:\n${
|
||||
diff(expected, this.#received, {
|
||||
contextLines: 1,
|
||||
expand: false,
|
||||
omitAnnotationLines: true,
|
||||
}) ?? 'Failed to compare outputs'
|
||||
}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBe(expected: mixed): void {
|
||||
const pass = this.#received === expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeInstanceOf(expected: Class<mixed>): void {
|
||||
const pass = this.#received instanceof expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be an instance of ${String(expected)}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeCloseTo(expected: number, precision: number = 2): void {
|
||||
const pass =
|
||||
Math.abs(expected - Number(this.#received)) < Math.pow(10, -precision);
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeNull(): void {
|
||||
const pass = this.#received == null;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be null`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toThrow(expected?: string): void {
|
||||
if (expected != null && typeof expected !== 'string') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
'toThrow() implementation only accepts strings as arguments.',
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
let pass = false;
|
||||
try {
|
||||
// $FlowExpectedError[not-a-function]
|
||||
this.#received();
|
||||
} catch (error) {
|
||||
pass = expected != null ? error.message === expected : true;
|
||||
}
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalled(): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length > 0;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalledTimes(times: number): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length === times;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeGreaterThan(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received > expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be greater than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeGreaterThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received >= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be greater than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeLessThan(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received < expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be less than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeLessThanOrEqual(expected: number): void {
|
||||
if (typeof this.#received !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a number but it was a ${typeof this.#received}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
if (typeof expected !== 'number') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(expected)} to be a number but it was a ${typeof expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
const pass = this.#received <= expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be less than or equal to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
#isExpectedResult(pass: boolean): boolean {
|
||||
return this.#isNot ? !pass : pass;
|
||||
}
|
||||
|
||||
#maybeNotLabel(): string {
|
||||
return this.#isNot ? ' not' : '';
|
||||
}
|
||||
|
||||
#requireMock(): JestMockFn<Array<mixed>, mixed>['mock'] {
|
||||
try {
|
||||
return ensureMockFunction(this.#received).mock;
|
||||
} catch (error) {
|
||||
const errorWithCustomBlame = ErrorWithCustomBlame.fromError(error);
|
||||
errorWithCustomBlame.message = `Expected ${String(this.#received)} to be a mock function, but it wasn't`;
|
||||
errorWithCustomBlame
|
||||
.blameToPreviousFrame() // ignore `ensureMockFunction`
|
||||
.blameToPreviousFrame() // ignore `requireMock`
|
||||
.blameToPreviousFrame(); // ignore `expect().[method]`
|
||||
throw errorWithCustomBlame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const expect: mixed => Expect = (received: mixed) => new Expect(received);
|
||||
|
||||
export default expect;
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export const MOCK_FN_TAG: symbol = Symbol('mock function');
|
||||
|
||||
// The type is defined this way because if we get a mixed value, we return
|
||||
// a generic mock function, and if we get a typed function, we get a typed mock.
|
||||
export const ensureMockFunction: (<TArgs: Array<mixed>, TReturn>(
|
||||
fn: (...TArgs) => TReturn,
|
||||
) => JestMockFn<TArgs, TReturn>) &
|
||||
((fn: mixed) => JestMockFn<Array<mixed>, mixed>) = fn => {
|
||||
// $FlowExpectedError[invalid-computed-prop]
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
if (typeof fn !== 'function' || !fn[MOCK_FN_TAG]) {
|
||||
throw new Error(
|
||||
`Expected ${String(fn)} to be a mock function, but it wasn't`,
|
||||
);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
// $FlowExpectedError[prop-missing]
|
||||
return fn;
|
||||
};
|
||||
|
||||
export function createMockFunction<TArgs: Array<mixed>, TReturn>(
|
||||
initialImplementation?: (...TArgs) => TReturn,
|
||||
): JestMockFn<TArgs, TReturn> {
|
||||
let implementation: ?(...TArgs) => TReturn = initialImplementation;
|
||||
|
||||
const mock: JestMockFn<TArgs, TReturn>['mock'] = {
|
||||
calls: [],
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
lastCall: undefined,
|
||||
instances: [],
|
||||
contexts: [],
|
||||
results: [],
|
||||
};
|
||||
|
||||
const mockFunction = function (this: mixed, ...args: TArgs): TReturn {
|
||||
let result: JestMockFn<TArgs, TReturn>['mock']['results'][number] = {
|
||||
isThrow: false,
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
if (implementation != null) {
|
||||
try {
|
||||
result.value = implementation.apply(this, args);
|
||||
} catch (error) {
|
||||
result.isThrow = true;
|
||||
result.value = error;
|
||||
}
|
||||
}
|
||||
|
||||
mock.calls.push(args);
|
||||
mock.lastCall = args;
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
mock.instances.push(new.target ? this : undefined);
|
||||
mock.contexts.push(this);
|
||||
mock.results.push(result);
|
||||
|
||||
if (result.isThrow) {
|
||||
throw result.value;
|
||||
}
|
||||
|
||||
return result.value;
|
||||
};
|
||||
|
||||
mockFunction.mock = mock;
|
||||
// $FlowExpectedError[invalid-computed-prop]
|
||||
mockFunction[MOCK_FN_TAG] = true;
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
return mockFunction;
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import deepEqual from 'deep-equal';
|
||||
import expect from './expect';
|
||||
import {createMockFunction} from './mocks';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
export type TestCaseResult = {
|
||||
@@ -119,218 +120,7 @@ global.jest = {
|
||||
fn: createMockFunction,
|
||||
};
|
||||
|
||||
const MOCK_FN_TAG = Symbol('mock function');
|
||||
|
||||
function createMockFunction<TArgs: $ReadOnlyArray<mixed>, TReturn>(
|
||||
initialImplementation?: (...TArgs) => TReturn,
|
||||
): JestMockFn<TArgs, TReturn> {
|
||||
let implementation: ?(...TArgs) => TReturn = initialImplementation;
|
||||
|
||||
const mock: JestMockFn<TArgs, TReturn>['mock'] = {
|
||||
calls: [],
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
lastCall: undefined,
|
||||
instances: [],
|
||||
contexts: [],
|
||||
results: [],
|
||||
};
|
||||
|
||||
const mockFunction = function (this: mixed, ...args: TArgs): TReturn {
|
||||
let result: JestMockFn<TArgs, TReturn>['mock']['results'][number] = {
|
||||
isThrow: false,
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
if (implementation != null) {
|
||||
try {
|
||||
result.value = implementation.apply(this, args);
|
||||
} catch (error) {
|
||||
result.isThrow = true;
|
||||
result.value = error;
|
||||
}
|
||||
}
|
||||
|
||||
mock.calls.push(args);
|
||||
mock.lastCall = args;
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
mock.instances.push(new.target ? this : undefined);
|
||||
mock.contexts.push(this);
|
||||
mock.results.push(result);
|
||||
|
||||
if (result.isThrow) {
|
||||
throw result.value;
|
||||
}
|
||||
|
||||
return result.value;
|
||||
};
|
||||
|
||||
mockFunction.mock = mock;
|
||||
// $FlowExpectedError[invalid-computed-prop]
|
||||
mockFunction[MOCK_FN_TAG] = true;
|
||||
|
||||
// $FlowExpectedError[prop-missing]
|
||||
return mockFunction;
|
||||
}
|
||||
|
||||
// flowlint unsafe-getters-setters:off
|
||||
|
||||
class ErrorWithCustomBlame extends Error {
|
||||
// Initially 5 to ignore all the frames from Babel helpers to instantiate this
|
||||
// custom error class.
|
||||
#ignoredFrameCount: number = 5;
|
||||
#cachedProcessedStack: ?string;
|
||||
|
||||
blameToPreviousFrame(): this {
|
||||
this.#ignoredFrameCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
get stack(): string {
|
||||
if (this.#cachedProcessedStack == null) {
|
||||
const originalStack = super.stack;
|
||||
|
||||
if (originalStack == null) {
|
||||
this.#cachedProcessedStack = originalStack;
|
||||
} else {
|
||||
const lines = originalStack.split('\n');
|
||||
lines.splice(1, this.#ignoredFrameCount);
|
||||
this.#cachedProcessedStack = lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return this.#cachedProcessedStack;
|
||||
}
|
||||
|
||||
set stack(value: string) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
class Expect {
|
||||
#received: mixed;
|
||||
#isNot: boolean = false;
|
||||
|
||||
constructor(received: mixed) {
|
||||
this.#received = received;
|
||||
}
|
||||
|
||||
get not(): this {
|
||||
this.#isNot = !this.#isNot;
|
||||
return this;
|
||||
}
|
||||
|
||||
toEqual(expected: mixed): void {
|
||||
const pass = deepEqual(this.#received, expected, {strict: true});
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} to equal ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBe(expected: mixed): void {
|
||||
const pass = this.#received === expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected${this.#maybeNotLabel()} ${String(expected)} but received ${String(this.#received)}.`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeInstanceOf(expected: Class<mixed>): void {
|
||||
const pass = this.#received instanceof expected;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be an instance of ${String(expected)}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeCloseTo(expected: number, precision: number = 2): void {
|
||||
const pass =
|
||||
Math.abs(expected - Number(this.#received)) < Math.pow(10, -precision);
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toBeNull(): void {
|
||||
const pass = this.#received == null;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be null`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toThrow(expected?: string): void {
|
||||
if (expected != null && typeof expected !== 'string') {
|
||||
throw new ErrorWithCustomBlame(
|
||||
'toThrow() implementation only accepts strings as arguments.',
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
|
||||
let pass = false;
|
||||
try {
|
||||
// $FlowExpectedError[not-a-function]
|
||||
this.#received();
|
||||
} catch (error) {
|
||||
pass = expected != null ? error.message === expected : true;
|
||||
}
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalled(): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length > 0;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
toHaveBeenCalledTimes(times: number): void {
|
||||
const mock = this.#requireMock();
|
||||
const pass = mock.calls.length === times;
|
||||
if (!this.#isExpectedResult(pass)) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
|
||||
).blameToPreviousFrame();
|
||||
}
|
||||
}
|
||||
|
||||
#isExpectedResult(pass: boolean): boolean {
|
||||
return this.#isNot ? !pass : pass;
|
||||
}
|
||||
|
||||
#maybeNotLabel(): string {
|
||||
return this.#isNot ? ' not' : '';
|
||||
}
|
||||
|
||||
#requireMock(): JestMockFn<$ReadOnlyArray<mixed>, mixed>['mock'] {
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
if (!this.#received?.[MOCK_FN_TAG]) {
|
||||
throw new ErrorWithCustomBlame(
|
||||
`Expected ${String(this.#received)} to be a mock function, but it wasn't`,
|
||||
)
|
||||
.blameToPreviousFrame()
|
||||
.blameToPreviousFrame();
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
return this.#received.mock;
|
||||
}
|
||||
}
|
||||
|
||||
global.expect = (received: mixed) => new Expect(received);
|
||||
global.expect = expect;
|
||||
|
||||
function runWithGuard(fn: () => void) {
|
||||
try {
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
"hermes-transform": "0.25.1",
|
||||
"inquirer": "^7.1.0",
|
||||
"jest": "^29.6.3",
|
||||
"jest-diff": "^29.7.0",
|
||||
"jest-junit": "^10.0.0",
|
||||
"jscodeshift": "^0.14.0",
|
||||
"metro-babel-register": "^0.81.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@generated SignedSource<<6b92b66e59525cef52902139f863f175>>
|
||||
Git revision: b61aae3ccc6e2684dfbf1e2a06b0f985b459f11f
|
||||
@generated SignedSource<<cecad43da3fd917e15322ee3efbed875>>
|
||||
Git revision: 486803f6bf272e0629297265dee8048a2f1269dd
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebookexperimental/rn-chrome-devtools-frontend
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -42,7 +42,7 @@ import*as e from"../../ui/legacy/legacy.js";import*as t from"../../core/host/hos
|
||||
<p>${d(a.docsDebuggingBasicsDetail)}</p>
|
||||
</div>
|
||||
</button>
|
||||
<button class="rn-welcome-docsfeed-item" type="button" role="link" @click=${this.#o.bind(this,"https://reactnative.dev/docs/react-devtools")} title=${d(a.docsReactNativeDevTools)}>
|
||||
<button class="rn-welcome-docsfeed-item" type="button" role="link" @click=${this.#o.bind(this,"https://reactnative.dev/docs/react-native-devtools")} title=${d(a.docsReactNativeDevTools)}>
|
||||
<div class="rn-welcome-image" style="background-image: url('${c}')"></div>
|
||||
<div>
|
||||
<p class="devtools-link">${d(a.docsReactNativeDevTools)}</p>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+7
-3
@@ -12,6 +12,7 @@ import com.facebook.react.utils.detectOSAwareHermesCommand
|
||||
import com.facebook.react.utils.moveTo
|
||||
import com.facebook.react.utils.windowsAwareCommandLine
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileTree
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
@@ -19,6 +20,7 @@ import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
abstract class BundleHermesCTask : DefaultTask() {
|
||||
|
||||
@@ -26,6 +28,8 @@ abstract class BundleHermesCTask : DefaultTask() {
|
||||
group = "react"
|
||||
}
|
||||
|
||||
@get:Inject abstract val execOperations: ExecOperations
|
||||
|
||||
@get:Internal abstract val root: DirectoryProperty
|
||||
|
||||
@get:InputFiles
|
||||
@@ -127,9 +131,9 @@ abstract class BundleHermesCTask : DefaultTask() {
|
||||
File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map")
|
||||
|
||||
private fun runCommand(command: List<Any>) {
|
||||
project.exec {
|
||||
it.workingDir(root.get().asFile)
|
||||
it.commandLine(command)
|
||||
execOperations.exec { exec ->
|
||||
exec.workingDir(root.get().asFile)
|
||||
exec.commandLine(command)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ abstract class BuildCodegenCLITask : Exec() {
|
||||
}
|
||||
|
||||
override fun exec() {
|
||||
val logfile = "${project.layout.buildDirectory}/build-cli.log"
|
||||
val logfile = "${project.layout.buildDirectory.getAsFile().get()}/build-cli.log"
|
||||
File(logfile).apply {
|
||||
parentFile.mkdirs()
|
||||
if (exists()) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -559,7 +559,7 @@ if (global.nativeLoggingHook) {
|
||||
let originalConsoleError = console.error;
|
||||
console.reportErrorsAsExceptions = true;
|
||||
function stringifySafe(arg) {
|
||||
return inspect(arg, {depth: 10}).replaceAll(/\n\s*/g, ' ');
|
||||
return inspect(arg, {depth: 10}).replace(/\n\s*/g, ' ');
|
||||
}
|
||||
console.error = function (...args) {
|
||||
originalConsoleError.apply(this, args);
|
||||
|
||||
+1
-1
@@ -327,7 +327,7 @@ export interface NativeModuleBooleanTypeAnnotation {
|
||||
|
||||
export type NativeModuleEnumMember = {
|
||||
readonly name: string;
|
||||
readonly value: string | number;
|
||||
readonly value: NativeModuleStringLiteralTypeAnnotation | NativeModuleNumberLiteralTypeAnnotation,
|
||||
};
|
||||
|
||||
export type NativeModuleEnumMemberType =
|
||||
|
||||
+2
-1
@@ -311,7 +311,7 @@ export type NativeModuleNumberTypeAnnotation = $ReadOnly<{
|
||||
|
||||
export type NativeModuleEnumMember = {
|
||||
name: string,
|
||||
value: string | number,
|
||||
value: StringLiteralTypeAnnotation | NumberLiteralTypeAnnotation,
|
||||
};
|
||||
|
||||
export type NativeModuleEnumMemberType =
|
||||
@@ -430,4 +430,5 @@ export type CompleteTypeAnnotation =
|
||||
| NativeModuleFunctionTypeAnnotation
|
||||
| NullableTypeAnnotation<NativeModuleTypeAnnotation>
|
||||
| EventEmitterTypeAnnotation
|
||||
| NativeModuleEnumDeclarationWithMembers
|
||||
| UnsafeAnyTypeAnnotation;
|
||||
|
||||
@@ -96,7 +96,7 @@ function combineSchemasInFileListAndWriteToFile(
|
||||
exclude: ?RegExp,
|
||||
): void {
|
||||
const combined = combineSchemasInFileList(fileList, platform, exclude);
|
||||
const formattedSchema = JSON.stringify(combined, null, 2);
|
||||
const formattedSchema = JSON.stringify(combined);
|
||||
fs.writeFileSync(outfile, formattedSchema);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,4 +84,4 @@ for (const file of schemaFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(output, JSON.stringify({modules}, null, 2));
|
||||
fs.writeFileSync(output, JSON.stringify({modules}));
|
||||
|
||||
@@ -406,6 +406,14 @@ struct Bridging<${enumName}> {
|
||||
};`;
|
||||
};
|
||||
|
||||
function getMemberValueAppearance(member: NativeModuleEnumMember['value']) {
|
||||
if (member.type === 'StringLiteralTypeAnnotation') {
|
||||
return `"${member.value}"`;
|
||||
} else {
|
||||
return member.value;
|
||||
}
|
||||
}
|
||||
|
||||
function generateEnum(
|
||||
hasteModuleName: string,
|
||||
origEnumName: string,
|
||||
@@ -417,9 +425,6 @@ function generateEnum(
|
||||
const nativeEnumMemberType: NativeEnumMemberValueType =
|
||||
memberType === 'StringTypeAnnotation' ? 'std::string' : 'int32_t';
|
||||
|
||||
const getMemberValueAppearance = (value: string | number) =>
|
||||
memberType === 'StringTypeAnnotation' ? `"${value}"` : `${value}`;
|
||||
|
||||
const fromCases =
|
||||
members
|
||||
.map(
|
||||
|
||||
+60
-15
@@ -163,11 +163,17 @@ const SIMPLE_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'ONE',
|
||||
value: '1',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TWO',
|
||||
value: '2',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -178,15 +184,24 @@ const SIMPLE_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'POINT_ZERO',
|
||||
value: '0.0',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'POINT_ONE',
|
||||
value: '0.1',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0.1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'POINT_TWO',
|
||||
value: '0.2',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0.2,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -197,11 +212,17 @@ const SIMPLE_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'HELLO',
|
||||
value: 'hello',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'GoodBye',
|
||||
value: 'goodbye',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'goodbye',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1926,11 +1947,17 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'IA',
|
||||
value: '23',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 23,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'IB',
|
||||
value: '42',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 42,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1941,11 +1968,17 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'FA',
|
||||
value: '1.23',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 1.23,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'FB',
|
||||
value: '4.56',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 4.56,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1956,11 +1989,17 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'NA',
|
||||
value: 'NA',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'NA',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'NB',
|
||||
value: 'NB',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'NB',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1971,11 +2010,17 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = {
|
||||
members: [
|
||||
{
|
||||
name: 'SA',
|
||||
value: 's---a',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 's---a',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SB',
|
||||
value: 's---b',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 's---b',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+2
-2
@@ -2047,7 +2047,7 @@ template <>
|
||||
struct Bridging<NativeSampleTurboModuleFloatEnum> {
|
||||
static NativeSampleTurboModuleFloatEnum fromJs(jsi::Runtime &rt, const jsi::Value &rawValue) {
|
||||
double value = (double)rawValue.asNumber();
|
||||
if (value == 0.0) {
|
||||
if (value == 0) {
|
||||
return NativeSampleTurboModuleFloatEnum::POINT_ZERO;
|
||||
} else if (value == 0.1) {
|
||||
return NativeSampleTurboModuleFloatEnum::POINT_ONE;
|
||||
@@ -2060,7 +2060,7 @@ struct Bridging<NativeSampleTurboModuleFloatEnum> {
|
||||
|
||||
static jsi::Value toJs(jsi::Runtime &rt, NativeSampleTurboModuleFloatEnum value) {
|
||||
if (value == NativeSampleTurboModuleFloatEnum::POINT_ZERO) {
|
||||
return bridging::toJs(rt, 0.0);
|
||||
return bridging::toJs(rt, 0);
|
||||
} else if (value == NativeSampleTurboModuleFloatEnum::POINT_ONE) {
|
||||
return bridging::toJs(rt, 0.1);
|
||||
} else if (value == NativeSampleTurboModuleFloatEnum::POINT_TWO) {
|
||||
|
||||
+16
-4
@@ -478,11 +478,17 @@ describe('typeEnumResolution', () => {
|
||||
members: [
|
||||
{
|
||||
name: 'Hello',
|
||||
value: 'hello',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Goodbye',
|
||||
value: 'goodbye',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'goodbye',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -521,11 +527,17 @@ describe('typeEnumResolution', () => {
|
||||
members: [
|
||||
{
|
||||
name: 'On',
|
||||
value: '1',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Off',
|
||||
value: '0',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+60
-15
@@ -147,11 +147,17 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`]
|
||||
'members': [
|
||||
{
|
||||
'name': 'SD',
|
||||
'value': 'SD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'SD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'HD',
|
||||
'value': 'HD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'HD'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -162,15 +168,24 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`]
|
||||
'members': [
|
||||
{
|
||||
'name': 'Corrupted',
|
||||
'value': -1
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': -1
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Low',
|
||||
'value': 720
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 720
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'High',
|
||||
'value': 1080
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 1080
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -181,15 +196,24 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`]
|
||||
'members': [
|
||||
{
|
||||
'name': 'One',
|
||||
'value': 'one'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'one'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Two',
|
||||
'value': 'two'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'two'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Three',
|
||||
'value': 'three'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'three'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -471,11 +495,17 @@ exports[`RN Codegen Flow Parser can generate fixture IOS_ONLY_NATIVE_MODULE 1`]
|
||||
'members': [
|
||||
{
|
||||
'name': 'SD',
|
||||
'value': 'SD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'SD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'HD',
|
||||
'value': 'HD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'HD'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -486,11 +516,17 @@ exports[`RN Codegen Flow Parser can generate fixture IOS_ONLY_NATIVE_MODULE 1`]
|
||||
'members': [
|
||||
{
|
||||
'name': 'Low',
|
||||
'value': 720
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 720
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'High',
|
||||
'value': 1080
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 1080
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -501,15 +537,24 @@ exports[`RN Codegen Flow Parser can generate fixture IOS_ONLY_NATIVE_MODULE 1`]
|
||||
'members': [
|
||||
{
|
||||
'name': 'One',
|
||||
'value': 'one'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'one'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Two',
|
||||
'value': 'two'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'two'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Three',
|
||||
'value': 'three'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'three'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+22
-4
@@ -230,10 +230,28 @@ class FlowParser implements Parser {
|
||||
parseEnumMembers(
|
||||
typeAnnotation: $FlowFixMe,
|
||||
): $ReadOnlyArray<NativeModuleEnumMember> {
|
||||
return typeAnnotation.members.map(member => ({
|
||||
name: member.id.name,
|
||||
value: member.init?.value ?? member.id.name,
|
||||
}));
|
||||
return typeAnnotation.members.map(member => {
|
||||
const value =
|
||||
typeof member.init?.value === 'number'
|
||||
? {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: member.init.value,
|
||||
}
|
||||
: typeof member.init?.value === 'string'
|
||||
? {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: member.init.value,
|
||||
}
|
||||
: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: member.id.name,
|
||||
};
|
||||
|
||||
return {
|
||||
name: member.id.name,
|
||||
value: value,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
isModuleInterface(node: $FlowFixMe): boolean {
|
||||
|
||||
+16
-4
@@ -175,21 +175,33 @@ export class MockedParser implements Parser {
|
||||
? [
|
||||
{
|
||||
name: 'Hello',
|
||||
value: 'hello',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Goodbye',
|
||||
value: 'goodbye',
|
||||
value: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: 'goodbye',
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
name: 'On',
|
||||
value: '1',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Off',
|
||||
value: '0',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
+60
-15
@@ -134,11 +134,17 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL
|
||||
'members': [
|
||||
{
|
||||
'name': 'SD',
|
||||
'value': 'SD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'SD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'HD',
|
||||
'value': 'HD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'HD'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -149,15 +155,24 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL
|
||||
'members': [
|
||||
{
|
||||
'name': 'Corrupted',
|
||||
'value': -1
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': -1
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Low',
|
||||
'value': 720
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 720
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'High',
|
||||
'value': 1080
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 1080
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -168,15 +183,24 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL
|
||||
'members': [
|
||||
{
|
||||
'name': 'One',
|
||||
'value': 'one'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'one'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Two',
|
||||
'value': 'two'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'two'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Three',
|
||||
'value': 'three'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'three'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -458,11 +482,17 @@ exports[`RN Codegen TypeScript Parser can generate fixture IOS_ONLY_NATIVE_MODUL
|
||||
'members': [
|
||||
{
|
||||
'name': 'SD',
|
||||
'value': 'SD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'SD'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'HD',
|
||||
'value': 'HD'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'HD'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -473,11 +503,17 @@ exports[`RN Codegen TypeScript Parser can generate fixture IOS_ONLY_NATIVE_MODUL
|
||||
'members': [
|
||||
{
|
||||
'name': 'Low',
|
||||
'value': 720
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 720
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'High',
|
||||
'value': 1080
|
||||
'value': {
|
||||
'type': 'NumberLiteralTypeAnnotation',
|
||||
'value': 1080
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -488,15 +524,24 @@ exports[`RN Codegen TypeScript Parser can generate fixture IOS_ONLY_NATIVE_MODUL
|
||||
'members': [
|
||||
{
|
||||
'name': 'One',
|
||||
'value': 'one'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'one'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Two',
|
||||
'value': 'two'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'two'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Three',
|
||||
'value': 'three'
|
||||
'value': {
|
||||
'type': 'StringLiteralTypeAnnotation',
|
||||
'value': 'three'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+35
-5
@@ -124,9 +124,27 @@ describe('TypeScript Module Parser', () => {
|
||||
|
||||
expect(parser).not.toThrow();
|
||||
expect(parser().enumMap.MyEnum.members).toEqual([
|
||||
{name: 'ZERO', value: 0},
|
||||
{name: 'POSITIVE', value: 1},
|
||||
{name: 'NEGATIVE', value: -1},
|
||||
{
|
||||
name: 'ZERO',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'POSITIVE',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'NEGATIVE',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: -1,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -147,8 +165,20 @@ describe('TypeScript Module Parser', () => {
|
||||
|
||||
expect(parser).not.toThrow();
|
||||
expect(parser().enumMap.MyEnum.members).toEqual([
|
||||
{name: 'ZERO', value: 0},
|
||||
{name: 'POSITIVE', value: 1},
|
||||
{
|
||||
name: 'ZERO',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'POSITIVE',
|
||||
value: {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -250,17 +250,30 @@ class TypeScriptParser implements Parser {
|
||||
typeAnnotation: $FlowFixMe,
|
||||
): $ReadOnlyArray<NativeModuleEnumMember> {
|
||||
return typeAnnotation.members.map(member => {
|
||||
// Handle negative values
|
||||
if (member.initializer?.operator === '-') {
|
||||
return {
|
||||
name: member.id.name,
|
||||
value: -member.initializer?.argument?.value ?? member.id.name,
|
||||
};
|
||||
}
|
||||
const value =
|
||||
member.initializer?.operator === '-'
|
||||
? {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: -1 * member.initializer?.argument?.value,
|
||||
}
|
||||
: typeof member.initializer?.value === 'number'
|
||||
? {
|
||||
type: 'NumberLiteralTypeAnnotation',
|
||||
value: member.initializer?.value,
|
||||
}
|
||||
: typeof member.initializer?.value === 'string'
|
||||
? {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: member.initializer?.value,
|
||||
}
|
||||
: {
|
||||
type: 'StringLiteralTypeAnnotation',
|
||||
value: member.id.name,
|
||||
};
|
||||
|
||||
return {
|
||||
name: member.id.name,
|
||||
value: member.initializer?.value ?? member.id.name,
|
||||
value,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,17 +106,17 @@ export function allowTransformProp(prop: string): void {
|
||||
}
|
||||
|
||||
export function isSupportedColorStyleProp(prop: string): boolean {
|
||||
return Object.hasOwn(SUPPORTED_COLOR_STYLES, prop);
|
||||
return SUPPORTED_COLOR_STYLES.hasOwnProperty(prop);
|
||||
}
|
||||
|
||||
export function isSupportedInterpolationParam(param: string): boolean {
|
||||
return Object.hasOwn(SUPPORTED_INTERPOLATION_PARAMS, param);
|
||||
return SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(param);
|
||||
}
|
||||
|
||||
export function isSupportedStyleProp(prop: string): boolean {
|
||||
return Object.hasOwn(SUPPORTED_STYLES, prop);
|
||||
return SUPPORTED_STYLES.hasOwnProperty(prop);
|
||||
}
|
||||
|
||||
export function isSupportedTransformProp(prop: string): boolean {
|
||||
return Object.hasOwn(SUPPORTED_TRANSFORMS, prop);
|
||||
return SUPPORTED_TRANSFORMS.hasOwnProperty(prop);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export type AnimationConfig = $ReadOnly<{
|
||||
onComplete?: ?EndCallback,
|
||||
iterations?: number,
|
||||
isLooping?: boolean,
|
||||
debugID?: ?string,
|
||||
...
|
||||
}>;
|
||||
|
||||
@@ -43,6 +44,7 @@ export default class Animation {
|
||||
__isInteraction: boolean;
|
||||
__isLooping: ?boolean;
|
||||
__iterations: number;
|
||||
__debugID: ?string;
|
||||
|
||||
constructor(config: AnimationConfig) {
|
||||
this.#useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
|
||||
@@ -51,6 +53,9 @@ export default class Animation {
|
||||
this.__isInteraction = config.isInteraction ?? !this.#useNativeDriver;
|
||||
this.__isLooping = config.isLooping;
|
||||
this.__iterations = config.iterations ?? 1;
|
||||
if (__DEV__) {
|
||||
this.__debugID = config.debugID;
|
||||
}
|
||||
}
|
||||
|
||||
start(
|
||||
|
||||
@@ -99,6 +99,7 @@ export default class TimingAnimation extends Animation {
|
||||
toValue: this._toValue,
|
||||
iterations: this.__iterations,
|
||||
platformConfig: this._platformConfig,
|
||||
debugID: __DEV__ ? this.__debugID : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -197,4 +197,6 @@ export default class AnimatedNode {
|
||||
toJSON(): mixed {
|
||||
return this.__getValue();
|
||||
}
|
||||
|
||||
__debugID: ?string = undefined;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ function createAnimatedProps(
|
||||
const key = keys[ii];
|
||||
const value = inputProps[key];
|
||||
|
||||
if (allowlist == null || Object.hasOwn(allowlist, key)) {
|
||||
if (allowlist == null || hasOwn(allowlist, key)) {
|
||||
let node;
|
||||
if (key === 'style') {
|
||||
node = AnimatedStyle.from(value, allowlist?.style);
|
||||
@@ -271,3 +271,11 @@ export default class AnimatedProps extends AnimatedNode {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
|
||||
// this shim when they do.
|
||||
// $FlowIgnore[method-unbinding]
|
||||
const _hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
|
||||
// $FlowIgnore[method-unbinding]
|
||||
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
|
||||
|
||||
@@ -35,7 +35,7 @@ function createAnimatedStyle(
|
||||
const key = keys[ii];
|
||||
const value = inputStyle[key];
|
||||
|
||||
if (allowlist == null || Object.hasOwn(allowlist, key)) {
|
||||
if (allowlist == null || hasOwn(allowlist, key)) {
|
||||
let node;
|
||||
if (value != null && key === 'transform') {
|
||||
node = ReactNativeFeatureFlags.shouldUseAnimatedObjectForTransform()
|
||||
@@ -241,3 +241,11 @@ export default class AnimatedStyle extends AnimatedWithChildren {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
|
||||
// this shim when they do.
|
||||
// $FlowIgnore[method-unbinding]
|
||||
const _hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
|
||||
// $FlowIgnore[method-unbinding]
|
||||
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
|
||||
|
||||
@@ -22,6 +22,7 @@ import AnimatedWithChildren from './AnimatedWithChildren';
|
||||
|
||||
export type AnimatedValueConfig = $ReadOnly<{
|
||||
useNativeDriver: boolean,
|
||||
debugID?: string,
|
||||
}>;
|
||||
|
||||
const NativeAnimatedAPI = NativeAnimatedHelper.API;
|
||||
@@ -97,8 +98,13 @@ export default class AnimatedValue extends AnimatedWithChildren {
|
||||
this._startingValue = this._value = value;
|
||||
this._offset = 0;
|
||||
this._animation = null;
|
||||
if (config && config.useNativeDriver) {
|
||||
this.__makeNative();
|
||||
if (config) {
|
||||
if (config.useNativeDriver) {
|
||||
this.__makeNative();
|
||||
}
|
||||
if (__DEV__) {
|
||||
this.__debugID = config.debugID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +304,7 @@ export default class AnimatedValue extends AnimatedWithChildren {
|
||||
type: 'value',
|
||||
value: this._value,
|
||||
offset: this._offset,
|
||||
debugID: __DEV__ ? this.__debugID : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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 enableFixForViewCommandRace:true
|
||||
*/
|
||||
|
||||
import '../../../Core/InitializeCore.js';
|
||||
import * as ReactNativeTester from '../../../../src/private/__tests__/ReactNativeTester';
|
||||
import TextInput from '../TextInput';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useLayoutEffect, useRef} from 'react';
|
||||
|
||||
describe('TextInput', () => {
|
||||
it('creates view before dispatching view command from ref function', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<TextInput
|
||||
ref={node => {
|
||||
if (node) {
|
||||
node.focus();
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(2);
|
||||
expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`');
|
||||
expect(mountingLogs[1]).toBe(
|
||||
'dispatch command `focus` on component `AndroidTextInput`',
|
||||
);
|
||||
});
|
||||
|
||||
it('creates view before dispatching view command from useLayoutEffect', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
function Component() {
|
||||
const textInputRef = useRef<null | React.ElementRef<typeof TextInput>>(
|
||||
null,
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
textInputRef.current?.focus();
|
||||
});
|
||||
|
||||
return <TextInput ref={textInputRef} />;
|
||||
}
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
const mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(2);
|
||||
expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`');
|
||||
expect(mountingLogs[1]).toBe(
|
||||
'dispatch command `focus` on component `AndroidTextInput`',
|
||||
);
|
||||
});
|
||||
|
||||
it('creates view before dispatching view command from useEffect', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
function Component() {
|
||||
const textInputRef = useRef<null | React.ElementRef<typeof TextInput>>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
textInputRef.current?.focus();
|
||||
});
|
||||
|
||||
return <TextInput ref={textInputRef} />;
|
||||
}
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
const mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(2);
|
||||
expect(mountingLogs[0]).toBe('create view type: `AndroidTextInput`');
|
||||
expect(mountingLogs[1]).toBe(
|
||||
'dispatch command `focus` on component `AndroidTextInput`',
|
||||
);
|
||||
});
|
||||
});
|
||||
+11
-3
@@ -28,6 +28,15 @@ type FormDataPart =
|
||||
...
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a FormData filename compliant with RFC 2183
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#directives
|
||||
*/
|
||||
function encodeFilename(filename: string): string {
|
||||
return encodeURIComponent(filename.replace(/\//g, '_'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Polyfill for XMLHttpRequest2 FormData API, allowing multipart POST requests
|
||||
* with mixed data (string, native files) to be submitted via XMLHttpRequest.
|
||||
@@ -82,9 +91,8 @@ class FormData {
|
||||
// content type (cf. web Blob interface.)
|
||||
if (typeof value === 'object' && !Array.isArray(value) && value) {
|
||||
if (typeof value.name === 'string') {
|
||||
headers['content-disposition'] += `; filename="${
|
||||
value.name
|
||||
}"; filename*=utf-8''${encodeURI(value.name)}`;
|
||||
headers['content-disposition'] +=
|
||||
`; filename="${encodeFilename(value.name)}"`;
|
||||
}
|
||||
if (typeof value.type === 'string') {
|
||||
headers['content-type'] = value.type;
|
||||
|
||||
@@ -48,8 +48,7 @@ describe('FormData', function () {
|
||||
type: 'image/jpeg',
|
||||
name: 'photo.jpg',
|
||||
headers: {
|
||||
'content-disposition':
|
||||
'form-data; name="photo"; filename="photo.jpg"; filename*=utf-8\'\'photo.jpg',
|
||||
'content-disposition': 'form-data; name="photo"; filename="photo.jpg"',
|
||||
'content-type': 'image/jpeg',
|
||||
},
|
||||
fieldName: 'photo',
|
||||
@@ -70,7 +69,7 @@ describe('FormData', function () {
|
||||
name: '测试photo.jpg',
|
||||
headers: {
|
||||
'content-disposition':
|
||||
'form-data; name="photo"; filename="测试photo.jpg"; filename*=utf-8\'\'%E6%B5%8B%E8%AF%95photo.jpg',
|
||||
'form-data; name="photo"; filename="%E6%B5%8B%E8%AF%95photo.jpg"',
|
||||
'content-type': 'image/jpeg',
|
||||
},
|
||||
fieldName: 'photo',
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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:false
|
||||
*/
|
||||
|
||||
import setUpReactFabricPublicInstanceFantomTests from './setUpReactFabricPublicInstanceFantomTests';
|
||||
|
||||
setUpReactFabricPublicInstanceFantomTests({isModern: false});
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 setUpReactFabricPublicInstanceFantomTests from './setUpReactFabricPublicInstanceFantomTests';
|
||||
|
||||
setUpReactFabricPublicInstanceFantomTests({isModern: true});
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/**
|
||||
* 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 '../../../Core/InitializeCore.js';
|
||||
|
||||
import * as ReactNativeTester from '../../../../src/private/__tests__/ReactNativeTester';
|
||||
import ReactNativeElement from '../../../../src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
import TextInputState from '../../../Components/TextInput/TextInputState';
|
||||
import View from '../../../Components/View/View';
|
||||
import ReactFabricHostComponent from '../ReactFabricHostComponent';
|
||||
import nullthrows from 'nullthrows';
|
||||
import * as React from 'react';
|
||||
|
||||
export default function setUpTests({isModern}: {isModern: boolean}) {
|
||||
// TODO: move these tests to the test file for `ReactNativeElement` when the legacy implementation is removed.
|
||||
describe(`ReactFabricPublicInstance (${isModern ? 'modern' : 'legacy'})`, () => {
|
||||
it('should provide instances of the right class as refs in host components', () => {
|
||||
let node;
|
||||
|
||||
const root = ReactNativeTester.createRoot();
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
ref={receivedNode => {
|
||||
node = receivedNode;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(node).toBeInstanceOf(
|
||||
isModern ? ReactNativeElement : ReactFabricHostComponent,
|
||||
);
|
||||
});
|
||||
|
||||
describe('blur', () => {
|
||||
test('blur() invokes TextInputState', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
ref={node => {
|
||||
maybeNode = node;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const node = nullthrows(maybeNode);
|
||||
|
||||
const blurTextInput = jest.fn();
|
||||
|
||||
// We don't support view commands in Fantom yet, so we have to mock this.
|
||||
TextInputState.blurTextInput = blurTextInput;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
node.blur();
|
||||
});
|
||||
|
||||
expect(blurTextInput).toHaveBeenCalledTimes(1);
|
||||
expect(blurTextInput.mock.calls).toEqual([[node]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('focus', () => {
|
||||
test('focus() invokes TextInputState', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
ref={node => {
|
||||
maybeNode = node;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const node = nullthrows(maybeNode);
|
||||
|
||||
const focusTextInput = jest.fn();
|
||||
|
||||
// We don't support view commands in Fantom yet, so we have to mock this.
|
||||
TextInputState.focusTextInput = focusTextInput;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
node.focus();
|
||||
});
|
||||
|
||||
expect(focusTextInput).toHaveBeenCalledTimes(1);
|
||||
expect(focusTextInput.mock.calls).toEqual([[node]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('measure', () => {
|
||||
it('component.measure(...) invokes callback', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeNode = node;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const node = nullthrows(maybeNode);
|
||||
|
||||
const callback = jest.fn();
|
||||
node.measure(callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
expect(callback.mock.calls).toEqual([[10, 10, 100, 100, 10, 10]]);
|
||||
});
|
||||
|
||||
it('unmounted.measure(...) does nothing', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeNode = node;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const node = nullthrows(maybeNode);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<></>);
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
node.measure(callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('measureInWindow', () => {
|
||||
it('component.measureInWindow(...) invokes callback', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeNode = node;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const node = nullthrows(maybeNode);
|
||||
|
||||
const callback = jest.fn();
|
||||
node.measureInWindow(callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
expect(callback.mock.calls).toEqual([[10, 10, 100, 100]]);
|
||||
});
|
||||
|
||||
it('unmounted.measureInWindow(...) does nothing', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeNode = node;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const node = nullthrows(maybeNode);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<></>);
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
node.measureInWindow(callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('measureLayout', () => {
|
||||
it('component.measureLayout(component, ...) invokes callback', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeParentNode;
|
||||
let maybeChildNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeParentNode = node;
|
||||
}}>
|
||||
<View
|
||||
style={{width: 10, height: 10, left: 20, top: 20}}
|
||||
ref={node => {
|
||||
maybeChildNode = node;
|
||||
}}
|
||||
/>
|
||||
</View>,
|
||||
);
|
||||
});
|
||||
|
||||
const parentNode = nullthrows(maybeParentNode);
|
||||
const childNode = nullthrows(maybeChildNode);
|
||||
|
||||
const callback = jest.fn();
|
||||
childNode.measureLayout(parentNode, callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
expect(callback.mock.calls).toEqual([[20, 20, 10, 10]]);
|
||||
});
|
||||
|
||||
it('unmounted.measureLayout(component, ...) does nothing', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeParentNode;
|
||||
let maybeChildNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeParentNode = node;
|
||||
}}>
|
||||
<View
|
||||
style={{width: 10, height: 10, left: 20, top: 20}}
|
||||
ref={node => {
|
||||
maybeChildNode = node;
|
||||
}}
|
||||
/>
|
||||
</View>,
|
||||
);
|
||||
});
|
||||
|
||||
const parentNode = nullthrows(maybeParentNode);
|
||||
const childNode = nullthrows(maybeChildNode);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View style={{width: 100, height: 100, left: 10, top: 10}} />,
|
||||
);
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
childNode.measureLayout(parentNode, callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('component.measureLayout(unmounted, ...) does nothing', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeParentNode;
|
||||
let maybeChildNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeParentNode = node;
|
||||
}}>
|
||||
<View
|
||||
style={{width: 10, height: 10, left: 20, top: 20}}
|
||||
ref={node => {
|
||||
maybeChildNode = node;
|
||||
}}
|
||||
/>
|
||||
</View>,
|
||||
);
|
||||
});
|
||||
|
||||
const parentNode = nullthrows(maybeParentNode);
|
||||
const childNode = nullthrows(maybeChildNode);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View style={{width: 100, height: 100, left: 10, top: 10}} />,
|
||||
);
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
parentNode.measureLayout(childNode, callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('unmounted.measureLayout(unmounted, ...) does nothing', () => {
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
let maybeParentNode;
|
||||
let maybeChildNode;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
style={{width: 100, height: 100, left: 10, top: 10}}
|
||||
ref={node => {
|
||||
maybeParentNode = node;
|
||||
}}>
|
||||
<View
|
||||
style={{width: 10, height: 10, left: 20, top: 20}}
|
||||
ref={node => {
|
||||
maybeChildNode = node;
|
||||
}}
|
||||
/>
|
||||
</View>,
|
||||
);
|
||||
});
|
||||
|
||||
const parentNode = nullthrows(maybeParentNode);
|
||||
const childNode = nullthrows(maybeChildNode);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<></>);
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
childNode.measureLayout(parentNode, callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 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 '../../Core/InitializeCore.js';
|
||||
import * as ReactNativeTester from '../../../src/private/__tests__/ReactNativeTester';
|
||||
import View from '../../Components/View/View';
|
||||
import * as React from 'react';
|
||||
import {Suspense, startTransition} from 'react';
|
||||
|
||||
let resolveFunction: (() => void) | null = null;
|
||||
|
||||
// This is a workaround for a bug to get the demo running.
|
||||
// TODO: replace with real implementation when the bug is fixed.
|
||||
// $FlowFixMe: [missing-local-annot]
|
||||
function use(promise) {
|
||||
if (promise.status === 'fulfilled') {
|
||||
return promise.value;
|
||||
} else if (promise.status === 'rejected') {
|
||||
throw promise.reason;
|
||||
} else if (promise.status === 'pending') {
|
||||
throw promise;
|
||||
} else {
|
||||
promise.status = 'pending';
|
||||
promise.then(
|
||||
result => {
|
||||
promise.status = 'fulfilled';
|
||||
promise.value = result;
|
||||
},
|
||||
reason => {
|
||||
promise.status = 'rejected';
|
||||
promise.reason = reason;
|
||||
},
|
||||
);
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
|
||||
type SquareData = {
|
||||
color: 'red' | 'green',
|
||||
};
|
||||
|
||||
enum SquareId {
|
||||
Green = 'green-square',
|
||||
Red = 'red-square',
|
||||
}
|
||||
|
||||
async function getGreenSquareData(): Promise<SquareData> {
|
||||
await new Promise(resolve => {
|
||||
resolveFunction = resolve;
|
||||
});
|
||||
return {
|
||||
color: 'green',
|
||||
};
|
||||
}
|
||||
|
||||
async function getRedSquareData(): Promise<SquareData> {
|
||||
await new Promise(resolve => {
|
||||
resolveFunction = resolve;
|
||||
});
|
||||
return {
|
||||
color: 'red',
|
||||
};
|
||||
}
|
||||
|
||||
const cache = new Map<SquareId, SquareData>();
|
||||
|
||||
async function getData(squareId: SquareId): Promise<SquareData> {
|
||||
switch (squareId) {
|
||||
case SquareId.Green:
|
||||
return await getGreenSquareData();
|
||||
case SquareId.Red:
|
||||
return await getRedSquareData();
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchData(squareId: SquareId): Promise<SquareData> {
|
||||
const data = await getData(squareId);
|
||||
cache.set(squareId, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
function Square(props: {squareId: SquareId}) {
|
||||
let data = cache.get(props.squareId);
|
||||
if (data == null) {
|
||||
data = use(fetchData(props.squareId));
|
||||
}
|
||||
return <View key={data.color} nativeID={'square with data: ' + data.color} />;
|
||||
}
|
||||
|
||||
function GreenSquare() {
|
||||
return <Square squareId={SquareId.Green} />;
|
||||
}
|
||||
|
||||
function RedSquare() {
|
||||
return <Square squareId={SquareId.Red} />;
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <View nativeID="suspense fallback" />;
|
||||
}
|
||||
|
||||
describe('Suspense', () => {
|
||||
it('shows fallback if data is not available', () => {
|
||||
cache.clear();
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<GreenSquare />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
|
||||
let mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `suspense fallback`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).not.toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
resolveFunction?.();
|
||||
resolveFunction = null;
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: green`',
|
||||
);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<RedSquare />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `suspense fallback`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).not.toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
resolveFunction?.();
|
||||
resolveFunction = null;
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: red`',
|
||||
);
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<GreenSquare />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: green`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).toBeNull();
|
||||
|
||||
root.destroy();
|
||||
});
|
||||
|
||||
// TODO(T207868872): this test only succeeds with enableFabricCompleteRootInCommitPhase enabled.
|
||||
// enableFabricCompleteRootInCommitPhase is hardcoded to true in the testing environment.
|
||||
it('shows stale data while transition is happening', () => {
|
||||
cache.clear();
|
||||
cache.set(SquareId.Green, {color: 'green'});
|
||||
|
||||
const root = ReactNativeTester.createRoot();
|
||||
|
||||
function App(props: {color: 'red' | 'green'}) {
|
||||
return (
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{props.color === 'green' ? <GreenSquare /> : <RedSquare />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
root.render(<App color="green" />);
|
||||
});
|
||||
|
||||
let mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: green`',
|
||||
);
|
||||
|
||||
expect(resolveFunction).toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
startTransition(() => {
|
||||
root.render(<App color="red" />);
|
||||
});
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
// Green square is still mounted. Fallback is not shown to the user.
|
||||
expect(mountingLogs.length).toBe(0);
|
||||
|
||||
expect(resolveFunction).not.toBeNull();
|
||||
ReactNativeTester.runTask(() => {
|
||||
resolveFunction?.();
|
||||
resolveFunction = null;
|
||||
});
|
||||
|
||||
mountingLogs = root.getMountingLogs();
|
||||
|
||||
expect(mountingLogs.length).toBe(1);
|
||||
expect(mountingLogs[0]).toBe(
|
||||
'create view type: `View` nativeId: `square with data: red`',
|
||||
);
|
||||
|
||||
root.destroy();
|
||||
});
|
||||
});
|
||||
+6
-1
@@ -12,7 +12,7 @@ import {Constructor} from '../../types/private/Utilities';
|
||||
import {AccessibilityProps} from '../Components/View/ViewAccessibility';
|
||||
import {NativeMethods} from '../../types/public/ReactNativeTypes';
|
||||
import {ColorValue, StyleProp} from '../StyleSheet/StyleSheet';
|
||||
import {TextStyle} from '../StyleSheet/StyleSheetTypes';
|
||||
import {TextStyle, ViewStyle} from '../StyleSheet/StyleSheetTypes';
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
LayoutChangeEvent,
|
||||
@@ -209,6 +209,11 @@ export interface TextProps
|
||||
* Specifies smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).
|
||||
*/
|
||||
minimumFontScale?: number | undefined;
|
||||
|
||||
/**
|
||||
* Controls how touch events are handled. Similar to `View`'s `pointerEvents`.
|
||||
*/
|
||||
pointerEvents?: ViewStyle['pointerEvents'] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -424,6 +424,7 @@ export type AnimationConfig = $ReadOnly<{
|
||||
onComplete?: ?EndCallback,
|
||||
iterations?: number,
|
||||
isLooping?: boolean,
|
||||
debugID?: ?string,
|
||||
...
|
||||
}>;
|
||||
declare export default class Animation {
|
||||
@@ -431,6 +432,7 @@ declare export default class Animation {
|
||||
__isInteraction: boolean;
|
||||
__isLooping: ?boolean;
|
||||
__iterations: number;
|
||||
__debugID: ?string;
|
||||
constructor(config: AnimationConfig): void;
|
||||
start(
|
||||
fromValue: number,
|
||||
@@ -959,6 +961,7 @@ exports[`public API should not change unintentionally Libraries/Animated/nodes/A
|
||||
__getPlatformConfig(): ?PlatformConfig;
|
||||
__setPlatformConfig(platformConfig: ?PlatformConfig): void;
|
||||
toJSON(): mixed;
|
||||
__debugID: ?string;
|
||||
}
|
||||
"
|
||||
`;
|
||||
@@ -1107,6 +1110,7 @@ declare export default class AnimatedTransform extends AnimatedWithChildren {
|
||||
exports[`public API should not change unintentionally Libraries/Animated/nodes/AnimatedValue.js 1`] = `
|
||||
"export type AnimatedValueConfig = $ReadOnly<{
|
||||
useNativeDriver: boolean,
|
||||
debugID?: string,
|
||||
}>;
|
||||
declare export function flushValue(rootNode: AnimatedNode): void;
|
||||
declare export default class AnimatedValue extends AnimatedWithChildren {
|
||||
|
||||
@@ -13,20 +13,18 @@ namespace facebook::react {
|
||||
|
||||
void RCTDefaultCxxLogFunction(ReactNativeLogLevel level, const char *message)
|
||||
{
|
||||
NSString *messageString = [NSString stringWithUTF8String:message];
|
||||
|
||||
switch (level) {
|
||||
case ReactNativeLogLevelInfo:
|
||||
LOG(INFO) << message;
|
||||
RCTLogInfo(@"%@", messageString);
|
||||
RCTLogInfo(@"%@", [NSString stringWithUTF8String:message]);
|
||||
break;
|
||||
case ReactNativeLogLevelWarning:
|
||||
LOG(WARNING) << message;
|
||||
RCTLogWarn(@"%@", messageString);
|
||||
RCTLogWarn(@"%@", [NSString stringWithUTF8String:message]);
|
||||
break;
|
||||
case ReactNativeLogLevelError:
|
||||
LOG(ERROR) << message;
|
||||
RCTLogError(@"%@", messageString);
|
||||
RCTLogError(@"%@", [NSString stringWithUTF8String:message]);
|
||||
break;
|
||||
case ReactNativeLogLevelFatal:
|
||||
LOG(FATAL) << message;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
/**
|
||||
* UIView class for root <ModalHostView> component.
|
||||
*/
|
||||
@interface RCTModalHostViewComponentView : RCTViewComponentView
|
||||
@interface RCTModalHostViewComponentView : RCTViewComponentView <UIAdaptivePresentationControllerDelegate>
|
||||
|
||||
/**
|
||||
* Subclasses may override this method and present the modal on different view controller.
|
||||
|
||||
+12
@@ -149,6 +149,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
{
|
||||
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
|
||||
if (shouldBePresented) {
|
||||
self.viewController.presentationController.delegate = self;
|
||||
|
||||
_isPresented = YES;
|
||||
[self presentViewController:self.viewController
|
||||
animated:_shouldAnimatePresentation
|
||||
@@ -274,6 +276,16 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
[childComponentView removeFromSuperview];
|
||||
}
|
||||
|
||||
#pragma mark - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)controller
|
||||
{
|
||||
auto eventEmitter = [self modalEventEmitter];
|
||||
if (eventEmitter) {
|
||||
eventEmitter->onRequestClose({});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+4
-9
@@ -189,10 +189,11 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
|
||||
UIEdgeInsets newEdgeInsets = _scrollView.contentInset;
|
||||
CGFloat inset = MAX(scrollViewLowerY - keyboardEndFrame.origin.y, 0);
|
||||
const auto &props = static_cast<const ScrollViewProps &>(*_props);
|
||||
if (isInverted) {
|
||||
newEdgeInsets.top = MAX(inset, _scrollView.contentInset.top);
|
||||
newEdgeInsets.top = MAX(inset, props.contentInset.top);
|
||||
} else {
|
||||
newEdgeInsets.bottom = MAX(inset, _scrollView.contentInset.bottom);
|
||||
newEdgeInsets.bottom = MAX(inset, props.contentInset.bottom);
|
||||
}
|
||||
|
||||
CGPoint newContentOffset = _scrollView.contentOffset;
|
||||
@@ -210,12 +211,6 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
contentDiff = keyboardEndFrame.origin.y - keyboardBeginFrame.origin.y;
|
||||
}
|
||||
} else {
|
||||
CGRect viewIntersection = CGRectIntersection(self.firstResponderFocus, keyboardEndFrame);
|
||||
|
||||
if (CGRectIsNull(viewIntersection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inner text field focused
|
||||
CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus);
|
||||
if (focusEnd > keyboardEndFrame.origin.y) {
|
||||
@@ -247,7 +242,7 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
animations:^{
|
||||
self->_scrollView.contentInset = newEdgeInsets;
|
||||
self->_scrollView.verticalScrollIndicatorInsets = newEdgeInsets;
|
||||
[self scrollToOffset:newContentOffset animated:NO];
|
||||
[self scrollTo:newContentOffset.x y:newContentOffset.y animated:NO];
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
@@ -73,9 +73,8 @@ static void RCTPerformMountInstructions(
|
||||
|
||||
case ShadowViewMutation::Insert: {
|
||||
auto &newChildShadowView = mutation.newChildShadowView;
|
||||
auto &parentShadowView = mutation.parentShadowView;
|
||||
auto &newChildViewDescriptor = [registry componentViewDescriptorWithTag:newChildShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:mutation.parentTag];
|
||||
|
||||
UIView<RCTComponentViewProtocol> *newChildComponentView = newChildViewDescriptor.view;
|
||||
|
||||
@@ -94,9 +93,8 @@ static void RCTPerformMountInstructions(
|
||||
|
||||
case ShadowViewMutation::Remove: {
|
||||
auto &oldChildShadowView = mutation.oldChildShadowView;
|
||||
auto &parentShadowView = mutation.parentShadowView;
|
||||
auto &oldChildViewDescriptor = [registry componentViewDescriptorWithTag:oldChildShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:parentShadowView.tag];
|
||||
auto &parentViewDescriptor = [registry componentViewDescriptorWithTag:mutation.parentTag];
|
||||
[parentViewDescriptor.view unmountChildComponentView:oldChildViewDescriptor.view index:mutation.index];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -322,9 +322,8 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
|
||||
NSNumber *reactTag = rootView.reactTag;
|
||||
RCTAssert(RCTIsReactRootView(reactTag), @"View %@ with tag #%@ is not a root view", rootView, reactTag);
|
||||
|
||||
UIView *existingView = _viewRegistry[reactTag];
|
||||
RCTAssert(
|
||||
existingView == nil || existingView == rootView,
|
||||
_viewRegistry[reactTag] == nil || _viewRegistry[reactTag] == rootView,
|
||||
@"Expect all root views to have unique tag. Added %@ twice",
|
||||
reactTag);
|
||||
|
||||
|
||||
@@ -121,8 +121,8 @@ using namespace facebook::react;
|
||||
auto &props = *sharedProps;
|
||||
props.layoutConstraints = LayoutConstraints{{0, 0}, {500, 500}};
|
||||
auto &yogaStyle = props.yogaStyle;
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
@@ -136,8 +136,8 @@ using namespace facebook::react;
|
||||
yogaStyle.setPositionType(yoga::PositionType::Absolute);
|
||||
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(0));
|
||||
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(0));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
@@ -216,8 +216,8 @@ using namespace facebook::react;
|
||||
yogaStyle.setPositionType(yoga::PositionType::Absolute);
|
||||
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(0));
|
||||
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(30));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(50));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(50));
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
@@ -260,8 +260,8 @@ using namespace facebook::react;
|
||||
yogaStyle.setPositionType(yoga::PositionType::Absolute);
|
||||
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(0));
|
||||
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(90));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(50));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(50));
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
@@ -418,8 +418,8 @@ static ParagraphShadowNode::ConcreteState::Shared stateWithShadowNode(
|
||||
auto &props = *sharedProps;
|
||||
props.layoutConstraints = LayoutConstraints{{0, 0}, {500, 500}};
|
||||
auto &yogaStyle = props.yogaStyle;
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
@@ -434,8 +434,8 @@ static ParagraphShadowNode::ConcreteState::Shared stateWithShadowNode(
|
||||
yogaStyle.setPositionType(yoga::PositionType::Absolute);
|
||||
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(0));
|
||||
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(90));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleLength::points(20));
|
||||
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(200));
|
||||
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(20));
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
|
||||
@@ -74,11 +74,7 @@ static SEL selectorForType(NSString *type)
|
||||
if (!_manager && [self isBridgeMode]) {
|
||||
_manager = [_bridge moduleForClass:_managerClass];
|
||||
} else if (!_manager && !_bridgelessViewManager) {
|
||||
_bridgelessViewManager = [_managerClass new];
|
||||
_bridgelessViewManager.bridge = _bridge;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
|
||||
object:nil
|
||||
userInfo:@{@"module" : _bridgelessViewManager}];
|
||||
_bridgelessViewManager = [_bridge moduleForClass:_managerClass];
|
||||
}
|
||||
return _manager ? _manager : _bridgelessViewManager;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,9 @@ CGFloat RCTCoreGraphicsFloatFromYogaValue(YGValue value, CGFloat baseFloatValue)
|
||||
return RCTCoreGraphicsFloatFromYogaFloat(value.value) * baseFloatValue;
|
||||
case YGUnitAuto:
|
||||
case YGUnitUndefined:
|
||||
case YGUnitMaxContent:
|
||||
case YGUnitFitContent:
|
||||
case YGUnitStretch:
|
||||
return baseFloatValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ typedef NS_ENUM(unsigned int, meta_prop_t) {
|
||||
#define RCT_SET_YGVALUE(ygvalue, setter, ...) \
|
||||
switch (ygvalue.unit) { \
|
||||
case YGUnitAuto: \
|
||||
case YGUnitMaxContent: \
|
||||
case YGUnitFitContent: \
|
||||
case YGUnitStretch: \
|
||||
case YGUnitUndefined: \
|
||||
setter(__VA_ARGS__, YGUndefined); \
|
||||
break; \
|
||||
@@ -88,6 +91,35 @@ typedef NS_ENUM(unsigned int, meta_prop_t) {
|
||||
case YGUnitPercent: \
|
||||
setter##Percent(__VA_ARGS__, ygvalue.value); \
|
||||
break; \
|
||||
case YGUnitMaxContent: \
|
||||
case YGUnitFitContent: \
|
||||
case YGUnitStretch: \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define RCT_SET_YGVALUE_AUTO_INTRINSIC(ygvalue, setter, ...) \
|
||||
switch (ygvalue.unit) { \
|
||||
case YGUnitAuto: \
|
||||
setter##Auto(__VA_ARGS__); \
|
||||
break; \
|
||||
case YGUnitMaxContent: \
|
||||
setter##MaxContent(__VA_ARGS__); \
|
||||
break; \
|
||||
case YGUnitFitContent: \
|
||||
setter##FitContent(__VA_ARGS__); \
|
||||
break; \
|
||||
case YGUnitStretch: \
|
||||
setter##Stretch(__VA_ARGS__); \
|
||||
break; \
|
||||
case YGUnitUndefined: \
|
||||
setter(__VA_ARGS__, YGUndefined); \
|
||||
break; \
|
||||
case YGUnitPoint: \
|
||||
setter(__VA_ARGS__, ygvalue.value); \
|
||||
break; \
|
||||
case YGUnitPercent: \
|
||||
setter##Percent(__VA_ARGS__, ygvalue.value); \
|
||||
break; \
|
||||
}
|
||||
|
||||
static void RCTProcessMetaPropsPadding(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node)
|
||||
@@ -483,14 +515,14 @@ RCT_BORDER_PROPERTY(Start, START)
|
||||
RCT_BORDER_PROPERTY(End, END)
|
||||
|
||||
// Dimensions
|
||||
#define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \
|
||||
-(void)set##setProp : (YGValue)value \
|
||||
{ \
|
||||
RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSet##cssProp, _yogaNode); \
|
||||
} \
|
||||
-(YGValue)getProp \
|
||||
{ \
|
||||
return YGNodeStyleGet##cssProp(_yogaNode); \
|
||||
#define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \
|
||||
-(void)set##setProp : (YGValue)value \
|
||||
{ \
|
||||
RCT_SET_YGVALUE_AUTO_INTRINSIC(value, YGNodeStyleSet##cssProp, _yogaNode); \
|
||||
} \
|
||||
-(YGValue)getProp \
|
||||
{ \
|
||||
return YGNodeStyleGet##cssProp(_yogaNode); \
|
||||
}
|
||||
|
||||
#define RCT_MIN_MAX_DIMENSION_PROPERTY(setProp, getProp, cssProp) \
|
||||
@@ -634,7 +666,7 @@ RCTShadowViewMeasure(YGNodeConstRef node, float width, YGMeasureMode widthMode,
|
||||
|
||||
- (void)setFlexBasis:(YGValue)value
|
||||
{
|
||||
RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSetFlexBasis, _yogaNode);
|
||||
RCT_SET_YGVALUE_AUTO_INTRINSIC(value, YGNodeStyleSetFlexBasis, _yogaNode);
|
||||
}
|
||||
|
||||
- (YGValue)flexBasis
|
||||
|
||||
@@ -426,6 +426,10 @@ public abstract interface class com/facebook/react/ReactRootView$ReactRootViewEv
|
||||
public abstract fun onAttachedToReactInstance (Lcom/facebook/react/ReactRootView;)V
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/TurboReactPackage : com/facebook/react/BaseReactPackage {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/ViewManagerOnDemandReactPackage {
|
||||
public abstract fun createViewManager (Lcom/facebook/react/bridge/ReactApplicationContext;Ljava/lang/String;)Lcom/facebook/react/uimanager/ViewManager;
|
||||
public abstract fun getViewManagerNames (Lcom/facebook/react/bridge/ReactApplicationContext;)Ljava/util/Collection;
|
||||
@@ -776,16 +780,6 @@ public abstract class com/facebook/react/bridge/GuardedAsyncTask : android/os/As
|
||||
protected abstract fun doInBackgroundGuarded ([Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/bridge/GuardedResultAsyncTask : android/os/AsyncTask {
|
||||
protected fun <init> (Lcom/facebook/react/bridge/JSExceptionHandler;)V
|
||||
protected fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
protected synthetic fun doInBackground ([Ljava/lang/Object;)Ljava/lang/Object;
|
||||
protected final fun doInBackground ([Ljava/lang/Void;)Ljava/lang/Object;
|
||||
protected abstract fun doInBackgroundGuarded ()Ljava/lang/Object;
|
||||
protected final fun onPostExecute (Ljava/lang/Object;)V
|
||||
protected abstract fun onPostExecuteGuarded (Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/bridge/GuardedRunnable : java/lang/Runnable {
|
||||
public fun <init> (Lcom/facebook/react/bridge/JSExceptionHandler;)V
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
@@ -2618,6 +2612,7 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
|
||||
public fun dispatchCommand (IILcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun dispatchCommand (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun dispatchCommand (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun experimental_prefetchResource (Ljava/lang/String;IILcom/facebook/react/common/mapbuffer/ReadableMapBuffer;)V
|
||||
public fun getColor (I[Ljava/lang/String;)I
|
||||
public fun getEventDispatcher ()Lcom/facebook/react/uimanager/events/EventDispatcher;
|
||||
public fun getInspectorDataForInstance (ILandroid/view/View;)Lcom/facebook/react/bridge/ReadableMap;
|
||||
@@ -2961,6 +2956,7 @@ public abstract interface annotation class com/facebook/react/module/annotations
|
||||
public final class com/facebook/react/module/model/ReactModuleInfo {
|
||||
public static final field Companion Lcom/facebook/react/module/model/ReactModuleInfo$Companion;
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZ)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZZ)V
|
||||
public final fun canOverrideExistingModule ()Z
|
||||
public static final fun classIsTurboModule (Ljava/lang/Class;)Z
|
||||
public final fun className ()Ljava/lang/String;
|
||||
@@ -3428,11 +3424,12 @@ public abstract interface class com/facebook/react/modules/network/CustomClientB
|
||||
public abstract fun apply (Lokhttp3/OkHttpClient$Builder;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/modules/network/ForwardingCookieHandler : java/net/CookieHandler {
|
||||
public final class com/facebook/react/modules/network/ForwardingCookieHandler : java/net/CookieHandler {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
public fun addCookies (Ljava/lang/String;Ljava/util/List;)V
|
||||
public fun clearCookies (Lcom/facebook/react/bridge/Callback;)V
|
||||
public fun destroy ()V
|
||||
public final fun addCookies (Ljava/lang/String;Ljava/util/List;)V
|
||||
public final fun clearCookies (Lcom/facebook/react/bridge/Callback;)V
|
||||
public final fun destroy ()V
|
||||
public fun get (Ljava/net/URI;Ljava/util/Map;)Ljava/util/Map;
|
||||
public fun put (Ljava/net/URI;Ljava/util/Map;)V
|
||||
}
|
||||
@@ -3514,14 +3511,6 @@ public class com/facebook/react/modules/network/ProgressResponseBody : okhttp3/R
|
||||
public fun totalBytesRead ()J
|
||||
}
|
||||
|
||||
public 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/ResponseUtil {
|
||||
public fun <init> ()V
|
||||
public static fun onDataReceived (Lcom/facebook/react/bridge/ReactApplicationContext;ILcom/facebook/react/bridge/WritableMap;)V
|
||||
@@ -4091,10 +4080,6 @@ public abstract class com/facebook/react/uimanager/BaseViewManagerDelegate : com
|
||||
public fun setProperty (Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/uimanager/ComponentNameResolver {
|
||||
public abstract fun getComponentNames ()[Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/uimanager/DisplayMetricsHolder {
|
||||
public static final field INSTANCE Lcom/facebook/react/uimanager/DisplayMetricsHolder;
|
||||
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
|
||||
@@ -4106,22 +4091,6 @@ public final class com/facebook/react/uimanager/DisplayMetricsHolder {
|
||||
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/uimanager/FabricViewStateManager {
|
||||
public fun <init> ()V
|
||||
public fun getStateData ()Lcom/facebook/react/bridge/ReadableMap;
|
||||
public fun hasStateWrapper ()Z
|
||||
public fun setState (Lcom/facebook/react/uimanager/FabricViewStateManager$StateUpdateCallback;)V
|
||||
public fun setStateWrapper (Lcom/facebook/react/uimanager/StateWrapper;)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/uimanager/FabricViewStateManager$HasFabricViewStateManager {
|
||||
public abstract fun getFabricViewStateManager ()Lcom/facebook/react/uimanager/FabricViewStateManager;
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/uimanager/FabricViewStateManager$StateUpdateCallback {
|
||||
public abstract fun getStateUpdate ()Lcom/facebook/react/bridge/WritableMap;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/uimanager/FloatUtil {
|
||||
public static final field INSTANCE Lcom/facebook/react/uimanager/FloatUtil;
|
||||
public static final fun floatsEqual (FF)Z
|
||||
@@ -7137,45 +7106,6 @@ public final class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout
|
||||
public fun setRefreshing (Z)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/switchview/ReactSwitchManager : com/facebook/react/uimanager/SimpleViewManager, com/facebook/react/viewmanagers/AndroidSwitchManagerInterface {
|
||||
public static final field REACT_CLASS Ljava/lang/String;
|
||||
public fun <init> ()V
|
||||
protected synthetic fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)V
|
||||
protected fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/switchview/ReactSwitch;)V
|
||||
public fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/LayoutShadowNode;
|
||||
public synthetic fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/ReactShadowNode;
|
||||
protected synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
|
||||
protected fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/switchview/ReactSwitch;
|
||||
protected fun getDelegate ()Lcom/facebook/react/uimanager/ViewManagerDelegate;
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public fun getShadowNodeClass ()Ljava/lang/Class;
|
||||
public fun measure (Landroid/content/Context;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/ReadableMap;FLcom/facebook/yoga/YogaMeasureMode;FLcom/facebook/yoga/YogaMeasureMode;[F)J
|
||||
public synthetic fun receiveCommand (Landroid/view/View;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun receiveCommand (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public synthetic fun setBackgroundColor (Landroid/view/View;I)V
|
||||
public fun setBackgroundColor (Lcom/facebook/react/views/switchview/ReactSwitch;I)V
|
||||
public synthetic fun setDisabled (Landroid/view/View;Z)V
|
||||
public fun setDisabled (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
|
||||
public synthetic fun setEnabled (Landroid/view/View;Z)V
|
||||
public fun setEnabled (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
|
||||
public synthetic fun setNativeValue (Landroid/view/View;Z)V
|
||||
public fun setNativeValue (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
|
||||
public synthetic fun setOn (Landroid/view/View;Z)V
|
||||
public fun setOn (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
|
||||
public synthetic fun setThumbColor (Landroid/view/View;Ljava/lang/Integer;)V
|
||||
public fun setThumbColor (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
|
||||
public synthetic fun setThumbTintColor (Landroid/view/View;Ljava/lang/Integer;)V
|
||||
public fun setThumbTintColor (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
|
||||
public synthetic fun setTrackColorForFalse (Landroid/view/View;Ljava/lang/Integer;)V
|
||||
public fun setTrackColorForFalse (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
|
||||
public synthetic fun setTrackColorForTrue (Landroid/view/View;Ljava/lang/Integer;)V
|
||||
public fun setTrackColorForTrue (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
|
||||
public synthetic fun setTrackTintColor (Landroid/view/View;Ljava/lang/Integer;)V
|
||||
public fun setTrackTintColor (Lcom/facebook/react/views/switchview/ReactSwitch;Ljava/lang/Integer;)V
|
||||
public synthetic fun setValue (Landroid/view/View;Z)V
|
||||
public fun setValue (Lcom/facebook/react/views/switchview/ReactSwitch;Z)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/text/DefaultStyleValuesUtil {
|
||||
public static final field INSTANCE Lcom/facebook/react/views/text/DefaultStyleValuesUtil;
|
||||
public static final fun getDefaultTextColor (Landroid/content/Context;)Landroid/content/res/ColorStateList;
|
||||
@@ -7835,6 +7765,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
|
||||
protected fun dispatchSetPressed (Z)V
|
||||
public fun draw (Landroid/graphics/Canvas;)V
|
||||
protected fun drawChild (Landroid/graphics/Canvas;Landroid/view/View;J)Z
|
||||
public fun endViewTransition (Landroid/view/View;)V
|
||||
protected fun getChildDrawingOrder (II)I
|
||||
public fun getClippingRect (Landroid/graphics/Rect;)V
|
||||
public fun getHitSlopRect ()Landroid/graphics/Rect;
|
||||
|
||||
@@ -591,7 +591,8 @@ android {
|
||||
"src/main/res/shell",
|
||||
"src/main/res/views/alert",
|
||||
"src/main/res/views/modal",
|
||||
"src/main/res/views/uimanager"))
|
||||
"src/main/res/views/uimanager",
|
||||
"src/main/res/views/view"))
|
||||
java.exclude("com/facebook/react/processing")
|
||||
java.exclude("com/facebook/react/module/processing")
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@Deprecated(
|
||||
message = "Use BaseReactPackage instead",
|
||||
replaceWith = ReplaceWith(expression = "BaseReactPackage"))
|
||||
public abstract class TurboReactPackage : BaseReactPackage() {}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
/**
|
||||
* Abstract base for a AsyncTask with result support that should have any RuntimeExceptions it
|
||||
* throws handled by the {@link JSExceptionHandler} registered if the app is in dev mode.
|
||||
*/
|
||||
public abstract class GuardedResultAsyncTask<Result> extends AsyncTask<Void, Void, Result> {
|
||||
|
||||
private final JSExceptionHandler mExceptionHandler;
|
||||
|
||||
protected GuardedResultAsyncTask(ReactContext reactContext) {
|
||||
this(reactContext.getExceptionHandler());
|
||||
}
|
||||
|
||||
protected GuardedResultAsyncTask(JSExceptionHandler exceptionHandler) {
|
||||
mExceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Result doInBackground(Void... params) {
|
||||
try {
|
||||
return doInBackgroundGuarded();
|
||||
} catch (RuntimeException e) {
|
||||
mExceptionHandler.handleException(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onPostExecute(Result result) {
|
||||
try {
|
||||
onPostExecuteGuarded(result);
|
||||
} catch (RuntimeException e) {
|
||||
mExceptionHandler.handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Result doInBackgroundGuarded();
|
||||
|
||||
protected abstract void onPostExecuteGuarded(Result result);
|
||||
}
|
||||
+18
-3
@@ -445,12 +445,18 @@ public class FabricUIManager
|
||||
|
||||
@Override
|
||||
public void markActiveTouchForTag(int surfaceId, int reactTag) {
|
||||
mMountingManager.getSurfaceManager(surfaceId).markActiveTouchForTag(reactTag);
|
||||
SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId);
|
||||
if (surfaceMountingManager != null) {
|
||||
surfaceMountingManager.markActiveTouchForTag(reactTag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sweepActiveTouchForTag(int surfaceId, int reactTag) {
|
||||
mMountingManager.getSurfaceManager(surfaceId).sweepActiveTouchForTag(reactTag);
|
||||
SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId);
|
||||
if (surfaceMountingManager != null) {
|
||||
surfaceMountingManager.sweepActiveTouchForTag(reactTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -872,6 +878,16 @@ public class FabricUIManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method initiates preloading of an image specified by ImageSource. It can later be consumed
|
||||
* by an ImageView.
|
||||
*/
|
||||
public void experimental_prefetchResource(
|
||||
String componentName, int surfaceId, int reactTag, ReadableMapBuffer params) {
|
||||
mMountingManager.experimental_prefetchResource(
|
||||
mReactApplicationContext, componentName, surfaceId, reactTag, params);
|
||||
}
|
||||
|
||||
public void setBinding(FabricUIManagerBinding binding) {
|
||||
mBinding = binding;
|
||||
}
|
||||
@@ -954,7 +970,6 @@ public class FabricUIManager
|
||||
* @param reactTag
|
||||
* @param eventName
|
||||
* @param canCoalesceEvent
|
||||
* @param customCoalesceKey
|
||||
* @param params
|
||||
* @param eventCategory
|
||||
*/
|
||||
|
||||
+7
-1
@@ -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<<6d8d8f4b81d7be882b315d0960499dcb>>
|
||||
* @generated SignedSource<<4a219bb47b1b9d988a164bca19eb4fa9>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -202,6 +202,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun excludeYogaFromRawProps(): Boolean = accessor.excludeYogaFromRawProps()
|
||||
|
||||
/**
|
||||
* Fixes a bug in Differentiator where parent views may be referenced before they're created
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean = accessor.fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
|
||||
/**
|
||||
* Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React.
|
||||
*/
|
||||
|
||||
+11
-1
@@ -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<<031fce8e8b4c20a3e3d6dbecf94d138a>>
|
||||
* @generated SignedSource<<d75efd6beee8dd9d38b5d648fbecbcda>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -49,6 +49,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
|
||||
private var enableUIConsistencyCache: Boolean? = null
|
||||
private var enableViewRecyclingCache: Boolean? = null
|
||||
private var excludeYogaFromRawPropsCache: Boolean? = null
|
||||
private var fixDifferentiatorEmittingUpdatesWithWrongParentTagCache: Boolean? = null
|
||||
private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null
|
||||
private var fixMountingCoordinatorReportedPendingTransactionsOnAndroidCache: Boolean? = null
|
||||
private var fuseboxEnabledDebugCache: Boolean? = null
|
||||
@@ -328,6 +329,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean {
|
||||
var cached = fixDifferentiatorEmittingUpdatesWithWrongParentTagCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTagCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean {
|
||||
var cached = fixMappingOfEventPrioritiesBetweenFabricAndReactCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -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<<35811667ac2543e1f64e27bbdb483ec1>>
|
||||
* @generated SignedSource<<7454ab19a01cfbb0a54f14bd83fc3a90>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -86,6 +86,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun excludeYogaFromRawProps(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun fixMountingCoordinatorReportedPendingTransactionsOnAndroid(): Boolean
|
||||
|
||||
+3
-1
@@ -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<<9d829c58e49164a0b2b6b66bc0ce088a>>
|
||||
* @generated SignedSource<<70d951b2956759280afae4af8f9a2869>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -81,6 +81,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun excludeYogaFromRawProps(): Boolean = false
|
||||
|
||||
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean = true
|
||||
|
||||
override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean = false
|
||||
|
||||
override fun fixMountingCoordinatorReportedPendingTransactionsOnAndroid(): Boolean = false
|
||||
|
||||
+12
-1
@@ -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<<0121e113410a5b0e14eaf74a3076df2f>>
|
||||
* @generated SignedSource<<f60000cb58a9632c3aa193854be3de4e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -53,6 +53,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var enableUIConsistencyCache: Boolean? = null
|
||||
private var enableViewRecyclingCache: Boolean? = null
|
||||
private var excludeYogaFromRawPropsCache: Boolean? = null
|
||||
private var fixDifferentiatorEmittingUpdatesWithWrongParentTagCache: Boolean? = null
|
||||
private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null
|
||||
private var fixMountingCoordinatorReportedPendingTransactionsOnAndroidCache: Boolean? = null
|
||||
private var fuseboxEnabledDebugCache: Boolean? = null
|
||||
@@ -361,6 +362,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean {
|
||||
var cached = fixDifferentiatorEmittingUpdatesWithWrongParentTagCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.fixDifferentiatorEmittingUpdatesWithWrongParentTag()
|
||||
accessedFeatureFlags.add("fixDifferentiatorEmittingUpdatesWithWrongParentTag")
|
||||
fixDifferentiatorEmittingUpdatesWithWrongParentTagCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean {
|
||||
var cached = fixMappingOfEventPrioritiesBetweenFabricAndReactCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -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<<2787d9027695dd14ec6b917a32a1a6de>>
|
||||
* @generated SignedSource<<d62af893c5d18a2152f098ff305ae41e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -81,6 +81,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun excludeYogaFromRawProps(): Boolean
|
||||
|
||||
@DoNotStrip public fun fixDifferentiatorEmittingUpdatesWithWrongParentTag(): Boolean
|
||||
|
||||
@DoNotStrip public fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): Boolean
|
||||
|
||||
@DoNotStrip public fun fixMountingCoordinatorReportedPendingTransactionsOnAndroid(): Boolean
|
||||
|
||||
+18
@@ -21,6 +21,24 @@ public class ReactModuleInfo(
|
||||
public val isCxxModule: Boolean,
|
||||
public val isTurboModule: Boolean
|
||||
) {
|
||||
|
||||
@Deprecated(
|
||||
"This constructor is deprecated and will be removed in the future. Use ReactModuleInfo(String, String, boolean, boolean, boolean, boolean)]",
|
||||
replaceWith =
|
||||
ReplaceWith(
|
||||
expression =
|
||||
"ReactModuleInfo(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)"),
|
||||
level = DeprecationLevel.WARNING)
|
||||
public constructor(
|
||||
name: String,
|
||||
className: String,
|
||||
canOverrideExistingModule: Boolean,
|
||||
needsEagerInit: Boolean,
|
||||
@Suppress("UNUSED_PARAMETER") hasConstants: Boolean,
|
||||
isCxxModule: Boolean,
|
||||
isTurboModule: Boolean
|
||||
) : this(name, className, canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule)
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* Checks if the passed class is a TurboModule. Useful to populate the parameter [isTurboModule]
|
||||
|
||||
+8
-5
@@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.facebook.fbreact.specs.NativeAppearanceSpec
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
|
||||
/** Module that exposes the user's preferred color scheme. */
|
||||
@@ -58,11 +59,13 @@ constructor(
|
||||
}
|
||||
|
||||
public override fun setColorScheme(style: String) {
|
||||
when (style) {
|
||||
"dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
"light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
"unspecified" ->
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
UiThreadUtil.runOnUiThread {
|
||||
when (style) {
|
||||
"dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
"light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
"unspecified" ->
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ constructor(
|
||||
// make sure to forward cookies for any requests via the okHttpClient
|
||||
// so that image requests to endpoints that use cookies still work
|
||||
val container = OkHttpCompat.getCookieJarContainer(client)
|
||||
val handler = ForwardingCookieHandler(context)
|
||||
val handler = ForwardingCookieHandler()
|
||||
container.setCookieJar(JavaNetCookieJar(handler))
|
||||
return newBuilder(context.applicationContext, client)
|
||||
.setNetworkFetcher(ReactOkHttpNetworkFetcher(client))
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import okhttp3.CookieJar;
|
||||
|
||||
public interface CookieJarContainer extends CookieJar {
|
||||
|
||||
void setCookieJar(CookieJar cookieJar);
|
||||
|
||||
void removeCookieJar();
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import okhttp3.CookieJar
|
||||
|
||||
public interface CookieJarContainer : CookieJar {
|
||||
public fun setCookieJar(cookieJar: CookieJar)
|
||||
|
||||
public fun removeCookieJar()
|
||||
}
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.CookieManager;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import java.io.IOException;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Cookie handler that forwards all cookies to the WebView CookieManager.
|
||||
*
|
||||
* <p>This class relies on CookieManager to persist cookies to disk so cookies may be lost if the
|
||||
* application is terminated before it syncs.
|
||||
*/
|
||||
public class ForwardingCookieHandler extends CookieHandler {
|
||||
private static final String VERSION_ZERO_HEADER = "Set-cookie";
|
||||
private static final String VERSION_ONE_HEADER = "Set-cookie2";
|
||||
private static final String COOKIE_HEADER = "Cookie";
|
||||
|
||||
private final ReactContext mContext;
|
||||
private @Nullable CookieManager mCookieManager;
|
||||
|
||||
public ForwardingCookieHandler(ReactContext context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> get(URI uri, Map<String, List<String>> headers)
|
||||
throws IOException {
|
||||
CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager == null) return Collections.emptyMap();
|
||||
|
||||
String cookies = cookieManager.getCookie(uri.toString());
|
||||
if (TextUtils.isEmpty(cookies)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
return Collections.singletonMap(COOKIE_HEADER, Collections.singletonList(cookies));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(URI uri, Map<String, List<String>> headers) throws IOException {
|
||||
String url = uri.toString();
|
||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key != null && isCookieHeader(key)) {
|
||||
addCookies(url, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCookies(final Callback callback) {
|
||||
CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager != null) {
|
||||
cookieManager.removeAllCookies(value -> callback.invoke(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {}
|
||||
|
||||
public void addCookies(final String url, final List<String> cookies) {
|
||||
final CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager == null) return;
|
||||
|
||||
for (String cookie : cookies) {
|
||||
addCookieAsync(url, cookie);
|
||||
}
|
||||
cookieManager.flush();
|
||||
}
|
||||
|
||||
private void addCookieAsync(String url, String cookie) {
|
||||
CookieManager cookieManager = getCookieManager();
|
||||
if (cookieManager != null) {
|
||||
cookieManager.setCookie(url, cookie, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCookieHeader(String name) {
|
||||
return name.equalsIgnoreCase(VERSION_ZERO_HEADER) || name.equalsIgnoreCase(VERSION_ONE_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily
|
||||
* to make sure it's done on a background thread as needed.
|
||||
*/
|
||||
private @Nullable CookieManager getCookieManager() {
|
||||
if (mCookieManager == null) {
|
||||
try {
|
||||
mCookieManager = CookieManager.getInstance();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=559720
|
||||
return null;
|
||||
} catch (Exception exception) {
|
||||
// Ideally we would like to catch a `MissingWebViewPackageException` here.
|
||||
// That API is private so we can't access it.
|
||||
// Historically we used string matching on the error message to understand
|
||||
// if the exception was a Missing Webview One.
|
||||
// OEMs have been customizing that message making really hard to catch it.
|
||||
// Therefore we result to returning null as a default instead of rethrowing
|
||||
// the exception as it will result in a app crash at runtime.
|
||||
// a) We will return null for all the other unhandled conditions when a webview provider is
|
||||
// not found.
|
||||
// b) We already have null checks in place for `getCookieManager()` calls.
|
||||
// c) We have annotated the method as @Nullable to notify future devs about our return type.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return mCookieManager;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import android.webkit.CookieManager
|
||||
import com.facebook.react.bridge.Callback
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import java.io.IOException
|
||||
import java.net.CookieHandler
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Cookie handler that forwards all cookies to the WebView CookieManager.
|
||||
*
|
||||
* This class relies on CookieManager to persist cookies to disk so cookies may be lost if the
|
||||
* application is terminated before it syncs.
|
||||
*/
|
||||
public class ForwardingCookieHandler() : CookieHandler() {
|
||||
|
||||
@Deprecated("Use the default constructor", ReplaceWith("ForwardingCookieHandler()"))
|
||||
public constructor(@Suppress("UNUSED_PARAMETER") reactContext: ReactContext) : this() {}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun get(uri: URI, headers: Map<String, List<String>>): Map<String, List<String>> {
|
||||
val cookies = cookieManager?.getCookie(uri.toString())
|
||||
if (cookies.isNullOrEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
return mapOf(COOKIE_HEADER to listOf(cookies))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun put(uri: URI, headers: Map<String, List<String>>) {
|
||||
val url = uri.toString()
|
||||
for ((key, value) in headers) {
|
||||
if (isCookieHeader(key)) {
|
||||
addCookies(url, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun clearCookies(callback: Callback): Unit {
|
||||
cookieManager?.removeAllCookies { value -> callback.invoke(value) }
|
||||
}
|
||||
|
||||
public fun destroy(): Unit = Unit
|
||||
|
||||
public fun addCookies(url: String, cookies: List<String>): Unit {
|
||||
for (cookie in cookies) {
|
||||
addCookieAsync(url, cookie)
|
||||
}
|
||||
cookieManager?.flush()
|
||||
}
|
||||
|
||||
private fun addCookieAsync(url: String, cookie: String) {
|
||||
cookieManager?.setCookie(url, cookie, null)
|
||||
}
|
||||
|
||||
private var cookieManager: CookieManager? = null
|
||||
/**
|
||||
* Instantiating CookieManager will load the Chromium task taking a 100ish ms so we do it lazily
|
||||
* to make sure it's done on a background thread as needed.
|
||||
*/
|
||||
get() {
|
||||
if (field == null) {
|
||||
try {
|
||||
field = CookieManager.getInstance()
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=559720
|
||||
return null
|
||||
} catch (exception: Exception) {
|
||||
// Ideally we would like to catch a `MissingWebViewPackageException` here.
|
||||
// That API is private so we can't access it.
|
||||
// Historically we used string matching on the error message to understand
|
||||
// if the exception was a Missing Webview One.
|
||||
// OEMs have been customizing that message making really hard to catch it.
|
||||
// Therefore we result to returning null as a default instead of rethrowing
|
||||
// the exception as it will result in a app crash at runtime.
|
||||
// a) We will return null for all the other unhandled conditions when a webview provider
|
||||
// is
|
||||
// not found.
|
||||
// b) We already have null checks in place for `getCookieManager()` calls.
|
||||
// c) We have annotated the method as @Nullable to notify future devs about our return
|
||||
// type.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val VERSION_ZERO_HEADER = "Set-cookie"
|
||||
private const val VERSION_ONE_HEADER = "Set-cookie2"
|
||||
private const val COOKIE_HEADER = "Cookie"
|
||||
|
||||
private fun isCookieHeader(name: String): Boolean =
|
||||
name.equals(VERSION_ZERO_HEADER, ignoreCase = true) ||
|
||||
name.equals(VERSION_ONE_HEADER, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -97,14 +97,14 @@ public final class NetworkingModule extends NativeNetworkingAndroidSpec {
|
||||
customClientBuilder = null;
|
||||
|
||||
private final OkHttpClient mClient;
|
||||
private final ForwardingCookieHandler mCookieHandler;
|
||||
private final ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler();
|
||||
private final @Nullable String mDefaultUserAgent;
|
||||
private final CookieJarContainer mCookieJarContainer;
|
||||
private final Set<Integer> mRequestIds;
|
||||
private final Set<Integer> mRequestIds = new HashSet<>();
|
||||
private final List<RequestBodyHandler> mRequestBodyHandlers = new ArrayList<>();
|
||||
private final List<UriHandler> mUriHandlers = new ArrayList<>();
|
||||
private final List<ResponseHandler> mResponseHandlers = new ArrayList<>();
|
||||
private boolean mShuttingDown;
|
||||
private boolean mShuttingDown = false;
|
||||
|
||||
public NetworkingModule(
|
||||
ReactApplicationContext reactContext,
|
||||
@@ -121,11 +121,8 @@ public final class NetworkingModule extends NativeNetworkingAndroidSpec {
|
||||
client = clientBuilder.build();
|
||||
}
|
||||
mClient = client;
|
||||
mCookieHandler = new ForwardingCookieHandler(reactContext);
|
||||
mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
|
||||
mShuttingDown = false;
|
||||
mCookieJarContainer = (CookieJarContainer) client.cookieJar();
|
||||
mDefaultUserAgent = defaultUserAgent;
|
||||
mRequestIds = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.network;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
/** Basic okhttp3 CookieJar container */
|
||||
public class ReactCookieJarContainer implements CookieJarContainer {
|
||||
|
||||
@Nullable private CookieJar cookieJar = null;
|
||||
|
||||
@Override
|
||||
public void setCookieJar(CookieJar cookieJar) {
|
||||
this.cookieJar = cookieJar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCookieJar() {
|
||||
cookieJar = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
|
||||
if (cookieJar != null) {
|
||||
cookieJar.saveFromResponse(url, cookies);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(HttpUrl url) {
|
||||
if (cookieJar != null) {
|
||||
List<Cookie> cookies = cookieJar.loadForRequest(url);
|
||||
ArrayList<Cookie> validatedCookies = new ArrayList<>();
|
||||
for (Cookie cookie : cookies) {
|
||||
try {
|
||||
Headers.Builder cookieChecker = new Headers.Builder();
|
||||
cookieChecker.add(cookie.name(), cookie.value());
|
||||
validatedCookies.add(cookie);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
return validatedCookies;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION_ERROR") // Conflicting okhttp versions
|
||||
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import java.util.ArrayList
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
/** Basic okhttp3 CookieJar container */
|
||||
internal class ReactCookieJarContainer : CookieJarContainer {
|
||||
|
||||
private var cookieJar: CookieJar? = null
|
||||
|
||||
override fun setCookieJar(cookieJar: CookieJar) {
|
||||
this.cookieJar = cookieJar
|
||||
}
|
||||
|
||||
override fun removeCookieJar() {
|
||||
cookieJar = null
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
cookieJar?.saveFromResponse(url, cookies)
|
||||
}
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
val cookieJar = cookieJar ?: return emptyList()
|
||||
val cookies = cookieJar.loadForRequest(url)
|
||||
val validatedCookies = ArrayList<Cookie>()
|
||||
for (cookie in cookies) {
|
||||
try {
|
||||
val cookieChecker = Headers.Builder()
|
||||
cookieChecker.add(cookie.name(), cookie.value())
|
||||
validatedCookies.add(cookie)
|
||||
} catch (ignored: IllegalArgumentException) {}
|
||||
}
|
||||
return validatedCookies
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -46,14 +46,12 @@ public final class WebSocketModule extends NativeWebSocketModuleSpec {
|
||||
|
||||
private final Map<Integer, WebSocket> mWebSocketConnections = new ConcurrentHashMap<>();
|
||||
private final Map<Integer, ContentHandler> mContentHandlers = new ConcurrentHashMap<>();
|
||||
|
||||
private ForwardingCookieHandler mCookieHandler;
|
||||
private final ForwardingCookieHandler mCookieHandler = new ForwardingCookieHandler();
|
||||
|
||||
private static @Nullable CustomClientBuilder customClientBuilder = null;
|
||||
|
||||
public WebSocketModule(ReactApplicationContext context) {
|
||||
super(context);
|
||||
mCookieHandler = new ForwardingCookieHandler(context);
|
||||
}
|
||||
|
||||
public static void setCustomClientBuilder(CustomClientBuilder ccb) {
|
||||
|
||||
+1
@@ -84,6 +84,7 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
view.setTag(R.id.accessibility_actions, null);
|
||||
view.setTag(R.id.accessibility_value, null);
|
||||
view.setTag(R.id.accessibility_state_expanded, null);
|
||||
view.setTag(R.id.view_clipped, null);
|
||||
|
||||
// This indirectly calls (and resets):
|
||||
// setTranslationX
|
||||
|
||||
+4
-5
@@ -5,13 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
package com.facebook.react.uimanager
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStripAny;
|
||||
import com.facebook.proguard.annotations.DoNotStripAny
|
||||
|
||||
@DoNotStripAny
|
||||
public interface ComponentNameResolver {
|
||||
|
||||
internal interface ComponentNameResolver {
|
||||
/* returns a list of all the component names that are registered in React Native. */
|
||||
String[] getComponentNames();
|
||||
public val componentNames: Array<String>?
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
/**
|
||||
* This is a helper base class for ViewGroups that use Fabric State.
|
||||
*
|
||||
* <p>Reason to use this: UpdateState calls from the View layer to the Fabric core can fail, and
|
||||
* optionally Fabric will call a "failure callback" if that happens. This class abstracts that and
|
||||
* makes it easier ensure that State in Fabric is always up-to-date.
|
||||
*
|
||||
* <p>1. Whenever ViewManager.updateState is called, call View.setStateWrapper. 2. Instead of
|
||||
* calling StateWrapper.updateState directly, call View.setState and it will automatically keep
|
||||
* retrying the UpdateState call until it succeeds; or you call setState again; or the View layer is
|
||||
* updated with a newer StateWrapper.
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
@Deprecated(
|
||||
since =
|
||||
"Deprecated class since v0.73.0, please use com.facebook.react.uimanager.StateWrapper"
|
||||
+ " instead.",
|
||||
forRemoval = true)
|
||||
public class FabricViewStateManager {
|
||||
private static final String TAG = "FabricViewStateManager";
|
||||
|
||||
@Deprecated
|
||||
public interface HasFabricViewStateManager {
|
||||
FabricViewStateManager getFabricViewStateManager();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public interface StateUpdateCallback {
|
||||
WritableMap getStateUpdate();
|
||||
}
|
||||
|
||||
@Nullable private StateWrapper mStateWrapper = null;
|
||||
|
||||
@Deprecated
|
||||
public void setStateWrapper(StateWrapper stateWrapper) {
|
||||
mStateWrapper = stateWrapper;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean hasStateWrapper() {
|
||||
return mStateWrapper != null;
|
||||
}
|
||||
|
||||
private void setState(
|
||||
@Nullable final StateWrapper stateWrapper,
|
||||
final StateUpdateCallback stateUpdateCallback,
|
||||
final int numTries) {
|
||||
// The StateWrapper will change, breaking the async loop, whenever the UpdateState MountItem
|
||||
// is executed.
|
||||
// The caller is responsible for detecting if data is up-to-date, and doing nothing, or
|
||||
// detecting if state is stale and calling setState again.
|
||||
if (stateWrapper == null) {
|
||||
FLog.e(TAG, "setState called without a StateWrapper");
|
||||
return;
|
||||
}
|
||||
if (stateWrapper != mStateWrapper) {
|
||||
return;
|
||||
}
|
||||
// We bail out after an arbitrary number of tries. In practice this should never go higher
|
||||
// than 2 or 3, but there's nothing guaranteeing that.
|
||||
if (numTries > 60) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Nullable WritableMap stateUpdate = stateUpdateCallback.getStateUpdate();
|
||||
if (stateUpdate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: State update cannot fail; remove `failureRunnable` and custom retrying logic.
|
||||
stateWrapper.updateState(stateUpdate);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setState(final StateUpdateCallback stateUpdateCallback) {
|
||||
setState(mStateWrapper, stateUpdateCallback, 0);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public @Nullable ReadableMap getStateData() {
|
||||
return mStateWrapper != null ? mStateWrapper.getStateData() : null;
|
||||
}
|
||||
}
|
||||
+11
-12
@@ -5,10 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
package com.facebook.react.uimanager
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
||||
/** Interface for the root native view of a React native application. */
|
||||
public interface RootView {
|
||||
@@ -17,21 +17,20 @@ public interface RootView {
|
||||
* Called when a child starts a native gesture (e.g. a scroll in a ScrollView). Should be called
|
||||
* from the child's onTouchIntercepted implementation.
|
||||
*/
|
||||
void onChildStartedNativeGesture(View childView, MotionEvent ev);
|
||||
public fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent)
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
default void onChildStartedNativeGesture(MotionEvent ev) {
|
||||
onChildStartedNativeGesture(null, ev);
|
||||
@Deprecated(
|
||||
message = "Use onChildStartedNativeGesture with a childView parameter.",
|
||||
replaceWith = ReplaceWith("onChildStartedNativeGesture"))
|
||||
public fun onChildStartedNativeGesture(ev: MotionEvent) {
|
||||
onChildStartedNativeGesture(null, ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child ends a native gesture. Should be called from the child's onTouchIntercepted
|
||||
* implementation.
|
||||
*/
|
||||
void onChildEndedNativeGesture(View childView, MotionEvent ev);
|
||||
public fun onChildEndedNativeGesture(childView: View, ev: MotionEvent)
|
||||
|
||||
void handleException(Throwable t);
|
||||
public fun handleException(t: Throwable)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user