Affix listener bug fix (#4756)

*  Affix listener bug fix

 + close #4755
 + close #4760
 + clearScrollEventListeners before setTargetEventListeners
 + add tests for throttle
 + append affix test case

*  genMockFn -> fn()
This commit is contained in:
陆离
2017-02-08 14:44:43 +08:00
committed by ddcat1115
parent f84dfbbaa9
commit 3789e0cbbb
5 changed files with 141 additions and 16 deletions
+29
View File
@@ -0,0 +1,29 @@
import throttleByAnimationFrame from '../throttleByAnimationFrame';
jest.useFakeTimers();
describe('Test utils function', () => {
it('throttle function should work', () => {
const callback = jest.fn();
const throttled = throttleByAnimationFrame(callback);
expect(callback).not.toBeCalled();
throttled();
throttled();
jest.runAllTimers();
expect(callback).toBeCalled();
expect(callback.mock.calls.length).toBe(1);
});
it('throttle function should be canceled', () => {
const callback = jest.fn();
const throttled = throttleByAnimationFrame(callback);
throttled();
throttled.cancel();
jest.runAllTimers();
expect(callback).not.toBeCalled();
});
});
+22 -1
View File
@@ -1,3 +1,5 @@
const availablePrefixs = ['moz', 'ms', 'webkit'];
function requestAnimationFramePolyfill() {
let lastTime = 0;
return function(callback) {
@@ -16,8 +18,27 @@ export default function getRequestAnimationFrame() {
if (window.requestAnimationFrame) {
return window.requestAnimationFrame;
}
const prefix = ['moz', 'ms', 'webkit'].filter(key => `${key}RequestAnimationFrame` in window)[0];
const prefix = availablePrefixs.filter(key => `${key}RequestAnimationFrame` in window)[0];
return prefix
? window[`${prefix}RequestAnimationFrame`]
: requestAnimationFramePolyfill();
}
export function cancelRequestAnimationFrame(id) {
if (typeof window === 'undefined') {
return null;
}
if (window.cancelAnimationFrame) {
return window.cancelAnimationFrame(id);
}
const prefix = availablePrefixs.filter(key =>
`${key}CancelAnimationFrame` in window || `${key}CancelRequestAnimationFrame` in window
)[0];
return prefix ?
(window[`${prefix}CancelAnimationFrame`] || window[`${prefix}CancelRequestAnimationFrame`]).call(this, id)
: clearTimeout(id);
}
+19 -14
View File
@@ -1,22 +1,27 @@
import getRequestAnimationFrame from '../_util/getRequestAnimationFrame';
import getRequestAnimationFrame, { cancelRequestAnimationFrame } from '../_util/getRequestAnimationFrame';
const reqAnimFrame = getRequestAnimationFrame();
export default function throttleByAnimationFrame(fn, threshhold = 250) {
let last;
return function() {
const self = this;
const args = arguments;
reqAnimFrame(timestamp => {
if (!last || timestamp - last > threshhold) {
last = timestamp;
fn.apply(self, args);
}
});
export default function throttleByAnimationFrame(fn) {
let requestId;
const later = args => () => {
requestId = null;
fn(...args);
};
const throttled = (...args) => {
if (requestId == null) {
requestId = reqAnimFrame(later(args));
}
};
(throttled as any).cancel = () => cancelRequestAnimationFrame(requestId);
return throttled;
}
export function throttleByAnimationFrameDecorator(threshhold = 250) {
export function throttleByAnimationFrameDecorator() {
return function(target, key, descriptor) {
let fn = descriptor.value;
let definingProperty = false;
@@ -27,7 +32,7 @@ export function throttleByAnimationFrameDecorator(threshhold = 250) {
return fn;
}
let boundFn = throttleByAnimationFrame(fn.bind(this), threshhold);
let boundFn = throttleByAnimationFrame(fn.bind(this));
definingProperty = true;
Object.defineProperty(this, key, {
value: boundFn,
+68
View File
@@ -0,0 +1,68 @@
import React from 'react';
import { mount } from 'enzyme';
import Affix from '..';
import Button from '../../button';
jest.useFakeTimers();
const events = {};
class AffixMounter extends React.Component {
componentDidMount() {
this.container.scrollTop = 100;
this.container.addEventListener = jest.fn().mockImplementation((event, cb) => {
events[event] = cb;
});
}
getTarget = () => {
return this.container;
}
render() {
return (<div
style={{
height: 100,
overflowY: 'scroll',
}}
ref={(node) => { this.container = node; }}
>
<div
className="background"
style={{
paddingTop: 60,
height: 300,
}}
>
<Affix
target={() => this.container}
ref={ele => this.affix = ele}
>
<Button type="primary" >
Fixed at the top of container
</Button>
</Affix>
</div>
</div>);
}
}
describe('Affix Render', () => {
it('Anchor render perfectly', () => {
document.body.innerHTML = '<div id="mounter" />';
const wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
jest.runAllTimers();
wrapper.node.affix.refs.fixedNode.parentNode.getBoundingClientRect = jest.fn(() => {
return {
bottom: 100, height: 28, left: 0, right: 0, top: -50, width: 195,
};
});
events.scroll({
type: 'scroll',
});
jest.runAllTimers();
expect(wrapper.node.affix.state.affixStyle).not.toBe(null);
});
});
+3 -1
View File
@@ -105,7 +105,7 @@ export default class Affix extends React.Component<AffixProps, any> {
this.setState({ placeholderStyle });
}
@throttleByAnimationFrameDecorator(250)
@throttleByAnimationFrameDecorator()
updatePosition(e) {
let { offsetTop, offsetBottom, offset, target = getDefaultTarget } = this.props;
const targetNode = target();
@@ -196,6 +196,7 @@ export default class Affix extends React.Component<AffixProps, any> {
componentWillUnmount() {
this.clearScrollEventListeners();
clearTimeout(this.timeout);
(this.updatePosition as any).cancel();
}
setTargetEventListeners(getTarget) {
@@ -203,6 +204,7 @@ export default class Affix extends React.Component<AffixProps, any> {
if (!target) {
return;
}
this.clearScrollEventListeners();
this.scrollEvent = addEventListener(target, 'scroll', this.updatePosition);
this.resizeEvent = addEventListener(target, 'resize', this.updatePosition);
}