diff --git a/source/WindowScroller/WindowScroller.example.js b/source/WindowScroller/WindowScroller.example.js
index 592149c40..4374c12a9 100644
--- a/source/WindowScroller/WindowScroller.example.js
+++ b/source/WindowScroller/WindowScroller.example.js
@@ -82,35 +82,38 @@ export default class WindowScrollerExample extends PureComponent<{}, State> {
value={scrollToIndex || ''}
/>
-
-
- {({height, isScrolling, onChildScroll, scrollTop}) => (
+
+
+ {({height, isScrolling, childRef, onChildScroll, scrollTop}) => (
+
{({width}) => (
- {
- window.listEl = el;
- }}
- autoHeight
- className={styles.List}
- height={height}
- isScrolling={isScrolling}
- onScroll={onChildScroll}
- overscanRowCount={2}
- rowCount={list.size}
- rowHeight={30}
- rowRenderer={this._rowRenderer}
- scrollToIndex={scrollToIndex}
- scrollTop={scrollTop}
- width={width}
- />
+
+ {
+ window.listEl = el;
+ }}
+ autoHeight
+ className={styles.List}
+ height={height}
+ isScrolling={isScrolling}
+ onScroll={onChildScroll}
+ overscanRowCount={2}
+ rowCount={list.size}
+ rowHeight={30}
+ rowRenderer={this._rowRenderer}
+ scrollToIndex={scrollToIndex}
+ scrollTop={scrollTop}
+ width={width}
+ />
+
)}
- )}
-
-
+
+ )}
+
);
}
diff --git a/source/WindowScroller/WindowScroller.jest.js b/source/WindowScroller/WindowScroller.jest.js
index 12f63261d..adb7a815d 100644
--- a/source/WindowScroller/WindowScroller.jest.js
+++ b/source/WindowScroller/WindowScroller.jest.js
@@ -6,16 +6,6 @@ import ReactDOMServer from 'react-dom/server';
import {render} from '../TestUtils';
import WindowScroller, {IS_SCROLLING_TIMEOUT} from './WindowScroller';
-class ChildComponent extends React.Component {
- render() {
- const {scrollTop, isScrolling, height, width} = this.props;
-
- return (
- {`scrollTop:${scrollTop}, isScrolling:${isScrolling}, height:${height}, width:${width}`}
- );
- }
-}
-
function mockGetBoundingClientRectForHeader({
documentOffset = 0,
height,
@@ -33,19 +23,10 @@ function mockGetBoundingClientRectForHeader({
}));
}
-function getMarkup({headerElements, documentOffset, childRef, ...props} = {}) {
+function getMarkup({headerElements, documentOffset, renderFn, ...props} = {}) {
const windowScroller = (
- {({width, height, isScrolling, onChildScroll, scrollTop}) => (
-
- )}
+ {params => {renderFn && renderFn(params)}
}
);
@@ -101,6 +82,26 @@ describe('WindowScroller', () => {
expect(component._positionFromLeft).toEqual(left);
});
+ it('should allow passing child element with childRef of children function param', () => {
+ const scrollElement = document.createElement('div');
+ scrollElement.scrollTop = 100;
+ scrollElement.scrollLeft = 150;
+ scrollElement.getBoundingClientRect = () => ({
+ top: 200,
+ left: 250,
+ });
+ const child = document.createElement('div');
+ child.getBoundingClientRect = () => ({
+ top: 300,
+ left: 350,
+ });
+ const renderFn = jest.fn();
+ const component = render(getMarkup({scrollElement, renderFn}));
+ renderFn.mock.calls[0][0].childRef(child);
+ expect(component._positionFromTop).toEqual(300 + 100 - 200);
+ expect(component._positionFromLeft).toEqual(350 + 150 - 250);
+ });
+
// 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', () => {
render.unmount();
@@ -120,12 +121,16 @@ describe('WindowScroller', () => {
});
it('inherits the window height and passes it to child component', () => {
- const component = render(getMarkup());
- const rendered = findDOMNode(component);
+ const renderFn = jest.fn();
+ const component = render(getMarkup({renderFn}));
expect(component.state.height).toEqual(window.innerHeight);
expect(component.state.height).toEqual(500);
- expect(rendered.textContent).toContain('height:500');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ height: 500,
+ }),
+ );
});
it('should restore pointerEvents on body after IS_SCROLLING_TIMEOUT', async () => {
@@ -149,7 +154,7 @@ describe('WindowScroller', () => {
});
describe('onScroll', () => {
- it('should trigger callback when window scrolls', async done => {
+ it('should trigger callback when window scrolls', async () => {
const onScroll = jest.fn();
render(getMarkup({onScroll}));
@@ -175,16 +180,19 @@ describe('WindowScroller', () => {
scrollLeft: 2500,
scrollTop: 5000,
});
-
- done();
});
- it('should update :scrollTop when window is scrolled', async done => {
- const component = render(getMarkup());
+ it('should update :scrollTop when window is scrolled', async () => {
+ const renderFn = jest.fn();
+ const component = render(getMarkup({renderFn}));
const rendered = findDOMNode(component);
// Initial load of the component should have 0 scrollTop
- expect(rendered.textContent).toContain('scrollTop:0');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ scrollTop: 0,
+ }),
+ );
simulateWindowScroll({scrollY: 5000});
@@ -193,52 +201,80 @@ describe('WindowScroller', () => {
const componentScrollTop = window.scrollY - component._positionFromTop;
expect(component.state.scrollTop).toEqual(componentScrollTop);
- expect(rendered.textContent).toContain(`scrollTop:${componentScrollTop}`);
-
- done();
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ scrollTop: componentScrollTop,
+ }),
+ );
});
- it('should specify :isScrolling when scrolling and reset after scrolling', async done => {
- const component = render(getMarkup());
- const rendered = findDOMNode(component);
+ it('should specify :isScrolling when scrolling and reset after scrolling', async () => {
+ const renderFn = jest.fn();
+ render(getMarkup({renderFn}));
simulateWindowScroll({scrollY: 5000});
- expect(rendered.textContent).toContain('isScrolling:true');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: true,
+ }),
+ );
await new Promise(resolve => setTimeout(resolve, 250));
- expect(rendered.textContent).toContain('isScrolling:false');
-
- done();
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: false,
+ }),
+ );
});
it('should support a custom :scrollingResetTimeInterval prop', async () => {
- const component = render(
+ const renderFn = jest.fn();
+ render(
getMarkup({
scrollingResetTimeInterval: 500,
+ renderFn,
}),
);
- const rendered = findDOMNode(component);
-
- expect(rendered.textContent).toContain('isScrolling:false');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: false,
+ }),
+ );
simulateWindowScroll({scrollY: 5000});
- expect(rendered.textContent).toContain('isScrolling:true');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: true,
+ }),
+ );
await new Promise(resolve => setTimeout(resolve, 100));
- expect(rendered.textContent).toContain('isScrolling:true');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: true,
+ }),
+ );
await new Promise(resolve => setTimeout(resolve, 100));
- expect(rendered.textContent).toContain('isScrolling:true');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: true,
+ }),
+ );
await new Promise(resolve => setTimeout(resolve, 400));
- expect(rendered.textContent).toContain('isScrolling:false');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ isScrolling: false,
+ }),
+ );
});
});
@@ -254,19 +290,27 @@ describe('WindowScroller', () => {
});
it('should update height when window resizes', () => {
- const component = render(getMarkup());
- const rendered = findDOMNode(component);
+ const renderFn = jest.fn();
+ const component = render(getMarkup({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(rendered.textContent).toContain('height: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(rendered.textContent).toContain('height:1000');
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ height: 1000,
+ }),
+ );
});
});
@@ -356,46 +400,40 @@ describe('WindowScroller', () => {
});
it('should scroll the scrollElement (when it is window) the desired amount', () => {
- let windowScroller, childComponent;
+ const renderFn = jest.fn();
+ let windowScroller;
render(
getMarkup({
ref: ref => {
windowScroller = ref;
},
- childRef: ref => {
- childComponent = ref;
- },
+ renderFn,
}),
);
- childComponent.props.onScroll({scrollTop: 200});
+ 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', () => {
- let childComponent;
+ const renderFn = jest.fn();
- render(
- getMarkup({
- childRef: ref => {
- childComponent = ref;
- },
- }),
- );
+ render(getMarkup({renderFn}));
simulateWindowScroll({scrollY: 200});
window.scrollTo = jest.fn();
- childComponent.props.onScroll({scrollTop: 200});
+ 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', () => {
- let windowScroller, childComponent;
+ let windowScroller;
+ const renderFn = jest.fn();
const divEl = document.createElement('div');
render(
@@ -403,49 +441,48 @@ describe('WindowScroller', () => {
ref: ref => {
windowScroller = ref;
},
+ renderFn,
scrollElement: divEl,
- childRef: ref => {
- childComponent = ref;
- },
}),
);
- childComponent.props.onScroll({scrollTop: 200});
+ renderFn.mock.calls[0][0].onChildScroll({scrollTop: 200});
expect(divEl.scrollTop).toEqual(200 + windowScroller._positionFromTop);
});
it('should update own scrollTop', () => {
- let windowScroller, childComponent;
+ const renderFn = jest.fn();
- render(
- getMarkup({
- ref: ref => {
- windowScroller = ref;
- },
- childRef: ref => {
- childComponent = ref;
- },
- }),
- );
+ render(getMarkup({renderFn}));
- childComponent.props.onScroll({scrollTop: 200});
+ renderFn.mock.calls[0][0].onChildScroll({scrollTop: 200});
- expect(windowScroller.state.scrollTop).toEqual(200);
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({scrollTop: 200}),
+ );
});
});
describe('server-side rendering', () => {
it('should render content with default widths and heights initially', () => {
- const rendered = ReactDOMServer.renderToString(
+ const renderFn = jest.fn();
+
+ render(
getMarkup({
+ renderFn,
scrollElement: null,
serverHeight: 100,
serverWidth: 200,
}),
);
- expect(rendered).toContain('height:100');
- expect(rendered).toContain('width:200');
+
+ expect(renderFn).lastCalledWith(
+ expect.objectContaining({
+ height: 100,
+ width: 200,
+ }),
+ );
});
});
});
diff --git a/source/WindowScroller/WindowScroller.js b/source/WindowScroller/WindowScroller.js
index 4c041f665..4f8a742f9 100644
--- a/source/WindowScroller/WindowScroller.js
+++ b/source/WindowScroller/WindowScroller.js
@@ -21,7 +21,8 @@ type Props = {
* ({ height, isScrolling, scrollLeft, scrollTop, width }) => PropTypes.element
*/
children: (params: {
- onChildScroll: (params: {scrollTop: number}) => void,
+ onChildScroll: ({scrollTop: number}) => void,
+ childRef: (?Element) => void,
height: number,
isScrolling: boolean,
scrollLeft: number,
@@ -36,7 +37,8 @@ type Props = {
onScroll: (params: {scrollLeft: number, scrollTop: number}) => void,
/** Element to attach scroll event listeners. Defaults to window. */
- scrollElement: ?(typeof window | Element),
+ scrollElement: ?Element,
+
/**
* Wait this amount of time after the last scroll event before resetting child `pointer-events`.
*/
@@ -87,6 +89,7 @@ export default class WindowScroller extends React.PureComponent {
_positionFromTop = 0;
_positionFromLeft = 0;
_detectElementResize: DetectElementResize = createDetectElementResize();
+ _child: ?Element;
state = {
...getDimensions(this.props.scrollElement, this.props),
@@ -102,7 +105,7 @@ export default class WindowScroller extends React.PureComponent {
const {onResize} = this.props;
const {height, width} = this.state;
- const thisNode = ReactDOM.findDOMNode(this);
+ const thisNode = this._child || ReactDOM.findDOMNode(this);
if (thisNode instanceof Element && scrollElement) {
const offset = getPositionOffset(thisNode, scrollElement);
this._positionFromTop = offset.top;
@@ -170,6 +173,7 @@ export default class WindowScroller extends React.PureComponent {
return children({
onChildScroll: this._onChildScroll,
+ childRef: this._childRef,
height,
isScrolling,
scrollLeft,
@@ -178,6 +182,11 @@ export default class WindowScroller extends React.PureComponent {
});
}
+ _childRef = element => {
+ this._child = element;
+ this.updatePosition();
+ };
+
_onChildScroll = ({scrollTop}) => {
if (this.state.scrollTop === scrollTop) {
return;