Handle fabric root level fragment with compareDocumentPosition (#34533)

The root instance doesn't have a canonical property so we were not
returning a public instance that we can call compareDocumentPosition on
when a Fragment had no other host parent in Fabric. In this case we need
to get the ReactNativeElement from the ReactNativeDocument.

I've also added test coverage for this case in DOM for consistency,
though it was already working there because we use DOM elements as root.
This same test will be copied to RN using Fantom.
This commit is contained in:
Jack Pope
2025-09-23 10:56:43 -04:00
committed by GitHub
parent 86bfd74aeb
commit 0177b80460
2 changed files with 121 additions and 6 deletions
@@ -1613,6 +1613,116 @@ describe('FragmentRefs', () => {
);
});
// @gate enableFragmentRefs
it('compares a root-level Fragment', async () => {
const fragmentRef = React.createRef();
const emptyFragmentRef = React.createRef();
const childRef = React.createRef();
const siblingPrecedingRef = React.createRef();
const siblingFollowingRef = React.createRef();
const root = ReactDOMClient.createRoot(container);
function Test() {
return (
<Fragment>
<div ref={siblingPrecedingRef} />
<Fragment ref={fragmentRef}>
<div ref={childRef} />
</Fragment>
<Fragment ref={emptyFragmentRef} />
<div ref={siblingFollowingRef} />
</Fragment>
);
}
await act(() => root.render(<Test />));
const fragmentInstance = fragmentRef.current;
if (fragmentInstance == null) {
throw new Error('Expected fragment instance to be non-null');
}
const emptyFragmentInstance = emptyFragmentRef.current;
if (emptyFragmentInstance == null) {
throw new Error('Expected empty fragment instance to be non-null');
}
expectPosition(
fragmentInstance.compareDocumentPosition(childRef.current),
{
preceding: false,
following: false,
contains: false,
containedBy: true,
disconnected: false,
implementationSpecific: false,
},
);
expectPosition(
fragmentInstance.compareDocumentPosition(siblingPrecedingRef.current),
{
preceding: true,
following: false,
contains: false,
containedBy: false,
disconnected: false,
implementationSpecific: false,
},
);
expectPosition(
fragmentInstance.compareDocumentPosition(siblingFollowingRef.current),
{
preceding: false,
following: true,
contains: false,
containedBy: false,
disconnected: false,
implementationSpecific: false,
},
);
expectPosition(
emptyFragmentInstance.compareDocumentPosition(childRef.current),
{
preceding: true,
following: false,
contains: false,
containedBy: false,
disconnected: false,
implementationSpecific: true,
},
);
expectPosition(
emptyFragmentInstance.compareDocumentPosition(
siblingPrecedingRef.current,
),
{
preceding: true,
following: false,
contains: false,
containedBy: false,
disconnected: false,
implementationSpecific: true,
},
);
expectPosition(
emptyFragmentInstance.compareDocumentPosition(
siblingFollowingRef.current,
),
{
preceding: false,
following: true,
contains: false,
containedBy: false,
disconnected: false,
implementationSpecific: true,
},
);
});
describe('with portals', () => {
// @gate enableFragmentRefs
it('handles portaled elements', async () => {
@@ -25,7 +25,6 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {HostText} from 'react-reconciler/src/ReactWorkTags';
import {
getFragmentParentHostFiber,
getInstanceFromHostFiber,
traverseFragmentInstance,
} from 'react-reconciler/src/ReactFiberTreeReflection';
@@ -303,6 +302,13 @@ export function getPublicInstance(instance: Instance): null | PublicInstance {
return instance.canonical.publicInstance;
}
// Handle root containers
if (instance.containerInfo != null) {
if (instance.containerInfo.publicInstance != null) {
return instance.containerInfo.publicInstance;
}
}
// For compatibility with the legacy renderer, in case it's used with Fabric
// in the same app.
// $FlowExpectedError[prop-missing]
@@ -347,9 +353,8 @@ export function getPublicInstanceFromInternalInstanceHandle(
}
function getPublicInstanceFromHostFiber(fiber: Fiber): PublicInstance {
const instance = getInstanceFromHostFiber<Instance>(fiber);
const publicInstance = getPublicInstance(instance);
if (publicInstance == null) {
const publicInstance = getPublicInstance(fiber.stateNode);
if (publicInstance === null) {
throw new Error('Expected to find a host node. This is a bug in React.');
}
return publicInstance;
@@ -698,11 +703,11 @@ FragmentInstance.prototype.compareDocumentPosition = function (
if (parentHostFiber === null) {
return Node.DOCUMENT_POSITION_DISCONNECTED;
}
const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
const children: Array<Fiber> = [];
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
if (children.length === 0) {
return compareDocumentPositionForEmptyFragment(
const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
return compareDocumentPositionForEmptyFragment<PublicInstance>(
this._fragmentFiber,
parentHostInstance,
otherNode,