@react-native/virtualized-lists 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ * @format
9
+ * @oncall react_native
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const VirtualizedSectionList = require('../VirtualizedSectionList');
15
+ const React = require('react');
16
+ const ReactTestRenderer = require('react-test-renderer');
17
+
18
+ describe('VirtualizedSectionList', () => {
19
+ it('renders simple list', () => {
20
+ const component = ReactTestRenderer.create(
21
+ <VirtualizedSectionList
22
+ sections={[
23
+ // $FlowFixMe[incompatible-type]
24
+ {title: 's1', data: [{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]},
25
+ ]}
26
+ // $FlowFixMe[missing-local-annot]
27
+ renderItem={({item}) => <item value={item.key} />}
28
+ getItem={(data, key) => data[key]}
29
+ getItemCount={data => data.length}
30
+ />,
31
+ );
32
+ expect(component).toMatchSnapshot();
33
+ });
34
+
35
+ it('renders empty list', () => {
36
+ const component = ReactTestRenderer.create(
37
+ <VirtualizedSectionList
38
+ sections={[]}
39
+ renderItem={({item}) => <item value={item.key} />}
40
+ getItem={(data, key) => data[key]}
41
+ getItemCount={data => data.length}
42
+ />,
43
+ );
44
+ expect(component).toMatchSnapshot();
45
+ });
46
+
47
+ it('renders empty list with empty component', () => {
48
+ const component = ReactTestRenderer.create(
49
+ <VirtualizedSectionList
50
+ sections={[]}
51
+ ListEmptyComponent={() => <empty />}
52
+ ListFooterComponent={() => <footer />}
53
+ ListHeaderComponent={() => <header />}
54
+ getItem={(data, key) => data[key]}
55
+ getItemCount={data => data.length}
56
+ renderItem={({item}) => <item value={item.key} />}
57
+ />,
58
+ );
59
+ expect(component).toMatchSnapshot();
60
+ });
61
+
62
+ it('renders list with empty component', () => {
63
+ const component = ReactTestRenderer.create(
64
+ <VirtualizedSectionList
65
+ // $FlowFixMe[incompatible-type]
66
+ sections={[{title: 's1', data: [{key: 'hello'}]}]}
67
+ ListEmptyComponent={() => <empty />}
68
+ getItem={(data, key) => data[key]}
69
+ getItemCount={data => data.length}
70
+ renderItem={({item}) => <item value={item.key} />}
71
+ />,
72
+ );
73
+ expect(component).toMatchSnapshot();
74
+ });
75
+
76
+ it('renders all the bells and whistles', () => {
77
+ const component = ReactTestRenderer.create(
78
+ <VirtualizedSectionList
79
+ ItemSeparatorComponent={() => <separator />}
80
+ ListEmptyComponent={() => <empty />}
81
+ ListFooterComponent={() => <footer />}
82
+ ListHeaderComponent={() => <header />}
83
+ sections={[
84
+ // $FlowFixMe[incompatible-type]
85
+ {
86
+ title: 's1',
87
+ // $FlowFixMe[incompatible-call]
88
+ data: new Array<void>(5).fill().map((_, ii) => ({id: String(ii)})),
89
+ },
90
+ ]}
91
+ getItem={(data, key) => data[key]}
92
+ getItemCount={data => data.length}
93
+ getItemLayout={({index}) => ({
94
+ index: -1,
95
+ length: 50,
96
+ offset: index * 50,
97
+ })}
98
+ inverted={true}
99
+ keyExtractor={(item, index) => item.id}
100
+ onRefresh={jest.fn()}
101
+ refreshing={false}
102
+ renderItem={({item}) => <item value={item.id} />}
103
+ />,
104
+ );
105
+ expect(component).toMatchSnapshot();
106
+ });
107
+
108
+ it('handles separators correctly', () => {
109
+ const infos = [];
110
+ let component;
111
+ ReactTestRenderer.act(() => {
112
+ component = ReactTestRenderer.create(
113
+ <VirtualizedSectionList
114
+ ItemSeparatorComponent={props => <separator {...props} />}
115
+ sections={[
116
+ // $FlowFixMe[incompatible-type]
117
+ {title: 's0', data: [{key: 'i0'}, {key: 'i1'}, {key: 'i2'}]},
118
+ ]}
119
+ renderItem={info => {
120
+ infos.push(info);
121
+ return <item title={info.item.key} />;
122
+ }}
123
+ getItem={(data, key) => data[key]}
124
+ getItemCount={data => data.length}
125
+ />,
126
+ );
127
+ });
128
+ expect(component).toMatchSnapshot();
129
+
130
+ ReactTestRenderer.act(() => {
131
+ infos[1].separators.highlight();
132
+ });
133
+ expect(component).toMatchSnapshot();
134
+ ReactTestRenderer.act(() => {
135
+ infos[2].separators.updateProps('leading', {press: true});
136
+ });
137
+ expect(component).toMatchSnapshot();
138
+ ReactTestRenderer.act(() => {
139
+ infos[1].separators.unhighlight();
140
+ });
141
+ expect(component).toMatchSnapshot();
142
+ });
143
+
144
+ it('handles nested lists', () => {
145
+ const component = ReactTestRenderer.create(
146
+ <VirtualizedSectionList
147
+ // $FlowFixMe[incompatible-type]
148
+ sections={[{title: 'outer', data: [{key: 'outer0'}, {key: 'outer1'}]}]}
149
+ renderItem={outerInfo => (
150
+ <VirtualizedSectionList
151
+ sections={[
152
+ // $FlowFixMe[incompatible-type]
153
+ {
154
+ title: 'inner',
155
+ data: [
156
+ {key: outerInfo.item.key + ':inner0'},
157
+ {key: outerInfo.item.key + ':inner1'},
158
+ ],
159
+ },
160
+ ]}
161
+ horizontal={outerInfo.item.key === 'outer1'}
162
+ renderItem={innerInfo => {
163
+ return <item title={innerInfo.item.key} />;
164
+ }}
165
+ getItem={(data, key) => data[key]}
166
+ getItemCount={data => data.length}
167
+ />
168
+ )}
169
+ getItem={(data, key) => data[key]}
170
+ getItemCount={data => data.length}
171
+ />,
172
+ );
173
+ expect(component).toMatchSnapshot();
174
+ });
175
+
176
+ describe('scrollToLocation', () => {
177
+ const ITEM_HEIGHT = 100;
178
+
179
+ const createVirtualizedSectionList = (props?: {
180
+ stickySectionHeadersEnabled: boolean,
181
+ }) => {
182
+ const component = ReactTestRenderer.create(
183
+ <VirtualizedSectionList
184
+ sections={[
185
+ // $FlowFixMe[incompatible-type]
186
+ {title: 's1', data: [{key: 'i1.1'}, {key: 'i1.2'}, {key: 'i1.3'}]},
187
+ // $FlowFixMe[incompatible-type]
188
+ {title: 's2', data: [{key: 'i2.1'}, {key: 'i2.2'}, {key: 'i2.3'}]},
189
+ ]}
190
+ renderItem={({item}) => <item value={item.key} />}
191
+ getItem={(data, key) => data[key]}
192
+ getItemCount={data => data.length}
193
+ getItemLayout={(data, index) => ({
194
+ length: ITEM_HEIGHT,
195
+ offset: ITEM_HEIGHT * index,
196
+ index,
197
+ })}
198
+ {...props}
199
+ />,
200
+ );
201
+ const instance = component.getInstance();
202
+ const spy = jest.fn();
203
+
204
+ // $FlowFixMe[incompatible-use] wrong types
205
+ // $FlowFixMe[prop-missing] wrong types
206
+ instance._listRef.scrollToIndex = spy;
207
+
208
+ return {
209
+ instance,
210
+ spy,
211
+ };
212
+ };
213
+
214
+ it('when sticky stickySectionHeadersEnabled={true}, header height is added to the developer-provided viewOffset', () => {
215
+ const {instance, spy} = createVirtualizedSectionList({
216
+ stickySectionHeadersEnabled: true,
217
+ });
218
+
219
+ const viewOffset = 25;
220
+
221
+ // $FlowFixMe[prop-missing] scrollToLocation isn't on instance
222
+ instance?.scrollToLocation({
223
+ sectionIndex: 0,
224
+ itemIndex: 1,
225
+ viewOffset,
226
+ });
227
+ expect(spy).toHaveBeenCalledWith({
228
+ index: 1,
229
+ itemIndex: 1,
230
+ sectionIndex: 0,
231
+ viewOffset: viewOffset + ITEM_HEIGHT,
232
+ });
233
+ });
234
+
235
+ it.each([
236
+ [
237
+ // prevents #18098
238
+ {sectionIndex: 0, itemIndex: 0},
239
+ {
240
+ index: 0,
241
+ itemIndex: 0,
242
+ sectionIndex: 0,
243
+ viewOffset: 0,
244
+ },
245
+ ],
246
+ [
247
+ {sectionIndex: 2, itemIndex: 1},
248
+ {
249
+ index: 11,
250
+ itemIndex: 1,
251
+ sectionIndex: 2,
252
+ viewOffset: 0,
253
+ },
254
+ ],
255
+ [
256
+ {
257
+ sectionIndex: 0,
258
+ itemIndex: 1,
259
+ viewOffset: 25,
260
+ },
261
+ {
262
+ index: 1,
263
+ itemIndex: 1,
264
+ sectionIndex: 0,
265
+ viewOffset: 25,
266
+ },
267
+ ],
268
+ ])(
269
+ 'given sectionIndex, itemIndex and viewOffset, scrollToIndex is called with correct params',
270
+ (scrollToLocationParams, expected) => {
271
+ const {instance, spy} = createVirtualizedSectionList();
272
+ // $FlowFixMe[prop-missing] scrollToLocation not on instance
273
+ instance?.scrollToLocation(scrollToLocationParams);
274
+ expect(spy).toHaveBeenCalledWith(expected);
275
+ },
276
+ );
277
+ });
278
+ });