├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── karma.conf.js ├── package.json ├── src ├── scroll-aware.jsx └── utils.js └── test └── scrollaware-test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-3", "react"], 3 | "plugins": [ 4 | "transform-class-properties", 5 | "transform-object-rest-spread", 6 | "transform-export-extensions" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': ['standard-deviation'] 3 | }; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | *.sublime-workspace 4 | 5 | node_modules 6 | lib 7 | 8 | # npm debug file 9 | npm-debug.log 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | CONTRIBUTING.md 3 | src 4 | test 5 | karma.conf.js 6 | karma.test.conf.js 7 | .babelrc 8 | .eslintignore 9 | .eslintrc.js 10 | .travis.yml 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '5' 5 | - '6' 6 | before_script: 7 | - export CHROME_BIN=chromium-browser 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master (unreleased) 2 | 3 | # 1.2.4 4 | 5 | - refactored test files 6 | 7 | # 1.2.2 8 | 9 | - improve source code comments 10 | - update Doc 11 | 12 | # 1.2.1 13 | 14 | - add Test 15 | - improve Doc 16 | 17 | ## 1.1.0 18 | 19 | - Remove on mount _handleScroll call 20 | 21 | ## 1.0.4 22 | 23 | - Update Doc 24 | 25 | ## 1.0.0 26 | 27 | - Initial release 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We love pull requests. Here's a quick guide: 2 | 3 | 1. Fork the repo. 4 | 2. Run the tests. We only take pull requests with passing tests, and it's great 5 | to know that you have a clean slate: `npm install && npm test`. 6 | 3. Add a test for your change. Only refactoring and documentation changes 7 | require no new tests. If you are adding functionality or fixing a bug, we 8 | need a test! 9 | 4. Make the test pass. 10 | 5. Push to your fork and submit a pull request. 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2016 bySabi Files 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-scrollaware (React Scroll-aware HoC) 2 | 3 | [![npm version](https://badge.fury.io/js/react-scrollaware.svg)](https://badge.fury.io/js/react-scrollaware) 4 | [![npm downloads](https://img.shields.io/npm/dm/react-scrollaware.svg?style=flat-square)](https://www.npmjs.com/package/react-scrollaware) 5 | [![Build Status](https://travis-ci.org/bySabi/react-scrollaware.svg?branch=master)](https://travis-ci.org/bySabi/react-scrollaware) 6 | [![bitHound Overall Score](https://www.bithound.io/github/bySabi/react-scrollaware/badges/score.svg)](https://www.bithound.io/github/bySabi/react-scrollaware) 7 | [![Donate](https://img.shields.io/badge/$-support-green.svg?style=flat-square)](https://paypal.me/bySabi/10) 8 | 9 | A React Higher-order Component for create `scroll-aware` wrapped components. Works in all containers that can scroll, including the `window` 10 | 11 | > The intended purpouse of this component is abstract a well tested scroll event handler pattern and build complex scroll behaviours on top of it. 12 | 13 | ## Installation 14 | 15 | ### npm 16 | 17 | ```bash 18 | npm install react-scrollaware --save 19 | ``` 20 | 21 | ### Dependencies 22 | * User should provide its own `React` and `React-DOM` package 23 | * on `npm 2` enviroments, package [fbjs](https://www.npmjs.com/package/fbjs) should be installed too: 24 | ```bash 25 | npm install fbjs --save 26 | ``` 27 | 28 | #### `fbjs` package 29 | [fbjs](https://www.npmjs.com/package/fbjs) is a collection of utility libraries created by React Team. It include useful modules like `warning` and `invariant` 30 | 31 | 32 | ## Usage 33 | 34 | ```javascript 35 | // scrolled-min.jsx 36 | import React from 'react'; 37 | import scrollAware from 'react-scrollaware'; 38 | 39 | export const ScrolledMinimal = scrollAware(class extends React.Component { 40 | _handleScroll(event) { 41 | console.log('scrolled: ', event); 42 | } 43 | 44 | render() { 45 | return ; 46 | } 47 | }) 48 | ``` 49 | 50 | ### What is need to know? 51 | 52 | * This example represent the minimal posible setup. Any wrapped scroll-aware component must have a `_handleScroll` class method. 53 | 54 | * Component´s `_handleScroll` will fired wherever ocurr a `scroll` event in the scrollable ancestor or get `resized` 55 | 56 | * Filtering scroll events relay on wrapped component. 57 | 58 | 59 | ## Prop types 60 | ```javascript 61 | propTypes: { 62 | /** 63 | * Scrollable Ancestor - A custom ancestor is useful in cases where 64 | * you do not want the immediate scrollable ancestor or `window` to be 65 | * the container. 66 | */ 67 | scrollableAncestor: PropTypes.any, 68 | 69 | /** 70 | * The `throttleHandler` prop provides a function that throttle the internal 71 | * scroll handler to increase performance. 72 | * See the section on "Throttling" for details on how to use it. 73 | */ 74 | throttleHandler: PropTypes.func, 75 | 76 | /** 77 | * react-scrollaware by default callback '_handleScroll' class method of wrapped 78 | * component wherever a 'scroll' or 'resize' event occur. 79 | * The `handleScroll` prop provides a different class method name to 80 | * wrapped component event handler. 81 | */ 82 | handleScroll: PropTypes.string 83 | } 84 | ``` 85 | 86 | ## Containing elements and `scrollableAncestor` 87 | `react-scrollaware` attach the event handler to first scrollable ancestor of the wrapped component. 88 | 89 | If that algorithm doesn't work for your use case, then you might find the 90 | `scrollableAncestor` prop useful. It allows you to specify what the scrollable 91 | ancestor is. Pass a node as that prop, and the `react-scrollaware` will use the scroll 92 | position of *that* node, rather than its first scrollable ancestor. 93 | 94 | ### Example Usage 95 | 96 | Sometimes, scroll-aware components that are deeply nested in the DOM tree may need to track the scroll position of the page as a whole. If you want to be sure that no other scrollable ancestor is used (since, once again, the first scrollable ancestor is what the library will use by default), then you can explicitly set the scrollableAncestor to be the window to ensure that no other element is used. 97 | 98 | This might look something like: 99 | 100 | ```javascript 101 | 102 | ``` 103 | 104 | ## Throttling 105 | By default, `react-scrollaware` will trigger on every scroll event. In most cases, this 106 | works just fine. But if you find yourself wanting to tweak the scrolling 107 | performance, the `throttleHandler` prop can come in handy. You pass in a 108 | function that returns a different (throttled) version of the function passed 109 | in. Here's an example using 110 | [lodash.throttle](https://www.npmjs.com/package/lodash.throttle): 111 | 112 | ```jsx 113 | // scrolled-min-throttle.jsx 114 | import React from 'react'; 115 | import throttle from 'lodash.throttle'; 116 | import { ScrolledMinimal } from './scrolled-min'; 117 | 118 | export function ScrolledMinimalThrottle200(props) { 119 | return React.createElement(ScrolledMinimal, { 120 | ...props, 121 | throttleHandler: (scrollHandler) => throttle(scrollHandler, 200) 122 | }); 123 | } 124 | 125 | ``` 126 | 127 | The argument passed in to the throttle handler function, `scrollHandler`, is 128 | `react-scrollaware` internal scroll handler. The `throttleHandler` is only invoked once 129 | during the lifetime of a `react-scrollaware` (when is mounted). 130 | 131 | To prevent errors coming from the fact that the scroll handler can be called 132 | after the `react-scrollaware` is unmounted, it's a good idea to cancel the throttle 133 | function on unmount. If used throttle function have a `cancel` function, `react-scrollaware` will call it on component unmount. 134 | 135 | 136 | ## handleScroll prop 137 | Example Usage 138 | ```javascript 139 | // scrolled-min.jsx 140 | import React from 'react'; 141 | import scrollAware from 'react-scrollaware'; 142 | 143 | function ScrolledMinimal(props) { 144 | return React.createElement(scrollAware(class ScrolledMinimal extends React.Component { 145 | onScroll(event) { 146 | console.log('scrolled: ', event); 147 | } 148 | 149 | render() { 150 | return ; 151 | } 152 | }), 153 | { ...props, handleScroll: 'onScroll' } 154 | ); 155 | } 156 | ``` 157 | 158 | ## Troubleshooting 159 | If your component isn't working the way you expect it to. Clone and modify the project locally. 160 | - clone this repo 161 | - add `console.log` or breakpoints where you think it would be useful. 162 | - `npm link` in the react-scrollaware repo. 163 | - `npm link react-scrollaware` in your project. 164 | - if needed rebuild react-scrollaware module: `npm run build-npm` 165 | 166 | ### Local development advice 167 | This package have `peerDependencies` on 'React' and 'React-DOM' modules. Don't install `react` or `react-dom` on local `react-scrollaware` if they had been previously installed on your app project. Many local `react` and `react-dom` package instances conflict between them. One way to solved is: 168 | ```bash 169 | cd "current App project"/node_modules/react 170 | npm link 171 | 172 | cd react-scrollaware 173 | npm link react 174 | ``` 175 | Repeat steps for `react-dom` 176 | 177 | ## Example 178 | 179 | [react-scrolled](https://github.com/bySabi/react-scrolled) WIP project, is a library of scroll behaviours components and HoC. 180 | 181 | 182 | ## Credits 183 | 184 | This project is based on [React Waypoint](https://github.com/brigade/react-waypoint) team and contributors code. 185 | 186 | ## Contributing 187 | 188 | * Documentation improvement 189 | * Feel free to send any PR 190 | 191 | ## License 192 | 193 | [ISC][isc-license] 194 | 195 | [isc-license]:./LICENSE 196 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const carma = require('carma-tap-webpack'); 2 | 3 | module.exports = function(config) { 4 | carma(config); 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-scrollaware", 3 | "version": "1.2.4", 4 | "description": "A React HoC component for create scroll-aware components", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/bySabi/react-scrollaware.git" 8 | }, 9 | "homepage": "https://github.com/bySabi/react-scrollaware.git", 10 | "bugs": "https://github.com/bySabi/react-scrollaware/issues", 11 | "keywords": [ 12 | "hoc", 13 | "higher-order", 14 | "react", 15 | "react-hoc", 16 | "component", 17 | "react-component", 18 | "scroll", 19 | "scroll-aware" 20 | ], 21 | "main": "lib/scroll-aware.js", 22 | "scripts": { 23 | "build-npm": "rimraf lib && mkdirp lib && babel src/ -d lib/", 24 | "lint": "eslint . --ext .js,.jsx", 25 | "karma": "karma start", 26 | "testonly": "npm run karma", 27 | "test": "npm run lint && npm run testonly", 28 | "prepublish": "in-publish && npm run build-npm || not-in-publish" 29 | }, 30 | "peerDependencies": { 31 | "react": "15.x.x", 32 | "react-dom": "15.x.x", 33 | "fbjs": "*" 34 | }, 35 | "devDependencies": { 36 | "babel-cli": "6.x.x", 37 | "babel-preset-es2015": "6.x.x", 38 | "babel-preset-react": "6.x.x", 39 | "babel-preset-stage-3": "6.x.x", 40 | "babel-plugin-transform-class-properties": "6.x.x", 41 | "babel-plugin-transform-object-rest-spread": "6.x.x", 42 | "babel-plugin-transform-export-extensions": "6.x.x", 43 | "eslint": "3.x.x", 44 | "eslint-config-standard-deviation": "1.x.x", 45 | "eslint-modules-standard-deviation": "1.x.x", 46 | "lodash.throttle": "4.x.x", 47 | "karma": "1.x.x", 48 | "carma-tap-webpack": "1.x.x", 49 | "webpack": "^2.1.0-beta", 50 | "testdouble": "1.x.x", 51 | "enzyme": "2.x.x", 52 | "fbjs": "*", 53 | "react": "15.x.x", 54 | "react-addons-test-utils": "15.x.x", 55 | "react-dom": "15.x.x", 56 | "in-publish": "2.x.x", 57 | "rimraf": "2.x.x", 58 | "mkdirp": "*" 59 | }, 60 | "author": "bySabi Files", 61 | "license": "ISC" 62 | } 63 | -------------------------------------------------------------------------------- /src/scroll-aware.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { findScrollableAncestor, getWindow } from './utils'; 4 | import warning from 'fbjs/lib/warning'; 5 | 6 | /** 7 | * @param {function} Component 8 | * Calls a function when you scroll to the element. 9 | * @return {function} 10 | */ 11 | export default function(Component) { 12 | return class _scrollAware extends React.Component { 13 | static propTypes = { 14 | scrollableAncestor: PropTypes.any, 15 | throttleHandler: PropTypes.func, 16 | handleScroll: PropTypes.string 17 | } 18 | 19 | static defaultProps = { 20 | throttleHandler(handler) { 21 | return handler; 22 | }, 23 | handleScroll: '_handleScroll' 24 | } 25 | 26 | state = { 27 | scrollableAncestor: null 28 | } 29 | 30 | _componentHandleScroll = (event) => { 31 | if (this.Component.type.prototype[this.props.handleScroll]) { 32 | this._componentHandleScroll = this.Component.type.prototype[this.props.handleScroll].bind(this.Component); 33 | this._componentHandleScroll(event); 34 | } 35 | warning(this.Component.type.prototype[this.props.handleScroll], `[scrollAware]: Returned Component instance does not have a "${this.props.handleScroll}" class method`); 36 | } 37 | 38 | _handleScroll(event) { 39 | this._componentHandleScroll(event); 40 | } 41 | 42 | componentDidMount() { 43 | if (!getWindow()) { 44 | return; 45 | } 46 | 47 | this._handleScroll = this.props.throttleHandler(this._handleScroll.bind(this)); 48 | this.scrollableAncestor = this.props.scrollableAncestor || 49 | findScrollableAncestor(ReactDOM.findDOMNode(this)); 50 | this.scrollableAncestor.addEventListener('scroll', this._handleScroll); 51 | window.addEventListener('resize', this._handleScroll); 52 | 53 | this.setState({ scrollableAncestor: this.scrollableAncestor }); 54 | } 55 | 56 | componentDidUpdate() { 57 | if (!getWindow()) { 58 | return; 59 | } 60 | 61 | // The element may have moved. 62 | this._handleScroll(null); 63 | } 64 | 65 | componentWillUnmount() { 66 | if (!getWindow()) { 67 | return; 68 | } 69 | 70 | // At the time of unmounting, the scrollable ancestor might no longer 71 | // exist. Guarding against this prevents the following error: 72 | // Cannot read property 'removeEventListener' of undefined 73 | this.scrollableAncestor && 74 | this.scrollableAncestor.removeEventListener('scroll', this._handleScroll); 75 | window.removeEventListener('resize', this._handleScroll); 76 | 77 | // cancel throttle function if possible 78 | this.props.throttleHandler.cancel && 79 | this.props.throttleHandler.cancel.call(this); 80 | } 81 | 82 | // Every wrapped component, one start, must be rendered two times cause their 83 | // rendered DOM node is needed for find an scrollable ancestor container 84 | render() { 85 | const { scrollableAncestor, throttleHandler, handleScroll, ...props } = this.props; // eslint-disable-line no-unused-vars 86 | this.Component = React.createElement(Component, 87 | { ...props, scrollableAncestor: this.state.scrollableAncestor }); 88 | return this.Component; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function getWindow() { 2 | return typeof window !== 'undefined'; 3 | } 4 | 5 | /** 6 | * Traverses up the DOM to find an ancestor container which has an overflow 7 | * style that allows for scrolling. 8 | * 9 | * @return {Object} the closest ancestor element with an overflow style that 10 | * allows for scrolling. If none is found, the `window` object is returned 11 | * as a fallback. 12 | */ 13 | export function findScrollableAncestor(node) { 14 | while (node.parentNode) { 15 | node = node.parentNode; 16 | 17 | if (node === document) { 18 | // This particular node does not have a computed style. 19 | continue; 20 | } 21 | 22 | if (node === document.documentElement) { 23 | // This particular node does not have a scroll bar, 24 | // it uses the window. 25 | continue; 26 | } 27 | 28 | const style = window.getComputedStyle(node); 29 | const overflowY = style.getPropertyValue('overflow-y') || 30 | style.getPropertyValue('overflow'); 31 | 32 | if (overflowY === 'auto' || overflowY === 'scroll') { 33 | return node; 34 | } 35 | } 36 | 37 | // A scrollable ancestor element was not found, 38 | // which means that we need to do stuff on window. 39 | return window; 40 | } 41 | -------------------------------------------------------------------------------- /test/scrollaware-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import test from 'tape'; 3 | import td from 'testdouble'; 4 | import { mount } from 'enzyme'; 5 | import throttle from 'lodash.throttle'; 6 | import scrollAware from '../src/scroll-aware'; 7 | 8 | // 'component' mounted attached to div container appended to 9 | // document 'body' ensure it's scrollable 10 | const mountAttached = function(component) { 11 | const div = document.createElement('div'); 12 | document.body.appendChild(div); 13 | const renderedComponent = mount(component, { attachTo: div }); 14 | return renderedComponent; 15 | }; 16 | 17 | const scrollNodeTo = function(node, scrollTop) { 18 | if (node === window) { 19 | window.scroll(0, scrollTop); 20 | } else { 21 | node.scrollTop = scrollTop; 22 | } 23 | const event = document.createEvent('Event'); 24 | event.initEvent('scroll', false, false); 25 | node.dispatchEvent(event); 26 | }; 27 | 28 | test('', t => { 29 | t.test('rendered html', t => { 30 | let wrapper; 31 | t.test('setup', t => { 32 | const ScrolledTest = scrollAware(class extends React.Component { 33 | _handleScroll() {} 34 | render() { 35 | return ; 36 | } 37 | }); 38 | wrapper = mount(); 39 | t.end(); 40 | }); 41 | 42 | t.test('rendered html', t => { 43 | t.equal(wrapper.html(), ''); 44 | t.end(); 45 | }); 46 | 47 | t.test('tearDown', t => { 48 | wrapper.unmount(); 49 | t.end(); 50 | }); 51 | t.end(); 52 | }); 53 | 54 | t.test('wrapped rendered 2 times', t => { 55 | let wrapper; 56 | let _render; 57 | t.test('setup', t => { 58 | _render = td.function('_render'); 59 | const ScrolledTest = scrollAware(class extends React.Component { 60 | _handleScroll() {} 61 | render() { 62 | _render(); 63 | return ; 64 | } 65 | }); 66 | wrapper = mount(); 67 | t.end(); 68 | }); 69 | 70 | t.test('wrapped rendered 2 times', t => { 71 | t.equal(td.explain(_render).callCount, 2); 72 | t.end(); 73 | }); 74 | 75 | t.test('tearDown', t => { 76 | wrapper.unmount(); 77 | td.reset(); 78 | t.end(); 79 | }); 80 | t.end(); 81 | }); 82 | 83 | t.test('handleScroll prop', t => { 84 | let wrapper; 85 | let ScrolledTest; 86 | let _customHandleScroll; 87 | t.test('setup', t => { 88 | _customHandleScroll = td.function('_customHandleScroll'); 89 | ScrolledTest = scrollAware(class extends React.Component { 90 | _customHandleScroll(event) { 91 | _customHandleScroll(event); 92 | } 93 | render() { 94 | return ; 95 | } 96 | }); 97 | wrapper = mount(); 98 | t.end(); 99 | }); 100 | 101 | t.test('handleScroll prop', t => { 102 | const explain = td.explain(_customHandleScroll); 103 | // First generated event come from component move/update and is unmanaged, always occur. 104 | t.equal(explain.callCount, 1); 105 | t.end(); 106 | }); 107 | 108 | t.test('tearDown', t => { 109 | wrapper.unmount(); 110 | td.reset(); 111 | t.end(); 112 | }); 113 | t.end(); 114 | }); 115 | 116 | t.test('captured scroll event', t => { 117 | let wrapper; 118 | let _handleScroll; 119 | t.test('setup', t => { 120 | _handleScroll = td.function('_handleScroll'); 121 | const ScrolledTest = scrollAware(class extends React.Component { 122 | _handleScroll(event) { 123 | _handleScroll(event); 124 | } 125 | render() { 126 | return ; 127 | } 128 | }); 129 | 130 | const parentStyle = { 131 | height: 100, // parentHeight 132 | overflow: 'auto', 133 | position: 'relative', 134 | width: 100, 135 | margin: 10 // Normalize the space above the viewport 136 | } 137 | wrapper = mountAttached( 138 |
139 |
140 | 141 |
142 |
143 | ); 144 | t.end(); 145 | }); 146 | 147 | t.test('captured scroll event', t => { 148 | // First generated event come from component move/update and is unmanaged, always occur. 149 | // Create 3 scroll events. 150 | scrollNodeTo(wrapper.instance(), 1); // enzyme API instance() return the 'DOM node' 151 | scrollNodeTo(wrapper.instance(), 1); 152 | scrollNodeTo(wrapper.instance(), 1); 153 | t.equal(td.explain(_handleScroll).callCount, 4); 154 | t.end(); 155 | }); 156 | 157 | t.test('tearDown', t => { 158 | wrapper.unmount(); 159 | td.reset(); 160 | scrollNodeTo(window, 0); 161 | t.end(); 162 | }); 163 | t.end(); 164 | }); 165 | 166 | t.test('throttleHandler prop', t => { 167 | let wrapper; 168 | let _handleScroll; 169 | t.test('setup', t => { 170 | _handleScroll = td.function('_handleScroll'); 171 | const ScrolledTest = scrollAware(class extends React.Component { 172 | _handleScroll(event) { 173 | _handleScroll(event); 174 | } 175 | render() { 176 | return ; 177 | } 178 | }); 179 | 180 | const parentStyle = { 181 | height: 100, // parentHeight 182 | overflow: 'auto', 183 | position: 'relative', 184 | width: 100, 185 | margin: 10 // Normalize the space above the viewport. 186 | } 187 | wrapper = mountAttached( 188 |
189 |
190 | throttle(scrollHandler, 100)} /> 191 |
192 |
193 | ); 194 | t.end(); 195 | }); 196 | 197 | t.test('throttleHandler prop captured scroll event', t => { 198 | // First generated event come from component move/update and is unmanaged, always occur. 199 | // Create 1 scroll events. 200 | scrollNodeTo(wrapper.instance(), 1); // first scroll event is ignored due throttle delay 201 | t.equal(td.explain(_handleScroll).callCount, 1); 202 | t.end(); 203 | }); 204 | 205 | t.test('tearDown', t => { 206 | wrapper.unmount(); 207 | td.reset(); 208 | scrollNodeTo(window, 0); 209 | t.end(); 210 | }); 211 | t.end(); 212 | }); 213 | 214 | t.test('scrollableAncestor prop', t => { 215 | let wrapper; 216 | let scrollableAncestor; 217 | t.test('setup', t => { 218 | scrollableAncestor = td.function('scrollableAncestor'); 219 | const ScrolledTest = scrollAware(class extends React.Component { 220 | _handleScroll() {} 221 | render() { 222 | scrollableAncestor(this.props); 223 | return ; 224 | } 225 | }); 226 | wrapper = mount(); 227 | t.end(); 228 | }); 229 | 230 | t.test('scrollableAncestor prop', t => { 231 | const explain = td.explain(scrollableAncestor); 232 | const first = explain.calls[0].args[0].scrollableAncestor; 233 | const second = explain.calls[1].args[0].scrollableAncestor; 234 | t.equal(first, null); // First render always pass scrollableAncestor 'null' 235 | t.equal(second, window); 236 | t.end(); 237 | }); 238 | 239 | t.test('tearDown', t => { 240 | wrapper.unmount(); 241 | td.reset(); 242 | t.end(); 243 | }); 244 | t.end(); 245 | }); 246 | t.end(); 247 | }); 248 | --------------------------------------------------------------------------------