UNPKG

react-virtualized

Version:

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

530 lines (522 loc) 18.7 kB
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; var _excluded = ["headerElements", "documentOffset", "renderFn"]; import _regeneratorRuntime from "@babel/runtime/regenerator"; import * as React from 'react'; import { findDOMNode } from 'react-dom'; import { render } from '../TestUtils'; import WindowScroller, { IS_SCROLLING_TIMEOUT } from './WindowScroller'; function mockGetBoundingClientRectForHeader(_ref) { var _ref$documentOffset = _ref.documentOffset, documentOffset = _ref$documentOffset === void 0 ? 0 : _ref$documentOffset, height = _ref.height, width = _ref.width; // Mock the WindowScroller element and window separately // The only way to mock the former (before its created) is globally Element.prototype.getBoundingClientRect = jest.fn(function () { return { top: height, left: width }; }); document.documentElement.getBoundingClientRect = jest.fn(function () { return { top: documentOffset, left: documentOffset }; }); } function getMarkup() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, headerElements = _ref2.headerElements, documentOffset = _ref2.documentOffset, renderFn = _ref2.renderFn, props = _objectWithoutProperties(_ref2, _excluded); var windowScroller = /*#__PURE__*/React.createElement(WindowScroller, props, function (params) { return /*#__PURE__*/React.createElement("div", null, renderFn && renderFn(params)); }); // JSDome doesn't implement a working getBoundingClientRect() // But WindowScroller requires it mockGetBoundingClientRectForHeader({ documentOffset: documentOffset, height: headerElements ? headerElements.props.style.height : 0, width: headerElements ? headerElements.props.style.width : 0 }); if (headerElements) { return /*#__PURE__*/React.createElement("div", null, headerElements, windowScroller); } else { return windowScroller; } } function simulateWindowScroll(_ref3) { var _ref3$scrollX = _ref3.scrollX, scrollX = _ref3$scrollX === void 0 ? 0 : _ref3$scrollX, _ref3$scrollY = _ref3.scrollY, scrollY = _ref3$scrollY === void 0 ? 0 : _ref3$scrollY; document.body.style.height = '10000px'; window.scrollX = scrollX; window.scrollY = scrollY; document.dispatchEvent(new window.Event('scroll', { bubbles: true })); document.body.style.height = ''; } function simulateWindowResize(_ref4) { var _ref4$height = _ref4.height, height = _ref4$height === void 0 ? 0 : _ref4$height, _ref4$width = _ref4.width, width = _ref4$width === void 0 ? 0 : _ref4$width; window.innerHeight = height; window.innerWidth = width; document.dispatchEvent(new window.Event('resize', { bubbles: true })); } describe('WindowScroller', function () { // Set default window height and scroll position between tests beforeEach(function () { window.scrollY = 0; window.scrollX = 0; window.innerHeight = 500; window.innerWidth = 500; }); // Starts updating scrollTop only when the top position is reached it('should have correct top and left properties to be defined on :_positionFromTop and :_positionFromLeft', function () { var component = render(getMarkup()); var rendered = findDOMNode(component); var _rendered$getBounding = rendered.getBoundingClientRect(), top = _rendered$getBounding.top, left = _rendered$getBounding.left; expect(component._positionFromTop).toEqual(top); expect(component._positionFromLeft).toEqual(left); }); it('should allow passing child element with registerChild of children function param', function () { var scrollElement = document.createElement('div'); scrollElement.scrollTop = 100; scrollElement.scrollLeft = 150; scrollElement.getBoundingClientRect = function () { return { top: 200, left: 250 }; }; var child = document.createElement('div'); child.getBoundingClientRect = function () { return { top: 300, left: 350 }; }; var renderFn = jest.fn(); var component = render(getMarkup({ scrollElement: scrollElement, renderFn: renderFn })); renderFn.mock.calls[0][0].registerChild(child); expect(component._positionFromTop).toEqual(300 + 100 - 200); expect(component._positionFromLeft).toEqual(350 + 150 - 250); }); it('should warn on passing non-element or not null', function () { var warnFn = jest.spyOn(console, 'warn'); var renderFn = jest.fn(); render(getMarkup({ renderFn: renderFn })); renderFn.mock.calls[0][0].registerChild(1); renderFn.mock.calls[0][0].registerChild(document.createElement('div')); renderFn.mock.calls[0][0].registerChild(null); expect(warnFn).toHaveBeenCalledTimes(1); warnFn.mockRestore(); }); // Test edge-case reported in bvaughn/react-virtualized/pull/346 it('should have correct top and left properties to be defined on :_positionFromTop and :_positionFromLeft if documentElement is scrolled', function () { render.unmount(); // Simulate scrolled documentElement var component = render(getMarkup({ documentOffset: -100 })); var rendered = findDOMNode(component); var _rendered$getBounding2 = rendered.getBoundingClientRect(), top = _rendered$getBounding2.top, left = _rendered$getBounding2.left; expect(component._positionFromTop).toEqual(top + 100); expect(component._positionFromLeft).toEqual(left + 100); // Reset override delete document.documentElement.getBoundingClientRect; }); it('inherits the window height and passes it to child component', function () { var renderFn = jest.fn(); var component = render(getMarkup({ renderFn: renderFn })); expect(component.state.height).toEqual(window.innerHeight); expect(component.state.height).toEqual(500); expect(renderFn).lastCalledWith(expect.objectContaining({ height: 500 })); }); it('should restore pointerEvents on body after IS_SCROLLING_TIMEOUT', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() { return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: render(getMarkup()); document.body.style.pointerEvents = 'all'; simulateWindowScroll({ scrollY: 5000 }); expect(document.body.style.pointerEvents).toEqual('none'); _context.next = 6; return new Promise(function (resolve) { return setTimeout(resolve, IS_SCROLLING_TIMEOUT + 100); }); case 6: expect(document.body.style.pointerEvents).toEqual('all'); case 7: case "end": return _context.stop(); } }, _callee); }))); it('should restore pointerEvents on body after unmount', function () { render(getMarkup()); document.body.style.pointerEvents = 'all'; simulateWindowScroll({ scrollY: 5000 }); expect(document.body.style.pointerEvents).toEqual('none'); render.unmount(); expect(document.body.style.pointerEvents).toEqual('all'); }); describe('onScroll', function () { it('should trigger callback when window scrolls', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2() { var onScroll; return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: onScroll = jest.fn(); render(getMarkup({ onScroll: onScroll })); simulateWindowScroll({ scrollY: 5000 }); // Allow scrolling timeout to complete so that the component computes state _context2.next = 5; return new Promise(function (resolve) { return setTimeout(resolve, 150); }); case 5: expect(onScroll).toHaveBeenCalledWith({ scrollLeft: 0, scrollTop: 5000 }); simulateWindowScroll({ scrollX: 2500, scrollY: 5000 }); // Allow scrolling timeout to complete so that the component computes state _context2.next = 9; return new Promise(function (resolve) { return setTimeout(resolve, 150); }); case 9: expect(onScroll).toHaveBeenCalledWith({ scrollLeft: 2500, scrollTop: 5000 }); case 10: case "end": return _context2.stop(); } }, _callee2); }))); it('should update :scrollTop when window is scrolled', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3() { var renderFn, component, componentScrollTop; return _regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: renderFn = jest.fn(); component = render(getMarkup({ renderFn: renderFn })); // Initial load of the component should have 0 scrollTop expect(renderFn).lastCalledWith(expect.objectContaining({ scrollTop: 0 })); simulateWindowScroll({ scrollY: 5000 }); // Allow scrolling timeout to complete so that the component computes state _context3.next = 6; return new Promise(function (resolve) { return setTimeout(resolve, 150); }); case 6: componentScrollTop = window.scrollY - component._positionFromTop; expect(component.state.scrollTop).toEqual(componentScrollTop); expect(renderFn).lastCalledWith(expect.objectContaining({ scrollTop: componentScrollTop })); case 9: case "end": return _context3.stop(); } }, _callee3); }))); it('should specify :isScrolling when scrolling and reset after scrolling', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4() { var renderFn; return _regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: renderFn = jest.fn(); render(getMarkup({ renderFn: renderFn })); simulateWindowScroll({ scrollY: 5000 }); expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: true })); _context4.next = 6; return new Promise(function (resolve) { return setTimeout(resolve, 250); }); case 6: expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: false })); case 7: case "end": return _context4.stop(); } }, _callee4); }))); it('should support a custom :scrollingResetTimeInterval prop', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee5() { var renderFn; return _regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) switch (_context5.prev = _context5.next) { case 0: renderFn = jest.fn(); render(getMarkup({ scrollingResetTimeInterval: 500, renderFn: renderFn })); expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: false })); simulateWindowScroll({ scrollY: 5000 }); expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: true })); _context5.next = 7; return new Promise(function (resolve) { return setTimeout(resolve, 100); }); case 7: expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: true })); _context5.next = 10; return new Promise(function (resolve) { return setTimeout(resolve, 100); }); case 10: expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: true })); _context5.next = 13; return new Promise(function (resolve) { return setTimeout(resolve, 400); }); case 13: expect(renderFn).lastCalledWith(expect.objectContaining({ isScrolling: false })); case 14: case "end": return _context5.stop(); } }, _callee5); }))); }); describe('onResize', function () { it('should trigger callback on init and when window resizes', function () { var resizeFn = jest.fn(); render(getMarkup({ onResize: resizeFn })); simulateWindowResize({ height: 1000, width: 1024 }); expect(resizeFn).toHaveBeenCalledTimes(1); expect(resizeFn).lastCalledWith({ height: 1000, width: 1024 }); }); it('should update height when window resizes', function () { var renderFn = jest.fn(); var component = render(getMarkup({ renderFn: renderFn })); // Initial load of the component should have the same window height = 500 expect(component.state.height).toEqual(window.innerHeight); expect(component.state.height).toEqual(500); expect(renderFn).lastCalledWith(expect.objectContaining({ height: 500 })); simulateWindowResize({ height: 1000 }); expect(component.state.height).toEqual(window.innerHeight); expect(component.state.height).toEqual(1000); expect(renderFn).lastCalledWith(expect.objectContaining({ height: 1000 })); }); }); describe('updatePosition', function () { it('should calculate the initial offset from the top of the page when mounted', function () { var windowScroller; render(getMarkup({ headerElements: /*#__PURE__*/React.createElement("div", { style: { height: 100 } }), ref: function ref(_ref10) { windowScroller = _ref10; } })); expect(windowScroller._positionFromTop).toBe(100); }); it('should recalculate the offset from the top when the window resizes', function () { var windowScroller; render(getMarkup({ headerElements: /*#__PURE__*/React.createElement("div", { id: "header", style: { height: 100, width: 150 } }), ref: function ref(_ref11) { windowScroller = _ref11; } })); expect(windowScroller._positionFromTop).toBe(100); expect(windowScroller._positionFromLeft).toBe(150); mockGetBoundingClientRectForHeader({ height: 200, width: 300 }); expect(windowScroller._positionFromTop).toBe(100); expect(windowScroller._positionFromLeft).toBe(150); simulateWindowResize({ height: 1000, width: 1000 }); expect(windowScroller._positionFromTop).toBe(200); expect(windowScroller._positionFromLeft).toBe(300); }); it('should recalculate the offset from the top if called externally', function () { var windowScroller; render(getMarkup({ headerElements: /*#__PURE__*/React.createElement("div", { id: "header", style: { height: 100, width: 150 } }), ref: function ref(_ref12) { windowScroller = _ref12; } })); expect(windowScroller._positionFromTop).toBe(100); expect(windowScroller._positionFromLeft).toBe(150); mockGetBoundingClientRectForHeader({ height: 200, width: 300 }); windowScroller.updatePosition(); expect(windowScroller._positionFromTop).toBe(200); expect(windowScroller._positionFromLeft).toBe(300); }); }); describe('when child scrolls', function () { var originalScrollTo; beforeEach(function () { originalScrollTo = window.scrollTo; window.scrollTo = function (scrollX, scrollY) { return simulateWindowScroll({ scrollX: scrollX, scrollY: scrollY }); }; }); afterEach(function () { window.scrollTo = originalScrollTo; render.unmount(); }); it('should scroll the scrollElement (when it is window) the desired amount', function () { var renderFn = jest.fn(); var windowScroller; render(getMarkup({ ref: function ref(_ref13) { windowScroller = _ref13; }, renderFn: renderFn })); renderFn.mock.calls[0][0].onChildScroll({ scrollTop: 200 }); expect(window.scrollY).toEqual(200 + windowScroller._positionFromTop); }); it('should not scroll the scrollElement if trying to scroll to where we already are', function () { var renderFn = jest.fn(); render(getMarkup({ renderFn: renderFn })); simulateWindowScroll({ scrollY: 200 }); window.scrollTo = jest.fn(); renderFn.mock.calls[0][0].onChildScroll({ scrollTop: 200 }); expect(window.scrollTo).not.toHaveBeenCalled(); }); it('should scroll the scrollElement (when it is an element) the desired amount', function () { var windowScroller; var renderFn = jest.fn(); var divEl = document.createElement('div'); render(getMarkup({ ref: function ref(_ref14) { windowScroller = _ref14; }, renderFn: renderFn, scrollElement: divEl })); renderFn.mock.calls[0][0].onChildScroll({ scrollTop: 200 }); expect(divEl.scrollTop).toEqual(200 + windowScroller._positionFromTop); }); it('should update own scrollTop', function () { var renderFn = jest.fn(); render(getMarkup({ renderFn: renderFn })); renderFn.mock.calls[0][0].onChildScroll({ scrollTop: 200 }); expect(renderFn).lastCalledWith(expect.objectContaining({ scrollTop: 200 })); }); }); });