Merge pull request #37258 from bang9/cherrypick-0.72-fix-virtualized-list-for-mvcp

This commit is contained in:
Lorenzo Sciandra
2023-05-22 11:56:46 +01:00
committed by GitHub
10 changed files with 888 additions and 120 deletions
@@ -111,6 +111,7 @@ exports[`FlatList renders all the bells and whistles 1`] = `
<RCTRefreshControl />
<View>
<View
collapsable={false}
onLayout={[Function]}
>
<header />
@@ -243,6 +243,7 @@ exports[`SectionList renders all the bells and whistles 1`] = `
<RCTRefreshControl />
<View>
<View
collapsable={false}
onLayout={[Function]}
>
<header
@@ -13,6 +13,7 @@
const React = require('react');
const {
ActivityIndicator,
Animated,
Image,
Platform,
@@ -33,16 +34,28 @@ export type Item = {
...
};
function genItemData(count: number, start: number = 0): Array<Item> {
function genItemData(i: number): Item {
const itemHash = Math.abs(hashCode('Item ' + i));
return {
title: 'Item ' + i,
text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20),
key: String(i),
pressed: false,
};
}
function genNewerItems(count: number, start: number = 0): Array<Item> {
const dataBlob = [];
for (let ii = start; ii < count + start; ii++) {
const itemHash = Math.abs(hashCode('Item ' + ii));
dataBlob.push({
title: 'Item ' + ii,
text: LOREM_IPSUM.substr(0, (itemHash % 301) + 20),
key: String(ii),
pressed: false,
});
for (let i = start; i < count + start; i++) {
dataBlob.push(genItemData(i));
}
return dataBlob;
}
function genOlderItems(count: number, start: number = 0): Array<Item> {
const dataBlob = [];
for (let i = count; i > 0; i--) {
dataBlob.push(genItemData(start - i));
}
return dataBlob;
}
@@ -147,6 +160,12 @@ class SeparatorComponent extends React.PureComponent<{...}> {
}
}
const LoadingComponent: React.ComponentType<{}> = React.memo(() => (
<View style={styles.loadingContainer}>
<ActivityIndicator />
</View>
));
class ItemSeparatorComponent extends React.PureComponent<$FlowFixMeProps> {
render(): React.Node {
const style = this.props.highlighted
@@ -352,6 +371,13 @@ const styles = StyleSheet.create({
text: {
flex: 1,
},
loadingContainer: {
alignItems: 'center',
justifyContent: 'center',
height: 100,
borderTopWidth: 1,
borderTopColor: 'rgb(200, 199, 204)',
},
});
module.exports = {
@@ -362,8 +388,10 @@ module.exports = {
ItemSeparatorComponent,
PlainInput,
SeparatorComponent,
LoadingComponent,
Spindicator,
genItemData,
genNewerItems,
genOlderItems,
getItemLayout,
pressItem,
renderSmallSwitchOption,
@@ -35,8 +35,10 @@ import {
ItemSeparatorComponent,
PlainInput,
SeparatorComponent,
LoadingComponent,
Spindicator,
genItemData,
genNewerItems,
genOlderItems,
getItemLayout,
pressItem,
renderSmallSwitchOption,
@@ -44,6 +46,11 @@ import {
import type {Item} from '../../components/ListExampleShared';
const PAGE_SIZE = 100;
const NUM_PAGES = 10;
const INITIAL_PAGE_OFFSET = Math.floor(NUM_PAGES / 2);
const LOAD_TIME = 2000;
const VIEWABILITY_CONFIG = {
minimumViewTime: 3000,
viewAreaCoveragePercentThreshold: 100,
@@ -53,6 +60,8 @@ const VIEWABILITY_CONFIG = {
type Props = $ReadOnly<{||}>;
type State = {|
data: Array<Item>,
first: number,
last: number,
debug: boolean,
horizontal: boolean,
inverted: boolean,
@@ -66,13 +75,18 @@ type State = {|
onPressDisabled: boolean,
textSelectable: boolean,
isRTL: boolean,
maintainVisibleContentPosition: boolean,
previousLoading: boolean,
nextLoading: boolean,
|};
const IS_RTL = I18nManager.isRTL;
class FlatListExample extends React.PureComponent<Props, State> {
state: State = {
data: genItemData(100),
data: genNewerItems(PAGE_SIZE, PAGE_SIZE * INITIAL_PAGE_OFFSET),
first: PAGE_SIZE * INITIAL_PAGE_OFFSET,
last: PAGE_SIZE + PAGE_SIZE * INITIAL_PAGE_OFFSET,
debug: false,
horizontal: false,
inverted: false,
@@ -86,6 +100,9 @@ class FlatListExample extends React.PureComponent<Props, State> {
onPressDisabled: false,
textSelectable: true,
isRTL: IS_RTL,
maintainVisibleContentPosition: true,
previousLoading: false,
nextLoading: false,
};
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
@@ -209,6 +226,11 @@ class FlatListExample extends React.PureComponent<Props, State> {
this.state.isRTL,
this._setIsRTL,
)}
{renderSmallSwitchOption(
'Maintain content position',
this.state.maintainVisibleContentPosition,
this._setBooleanValue('maintainVisibleContentPosition'),
)}
{Platform.OS === 'android' && (
<View>
<TextInput
@@ -230,8 +252,12 @@ class FlatListExample extends React.PureComponent<Props, State> {
<Animated.FlatList
fadingEdgeLength={this.state.fadingEdgeLength}
ItemSeparatorComponent={ItemSeparatorComponent}
ListHeaderComponent={<HeaderComponent />}
ListFooterComponent={FooterComponent}
ListHeaderComponent={
this.state.previousLoading ? LoadingComponent : HeaderComponent
}
ListFooterComponent={
this.state.nextLoading ? LoadingComponent : FooterComponent
}
ListEmptyComponent={ListEmptyComponent}
// $FlowFixMe[missing-empty-array-annot]
data={this.state.empty ? [] : filteredData}
@@ -250,6 +276,8 @@ class FlatListExample extends React.PureComponent<Props, State> {
keyboardShouldPersistTaps="always"
keyboardDismissMode="on-drag"
numColumns={1}
onStartReached={this._onStartReached}
initialScrollIndex={Math.floor(PAGE_SIZE / 2)}
onEndReached={this._onEndReached}
onRefresh={this._onRefresh}
onScroll={
@@ -260,6 +288,11 @@ class FlatListExample extends React.PureComponent<Props, State> {
refreshing={false}
contentContainerStyle={styles.list}
viewabilityConfig={VIEWABILITY_CONFIG}
maintainVisibleContentPosition={
this.state.maintainVisibleContentPosition
? {minIndexForVisible: 0}
: undefined
}
{...flatListItemRendererProps}
/>
</View>
@@ -280,13 +313,33 @@ class FlatListExample extends React.PureComponent<Props, State> {
_getItemLayout = (data: any, index: number) => {
return getItemLayout(data, index, this.state.horizontal);
};
_onEndReached = () => {
if (this.state.data.length >= 1000) {
_onStartReached = () => {
if (this.state.first <= 0 || this.state.previousLoading) {
return;
}
this.setState(state => ({
data: state.data.concat(genItemData(100, state.data.length)),
}));
this.setState({previousLoading: true});
setTimeout(() => {
this.setState(state => ({
previousLoading: false,
data: genOlderItems(PAGE_SIZE, state.first).concat(state.data),
first: state.first - PAGE_SIZE,
}));
}, LOAD_TIME);
};
_onEndReached = () => {
if (this.state.last >= PAGE_SIZE * NUM_PAGES || this.state.nextLoading) {
return;
}
this.setState({nextLoading: true});
setTimeout(() => {
this.setState(state => ({
nextLoading: false,
data: state.data.concat(genNewerItems(PAGE_SIZE, state.last)),
last: state.last + PAGE_SIZE,
}));
}, LOAD_TIME);
};
// $FlowFixMe[missing-local-annot]
_onPressCallback = () => {
@@ -343,7 +396,7 @@ class FlatListExample extends React.PureComponent<Props, State> {
_pressItem = (key: string) => {
this._listRef?.recordInteraction();
const index = Number(key);
const index = this.state.data.findIndex(item => item.key === key);
const itemState = pressItem(this.state.data[index]);
this.setState(state => ({
...state,
@@ -23,7 +23,7 @@ const {
ItemComponent,
PlainInput,
SeparatorComponent,
genItemData,
genNewerItems,
getItemLayout,
pressItem,
renderSmallSwitchOption,
@@ -46,7 +46,7 @@ class MultiColumnExample extends React.PureComponent<
numColumns: number,
virtualized: boolean,
|} = {
data: genItemData(1000),
data: genNewerItems(1000),
filterText: '',
fixedHeight: true,
logViewable: false,
@@ -22,7 +22,7 @@ const {
PlainInput,
SeparatorComponent,
Spindicator,
genItemData,
genNewerItems,
pressItem,
renderSmallSwitchOption,
renderStackedItem,
@@ -170,7 +170,7 @@ export function SectionList_scrollable(Props: {
const [logViewable, setLogViewable] = React.useState(false);
const [debug, setDebug] = React.useState(false);
const [inverted, setInverted] = React.useState(false);
const [data, setData] = React.useState(genItemData(1000));
const [data, setData] = React.useState(genNewerItems(1000));
const filterRegex = new RegExp(String(filterText), 'i');
const filter = (item: Item) =>
@@ -73,6 +73,10 @@ type ViewabilityHelperCallbackTuple = {
type State = {
renderMask: CellRenderMask,
cellsAroundViewport: {first: number, last: number},
// Used to track items added at the start of the list for maintainVisibleContentPosition.
firstVisibleItemKey: ?string,
// When > 0 the scroll position available in JS is considered stale and should not be used.
pendingScrollUpdateCount: number,
};
/**
@@ -448,9 +452,24 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
const initialRenderRegion = VirtualizedList._initialRenderRegion(props);
const minIndexForVisible =
this.props.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
this.state = {
cellsAroundViewport: initialRenderRegion,
renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion),
firstVisibleItemKey:
this.props.getItemCount(this.props.data) > minIndexForVisible
? VirtualizedList._getItemKey(this.props, minIndexForVisible)
: null,
// When we have a non-zero initialScrollIndex, we will receive a
// scroll event later so this will prevent the window from updating
// until we get a valid offset.
pendingScrollUpdateCount:
this.props.initialScrollIndex != null &&
this.props.initialScrollIndex > 0
? 1
: 0,
};
}
@@ -502,6 +521,40 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
}
}
static _findItemIndexWithKey(
props: Props,
key: string,
hint: ?number,
): ?number {
const itemCount = props.getItemCount(props.data);
if (hint != null && hint >= 0 && hint < itemCount) {
const curKey = VirtualizedList._getItemKey(props, hint);
if (curKey === key) {
return hint;
}
}
for (let ii = 0; ii < itemCount; ii++) {
const curKey = VirtualizedList._getItemKey(props, ii);
if (curKey === key) {
return ii;
}
}
return null;
}
static _getItemKey(
props: {
data: Props['data'],
getItem: Props['getItem'],
keyExtractor: Props['keyExtractor'],
...
},
index: number,
): string {
const item = props.getItem(props.data, index);
return VirtualizedList._keyExtractor(item, index, props);
}
static _createRenderMask(
props: Props,
cellsAroundViewport: {first: number, last: number},
@@ -585,6 +638,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
_adjustCellsAroundViewport(
props: Props,
cellsAroundViewport: {first: number, last: number},
pendingScrollUpdateCount: number,
): {first: number, last: number} {
const {data, getItemCount} = props;
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
@@ -616,21 +670,9 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
),
};
} else {
// If we have a non-zero initialScrollIndex and run this before we've scrolled,
// we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex.
// So let's wait until we've scrolled the view to the right place. And until then,
// we will trust the initialScrollIndex suggestion.
// Thus, we want to recalculate the windowed render limits if any of the following hold:
// - initialScrollIndex is undefined or is 0
// - initialScrollIndex > 0 AND scrolling is complete
// - initialScrollIndex > 0 AND the end of the list is visible (this handles the case
// where the list is shorter than the visible area)
if (
props.initialScrollIndex &&
!this._scrollMetrics.offset &&
Math.abs(distanceFromEnd) >= Number.EPSILON
) {
// If we have a pending scroll update, we should not adjust the render window as it
// might override the correct window.
if (pendingScrollUpdateCount > 0) {
return cellsAroundViewport.last >= getItemCount(data)
? VirtualizedList._constrainToItemCount(cellsAroundViewport, props)
: cellsAroundViewport;
@@ -712,14 +754,59 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
return prevState;
}
let maintainVisibleContentPositionAdjustment: ?number = null;
const prevFirstVisibleItemKey = prevState.firstVisibleItemKey;
const minIndexForVisible =
newProps.maintainVisibleContentPosition?.minIndexForVisible ?? 0;
const newFirstVisibleItemKey =
newProps.getItemCount(newProps.data) > minIndexForVisible
? VirtualizedList._getItemKey(newProps, minIndexForVisible)
: null;
if (
newProps.maintainVisibleContentPosition != null &&
prevFirstVisibleItemKey != null &&
newFirstVisibleItemKey != null
) {
if (newFirstVisibleItemKey !== prevFirstVisibleItemKey) {
// Fast path if items were added at the start of the list.
const hint =
itemCount - prevState.renderMask.numCells() + minIndexForVisible;
const firstVisibleItemIndex = VirtualizedList._findItemIndexWithKey(
newProps,
prevFirstVisibleItemKey,
hint,
);
maintainVisibleContentPositionAdjustment =
firstVisibleItemIndex != null
? firstVisibleItemIndex - minIndexForVisible
: null;
} else {
maintainVisibleContentPositionAdjustment = null;
}
}
const constrainedCells = VirtualizedList._constrainToItemCount(
prevState.cellsAroundViewport,
maintainVisibleContentPositionAdjustment != null
? {
first:
prevState.cellsAroundViewport.first +
maintainVisibleContentPositionAdjustment,
last:
prevState.cellsAroundViewport.last +
maintainVisibleContentPositionAdjustment,
}
: prevState.cellsAroundViewport,
newProps,
);
return {
cellsAroundViewport: constrainedCells,
renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells),
firstVisibleItemKey: newFirstVisibleItemKey,
pendingScrollUpdateCount:
maintainVisibleContentPositionAdjustment != null
? prevState.pendingScrollUpdateCount + 1
: prevState.pendingScrollUpdateCount,
};
}
@@ -751,7 +838,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
for (let ii = first; ii <= last; ii++) {
const item = getItem(data, ii);
const key = this._keyExtractor(item, ii, this.props);
const key = VirtualizedList._keyExtractor(item, ii, this.props);
this._indicesToKeys.set(ii, key);
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
@@ -824,15 +911,14 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
_getSpacerKey = (isVertical: boolean): string =>
isVertical ? 'height' : 'width';
_keyExtractor(
static _keyExtractor(
item: Item,
index: number,
props: {
keyExtractor?: ?(item: Item, index: number) => string,
...
},
// $FlowFixMe[missing-local-annot]
) {
): string {
if (props.keyExtractor != null) {
return props.keyExtractor(item, index);
}
@@ -878,6 +964,10 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
cellKey={this._getCellKey() + '-header'}
key="$header">
<View
// We expect that header component will be a single native view so make it
// not collapsable to avoid this view being flattened and make this assumption
// no longer true.
collapsable={false}
onLayout={this._onLayoutHeader}
style={StyleSheet.compose(
inversionStyle,
@@ -1035,6 +1125,16 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
style: inversionStyle
? [inversionStyle, this.props.style]
: this.props.style,
maintainVisibleContentPosition:
this.props.maintainVisibleContentPosition != null
? {
...this.props.maintainVisibleContentPosition,
// Adjust index to account for ListHeaderComponent.
minIndexForVisible:
this.props.maintainVisibleContentPosition.minIndexForVisible +
(this.props.ListHeaderComponent ? 1 : 0),
}
: undefined,
};
this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1;
@@ -1456,8 +1556,13 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
onStartReachedThreshold,
onEndReached,
onEndReachedThreshold,
initialScrollIndex,
} = this.props;
// If we have any pending scroll updates it means that the scroll metrics
// are out of date and we should not call any of the edge reached callbacks.
if (this.state.pendingScrollUpdateCount > 0) {
return;
}
const {contentLength, visibleLength, offset} = this._scrollMetrics;
let distanceFromStart = offset;
let distanceFromEnd = contentLength - visibleLength - offset;
@@ -1509,14 +1614,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
isWithinStartThreshold &&
this._scrollMetrics.contentLength !== this._sentStartForContentLength
) {
// On initial mount when using initialScrollIndex the offset will be 0 initially
// and will trigger an unexpected onStartReached. To avoid this we can use
// timestamp to differentiate between the initial scroll metrics and when we actually
// received the first scroll event.
if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) {
this._sentStartForContentLength = this._scrollMetrics.contentLength;
onStartReached({distanceFromStart});
}
this._sentStartForContentLength = this._scrollMetrics.contentLength;
onStartReached({distanceFromStart});
}
// If the user scrolls away from the start or end and back again,
@@ -1643,6 +1742,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
visibleLength,
zoomScale,
};
if (this.state.pendingScrollUpdateCount > 0) {
this.setState(state => ({
pendingScrollUpdateCount: state.pendingScrollUpdateCount - 1,
}));
}
this._updateViewableItems(this.props, this.state.cellsAroundViewport);
if (!this.props) {
return;
@@ -1758,6 +1862,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
const cellsAroundViewport = this._adjustCellsAroundViewport(
props,
state.cellsAroundViewport,
state.pendingScrollUpdateCount,
);
const renderMask = VirtualizedList._createRenderMask(
props,
@@ -1788,7 +1893,7 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
return {
index,
item,
key: this._keyExtractor(item, index, props),
key: VirtualizedList._keyExtractor(item, index, props),
isViewable,
};
};
@@ -1849,13 +1954,12 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
inLayout?: boolean,
...
} => {
const {data, getItem, getItemCount, getItemLayout} = props;
const {data, getItemCount, getItemLayout} = props;
invariant(
index >= 0 && index < getItemCount(data),
'Tried to get frame for out of range index ' + index,
);
const item = getItem(data, index);
const frame = this._frames[this._keyExtractor(item, index, props)];
const frame = this._frames[VirtualizedList._getItemKey(props, index)];
if (!frame || frame.index !== index) {
if (getItemLayout) {
/* $FlowFixMe[prop-missing] (>=0.63.0 site=react_native_fb) This comment
@@ -1890,11 +1994,8 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
// where it is.
if (
focusedCellIndex >= itemCount ||
this._keyExtractor(
props.getItem(props.data, focusedCellIndex),
focusedCellIndex,
props,
) !== this._lastFocusedCellKey
VirtualizedList._getItemKey(props, focusedCellIndex) !==
this._lastFocusedCellKey
) {
return [];
}
@@ -1935,6 +2036,11 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
props: FrameMetricProps,
cellsAroundViewport: {first: number, last: number},
) {
// If we have any pending scroll updates it means that the scroll metrics
// are out of date and we should not call any of the visibility callbacks.
if (this.state.pendingScrollUpdateCount > 0) {
return;
}
this._viewabilityTuples.forEach(tuple => {
tuple.viewabilityHelper.onUpdate(
props,
@@ -2164,10 +2164,70 @@ it('virtualizes away last focused index if item removed', () => {
expect(component).toMatchSnapshot();
});
function generateItems(count) {
it('handles maintainVisibleContentPosition', () => {
const items = generateItems(20);
const ITEM_HEIGHT = 10;
let component;
ReactTestRenderer.act(() => {
component = ReactTestRenderer.create(
<VirtualizedList
initialNumToRender={1}
windowSize={1}
maintainVisibleContentPosition={{minIndexForVisible: 0}}
{...baseItemProps(items)}
{...fixedHeightItemLayoutProps(ITEM_HEIGHT)}
/>,
);
});
ReactTestRenderer.act(() => {
simulateLayout(component, {
viewport: {width: 10, height: 50},
content: {width: 10, height: items.length * ITEM_HEIGHT},
});
performAllBatches();
});
// Initial render.
expect(component).toMatchSnapshot();
// Add new items at the start of the list to trigger the maintainVisibleContentPosition adjustment.
const newItems = [...generateItems(10, items.length), ...items];
ReactTestRenderer.act(() => {
component.update(
<VirtualizedList
initialNumToRender={1}
windowSize={1}
maintainVisibleContentPosition={{minIndexForVisible: 0}}
{...baseItemProps(newItems)}
{...fixedHeightItemLayoutProps(ITEM_HEIGHT)}
/>,
);
});
// Previously rendered cells should be rendered still.
expect(component).toMatchSnapshot();
// Simulate scroll adjustment from native maintainVisibleContentPosition.
ReactTestRenderer.act(() => {
simulateContentLayout(component, {
width: 10,
height: newItems.length * ITEM_HEIGHT,
});
simulateScroll(component, {x: 0, y: 10 * ITEM_HEIGHT});
performAllBatches();
});
// Previously rendered cells should be rendered still + starting to render new cells ahead.
expect(component).toMatchSnapshot();
});
function generateItems(count, startKey = 0) {
return Array(count)
.fill()
.map((_, i) => ({key: i}));
.map((_, i) => ({key: i + startKey}));
}
function generateItemsStickyEveryN(count, n) {
@@ -2225,7 +2285,7 @@ function simulateContentLayout(component, dimensions) {
function simulateCellLayout(component, items, itemIndex, dimensions) {
const instance = component.getInstance();
const cellKey = instance._keyExtractor(
const cellKey = VirtualizedList._keyExtractor(
items[itemIndex],
itemIndex,
instance.props,
@@ -65,6 +65,7 @@ exports[`VirtualizedList forwards correct stickyHeaderIndices when ListHeaderCom
>
<View>
<View
collapsable={false}
onLayout={[Function]}
>
<Header />
@@ -1048,6 +1049,7 @@ exports[`VirtualizedList renders all the bells and whistles 1`] = `
<RCTRefreshControl />
<View>
<View
collapsable={false}
onLayout={[Function]}
style={
Object {
@@ -1249,6 +1251,7 @@ exports[`VirtualizedList renders empty list with empty component 1`] = `
>
<View>
<View
collapsable={false}
onLayout={[Function]}
>
<header />
@@ -1834,45 +1837,12 @@ exports[`adjusts render area with non-zero initialScrollIndex 1`] = `
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={15}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={16}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={17}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={18}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={19}
/>
</View>
style={
Object {
"height": 50,
}
}
/>
</View>
</RCTScrollView>
`;
@@ -3121,10 +3091,566 @@ exports[`gracefully handles negative initialScrollIndex 1`] = `
value={3}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={4}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={5}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={6}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={7}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={8}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={9}
/>
</View>
</View>
</RCTScrollView>
`;
exports[`handles maintainVisibleContentPosition 1`] = `
<RCTScrollView
data={
Array [
Object {
"key": 0,
},
Object {
"key": 1,
},
Object {
"key": 2,
},
Object {
"key": 3,
},
Object {
"key": 4,
},
Object {
"key": 5,
},
Object {
"key": 6,
},
Object {
"key": 7,
},
Object {
"key": 8,
},
Object {
"key": 9,
},
Object {
"key": 10,
},
Object {
"key": 11,
},
Object {
"key": 12,
},
Object {
"key": 13,
},
Object {
"key": 14,
},
Object {
"key": 15,
},
Object {
"key": 16,
},
Object {
"key": 17,
},
Object {
"key": 18,
},
Object {
"key": 19,
},
]
}
getItem={[Function]}
getItemCount={[Function]}
getItemLayout={[Function]}
initialNumToRender={1}
maintainVisibleContentPosition={
Object {
"minIndexForVisible": 0,
}
}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
windowSize={1}
>
<View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={0}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={1}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={2}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={3}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={4}
/>
</View>
<View
style={
Object {
"height": 60,
"height": 150,
}
}
/>
</View>
</RCTScrollView>
`;
exports[`handles maintainVisibleContentPosition 2`] = `
<RCTScrollView
data={
Array [
Object {
"key": 20,
},
Object {
"key": 21,
},
Object {
"key": 22,
},
Object {
"key": 23,
},
Object {
"key": 24,
},
Object {
"key": 25,
},
Object {
"key": 26,
},
Object {
"key": 27,
},
Object {
"key": 28,
},
Object {
"key": 29,
},
Object {
"key": 0,
},
Object {
"key": 1,
},
Object {
"key": 2,
},
Object {
"key": 3,
},
Object {
"key": 4,
},
Object {
"key": 5,
},
Object {
"key": 6,
},
Object {
"key": 7,
},
Object {
"key": 8,
},
Object {
"key": 9,
},
Object {
"key": 10,
},
Object {
"key": 11,
},
Object {
"key": 12,
},
Object {
"key": 13,
},
Object {
"key": 14,
},
Object {
"key": 15,
},
Object {
"key": 16,
},
Object {
"key": 17,
},
Object {
"key": 18,
},
Object {
"key": 19,
},
]
}
getItem={[Function]}
getItemCount={[Function]}
getItemLayout={[Function]}
initialNumToRender={1}
maintainVisibleContentPosition={
Object {
"minIndexForVisible": 0,
}
}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
windowSize={1}
>
<View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={20}
/>
</View>
<View
style={
Object {
"height": 90,
}
}
/>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={0}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={1}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={2}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={3}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={4}
/>
</View>
<View
style={
Object {
"height": 150,
}
}
/>
</View>
</RCTScrollView>
`;
exports[`handles maintainVisibleContentPosition 3`] = `
<RCTScrollView
data={
Array [
Object {
"key": 20,
},
Object {
"key": 21,
},
Object {
"key": 22,
},
Object {
"key": 23,
},
Object {
"key": 24,
},
Object {
"key": 25,
},
Object {
"key": 26,
},
Object {
"key": 27,
},
Object {
"key": 28,
},
Object {
"key": 29,
},
Object {
"key": 0,
},
Object {
"key": 1,
},
Object {
"key": 2,
},
Object {
"key": 3,
},
Object {
"key": 4,
},
Object {
"key": 5,
},
Object {
"key": 6,
},
Object {
"key": 7,
},
Object {
"key": 8,
},
Object {
"key": 9,
},
Object {
"key": 10,
},
Object {
"key": 11,
},
Object {
"key": 12,
},
Object {
"key": 13,
},
Object {
"key": 14,
},
Object {
"key": 15,
},
Object {
"key": 16,
},
Object {
"key": 17,
},
Object {
"key": 18,
},
Object {
"key": 19,
},
]
}
getItem={[Function]}
getItemCount={[Function]}
getItemLayout={[Function]}
initialNumToRender={1}
maintainVisibleContentPosition={
Object {
"minIndexForVisible": 0,
}
}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
windowSize={1}
>
<View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={20}
/>
</View>
<View
style={
Object {
"height": 80,
}
}
/>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={29}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={0}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={1}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={2}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={3}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={4}
/>
</View>
<View
style={
Object {
"height": 150,
}
}
/>
@@ -4072,21 +4598,12 @@ exports[`renders new items when data is updated with non-zero initialScrollIndex
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={2}
/>
</View>
<View
onFocusCapture={[Function]}
style={null}
>
<MockCellItem
value={3}
/>
</View>
style={
Object {
"height": 20,
}
}
/>
</View>
</RCTScrollView>
`;
@@ -831,6 +831,7 @@ exports[`VirtualizedSectionList renders all the bells and whistles 1`] = `
<RCTRefreshControl />
<View>
<View
collapsable={false}
onLayout={[Function]}
style={
Object {
@@ -1056,6 +1057,7 @@ exports[`VirtualizedSectionList renders empty list with empty component 1`] = `
>
<View>
<View
collapsable={false}
onLayout={[Function]}
>
<header />