Skip to content

Commit e58bbbc

Browse files
rubennortemeta-codesync[bot]
authored andcommitted
Allow passing refs where nodes are expected (facebook#54221)
Summary: Pull Request resolved: facebook#54221 Changelog: [internal] Small change in the Fantom API to accept refs and reduce boilerplate. See next diff for examples Reviewed By: javache Differential Revision: D85143177 fbshipit-source-id: fd65abcc1b107df8726fee172b284f5dc96e88a6
1 parent bd004e0 commit e58bbbc

File tree

2 files changed

+194
-34
lines changed

2 files changed

+194
-34
lines changed

private/react-native-fantom/src/__tests__/Fantom-itest.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,31 @@ describe('Fantom', () => {
584584
expect(focusEvent).toHaveBeenCalledTimes(1);
585585
});
586586

587+
it('sends event without payload (with ref)', () => {
588+
const root = Fantom.createRoot();
589+
590+
let focusEvent = jest.fn();
591+
592+
const ref = createRef<HostInstance>();
593+
594+
Fantom.runTask(() => {
595+
root.render(<TextInput onFocus={focusEvent} ref={ref} />);
596+
});
597+
598+
expect(focusEvent).toHaveBeenCalledTimes(0);
599+
600+
Fantom.runOnUIThread(() => {
601+
Fantom.enqueueNativeEvent(ref, 'focus');
602+
});
603+
604+
// The tasks have not run.
605+
expect(focusEvent).toHaveBeenCalledTimes(0);
606+
607+
Fantom.runWorkLoop();
608+
609+
expect(focusEvent).toHaveBeenCalledTimes(1);
610+
});
611+
587612
it('sends event with payload', () => {
588613
const root = Fantom.createRoot();
589614
const ref = createRef<HostInstance>();
@@ -692,6 +717,23 @@ describe('Fantom', () => {
692717

693718
expect(focusEvent).toHaveBeenCalledTimes(1);
694719
});
720+
721+
it('flushes the event and runs the work loop (with ref)', () => {
722+
const root = Fantom.createRoot();
723+
const ref = createRef<HostInstance>();
724+
725+
let focusEvent = jest.fn();
726+
727+
Fantom.runTask(() => {
728+
root.render(<TextInput onFocus={focusEvent} ref={ref} />);
729+
});
730+
731+
expect(focusEvent).toHaveBeenCalledTimes(0);
732+
733+
Fantom.dispatchNativeEvent(ref, 'focus');
734+
735+
expect(focusEvent).toHaveBeenCalledTimes(1);
736+
});
695737
});
696738

697739
describe('enqueueScrollEvent', () => {
@@ -783,6 +825,36 @@ describe('Fantom', () => {
783825

784826
root.destroy();
785827
});
828+
829+
it('delivers onScroll event (with ref)', () => {
830+
const root = Fantom.createRoot();
831+
const viewRef = createRef<HostInstance>();
832+
const scrollViewRef = createRef<HostInstance>();
833+
const onScroll = jest.fn();
834+
835+
Fantom.runTask(() => {
836+
root.render(
837+
<ScrollView
838+
onScroll={event => {
839+
onScroll(event.nativeEvent);
840+
}}
841+
ref={scrollViewRef}>
842+
<View style={{width: 1, height: 2, top: 3}} ref={viewRef} />
843+
</ScrollView>,
844+
);
845+
});
846+
847+
Fantom.runOnUIThread(() => {
848+
Fantom.enqueueScrollEvent(scrollViewRef, {
849+
x: 0,
850+
y: 1,
851+
});
852+
});
853+
854+
Fantom.runWorkLoop();
855+
856+
expect(onScroll).toHaveBeenCalledTimes(1);
857+
});
786858
});
787859

788860
describe('scrollTo', () => {
@@ -872,6 +944,39 @@ describe('Fantom', () => {
872944

873945
root.destroy();
874946
});
947+
948+
it('delivers onScroll event and affects position of elements on screen (with ref)', () => {
949+
const root = Fantom.createRoot();
950+
const scrollViewRef = createRef<HostInstance>();
951+
const viewRef = createRef<HostInstance>();
952+
const onScroll = jest.fn();
953+
954+
Fantom.runTask(() => {
955+
root.render(
956+
<ScrollView
957+
onScroll={event => {
958+
onScroll(event.nativeEvent);
959+
}}
960+
ref={scrollViewRef}>
961+
<View style={{width: 1, height: 2, top: 3}} ref={viewRef} />
962+
</ScrollView>,
963+
);
964+
});
965+
966+
const scrollViewElement = ensureInstance(
967+
scrollViewRef.current,
968+
ReactNativeElement,
969+
);
970+
971+
expect(scrollViewElement.scrollTop).toBe(0);
972+
973+
Fantom.scrollTo(scrollViewRef, {
974+
x: 0,
975+
y: 1,
976+
});
977+
978+
expect(scrollViewElement.scrollTop).toBe(1);
979+
});
875980
});
876981

877982
describe('flushAllNativeEvents', () => {
@@ -956,5 +1061,37 @@ describe('Fantom', () => {
9561061
expect(boundingClientRect.height).toBe(25);
9571062
expect(boundingClientRect.width).toBe(50);
9581063
});
1064+
1065+
it('change size of <Modal /> (with ref)', () => {
1066+
const root = Fantom.createRoot();
1067+
const modalNodeRef = createRef<HostInstance>();
1068+
const viewNodeRef = createRef<HostInstance>();
1069+
1070+
Fantom.runTask(() => {
1071+
root.render(
1072+
<Modal ref={modalNodeRef}>
1073+
<View style={{width: '50%', height: '25%'}} ref={viewNodeRef} />
1074+
</Modal>,
1075+
);
1076+
});
1077+
1078+
Fantom.runOnUIThread(() => {
1079+
Fantom.enqueueModalSizeUpdate(modalNodeRef, {
1080+
width: 100,
1081+
height: 100,
1082+
});
1083+
});
1084+
1085+
Fantom.runWorkLoop();
1086+
1087+
const viewElement = ensureInstance(
1088+
viewNodeRef.current,
1089+
ReactNativeElement,
1090+
);
1091+
1092+
const boundingClientRect = viewElement.getBoundingClientRect();
1093+
expect(boundingClientRect.height).toBe(25);
1094+
expect(boundingClientRect.width).toBe(50);
1095+
});
9591096
});
9601097
});

private/react-native-fantom/src/index.js

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import type {
1515
import type {MixedElement} from 'react';
1616
import type {RootTag} from 'react-native';
1717
import type ReactNativeDocument from 'react-native/src/private/webapis/dom/nodes/ReactNativeDocument';
18-
import type ReadOnlyNode from 'react-native/src/private/webapis/dom/nodes/ReadOnlyNode';
1918

2019
import * as Benchmark from './Benchmark';
2120
import {getConstants} from './Constants';
@@ -25,11 +24,14 @@ import NativeFantom, {
2524
NativeEventCategory,
2625
} from 'react-native/src/private/testing/fantom/specs/NativeFantom';
2726
import {getNativeNodeReference} from 'react-native/src/private/webapis/dom/nodes/internals/NodeInternals';
27+
import ReadOnlyNode from 'react-native/src/private/webapis/dom/nodes/ReadOnlyNode';
2828

2929
const nativeRuntimeScheduler = global.nativeRuntimeScheduler;
3030
const {unstable_scheduleCallback, unstable_ImmediatePriority} =
3131
nativeRuntimeScheduler;
3232

33+
type NodeOrRef = ReadOnlyNode | $ReadOnly<{current: ?ReadOnlyNode}>;
34+
3335
export type RootConfig = {
3436
viewportWidth?: number,
3537
viewportHeight?: number,
@@ -218,17 +220,19 @@ export function unstable_produceFramesForDuration(milliseconds: number) {
218220
* Note: This API is marked as unstable and may change in future versions.
219221
*/
220222
export function unstable_getDirectManipulationProps(
221-
node: ReadOnlyNode,
223+
nodeOrRef: NodeOrRef,
222224
): $ReadOnly<{
223225
[string]: mixed,
224226
}> {
227+
const node = getNode(nodeOrRef);
225228
const shadowNode = getNativeNodeReference(node);
226229
return NativeFantom.getDirectManipulationProps(shadowNode);
227230
}
228231

229-
export function unstable_getFabricUpdateProps(node: ReadOnlyNode): $ReadOnly<{
232+
export function unstable_getFabricUpdateProps(nodeOrRef: NodeOrRef): $ReadOnly<{
230233
[string]: mixed,
231234
}> {
235+
const node = getNode(nodeOrRef);
232236
const shadowNode = getNativeNodeReference(node);
233237
return NativeFantom.getFabricUpdateProps(shadowNode);
234238
}
@@ -394,11 +398,12 @@ export function createRoot(rootConfig?: RootConfig): Root {
394398
* ```
395399
*/
396400
export function enqueueNativeEvent(
397-
node: ReadOnlyNode,
401+
nodeOrRef: NodeOrRef,
398402
type: string,
399403
payload?: $ReadOnly<{[key: string]: mixed}>,
400404
options?: $ReadOnly<{category?: NativeEventCategory, isUnique?: boolean}>,
401405
) {
406+
const node = getNode(nodeOrRef);
402407
const shadowNode = getNativeNodeReference(node);
403408
NativeFantom.enqueueNativeEvent(
404409
shadowNode,
@@ -425,11 +430,13 @@ export function enqueueNativeEvent(
425430
* ```
426431
*/
427432
export function dispatchNativeEvent(
428-
node: ReadOnlyNode,
433+
nodeOrRef: NodeOrRef,
429434
type: string,
430435
payload?: $ReadOnly<{[key: string]: mixed}>,
431436
options?: $ReadOnly<{category?: NativeEventCategory, isUnique?: boolean}>,
432437
) {
438+
const node = getNode(nodeOrRef);
439+
433440
runOnUIThread(() => {
434441
enqueueNativeEvent(node, type, payload, options);
435442
});
@@ -487,9 +494,10 @@ export type ScrollEventOptions = {
487494
* ```
488495
*/
489496
export function enqueueScrollEvent(
490-
node: ReadOnlyNode,
497+
nodeOrRef: NodeOrRef,
491498
options: ScrollEventOptions,
492499
) {
500+
const node = getNode(nodeOrRef);
493501
const shadowNode = getNativeNodeReference(node);
494502
NativeFantom.enqueueScrollEvent(shadowNode, options);
495503
}
@@ -525,7 +533,9 @@ export function enqueueScrollEvent(
525533
* // Assert that changes from Fantom.scrollTo are in effect.
526534
* ```
527535
*/
528-
export function scrollTo(node: ReadOnlyNode, options: ScrollEventOptions) {
536+
export function scrollTo(nodeOrRef: NodeOrRef, options: ScrollEventOptions) {
537+
const node = getNode(nodeOrRef);
538+
529539
runOnUIThread(() => {
530540
enqueueScrollEvent(node, options);
531541
});
@@ -558,9 +568,10 @@ export function scrollTo(node: ReadOnlyNode, options: ScrollEventOptions) {
558568
* ```
559569
*/
560570
export function enqueueModalSizeUpdate(
561-
node: ReadOnlyNode,
571+
nodeOrRef: NodeOrRef,
562572
size: $ReadOnly<{width: number, height: number}>,
563573
) {
574+
const node = getNode(nodeOrRef);
564575
const shadowNode = getNativeNodeReference(node);
565576
NativeFantom.enqueueModalSizeUpdate(shadowNode, size.width, size.height);
566577
}
@@ -572,28 +583,6 @@ export type {
572583
TestOptions as BenchmarkTestOptions,
573584
} from './Benchmark';
574585

575-
/**
576-
* Quick and dirty polyfills required by tinybench.
577-
*/
578-
579-
if (typeof global.Event === 'undefined') {
580-
global.Event =
581-
require('react-native/src/private/webapis/dom/events/Event').default;
582-
} else {
583-
console.warn(
584-
'The global Event class is already defined. If this API is already defined by React Native, you might want to remove this logic.',
585-
);
586-
}
587-
588-
if (typeof global.EventTarget === 'undefined') {
589-
global.EventTarget =
590-
require('react-native/src/private/webapis/dom/events/EventTarget').default;
591-
} else {
592-
console.warn(
593-
'The global Event class is already defined. If this API is already defined by React Native, you might want to remove this logic.',
594-
);
595-
}
596-
597586
/**
598587
* Returns a function that returns the current reference count for the supplied
599588
* element's shadow node. If the reference count is zero, that means the shadow
@@ -602,9 +591,10 @@ if (typeof global.EventTarget === 'undefined') {
602591
* @param node The node for which to create a reference counting function.
603592
*/
604593
export function createShadowNodeReferenceCounter(
605-
node: ReadOnlyNode,
594+
nodeOrRef: NodeOrRef,
606595
): () => number {
607-
let shadowNode = getNativeNodeReference(node);
596+
const node = getNode(nodeOrRef);
597+
const shadowNode = getNativeNodeReference(node);
608598
return NativeFantom.createShadowNodeReferenceCounter(shadowNode);
609599
}
610600

@@ -615,9 +605,10 @@ export function createShadowNodeReferenceCounter(
615605
* @param node The node for which to create a revision getter.
616606
*/
617607
export function createShadowNodeRevisionGetter(
618-
node: ReadOnlyNode,
608+
nodeOrRef: NodeOrRef,
619609
): () => ?number {
620-
let shadowNode = getNativeNodeReference(node);
610+
const node = getNode(nodeOrRef);
611+
const shadowNode = getNativeNodeReference(node);
621612
return NativeFantom.createShadowNodeRevisionGetter(shadowNode);
622613
}
623614

@@ -681,4 +672,36 @@ function runLogBoxCheck() {
681672
}
682673
}
683674

675+
function getNode(nodeOrRef: NodeOrRef): ReadOnlyNode {
676+
if (nodeOrRef instanceof ReadOnlyNode) {
677+
return nodeOrRef;
678+
} else if (nodeOrRef.current != null) {
679+
return nodeOrRef.current;
680+
} else {
681+
throw new TypeError('Could not get node from ref');
682+
}
683+
}
684+
685+
/**
686+
* Quick and dirty polyfills required by tinybench.
687+
*/
688+
689+
if (typeof global.Event === 'undefined') {
690+
global.Event =
691+
require('react-native/src/private/webapis/dom/events/Event').default;
692+
} else {
693+
console.warn(
694+
'The global Event class is already defined. If this API is already defined by React Native, you might want to remove this logic.',
695+
);
696+
}
697+
698+
if (typeof global.EventTarget === 'undefined') {
699+
global.EventTarget =
700+
require('react-native/src/private/webapis/dom/events/EventTarget').default;
701+
} else {
702+
console.warn(
703+
'The global Event class is already defined. If this API is already defined by React Native, you might want to remove this logic.',
704+
);
705+
}
706+
684707
global.__FANTOM_PACKAGE_LOADED__ = true;

0 commit comments

Comments
 (0)