react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
530 lines (522 loc) • 18.7 kB
JavaScript
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
}));
});
});
});