mirror of
https://github.com/ant-design/ant-design.git
synced 2025-08-01 21:37:06 +08:00
fix: ResizeObserver should ignore shaking (#18289)
* fix: ResizeObserver should ignore shaking * fix lint * fix domHook
This commit is contained in:
parent
2f365f8273
commit
fd284ecc47
@ -1,10 +1,12 @@
|
|||||||
|
const __NULL__ = { notExist: true };
|
||||||
|
|
||||||
export function spyElementPrototypes(Element, properties) {
|
export function spyElementPrototypes(Element, properties) {
|
||||||
const propNames = Object.keys(properties);
|
const propNames = Object.keys(properties);
|
||||||
const originDescriptors = {};
|
const originDescriptors = {};
|
||||||
|
|
||||||
propNames.forEach(propName => {
|
propNames.forEach(propName => {
|
||||||
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
|
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
|
||||||
originDescriptors[propName] = originDescriptor;
|
originDescriptors[propName] = originDescriptor || __NULL__;
|
||||||
|
|
||||||
const spyProp = properties[propName];
|
const spyProp = properties[propName];
|
||||||
|
|
||||||
@ -37,7 +39,9 @@ export function spyElementPrototypes(Element, properties) {
|
|||||||
mockRestore() {
|
mockRestore() {
|
||||||
propNames.forEach(propName => {
|
propNames.forEach(propName => {
|
||||||
const originDescriptor = originDescriptors[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;
|
Element.prototype[propName] = originDescriptor;
|
||||||
} else {
|
} else {
|
||||||
Object.defineProperty(Element.prototype, propName, originDescriptor);
|
Object.defineProperty(Element.prototype, propName, originDescriptor);
|
||||||
|
@ -9,6 +9,8 @@ import triggerEvent from '../triggerEvent';
|
|||||||
import Wave from '../wave';
|
import Wave from '../wave';
|
||||||
import TransButton from '../transButton';
|
import TransButton from '../transButton';
|
||||||
import openAnimation from '../openAnimation';
|
import openAnimation from '../openAnimation';
|
||||||
|
import ResizeObserver from '../resizeObserver';
|
||||||
|
import { spyElementPrototype } from '../../__tests__/util/domHook';
|
||||||
|
|
||||||
describe('Test utils function', () => {
|
describe('Test utils function', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -143,7 +145,9 @@ describe('Test utils function', () => {
|
|||||||
it('bindAnimationEvent should return when node is null', () => {
|
it('bindAnimationEvent should return when node is null', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Wave>
|
<Wave>
|
||||||
<button type="button" disabled>button</button>
|
<button type="button" disabled>
|
||||||
|
button
|
||||||
|
</button>
|
||||||
</Wave>,
|
</Wave>,
|
||||||
).instance();
|
).instance();
|
||||||
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
||||||
@ -152,7 +156,9 @@ describe('Test utils function', () => {
|
|||||||
it('bindAnimationEvent.onClick should return when children is hidden', () => {
|
it('bindAnimationEvent.onClick should return when children is hidden', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<Wave>
|
<Wave>
|
||||||
<button type="button" style={{ display: 'none' }}>button</button>
|
<button type="button" style={{ display: 'none' }}>
|
||||||
|
button
|
||||||
|
</button>
|
||||||
</Wave>,
|
</Wave>,
|
||||||
).instance();
|
).instance();
|
||||||
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
expect(wrapper.bindAnimationEvent()).toBe(undefined);
|
||||||
@ -220,4 +226,47 @@ describe('Test utils function', () => {
|
|||||||
expect(done).toHaveBeenCalled();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,9 +10,19 @@ interface ResizeObserverProps {
|
|||||||
onResize?: () => void;
|
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;
|
resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.onComponentUpdated();
|
this.onComponentUpdated();
|
||||||
}
|
}
|
||||||
@ -38,10 +48,30 @@ class ReactResizeObserver extends React.Component<ResizeObserverProps, {}> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onResize = () => {
|
onResize: ResizeObserverCallback = (entries: ResizeObserverEntry[]) => {
|
||||||
const { onResize } = this.props;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { mount } from 'enzyme';
|
|||||||
import Affix from '..';
|
import Affix from '..';
|
||||||
import { getObserverEntities } from '../utils';
|
import { getObserverEntities } from '../utils';
|
||||||
import Button from '../../button';
|
import Button from '../../button';
|
||||||
|
import { spyElementPrototype } from '../../__tests__/util/domHook';
|
||||||
|
|
||||||
const events = {};
|
const events = {};
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ class AffixMounter extends React.Component {
|
|||||||
|
|
||||||
describe('Affix Render', () => {
|
describe('Affix Render', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let domMock;
|
||||||
|
|
||||||
const classRect = {
|
const classRect = {
|
||||||
container: {
|
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(() => {
|
beforeAll(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
domMock = spyElementPrototype(HTMLElement, 'getBoundingClientRect', function mockBounding() {
|
||||||
|
return (
|
||||||
|
classRect[this.className] || {
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
HTMLElement.prototype.getBoundingClientRect = originGetBoundingClientRect;
|
domMock.mockRestore();
|
||||||
});
|
});
|
||||||
const movePlaceholder = top => {
|
const movePlaceholder = top => {
|
||||||
classRect.fixed = {
|
classRect.fixed = {
|
||||||
@ -185,7 +185,7 @@ describe('Affix Render', () => {
|
|||||||
.find('ReactResizeObserver')
|
.find('ReactResizeObserver')
|
||||||
.at(index)
|
.at(index)
|
||||||
.instance()
|
.instance()
|
||||||
.onResize();
|
.onResize([{ target: { getBoundingClientRect: () => ({ width: 99, height: 99 }) } }]);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
|
||||||
expect(updateCalled).toHaveBeenCalled();
|
expect(updateCalled).toHaveBeenCalled();
|
||||||
|
Loading…
Reference in New Issue
Block a user