Skip to content

Commit

Permalink
Fixed issue with EuiResizeObserver fallback (#3088)
Browse files Browse the repository at this point in the history
* fix the issue

* edite the CHANGELOG.md

* fix the typo

* filter out the non rezise events

* remove unused eventlistener

Co-authored-by: Greg Thompson <[email protected]>
  • Loading branch information
PeterSenpai and thompsongl authored Mar 23, 2020
1 parent f8bb840 commit 51cb3ac
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

- Fixed bug in `EuiSuperDatePicker` to show correct values of commonly used values in relative tab ([#3106](https://github.com/elastic/eui/pull/3106))
- Fixed race condition in `EuiIcon` when switching from dynamically fetched components ([#3118](https://github.com/elastic/eui/pull/3118))
- Fixed the issue that `EuiResizeObserver` fallback did not properly listen to pure window resizing ([#3088](https://github.com/elastic/eui/pull/3088))

**Breaking changes**

Expand Down
126 changes: 65 additions & 61 deletions src/components/observer/resize_observer/resize_observer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,10 @@ export async function waitforResizeObserver(period = 30) {
await sleep(period);
}

describe('EuiResizeObserver', () => {
it('watches for a resize', async () => {
expect.assertions(2);
const onResize = jest.fn();

const Wrapper: FunctionComponent<{}> = ({ children }) => {
return (
<EuiResizeObserver onResize={onResize}>
{(resizeRef: (e: HTMLElement | null) => void) => (
<div ref={resizeRef}>{children}</div>
)}
</EuiResizeObserver>
);
};

const component = mount(<Wrapper children={<div>Hello World</div>} />);

// Resize observer is expected to fire once on mount
await waitforResizeObserver();
expect(onResize).toHaveBeenCalledTimes(1);

component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});

await waitforResizeObserver();

// Expect 2 calls because it's called once on mount
expect(onResize).toHaveBeenCalledTimes(2);
});
});

type GetBoundingClientRect = typeof HTMLElement['prototype']['getBoundingClientRect'];
describe('useResizeObserver', () => {
describe('testResizeObservers', () => {
// refactor the tests structure to make sure that `EuiResizeObserver` test can get
// the proper size of the dom element.
type GetBoundingClientRect = typeof HTMLElement['prototype']['getBoundingClientRect'];
let _originalgetBoundingClientRect: undefined | GetBoundingClientRect;
beforeAll(() => {
_originalgetBoundingClientRect =
Expand All @@ -64,33 +29,72 @@ describe('useResizeObserver', () => {
HTMLElement.prototype.getBoundingClientRect = _originalgetBoundingClientRect!;
});

it('watches for a resize', async () => {
expect.assertions(2);

const Wrapper: FunctionComponent<{}> = jest.fn(({ children }) => {
const [ref, setRef] = useState();
useResizeObserver(ref);
return <div ref={setRef}>{children}</div>;
describe('EuiResizeObserver', () => {
it('watches for a resize', async () => {
expect.assertions(2);
const onResize = jest.fn();

const Wrapper: FunctionComponent<{}> = ({ children }) => {
return (
<EuiResizeObserver onResize={onResize}>
{(resizeRef: (e: HTMLElement | null) => void) => (
<div ref={resizeRef}>{children}</div>
)}
</EuiResizeObserver>
);
};

const component = mount(<Wrapper children={<div>Hello World</div>} />);

// Resize observer is expected to fire once on mount
await waitforResizeObserver();
expect(onResize).toHaveBeenCalledTimes(1);

component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});

await waitforResizeObserver();

// Expect 2 calls because it's called once on mount
expect(onResize).toHaveBeenCalledTimes(2);
});
});

const component = mount(<Wrapper children={<div>Hello World</div>} />);
describe('useResizeObserver', () => {
it('watches for a resize', async () => {
expect.assertions(2);

// Expect the initial render, re-render when the ref is created, and a 3rd for the onresize callback
await act(() => waitforResizeObserver());
expect(Wrapper).toHaveBeenCalledTimes(3);
const Wrapper: FunctionComponent<{}> = jest.fn(({ children }) => {
const [ref, setRef] = useState();
useResizeObserver(ref);
return <div ref={setRef}>{children}</div>;
});

component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});
const component = mount(<Wrapper children={<div>Hello World</div>} />);

// Expect the initial render, re-render when the ref is created, and a 3rd for the onresize callback
await act(() => waitforResizeObserver());
expect(Wrapper).toHaveBeenCalledTimes(3);

await waitforResizeObserver();
component.setProps({
children: (
<div>
<div>Hello World</div>
<div>Hello Again</div>
</div>
),
});

// Expect two more calls because children changed (re-render) & resize observer reacted
expect(Wrapper).toHaveBeenCalledTimes(5);
await waitforResizeObserver();

// Expect two more calls because children changed (re-render) & resize observer reacted
expect(Wrapper).toHaveBeenCalledTimes(5);
});
});
});
29 changes: 27 additions & 2 deletions src/components/observer/resize_observer/resize_observer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,25 @@ const mutationObserverOptions = {
export class EuiResizeObserver extends EuiObserver<Props> {
name = 'EuiResizeObserver';

state = {
height: 0,
width: 0,
};

onResize = () => {
if (this.childNode != null) {
// Eventually use `clientRect` on the `entries[]` returned natively
const { height, width } = this.childNode.getBoundingClientRect();
// Check for actual resize event
if (this.state.height === height && this.state.width === width) {
return;
}

this.props.onResize({
height,
width,
});
this.setState({ height, width });
}
};

Expand All @@ -40,14 +51,28 @@ export class EuiResizeObserver extends EuiObserver<Props> {
};
}

const makeCompatibleObserver = (node: Element, callback: () => void) => {
const observer = new MutationObserver(callback);
observer.observe(node, mutationObserverOptions);

window.addEventListener('resize', callback);

observer.disconnect = () => {
observer.disconnect();

window.removeEventListener('resize', callback);
};

return observer;
};

const makeResizeObserver = (node: Element, callback: () => void) => {
let observer: Observer | undefined;
if (hasResizeObserver) {
observer = new window.ResizeObserver(callback);
observer.observe(node);
} else {
observer = new MutationObserver(callback);
observer.observe(node, mutationObserverOptions);
observer = makeCompatibleObserver(node, callback);
requestAnimationFrame(callback); // Mimic ResizeObserver behavior of triggering a resize event on init
}
return observer;
Expand Down

0 comments on commit 51cb3ac

Please sign in to comment.