Skip to content

Commit 5992e1d

Browse files
committed
Improve mock ResizeObserver test utils
1 parent 71df53d commit 5992e1d

File tree

5 files changed

+160
-84
lines changed

5 files changed

+160
-84
lines changed

lib/components/grid/Grid.test.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
44
import { EMPTY_OBJECT } from "../../../src/constants";
55
import {
66
disableResizeObserverForCurrentTest,
7-
simulateUnsupportedEnvironmentForTest,
8-
updateMockResizeObserver
7+
setDefaultElementSize,
8+
simulateUnsupportedEnvironmentForTest
99
} from "../../utils/test/mockResizeObserver";
1010
import { Grid } from "./Grid";
1111
import type { CellComponentProps, GridImperativeAPI } from "./types";
@@ -36,7 +36,7 @@ describe("Grid", () => {
3636
beforeEach(() => {
3737
CellComponent.mockReset();
3838

39-
updateMockResizeObserver(new DOMRect(0, 0, 100, 40));
39+
setDefaultElementSize({ height: 40, width: 100 });
4040

4141
mountedCells = new Map();
4242
});
@@ -273,7 +273,6 @@ describe("Grid", () => {
273273

274274
const items = screen.queryAllByRole("gridcell");
275275
expect(items).toHaveLength(8);
276-
// TODO
277276
});
278277

279278
test("should call onCellsRendered", () => {

lib/components/list/List.test.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { act, render, screen } from "@testing-library/react";
22
import { createRef, useLayoutEffect } from "react";
33
import { beforeEach, describe, expect, test, vi } from "vitest";
44
import { EMPTY_OBJECT } from "../../../src/constants";
5+
import { assert } from "../../utils/assert";
56
import {
67
disableResizeObserverForCurrentTest,
7-
simulateUnsupportedEnvironmentForTest,
8-
updateMockResizeObserver
8+
setDefaultElementSize,
9+
setElementSize,
10+
simulateUnsupportedEnvironmentForTest
911
} from "../../utils/test/mockResizeObserver";
1012
import { List } from "./List";
1113
import { type ListImperativeAPI, type RowComponentProps } from "./types";
@@ -34,7 +36,7 @@ describe("List", () => {
3436
beforeEach(() => {
3537
RowComponent.mockReset();
3638

37-
updateMockResizeObserver(new DOMRect(0, 0, 50, 100));
39+
setDefaultElementSize({ height: 100, width: 50 });
3840

3941
mountedRows = new Map();
4042
});
@@ -56,7 +58,7 @@ describe("List", () => {
5658
test("should render enough rows to fill the available height", () => {
5759
const onResize = vi.fn();
5860

59-
render(
61+
const { container } = render(
6062
<List
6163
onResize={onResize}
6264
overscanCount={0}
@@ -85,7 +87,14 @@ describe("List", () => {
8587
);
8688

8789
act(() => {
88-
updateMockResizeObserver(new DOMRect(0, 0, 50, 75));
90+
const listElement = container.querySelector<HTMLElement>('[role="list"]');
91+
assert(listElement !== null);
92+
93+
setElementSize({
94+
element: listElement,
95+
height: 75,
96+
width: 50
97+
});
8998
});
9099

91100
items = screen.queryAllByRole("listitem");
@@ -107,7 +116,7 @@ describe("List", () => {
107116
});
108117

109118
test("should render enough rows to fill the available height with overscan", () => {
110-
render(
119+
const { container } = render(
111120
<List
112121
overscanCount={2}
113122
rowCount={100}
@@ -123,7 +132,14 @@ describe("List", () => {
123132
expect(items[5]).toHaveTextContent("Row 5");
124133

125134
act(() => {
126-
updateMockResizeObserver(new DOMRect(0, 0, 50, 75));
135+
const listElement = container.querySelector<HTMLElement>('[role="list"]');
136+
assert(listElement !== null);
137+
138+
setElementSize({
139+
element: listElement,
140+
height: 75,
141+
width: 50
142+
});
127143
});
128144

129145
items = screen.queryAllByRole("listitem");

lib/core/useVirtualizer.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { renderHook } from "@testing-library/react";
22
import { beforeEach, describe, expect, test } from "vitest";
33
import { EMPTY_OBJECT, NOOP_FUNCTION } from "../../src/constants";
4-
import { updateMockResizeObserver } from "../utils/test/mockResizeObserver";
4+
import { setDefaultElementSize } from "../utils/test/mockResizeObserver";
55
import { useVirtualizer } from "./useVirtualizer";
66

77
describe("useVirtualizer", () => {
@@ -17,7 +17,10 @@ describe("useVirtualizer", () => {
1717
};
1818

1919
beforeEach(() => {
20-
updateMockResizeObserver(new DOMRect(0, 0, 50, 100));
20+
setDefaultElementSize({
21+
height: 100,
22+
width: 50
23+
});
2124
});
2225

2326
describe("getCellBounds", () => {

lib/hooks/useResizeObserver.test.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { act, renderHook } from "@testing-library/react";
22
import { beforeEach, describe, expect, test } from "vitest";
33
import {
44
simulateUnsupportedEnvironmentForTest,
5-
updateMockResizeObserver
5+
setDefaultElementSize,
6+
setElementSize
67
} from "../utils/test/mockResizeObserver";
78
import { useResizeObserver } from "./useResizeObserver";
89

910
describe("useResizeObserver", () => {
1011
beforeEach(() => {
11-
updateMockResizeObserver({ height: 100, width: 50 });
12+
setDefaultElementSize({ height: 100, width: 50 });
1213
});
1314

1415
test("should use default width/height if disabled", () => {
@@ -34,7 +35,7 @@ describe("useResizeObserver", () => {
3435

3536
act(() => {
3637
// Updates should be ignored as well
37-
updateMockResizeObserver({ height: 25, target: element });
38+
setElementSize({ element, height: 25, width: 50 });
3839
});
3940

4041
expect(result.current).toEqual({
@@ -79,9 +80,10 @@ describe("useResizeObserver", () => {
7980
});
8081

8182
act(() => {
82-
updateMockResizeObserver({
83+
setElementSize({
84+
element,
8385
height: 50,
84-
target: element
86+
width: 50
8587
});
8688
});
8789

@@ -104,9 +106,10 @@ describe("useResizeObserver", () => {
104106
);
105107

106108
act(() => {
107-
updateMockResizeObserver({
109+
setElementSize({
110+
element: otherElement,
108111
height: 50,
109-
target: otherElement
112+
width: 50
110113
});
111114
});
112115

Lines changed: 119 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,159 @@
11
import EventEmitter from "node:events";
22

3+
type GetDOMRect = (element: HTMLElement) => DOMRectReadOnly | undefined | void;
4+
35
const emitter = new EventEmitter();
4-
emitter.setMaxListeners(25);
6+
emitter.setMaxListeners(100);
7+
8+
const elementToDOMRect = new Map<HTMLElement, DOMRect>();
59

10+
let defaultDomRect: DOMRectReadOnly = new DOMRect(0, 0, 0, 0);
611
let disabled: boolean = false;
7-
let entrySize: DOMRectReadOnly = new DOMRect(0, 0, 0, 0);
12+
let getDOMRect: GetDOMRect | undefined = undefined;
813

914
export function disableResizeObserverForCurrentTest() {
1015
disabled = true;
1116
}
1217

13-
export function simulateUnsupportedEnvironmentForTest() {
14-
// @ts-expect-error Simulate API being unsupported
15-
window.ResizeObserver = null;
18+
export function setDefaultElementSize({
19+
height,
20+
width
21+
}: {
22+
height: number;
23+
width: number;
24+
}) {
25+
defaultDomRect = new DOMRect(0, 0, width, height);
26+
27+
emitter.emit("change");
28+
}
29+
30+
export function setElementSizeFunction(value: GetDOMRect) {
31+
getDOMRect = value;
32+
33+
emitter.emit("change");
1634
}
1735

18-
export function updateMockResizeObserver({
36+
export function setElementSize({
37+
element,
1938
height,
20-
target,
2139
width
2240
}: {
23-
height?: number;
24-
target?: HTMLElement;
25-
width?: number;
26-
}): void {
27-
entrySize = new DOMRect(
28-
0,
29-
0,
30-
width ?? entrySize.width,
31-
height ?? entrySize.height
32-
);
33-
34-
emitter.emit("change", target);
41+
element: HTMLElement;
42+
height: number;
43+
width: number;
44+
}) {
45+
elementToDOMRect.set(element, new DOMRect(0, 0, width, height));
46+
47+
emitter.emit("change", element);
48+
}
49+
50+
export function simulateUnsupportedEnvironmentForTest() {
51+
// @ts-expect-error Simulate API being unsupported
52+
window.ResizeObserver = null;
3553
}
3654

3755
export function mockResizeObserver() {
3856
disabled = false;
3957

4058
const originalResizeObserver = window.ResizeObserver;
4159

42-
window.ResizeObserver = class implements ResizeObserver {
43-
readonly #callback: ResizeObserverCallback;
44-
#disconnected: boolean = false;
45-
#elements: Set<HTMLElement> = new Set();
60+
window.ResizeObserver = MockResizeObserver;
4661

47-
constructor(callback: ResizeObserverCallback) {
48-
this.#callback = callback;
62+
return function unmockResizeObserver() {
63+
window.ResizeObserver = originalResizeObserver;
4964

50-
emitter.addListener("change", this.#onChange);
51-
}
65+
defaultDomRect = new DOMRect(0, 0, 0, 0);
66+
disabled = false;
67+
getDOMRect = undefined;
5268

53-
observe(element: HTMLElement) {
54-
if (this.#disconnected) {
55-
return;
56-
}
69+
elementToDOMRect.clear();
70+
};
71+
}
5772

58-
this.#elements.add(element);
59-
this.#notify(element);
60-
}
73+
class MockResizeObserver implements ResizeObserver {
74+
readonly #callback: ResizeObserverCallback;
75+
#disconnected: boolean = false;
76+
#elements: Set<HTMLElement> = new Set();
6177

62-
unobserve(element: HTMLElement) {
63-
this.#elements.delete(element);
78+
constructor(callback: ResizeObserverCallback) {
79+
this.#callback = callback;
80+
81+
emitter.addListener("change", this.#onChange);
82+
}
83+
84+
observe(element: HTMLElement) {
85+
if (this.#disconnected) {
86+
return;
6487
}
6588

66-
disconnect() {
67-
this.#disconnected = true;
68-
this.#elements.clear();
89+
this.#elements.add(element);
90+
this.#notify([element]);
91+
}
92+
93+
unobserve(element: HTMLElement) {
94+
this.#elements.delete(element);
95+
}
6996

70-
emitter.removeListener("change", this.#onChange);
97+
disconnect() {
98+
this.#disconnected = true;
99+
this.#elements.clear();
100+
101+
emitter.removeListener("change", this.#onChange);
102+
}
103+
104+
#notify(elements: HTMLElement[]) {
105+
if (disabled) {
106+
return;
71107
}
72108

73-
#notify(target: HTMLElement) {
74-
if (disabled) {
75-
return;
109+
const entries = elements.map((element) => {
110+
const computedStyle = window.getComputedStyle(element);
111+
const writingMode = computedStyle.writingMode;
112+
113+
let contentRect: DOMRectReadOnly =
114+
elementToDOMRect.get(element) ?? defaultDomRect;
115+
116+
if (getDOMRect) {
117+
const contentRectOverride = getDOMRect(element);
118+
if (contentRectOverride) {
119+
contentRect = contentRectOverride;
120+
}
76121
}
77122

78-
this.#callback(
79-
[
123+
let blockSize = 0;
124+
let inlineSize = 0;
125+
if (writingMode.includes("vertical")) {
126+
blockSize = contentRect.width;
127+
inlineSize = contentRect.height;
128+
} else {
129+
blockSize = contentRect.height;
130+
inlineSize = contentRect.width;
131+
}
132+
133+
return {
134+
borderBoxSize: [
80135
{
81-
borderBoxSize: [],
82-
contentBoxSize: [],
83-
contentRect: entrySize,
84-
devicePixelContentBoxSize: [],
85-
target
136+
blockSize,
137+
inlineSize
86138
}
87139
],
88-
this
89-
);
90-
}
91-
92-
#onChange = (target?: HTMLElement) => {
93-
if (target) {
94-
this.#notify(target);
95-
} else {
96-
this.#elements.forEach((element) => this.#notify(element));
140+
contentBoxSize: [],
141+
contentRect,
142+
devicePixelContentBoxSize: [],
143+
target: element
144+
};
145+
});
146+
147+
this.#callback(entries, this);
148+
}
149+
150+
#onChange = (target?: HTMLElement) => {
151+
if (target) {
152+
if (this.#elements.has(target)) {
153+
this.#notify([target]);
97154
}
98-
};
99-
};
100-
101-
return function unmockResizeObserver() {
102-
window.ResizeObserver = originalResizeObserver;
155+
} else {
156+
this.#notify(Array.from(this.#elements));
157+
}
103158
};
104159
}

0 commit comments

Comments
 (0)