mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f99108e161 | ||
|
|
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 |
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',
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
getBuckModeForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
getFantomTestConfig,
|
||||
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,7 +95,9 @@ 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'),
|
||||
@@ -163,76 +121,54 @@ 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 ?? ''}`,
|
||||
},
|
||||
});
|
||||
]);
|
||||
|
||||
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 +184,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,158 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import {spawnSync} from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {extract, parse} from 'jest-docblock';
|
||||
import os from 'os';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import {SourceMapConsumer} from 'source-map';
|
||||
|
||||
type DocblockPragmas = {[key: string]: string | string[]};
|
||||
type FantomTestMode = 'dev' | 'opt';
|
||||
type FantomTestConfig = {
|
||||
mode: FantomTestMode,
|
||||
};
|
||||
|
||||
const DEFAULT_MODE: FantomTestMode = 'dev';
|
||||
|
||||
/**
|
||||
* Extracts the Fantom configuration from the test file, specified as part of
|
||||
* the docblock comment. E.g.:
|
||||
*
|
||||
* ```
|
||||
* /**
|
||||
* * @flow strict-local
|
||||
* * @fantom mode:opt
|
||||
* *
|
||||
* ```
|
||||
*
|
||||
* So far the only supported option is `mode`, which can be 'dev' or 'opt'.
|
||||
*/
|
||||
export function getFantomTestConfig(testPath: string): FantomTestConfig {
|
||||
const docblock = extract(fs.readFileSync(testPath, 'utf8'));
|
||||
const pragmas = parse(docblock) as DocblockPragmas;
|
||||
|
||||
const config = {
|
||||
mode: DEFAULT_MODE,
|
||||
};
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
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';
|
||||
@@ -307,6 +307,48 @@ class Expect {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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,9 +1,9 @@
|
||||
@generated SignedSource<<6b92b66e59525cef52902139f863f175>>
|
||||
Git revision: b61aae3ccc6e2684dfbf1e2a06b0f985b459f11f
|
||||
@generated SignedSource<<d09e665db66f49ff47c245adf0a4b543>>
|
||||
Git revision: 6b80704fd50ea0bf10f5f5da5a4343de29aff8b2
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebookexperimental/rn-chrome-devtools-frontend
|
||||
Remote branch: main
|
||||
Remote branch: 0.77-stable
|
||||
GN build args (overrides only):
|
||||
is_official_build = true
|
||||
Git status in checkout:
|
||||
|
||||
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,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
+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',
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 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
|
||||
*/
|
||||
|
||||
import './setUpFeatureFlags';
|
||||
import setUpReactFabricPublicInstanceFantomTests from './setUpReactFabricPublicInstanceFantomTests';
|
||||
|
||||
setUpReactFabricPublicInstanceFantomTests({isModern: true});
|
||||
packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/__tests__/setUpFeatureFlags.js
Vendored
+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.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import * as ReactNativeFeatureFlags from '../../../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
|
||||
ReactNativeFeatureFlags.override({
|
||||
enableAccessToHostTreeInFabric: () => 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
+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
|
||||
|
||||
@@ -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;
|
||||
@@ -7137,45 +7141,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;
|
||||
|
||||
+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() {}
|
||||
+8
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-232
@@ -1,232 +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.
|
||||
*/
|
||||
|
||||
// switchview because switch is a keyword
|
||||
package com.facebook.react.views.switchview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.LayoutShadowNode;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.ViewManagerDelegate;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.viewmanagers.AndroidSwitchManagerDelegate;
|
||||
import com.facebook.react.viewmanagers.AndroidSwitchManagerInterface;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaMeasureMode;
|
||||
import com.facebook.yoga.YogaMeasureOutput;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
|
||||
/** View manager for {@link ReactSwitch} components. */
|
||||
public class ReactSwitchManager extends SimpleViewManager<ReactSwitch>
|
||||
implements AndroidSwitchManagerInterface<ReactSwitch> {
|
||||
|
||||
public static final String REACT_CLASS = "AndroidSwitch";
|
||||
|
||||
static class ReactSwitchShadowNode extends LayoutShadowNode implements YogaMeasureFunction {
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private boolean mMeasured;
|
||||
|
||||
private ReactSwitchShadowNode() {
|
||||
initMeasureFunction();
|
||||
}
|
||||
|
||||
private void initMeasureFunction() {
|
||||
setMeasureFunction(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long measure(
|
||||
YogaNode node,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode) {
|
||||
if (!mMeasured) {
|
||||
// Create a switch with the default config and measure it; since we don't (currently)
|
||||
// support setting custom switch text, this is fine, as all switches will measure the same
|
||||
// on a specific device/theme/locale combination.
|
||||
ReactSwitch reactSwitch = new ReactSwitch(getThemedContext());
|
||||
reactSwitch.setShowText(false);
|
||||
final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
reactSwitch.measure(spec, spec);
|
||||
mWidth = reactSwitch.getMeasuredWidth();
|
||||
mHeight = reactSwitch.getMeasuredHeight();
|
||||
mMeasured = true;
|
||||
}
|
||||
|
||||
return YogaMeasureOutput.make(mWidth, mHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private static final CompoundButton.OnCheckedChangeListener ON_CHECKED_CHANGE_LISTENER =
|
||||
new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
ReactContext reactContext = (ReactContext) buttonView.getContext();
|
||||
|
||||
int reactTag = buttonView.getId();
|
||||
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag)
|
||||
.dispatchEvent(
|
||||
new ReactSwitchEvent(
|
||||
UIManagerHelper.getSurfaceId(reactContext), reactTag, isChecked));
|
||||
}
|
||||
};
|
||||
|
||||
private final ViewManagerDelegate<ReactSwitch> mDelegate;
|
||||
|
||||
public ReactSwitchManager() {
|
||||
mDelegate = new AndroidSwitchManagerDelegate<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutShadowNode createShadowNodeInstance() {
|
||||
return new ReactSwitchShadowNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getShadowNodeClass() {
|
||||
return ReactSwitchShadowNode.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactSwitch createViewInstance(ThemedReactContext context) {
|
||||
ReactSwitch view = new ReactSwitch(context);
|
||||
view.setShowText(false);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackgroundColor(ReactSwitch view, @ColorInt int backgroundColor) {
|
||||
view.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "disabled", defaultBoolean = false)
|
||||
public void setDisabled(ReactSwitch view, boolean disabled) {
|
||||
view.setEnabled(!disabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
|
||||
public void setEnabled(ReactSwitch view, boolean enabled) {
|
||||
view.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = ViewProps.ON)
|
||||
public void setOn(ReactSwitch view, boolean on) {
|
||||
setValueInternal(view, on);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "value")
|
||||
public void setValue(ReactSwitch view, boolean value) {
|
||||
setValueInternal(view, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "thumbTintColor", customType = "Color")
|
||||
public void setThumbTintColor(ReactSwitch view, @Nullable Integer color) {
|
||||
this.setThumbColor(view, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "thumbColor", customType = "Color")
|
||||
public void setThumbColor(ReactSwitch view, @Nullable Integer color) {
|
||||
view.setThumbColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "trackColorForFalse", customType = "Color")
|
||||
public void setTrackColorForFalse(ReactSwitch view, @Nullable Integer color) {
|
||||
view.setTrackColorForFalse(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "trackColorForTrue", customType = "Color")
|
||||
public void setTrackColorForTrue(ReactSwitch view, @Nullable Integer color) {
|
||||
view.setTrackColorForTrue(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ReactProp(name = "trackTintColor", customType = "Color")
|
||||
public void setTrackTintColor(ReactSwitch view, @Nullable Integer color) {
|
||||
view.setTrackColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNativeValue(ReactSwitch view, boolean value) {
|
||||
setValueInternal(view, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveCommand(
|
||||
@NonNull ReactSwitch view, String commandId, @Nullable ReadableArray args) {
|
||||
switch (commandId) {
|
||||
case "setNativeValue":
|
||||
setValueInternal(view, args != null && args.getBoolean(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSwitch view) {
|
||||
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewManagerDelegate<ReactSwitch> getDelegate() {
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long measure(
|
||||
Context context,
|
||||
ReadableMap localData,
|
||||
ReadableMap props,
|
||||
ReadableMap state,
|
||||
float width,
|
||||
YogaMeasureMode widthMode,
|
||||
float height,
|
||||
YogaMeasureMode heightMode,
|
||||
@Nullable float[] attachmentsPositions) {
|
||||
ReactSwitch view = new ReactSwitch(context);
|
||||
view.setShowText(false);
|
||||
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
view.measure(measureSpec, measureSpec);
|
||||
return YogaMeasureOutput.make(
|
||||
PixelUtil.toDIPFromPixel(view.getMeasuredWidth()),
|
||||
PixelUtil.toDIPFromPixel(view.getMeasuredHeight()));
|
||||
}
|
||||
|
||||
private static void setValueInternal(ReactSwitch view, boolean value) {
|
||||
// we set the checked change listener to null and then restore it so that we don't fire an
|
||||
// onChange event to JS when JS itself is updating the value of the switch
|
||||
view.setOnCheckedChangeListener(null);
|
||||
view.setOn(value);
|
||||
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER);
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.switchview
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import androidx.annotation.ColorInt
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.uimanager.BaseViewManager
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
import com.facebook.react.uimanager.ViewManagerDelegate
|
||||
import com.facebook.react.uimanager.ViewProps
|
||||
import com.facebook.react.uimanager.annotations.ReactProp
|
||||
import com.facebook.react.viewmanagers.AndroidSwitchManagerDelegate
|
||||
import com.facebook.react.viewmanagers.AndroidSwitchManagerInterface
|
||||
import com.facebook.yoga.YogaMeasureMode
|
||||
import com.facebook.yoga.YogaMeasureOutput
|
||||
|
||||
internal class ReactSwitchManager :
|
||||
BaseViewManager<ReactSwitch, ReactSwitchShadowNode>(),
|
||||
AndroidSwitchManagerInterface<ReactSwitch> {
|
||||
|
||||
private val delegate: ViewManagerDelegate<ReactSwitch> = AndroidSwitchManagerDelegate(this)
|
||||
|
||||
override fun getName(): String = REACT_CLASS
|
||||
|
||||
override fun createShadowNodeInstance(): ReactSwitchShadowNode = ReactSwitchShadowNode()
|
||||
|
||||
override fun getShadowNodeClass(): Class<ReactSwitchShadowNode> =
|
||||
ReactSwitchShadowNode::class.java
|
||||
|
||||
override fun createViewInstance(context: ThemedReactContext): ReactSwitch =
|
||||
ReactSwitch(context).apply { showText = false }
|
||||
|
||||
override fun setBackgroundColor(view: ReactSwitch, @ColorInt backgroundColor: Int) {
|
||||
view.setBackgroundColor(backgroundColor)
|
||||
}
|
||||
|
||||
@ReactProp(name = "disabled", defaultBoolean = false)
|
||||
override fun setDisabled(view: ReactSwitch, disabled: Boolean) {
|
||||
view.isEnabled = !disabled
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
|
||||
override fun setEnabled(view: ReactSwitch, enabled: Boolean) {
|
||||
view.isEnabled = enabled
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.ON)
|
||||
override fun setOn(view: ReactSwitch, on: Boolean) {
|
||||
setValueInternal(view, on)
|
||||
}
|
||||
|
||||
@ReactProp(name = "value")
|
||||
override fun setValue(view: ReactSwitch, value: Boolean) {
|
||||
setValueInternal(view, value)
|
||||
}
|
||||
|
||||
@ReactProp(name = "thumbTintColor", customType = "Color")
|
||||
override fun setThumbTintColor(view: ReactSwitch, color: Int?) {
|
||||
setThumbColor(view, color)
|
||||
}
|
||||
|
||||
@ReactProp(name = "thumbColor", customType = "Color")
|
||||
override fun setThumbColor(view: ReactSwitch, color: Int?) {
|
||||
view.setThumbColor(color)
|
||||
}
|
||||
|
||||
@ReactProp(name = "trackColorForFalse", customType = "Color")
|
||||
override fun setTrackColorForFalse(view: ReactSwitch, color: Int?) {
|
||||
view.setTrackColorForFalse(color)
|
||||
}
|
||||
|
||||
@ReactProp(name = "trackColorForTrue", customType = "Color")
|
||||
override fun setTrackColorForTrue(view: ReactSwitch, color: Int?) {
|
||||
view.setTrackColorForTrue(color)
|
||||
}
|
||||
|
||||
@ReactProp(name = "trackTintColor", customType = "Color")
|
||||
override fun setTrackTintColor(view: ReactSwitch, color: Int?) {
|
||||
view.setTrackColor(color)
|
||||
}
|
||||
|
||||
override fun setNativeValue(view: ReactSwitch, value: Boolean) {
|
||||
setValueInternal(view, value)
|
||||
}
|
||||
|
||||
override fun receiveCommand(view: ReactSwitch, commandId: String, args: ReadableArray?) {
|
||||
when (commandId) {
|
||||
"setNativeValue" -> setValueInternal(view, args?.getBoolean(0) ?: false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addEventEmitters(reactContext: ThemedReactContext, view: ReactSwitch) {
|
||||
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER)
|
||||
}
|
||||
|
||||
override fun updateExtraData(root: ReactSwitch, extraData: Any) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun getDelegate(): ViewManagerDelegate<ReactSwitch> = delegate
|
||||
|
||||
override fun measure(
|
||||
context: Context,
|
||||
localData: ReadableMap?,
|
||||
props: ReadableMap?,
|
||||
state: ReadableMap?,
|
||||
width: Float,
|
||||
widthMode: YogaMeasureMode,
|
||||
height: Float,
|
||||
heightMode: YogaMeasureMode,
|
||||
attachmentsPositions: FloatArray?
|
||||
): Long {
|
||||
val view = ReactSwitch(context).apply { showText = false }
|
||||
val measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||
view.measure(measureSpec, measureSpec)
|
||||
return YogaMeasureOutput.make(
|
||||
PixelUtil.toDIPFromPixel(view.measuredWidth.toFloat()),
|
||||
PixelUtil.toDIPFromPixel(view.measuredHeight.toFloat()))
|
||||
}
|
||||
|
||||
private fun setValueInternal(view: ReactSwitch, value: Boolean) {
|
||||
// Temporarily remove the listener to avoid triggering JS events
|
||||
view.setOnCheckedChangeListener(null)
|
||||
view.setOn(value)
|
||||
view.setOnCheckedChangeListener(ON_CHECKED_CHANGE_LISTENER)
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
public const val REACT_CLASS: String = "AndroidSwitch"
|
||||
|
||||
private val ON_CHECKED_CHANGE_LISTENER =
|
||||
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
|
||||
val reactContext = buttonView.context as ReactContext
|
||||
val reactTag = buttonView.id
|
||||
UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag)
|
||||
?.dispatchEvent(
|
||||
ReactSwitchEvent(UIManagerHelper.getSurfaceId(reactContext), reactTag, isChecked))
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.switchview
|
||||
|
||||
import android.view.View
|
||||
import com.facebook.react.uimanager.LayoutShadowNode
|
||||
import com.facebook.yoga.YogaMeasureFunction
|
||||
import com.facebook.yoga.YogaMeasureMode
|
||||
import com.facebook.yoga.YogaMeasureOutput
|
||||
import com.facebook.yoga.YogaNode
|
||||
|
||||
internal class ReactSwitchShadowNode : LayoutShadowNode(), YogaMeasureFunction {
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
private var measured = false
|
||||
|
||||
init {
|
||||
initMeasureFunction()
|
||||
}
|
||||
|
||||
private fun initMeasureFunction() {
|
||||
setMeasureFunction(this)
|
||||
}
|
||||
|
||||
override fun measure(
|
||||
node: YogaNode,
|
||||
width: Float,
|
||||
widthMode: YogaMeasureMode,
|
||||
height: Float,
|
||||
heightMode: YogaMeasureMode
|
||||
): Long {
|
||||
if (!measured) {
|
||||
// Create a switch with the default config and measure it; since we don't (currently)
|
||||
// support setting custom switch text, this is fine, as all switches will measure the same
|
||||
// on a specific device/theme/locale combination.
|
||||
val reactSwitch = ReactSwitch(themedContext)
|
||||
reactSwitch.showText = false
|
||||
val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||
reactSwitch.measure(spec, spec)
|
||||
this.width = reactSwitch.measuredWidth
|
||||
this.height = reactSwitch.measuredHeight
|
||||
measured = true
|
||||
}
|
||||
|
||||
return YogaMeasureOutput.make(this.width, this.height)
|
||||
}
|
||||
}
|
||||
@@ -221,6 +221,13 @@ Pod::Spec.new do |s|
|
||||
|
||||
end
|
||||
|
||||
s.subspec "consistency" do |ss|
|
||||
ss.dependency folly_dep_name, folly_version
|
||||
ss.compiler_flags = folly_compiler_flags
|
||||
ss.source_files = "react/renderer/consistency/**/*.{m,mm,cpp,h}"
|
||||
ss.header_dir = "react/renderer/consistency"
|
||||
end
|
||||
|
||||
s.subspec "uimanager" do |ss|
|
||||
ss.subspec "consistency" do |sss|
|
||||
sss.dependency folly_dep_name, folly_version
|
||||
|
||||
@@ -125,18 +125,14 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
|
||||
shouldSendOKResponse = true;
|
||||
isFinishedHandlingRequest = true;
|
||||
} else if (req.method == "FuseboxClient.setClientMetadata") {
|
||||
} else if (req.method == "ReactNativeApplication.enable") {
|
||||
sessionState_.isReactNativeApplicationDomainEnabled = true;
|
||||
fuseboxClientType_ = FuseboxClientType::Fusebox;
|
||||
|
||||
if (sessionState_.isLogDomainEnabled) {
|
||||
sendFuseboxNotice();
|
||||
}
|
||||
|
||||
shouldSendOKResponse = true;
|
||||
isFinishedHandlingRequest = true;
|
||||
} else if (req.method == "ReactNativeApplication.enable") {
|
||||
sessionState_.isReactNativeApplicationDomainEnabled = true;
|
||||
|
||||
frontendChannel_(cdp::jsonNotification(
|
||||
"ReactNativeApplication.metadataUpdated",
|
||||
createHostMetadataPayload(hostMetadata_)));
|
||||
|
||||
@@ -348,21 +348,6 @@ TYPED_TEST(JsiIntegrationPortableTest, ExceptionDuringAddBindingIsIgnored) {
|
||||
EXPECT_TRUE(this->eval("globalThis.foo === 42").getBool());
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, FuseboxSetClientMetadata) {
|
||||
this->connect();
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "FuseboxClient.setClientMetadata",
|
||||
"params": {}
|
||||
})");
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, ReactNativeApplicationEnable) {
|
||||
this->connect();
|
||||
|
||||
|
||||
+30
-8
@@ -41,16 +41,38 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes& rhs) const {
|
||||
|
||||
#if RN_DEBUG_STRING_CONVERTIBLE
|
||||
SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const {
|
||||
ParagraphAttributes paragraphAttributes{};
|
||||
return {
|
||||
debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines),
|
||||
debugStringConvertibleItem("ellipsizeMode", ellipsizeMode),
|
||||
debugStringConvertibleItem("textBreakStrategy", textBreakStrategy),
|
||||
debugStringConvertibleItem("adjustsFontSizeToFit", adjustsFontSizeToFit),
|
||||
debugStringConvertibleItem("minimumFontSize", minimumFontSize),
|
||||
debugStringConvertibleItem("maximumFontSize", maximumFontSize),
|
||||
debugStringConvertibleItem("includeFontPadding", includeFontPadding),
|
||||
debugStringConvertibleItem(
|
||||
"android_hyphenationFrequency", android_hyphenationFrequency)};
|
||||
"maximumNumberOfLines",
|
||||
maximumNumberOfLines,
|
||||
paragraphAttributes.maximumNumberOfLines),
|
||||
debugStringConvertibleItem(
|
||||
"ellipsizeMode", ellipsizeMode, paragraphAttributes.ellipsizeMode),
|
||||
debugStringConvertibleItem(
|
||||
"textBreakStrategy",
|
||||
textBreakStrategy,
|
||||
paragraphAttributes.textBreakStrategy),
|
||||
debugStringConvertibleItem(
|
||||
"adjustsFontSizeToFit",
|
||||
adjustsFontSizeToFit,
|
||||
paragraphAttributes.adjustsFontSizeToFit),
|
||||
debugStringConvertibleItem(
|
||||
"minimumFontSize",
|
||||
minimumFontSize,
|
||||
paragraphAttributes.minimumFontSize),
|
||||
debugStringConvertibleItem(
|
||||
"maximumFontSize",
|
||||
maximumFontSize,
|
||||
paragraphAttributes.maximumFontSize),
|
||||
debugStringConvertibleItem(
|
||||
"includeFontPadding",
|
||||
includeFontPadding,
|
||||
paragraphAttributes.includeFontPadding),
|
||||
debugStringConvertibleItem(
|
||||
"android_hyphenationFrequency",
|
||||
android_hyphenationFrequency,
|
||||
paragraphAttributes.android_hyphenationFrequency)};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
+77
-29
@@ -197,49 +197,97 @@ TextAttributes TextAttributes::defaultTextAttributes() {
|
||||
|
||||
#if RN_DEBUG_STRING_CONVERTIBLE
|
||||
SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
|
||||
const auto& textAttributes = TextAttributes::defaultTextAttributes();
|
||||
return {
|
||||
// Color
|
||||
debugStringConvertibleItem("backgroundColor", backgroundColor),
|
||||
debugStringConvertibleItem("foregroundColor", foregroundColor),
|
||||
debugStringConvertibleItem("opacity", opacity),
|
||||
debugStringConvertibleItem(
|
||||
"backgroundColor", backgroundColor, textAttributes.backgroundColor),
|
||||
debugStringConvertibleItem(
|
||||
"foregroundColor", foregroundColor, textAttributes.foregroundColor),
|
||||
debugStringConvertibleItem("opacity", opacity, textAttributes.opacity),
|
||||
|
||||
// Font
|
||||
debugStringConvertibleItem("fontFamily", fontFamily),
|
||||
debugStringConvertibleItem("fontSize", fontSize),
|
||||
debugStringConvertibleItem("fontSizeMultiplier", fontSizeMultiplier),
|
||||
debugStringConvertibleItem("fontWeight", fontWeight),
|
||||
debugStringConvertibleItem("fontStyle", fontStyle),
|
||||
debugStringConvertibleItem("fontVariant", fontVariant),
|
||||
debugStringConvertibleItem("allowFontScaling", allowFontScaling),
|
||||
debugStringConvertibleItem("dynamicTypeRamp", dynamicTypeRamp),
|
||||
debugStringConvertibleItem("letterSpacing", letterSpacing),
|
||||
debugStringConvertibleItem(
|
||||
"fontFamily", fontFamily, textAttributes.fontFamily),
|
||||
debugStringConvertibleItem("fontSize", fontSize, textAttributes.fontSize),
|
||||
debugStringConvertibleItem(
|
||||
"fontSizeMultiplier",
|
||||
fontSizeMultiplier,
|
||||
textAttributes.fontSizeMultiplier),
|
||||
debugStringConvertibleItem(
|
||||
"fontWeight", fontWeight, textAttributes.fontWeight),
|
||||
debugStringConvertibleItem(
|
||||
"fontStyle", fontStyle, textAttributes.fontStyle),
|
||||
debugStringConvertibleItem(
|
||||
"fontVariant", fontVariant, textAttributes.fontVariant),
|
||||
debugStringConvertibleItem(
|
||||
"allowFontScaling",
|
||||
allowFontScaling,
|
||||
textAttributes.allowFontScaling),
|
||||
debugStringConvertibleItem(
|
||||
"dynamicTypeRamp", dynamicTypeRamp, textAttributes.dynamicTypeRamp),
|
||||
debugStringConvertibleItem(
|
||||
"letterSpacing", letterSpacing, textAttributes.letterSpacing),
|
||||
|
||||
// Paragraph Styles
|
||||
debugStringConvertibleItem("lineHeight", lineHeight),
|
||||
debugStringConvertibleItem("alignment", alignment),
|
||||
debugStringConvertibleItem("baseWritingDirection", baseWritingDirection),
|
||||
debugStringConvertibleItem("lineBreakStrategyIOS", lineBreakStrategy),
|
||||
debugStringConvertibleItem("lineBreakModeIOS", lineBreakMode),
|
||||
debugStringConvertibleItem(
|
||||
"lineHeight", lineHeight, textAttributes.lineHeight),
|
||||
debugStringConvertibleItem(
|
||||
"alignment", alignment, textAttributes.alignment),
|
||||
debugStringConvertibleItem(
|
||||
"baseWritingDirection",
|
||||
baseWritingDirection,
|
||||
textAttributes.baseWritingDirection),
|
||||
debugStringConvertibleItem(
|
||||
"lineBreakStrategyIOS",
|
||||
lineBreakStrategy,
|
||||
textAttributes.lineBreakStrategy),
|
||||
debugStringConvertibleItem(
|
||||
"lineBreakModeIOS", lineBreakMode, textAttributes.lineBreakMode),
|
||||
|
||||
// Decoration
|
||||
debugStringConvertibleItem("textDecorationColor", textDecorationColor),
|
||||
debugStringConvertibleItem(
|
||||
"textDecorationLineType", textDecorationLineType),
|
||||
debugStringConvertibleItem("textDecorationStyle", textDecorationStyle),
|
||||
"textDecorationColor",
|
||||
textDecorationColor,
|
||||
textAttributes.textDecorationColor),
|
||||
debugStringConvertibleItem(
|
||||
"textDecorationLineType",
|
||||
textDecorationLineType,
|
||||
textAttributes.textDecorationLineType),
|
||||
debugStringConvertibleItem(
|
||||
"textDecorationStyle",
|
||||
textDecorationStyle,
|
||||
textAttributes.textDecorationStyle),
|
||||
|
||||
// Shadow
|
||||
debugStringConvertibleItem("textShadowOffset", textShadowOffset),
|
||||
debugStringConvertibleItem("textShadowRadius", textShadowRadius),
|
||||
debugStringConvertibleItem("textShadowColor", textShadowColor),
|
||||
debugStringConvertibleItem(
|
||||
"textShadowOffset",
|
||||
textShadowOffset,
|
||||
textAttributes.textShadowOffset),
|
||||
debugStringConvertibleItem(
|
||||
"textShadowRadius",
|
||||
textShadowRadius,
|
||||
textAttributes.textShadowRadius),
|
||||
debugStringConvertibleItem(
|
||||
"textShadowColor", textShadowColor, textAttributes.textShadowColor),
|
||||
|
||||
// Special
|
||||
debugStringConvertibleItem("isHighlighted", isHighlighted),
|
||||
debugStringConvertibleItem("isPressable", isPressable),
|
||||
debugStringConvertibleItem("layoutDirection", layoutDirection),
|
||||
debugStringConvertibleItem("accessibilityRole", accessibilityRole),
|
||||
debugStringConvertibleItem("role", role),
|
||||
debugStringConvertibleItem(
|
||||
"isHighlighted", isHighlighted, textAttributes.isHighlighted),
|
||||
debugStringConvertibleItem(
|
||||
"isPressable", isPressable, textAttributes.isPressable),
|
||||
debugStringConvertibleItem(
|
||||
"layoutDirection", layoutDirection, textAttributes.layoutDirection),
|
||||
debugStringConvertibleItem(
|
||||
"accessibilityRole",
|
||||
accessibilityRole,
|
||||
textAttributes.accessibilityRole),
|
||||
debugStringConvertibleItem("role", role, textAttributes.role),
|
||||
|
||||
debugStringConvertibleItem("textAlignVertical", textAlignVertical),
|
||||
debugStringConvertibleItem(
|
||||
"textAlignVertical",
|
||||
textAlignVertical,
|
||||
textAttributes.textAlignVertical),
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
+19
-4
@@ -15,14 +15,29 @@
|
||||
|
||||
#include <react/renderer/debug/DebugStringConvertible.h>
|
||||
#include <react/renderer/debug/DebugStringConvertibleItem.h>
|
||||
#include <react/utils/FloatComparison.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
#if RN_DEBUG_STRING_CONVERTIBLE
|
||||
|
||||
inline SharedDebugStringConvertible debugStringConvertibleItem(
|
||||
const std::string& name,
|
||||
float value,
|
||||
float defaultValue = {}) {
|
||||
if (floatEquality(value, defaultValue)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_shared<DebugStringConvertibleItem>(
|
||||
name, facebook::react::toString(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline SharedDebugStringConvertible
|
||||
debugStringConvertibleItem(std::string name, T value, T defaultValue = {}) {
|
||||
inline SharedDebugStringConvertible debugStringConvertibleItem(
|
||||
const std::string& name,
|
||||
T value,
|
||||
T defaultValue = {}) {
|
||||
if (value == defaultValue) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -33,7 +48,7 @@ debugStringConvertibleItem(std::string name, T value, T defaultValue = {}) {
|
||||
|
||||
template <typename T>
|
||||
inline SharedDebugStringConvertible debugStringConvertibleItem(
|
||||
std::string name,
|
||||
const std::string& name,
|
||||
std::optional<T> value,
|
||||
T defaultValue = {}) {
|
||||
if (!value.has_value()) {
|
||||
@@ -54,7 +69,7 @@ inline SharedDebugStringConvertibleList operator+(
|
||||
}
|
||||
|
||||
inline SharedDebugStringConvertible debugStringConvertibleItem(
|
||||
std::string name,
|
||||
const std::string& name,
|
||||
DebugStringConvertible value,
|
||||
std::string defaultValue) {
|
||||
return debugStringConvertibleItem(
|
||||
|
||||
+117
-184
@@ -45,17 +45,22 @@ static SharedViewProps nonFlattenedDefaultProps(
|
||||
static ShadowNode::Shared makeNode(
|
||||
const ComponentDescriptor& componentDescriptor,
|
||||
int tag,
|
||||
const ShadowNode::ListOfShared& children,
|
||||
std::shared_ptr<ShadowNode::ListOfShared> children,
|
||||
bool flattened = false) {
|
||||
auto props = flattened ? generateDefaultProps(componentDescriptor)
|
||||
: nonFlattenedDefaultProps(componentDescriptor);
|
||||
|
||||
return componentDescriptor.createShadowNode(
|
||||
ShadowNodeFragment{
|
||||
props, std::make_shared<ShadowNode::ListOfShared>(children)},
|
||||
ShadowNodeFragment{std::move(props), std::move(children)},
|
||||
componentDescriptor.createFamily({tag, SurfaceId(1), nullptr}));
|
||||
}
|
||||
|
||||
static std::shared_ptr<ShadowNode::ListOfShared> listOfChildren(
|
||||
std::initializer_list<ShadowNode::Shared> list) {
|
||||
return std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{list});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reordering of views with the same parent:
|
||||
*
|
||||
@@ -117,25 +122,20 @@ TEST(MountingTest, testReorderingInstructionGeneration) {
|
||||
auto shadowNodeV1 = viewComponentDescriptor.createShadowNode(
|
||||
ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childB, childC, childD})},
|
||||
listOfChildren({childB, childC, childD})},
|
||||
family);
|
||||
auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childA, childB, childC, childD})});
|
||||
listOfChildren({childA, childB, childC, childD})});
|
||||
auto shadowNodeV3 = shadowNodeV2->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childB, childC, childD})});
|
||||
listOfChildren({childB, childC, childD})});
|
||||
auto shadowNodeV4 = shadowNodeV3->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childB, childD, childE})});
|
||||
listOfChildren({childB, childD, childE})});
|
||||
auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childB, childA, childE, childC})});
|
||||
listOfChildren({childB, childA, childE, childC})});
|
||||
auto shadowNodeV6 = shadowNodeV5->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(ShadowNode::ListOfShared{
|
||||
@@ -157,38 +157,31 @@ TEST(MountingTest, testReorderingInstructionGeneration) {
|
||||
auto rootNodeV1 = std::static_pointer_cast<const RootShadowNode>(
|
||||
emptyRootNode->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV1})}));
|
||||
listOfChildren({shadowNodeV1})}));
|
||||
auto rootNodeV2 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV1->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV2})}));
|
||||
listOfChildren({shadowNodeV2})}));
|
||||
auto rootNodeV3 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV2->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV3})}));
|
||||
listOfChildren({shadowNodeV3})}));
|
||||
auto rootNodeV4 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV3->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV4})}));
|
||||
listOfChildren({shadowNodeV4})}));
|
||||
auto rootNodeV5 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV4->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV5})}));
|
||||
listOfChildren({shadowNodeV5})}));
|
||||
auto rootNodeV6 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV5->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV6})}));
|
||||
listOfChildren({shadowNodeV6})}));
|
||||
auto rootNodeV7 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV6->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV7})}));
|
||||
listOfChildren({shadowNodeV7})}));
|
||||
|
||||
// Layout
|
||||
std::vector<const LayoutableShadowNode*> affectedLayoutableNodesV1{};
|
||||
@@ -289,7 +282,6 @@ TEST(MountingTest, testReorderingInstructionGeneration) {
|
||||
|
||||
// Calculating mutations.
|
||||
auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4);
|
||||
LOG(ERROR) << "Num mutations IN OLD TEST mutations3: " << mutations3.size();
|
||||
|
||||
// The order and exact mutation instructions here may change at any time.
|
||||
// This test just ensures that any changes are intentional.
|
||||
@@ -425,172 +417,107 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) {
|
||||
auto family =
|
||||
viewComponentDescriptor.createFamily({10, SurfaceId(1), nullptr});
|
||||
|
||||
auto reparentedViewA = makeNode(
|
||||
viewComponentDescriptor,
|
||||
1000,
|
||||
ShadowNode::ListOfShared{
|
||||
childC->clone({}), childA->clone({}), childB->clone({})});
|
||||
auto reparentedViewA_ = makeNode(
|
||||
viewComponentDescriptor, 1000, listOfChildren({childC, childA, childB}));
|
||||
auto reparentedViewA = reparentedViewA_->clone(
|
||||
ShadowNodeFragment{nonFlattenedDefaultProps(viewComponentDescriptor)});
|
||||
auto reparentedViewB = makeNode(
|
||||
viewComponentDescriptor,
|
||||
2000,
|
||||
ShadowNode::ListOfShared{
|
||||
childF->clone({}), childE->clone({}), childD->clone({})});
|
||||
viewComponentDescriptor, 2000, listOfChildren({childF, childE, childD}));
|
||||
|
||||
// Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened]
|
||||
auto shadowNodeV1 = viewComponentDescriptor.createShadowNode(
|
||||
ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childH->clone(ShadowNodeFragment{
|
||||
listOfChildren({childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childH->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childI->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
childI->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(
|
||||
viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(
|
||||
viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
reparentedViewA->clone(
|
||||
{})})})})})})})})})})},
|
||||
listOfChildren({reparentedViewA_})})})})})})})})})},
|
||||
family);
|
||||
|
||||
// Root -> G* -> H* -> I -> J -> A* [nodes with * are _not_ flattened]
|
||||
// Force an update with A with new props
|
||||
auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childG->clone(ShadowNodeFragment{
|
||||
listOfChildren({childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childH->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childH->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(ShadowNode::ListOfShared{
|
||||
childI->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(
|
||||
viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
reparentedViewA->clone(
|
||||
{})})})})})})})})})})});
|
||||
listOfChildren({childI->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({reparentedViewA})})})})})})})})})});
|
||||
|
||||
// Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened]
|
||||
auto shadowNodeV3 = shadowNodeV2->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childH->clone(ShadowNodeFragment{
|
||||
listOfChildren({childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childH->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childI->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(ShadowNode::ListOfShared{
|
||||
childI->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(
|
||||
viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
reparentedViewA->clone(
|
||||
{})})})})})})})})})})});
|
||||
listOfChildren({reparentedViewA})})})})})})})})})});
|
||||
|
||||
// The view is reparented 1 level down with a different sibling
|
||||
// Root -> G* -> H* -> I* -> J -> [B*, A*] [nodes with * are _not_ flattened]
|
||||
auto shadowNodeV4 = shadowNodeV3->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childG->clone(ShadowNodeFragment{
|
||||
listOfChildren({childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childH->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childH->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(ShadowNode::ListOfShared{
|
||||
childI->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(
|
||||
viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
reparentedViewB->clone({}),
|
||||
reparentedViewA->clone(
|
||||
{})})})})})})})})})})});
|
||||
listOfChildren({childI->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childJ->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren(
|
||||
{reparentedViewB, reparentedViewA})})})})})})})})})});
|
||||
|
||||
// The view is reparented 1 level further down with its order with the sibling
|
||||
// swapped
|
||||
// Root -> G* -> H* -> I* -> J* -> [A*, B*] [nodes with * are _not_ flattened]
|
||||
auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{
|
||||
generateDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childG->clone(ShadowNodeFragment{
|
||||
listOfChildren({childG->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childH->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{childH->clone(ShadowNodeFragment{
|
||||
listOfChildren({childI->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
listOfChildren({childJ->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(ShadowNode::ListOfShared{
|
||||
childI->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(viewComponentDescriptor),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
childJ->clone(ShadowNodeFragment{
|
||||
nonFlattenedDefaultProps(
|
||||
viewComponentDescriptor),
|
||||
std::make_shared<
|
||||
ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{
|
||||
reparentedViewA->clone({}),
|
||||
reparentedViewB->clone(
|
||||
{})})})})})})})})})})});
|
||||
listOfChildren(
|
||||
{reparentedViewA, reparentedViewB})})})})})})})})})});
|
||||
|
||||
// Injecting a tree into the root node.
|
||||
auto rootNodeV1 = std::static_pointer_cast<const RootShadowNode>(
|
||||
emptyRootNode->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV1})}));
|
||||
listOfChildren({shadowNodeV1})}));
|
||||
auto rootNodeV2 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV1->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV2})}));
|
||||
listOfChildren({shadowNodeV2})}));
|
||||
auto rootNodeV3 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV2->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV3})}));
|
||||
listOfChildren({shadowNodeV3})}));
|
||||
auto rootNodeV4 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV3->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV4})}));
|
||||
listOfChildren({shadowNodeV4})}));
|
||||
auto rootNodeV5 = std::static_pointer_cast<const RootShadowNode>(
|
||||
rootNodeV4->ShadowNode::clone(ShadowNodeFragment{
|
||||
ShadowNodeFragment::propsPlaceholder(),
|
||||
std::make_shared<ShadowNode::ListOfShared>(
|
||||
ShadowNode::ListOfShared{shadowNodeV5})}));
|
||||
listOfChildren({shadowNodeV5})}));
|
||||
|
||||
// Layout
|
||||
std::vector<const LayoutableShadowNode*> affectedLayoutableNodesV1{};
|
||||
@@ -626,33 +553,38 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) {
|
||||
// Calculating mutations.
|
||||
auto mutations1 = calculateShadowViewMutations(*rootNodeV1, *rootNodeV2);
|
||||
|
||||
EXPECT_EQ(mutations1.size(), 5);
|
||||
EXPECT_EQ(mutations1.size(), 6);
|
||||
EXPECT_EQ(mutations1[0].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations1[0].oldChildShadowView.tag, 106);
|
||||
EXPECT_EQ(mutations1[1].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations1[1].oldChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations1[2].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations1[2].newChildShadowView.tag, 107);
|
||||
EXPECT_EQ(mutations1[3].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations1[3].newChildShadowView.tag, 107);
|
||||
EXPECT_EQ(mutations1[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations1[1].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations1[1].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
// This is incorrect! ChildH does not exist yet at this point
|
||||
EXPECT_EQ(mutations1[1].parentShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations1[2].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations1[2].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations1[3].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations1[3].newChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations1[4].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations1[4].newChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations1[4].newChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations1[5].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations1[5].newChildShadowView.tag, reparentedViewA->getTag());
|
||||
|
||||
auto mutations2 = calculateShadowViewMutations(*rootNodeV2, *rootNodeV3);
|
||||
|
||||
EXPECT_EQ(mutations2.size(), 5);
|
||||
EXPECT_EQ(mutations2[0].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations2[0].oldChildShadowView.tag, 106);
|
||||
EXPECT_EQ(mutations2[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations2[0].parentShadowView.tag, emptyRootNode->getTag());
|
||||
EXPECT_EQ(mutations2[1].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations2[1].oldChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations2[1].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations2[2].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations2[2].oldChildShadowView.tag, 107);
|
||||
EXPECT_EQ(mutations2[2].oldChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(
|
||||
mutations2[3].type,
|
||||
ShadowViewMutation::Delete); // correct, 107 is removed from tree entirely
|
||||
EXPECT_EQ(mutations2[3].oldChildShadowView.tag, 107);
|
||||
ShadowViewMutation::Delete); // correct, H is removed from tree entirely
|
||||
EXPECT_EQ(mutations2[3].oldChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations2[4].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations2[4].newChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations2[4].newChildShadowView.tag, reparentedViewA->getTag());
|
||||
|
||||
auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4);
|
||||
|
||||
@@ -661,57 +593,58 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) {
|
||||
|
||||
EXPECT_EQ(mutations3.size(), 15);
|
||||
EXPECT_EQ(mutations3[0].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations3[0].oldChildShadowView.tag, 106);
|
||||
EXPECT_EQ(mutations3[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations3[0].parentShadowView.tag, emptyRootNode->getTag());
|
||||
EXPECT_EQ(mutations3[1].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations3[1].oldChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations3[1].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations3[2].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations3[2].newChildShadowView.tag, 107);
|
||||
EXPECT_EQ(mutations3[2].newChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations3[3].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations3[3].newChildShadowView.tag, 2000);
|
||||
EXPECT_EQ(mutations3[3].newChildShadowView.tag, reparentedViewB->getTag());
|
||||
EXPECT_EQ(mutations3[4].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations3[4].newChildShadowView.tag, 108);
|
||||
EXPECT_EQ(mutations3[4].newChildShadowView.tag, childI->getTag());
|
||||
EXPECT_EQ(mutations3[5].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations3[5].newChildShadowView.tag, 105);
|
||||
EXPECT_EQ(mutations3[5].newChildShadowView.tag, childF->getTag());
|
||||
EXPECT_EQ(mutations3[6].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations3[6].newChildShadowView.tag, 104);
|
||||
EXPECT_EQ(mutations3[6].newChildShadowView.tag, childE->getTag());
|
||||
EXPECT_EQ(mutations3[7].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations3[7].newChildShadowView.tag, 103);
|
||||
EXPECT_EQ(mutations3[7].newChildShadowView.tag, childD->getTag());
|
||||
EXPECT_EQ(mutations3[8].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[8].newChildShadowView.tag, 105);
|
||||
EXPECT_EQ(mutations3[8].newChildShadowView.tag, childF->getTag());
|
||||
EXPECT_EQ(mutations3[9].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[9].newChildShadowView.tag, 104);
|
||||
EXPECT_EQ(mutations3[9].newChildShadowView.tag, childE->getTag());
|
||||
EXPECT_EQ(mutations3[10].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[10].newChildShadowView.tag, 103);
|
||||
EXPECT_EQ(mutations3[10].newChildShadowView.tag, childD->getTag());
|
||||
EXPECT_EQ(mutations3[11].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[11].newChildShadowView.tag, 107);
|
||||
EXPECT_EQ(mutations3[11].newChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations3[12].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[12].newChildShadowView.tag, 108);
|
||||
EXPECT_EQ(mutations3[12].newChildShadowView.tag, childI->getTag());
|
||||
EXPECT_EQ(mutations3[13].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[13].newChildShadowView.tag, 2000);
|
||||
EXPECT_EQ(mutations3[13].newChildShadowView.tag, reparentedViewB->getTag());
|
||||
EXPECT_EQ(mutations3[14].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations3[14].newChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations3[14].newChildShadowView.tag, reparentedViewA->getTag());
|
||||
|
||||
auto mutations4 = calculateShadowViewMutations(*rootNodeV4, *rootNodeV5);
|
||||
|
||||
EXPECT_EQ(mutations4.size(), 9);
|
||||
EXPECT_EQ(mutations4[0].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations4[0].oldChildShadowView.tag, 106);
|
||||
EXPECT_EQ(mutations4[0].oldChildShadowView.tag, childG->getTag());
|
||||
EXPECT_EQ(mutations4[1].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations4[1].oldChildShadowView.tag, 107);
|
||||
EXPECT_EQ(mutations4[1].oldChildShadowView.tag, childH->getTag());
|
||||
EXPECT_EQ(mutations4[2].type, ShadowViewMutation::Update);
|
||||
EXPECT_EQ(mutations4[2].oldChildShadowView.tag, 108);
|
||||
EXPECT_EQ(mutations4[2].oldChildShadowView.tag, childI->getTag());
|
||||
EXPECT_EQ(mutations4[3].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations4[3].oldChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations4[3].oldChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations4[4].type, ShadowViewMutation::Remove);
|
||||
EXPECT_EQ(mutations4[4].oldChildShadowView.tag, 2000);
|
||||
EXPECT_EQ(mutations4[4].oldChildShadowView.tag, reparentedViewB->getTag());
|
||||
EXPECT_EQ(mutations4[5].type, ShadowViewMutation::Create);
|
||||
EXPECT_EQ(mutations4[5].newChildShadowView.tag, 109);
|
||||
EXPECT_EQ(mutations4[5].newChildShadowView.tag, childJ->getTag());
|
||||
EXPECT_EQ(mutations4[6].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations4[6].newChildShadowView.tag, 109);
|
||||
EXPECT_EQ(mutations4[6].newChildShadowView.tag, childJ->getTag());
|
||||
EXPECT_EQ(mutations4[7].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations4[7].newChildShadowView.tag, 1000);
|
||||
EXPECT_EQ(mutations4[7].newChildShadowView.tag, reparentedViewA->getTag());
|
||||
EXPECT_EQ(mutations4[8].type, ShadowViewMutation::Insert);
|
||||
EXPECT_EQ(mutations4[8].newChildShadowView.tag, 2000);
|
||||
EXPECT_EQ(mutations4[8].newChildShadowView.tag, reparentedViewB->getTag());
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+4
-1
@@ -121,8 +121,11 @@ void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags)
|
||||
[_bridgeModuleDecorator.callableJSModules
|
||||
setBridgelessJSModuleMethodInvoker:^(
|
||||
NSString *moduleName, NSString *methodName, NSArray *args, dispatch_block_t onComplete) {
|
||||
// TODO: Make RCTInstance call onComplete
|
||||
[weakSelf callFunctionOnJSModule:moduleName method:methodName args:args];
|
||||
if (onComplete) {
|
||||
[weakSelf
|
||||
callFunctionOnBufferedRuntimeExecutor:[onComplete](facebook::jsi::Runtime &_) { onComplete(); }];
|
||||
}
|
||||
}];
|
||||
}
|
||||
_launchOptions = launchOptions;
|
||||
|
||||
@@ -17,7 +17,7 @@ assertj = "3.21.0"
|
||||
binary-compatibility-validator = "0.13.2"
|
||||
download = "5.4.0"
|
||||
fbjni = "0.7.0"
|
||||
fresco = "3.4.0"
|
||||
fresco = "3.5.0"
|
||||
infer-annotation = "0.18.0"
|
||||
javax-annotation-api = "1.3.2"
|
||||
javax-inject = "1"
|
||||
|
||||
@@ -366,7 +366,7 @@ class ReactNativePodsUtils
|
||||
Pod::UI.puts "Setting -DRCT_DYNAMIC_FRAMEWORKS=1 to React-RCTFabric".green
|
||||
rct_dynamic_framework_flag = " -DRCT_DYNAMIC_FRAMEWORKS=1"
|
||||
target_installation_result.native_target.build_configurations.each do |config|
|
||||
prev_build_settings = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherithed)"
|
||||
prev_build_settings = config.build_settings['OTHER_CPLUSPLUSFLAGS'] != nil ? config.build_settings['OTHER_CPLUSPLUSFLAGS'] : "$(inherited)"
|
||||
config.build_settings['OTHER_CPLUSPLUSFLAGS'] = prev_build_settings + rct_dynamic_framework_flag
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {MixedElement} from 'react';
|
||||
|
||||
import ReactFabric from '../../../Libraries/Renderer/shims/ReactFabric';
|
||||
|
||||
let globalSurfaceIdCounter = 1;
|
||||
|
||||
const nativeRuntimeScheduler = global.nativeRuntimeScheduler;
|
||||
const schedulerPriorityImmediate =
|
||||
nativeRuntimeScheduler.unstable_ImmediatePriority;
|
||||
|
||||
class Root {
|
||||
#surfaceId: number;
|
||||
#hasRendered: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.#surfaceId = globalSurfaceIdCounter;
|
||||
globalSurfaceIdCounter += 10;
|
||||
}
|
||||
|
||||
render(element: MixedElement) {
|
||||
if (!this.#hasRendered) {
|
||||
global.$$JSTesterModuleName$$.startSurface(this.#surfaceId);
|
||||
this.#hasRendered = true;
|
||||
}
|
||||
|
||||
ReactFabric.render(element, this.#surfaceId, () => {}, true);
|
||||
}
|
||||
|
||||
getMountingLogs(): Array<string> {
|
||||
return global.$$JSTesterModuleName$$.getMountingManagerLogs(
|
||||
this.#surfaceId,
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// TODO: check for leaks.
|
||||
global.$$JSTesterModuleName$$.stopSurface(this.#surfaceId);
|
||||
global.$$JSTesterModuleName$$.flushMessageQueue();
|
||||
}
|
||||
|
||||
// TODO: add an API to check if all surfaces were deallocated when tests are finished.
|
||||
}
|
||||
|
||||
/*
|
||||
* Runs a task on on the event loop. To be used together with root.render.
|
||||
*
|
||||
* React must run inside of event loop to ensure scheduling environment is closer to production.
|
||||
*/
|
||||
export function runTask(task: () => void) {
|
||||
nativeRuntimeScheduler.unstable_scheduleCallback(
|
||||
schedulerPriorityImmediate,
|
||||
task,
|
||||
);
|
||||
global.$$JSTesterModuleName$$.flushMessageQueue();
|
||||
}
|
||||
|
||||
// TODO: Add option to define surface props and pass it to startSurface
|
||||
// Surfacep rops: concurrentRoot, surfaceWidth, surfaceHeight, layoutDirection, pointScaleFactor.
|
||||
export function createRoot(): Root {
|
||||
return new Root();
|
||||
}
|
||||
@@ -113,7 +113,7 @@ export function createCompositeKeyForProps(
|
||||
const key = keys[ii];
|
||||
const value = props[key];
|
||||
|
||||
if (allowlist == null || Object.hasOwn(allowlist, key)) {
|
||||
if (allowlist == null || hasOwn(allowlist, key)) {
|
||||
let compositeKeyComponent;
|
||||
if (key === 'style') {
|
||||
// $FlowFixMe[incompatible-call] - `style` is a valid argument.
|
||||
@@ -205,7 +205,7 @@ function createCompositeKeyForObject(
|
||||
for (let ii = 0, length = keys.length; ii < length; ii++) {
|
||||
const key = keys[ii];
|
||||
|
||||
if (allowlist == null || Object.hasOwn(allowlist, key)) {
|
||||
if (allowlist == null || hasOwn(allowlist, key)) {
|
||||
const value = object[key];
|
||||
|
||||
let compositeKeyComponent;
|
||||
@@ -250,7 +250,7 @@ export function areCompositeKeysEqual(
|
||||
}
|
||||
for (let ii = 0; ii < length; ii++) {
|
||||
const key = keys[ii];
|
||||
if (!Object.hasOwn(next, key)) {
|
||||
if (!hasOwn(next, key)) {
|
||||
return false;
|
||||
}
|
||||
const prevComponent = prev[key];
|
||||
@@ -336,7 +336,7 @@ function areCompositeKeyComponentsEqual(
|
||||
for (let ii = 0; ii < length; ii++) {
|
||||
const key = keys[ii];
|
||||
if (
|
||||
!Object.hasOwn(nullthrows(next), key) ||
|
||||
!hasOwn(nullthrows(next), key) ||
|
||||
!areCompositeKeyComponentsEqual(prev[key], next[key])
|
||||
) {
|
||||
return false;
|
||||
@@ -346,3 +346,11 @@ function areCompositeKeyComponentsEqual(
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* 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 type {PerformanceObserverCallbackOptions} from '../PerformanceObserver';
|
||||
|
||||
import * as ReactNativeTester from '../../../__tests__/ReactNativeTester';
|
||||
import setUpPerformanceObserver from '../../../setup/setUpPerformanceObserver';
|
||||
import {PerformanceLongTaskTiming} from '../LongTasks';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import '../../../../../Libraries/Core/InitializeCore.js';
|
||||
|
||||
setUpPerformanceObserver();
|
||||
|
||||
function sleep(ms: number) {
|
||||
const end = performance.now() + ms;
|
||||
while (performance.now() < end) {}
|
||||
}
|
||||
|
||||
function ensurePerformanceLongTaskTiming(
|
||||
value: mixed,
|
||||
): PerformanceLongTaskTiming {
|
||||
if (!(value instanceof PerformanceLongTaskTiming)) {
|
||||
throw new Error(
|
||||
`Expected instance of PerformanceLongTaskTiming but got ${String(value)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
describe('LongTask API', () => {
|
||||
it('does NOT report short tasks (under 50ms)', () => {
|
||||
const callback = jest.fn();
|
||||
|
||||
const observer = new PerformanceObserver(callback);
|
||||
observer.observe({entryTypes: ['longtask']});
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
// Short task.
|
||||
});
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
// Slightly longer task, but still not long.
|
||||
sleep(40);
|
||||
});
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reports long tasks (over 50ms)', () => {
|
||||
const callback = jest.fn();
|
||||
|
||||
const observer = new PerformanceObserver(callback);
|
||||
observer.observe({entryTypes: ['longtask']});
|
||||
|
||||
const beforeTaskStartTime = performance.now();
|
||||
let afterTaskStartTime;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
afterTaskStartTime = performance.now();
|
||||
// Long task.
|
||||
sleep(51);
|
||||
});
|
||||
|
||||
const afterTaskEndTime = performance.now();
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
const [entries, _observer, options] = callback.mock
|
||||
.lastCall as $FlowFixMe as [
|
||||
PerformanceObserverEntryList,
|
||||
PerformanceObserver,
|
||||
PerformanceObserverCallbackOptions,
|
||||
];
|
||||
|
||||
expect(_observer).toBe(observer);
|
||||
expect(options).toEqual({droppedEntriesCount: 0});
|
||||
|
||||
const allEntries = entries.getEntries();
|
||||
expect(allEntries.length).toBe(1);
|
||||
expect(allEntries[0]).toBeInstanceOf(PerformanceLongTaskTiming);
|
||||
|
||||
const entry = ensurePerformanceLongTaskTiming(allEntries[0]);
|
||||
|
||||
expect(entry.name).toBe('self');
|
||||
expect(entry.entryType).toBe('longtask');
|
||||
expect(entry.startTime).toBeGreaterThanOrEqual(beforeTaskStartTime);
|
||||
expect(entry.startTime).toBeLessThanOrEqual(nullthrows(afterTaskStartTime));
|
||||
expect(entry.duration).toBeGreaterThanOrEqual(51);
|
||||
expect(entry.duration).toBeLessThanOrEqual(
|
||||
afterTaskEndTime - beforeTaskStartTime,
|
||||
);
|
||||
expect(entry.attribution).toEqual([]);
|
||||
});
|
||||
|
||||
describe('tasks that yield', () => {
|
||||
it('should NOT be reported if they are longer than 50ms but had yielding opportunities in intervals shorter than 50ms', () => {
|
||||
const callback = jest.fn();
|
||||
|
||||
const observer = new PerformanceObserver(callback);
|
||||
observer.observe({entryTypes: ['longtask']});
|
||||
|
||||
const shouldYield = global.nativeRuntimeScheduler.unstable_shouldYield;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
sleep(40);
|
||||
shouldYield();
|
||||
sleep(40);
|
||||
shouldYield();
|
||||
sleep(40);
|
||||
});
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be reported if running for longer than 50ms between yielding opportunities', () => {
|
||||
const callback = jest.fn();
|
||||
|
||||
const observer = new PerformanceObserver(callback);
|
||||
observer.observe({entryTypes: ['longtask']});
|
||||
|
||||
const shouldYield = global.nativeRuntimeScheduler.unstable_shouldYield;
|
||||
|
||||
const beforeTaskStartTime = performance.now();
|
||||
let afterTaskStartTime;
|
||||
|
||||
ReactNativeTester.runTask(() => {
|
||||
afterTaskStartTime = performance.now();
|
||||
sleep(40);
|
||||
shouldYield();
|
||||
sleep(51); // long interval without yielding
|
||||
shouldYield();
|
||||
sleep(40);
|
||||
});
|
||||
|
||||
const afterTaskEndTime = performance.now();
|
||||
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
const entries = callback.mock.lastCall[0] as PerformanceObserverEntryList;
|
||||
const allEntries = entries.getEntries();
|
||||
expect(allEntries.length).toBe(1);
|
||||
|
||||
const entry = ensurePerformanceLongTaskTiming(allEntries[0]);
|
||||
expect(entry.name).toBe('self');
|
||||
expect(entry.entryType).toBe('longtask');
|
||||
expect(entry.startTime).toBeGreaterThanOrEqual(beforeTaskStartTime);
|
||||
expect(entry.startTime).toBeLessThanOrEqual(
|
||||
nullthrows(afterTaskStartTime),
|
||||
);
|
||||
expect(entry.duration).toBeGreaterThanOrEqual(131); // just the sum of the sleep times in the task
|
||||
expect(entry.duration).toBeLessThanOrEqual(
|
||||
afterTaskEndTime - beforeTaskStartTime,
|
||||
);
|
||||
expect(entry.attribution).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@
|
||||
import type {RNTesterModule} from '../../types/RNTesterTypes';
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, TextInput, View, Text} from 'react-native';
|
||||
import {StyleSheet, Text, TextInput, View} from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contents: {
|
||||
|
||||
@@ -27,8 +27,8 @@ const {
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
View,
|
||||
TextInput,
|
||||
View,
|
||||
} = require('react-native');
|
||||
|
||||
class WithLabel extends React.Component<$FlowFixMeProps> {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const chalk = require('chalk');
|
||||
const {execSync: exec} = require('child_process');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
@@ -30,6 +31,18 @@ type WorkflowRun = {
|
||||
url: string,
|
||||
created_at: string,
|
||||
conclusion: "success" | "failure" | "neutral" | "cancelled" | "skipped" | "timed_out" | "action_required" | null,
|
||||
head_commit: {
|
||||
author: {
|
||||
name: string,
|
||||
},
|
||||
message: string,
|
||||
...
|
||||
};
|
||||
triggering_actor: {
|
||||
login: string,
|
||||
...
|
||||
};
|
||||
run_started_at: string,
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +115,13 @@ async function _getArtifacts(run_id /*: number */) /*: Promise<Artifacts> */ {
|
||||
return body;
|
||||
}
|
||||
|
||||
function quote(text /*: string*/, prefix /*: string */ = ' > ') {
|
||||
return text
|
||||
.split('\n')
|
||||
.map(line => prefix + line)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// === Public Interface === //
|
||||
async function initialize(
|
||||
ciToken /*: string */,
|
||||
@@ -149,6 +169,21 @@ async function initialize(
|
||||
) ?? workflow;
|
||||
}
|
||||
|
||||
const commit = workflow.head_commit;
|
||||
const hours =
|
||||
(new Date().getTime() - new Date(workflow.run_started_at).getTime()) /
|
||||
(60 * 60 * 1000);
|
||||
const started_by = workflow.triggering_actor.login;
|
||||
|
||||
console.log(
|
||||
chalk.green(`The artifact being used is from a workflow started ${chalk.bold.magentaBright(hours.toFixed(0))} hours ago by ${chalk.bold.magentaBright(started_by)}:
|
||||
|
||||
Author: ${chalk.bold(commit.author.name)}
|
||||
Message:
|
||||
${chalk.magentaBright(quote(commit.message))}
|
||||
`),
|
||||
);
|
||||
|
||||
artifacts = await _getArtifacts(workflow.id);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user