Skip to content

Commit

Permalink
feat: 🎸 remove resize-observer-polyfill from useMeasure
Browse files Browse the repository at this point in the history
BREAKING CHANGE: resize-observer-polyfill package is not used with useMeasure() hook
anymore.
  • Loading branch information
streamich committed Jan 12, 2020
1 parent 4dfb258 commit bf11131
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 79 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@
"coverageDirectory": "coverage",
"testMatch": [
"<rootDir>/tests/**/*.test.(ts|tsx)"
],
"setupFiles": [
"<rootDir>/tests/_setup.js"
]
}
}
3 changes: 1 addition & 2 deletions src/useMeasure.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState, useMemo } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';

export type UseMeasureRect = Pick<
Expand All @@ -26,7 +25,7 @@ const useMeasure = (): UseMeasureResult => {

const observer = useMemo(
() =>
new ResizeObserver(entries => {
new (window as any).ResizeObserver(entries => {
if (entries[0]) {
const { x, y, width, height, top, left, bottom, right } = entries[0].contentRect;
setRect({ x, y, width, height, top, left, bottom, right });
Expand Down
5 changes: 5 additions & 0 deletions tests/_setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
window.ResizeObserver = class ResizeObserver {
constructor() {}
observe() {}
disconnect() {}
};
239 changes: 162 additions & 77 deletions tests/useMeasure.test.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,79 @@
import { act, renderHook } from '@testing-library/react-hooks';
import useMeasure, { ContentRect } from '../src/useMeasure';

interface Entry {
target: HTMLElement;
contentRect: ContentRect;
}

jest.mock('resize-observer-polyfill', () => {
return class ResizeObserver {
private cb: (entries: Entry[]) => void;
private map: WeakMap<HTMLElement, any>;
private targets: HTMLElement[];
constructor(cb: () => void) {
this.cb = cb;
this.map = new WeakMap();
this.targets = [];
}
public disconnect() {
this.targets.map(target => {
const originMethod = this.map.get(target);
target.setAttribute = originMethod;
this.map.delete(target);
});
}
public observe(target: HTMLElement) {
const method = 'setAttribute';
const originMethod = target[method];
this.map.set(target, originMethod);
this.targets.push(target);
target[method] = (...args) => {
const [attrName, value] = args;
if (attrName === 'style') {
const rect: DOMRectReadOnly = {
x: 0,
y: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
width: 0,
height: 0,
} as DOMRectReadOnly;
value.split(';').map(kv => {
const [key, v] = kv.split(':');
if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) {
rect[key] = parseInt(v, 10);
}
});
target.getBoundingClientRect = () => rect;
}
originMethod.apply(target, args);
this.fireCallback();
};
}
private fireCallback() {
if (this.cb) {
this.cb(
this.targets.map(target => {
return {
target,
contentRect: target.getBoundingClientRect() as ContentRect,
};
})
);
}
import { renderHook, act } from '@testing-library/react-hooks';
import useMeasure, { UseMeasureRef } from '../src/useMeasure';

it('by default, state defaults every value to -1', () => {
const { result } = renderHook(() => useMeasure());

act(() => {
const div = document.createElement('div');
(result.current[0] as UseMeasureRef)(div);
});

expect(result.current[1]).toMatchObject({
width: -1,
height: -1,
top: -1,
bottom: -1,
left: -1,
right: -1,
});
});

it('synchronously sets up ResizeObserver listener', () => {
let listener: ((rect: any) => void) | undefined = undefined;
(window as any).ResizeObserver = class ResizeObserver {
constructor(ls) {
listener = ls;
}
observe() {}
disconnect() {}
};

const { result } = renderHook(() => useMeasure());

act(() => {
const div = document.createElement('div');
(result.current[0] as UseMeasureRef)(div);
});

expect(typeof listener).toBe('function');
});

it('reacts to changes in size of any of the observed elements', () => {
it('tracks rectangle of a DOM element', () => {
let listener: ((rect: any) => void) | undefined = undefined;
(window as any).ResizeObserver = class ResizeObserver {
constructor(ls) {
listener = ls;
}
observe() {}
disconnect() {}
};

const { result } = renderHook(() => useMeasure());
const div = document.createElement('div');
result.current[0](div);
expect(result.current[1]).toMatchObject({
width: 0,
height: 0,
top: 0,
bottom: 0,
left: 0,
right: 0,

act(() => {
const div = document.createElement('div');
(result.current[0] as UseMeasureRef)(div);
});

act(() => {
listener!([{
contentRect: {
x: 1,
y: 2,
width: 200,
height: 200,
top: 100,
bottom: 0,
left: 100,
right: 0,
}
}]);
});
act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100'));

expect(result.current[1]).toMatchObject({
x: 1,
y: 2,
width: 200,
height: 200,
top: 100,
Expand All @@ -91,3 +82,97 @@ it('reacts to changes in size of any of the observed elements', () => {
right: 0,
});
});

it('tracks multiple updates', () => {
let listener: ((rect: any) => void) | undefined = undefined;
(window as any).ResizeObserver = class ResizeObserver {
constructor(ls) {
listener = ls;
}
observe() {}
disconnect() {}
};

const { result } = renderHook(() => useMeasure());

act(() => {
const div = document.createElement('div');
(result.current[0] as UseMeasureRef)(div);
});

act(() => {
listener!([{
contentRect: {
x: 1,
y: 1,
width: 1,
height: 1,
top: 1,
bottom: 1,
left: 1,
right: 1,
}
}]);
});

expect(result.current[1]).toMatchObject({
x: 1,
y: 1,
width: 1,
height: 1,
top: 1,
bottom: 1,
left: 1,
right: 1,
});

act(() => {
listener!([{
contentRect: {
x: 2,
y: 2,
width: 2,
height: 2,
top: 2,
bottom: 2,
left: 2,
right: 2,
}
}]);
});

expect(result.current[1]).toMatchObject({
x: 2,
y: 2,
width: 2,
height: 2,
top: 2,
bottom: 2,
left: 2,
right: 2,
});
});

it('calls .disconnect() on ResizeObserver when component unmounts', () => {
const disconnect = jest.fn();
(window as any).ResizeObserver = class ResizeObserver {
constructor() {}
observe() {}
disconnect() {
disconnect();
}
};

const { result, unmount } = renderHook(() => useMeasure());

act(() => {
const div = document.createElement('div');
(result.current[0] as UseMeasureRef)(div);
});

expect(disconnect).toHaveBeenCalledTimes(0);

unmount();

expect(disconnect).toHaveBeenCalledTimes(1);
});

0 comments on commit bf11131

Please sign in to comment.