diff --git a/packages/react-native/Libraries/Lists/__tests__/FlatList-itest.js b/packages/react-native/Libraries/Lists/__tests__/FlatList-itest.js index 15afb533d503..c290b5d3da74 100644 --- a/packages/react-native/Libraries/Lists/__tests__/FlatList-itest.js +++ b/packages/react-native/Libraries/Lists/__tests__/FlatList-itest.js @@ -680,6 +680,12 @@ describe('', () => { timers.advanceTimersByTime(200); + // The first timer represents an intermediate viewport snapshot and + // must not publish after the final visible set supersedes it. + expect(onViewableItemsChanged).not.toHaveBeenCalled(); + + timers.advanceTimersByTime(200); + expect(onViewableItemsChanged).toHaveBeenCalled(); } finally { timers.uninstall(); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/TracingTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/TracingTest.cpp index c371e76349e6..af45b463e1a4 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/TracingTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/TracingTest.cpp @@ -37,7 +37,15 @@ class TracingTest : public TracingTestBase< TEST_F(TracingTest, EnablesSamplingProfilerOnlyCategoryIsSpecified) { InSequence s; + const auto runSamplingWorkload = [this]() { + eval(R"( + const start = Date.now(); + while (Date.now() - start < 10) {} + )"); + }; + startTracing({}); + runSamplingWorkload(); auto allTraceEvents = endTracingAndCollectEvents(); EXPECT_THAT( @@ -47,6 +55,7 @@ TEST_F(TracingTest, EnablesSamplingProfilerOnlyCategoryIsSpecified) { AtJsonPtr("/cat", "disabled-by-default-v8.cpu_profiler"))))); startTracing({tracing::Category::JavaScriptSampling}); + runSamplingWorkload(); allTraceEvents = endTracingAndCollectEvents(); EXPECT_THAT( diff --git a/packages/virtualized-lists/Lists/ViewabilityHelper.js b/packages/virtualized-lists/Lists/ViewabilityHelper.js index 08c7ef8c80c4..8157efa4dfa5 100644 --- a/packages/virtualized-lists/Lists/ViewabilityHelper.js +++ b/packages/virtualized-lists/Lists/ViewabilityHelper.js @@ -231,6 +231,10 @@ class ViewabilityHelper { * comment suppresses an error found when Flow v0.63 was deployed. To * see the error delete this comment and run Flow. */ this._timers.delete(handle); + // `onUpdate` replaces the array whenever the visible set changes. + if (this._viewableIndices !== viewableIndices) { + return; + } this._onUpdateSync( props, viewableIndices, diff --git a/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js b/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js index 3fc9f79cc47e..257757048d6e 100644 --- a/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js +++ b/packages/virtualized-lists/Lists/__tests__/ViewabilityHelper-test.js @@ -342,6 +342,58 @@ describe('onUpdate', function () { }); }); + it('minimumViewTime ignores stale viewability updates', function () { + const helper = new ViewabilityHelper({ + minimumViewTime: 350, + viewAreaCoveragePercentThreshold: 0, + }); + rowFrames = { + a: {y: 0, height: 200}, + b: {y: 200, height: 200}, + }; + data = [{key: 'a'}, {key: 'b'}]; + const onViewableItemsChanged = jest.fn(); + helper.onUpdate( + props, + 0, + 200, + // $FlowFixMe[incompatible-type] - Invalid `ListMetricsAggregator`. + {getCellMetrics}, + createViewToken, + onViewableItemsChanged, + ); + helper.onUpdate( + props, + 0, + 400, + // $FlowFixMe[incompatible-type] - Invalid `ListMetricsAggregator`. + {getCellMetrics}, + createViewToken, + onViewableItemsChanged, + ); + + jest.runAllTimers(); + + expect(onViewableItemsChanged.mock.calls).toEqual([ + [ + { + changed: [ + {isViewable: true, key: 'a'}, + {isViewable: true, key: 'b'}, + ], + viewabilityConfig: { + minimumViewTime: 350, + viewAreaCoveragePercentThreshold: 0, + }, + viewableItems: [ + {isViewable: true, key: 'a'}, + {isViewable: true, key: 'b'}, + ], + }, + ], + ]); + }); + it('minimumViewTime skips briefly visible items', function () { const helper = new ViewabilityHelper({ minimumViewTime: 350,