UNPKG

react-infinite

Version:

A browser-ready efficient scrolling container based on UITableView

571 lines (499 loc) 20.7 kB
jest.dontMock('../src/react-infinite.jsx'); jest.dontMock('../src/computers/infinite_computer.js'); jest.dontMock('../src/computers/constant_infinite_computer.js'); jest.dontMock('../src/computers/array_infinite_computer.js'); jest.dontMock('../src/utils/binary_index_search.js'); jest.dontMock('lodash.isfinite'); jest.dontMock('lodash.isarray'); var React = require('react/addons'); var TestUtils = React.addons.TestUtils; var Infinite = require('../src/react-infinite.jsx'); var renderHelpers = { divGenerator: function(number, height) { number = number || 10; height = height || 100; var divArray = []; for (var i = 0; i < number; i++) { divArray.push(<div className={"test-div-" + i} key={i} style={{height: height}}/>) } return divArray; }, variableDivGenerator: function(heights) { var divArray = []; for (var i = 0; i < heights.length; i++) { divArray.push(<div className={"test-div-" + i} key={i} style={{height: heights[i]}}/>) } return divArray; } } describe('Rendering the React Infinite Component Wrapper', function() { it('throws an error when given only one child', function() { expect(function() { TestUtils.renderIntoDocument( <Infinite elementHeight={200} containerHeight={800} className={"root-scrollable-node"}> <div/> </Infinite> ); }).toThrow(); }); it('renders itself into the DOM with the correct container styles', function() { var infinite = TestUtils.renderIntoDocument( <Infinite elementHeight={200} containerHeight={800} className={"root-scrollable-node"}> <div/> <div/> </Infinite> ); var rootScrollable = TestUtils.findRenderedDOMComponentWithClass(infinite, 'root-scrollable-node') expect(rootScrollable.props.style.height).toEqual(800); expect(rootScrollable.props.style.overflowX).toEqual('hidden'); expect(rootScrollable.props.style.overflowY).toEqual('scroll'); }); it('applies the provided class name to the root node', function() { var infinite = TestUtils.renderIntoDocument( <Infinite elementHeight={200} containerHeight={800} className={"correct-class-name"}> <div/> <div/> </Infinite> ); expect(infinite.props.className).toEqual("correct-class-name"); }); }) describe('The Children of the React Infinite Component', function() { it ("renders its children when no hiding behavior is required", function() { var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={200} containerHeight={800} className={"correct-class-name"}> <div className={"test-div-0"}/> <div className={"test-div-1"}/> </Infinite> ); expect(rootNode.refs.topSpacer.props.style.height).toEqual("0px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("0px"); expect(TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-0')).not.toBeUndefined(); expect(TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-1')).not.toBeUndefined(); }); it ("renders its children when some DOM nodes are hidden", function() { var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} className={"correct-class-name"}> {renderHelpers.divGenerator(10, elementHeight)} </Infinite> ); expect(rootNode.refs.topSpacer.props.style.height).toEqual("0px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("800px"); // Why are six nodes rendered? Since we have not scrolled at // all, the extent that React Infinite will render is // preloadBatchSize + preloadAdditionalHeight below the container. // // preloadBatchSize defaults to the containerHeight, 800 pixels // preloadAdditionalHeight defaults to containerHeight / 2 pixels, 400 pixels // // Their sum is 1200 pixels, or 6 200-pixel elements. for (var i = 0; i < 6; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).not.toThrow(); } for (var i = 6; i < 10; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } }); it ("renders more children when preloadAdditionalHeight is increased beyond its default", function() { var elementHeight = 200; var infinite = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} preloadAdditionalHeight={1000} className={"correct-class-name"}> {renderHelpers.divGenerator(10, elementHeight)} </Infinite> ); // Why are six nodes rendered? Since we have not scrolled at // all, the extent that React Infinite will render is // preloadBatchSize + preloadAdditionalHeight below the container. // // preloadBatchSize defaults to the containerHeight, 800 pixels // preloadAdditionalHeight defaults to containerHeight / 2 pixels, 400 pixels // // Their sum is 1200 pixels, or 6 200-pixel elements. for (var i = 0; i < 7; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(infinite, 'test-div-' + i) }).not.toThrow(); } for (var i = 7; i < 10; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(infinite, 'test-div-' + i) }).toThrow(); } }); it ("renders more children when preloadBatchSize is increased beyond its default", function() { var elementHeight = 200; var infinite = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} preloadBatchSize={800} className={"correct-class-name"}> {renderHelpers.divGenerator(10, elementHeight)} </Infinite> ); // Why are six nodes rendered? Since we have not scrolled at // all, the extent that React Infinite will render is // preloadBatchSize + preloadAdditionalHeight below the container. // // preloadBatchSize defaults to the containerHeight, 800 pixels // preloadAdditionalHeight defaults to containerHeight / 2 pixels, 400 pixels // // Their sum is 1200 pixels, or 6 200-pixel elements. for (var i = 0; i < 8; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(infinite, 'test-div-' + i) }).not.toThrow(); } for (var i = 8; i < 10; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(infinite, 'test-div-' + i) }).toThrow(); } }); }) describe('The Scrolling Behavior of the Constant Height React Infinite Component', function() { it('hides visible elements when the user scrolls sufficiently', function() { var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 1500 } }); // Schematic // 0 pixels: start of topSpacer element // 400 pixels: windowTop, start of first displayed element // 1200 pixels: blockStart, start of the block that scrollTop of 1500 pixels is in // (the block size default is containerHeight / 2) // 1600 pixels: blockEnd, end of block that scrollTop of 1500 pixels is in // 2400 pixels: windowBottom, end of first displayed element // 4000 pixels: end of bottomSpacer element expect(rootNode.refs.topSpacer.props.style.height).toEqual("400px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("1600px"); // Above the batch and its preloadAdditionalHeight for (var i = 0; i < 2; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } // Within the batch and its preloadAdditionalHeight, top and bottom for (var i = 2; i < 12; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).not.toThrow(); } // Below the batch and its preloadAdditionalHeight for (var i = 12; i < 20; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } }); it("functions correctly at the end of its range", function() { var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); // The total scrollable height here is 4000 pixels TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 3600 } }); expect(rootNode.refs.topSpacer.props.style.height).toEqual("2800px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("0px"); // Above the batch and its preloadAdditionalHeight for (var i = 0; i < 14; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } // Within the batch and its preloadAdditionalHeight, top and bottom for (var i = 14; i < 20; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).not.toThrow(); } }) }); describe('The Behavior of the Variable Height React Infinite Component', function() { it('hides elements when the user has not yet scrolled', function() { // 20 40 200 300 350 500 525 550 575 600 725 805 880 900 1050 1300 1400 (16) var elementHeight = [20, 20, 160, 100, 50, 150, 25, 25, 25, 25, 125, 80, 75, 20, 150, 250, 100]; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={420} className={"correct-class-name"}> {renderHelpers.variableDivGenerator(elementHeight)} </Infinite> ); // Schematic // 0 pixels: start of topSpacer element, start of windowTop // 420 pixels: end of container // 630 pixels: end of windowBottom // 1400 pixels: end of bottomSpacer element expect(rootNode.refs.topSpacer.props.style.height).toEqual("0px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("675px"); // Within the batch and its preloadAdditionalHeight, top and bottom for (var i = 1; i < 11; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).not.toThrow(); } // Below the batch and its preloadAdditionalHeight for (var i = 11; i < 16; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } }); it('hides visible elements when the user scrolls sufficiently', function() { // 20 40 200 300 350 500 525 550 575 600 725 805 880 900 1050 1300 1400 (17) var elementHeight = [20, 20, 160, 100, 50, 150, 25, 25, 25, 25, 125, 80, 75, 20, 150, 250, 100]; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={400} className={"correct-class-name"}> {renderHelpers.variableDivGenerator(elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 700 } }); // Schematic // 0 pixels: start of topSpacer element // 200 pixels: windowTop, start of first displayed element // 600 pixels: blockStart, start of the block that the scrollTop of 700 pixels is in // 800 pixels: blockEnd, end of the block that the scrollTop of 700 pixels is in // 1200 pixels: windowBottom, end of displayed element // 1400 pixels: end of bottomSpacer element expect(rootNode.refs.topSpacer.props.style.height).toEqual("40px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("100px"); // Above the batch and its preloadAdditionalHeight for (var i = 0; i < 2; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } // Within the batch and its preloadAdditionalHeight, top and bottom for (var i = 2; i < 16; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).not.toThrow(); } // Below the batch and its preloadAdditionalHeight for (var i = 16; i < 17; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } }); it("functions correctly at the end of its range", function() { // 20 40 200 300 350 500 525 550 575 600 725 805 880 900 1050 1300 1400 (16) var elementHeight = [20, 20, 160, 100, 50, 150, 25, 25, 25, 25, 125, 80, 75, 20, 150, 250, 100]; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={400} className={"correct-class-name"}> {renderHelpers.variableDivGenerator(elementHeight)} </Infinite> ); // The total scrollable height here is 4000 pixels TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 1000 } }); // Schematic // 0 pixels: start of topSpacer element // 600 pixels: start of windowTop // 1000 pixels: start of block // 1400 pixels: end of block // 1400 pixels: end of windowBottom expect(rootNode.refs.topSpacer.props.style.height).toEqual("575px"); expect(rootNode.refs.bottomSpacer.props.style.height).toEqual("0px"); // Above the batch and its preloadAdditionalHeight for (var i = 0; i < 9; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).toThrow(); } // Within the batch and its preloadAdditionalHeight, top and bottom for (var i = 9; i < 15; i++) { expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'test-div-' + i) }).not.toThrow(); } }) }); describe("React Infinite's Infinite Scroll Capabilities", function() { it("infiniteLoadBeginBottomOffset does not always trigger infinite load on scroll", function() { var infiniteSpy = jasmine.createSpy('infiniteSpy'); var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} onInfiniteLoad={infiniteSpy} infiniteLoadBeginBottomOffset={1000} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 300 // past the bottom offset } }); expect(infiniteSpy).not.toHaveBeenCalled(); }); it("triggers the onInfiniteLoad function when scrolling past infiniteLoadBeginBottomOffset", function() { var infiniteSpy = jasmine.createSpy('infiniteSpy'); var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} onInfiniteLoad={infiniteSpy} infiniteLoadBeginBottomOffset={1000} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 3600 // past the bottom offset } }); expect(infiniteSpy).toHaveBeenCalled(); }); it("does not always display the loadingSpinnerDelegate", function() { var infiniteSpy = jasmine.createSpy('infiniteSpy'); var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} onInfiniteLoad={infiniteSpy} infiniteLoadBeginBottomOffset={1000} loadingSpinnerDelegate={<div className={"delegate-div"} />} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 100 // past the bottom offset } }); expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'delegate-div') }).toThrow(); }); it("displays the loadingSpinnerDelegate when isInfiniteLoading", function() { var infiniteSpy = jasmine.createSpy('infiniteSpy'); var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} onInfiniteLoad={infiniteSpy} infiniteLoadBeginBottomOffset={1000} loadingSpinnerDelegate={<div className={"delegate-div"} />} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 3600 // past the bottom offset } }); expect(function() { TestUtils.findRenderedDOMComponentWithClass(rootNode, 'delegate-div') }).not.toThrow(); }); }); describe("Maintaining React Infinite's internal scroll state", function() { it("has does not have pointer-events: none by default", function() { var infiniteSpy = jasmine.createSpy('infiniteSpy'); var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} timeScrollStateLastsForAfterUserScrolls={10000} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); var wrapper = rootNode.refs.smoothScrollingWrapper; expect(wrapper.props.style.pointerEvents).toBeUndefined(); }); it("has pointer-events: none upon scroll", function() { var infiniteSpy = jasmine.createSpy('infiniteSpy'); var elementHeight = 200; var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={elementHeight} containerHeight={800} timeScrollStateLastsForAfterUserScrolls={10000} className={"correct-class-name"}> {renderHelpers.divGenerator(20, elementHeight)} </Infinite> ); TestUtils.Simulate.scroll(rootNode.getDOMNode(), { target: { scrollTop: 100 // past the bottom offset } }); var wrapper = rootNode.refs.smoothScrollingWrapper; expect(wrapper.props.style.pointerEvents).toEqual('none'); }); }) describe("Rerendering React Infinite", function() { it("updates the infinite computer", function() { var rootNode = TestUtils.renderIntoDocument( <Infinite elementHeight={17} containerHeight={450} infiniteLoadBeginBottomOffset={1000} loadingSpinnerDelegate={<div className={"delegate-div"} />} className={"correct-class-name"}> {renderHelpers.divGenerator(20, 17)} </Infinite> ); expect(rootNode.state.infiniteComputer.heightData).toEqual(17); expect(rootNode.state.infiniteComputer.numberOfChildren).toEqual(20); rootNode.setProps({ children: renderHelpers.divGenerator(74, 17) }); expect(rootNode.state.infiniteComputer.numberOfChildren).toEqual(74); rootNode.setProps({ elementHeight: [10, 20, 30] }); expect(rootNode.state.infiniteComputer.heightData).toEqual([10, 20, 30]); }); })