From 0548b601517f30b5d696aa05133e2969f44b9993 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 22 Jan 2025 05:55:34 -0800 Subject: [PATCH] introduce Fantom.dispatchNativeEvent (#48793) Summary: changelog: [internal] Adds new method `dispatchNativeEvent` to Fantom give option to dispatch fake native events. The API is expanded in subsequent diffs. The version introduced in this diff supports only dispatching event without payload and without any options. Reviewed By: rubennorte Differential Revision: D68331986 --- .../src/__tests__/Fantom-itest.js | 45 ++++++++++++++++++- packages/react-native-fantom/src/index.js | 17 ++++++- .../__snapshots__/public-api-test.js.snap | 2 + .../react/nativemodule/dom/NativeDOM.cpp | 1 + .../src/private/specs/modules/NativeFantom.js | 5 +++ 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/packages/react-native-fantom/src/__tests__/Fantom-itest.js b/packages/react-native-fantom/src/__tests__/Fantom-itest.js index 773f92665b9c..ef56c4b86d85 100644 --- a/packages/react-native-fantom/src/__tests__/Fantom-itest.js +++ b/packages/react-native-fantom/src/__tests__/Fantom-itest.js @@ -14,9 +14,15 @@ import 'react-native/Libraries/Core/InitializeCore'; import type {Root} from '..'; -import {createRoot, runTask} from '..'; +import { + createRoot, + dispatchNativeEvent, + runOnUIThread, + runTask, + runWorkLoop, +} from '..'; import * as React from 'react'; -import {Text, View} from 'react-native'; +import {Text, TextInput, View} from 'react-native'; import ensureInstance from 'react-native/src/private/utilities/ensureInstance'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; @@ -369,4 +375,39 @@ describe('Fantom', () => { }); }); }); + + describe('runOnUIThread + dispatchNativeEvent', () => { + it('sends focus event', () => { + const root = createRoot(); + let maybeNode; + + let focusEvent = jest.fn(); + + runTask(() => { + root.render( + { + maybeNode = node; + }} + />, + ); + }); + + const element = ensureInstance(maybeNode, ReactNativeElement); + + expect(focusEvent).toHaveBeenCalledTimes(0); + + runOnUIThread(() => { + dispatchNativeEvent(element, 'focus'); + }); + + // The tasks have not run. + expect(focusEvent).toHaveBeenCalledTimes(0); + + runWorkLoop(); + + expect(focusEvent).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/packages/react-native-fantom/src/index.js b/packages/react-native-fantom/src/index.js index f27ac7b67295..ec870571ad01 100644 --- a/packages/react-native-fantom/src/index.js +++ b/packages/react-native-fantom/src/index.js @@ -14,6 +14,8 @@ import type { } from './getFantomRenderedOutput'; import type {MixedElement} from 'react'; +import ReactNativeElement from '../../react-native/src/private/webapis/dom/nodes/ReadOnlyNode'; +import {getShadowNode} from '../../react-native/src/private/webapis/dom/nodes/ReadOnlyNode'; import * as Benchmark from './Benchmark'; import getFantomRenderedOutput from './getFantomRenderedOutput'; import ReactFabric from 'react-native/Libraries/Renderer/shims/ReactFabric'; @@ -100,7 +102,7 @@ export function scheduleTask(task: () => void | Promise) { let flushingQueue = false; /* - * Runs a task on on the event loop. To be used together with root.render. + * Runs a task 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. */ @@ -115,6 +117,14 @@ export function runTask(task: () => void | Promise) { runWorkLoop(); } +/* + * Simmulates running a task on the UI thread and forces side effect to drain the event queue, dispatching events to JavaScript. + */ +export function runOnUIThread(task: () => void) { + task(); + NativeFantom.flushEventQueue(); +} + /** * Runs the event loop until all tasks are executed. */ @@ -139,6 +149,11 @@ export function createRoot(rootConfig?: RootConfig): Root { return new Root(rootConfig); } +export function dispatchNativeEvent(node: ReactNativeElement, type: string) { + const shadowNode = getShadowNode(node); + NativeFantom.dispatchNativeEvent(shadowNode, type); +} + export const unstable_benchmark = Benchmark; type FantomConstants = $ReadOnly<{ diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 758ba63be6bc..2c234d4c94a4 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -10917,8 +10917,10 @@ interface Spec extends TurboModule { devicePixelRatio: number ) => void; stopSurface: (surfaceId: number) => void; + dispatchNativeEvent: (shadowNode: mixed, type: string) => void; getMountingManagerLogs: (surfaceId: number) => Array; flushMessageQueue: () => void; + flushEventQueue: () => void; getRenderedOutput: (surfaceId: number, config: RenderFormatOptions) => string; reportTestSuiteResultsJSON: (results: string) => void; } diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp index 490a692808f5..58a5a35cfe7a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef RN_DISABLE_OSS_PLUGIN_HEADER #include "Plugins.h" diff --git a/packages/react-native/src/private/specs/modules/NativeFantom.js b/packages/react-native/src/private/specs/modules/NativeFantom.js index 1fa0c0d2fb7f..64303435e3ca 100644 --- a/packages/react-native/src/private/specs/modules/NativeFantom.js +++ b/packages/react-native/src/private/specs/modules/NativeFantom.js @@ -26,8 +26,13 @@ interface Spec extends TurboModule { devicePixelRatio: number, ) => void; stopSurface: (surfaceId: number) => void; + dispatchNativeEvent: ( + shadowNode: mixed /* ShadowNode */, + type: string, + ) => void; getMountingManagerLogs: (surfaceId: number) => Array; flushMessageQueue: () => void; + flushEventQueue: () => void; getRenderedOutput: (surfaceId: number, config: RenderFormatOptions) => string; reportTestSuiteResultsJSON: (results: string) => void; }