Skip to content

Commit 07fde4c

Browse files
authored
chore: Full test coverage (#3)
* add demo * full coverage * clean up
1 parent d7e94e3 commit 07fde4c

File tree

7 files changed

+520
-129
lines changed

7 files changed

+520
-129
lines changed

examples/without-height.tsx renamed to examples/no-virtual.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,35 @@ const Demo = () => {
3838
return (
3939
<React.StrictMode>
4040
<div>
41-
<h2>Without Height</h2>
41+
<h2>Less Count</h2>
42+
<List
43+
data={data.slice(0, 1)}
44+
itemHeight={30}
45+
height={100}
46+
itemKey="id"
47+
style={{
48+
border: '1px solid red',
49+
boxSizing: 'border-box',
50+
}}
51+
>
52+
{item => <ForwardMyItem {...item} />}
53+
</List>
4254

55+
<h2>Less Item Height</h2>
56+
<List
57+
data={data.slice(0, 10)}
58+
itemHeight={1}
59+
height={100}
60+
itemKey="id"
61+
style={{
62+
border: '1px solid red',
63+
boxSizing: 'border-box',
64+
}}
65+
>
66+
{item => <ForwardMyItem {...item} />}
67+
</List>
68+
69+
<h2>Without Height</h2>
4370
<List
4471
data={data}
4572
itemHeight={30}

src/List.tsx

Lines changed: 100 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,28 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
102102
/**
103103
* Always point to the latest props if `disabled` is `false`
104104
*/
105-
cachedProps: Partial<ListProps<T>> = {};
105+
cachedProps: Partial<ListProps<T>>;
106106

107107
/**
108108
* Lock scroll process with `onScroll` event.
109109
* This is used for `data` length change and `scrollTop` restore
110110
*/
111111
lockScroll: boolean = false;
112112

113+
constructor(props: ListProps<T>) {
114+
super(props);
115+
116+
this.cachedProps = props;
117+
}
118+
113119
/**
114120
* Phase 1: Initial should sync with default scroll top
115121
*/
116122
public componentDidMount() {
117-
this.listRef.current.scrollTop = 0;
118-
this.onScroll();
123+
if (this.listRef.current) {
124+
this.listRef.current.scrollTop = 0;
125+
this.onScroll();
126+
}
119127
}
120128

121129
/**
@@ -127,7 +135,7 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
127135
const { data, height, itemHeight, disabled } = this.props;
128136
const prevData: T[] = this.cachedProps.data || [];
129137

130-
if (disabled) {
138+
if (disabled || !this.listRef.current) {
131139
return;
132140
}
133141

@@ -209,7 +217,7 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
209217
itemElementHeights: this.itemElementHeights,
210218
});
211219

212-
this.scrollTo({
220+
this.internalScrollTo({
213221
itemIndex: originCompareItemIndex,
214222
relativeTop: originCompareItemTop,
215223
});
@@ -262,6 +270,7 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
262270

263271
const item = data[index];
264272
if (!item) {
273+
/* istanbul ignore next */
265274
console.error('Not find index item. Please report this since it is a bug.');
266275
}
267276

@@ -286,106 +295,106 @@ class List<T> extends React.Component<ListProps<T>, ListState<T>> {
286295
}
287296
};
288297

289-
public scrollTo(arg: number | RelativeScroll): void {
290-
if (typeof arg === 'number') {
291-
this.listRef.current.scrollTop = arg;
292-
} else if (typeof arg === 'object') {
293-
const { itemIndex: compareItemIndex, relativeTop: compareItemRelativeTop } = arg;
294-
const { scrollTop: originScrollTop } = this.state;
295-
const { data, itemHeight, height } = this.props;
298+
public scrollTo(scrollTop: number) {
299+
this.listRef.current.scrollTop = scrollTop;
300+
}
301+
302+
public internalScrollTo(relativeScroll: RelativeScroll): void {
303+
const { itemIndex: compareItemIndex, relativeTop: compareItemRelativeTop } = relativeScroll;
304+
const { scrollTop: originScrollTop } = this.state;
305+
const { data, itemHeight, height } = this.props;
296306

297-
// 1. Find the best match compare item top
298-
let bestSimilarity = Number.MAX_VALUE;
299-
let bestScrollTop: number = null;
300-
let bestItemIndex: number = null;
301-
let bestItemOffsetPtg: number = null;
302-
let bestStartIndex: number = null;
303-
let bestEndIndex: number = null;
307+
// 1. Find the best match compare item top
308+
let bestSimilarity = Number.MAX_VALUE;
309+
let bestScrollTop: number = null;
310+
let bestItemIndex: number = null;
311+
let bestItemOffsetPtg: number = null;
312+
let bestStartIndex: number = null;
313+
let bestEndIndex: number = null;
304314

305-
let missSimilarity = 0;
315+
let missSimilarity = 0;
306316

307-
const scrollHeight = data.length * itemHeight;
308-
const { clientHeight } = this.listRef.current;
309-
const maxScrollTop = scrollHeight - clientHeight;
317+
const scrollHeight = data.length * itemHeight;
318+
const { clientHeight } = this.listRef.current;
319+
const maxScrollTop = scrollHeight - clientHeight;
310320

311-
for (let i = 0; i < maxScrollTop; i += 1) {
312-
const scrollTop = getIndexByStartLoc(0, maxScrollTop, originScrollTop, i);
321+
for (let i = 0; i < maxScrollTop; i += 1) {
322+
const scrollTop = getIndexByStartLoc(0, maxScrollTop, originScrollTop, i);
313323

314-
const scrollPtg = getScrollPercentage({ scrollTop, scrollHeight, clientHeight });
315-
const visibleCount = Math.ceil(height / itemHeight);
324+
const scrollPtg = getScrollPercentage({ scrollTop, scrollHeight, clientHeight });
325+
const visibleCount = Math.ceil(height / itemHeight);
316326

317-
const { itemIndex, itemOffsetPtg, startIndex, endIndex } = getRangeIndex(
327+
const { itemIndex, itemOffsetPtg, startIndex, endIndex } = getRangeIndex(
328+
scrollPtg,
329+
data.length,
330+
visibleCount,
331+
);
332+
333+
// No need to check if compare item out of the index to save performance
334+
if (startIndex <= compareItemIndex && compareItemIndex <= endIndex) {
335+
// 1.1 Get measure located item relative top
336+
const locatedItemRelativeTop = getItemRelativeTop({
337+
itemIndex,
338+
itemOffsetPtg,
339+
itemElementHeights: this.itemElementHeights,
318340
scrollPtg,
319-
data.length,
320-
visibleCount,
321-
);
322-
323-
// No need to check if compare item out of the index to save performance
324-
if (startIndex <= compareItemIndex && compareItemIndex <= endIndex) {
325-
// 1.1 Get measure located item relative top
326-
const locatedItemRelativeTop = getItemRelativeTop({
327-
itemIndex,
328-
itemOffsetPtg,
329-
itemElementHeights: this.itemElementHeights,
330-
scrollPtg,
331-
clientHeight,
332-
getItemKey: this.getIndexKey,
333-
});
334-
335-
const compareItemTop = getCompareItemRelativeTop({
336-
locatedItemRelativeTop,
337-
locatedItemIndex: itemIndex,
338-
compareItemIndex, // Same as origin index
339-
startIndex,
340-
endIndex,
341-
getItemKey: this.getIndexKey,
342-
itemElementHeights: this.itemElementHeights,
343-
});
344-
345-
// 1.2 Find best match compare item top
346-
const similarity = Math.abs(compareItemTop - compareItemRelativeTop);
347-
if (similarity < bestSimilarity) {
348-
bestSimilarity = similarity;
349-
bestScrollTop = scrollTop;
350-
bestItemIndex = itemIndex;
351-
bestItemOffsetPtg = itemOffsetPtg;
352-
bestStartIndex = startIndex;
353-
bestEndIndex = endIndex;
354-
355-
missSimilarity = 0;
356-
} else {
357-
missSimilarity += 1;
358-
}
359-
}
341+
clientHeight,
342+
getItemKey: this.getIndexKey,
343+
});
360344

361-
// If keeping 10 times not match similarity,
362-
// check more scrollTop is meaningless.
363-
// Here boundary is set to 10.
364-
if (missSimilarity > 10) {
365-
break;
345+
const compareItemTop = getCompareItemRelativeTop({
346+
locatedItemRelativeTop,
347+
locatedItemIndex: itemIndex,
348+
compareItemIndex, // Same as origin index
349+
startIndex,
350+
endIndex,
351+
getItemKey: this.getIndexKey,
352+
itemElementHeights: this.itemElementHeights,
353+
});
354+
355+
// 1.2 Find best match compare item top
356+
const similarity = Math.abs(compareItemTop - compareItemRelativeTop);
357+
if (similarity < bestSimilarity) {
358+
bestSimilarity = similarity;
359+
bestScrollTop = scrollTop;
360+
bestItemIndex = itemIndex;
361+
bestItemOffsetPtg = itemOffsetPtg;
362+
bestStartIndex = startIndex;
363+
bestEndIndex = endIndex;
364+
365+
missSimilarity = 0;
366+
} else {
367+
missSimilarity += 1;
366368
}
367369
}
368370

369-
// 2. Re-scroll if has best scroll match
370-
if (bestScrollTop !== null) {
371-
this.lockScroll = true;
372-
this.listRef.current.scrollTop = bestScrollTop;
373-
374-
this.setState({
375-
status: 'MEASURE_START',
376-
scrollTop: bestScrollTop,
377-
itemIndex: bestItemIndex,
378-
itemOffsetPtg: bestItemOffsetPtg,
379-
startIndex: bestStartIndex,
380-
endIndex: bestEndIndex,
381-
});
371+
// If keeping 10 times not match similarity,
372+
// check more scrollTop is meaningless.
373+
// Here boundary is set to 10.
374+
if (missSimilarity > 10) {
375+
break;
376+
}
377+
}
378+
379+
// 2. Re-scroll if has best scroll match
380+
if (bestScrollTop !== null) {
381+
this.lockScroll = true;
382+
this.listRef.current.scrollTop = bestScrollTop;
382383

384+
this.setState({
385+
status: 'MEASURE_START',
386+
scrollTop: bestScrollTop,
387+
itemIndex: bestItemIndex,
388+
itemOffsetPtg: bestItemOffsetPtg,
389+
startIndex: bestStartIndex,
390+
endIndex: bestEndIndex,
391+
});
392+
393+
requestAnimationFrame(() => {
383394
requestAnimationFrame(() => {
384-
requestAnimationFrame(() => {
385-
this.lockScroll = false;
386-
});
395+
this.lockScroll = false;
387396
});
388-
}
397+
});
389398
}
390399
}
391400

src/utils/algorithmUtil.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function getIndexByStartLoc(min: number, max: number, start: number, inde
2222
if (index % 2) {
2323
return start + stepIndex + 1;
2424
}
25-
return start - stepIndex;
25+
return start - stepIndex;
2626
}
2727

2828
// One is out of range
@@ -46,7 +46,7 @@ export function findListDiffIndex<T>(
4646
}
4747

4848
let startIndex = 0;
49-
let endIndex = originList.length - 1;
49+
let endIndex = Math.max(originList.length, targetList.length) - 1;
5050
let midIndex = Math.floor((startIndex + endIndex) / 2);
5151

5252
const keyCache: Map<T, string | { __EMPTY_ITEM__: true }> = new Map();

0 commit comments

Comments
 (0)