UNPKG

@shopify/flash-list

Version:

FlashList is a more performant FlatList replacement

902 lines 38.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var react_1 = tslib_1.__importStar(require("react")); var react_native_1 = require("react-native"); require("@quilted/react-testing/matchers"); var recyclerlistview_1 = require("recyclerlistview"); var Warnings_1 = tslib_1.__importDefault(require("../errors/Warnings")); var AutoLayoutView_1 = tslib_1.__importDefault(require("../native/auto-layout/AutoLayoutView")); var CellContainer_1 = tslib_1.__importDefault(require("../native/cell-container/CellContainer")); var FlashListProps_1 = require("../FlashListProps"); var mountFlashList_1 = require("./helpers/mountFlashList"); jest.mock("../native/cell-container/CellContainer", function () { return jest.requireActual("../native/cell-container/CellContainer.ios.ts") .default; }); describe("FlashList", function () { beforeEach(function () { jest.clearAllMocks(); jest.useFakeTimers(); }); it("renders items", function () { var flashList = (0, mountFlashList_1.mountFlashList)(); expect(flashList).toContainReactComponent(react_native_1.Text, { children: "One" }); expect(flashList).toContainReactComponent(recyclerlistview_1.ProgressiveListView, { isHorizontal: false, }); }); it("sets ProgressiveListView to horizontal", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ horizontal: true }); expect(flashList).toContainReactComponent(recyclerlistview_1.ProgressiveListView, { isHorizontal: true, }); }); it("calls prepareForLayoutAnimationRender", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ keyExtractor: function (item) { return item; }, }); var warn = jest.spyOn(console, "warn").mockReturnValue(); var prepareForLayoutAnimationRender = jest.spyOn(flashList.instance.recyclerlistview_unsafe, "prepareForLayoutAnimationRender"); flashList.instance.prepareForLayoutAnimationRender(); expect(prepareForLayoutAnimationRender).toHaveBeenCalledTimes(1); expect(warn).not.toHaveBeenCalled(); }); it("sends a warning when prepareForLayoutAnimationRender without keyExtractor", function () { var flashList = (0, mountFlashList_1.mountFlashList)(); var warn = jest.spyOn(console, "warn").mockReturnValue(); var prepareForLayoutAnimationRender = jest.spyOn(flashList.instance.recyclerlistview_unsafe, "prepareForLayoutAnimationRender"); flashList.instance.prepareForLayoutAnimationRender(); expect(prepareForLayoutAnimationRender).not.toHaveBeenCalled(); expect(warn).toHaveBeenCalledWith(Warnings_1.default.missingKeyExtractor); }); it("disables initial scroll correction on recyclerlistview if initialScrollIndex is in first row", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ initialScrollIndex: 0, numColumns: 3 }); expect(flashList.instance["getUpdatedWindowCorrectionConfig"]() .applyToInitialOffset).toBe(false); flashList = (0, mountFlashList_1.mountFlashList)({ initialScrollIndex: 3, numColumns: 3 }); expect(flashList.instance["getUpdatedWindowCorrectionConfig"]() .applyToInitialOffset).toBe(true); flashList = (0, mountFlashList_1.mountFlashList)({ initialScrollIndex: 2, numColumns: 3 }); expect(flashList.instance["getUpdatedWindowCorrectionConfig"]() .applyToInitialOffset).toBe(false); }); it("assigns distance from window to window correction object", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ estimatedFirstItemOffset: 100 }); expect(flashList.instance["getUpdatedWindowCorrectionConfig"]().value.windowShift).toBe(-100); }); it("only forwards onBlankArea prop to AutoLayout when needed", function () { var _a; var flashList = (0, mountFlashList_1.mountFlashList)(); var autoLayoutView = (_a = flashList.find(AutoLayoutView_1.default)) === null || _a === void 0 ? void 0 : _a.instance; expect(autoLayoutView.props.onBlankAreaEvent).toBeUndefined(); flashList.setProps({ onBlankArea: function () { } }); expect(autoLayoutView.props.onBlankAreaEvent).not.toBeUndefined(); }); it("calls render item only when data of the items has changed", function () { var renderItemMock = jest.fn(function (_a) { var item = _a.item; return react_1.default.createElement(react_native_1.Text, null, item); }); var flashList = (0, mountFlashList_1.mountFlashList)({ renderItem: renderItemMock, data: ["One", "Two", "Three", "Four"], }); // because we have 4 data items expect(renderItemMock).toHaveBeenCalledTimes(4); // reset counter renderItemMock.mockClear(); // changes layout of all four items flashList.setProps({ numColumns: 2 }); // render item should be called 0 times because only layout of items would have changed expect(renderItemMock).toHaveBeenCalledTimes(0); flashList.unmount(); }); it("keeps component mounted based on prepareForLayoutAnimationRender being called", function () { // Tracks components being unmounted var unmountMock = jest.fn(); var Item = function (_a) { var text = _a.text; (0, react_1.useEffect)(function () { return unmountMock; }, []); return react_1.default.createElement(react_native_1.Text, null, text); }; var flashList = (0, mountFlashList_1.mountFlashList)({ keyExtractor: function (item) { return item; }, renderItem: function (_a) { var item = _a.item; return react_1.default.createElement(Item, { text: item }); }, data: ["One", "Two", "Three", "Four"], }); // Change data without prepareForLayoutAnimationRender flashList.setProps({ data: ["One", "Two", "Three", "Five"] }); expect(unmountMock).not.toHaveBeenCalled(); // Before changing data, we run prepareForLayoutAnimationRender. // This ensures component gets unmounted instead of being recycled to ensure layout animations run as expected. flashList.instance.prepareForLayoutAnimationRender(); flashList.setProps({ data: ["One", "Two", "Three", "Six"] }); expect(unmountMock).toHaveBeenCalledTimes(1); }); it("fires onLoad event", function () { var _a; var onLoadMock = jest.fn(); // empty list (0, mountFlashList_1.mountFlashList)({ data: [], onLoad: onLoadMock }); expect(onLoadMock).toHaveBeenCalledWith({ elapsedTimeInMs: expect.any(Number), }); onLoadMock.mockClear(); // non-empty list var flashList = (0, mountFlashList_1.mountFlashList)({ onLoad: onLoadMock }); (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance.onItemLayout(0); expect(onLoadMock).toHaveBeenCalledWith({ elapsedTimeInMs: expect.any(Number), }); }); it("loads an empty state", function () { var EmptyComponent = function () { return react_1.default.createElement(react_native_1.Text, null, "Empty"); }; var flashList = (0, mountFlashList_1.mountFlashList)({ data: [], ListEmptyComponent: EmptyComponent, }); expect(flashList).toContainReactComponent(EmptyComponent); }); it("loads header and footer in empty state", function () { var HeaderComponent = function () { return react_1.default.createElement(react_native_1.Text, null, "Empty"); }; var FooterComponent = function () { return react_1.default.createElement(react_native_1.Text, null, "Empty"); }; var flashList = (0, mountFlashList_1.mountFlashList)({ data: [], ListHeaderComponent: HeaderComponent, ListFooterComponent: FooterComponent, }); expect(flashList).toContainReactComponent(HeaderComponent); expect(flashList).toContainReactComponent(FooterComponent); }); it("reports layout changes to the layout provider", function () { var _a; var flashList = (0, mountFlashList_1.mountFlashList)(); var reportItemLayoutMock = jest.spyOn(flashList.instance.state.layoutProvider, "reportItemLayout"); (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance.onItemLayout(0); expect(reportItemLayoutMock).toHaveBeenCalledWith(0); flashList.unmount(); }); it("should prefer overrideItemLayout over estimate and average", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ overrideItemLayout: function (layout) { layout.size = 50; }, }); expect(flashList.instance.state.layoutProvider.averageItemSize).toBe(200); expect(flashList.instance.state .layoutProvider.getLayoutManager() .getLayouts()[0].height).toBe(50); }); it("should override span with overrideItemLayout", function () { var renderItemMock = jest.fn(function (_a) { var item = _a.item; return react_1.default.createElement(react_native_1.Text, null, item); }); (0, mountFlashList_1.mountFlashList)({ overrideItemLayout: function (layout) { layout.span = 2; }, numColumns: 2, estimatedItemSize: 300, renderItem: renderItemMock, }); expect(renderItemMock).toHaveBeenCalledTimes(3); renderItemMock.mockClear(); (0, mountFlashList_1.mountFlashList)({ overrideItemLayout: function (layout, _, index) { if (index > 2) { layout.span = 2; } }, data: new Array(20).fill(""), numColumns: 3, estimatedItemSize: 100, renderItem: renderItemMock, }); expect(renderItemMock).toHaveBeenCalledTimes(11); }); it("overrideItemLayout should consider 0 as a valid span", function () { var renderItemMock = jest.fn(function (_a) { var item = _a.item; return react_1.default.createElement(react_native_1.Text, null, item); }); (0, mountFlashList_1.mountFlashList)({ overrideItemLayout: function (layout, _, index) { if (index < 4) { layout.span = 0; } }, data: new Array(20).fill(""), numColumns: 2, renderItem: renderItemMock, }); expect(renderItemMock).toHaveBeenCalledTimes(14); }); it("reports onViewableItemsChanged for viewable items", function () { var _a, _b, _c, _d; var onViewableItemsChanged = jest.fn(); var onViewableItemsChangedForItemVisiblePercentThreshold = jest.fn(); var flashList = (0, mountFlashList_1.mountFlashList)({ estimatedItemSize: 300, viewabilityConfig: { minimumViewTime: 250, }, viewabilityConfigCallbackPairs: [ { onViewableItemsChanged: onViewableItemsChangedForItemVisiblePercentThreshold, viewabilityConfig: { itemVisiblePercentThreshold: 50, waitForInteraction: true, }, }, ], onViewableItemsChanged: onViewableItemsChanged, }); // onViewableItemsChanged is not called before 250 ms have elapsed expect(onViewableItemsChanged).not.toHaveBeenCalled(); jest.advanceTimersByTime(250); // Initial viewable items expect(onViewableItemsChanged).toHaveBeenCalledWith({ changed: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], }); expect(onViewableItemsChangedForItemVisiblePercentThreshold).not.toHaveBeenCalled(); // onViewableItemsChangedForItemVisiblePercentThreshold waits for interaction before reporting viewable items flashList.instance.recordInteraction(); jest.advanceTimersByTime(250); expect(onViewableItemsChangedForItemVisiblePercentThreshold).toHaveBeenCalledWith({ changed: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], }); onViewableItemsChanged.mockReset(); onViewableItemsChangedForItemVisiblePercentThreshold.mockReset(); // Mocking a scroll that will make the first item not visible and the last item visible jest .spyOn(flashList.instance.recyclerlistview_unsafe, "getCurrentScrollOffset") .mockReturnValue(200); (_b = (_a = flashList.instance.recyclerlistview_unsafe.props).onVisibleIndicesChanged) === null || _b === void 0 ? void 0 : _b.call(_a, [0, 1, 2, 3], [], []); (_d = (_c = flashList.instance.recyclerlistview_unsafe.props).onScroll) === null || _d === void 0 ? void 0 : _d.call(_c, { nativeEvent: { contentOffset: { x: 0, y: 200 } } }, 0, 200); jest.advanceTimersByTime(250); expect(onViewableItemsChanged).toHaveBeenCalledWith({ changed: [ { index: 3, isViewable: true, item: "Four", key: "3", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, { index: 3, isViewable: true, item: "Four", key: "3", timestamp: expect.any(Number), }, ], }); expect(onViewableItemsChangedForItemVisiblePercentThreshold).toHaveBeenCalledWith({ changed: [ { index: 3, isViewable: true, item: "Four", key: "3", timestamp: expect.any(Number), }, { index: 0, isViewable: false, item: "One", key: "0", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, { index: 3, isViewable: true, item: "Four", key: "3", timestamp: expect.any(Number), }, ], }); }); it("viewability reports take into account estimatedFirstItemOffset", function () { var onViewableItemsChanged = jest.fn(); (0, mountFlashList_1.mountFlashList)({ estimatedFirstItemOffset: 200, estimatedItemSize: 300, onViewableItemsChanged: onViewableItemsChanged, viewabilityConfig: { itemVisiblePercentThreshold: 50 }, }); // onViewableItemsChanged is not called before 250 ms have elapsed expect(onViewableItemsChanged).not.toHaveBeenCalled(); jest.advanceTimersByTime(250); // Initial viewable items expect(onViewableItemsChanged).toHaveBeenCalledWith({ changed: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, ], }); }); it("retrigers viewability events when recomputeViewableItems is called", function () { var onViewableItemsChanged = jest.fn(); var flashList = (0, mountFlashList_1.mountFlashList)({ estimatedItemSize: 300, onViewableItemsChanged: onViewableItemsChanged, viewabilityConfig: { itemVisiblePercentThreshold: 50, minimumViewTime: 0, }, }); // Initial viewable items expect(onViewableItemsChanged).toHaveBeenCalledWith({ changed: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], }); onViewableItemsChanged.mockClear(); flashList.instance.recomputeViewableItems(); expect(onViewableItemsChanged).toHaveBeenCalledWith({ changed: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], viewableItems: [ { index: 0, isViewable: true, item: "One", key: "0", timestamp: expect.any(Number), }, { index: 1, isViewable: true, item: "Two", key: "1", timestamp: expect.any(Number), }, { index: 2, isViewable: true, item: "Three", key: "2", timestamp: expect.any(Number), }, ], }); }); it("should not overlap header with sitcky index 0", function () { var HeaderComponent = function () { return react_1.default.createElement(react_native_1.Text, null, "Empty"); }; var flashList = (0, mountFlashList_1.mountFlashList)({ ListHeaderComponent: HeaderComponent, stickyHeaderIndices: [0], }); // If sticky renders there'll be 6 expect(flashList.findAll(react_native_1.Text).length).toBe(5); }); it("rerenders all items when layout manager changes", function () { var _a; var countMounts = 0; var currentId = 0; // Effect will be triggered once per mount var RenderComponent = function (_a) { var id = _a.id; (0, react_1.useEffect)(function () { countMounts++; }, [id]); return react_1.default.createElement(react_native_1.Text, null, "Test"); }; var renderItem = function () { return react_1.default.createElement(RenderComponent, { id: currentId }); }; var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(100).fill("1"), estimatedItemSize: 70, renderItem: renderItem, }); var scrollTo = function (y) { var _a; (_a = flashList.find(react_native_1.ScrollView)) === null || _a === void 0 ? void 0 : _a.trigger("onScroll", { nativeEvent: { contentOffset: { x: 0, y: y } }, }); }; // Mocking some scrolls scrollTo(200); scrollTo(400); scrollTo(600); scrollTo(3000); scrollTo(2000); // changing id will trigger effects if components rerender currentId = 1; // capturing current component count to check later var currentComponentCount = countMounts; // resetting count countMounts = 0; // items widths before layout manager change should be 400 flashList.findAll(CellContainer_1.default).forEach(function (cell) { if (cell.props.index !== -1) { expect(cell.instance.props.style.width).toBe(400); } }); // This will cause a layout manager change (_a = flashList.find(react_native_1.ScrollView)) === null || _a === void 0 ? void 0 : _a.trigger("onLayout", { nativeEvent: { layout: { height: 400, width: 900 } }, }); // If counts match, then all components were updated expect(countMounts).toBe(currentComponentCount); // items widths after layout manager change should be 900 flashList.findAll(CellContainer_1.default).forEach(function (cell) { if (cell.props.index !== -1) { expect(cell.instance.props.style.width).toBe(900); } }); flashList.unmount(); }); it("sends a warning when estimatedItemSize is not set", function () { var _a, _b, _c; var warn = jest.spyOn(console, "warn").mockReturnValue(); var flashList = (0, mountFlashList_1.mountFlashList)({ disableDefaultEstimatedItemSize: true, renderItem: function (_a) { var item = _a.item; return react_1.default.createElement(react_native_1.Text, { style: { height: 200 } }, item); }, }); var layoutData = flashList.instance.state.layoutProvider .getLayoutManager() .getLayouts(); layoutData[0].height = 100; layoutData[1].height = 200; layoutData[2].height = 300; (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance.onItemLayout(0); expect(flashList.instance.state.layoutProvider.averageItemSize).toBe(100); (_b = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _b === void 0 ? void 0 : _b.instance.onItemLayout(1); (_c = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _c === void 0 ? void 0 : _c.instance.onItemLayout(2); jest.advanceTimersByTime(1000); var averageItemSize = flashList.instance.state.layoutProvider.averageItemSize; expect(warn).toHaveBeenCalledWith(Warnings_1.default.estimatedItemSizeMissingWarning.replace("@size", averageItemSize.toString())); expect(flashList.instance.state.layoutProvider.averageItemSize).toBe(175); flashList.unmount(); }); it("clears size warning timeout on unmount", function () { var _a; var warn = jest.spyOn(console, "warn").mockReturnValue(); var flashList = (0, mountFlashList_1.mountFlashList)({ disableDefaultEstimatedItemSize: true, }); (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance.onItemLayout(0); flashList.unmount(); jest.advanceTimersByTime(1000); expect(warn).toBeCalledTimes(0); }); it("measure size of horizontal list when appropriate", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(1).fill("1"), horizontal: true, }); var forceUpdate = jest.spyOn(flashList.instance, "forceUpdate"); // should contain 1 actual text and 1 dummy on mount expect(flashList.findAll(react_native_1.Text).length).toBe(2); // Trigger onLoad flashList.instance["onItemLayout"](0); jest.advanceTimersByTime(600); expect(forceUpdate).toBeCalledTimes(1); // TODO: Investigate why forceUpdate isn't working in tests, forcing an update flashList.setProps({ overrideItemLayout: function () { } }); // After update the dummy should get removed expect(flashList.findAll(react_native_1.Text).length).toBe(1); flashList.unmount(); flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(1).fill("1"), horizontal: true, disableHorizontalListHeightMeasurement: true, }); // should contain 1 actual text as measurement is disabled expect(flashList.findAll(react_native_1.Text).length).toBe(1); flashList.unmount(); }); it("cancels post load setTimeout on unmount", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(1).fill("1"), horizontal: true, }); var forceUpdate = jest.spyOn(flashList.instance, "forceUpdate"); flashList.instance["onItemLayout"](0); flashList.unmount(); jest.advanceTimersByTime(600); expect(forceUpdate).toBeCalledTimes(0); }); it("uses 250 as draw distance on Android/iOS", function () { var _a, _b; var flashList = (0, mountFlashList_1.mountFlashList)(); (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance.onItemLayout(0); jest.advanceTimersByTime(1000); expect((_b = flashList .find(recyclerlistview_1.ProgressiveListView)) === null || _b === void 0 ? void 0 : _b.instance.getCurrentRenderAheadOffset()).toBe(250); flashList.unmount(); }); it("forwards correct renderTarget", function () { var _a, _b, _c, _d, _e; var renderItem = function (_a) { var target = _a.target; return react_1.default.createElement(react_native_1.Text, null, target); }; var flashList = (0, mountFlashList_1.mountFlashList)({ data: ["0"], stickyHeaderIndices: [0], renderItem: renderItem, }); expect((_b = (_a = flashList.find(react_native_1.Animated.View)) === null || _a === void 0 ? void 0 : _a.find(react_native_1.Text)) === null || _b === void 0 ? void 0 : _b.props.children).toBe(FlashListProps_1.RenderTargetOptions.StickyHeader); expect((_d = (_c = flashList.find(react_native_1.View)) === null || _c === void 0 ? void 0 : _c.find(react_native_1.Text)) === null || _d === void 0 ? void 0 : _d.props.children).toBe(FlashListProps_1.RenderTargetOptions.Cell); var flashListHorizontal = (0, mountFlashList_1.mountFlashList)({ renderItem: renderItem, horizontal: true, }); expect((_e = flashListHorizontal .findAllWhere(function (node) { var _a, _b; return ((_b = (_a = node === null || node === void 0 ? void 0 : node.props) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.opacity) === 0; })[0] .find(react_native_1.Text)) === null || _e === void 0 ? void 0 : _e.props.children).toBe("Measurement"); }); it("force updates items only when renderItem change", function () { var renderItem = jest.fn(function () { return react_1.default.createElement(react_native_1.Text, null, "Test"); }); var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(1).fill("1"), renderItem: renderItem, }); flashList.setProps({ data: new Array(1).fill("1") }); expect(renderItem).toBeCalledTimes(1); var newRenderItem = jest.fn(function () { return react_1.default.createElement(react_native_1.Text, null, "Test"); }); flashList.setProps({ data: new Array(1).fill("1"), renderItem: newRenderItem, }); expect(newRenderItem).toBeCalledTimes(1); }); it("forwards disableAutoLayout prop correctly", function () { var _a, _b; var flashList = (0, mountFlashList_1.mountFlashList)(); expect((_a = flashList.find(AutoLayoutView_1.default)) === null || _a === void 0 ? void 0 : _a.props.disableAutoLayout).toBe(undefined); flashList.setProps({ disableAutoLayout: true }); expect((_b = flashList.find(AutoLayoutView_1.default)) === null || _b === void 0 ? void 0 : _b.props.disableAutoLayout).toBe(true); }); it("computes correct scrollTo offset when view position is specified", function () { var _a; var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(40).fill(1).map(function (_, index) { return index.toString(); }), }); var plv = (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance; var scrollToOffset = jest.spyOn(plv, "scrollToOffset"); flashList.instance.scrollToIndex({ index: 10, viewPosition: 0.5 }); // Each item is 200px in height and to position it in the middle of the window (900 x 400), its offset needs to be // reduced by 350px. That gives us 1650. Other test cases follow the same logic. expect(scrollToOffset).toBeCalledWith(1650, 1650, false, true); flashList.instance.scrollToItem({ item: "10", viewPosition: 0.5, }); expect(scrollToOffset).toBeCalledWith(1650, 1650, false, true); flashList.setProps({ horizontal: true }); flashList.instance.scrollToItem({ item: "10", viewPosition: 0.5, }); expect(scrollToOffset).toBeCalledWith(1900, 1900, false, true); flashList.unmount(); }); it("computes correct scrollTo offset when view offset is specified", function () { var _a; var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(40).fill(1).map(function (_, index) { return index.toString(); }), }); var plv = (_a = flashList.find(recyclerlistview_1.ProgressiveListView)) === null || _a === void 0 ? void 0 : _a.instance; var scrollToOffset = jest.spyOn(plv, "scrollToOffset"); // Each item is 200px in height and to position it in the middle of the window (900 x 400), it's offset needs to be // reduced by 350px + 100px offset. That gives us 1550. Other test cases follow the same logic. flashList.instance.scrollToIndex({ index: 10, viewPosition: 0.5, viewOffset: 100, }); expect(scrollToOffset).toBeCalledWith(1550, 1550, false, true); flashList.setProps({ horizontal: true }); flashList.instance.scrollToItem({ item: "10", viewPosition: 0.5, viewOffset: 100, }); expect(scrollToOffset).toBeCalledWith(1800, 1800, false, true); flashList.unmount(); }); it("applies horizontal content container padding for vertical list", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ numColumns: 4, contentContainerStyle: { paddingHorizontal: 10 }, }); var hasLayoutItems = false; flashList.instance.state.layoutProvider .getLayoutManager() .getLayouts() .forEach(function (layout) { hasLayoutItems = true; expect(layout.width).toBe(95); }); expect(hasLayoutItems).toBe(true); flashList.unmount(); }); it("applies vertical content container padding for horizontal list", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ horizontal: true, contentContainerStyle: { paddingVertical: 10 }, }); var hasLayoutItems = false; flashList.instance.state.layoutProvider .getLayoutManager() .getLayouts() .forEach(function (layout) { hasLayoutItems = true; expect(layout.height).toBe(880); }); expect(hasLayoutItems).toBe(true); flashList.unmount(); }); it("warns if rendered size is too small but only when it remain small for a duration", function () { var flashList = (0, mountFlashList_1.mountFlashList)({ data: new Array(1).fill("1"), }); var warn = jest.spyOn(console, "warn").mockReturnValue(); var triggerLayout = function (height, time) { var _a; (_a = flashList.find(react_native_1.ScrollView)) === null || _a === void 0 ? void 0 : _a.trigger("onLayout", { nativeEvent: { layout: { height: height, width: 900 } }, }); jest.advanceTimersByTime(time); }; triggerLayout(0, 500); triggerLayout(100, 1000); triggerLayout(0, 1200); expect(warn).toHaveBeenCalledTimes(1); triggerLayout(100, 500); triggerLayout(0, 500); flashList.unmount(); jest.advanceTimersByTime(1200); expect(warn).toHaveBeenCalledTimes(1); }); }); //# sourceMappingURL=FlashList.test.js.map