UNPKG

react-virtualized

Version:

React components for efficiently rendering large, scrollable lists and tabular data

517 lines (513 loc) 21 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _reactDom = require("react-dom"); var _TestUtils = require("../TestUtils"); var _testUtils = require("react-dom/test-utils"); var _immutable = _interopRequireDefault(require("immutable")); var _List = _interopRequireDefault(require("./List")); var _Grid = require("../Grid"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } describe('List', function () { var array = []; for (var i = 0; i < 100; i++) { array.push("Name ".concat(i)); } var names = _immutable["default"].fromJS(array); // Override default behavior of overscanning by at least 1 (for accessibility) // Because it makes for simple tests below function overscanIndicesGetter(_ref) { var startIndex = _ref.startIndex, stopIndex = _ref.stopIndex; return { overscanStartIndex: startIndex, overscanStopIndex: stopIndex }; } function getMarkup() { var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; function rowRenderer(_ref2) { var index = _ref2.index, key = _ref2.key, style = _ref2.style; return /*#__PURE__*/React.createElement("div", { className: "listItem", key: key, style: style }, names.get(index)); } return /*#__PURE__*/React.createElement(_List["default"], (0, _extends2["default"])({ height: 100, overscanIndicesGetter: overscanIndicesGetter, overscanRowCount: 0, rowHeight: 10, rowCount: names.size, rowRenderer: rowRenderer, width: 100 }, props)); } describe('number of rendered children', function () { it('should render enough children to fill the view', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); expect(rendered.querySelectorAll('.listItem').length).toEqual(10); }); it('should not render more children than available if the list is not filled', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ rowCount: 5 }))); expect(rendered.querySelectorAll('.listItem').length).toEqual(5); }); }); describe('scrollToPosition', function () { it('should scroll to the top', function () { var instance = (0, _TestUtils.render)(getMarkup({ rowHeight: 10 })); instance.scrollToPosition(100); var rendered = (0, _reactDom.findDOMNode)(instance); expect(rendered.textContent).toContain('Name 10'); expect(rendered.textContent).toContain('Name 19'); }); }); /** Tests scrolling via initial props */ describe('scrollToIndex', function () { it('should scroll to the top', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 0 }))); expect(rendered.textContent).toContain('Name 0'); }); it('should scroll down to the middle', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 49 }))); // 100 items * 10 item height = 1,000 total item height // 10 items can be visible at a time and :scrollTop is initially 0, // So the minimum amount of scrolling leaves the 50th item at the bottom (just scrolled into view). expect(rendered.textContent).toContain('Name 49'); }); it('should scroll to the bottom', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 99 }))); // 100 height - 10 header = 90 available scroll space. // 100 items * 10 item height = 1,000 total item height // Target height for the last item then is 1000 - 90 expect(rendered.textContent).toContain('Name 99'); }); it('should scroll to the correct position for :scrollToAlignment "start"', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToAlignment: 'start', scrollToIndex: 49 }))); // 100 items * 10 item height = 1,000 total item height; 10 items can be visible at a time. expect(rendered.textContent).toContain('Name 49'); expect(rendered.textContent).toContain('Name 58'); }); it('should scroll to the correct position for :scrollToAlignment "end"', function () { (0, _TestUtils.render)(getMarkup({ scrollToIndex: 99 })); var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToAlignment: 'end', scrollToIndex: 49 }))); // 100 items * 10 item height = 1,000 total item height; 10 items can be visible at a time. expect(rendered.textContent).toContain('Name 40'); expect(rendered.textContent).toContain('Name 49'); }); it('should scroll to the correct position for :scrollToAlignment "center"', function () { (0, _TestUtils.render)(getMarkup({ scrollToIndex: 99 })); var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToAlignment: 'center', scrollToIndex: 49 }))); // 100 items * 10 item height = 1,000 total item height; 11 items can be visible at a time (the first and last item are only partially visible) expect(rendered.textContent).toContain('Name 44'); expect(rendered.textContent).toContain('Name 54'); }); }); describe('property updates', function () { it('should update :scrollToIndex position when :rowHeight changes', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 50 }))); expect(rendered.textContent).toContain('Name 50'); // Making rows taller pushes name off/beyond the scrolled area rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 50, rowHeight: 20 }))); expect(rendered.textContent).toContain('Name 50'); }); it('should update :scrollToIndex position when :height changes', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 50 }))); expect(rendered.textContent).toContain('Name 50'); // Making the list shorter leaves only room for 1 item rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 50, height: 20 }))); expect(rendered.textContent).toContain('Name 50'); }); it('should update :scrollToIndex position when :scrollToIndex changes', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); expect(rendered.textContent).not.toContain('Name 50'); rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 50 }))); expect(rendered.textContent).toContain('Name 50'); }); it('should update scroll position if size shrinks smaller than the current scroll', function () { (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 500 }))); (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ scrollToIndex: 500, rowCount: 10 }))); expect(rendered.textContent).toContain('Name 9'); }); }); describe('noRowsRenderer', function () { it('should call :noRowsRenderer if :rowCount is 0', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ noRowsRenderer: function noRowsRenderer() { return /*#__PURE__*/React.createElement("div", null, "No rows!"); }, rowCount: 0 }))); expect(rendered.textContent).toEqual('No rows!'); }); it('should render an empty body if :rowCount is 0 and there is no :noRowsRenderer', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ rowCount: 0 }))); expect(rendered.textContent).toEqual(''); }); }); describe('onRowsRendered', function () { it('should call :onRowsRendered if at least one row is rendered', function () { var startIndex, stopIndex; (0, _TestUtils.render)(getMarkup({ onRowsRendered: function onRowsRendered(params) { var _params; return _params = params, startIndex = _params.startIndex, stopIndex = _params.stopIndex, _params; } })); expect(startIndex).toEqual(0); expect(stopIndex).toEqual(9); }); it('should not call :onRowsRendered unless the start or stop indices have changed', function () { var numCalls = 0; var startIndex; var stopIndex; var onRowsRendered = function onRowsRendered(params) { startIndex = params.startIndex; stopIndex = params.stopIndex; numCalls++; }; (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ onRowsRendered: onRowsRendered }))); expect(numCalls).toEqual(1); expect(startIndex).toEqual(0); expect(stopIndex).toEqual(9); (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ onRowsRendered: onRowsRendered }))); expect(numCalls).toEqual(1); expect(startIndex).toEqual(0); expect(stopIndex).toEqual(9); }); it('should call :onRowsRendered if the start or stop indices have changed', function () { var numCalls = 0; var startIndex; var stopIndex; var onRowsRendered = function onRowsRendered(params) { startIndex = params.startIndex; stopIndex = params.stopIndex; numCalls++; }; (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ onRowsRendered: onRowsRendered }))); expect(numCalls).toEqual(1); expect(startIndex).toEqual(0); expect(stopIndex).toEqual(9); (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ height: 50, onRowsRendered: onRowsRendered }))); expect(numCalls).toEqual(2); expect(startIndex).toEqual(0); expect(stopIndex).toEqual(4); }); it('should not call :onRowsRendered if no rows are rendered', function () { var startIndex, stopIndex; (0, _TestUtils.render)(getMarkup({ height: 0, onRowsRendered: function onRowsRendered(params) { var _params2; return _params2 = params, startIndex = _params2.startIndex, stopIndex = _params2.stopIndex, _params2; } })); expect(startIndex).toEqual(undefined); expect(stopIndex).toEqual(undefined); }); }); describe(':scrollTop property', function () { it('should render correctly when an initial :scrollTop property is specified', function () { var startIndex, stopIndex; (0, _TestUtils.render)(getMarkup({ onRowsRendered: function onRowsRendered(params) { var _params3; return _params3 = params, startIndex = _params3.startIndex, stopIndex = _params3.stopIndex, _params3; }, scrollTop: 100 })); expect(startIndex).toEqual(10); expect(stopIndex).toEqual(19); }); it('should render correctly when :scrollTop property is updated', function () { var startIndex, stopIndex; (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ onRowsRendered: function onRowsRendered(params) { var _params4; return _params4 = params, startIndex = _params4.startIndex, stopIndex = _params4.stopIndex, _params4; } }))); expect(startIndex).toEqual(0); expect(stopIndex).toEqual(9); (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ onRowsRendered: function onRowsRendered(params) { var _params5; return _params5 = params, startIndex = _params5.startIndex, stopIndex = _params5.stopIndex, _params5; }, scrollTop: 100 }))); expect(startIndex).toEqual(10); expect(stopIndex).toEqual(19); }); }); describe('styles, classNames, and ids', function () { it('should use the expected global CSS classNames', function () { var node = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); expect(node.className).toContain('ReactVirtualized__List'); }); it('should use a custom :className if specified', function () { var node = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ className: 'foo' }))); expect(node.className).toContain('foo'); }); it('should use a custom :id if specified', function () { var node = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ id: 'bar' }))); expect(node.getAttribute('id')).toEqual('bar'); }); it('should use a custom :style if specified', function () { var style = { backgroundColor: 'red' }; var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ style: style }))); expect(rendered.style.backgroundColor).toEqual('red'); }); it('should set the width of a row to be 100% by default', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); var cell = rendered.querySelector('.listItem'); expect(cell.style.width).toEqual('100%'); }); }); describe('overscanRowCount', function () { it('should not overscan by default', function () { var mock = jest.fn(); mock.mockImplementation(overscanIndicesGetter); (0, _TestUtils.render)(getMarkup({ overscanIndicesGetter: mock })); expect(mock.mock.calls[0][0].overscanCellsCount).toEqual(0); expect(mock.mock.calls[1][0].overscanCellsCount).toEqual(0); }); it('should overscan the specified amount', function () { var mock = jest.fn(); mock.mockImplementation(overscanIndicesGetter); (0, _TestUtils.render)(getMarkup({ overscanIndicesGetter: mock, overscanRowCount: 10 })); expect(mock.mock.calls[0][0].overscanCellsCount).toEqual(0); expect(mock.mock.calls[1][0].overscanCellsCount).toEqual(10); }); }); describe('onScroll', function () { it('should trigger callback when component initially mounts', function () { var onScrollCalls = []; (0, _TestUtils.render)(getMarkup({ onScroll: function onScroll(params) { return onScrollCalls.push(params); } })); expect(onScrollCalls).toEqual([{ clientHeight: 100, scrollHeight: 1000, scrollTop: 0 }]); }); it('should trigger callback when component scrolls', function () { var onScrollCalls = []; var rendered = (0, _TestUtils.render)(getMarkup({ onScroll: function onScroll(params) { return onScrollCalls.push(params); } })); var target = { scrollLeft: 0, scrollTop: 100 }; rendered.Grid._scrollingContainer = target; // HACK to work around _onScroll target check _testUtils.Simulate.scroll((0, _reactDom.findDOMNode)(rendered), { target: target }); expect(onScrollCalls[onScrollCalls.length - 1]).toEqual({ clientHeight: 100, scrollHeight: 1000, scrollTop: 100 }); }); }); describe('measureAllRows', function () { it('should measure any unmeasured rows', function () { var rendered = (0, _TestUtils.render)(getMarkup({ estimatedRowSize: 15, height: 0, rowCount: 10, rowHeight: function rowHeight() { return 20; }, width: 0 })); expect(rendered.Grid.state.instanceProps.rowSizeAndPositionManager.getTotalSize()).toEqual(150); rendered.measureAllRows(); expect(rendered.Grid.state.instanceProps.rowSizeAndPositionManager.getTotalSize()).toEqual(200); }); }); describe('recomputeRowHeights', function () { it('should recompute row heights and other values when called', function () { var indices = []; var rowHeight = function rowHeight(_ref3) { var index = _ref3.index; indices.push(index); return 10; }; var component = (0, _TestUtils.render)(getMarkup({ rowHeight: rowHeight, rowCount: 50 })); indices.splice(0); component.recomputeRowHeights(); // Only the rows required to fill the current viewport will be rendered expect(indices[0]).toEqual(0); expect(indices[indices.length - 1]).toEqual(9); indices.splice(0); component.recomputeRowHeights(4); expect(indices[0]).toEqual(4); expect(indices[indices.length - 1]).toEqual(9); }); }); describe('forceUpdateGrid', function () { it('should refresh inner Grid content when called', function () { var marker = 'a'; function rowRenderer(_ref4) { var index = _ref4.index, key = _ref4.key, style = _ref4.style; return /*#__PURE__*/React.createElement("div", { key: key, style: style }, index, marker); } var component = (0, _TestUtils.render)(getMarkup({ rowRenderer: rowRenderer })); var node = (0, _reactDom.findDOMNode)(component); expect(node.textContent).toContain('1a'); marker = 'b'; component.forceUpdateGrid(); expect(node.textContent).toContain('1b'); }); }); describe('tabIndex', function () { it('should be focusable by default', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); expect(rendered.tabIndex).toEqual(0); }); it('should allow tabIndex to be overridden', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ tabIndex: -1 }))); expect(rendered.tabIndex).toEqual(-1); }); }); it('should pass the cellRenderer an :isVisible flag', function () { var rowRendererCalls = []; function rowRenderer(props) { rowRendererCalls.push(props); return null; } (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ height: 50, overscanIndicesGetter: _Grid.defaultOverscanIndicesGetter, overscanRowCount: 1, rowHeight: 50, rowRenderer: rowRenderer }))); expect(rowRendererCalls[0].isVisible).toEqual(true); expect(rowRendererCalls[1].isVisible).toEqual(false); }); it('should relay the Grid :parent param to the :rowRenderer', function () { var rowRenderer = jest.fn().mockReturnValue(null); (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup({ rowRenderer: rowRenderer }))); expect(rowRenderer.mock.calls[0][0].parent).not.toBeUndefined(); }); describe('pure', function () { it('should not re-render unless props have changed', function () { var rowRendererCalled = false; function rowRenderer(_ref5) { var index = _ref5.index, key = _ref5.key, style = _ref5.style; rowRendererCalled = true; return /*#__PURE__*/React.createElement("div", { key: key, style: style }, index); } var markup = getMarkup({ rowRenderer: rowRenderer }); (0, _TestUtils.render)(markup); expect(rowRendererCalled).toEqual(true); rowRendererCalled = false; (0, _TestUtils.render)(markup); expect(rowRendererCalled).toEqual(false); }); }); it('should set the width of the single-column inner Grid to auto', function () { var rendered = (0, _reactDom.findDOMNode)((0, _TestUtils.render)(getMarkup())); expect(rendered.querySelector('.ReactVirtualized__Grid__innerScrollContainer').style.width).toEqual('auto'); }); });