Skip to content

Commit e46d467

Browse files
authored
Merge pull request #23 from trurl-master/develop
Add ResizeObserver mock
2 parents 496fc42 + 6895bb4 commit e46d467

File tree

18 files changed

+1876
-2650
lines changed

18 files changed

+1876
-2650
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ${{ matrix.os }}
88
strategy:
99
matrix:
10-
node: ['10.x', '12.x', '14.x']
10+
node: ['12.x', '14.x']
1111
os: [ubuntu-latest, windows-latest, macOS-latest]
1212

1313
steps:

README.md

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe('Desktop specific tests', () => {
8080
})
8181
```
8282

83-
## Mock intersection observer
83+
## Mock IntersectionObserver
8484

8585
Provides a way of triggering intersection observer events
8686

@@ -121,6 +121,81 @@ Triggers the intersection observer callback for all of the observed nodes
121121
and `isIntersected` set to `true` (for `enterAll`) or `false` (for `leaveAll`).
122122
Other `IntersectionObserverEntry` params can be passed as `desc` argument
123123

124+
## Mock ResizeObserver
125+
126+
Provides a way of triggering resize observer events. It's up to you to mock elements' sizes. If your component uses `contentRect` provided by the callback, you must mock element's `getBoundingClientRect` (for exemple using a helper function `mockElementBoundingClientRect` provided by the lib)
127+
128+
_Currently the mock doesn't take into account multi-column layouts, so `borderBoxSize` and `contentBoxSize` will contain only one full-sized item_
129+
130+
Example, using `React Testing Library`:
131+
132+
```jsx
133+
import {
134+
mockResizeObserver,
135+
mockElementBoundingClientRect,
136+
} from 'jsdom-testing-mocks';
137+
138+
const DivWithSize = () => {
139+
const [size, setSize] = useState({ width: 0, height: 0 });
140+
const ref = useRef(null);
141+
142+
useEffect(() => {
143+
const observer = new ResizeObserver(entries => {
144+
setSize({
145+
width: entries[0].contentRect.width,
146+
height: entries[0].contentRect.height,
147+
});
148+
});
149+
150+
observer.observe(ref.current);
151+
152+
return () => {
153+
observer.disconnect();
154+
};
155+
}, []);
156+
157+
return (
158+
<div data-testid="theDiv" ref={ref}>
159+
{size.width} x {size.height}
160+
</div>
161+
);
162+
};
163+
164+
const resizeObserver = mockResizeObserver();
165+
166+
it('prints the size of the div', () => {
167+
render(<DivWithSize />);
168+
169+
const theDiv = screen.getByTestId('theDiv');
170+
171+
expect(screen.getByText('0 x 0')).toBeInTheDocument();
172+
173+
mockElementBoundingClientRect(theDiv, { width: 300, height: 200 });
174+
175+
act(() => {
176+
resizeObserver.resize(theDiv);
177+
});
178+
179+
expect(screen.getByText('300 x 200')).toBeInTheDocument();
180+
181+
mockElementBoundingClientRect(theDiv, { width: 200, height: 500 });
182+
183+
act(() => {
184+
resizeObserver.resize(theDiv);
185+
});
186+
187+
expect(screen.getByText('200 x 500')).toBeInTheDocument();
188+
});
189+
```
190+
191+
### API
192+
193+
`mockResizeObserver` returns an object, that has one method:
194+
195+
#### .resize(elements: HTMLElement | HTMLElement[])
196+
197+
Triggers all resize observer callbacks for all observers that observe the passed elements
198+
124199
<!-- prettier-ignore-start -->
125200

126201
[version-badge]: https://img.shields.io/npm/v/jsdom-testing-mocks.svg?style=flat-square

example/App.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as React from 'react';
2-
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
2+
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
33

44
import Nav from './Nav';
55

66
import GlobalObserver from './intersection-observer/global-observer/GlobalObserver';
7+
import MeasureParent from './resize-observer/measure-parent/MeasureParent';
8+
import PrintMySize from './resize-observer/print-my-size/PrintMySize';
79
import CustomUseMedia from './viewport/custom-use-media/CustomUseMedia';
810

911
export default function App() {
@@ -18,6 +20,12 @@ export default function App() {
1820
<Route path="/viewport">
1921
<CustomUseMedia />
2022
</Route>
23+
<Route path="/resize-observer/do-i-fit">
24+
<MeasureParent />
25+
</Route>
26+
<Route path="/resize-observer/print-my-size">
27+
<PrintMySize />
28+
</Route>
2129
</Switch>
2230
</div>
2331
</Router>

example/Nav.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ const Nav = (): React.ReactElement => (
1313
<li style={{ marginRight: '2em' }}>
1414
<Link to="/intersection-observer">Intersection Observer</Link>
1515
</li>
16+
<li style={{ marginRight: '2em' }}>
17+
<Link to="/resize-observer/do-i-fit">Resize Observer: do I fit?</Link>
18+
</li>
19+
<li style={{ marginRight: '2em' }}>
20+
<Link to="/resize-observer/print-my-size">
21+
Resize Observer: print my size
22+
</Link>
23+
</li>
1624
<li>
1725
<Link to="/viewport">Viewport</Link>
1826
</li>

example/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99

1010
<body>
1111
<div id="root"></div>
12-
<script src="./index.tsx"></script>
12+
<script src="./index.tsx" type="module"></script>
1313
</body>
1414
</html>

example/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'react-app-polyfill/ie11';
21
import * as React from 'react';
32
import * as ReactDOM from 'react-dom';
43
import App from './App';

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@types/jest": "^26.0.21",
2121
"@types/react": "^16.9.11",
2222
"@types/react-dom": "^16.8.4",
23-
"parcel": "^2.0.0-beta.2",
23+
"parcel": "^2.0.1",
2424
"typescript": "^3.4.5"
2525
}
2626
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as React from 'react';
2+
import { useRef } from 'react';
3+
4+
import useDoIFit from './useDoIFit';
5+
6+
const MeasureParent = () => {
7+
const ref1 = useRef(null);
8+
const ref2 = useRef(null);
9+
const iFit1 = useDoIFit(ref1);
10+
const iFit2 = useDoIFit(ref2);
11+
12+
return (
13+
<div>
14+
<div>
15+
<div
16+
style={{
17+
width: 400,
18+
height: 200,
19+
backgroundColor: 'rgba(255,0,0,0.5)',
20+
}}
21+
data-testid="parent1"
22+
>
23+
<div
24+
ref={ref1}
25+
style={{
26+
width: 200,
27+
height: 100,
28+
backgroundColor: 'rgba(0,0,0,0.5)',
29+
}}
30+
data-testid="child1"
31+
/>
32+
</div>
33+
<div data-testid="result">{iFit1 ? 'fit' : "doesn't fit"}</div>
34+
</div>
35+
<div>
36+
<div
37+
style={{
38+
width: 400,
39+
height: 200,
40+
backgroundColor: 'rgba(255,0,0,0.5)',
41+
marginTop: 20,
42+
}}
43+
data-testid="parent2"
44+
>
45+
<div
46+
ref={ref2}
47+
style={{
48+
width: 500,
49+
height: 300,
50+
backgroundColor: 'rgba(0,0,0,0.5)',
51+
}}
52+
data-testid="child2"
53+
/>
54+
</div>
55+
<div data-testid="result">{iFit2 ? 'fit' : "doesn't fit"}</div>
56+
</div>
57+
</div>
58+
);
59+
};
60+
61+
export default MeasureParent;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const useDoIFit = (ref: React.RefObject<HTMLElement>) => {
4+
const [iFit, setIFit] = useState(false);
5+
6+
useEffect(() => {
7+
if (!ref.current || !ref.current.parentElement) {
8+
return;
9+
}
10+
11+
const parentElement = ref.current.parentElement;
12+
13+
const observer = new ResizeObserver(entries => {
14+
const { width, height } = entries[0].contentRect;
15+
const childElement = parentElement.children[0] as HTMLElement;
16+
const {
17+
width: childWidth,
18+
height: childHeight,
19+
} = childElement.getBoundingClientRect();
20+
21+
setIFit(childWidth < width && childHeight < height);
22+
});
23+
24+
observer.observe(parentElement);
25+
26+
return () => {
27+
observer.disconnect();
28+
};
29+
}, []);
30+
31+
return iFit;
32+
};
33+
34+
export default useDoIFit;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as React from 'react';
2+
import { useState, useEffect, useRef } from 'react';
3+
4+
const PrintMySize = () => {
5+
const ref1 = useRef<HTMLDivElement>(null);
6+
const ref2 = useRef<HTMLDivElement>(null);
7+
const [sizes, setSizes] = useState<{ width: number; height: number }[]>([]);
8+
9+
useEffect(() => {
10+
if (!ref1.current || !ref2.current) {
11+
return;
12+
}
13+
14+
const observer = new ResizeObserver(entries => {
15+
setSizes(entries.map(entry => entry.contentRect));
16+
});
17+
18+
observer.observe(ref1.current);
19+
observer.observe(ref2.current);
20+
21+
return () => {
22+
observer.disconnect();
23+
};
24+
}, []);
25+
26+
return (
27+
<div>
28+
<div
29+
style={{
30+
width: 400,
31+
height: 200,
32+
backgroundColor: 'rgba(255,0,0,0.5)',
33+
}}
34+
data-testid="element"
35+
ref={ref1}
36+
>
37+
{sizes[0] && `${sizes[0].width}x${sizes[0].height}`}
38+
</div>
39+
<div
40+
style={{
41+
width: 230,
42+
height: 70,
43+
backgroundColor: 'rgba(255,0,0,0.5)',
44+
marginTop: 20,
45+
}}
46+
data-testid="element"
47+
ref={ref2}
48+
>
49+
{sizes[1] && `${sizes[1].width}x${sizes[1].height}`}
50+
</div>
51+
</div>
52+
);
53+
};
54+
55+
export default PrintMySize;

0 commit comments

Comments
 (0)