@praxisjs/composables 0.1.5 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/dist/__tests__/browser.test.js +289 -81
- package/dist/__tests__/browser.test.js.map +1 -1
- package/dist/__tests__/dom.test.js +282 -118
- package/dist/__tests__/dom.test.js.map +1 -1
- package/dist/__tests__/utilities.test.js +198 -193
- package/dist/__tests__/utilities.test.js.map +1 -1
- package/dist/browser.d.ts +48 -12
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +130 -49
- package/dist/browser.js.map +1 -1
- package/dist/dom.d.ts +52 -25
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +123 -74
- package/dist/dom.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/utilities.d.ts +72 -26
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +117 -94
- package/dist/utilities.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/browser.test.ts +329 -89
- package/src/__tests__/dom.test.ts +325 -132
- package/src/__tests__/utilities.test.ts +249 -215
- package/src/browser.ts +161 -53
- package/src/dom.ts +145 -82
- package/src/index.ts +11 -18
- package/src/utilities.ts +162 -111
|
@@ -1,212 +1,405 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import {
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { signal } from "@praxisjs/core/internal";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { WindowSize, ScrollPosition, ElementSize, Intersection, Focus } from "../dom";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
it("starts with current = null", () => {
|
|
10
|
-
const ref = createRef();
|
|
11
|
-
expect(ref.current).toBeNull();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("can be assigned an element", () => {
|
|
15
|
-
const ref = createRef();
|
|
16
|
-
const el = document.createElement("div");
|
|
17
|
-
ref.current = el;
|
|
18
|
-
expect(ref.current).toBe(el);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
8
|
+
// ── WindowSize ────────────────────────────────────────────────────────────────
|
|
21
9
|
|
|
22
|
-
|
|
10
|
+
describe("WindowSize", () => {
|
|
11
|
+
it("reads initial window dimensions", () => {
|
|
12
|
+
Object.defineProperty(window, "innerWidth", { value: 1024, configurable: true });
|
|
13
|
+
Object.defineProperty(window, "innerHeight", { value: 768, configurable: true });
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1024 });
|
|
27
|
-
Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 768 });
|
|
28
|
-
|
|
29
|
-
const { width, height } = useWindowSize();
|
|
15
|
+
const ws = new WindowSize();
|
|
16
|
+
const { width, height } = ws.setup() as { width: () => number; height: () => number };
|
|
30
17
|
expect(width()).toBe(1024);
|
|
31
18
|
expect(height()).toBe(768);
|
|
32
19
|
});
|
|
33
20
|
|
|
34
21
|
it("updates on resize event", () => {
|
|
35
|
-
Object.defineProperty(window, "innerWidth", {
|
|
36
|
-
Object.defineProperty(window, "innerHeight", {
|
|
22
|
+
Object.defineProperty(window, "innerWidth", { value: 800, configurable: true });
|
|
23
|
+
Object.defineProperty(window, "innerHeight", { value: 600, configurable: true });
|
|
37
24
|
|
|
38
|
-
const
|
|
25
|
+
const ws = new WindowSize();
|
|
26
|
+
const { width, height } = ws.setup() as { width: () => number; height: () => number };
|
|
39
27
|
|
|
40
|
-
Object.defineProperty(window, "innerWidth", {
|
|
41
|
-
Object.defineProperty(window, "innerHeight", {
|
|
28
|
+
Object.defineProperty(window, "innerWidth", { value: 1280, configurable: true });
|
|
29
|
+
Object.defineProperty(window, "innerHeight", { value: 900, configurable: true });
|
|
42
30
|
window.dispatchEvent(new Event("resize"));
|
|
43
31
|
|
|
44
32
|
expect(width()).toBe(1280);
|
|
45
|
-
expect(height()).toBe(
|
|
33
|
+
expect(height()).toBe(900);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("removes listener on unmount", () => {
|
|
37
|
+
const remove = vi.spyOn(window, "removeEventListener");
|
|
38
|
+
const ws = new WindowSize();
|
|
39
|
+
ws.setup();
|
|
40
|
+
ws.onUnmount();
|
|
41
|
+
expect(remove).toHaveBeenCalledWith("resize", expect.any(Function));
|
|
46
42
|
});
|
|
47
43
|
});
|
|
48
44
|
|
|
49
|
-
//
|
|
45
|
+
// ── ScrollPosition ────────────────────────────────────────────────────────────
|
|
50
46
|
|
|
51
|
-
describe("
|
|
47
|
+
describe("ScrollPosition", () => {
|
|
52
48
|
it("starts at (0, 0)", () => {
|
|
53
|
-
const
|
|
49
|
+
const sp = new ScrollPosition(window);
|
|
50
|
+
const { x, y } = sp.setup() as { x: () => number; y: () => number };
|
|
54
51
|
expect(x()).toBe(0);
|
|
55
52
|
expect(y()).toBe(0);
|
|
56
53
|
});
|
|
57
54
|
|
|
58
|
-
it("updates on
|
|
55
|
+
it("updates on scroll event for window", () => {
|
|
56
|
+
const sp = new ScrollPosition(window);
|
|
57
|
+
const { x, y } = sp.setup() as { x: () => number; y: () => number };
|
|
58
|
+
|
|
59
|
+
Object.defineProperty(window, "scrollX", { value: 100, configurable: true });
|
|
60
|
+
Object.defineProperty(window, "scrollY", { value: 200, configurable: true });
|
|
61
|
+
window.dispatchEvent(new Event("scroll"));
|
|
62
|
+
|
|
63
|
+
expect(x()).toBe(100);
|
|
64
|
+
expect(y()).toBe(200);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("updates on scroll event for element", () => {
|
|
59
68
|
const el = document.createElement("div");
|
|
60
|
-
const
|
|
69
|
+
const sp = new ScrollPosition(el);
|
|
70
|
+
const { x, y } = sp.setup() as { x: () => number; y: () => number };
|
|
61
71
|
|
|
62
|
-
Object.defineProperty(el, "scrollLeft", {
|
|
63
|
-
Object.defineProperty(el, "scrollTop", {
|
|
72
|
+
Object.defineProperty(el, "scrollLeft", { value: 50, configurable: true });
|
|
73
|
+
Object.defineProperty(el, "scrollTop", { value: 75, configurable: true });
|
|
64
74
|
el.dispatchEvent(new Event("scroll"));
|
|
65
75
|
|
|
66
76
|
expect(x()).toBe(50);
|
|
67
|
-
expect(y()).toBe(
|
|
77
|
+
expect(y()).toBe(75);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("removes listener on unmount", () => {
|
|
81
|
+
const el = document.createElement("div");
|
|
82
|
+
const remove = vi.spyOn(el, "removeEventListener");
|
|
83
|
+
const sp = new ScrollPosition(el);
|
|
84
|
+
sp.setup();
|
|
85
|
+
sp.onUnmount();
|
|
86
|
+
expect(remove).toHaveBeenCalledWith("scroll", expect.any(Function));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ── ElementSize ───────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
describe("ElementSize", () => {
|
|
93
|
+
let observerCallback: ResizeObserverCallback;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
vi.stubGlobal("ResizeObserver", class {
|
|
97
|
+
constructor(cb: ResizeObserverCallback) { observerCallback = cb; }
|
|
98
|
+
observe = vi.fn();
|
|
99
|
+
disconnect = vi.fn();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("starts at (0, 0) with no element", () => {
|
|
104
|
+
const ref = { current: null };
|
|
105
|
+
const es = new ElementSize(ref);
|
|
106
|
+
const { width, height } = es.setup() as { width: () => number; height: () => number };
|
|
107
|
+
expect(width()).toBe(0);
|
|
108
|
+
expect(height()).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("updates via ResizeObserver callback", () => {
|
|
112
|
+
const el = document.createElement("div");
|
|
113
|
+
const ref = { current: el };
|
|
114
|
+
const es = new ElementSize(ref);
|
|
115
|
+
const { width, height } = es.setup() as { width: () => number; height: () => number };
|
|
116
|
+
|
|
117
|
+
observerCallback(
|
|
118
|
+
[{ contentRect: { width: 300, height: 150 } } as ResizeObserverEntry],
|
|
119
|
+
{} as ResizeObserver,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(width()).toBe(300);
|
|
123
|
+
expect(height()).toBe(150);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("disconnects observer on unmount", () => {
|
|
127
|
+
const disconnect = vi.fn();
|
|
128
|
+
vi.stubGlobal("ResizeObserver", class {
|
|
129
|
+
constructor(cb: ResizeObserverCallback) { observerCallback = cb; }
|
|
130
|
+
observe = vi.fn();
|
|
131
|
+
disconnect = disconnect;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const ref = { current: null };
|
|
135
|
+
const es = new ElementSize(ref);
|
|
136
|
+
es.setup();
|
|
137
|
+
es.onUnmount();
|
|
138
|
+
expect(disconnect).toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ── Intersection ──────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
describe("Intersection", () => {
|
|
145
|
+
let observerCallback: IntersectionObserverCallback;
|
|
146
|
+
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
vi.stubGlobal("IntersectionObserver", class {
|
|
149
|
+
constructor(cb: IntersectionObserverCallback) { observerCallback = cb; }
|
|
150
|
+
observe = vi.fn();
|
|
151
|
+
disconnect = vi.fn();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("starts as not visible", () => {
|
|
156
|
+
const ref = { current: null };
|
|
157
|
+
const int = new Intersection(ref);
|
|
158
|
+
const { visible } = int.setup() as { visible: () => boolean };
|
|
159
|
+
expect(visible()).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("becomes visible when intersecting", () => {
|
|
163
|
+
const el = document.createElement("div");
|
|
164
|
+
const ref = { current: el };
|
|
165
|
+
const int = new Intersection(ref);
|
|
166
|
+
const { visible } = int.setup() as { visible: () => boolean };
|
|
167
|
+
|
|
168
|
+
observerCallback(
|
|
169
|
+
[{ isIntersecting: true } as IntersectionObserverEntry],
|
|
170
|
+
{} as IntersectionObserver,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(visible()).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("disconnects on unmount", () => {
|
|
177
|
+
const disconnect = vi.fn();
|
|
178
|
+
vi.stubGlobal("IntersectionObserver", class {
|
|
179
|
+
constructor(cb: IntersectionObserverCallback) { observerCallback = cb; }
|
|
180
|
+
observe = vi.fn();
|
|
181
|
+
disconnect = disconnect;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const ref = { current: null };
|
|
185
|
+
const int = new Intersection(ref);
|
|
186
|
+
int.setup();
|
|
187
|
+
int.onUnmount();
|
|
188
|
+
expect(disconnect).toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ── ElementSize (additional) ──────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
describe("ElementSize (additional)", () => {
|
|
195
|
+
it("ref.current changes to a new element: old observer is disconnected and new one started", () => {
|
|
196
|
+
const disconnectFn = vi.fn();
|
|
197
|
+
const observeFn = vi.fn();
|
|
198
|
+
|
|
199
|
+
vi.stubGlobal("ResizeObserver", class {
|
|
200
|
+
observe = observeFn;
|
|
201
|
+
disconnect = disconnectFn;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const el1 = document.createElement("div");
|
|
205
|
+
const ref = { current: el1 as HTMLElement | null };
|
|
206
|
+
const es = new ElementSize(ref);
|
|
207
|
+
es.setup();
|
|
208
|
+
|
|
209
|
+
// Disconnect old observer and create new one for el2
|
|
210
|
+
es.onUnmount();
|
|
211
|
+
expect(disconnectFn).toHaveBeenCalled();
|
|
212
|
+
|
|
213
|
+
const el2 = document.createElement("div");
|
|
214
|
+
ref.current = el2;
|
|
215
|
+
const es2 = new ElementSize(ref);
|
|
216
|
+
es2.setup();
|
|
217
|
+
expect(observeFn).toHaveBeenCalledWith(el2);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ── Intersection (additional) ─────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
describe("Intersection (additional)", () => {
|
|
224
|
+
let observerCallback: IntersectionObserverCallback;
|
|
225
|
+
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
vi.stubGlobal("IntersectionObserver", class {
|
|
228
|
+
constructor(cb: IntersectionObserverCallback) { observerCallback = cb; }
|
|
229
|
+
observe = vi.fn();
|
|
230
|
+
disconnect = vi.fn();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("visibility toggles false after being true", () => {
|
|
235
|
+
const el = document.createElement("div");
|
|
236
|
+
const ref = { current: el };
|
|
237
|
+
const int = new Intersection(ref);
|
|
238
|
+
const { visible } = int.setup() as { visible: () => boolean };
|
|
239
|
+
|
|
240
|
+
observerCallback(
|
|
241
|
+
[{ isIntersecting: true } as IntersectionObserverEntry],
|
|
242
|
+
{} as IntersectionObserver,
|
|
243
|
+
);
|
|
244
|
+
expect(visible()).toBe(true);
|
|
245
|
+
|
|
246
|
+
observerCallback(
|
|
247
|
+
[{ isIntersecting: false } as IntersectionObserverEntry],
|
|
248
|
+
{} as IntersectionObserver,
|
|
249
|
+
);
|
|
250
|
+
expect(visible()).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ── ScrollPosition (additional) ───────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
describe("ScrollPosition (additional)", () => {
|
|
257
|
+
it("setup() called twice registers only one scroll listener", () => {
|
|
258
|
+
const el = document.createElement("div");
|
|
259
|
+
const add = vi.spyOn(el, "addEventListener");
|
|
260
|
+
const sp = new ScrollPosition(el);
|
|
261
|
+
sp.setup();
|
|
262
|
+
sp.setup();
|
|
263
|
+
const scrollCalls = add.mock.calls.filter((c) => c[0] === "scroll").length;
|
|
264
|
+
expect(scrollCalls).toBe(1);
|
|
68
265
|
});
|
|
69
266
|
});
|
|
70
267
|
|
|
71
|
-
//
|
|
268
|
+
// ── Focus ─────────────────────────────────────────────────────────────────────
|
|
72
269
|
|
|
73
|
-
describe("
|
|
270
|
+
describe("Focus", () => {
|
|
74
271
|
it("starts as not focused", () => {
|
|
75
272
|
const el = document.createElement("input");
|
|
76
|
-
document.body.appendChild(el);
|
|
77
273
|
const ref = { current: el };
|
|
78
|
-
const
|
|
274
|
+
const focus = new Focus(ref);
|
|
275
|
+
const { focused } = focus.setup() as { focused: () => boolean };
|
|
79
276
|
expect(focused()).toBe(false);
|
|
80
|
-
document.body.removeChild(el);
|
|
81
277
|
});
|
|
82
278
|
|
|
83
|
-
it("
|
|
279
|
+
it("sets focused=true on focus event", () => {
|
|
84
280
|
const el = document.createElement("input");
|
|
85
281
|
document.body.appendChild(el);
|
|
86
282
|
const ref = { current: el };
|
|
87
|
-
const
|
|
88
|
-
|
|
283
|
+
const focus = new Focus(ref);
|
|
284
|
+
const { focused } = focus.setup() as { focused: () => boolean };
|
|
285
|
+
|
|
286
|
+
el.dispatchEvent(new Event("focus"));
|
|
89
287
|
expect(focused()).toBe(true);
|
|
288
|
+
|
|
90
289
|
document.body.removeChild(el);
|
|
91
290
|
});
|
|
92
291
|
|
|
93
|
-
it("
|
|
292
|
+
it("sets focused=false on blur event", () => {
|
|
94
293
|
const el = document.createElement("input");
|
|
95
294
|
document.body.appendChild(el);
|
|
96
295
|
const ref = { current: el };
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
296
|
+
const focus = new Focus(ref);
|
|
297
|
+
const { focused } = focus.setup() as { focused: () => boolean };
|
|
298
|
+
|
|
299
|
+
el.dispatchEvent(new Event("focus"));
|
|
300
|
+
el.dispatchEvent(new Event("blur"));
|
|
100
301
|
expect(focused()).toBe(false);
|
|
302
|
+
|
|
101
303
|
document.body.removeChild(el);
|
|
102
304
|
});
|
|
103
305
|
|
|
104
|
-
it("does
|
|
105
|
-
const ref
|
|
106
|
-
|
|
306
|
+
it("does not throw when ref is null", () => {
|
|
307
|
+
const ref = { current: null };
|
|
308
|
+
const focus = new Focus(ref);
|
|
309
|
+
expect(() => focus.setup()).not.toThrow();
|
|
107
310
|
});
|
|
108
|
-
});
|
|
109
311
|
|
|
110
|
-
|
|
312
|
+
it("removes focus/blur listeners when reactive ref changes (effect cleanup)", () => {
|
|
313
|
+
const el1 = document.createElement("input");
|
|
314
|
+
document.body.appendChild(el1);
|
|
111
315
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
316
|
+
// Make ref.current reactive so the effect tracks it and re-runs on change
|
|
317
|
+
const refSignal = signal<HTMLElement | null>(el1);
|
|
318
|
+
const ref = { get current() { return refSignal(); } };
|
|
115
319
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
observerCallback = () => {};
|
|
320
|
+
const focus = new Focus(ref);
|
|
321
|
+
focus.setup();
|
|
119
322
|
|
|
120
|
-
|
|
121
|
-
globalThis.ResizeObserver = class {
|
|
122
|
-
constructor(cb: ResizeObserverCallback) { observerCallback = cb; }
|
|
123
|
-
observe = vi.fn();
|
|
124
|
-
disconnect = mockDisconnect;
|
|
125
|
-
} as unknown as typeof ResizeObserver;
|
|
126
|
-
});
|
|
323
|
+
const removeEl1 = vi.spyOn(el1, "removeEventListener");
|
|
127
324
|
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
325
|
+
// Changing the signal causes the effect to re-run, calling cleanup for el1
|
|
326
|
+
// This covers the cleanup function at dom.ts:134-135
|
|
327
|
+
const el2 = document.createElement("input");
|
|
328
|
+
document.body.appendChild(el2);
|
|
329
|
+
refSignal.set(el2);
|
|
132
330
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const { width, height } = useElementSize(ref);
|
|
136
|
-
expect(width()).toBe(0);
|
|
137
|
-
expect(height()).toBe(0);
|
|
138
|
-
});
|
|
331
|
+
expect(removeEl1).toHaveBeenCalledWith("focus", expect.any(Function));
|
|
332
|
+
expect(removeEl1).toHaveBeenCalledWith("blur", expect.any(Function));
|
|
139
333
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const ref = { current: el };
|
|
143
|
-
const { stop } = useElementSize(ref);
|
|
144
|
-
stop();
|
|
145
|
-
expect(mockDisconnect).toHaveBeenCalled();
|
|
334
|
+
document.body.removeChild(el1);
|
|
335
|
+
document.body.removeChild(el2);
|
|
146
336
|
});
|
|
147
337
|
|
|
148
|
-
it("
|
|
149
|
-
const el = document.createElement("
|
|
338
|
+
it("setup() called twice registers only one listener pair", () => {
|
|
339
|
+
const el = document.createElement("input");
|
|
340
|
+
document.body.appendChild(el);
|
|
341
|
+
const add = vi.spyOn(el, "addEventListener");
|
|
150
342
|
const ref = { current: el };
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
expect(width()).toBe(300);
|
|
159
|
-
expect(height()).toBe(200);
|
|
343
|
+
const focus = new Focus(ref);
|
|
344
|
+
focus.setup();
|
|
345
|
+
focus.setup();
|
|
346
|
+
const focusCalls = add.mock.calls.filter((c) => c[0] === "focus").length;
|
|
347
|
+
expect(focusCalls).toBe(1);
|
|
348
|
+
document.body.removeChild(el);
|
|
160
349
|
});
|
|
161
350
|
});
|
|
162
351
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
describe("useIntersection", () => {
|
|
166
|
-
let intersectionCallback: IntersectionObserverCallback = () => {};
|
|
167
|
-
let capturedOptions: IntersectionObserverInit | undefined;
|
|
352
|
+
// ── WindowSize (additional) ───────────────────────────────────────────────────
|
|
168
353
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
capturedOptions = opts;
|
|
177
|
-
}
|
|
178
|
-
observe = vi.fn();
|
|
179
|
-
disconnect = vi.fn();
|
|
180
|
-
} as unknown as typeof IntersectionObserver;
|
|
354
|
+
describe("WindowSize (additional)", () => {
|
|
355
|
+
it("setup() called twice returns the same view object", () => {
|
|
356
|
+
const ws = new WindowSize();
|
|
357
|
+
const view1 = ws.setup();
|
|
358
|
+
const view2 = ws.setup();
|
|
359
|
+
expect(view1).toBe(view2);
|
|
360
|
+
ws.onUnmount();
|
|
181
361
|
});
|
|
182
362
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
363
|
+
it("setup() called twice registers only one resize listener", () => {
|
|
364
|
+
const add = vi.spyOn(window, "addEventListener");
|
|
365
|
+
const ws = new WindowSize();
|
|
366
|
+
ws.setup();
|
|
367
|
+
ws.setup();
|
|
368
|
+
const resizeCalls = add.mock.calls.filter((c) => c[0] === "resize").length;
|
|
369
|
+
expect(resizeCalls).toBe(1);
|
|
370
|
+
ws.onUnmount();
|
|
371
|
+
vi.restoreAllMocks();
|
|
186
372
|
});
|
|
373
|
+
});
|
|
187
374
|
|
|
188
|
-
|
|
189
|
-
const ref: { current: HTMLElement | null } = { current: null };
|
|
190
|
-
const visible = useIntersection(ref);
|
|
191
|
-
expect(visible()).toBe(false);
|
|
192
|
-
});
|
|
375
|
+
// ── ElementSize (cache) ───────────────────────────────────────────────────────
|
|
193
376
|
|
|
194
|
-
|
|
377
|
+
describe("ElementSize (cache)", () => {
|
|
378
|
+
it("setup() called twice returns the same view object", () => {
|
|
195
379
|
const el = document.createElement("div");
|
|
380
|
+
document.body.appendChild(el);
|
|
196
381
|
const ref = { current: el };
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
203
|
-
expect(visible()).toBe(true);
|
|
382
|
+
const es = new ElementSize(ref);
|
|
383
|
+
const view1 = es.setup();
|
|
384
|
+
const view2 = es.setup();
|
|
385
|
+
expect(view1).toBe(view2);
|
|
386
|
+
es.onUnmount();
|
|
387
|
+
document.body.removeChild(el);
|
|
204
388
|
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// ── Intersection (cache) ──────────────────────────────────────────────────────
|
|
205
392
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
393
|
+
describe("Intersection (cache)", () => {
|
|
394
|
+
it("setup() called twice returns the same view object", () => {
|
|
395
|
+
const el = document.createElement("div");
|
|
396
|
+
document.body.appendChild(el);
|
|
397
|
+
const ref = { current: el };
|
|
398
|
+
const inter = new Intersection(ref);
|
|
399
|
+
const view1 = inter.setup();
|
|
400
|
+
const view2 = inter.setup();
|
|
401
|
+
expect(view1).toBe(view2);
|
|
402
|
+
inter.onUnmount();
|
|
403
|
+
document.body.removeChild(el);
|
|
211
404
|
});
|
|
212
405
|
});
|