@shopify/flash-list
Version:
FlashList is a more performant FlatList replacement
180 lines (161 loc) • 6.36 kB
text/typescript
import { ScrollView } from "react-native";
import { ProgressiveListView } from "recyclerlistview";
import FlashList from "../FlashList";
import { mountFlashList } from "./helpers/mountFlashList";
describe("GridLayoutProviderWithProps", () => {
it("updates average window on layout manager change", () => {
const flashList = mountFlashList();
const oldAverageWindow = (flashList.instance as FlashList<any>).state
.layoutProvider["averageWindow"];
// width change from default 400 to 600 will force layout manager to change
flashList.find(ScrollView)?.trigger("onLayout", {
nativeEvent: { layout: { height: 900, width: 600 } },
});
const newAverageWindow =
flashList.instance.state.layoutProvider["averageWindow"];
expect(newAverageWindow).not.toBe(oldAverageWindow);
flashList.unmount();
});
it("average window's size is two times the number of items that will fill the screen", () => {
const flashList = mountFlashList({ numColumns: 2 });
expect(
flashList.instance.state.layoutProvider["averageWindow"]["inputValues"]
.length
).toBe(20);
flashList.find(ScrollView)?.trigger("onLayout", {
nativeEvent: { layout: { height: 2000, width: 600 } },
});
expect(
flashList.instance.state.layoutProvider["averageWindow"]["inputValues"]
.length
).toBe(40);
flashList.setProps({ numColumns: 1 });
expect(
flashList.instance.state.layoutProvider["averageWindow"]["inputValues"]
.length
).toBe(20);
flashList.unmount();
});
it("average window should not be less than 6 in size", () => {
const flashList = mountFlashList();
flashList.find(ScrollView)?.trigger("onLayout", {
nativeEvent: { layout: { height: 100, width: 100 } },
});
expect(
flashList.instance.state.layoutProvider["averageWindow"]["inputValues"]
.length
).toBe(6);
flashList.unmount();
});
it("vertical list: average should not update when widths change", () => {
const flashList = mountFlashList();
const layoutProvider = flashList.instance.state.layoutProvider;
const oldAverage = layoutProvider.averageItemSize;
layoutProvider.getLayoutManager()!.getLayouts()[0].width = 500;
flashList.find(ProgressiveListView)?.instance.onItemLayout(0);
expect(oldAverage).toBe(layoutProvider.averageItemSize);
flashList.unmount();
});
it("horizontal list: average should not update when heights change", () => {
const flashList = mountFlashList({ horizontal: true });
const layoutProvider = flashList.instance.state.layoutProvider;
const oldAverage = layoutProvider.averageItemSize;
layoutProvider.getLayoutManager()!.getLayouts()[0].height = 600;
// Can throw a no op set state warning. Should be handled in PLV.
flashList.find(ProgressiveListView)?.instance.onItemLayout(0);
expect(oldAverage).toBe(layoutProvider.averageItemSize);
flashList.unmount();
});
it("computes correct average", () => {
const flashList = mountFlashList();
const layoutProvider = flashList.instance.state.layoutProvider;
expect(layoutProvider.averageItemSize).toBe(200);
const layouts = layoutProvider.getLayoutManager()!.getLayouts();
const progressiveListView = flashList.find(ProgressiveListView);
layouts[0].height = 100;
layouts[1].height = 200;
layouts[2].height = 300;
layouts[3].height = 400;
progressiveListView?.instance.onItemLayout(0);
progressiveListView?.instance.onItemLayout(1);
progressiveListView?.instance.onItemLayout(2);
progressiveListView?.instance.onItemLayout(3);
// estimatedItemSize is treated as one of the values. That's why 240.
expect(layoutProvider.averageItemSize).toBe(240);
flashList.unmount();
});
it("updates all cached widths for vertical list and heights for horizontal list when orientation changes", () => {
const runCacheUpdateTest = (horizontal: boolean) => {
const flashList = mountFlashList({
data: new Array(20).fill("1"),
horizontal,
});
const progressiveListView = flashList.find(ProgressiveListView);
const layoutProvider = flashList.instance.state.layoutProvider;
layoutProvider
.getLayoutManager()!
.getLayouts()
.forEach((layout, index) => {
// marking layouts as if they're actual rendered sizes
progressiveListView?.instance.onItemLayout(index);
// checking size
if (horizontal) {
expect(layout.height).toBe(900);
} else {
expect(layout.width).toBe(400);
}
});
// simulates orientation change
flashList.find(ScrollView)?.trigger("onLayout", {
nativeEvent: { layout: { height: 400, width: 900 } },
});
layoutProvider
.getLayoutManager()!
.getLayouts()
.forEach((layout) => {
// making sure all widths or heights are updated
if (horizontal) {
expect(layout.height).toBe(400);
} else {
expect(layout.width).toBe(900);
}
// making sure extra keys don't make their way to layout unnecessarily
expect(Object.keys(layout).length).toBe(6);
});
flashList.unmount();
};
// vertical list
runCacheUpdateTest(false);
// horizontal list
runCacheUpdateTest(true);
});
it("expires if column count or padding changes", () => {
const flashList = mountFlashList();
const baseProps = flashList.instance.props;
expect(
flashList.instance.state.layoutProvider.updateProps({
...baseProps,
contentContainerStyle: { paddingTop: 10 },
}).hasExpired
).toBe(false);
expect(
flashList.instance.state.layoutProvider.updateProps({
...baseProps,
contentContainerStyle: { paddingBottom: 10 },
}).hasExpired
).toBe(false);
expect(
flashList.instance.state.layoutProvider.updateProps({
...baseProps,
contentContainerStyle: { paddingLeft: 10 },
}).hasExpired
).toBe(true);
flashList.instance.state.layoutProvider["_hasExpired"] = false;
expect(
flashList.instance.state.layoutProvider.updateProps({
...baseProps,
numColumns: 2,
}).hasExpired
).toBe(true);
});
});