diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js index d7536bb4104..43be9bac989 100644 --- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js +++ b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js @@ -2327,6 +2327,126 @@ describe('reparenting', () => { 'Insert {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}', ]); }); + + test('parent-child flattening with deep hierarchy', () => { + function renderTree(root: Fantom.Root, isFinal: boolean) { + Fantom.runTask(() => { + root.render( + + + + + + + + + + + , + ); + }); + } + + const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100}); + + renderTree(root, false); + + expect(root.takeMountingManagerLogs()).not.toContain( + 'Create {type: "View", nativeID: "child"}', + ); + + renderTree(root, true); + + expect(root.takeMountingManagerLogs()).toContain( + 'Create {type: "View", nativeID: "child"}', + ); + + const finalRoot = Fantom.createRoot({ + viewportWidth: 100, + viewportHeight: 100, + }); + + renderTree(finalRoot, true); + + expect(root.getRenderedOutput().toJSON).toEqual( + finalRoot.getRenderedOutput().toJSON, + ); + }); + + test('parent-child unflattening with deep hierarchy', () => { + function renderTree(root: Fantom.Root, isFinal: boolean) { + Fantom.runTask(() => { + root.render( + + + + + + + + + + + , + ); + }); + } + + const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100}); + + renderTree(root, false); + + expect(root.takeMountingManagerLogs()).not.toContain( + 'Create {type: "View", nativeID: "child"}', + ); + + renderTree(root, true); + + expect(root.takeMountingManagerLogs()).toContain( + 'Create {type: "View", nativeID: "child"}', + ); + + const finalRoot = Fantom.createRoot({ + viewportWidth: 100, + viewportHeight: 100, + }); + + renderTree(finalRoot, true); + + expect(root.getRenderedOutput().toJSON).toEqual( + finalRoot.getRenderedOutput().toJSON, + ); + }); }); describe('opt out mechanism - Unstable_uncullableView & Unstable_uncullableTrace', () => { diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp index 6aa22194ad1..3c032efbfba 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp @@ -630,7 +630,8 @@ static void calculateShadowViewMutationsFlattener( // Update children if appropriate. if (!oldTreeNodePair.flattened && !newTreeNodePair.flattened) { - if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode) { + if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode || + adjustedOldCullingContext != adjustedNewCullingContext) { ViewNodePairScope innerScope{}; auto oldGrandChildPairs = sliceChildShadowNodeViewPairsFromViewNodePair( @@ -678,7 +679,7 @@ static void calculateShadowViewMutationsFlattener( : parentTag), subVisitedNewMap, subVisitedOldMap, - cullingContext, + cullingContextForUnvisitedOtherNodes, cullingContext.adjustCullingContextIfNeeded(treeChildPair)); } else { // Get flattened nodes from either new or old tree