fix: ResizeObserver should ignore shaking (#18289)

* fix: ResizeObserver should ignore shaking

* fix lint

* fix domHook
This commit is contained in:
zombieJ 2019-08-15 15:52:36 +08:00 committed by GitHub
parent 2f365f8273
commit fd284ecc47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 20 deletions

View File

@ -1,10 +1,12 @@
const __NULL__ = { notExist: true };
export function spyElementPrototypes(Element, properties) {
const propNames = Object.keys(properties);
const originDescriptors = {};
propNames.forEach(propName => {
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
originDescriptors[propName] = originDescriptor;
originDescriptors[propName] = originDescriptor || __NULL__;
const spyProp = properties[propName];
@ -37,7 +39,9 @@ export function spyElementPrototypes(Element, properties) {
mockRestore() {
propNames.forEach(propName => {
const originDescriptor = originDescriptors[propName];
if (typeof originDescriptor === 'function') {
if (originDescriptor === __NULL__) {
delete Element.prototype[propName];
} else if (typeof originDescriptor === 'function') {
Element.prototype[propName] = originDescriptor;
} else {
Object.defineProperty(Element.prototype, propName, originDescriptor);

View File

@ -9,6 +9,8 @@ import triggerEvent from '../triggerEvent';
import Wave from '../wave';
import TransButton from '../transButton';
import openAnimation from '../openAnimation';
import ResizeObserver from '../resizeObserver';
import { spyElementPrototype } from '../../__tests__/util/domHook';
describe('Test utils function', () => {
beforeAll(() => {
@ -143,7 +145,9 @@ describe('Test utils function', () => {
it('bindAnimationEvent should return when node is null', () => {
const wrapper = mount(
<Wave>
<button type="button" disabled>button</button>
<button type="button" disabled>
button
</button>
</Wave>,
).instance();
expect(wrapper.bindAnimationEvent()).toBe(undefined);
@ -152,7 +156,9 @@ describe('Test utils function', () => {
it('bindAnimationEvent.onClick should return when children is hidden', () => {
const wrapper = mount(
<Wave>
<button type="button" style={{ display: 'none' }}>button</button>
<button type="button" style={{ display: 'none' }}>
button
</button>
</Wave>,
).instance();
expect(wrapper.bindAnimationEvent()).toBe(undefined);
@ -220,4 +226,47 @@ describe('Test utils function', () => {
expect(done).toHaveBeenCalled();
});
});
describe('ResizeObserver', () => {
let domMock;
beforeAll(() => {
domMock = spyElementPrototype(HTMLDivElement, 'getBoundingClientRect', () => {
return {
width: 1128 + Math.random(),
height: 903 + Math.random(),
};
});
});
afterAll(() => {
domMock.mockRestore();
});
it('should not trigger `onResize` if size shaking', () => {
const onResize = jest.fn();
let divNode;
const wrapper = mount(
<ResizeObserver onResize={onResize}>
<div
ref={node => {
divNode = node;
}}
/>
</ResizeObserver>,
);
// First trigger
wrapper.instance().onResize([{ target: divNode }]);
onResize.mockReset();
// Repeat trigger should not trigger outer `onResize` with shaking
for (let i = 0; i < 10; i += 1) {
wrapper.instance().onResize([{ target: divNode }]);
}
expect(onResize).not.toHaveBeenCalled();
});
});
});

View File

@ -10,9 +10,19 @@ interface ResizeObserverProps {
onResize?: () => void;
}
class ReactResizeObserver extends React.Component<ResizeObserverProps, {}> {
interface ResizeObserverState {
height: number;
width: number;
}
class ReactResizeObserver extends React.Component<ResizeObserverProps, ResizeObserverState> {
resizeObserver: ResizeObserver | null = null;
state = {
width: 0,
height: 0,
};
componentDidMount() {
this.onComponentUpdated();
}
@ -38,10 +48,30 @@ class ReactResizeObserver extends React.Component<ResizeObserverProps, {}> {
}
}
onResize = () => {
onResize: ResizeObserverCallback = (entries: ResizeObserverEntry[]) => {
const { onResize } = this.props;
if (onResize) {
onResize();
const { target } = entries[0];
const { width, height } = target.getBoundingClientRect();
/**
* Resize observer trigger when content size changed.
* In most case we just care about element size,
* let's use `boundary` instead of `contentRect` here to avoid shaking.
*/
const fixedWidth = Math.floor(width);
const fixedHeight = Math.floor(height);
if (this.state.width !== fixedWidth || this.state.height !== fixedHeight) {
this.setState({
width: fixedWidth,
height: fixedHeight,
});
if (onResize) {
onResize();
}
}
};

View File

@ -3,6 +3,7 @@ import { mount } from 'enzyme';
import Affix from '..';
import { getObserverEntities } from '../utils';
import Button from '../../button';
import { spyElementPrototype } from '../../__tests__/util/domHook';
const events = {};
@ -40,6 +41,7 @@ class AffixMounter extends React.Component {
describe('Affix Render', () => {
let wrapper;
let domMock;
const classRect = {
container: {
@ -48,23 +50,21 @@ describe('Affix Render', () => {
},
};
const originGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
HTMLElement.prototype.getBoundingClientRect = function getBoundingClientRect() {
return (
classRect[this.className] || {
top: 0,
bottom: 0,
}
);
};
beforeAll(() => {
jest.useFakeTimers();
domMock = spyElementPrototype(HTMLElement, 'getBoundingClientRect', function mockBounding() {
return (
classRect[this.className] || {
top: 0,
bottom: 0,
}
);
});
});
afterAll(() => {
jest.useRealTimers();
HTMLElement.prototype.getBoundingClientRect = originGetBoundingClientRect;
domMock.mockRestore();
});
const movePlaceholder = top => {
classRect.fixed = {
@ -185,7 +185,7 @@ describe('Affix Render', () => {
.find('ReactResizeObserver')
.at(index)
.instance()
.onResize();
.onResize([{ target: { getBoundingClientRect: () => ({ width: 99, height: 99 }) } }]);
jest.runAllTimers();
expect(updateCalled).toHaveBeenCalled();