Skip to content

Commit

Permalink
fix: ResizeObserver should ignore shaking (#18289)
Browse files Browse the repository at this point in the history
* fix: ResizeObserver should ignore shaking

* fix lint

* fix domHook
  • Loading branch information
zombieJ committed Aug 15, 2019
1 parent 2f365f8 commit fd284ec
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 20 deletions.
8 changes: 6 additions & 2 deletions components/__tests__/util/domHook.js
@@ -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];

Expand Down Expand Up @@ -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);
Expand Down
53 changes: 51 additions & 2 deletions components/_util/__tests__/util.test.js
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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();
});
});
});
38 changes: 34 additions & 4 deletions components/_util/resizeObserver.tsx
Expand Up @@ -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();
}
Expand All @@ -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();
}
}
};

Expand Down
24 changes: 12 additions & 12 deletions components/affix/__tests__/Affix.test.js
Expand Up @@ -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 = {};

Expand Down Expand Up @@ -40,6 +41,7 @@ class AffixMounter extends React.Component {

describe('Affix Render', () => {
let wrapper;
let domMock;

const classRect = {
container: {
Expand All @@ -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 = {
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit fd284ec

Please sign in to comment.