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 fdfbc7b0bc72..bec55546a9e7 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 @@ -1315,3 +1315,156 @@ test('parent-child switching from unflattened-flattened to flattened-unflattened 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', ]); }); + +test('unflattening and creating a subtree that is partially culled', () => { + const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100}); + + // First render with a flattened view container that is visible. + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "RootView", nativeID: (root)}', + 'Create {type: "ScrollView", nativeID: (N/A)}', + 'Create {type: "View", nativeID: (N/A)}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', + 'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}', + ]); + + let maybeNode = null; + + // Now update opacity to unflattned the container and add a child that has a culled descendant. + Fantom.runTask(() => { + root.render( + { + maybeNode = node; + }} + contentOffset={{x: 0, y: 111}}> + + + + + + , + ); + }); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "ScrollView", nativeID: (N/A)}', + 'Update {type: "View", nativeID: (N/A)}', + 'Create {type: "View", nativeID: (N/A)}', + 'Create {type: "View", nativeID: "child"}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}', + ]); + + const element = ensureInstance(maybeNode, ReactNativeElement); + + // Scroll down to see the grandchild. + Fantom.scrollTo(element, { + x: 0, + y: 115, + }); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "ScrollView", nativeID: (N/A)}', + 'Create {type: "View", nativeID: "grandchild"}', + 'Insert {type: "View", parentNativeID: "child", index: 0, nativeID: "grandchild"}', + ]); +}); + +test('flattening and deleting a subtree that is partially culled', () => { + const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100}); + + // First render with a unflattened view container that is visible and a subtree that is partially culled. + Fantom.runTask(() => { + root.render( + + + + + + + , + ); + }); + + // All views are mounted, except for the grandchild. + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "RootView", nativeID: (root)}', + 'Create {type: "ScrollView", nativeID: (N/A)}', + 'Create {type: "View", nativeID: (N/A)}', + 'Create {type: "View", nativeID: (N/A)}', + 'Create {type: "View", nativeID: "child"}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', + 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', + 'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}', + ]); + + // Now change opacity to the default to flatten the container and delete container's subtree. + Fantom.runTask(() => { + root.render( + + + , + ); + }); + + // Note that the grandchild is not deleted because it was not previously mounted. + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "ScrollView", nativeID: (N/A)}', + 'Update {type: "View", nativeID: (N/A)}', + 'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}', + 'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}', + 'Delete {type: "View", nativeID: (N/A)}', + 'Delete {type: "View", nativeID: "child"}', + ]); +}); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp index a7c6307c2cca..e6b86171c79f 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp @@ -854,8 +854,6 @@ static void calculateShadowViewMutationsFlattener( if (!treeChildPair.flattened) { ViewNodePairScope innerScope{}; - // TODO(T217775046): Find a test case for this branch of view - // flattening + culling. calculateShadowViewMutations( innerScope, mutationContainer.destructiveDownwardMutations, @@ -872,8 +870,6 @@ static void calculateShadowViewMutationsFlattener( if (!treeChildPair.flattened) { ViewNodePairScope innerScope{}; - // TODO(T217775046): Find a test case for this branch of view - // flattening + culling. calculateShadowViewMutations( innerScope, mutationContainer.downwardMutations,