@sveltia/ui 0.36.1 → 0.37.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.
@@ -1 +0,0 @@
1
- export {};
@@ -1,18 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { initLocales } from './i18n.js';
3
-
4
- describe('initLocales', () => {
5
- it('should not throw when called with default options', () => {
6
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
7
-
8
- expect(() => initLocales()).not.toThrow();
9
- warnSpy.mockRestore();
10
- });
11
-
12
- it('should not throw when called with custom locale options', () => {
13
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
14
-
15
- expect(() => initLocales({ fallbackLocale: 'en', initialLocale: 'ja' })).not.toThrow();
16
- warnSpy.mockRestore();
17
- });
18
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,641 +0,0 @@
1
- /* eslint-disable jsdoc/require-description */
2
- /* eslint-disable jsdoc/require-jsdoc */
3
- /* eslint-disable jsdoc/require-param */
4
- /* eslint-disable jsdoc/require-param-description */
5
- /* eslint-disable jsdoc/require-returns */
6
- /* eslint-disable lines-between-class-members */
7
- /* eslint-disable max-classes-per-file */
8
-
9
- import { get } from 'svelte/store';
10
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
- import { activatePopup } from './popup.svelte.js';
12
-
13
- describe('Popup', () => {
14
- /** @type {HTMLButtonElement} */
15
- let anchor;
16
- /** @type {HTMLDialogElement} */
17
- let popup;
18
-
19
- beforeEach(() => {
20
- anchor = /** @type {HTMLButtonElement} */ (document.createElement('button'));
21
- popup = /** @type {HTMLDialogElement} */ (document.createElement('dialog'));
22
- document.body.appendChild(anchor);
23
- document.body.appendChild(popup);
24
- });
25
-
26
- afterEach(() => {
27
- anchor.remove();
28
- popup.remove();
29
- });
30
-
31
- it('should assign an id to the popup element', () => {
32
- activatePopup(anchor, popup, 'bottom-left');
33
- expect(popup.id).toBeTruthy();
34
- });
35
-
36
- it('should set aria-controls on the anchor to match the popup id', () => {
37
- activatePopup(anchor, popup, 'bottom-left');
38
- expect(anchor.getAttribute('aria-controls')).toBe(popup.id);
39
- });
40
-
41
- it('should expose the open store defaulting to false', () => {
42
- const instance = activatePopup(anchor, popup, 'bottom-left');
43
-
44
- expect(get(instance.open)).toBe(false);
45
- });
46
-
47
- it('should set aria-expanded to false initially', () => {
48
- activatePopup(anchor, popup, 'bottom-left');
49
- expect(anchor.getAttribute('aria-expanded')).toBe('false');
50
- });
51
-
52
- it('should toggle open store to true on anchor click', () => {
53
- const instance = activatePopup(anchor, popup, 'bottom-left');
54
-
55
- anchor.click();
56
- expect(get(instance.open)).toBe(true);
57
- });
58
-
59
- it('should set aria-expanded to true after anchor click', () => {
60
- activatePopup(anchor, popup, 'bottom-left');
61
- anchor.click();
62
- expect(anchor.getAttribute('aria-expanded')).toBe('true');
63
- });
64
-
65
- it('should not toggle open when anchor is disabled', () => {
66
- const instance = activatePopup(anchor, popup, 'bottom-left');
67
-
68
- anchor.setAttribute('aria-disabled', 'true');
69
- anchor.click();
70
- expect(get(instance.open)).toBe(false);
71
- });
72
-
73
- it('should not toggle open when anchor is read-only', () => {
74
- const instance = activatePopup(anchor, popup, 'bottom-left');
75
-
76
- anchor.setAttribute('aria-readonly', 'true');
77
- anchor.click();
78
- expect(get(instance.open)).toBe(false);
79
- });
80
-
81
- it('should report isDisabled as false when not disabled', () => {
82
- const instance = activatePopup(anchor, popup, 'bottom-left');
83
-
84
- expect(instance.isDisabled).toBe(false);
85
- });
86
-
87
- it('should report isDisabled as true when aria-disabled is set', () => {
88
- const instance = activatePopup(anchor, popup, 'bottom-left');
89
-
90
- anchor.setAttribute('aria-disabled', 'true');
91
- expect(instance.isDisabled).toBe(true);
92
- });
93
-
94
- it('should report isReadOnly as false when not read-only', () => {
95
- const instance = activatePopup(anchor, popup, 'bottom-left');
96
-
97
- expect(instance.isReadOnly).toBe(false);
98
- });
99
-
100
- it('should report isReadOnly as true when aria-readonly is set', () => {
101
- const instance = activatePopup(anchor, popup, 'bottom-left');
102
-
103
- anchor.setAttribute('aria-readonly', 'true');
104
- expect(instance.isReadOnly).toBe(true);
105
- });
106
-
107
- it('should close on Escape keydown on popup', () => {
108
- const instance = activatePopup(anchor, popup, 'bottom-left');
109
-
110
- anchor.click(); // open first
111
- popup.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: false }));
112
- expect(get(instance.open)).toBe(false);
113
- });
114
-
115
- it('should close when a menu option inside popup is clicked', () => {
116
- const instance = activatePopup(anchor, popup, 'bottom-left');
117
- const menuItem = document.createElement('div');
118
-
119
- menuItem.setAttribute('role', 'menuitem');
120
- popup.appendChild(menuItem);
121
- anchor.click(); // open
122
- menuItem.dispatchEvent(new MouseEvent('click', { bubbles: true }));
123
- expect(get(instance.open)).toBe(false);
124
- });
125
-
126
- it('should not close popup when clicking a non-menuitem element inside it (branch 35 false)', () => {
127
- const instance = activatePopup(anchor, popup, 'bottom-left');
128
- const div = document.createElement('div');
129
-
130
- popup.appendChild(div);
131
- anchor.click(); // open
132
- div.dispatchEvent(new MouseEvent('click', { bubbles: true }));
133
- // div has no role → neither menuitem nor popup backdrop → popup stays open
134
- expect(get(instance.open)).toBe(true);
135
- div.remove();
136
- });
137
-
138
- it('should not close popup on Escape with modifier key held (branch 38 false)', () => {
139
- const instance = activatePopup(anchor, popup, 'bottom-left');
140
-
141
- anchor.click(); // open
142
- popup.dispatchEvent(
143
- new KeyboardEvent('keydown', { key: 'Escape', shiftKey: true, bubbles: false }),
144
- );
145
- // hasModifier=true → condition false → popup stays open
146
- expect(get(instance.open)).toBe(true);
147
- });
148
-
149
- it('should toggle open to true on Enter keydown on anchor', () => {
150
- const instance = activatePopup(anchor, popup, 'bottom-left');
151
-
152
- anchor.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
153
- expect(get(instance.open)).toBe(true);
154
- });
155
-
156
- it('should toggle open to true on Space keydown on anchor', () => {
157
- const instance = activatePopup(anchor, popup, 'bottom-left');
158
-
159
- anchor.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }));
160
- expect(get(instance.open)).toBe(true);
161
- });
162
-
163
- it('should not toggle open when anchor keydown has a modifier key', () => {
164
- const instance = activatePopup(anchor, popup, 'bottom-left');
165
-
166
- anchor.dispatchEvent(
167
- new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true, bubbles: true }),
168
- );
169
- expect(get(instance.open)).toBe(false);
170
- });
171
-
172
- it('should close on click directly on the popup backdrop element', () => {
173
- const instance = activatePopup(anchor, popup, 'bottom-left');
174
-
175
- anchor.click(); // open
176
- popup.dispatchEvent(new MouseEvent('click', { bubbles: false }));
177
- expect(get(instance.open)).toBe(false);
178
- });
179
-
180
- it('should remove aria-controls from anchor when popup closes after being open', () => {
181
- activatePopup(anchor, popup, 'bottom-left');
182
- anchor.click(); // open → aria-expanded='true'
183
- anchor.click(); // close → aria-controls should be removed
184
- expect(anchor.getAttribute('aria-controls')).toBeNull();
185
- });
186
-
187
- it('should set aria-expanded to false after closing', () => {
188
- activatePopup(anchor, popup, 'bottom-left');
189
- anchor.click(); // open
190
- anchor.click(); // close
191
- expect(anchor.getAttribute('aria-expanded')).toBe('false');
192
- });
193
- });
194
-
195
- describe('Popup - hideImmediately', () => {
196
- /** @type {HTMLButtonElement} */
197
- let anchor;
198
- /** @type {HTMLDialogElement} */
199
- let popup;
200
-
201
- beforeEach(() => {
202
- vi.useFakeTimers();
203
- anchor = /** @type {HTMLButtonElement} */ (document.createElement('button'));
204
- popup = /** @type {HTMLDialogElement} */ (document.createElement('dialog'));
205
- document.body.appendChild(anchor);
206
- document.body.appendChild(popup);
207
- });
208
-
209
- afterEach(() => {
210
- anchor.remove();
211
- popup.remove();
212
- vi.useRealTimers();
213
- });
214
-
215
- it('should set open to false immediately when hideImmediately is called', async () => {
216
- const instance = activatePopup(anchor, popup, 'bottom-left');
217
-
218
- anchor.click();
219
- expect(get(instance.open)).toBe(true);
220
-
221
- const hidePromise = instance.hideImmediately();
222
-
223
- expect(get(instance.open)).toBe(false);
224
- await vi.advanceTimersByTimeAsync(100);
225
- await hidePromise;
226
- });
227
-
228
- it('should temporarily set popup.hidden and then restore it', async () => {
229
- const instance = activatePopup(anchor, popup, 'bottom-left');
230
-
231
- anchor.click();
232
-
233
- const hidePromise = instance.hideImmediately();
234
-
235
- expect(popup.hidden).toBe(true);
236
- await vi.advanceTimersByTimeAsync(100);
237
- await hidePromise;
238
- expect(popup.hidden).toBe(false);
239
- });
240
- });
241
-
242
- describe('Popup - transitionstart', () => {
243
- /** @type {HTMLButtonElement} */
244
- let anchor;
245
- /** @type {HTMLDialogElement} */
246
- let popup;
247
- /** @type {HTMLDivElement} */
248
- let wrapper;
249
-
250
- beforeEach(() => {
251
- vi.useFakeTimers();
252
- anchor = /** @type {HTMLButtonElement} */ (document.createElement('button'));
253
- popup = /** @type {HTMLDialogElement} */ (document.createElement('dialog'));
254
- wrapper = document.createElement('div');
255
- wrapper.appendChild(anchor);
256
- document.body.appendChild(wrapper);
257
- document.body.appendChild(popup);
258
- });
259
-
260
- afterEach(() => {
261
- wrapper.remove();
262
- popup.remove();
263
- vi.useRealTimers();
264
- });
265
-
266
- it('should hide popup when anchor transitions inside a .hiding ancestor', async () => {
267
- const instance = activatePopup(anchor, popup, 'bottom-left');
268
-
269
- anchor.click();
270
- expect(get(instance.open)).toBe(true);
271
- wrapper.classList.add('hiding');
272
- anchor.dispatchEvent(new Event('transitionstart'));
273
- expect(get(instance.open)).toBe(false);
274
- wrapper.classList.remove('hiding');
275
- await vi.advanceTimersByTimeAsync(100);
276
- });
277
-
278
- it('should not hide popup on transitionstart when no hiding ancestor', async () => {
279
- const instance = activatePopup(anchor, popup, 'bottom-left');
280
-
281
- anchor.click();
282
- anchor.dispatchEvent(new Event('transitionstart'));
283
- expect(get(instance.open)).toBe(true);
284
- await vi.advanceTimersByTimeAsync(100);
285
- });
286
- });
287
-
288
- describe('Popup - IntersectionObserver on anchor (lines 179-180)', () => {
289
- /** @type {HTMLButtonElement} */
290
- let anchor;
291
- /** @type {HTMLDialogElement} */
292
- let popup;
293
- /** @type {typeof globalThis.IntersectionObserver} */
294
- let OrigIObserver;
295
- /** @type {((entries: any[]) => void)[]} */
296
- let ioCallbacks;
297
-
298
- beforeEach(() => {
299
- vi.useFakeTimers();
300
- anchor = /** @type {HTMLButtonElement} */ (document.createElement('button'));
301
- popup = /** @type {HTMLDialogElement} */ (document.createElement('dialog'));
302
- document.body.appendChild(anchor);
303
- document.body.appendChild(popup);
304
- ioCallbacks = [];
305
- OrigIObserver = globalThis.IntersectionObserver;
306
-
307
- // Stub IntersectionObserver to capture callbacks
308
- globalThis.IntersectionObserver = /** @type {any} */ (
309
- class {
310
- /** @param {(entries: any[]) => void} cb */
311
- constructor(cb) {
312
- ioCallbacks.push(cb);
313
- }
314
-
315
- observe() {}
316
- unobserve() {}
317
- disconnect() {}
318
- }
319
- );
320
- });
321
-
322
- afterEach(async () => {
323
- anchor.remove();
324
- popup.remove();
325
- globalThis.IntersectionObserver = OrigIObserver;
326
- await vi.runAllTimersAsync();
327
- vi.useRealTimers();
328
- });
329
-
330
- it('should hide when anchor becomes non-intersecting while popup is open', async () => {
331
- const instance = activatePopup(anchor, popup, 'bottom-left');
332
-
333
- anchor.click();
334
- expect(get(instance.open)).toBe(true);
335
-
336
- // ioCallbacks[0] = position observer; ioCallbacks[1] = anchor visibility observer
337
- const anchorVisibilityCallback = ioCallbacks[1];
338
-
339
- anchorVisibilityCallback([{ isIntersecting: false }]);
340
- expect(get(instance.open)).toBe(false);
341
- await vi.advanceTimersByTimeAsync(100);
342
- });
343
-
344
- it('should not hide when anchor leaves viewport but popup is already closed', () => {
345
- const instance = activatePopup(anchor, popup, 'bottom-left');
346
- // popup is not opened
347
- const anchorVisibilityCallback = ioCallbacks[1];
348
-
349
- anchorVisibilityCallback([{ isIntersecting: false }]);
350
- expect(get(instance.open)).toBe(false);
351
- });
352
- });
353
- describe('Popup - IntersectionObserver position callback (lines 35-132)', () => {
354
- /** @type {HTMLButtonElement} */
355
- let anchor;
356
- /** @type {HTMLDialogElement} */
357
- let popup;
358
- /** @type {HTMLDivElement} */
359
- let content;
360
- /** @type {typeof globalThis.IntersectionObserver} */
361
- let OrigIObserver;
362
- /** @type {((entries: any[]) => void)[]} */
363
- let ioCallbacks;
364
-
365
- beforeEach(() => {
366
- anchor = /** @type {HTMLButtonElement} */ (document.createElement('button'));
367
- popup = /** @type {HTMLDialogElement} */ (document.createElement('dialog'));
368
- // The position callback accesses popup.querySelector('.content')
369
- content = document.createElement('div');
370
- content.className = 'content';
371
- popup.appendChild(content);
372
- document.body.appendChild(anchor);
373
- document.body.appendChild(popup);
374
-
375
- ioCallbacks = [];
376
- OrigIObserver = globalThis.IntersectionObserver;
377
- globalThis.IntersectionObserver = /** @type {any} */ (
378
- class {
379
- /** @param {(entries: any[]) => void} cb */
380
- constructor(cb) {
381
- ioCallbacks.push(cb);
382
- }
383
-
384
- observe() {}
385
- unobserve() {}
386
- disconnect() {}
387
- }
388
- );
389
- });
390
-
391
- afterEach(() => {
392
- anchor.remove();
393
- popup.remove();
394
- globalThis.IntersectionObserver = OrigIObserver;
395
- });
396
-
397
- /**
398
- * Helper to create a fake intersection entry.
399
- */
400
- const makeEntry = ({
401
- top = 100,
402
- bottom = 150,
403
- left = 50,
404
- right = 300,
405
- vw = 800,
406
- vh = 600,
407
- } = {}) => ({
408
- intersectionRect: { top, bottom, left, right, width: right - left, height: bottom - top },
409
- rootBounds: { width: vw, height: vh },
410
- });
411
-
412
- it('should set popup style on intersection (bottom-left, normal case)', () => {
413
- const instance = activatePopup(anchor, popup, 'bottom-left');
414
-
415
- // ioCallbacks[0] is the position observer
416
- ioCallbacks[0]([makeEntry()]);
417
-
418
- // Style should be updated with computed inset
419
- const style = get(instance.style);
420
-
421
- expect(style.inset).toBeTruthy();
422
- expect(style.zIndex).toBe(1000);
423
- });
424
-
425
- it('should skip entry when intersectionRect is null', () => {
426
- const instance = activatePopup(anchor, popup, 'bottom-left');
427
-
428
- ioCallbacks[0]([{ intersectionRect: null, rootBounds: null }]);
429
-
430
- // Style remains at default (no crash)
431
- const style = get(instance.style);
432
-
433
- expect(style.inset).toBeUndefined();
434
- });
435
-
436
- it('should switch position to top-left when content overflows bottom', () => {
437
- const instance = activatePopup(anchor, popup, 'bottom-left');
438
-
439
- // contentHeight > bottomMargin AND topMargin > bottomMargin → switches to top-
440
- Object.defineProperty(content, 'scrollHeight', { configurable: true, get: () => 500 });
441
- ioCallbacks[0]([makeEntry({ top: 400, bottom: 450, left: 50, right: 300, vw: 800, vh: 500 })]);
442
-
443
- const style = get(instance.style);
444
-
445
- // Position changed to top-left → bottom should be calculated (not auto)
446
- expect(style.inset).not.toBeUndefined();
447
- });
448
-
449
- it('should switch position to bottom-right when content overflows to the right', () => {
450
- const instance = activatePopup(anchor, popup, 'bottom-left');
451
-
452
- // contentWidth > remaining right space → switch to bottom-right
453
- Object.defineProperty(content, 'scrollWidth', { configurable: true, get: () => 760 });
454
- ioCallbacks[0]([makeEntry({ left: 50, right: 300 })]);
455
-
456
- const style = get(instance.style);
457
-
458
- expect(style.inset).not.toBeUndefined();
459
- });
460
-
461
- it('should switch position to bottom-left when content overflows to the left', () => {
462
- const instance = activatePopup(anchor, popup, 'bottom-right');
463
-
464
- // contentWidth causes left edge to be < 8 → switch to bottom-left
465
- Object.defineProperty(content, 'scrollWidth', { configurable: true, get: () => 290 });
466
- ioCallbacks[0]([makeEntry({ left: 50, right: 100 })]);
467
-
468
- const style = get(instance.style);
469
-
470
- expect(style.inset).not.toBeUndefined();
471
- });
472
-
473
- it('should normalize RTL position when document.dir is rtl', () => {
474
- Object.defineProperty(document, 'dir', { get: () => 'rtl', configurable: true });
475
-
476
- const instance = activatePopup(anchor, popup, 'bottom-left');
477
-
478
- ioCallbacks[0]([makeEntry()]);
479
-
480
- const style = get(instance.style);
481
-
482
- expect(style.inset).not.toBeUndefined();
483
- Reflect.deleteProperty(document, 'dir');
484
- });
485
-
486
- it('should normalize bottom-right to bottom-left in RTL (endsWith -right branch)', () => {
487
- Object.defineProperty(document, 'dir', { get: () => 'rtl', configurable: true });
488
-
489
- const instance = activatePopup(anchor, popup, 'bottom-right');
490
-
491
- ioCallbacks[0]([makeEntry()]);
492
-
493
- const style = get(instance.style);
494
-
495
- // After RTL normalization bottom-right → bottom-left; inset should be computed
496
- expect(style.inset).not.toBeUndefined();
497
- Reflect.deleteProperty(document, 'dir');
498
- });
499
-
500
- it('should normalize left-top to right-top in RTL (startsWith left- branch)', () => {
501
- Object.defineProperty(document, 'dir', { get: () => 'rtl', configurable: true });
502
-
503
- const instance = activatePopup(anchor, popup, 'left-top');
504
-
505
- ioCallbacks[0]([makeEntry()]);
506
-
507
- const style = get(instance.style);
508
-
509
- // After RTL normalization left-top → right-top; inset should be computed
510
- expect(style.inset).not.toBeUndefined();
511
- Reflect.deleteProperty(document, 'dir');
512
- });
513
-
514
- it('should normalize right-top to left-top in RTL (startsWith right- branch)', () => {
515
- Object.defineProperty(document, 'dir', { get: () => 'rtl', configurable: true });
516
-
517
- const instance = activatePopup(anchor, popup, 'right-top');
518
-
519
- ioCallbacks[0]([makeEntry()]);
520
-
521
- const style = get(instance.style);
522
-
523
- // After RTL normalization right-top → left-top; inset should be computed
524
- expect(style.inset).not.toBeUndefined();
525
- Reflect.deleteProperty(document, 'dir');
526
- });
527
-
528
- it('should set height to bottomMargin when content overflows bottom but top is not better', () => {
529
- const instance = activatePopup(anchor, popup, 'bottom-left');
530
-
531
- // bottomMargin = 500 - 400 - 8 = 92; topMargin = 50 - 8 = 42; topMargin < bottomMargin
532
- // so the else branch runs: height = bottomMargin (92px)
533
- Object.defineProperty(content, 'scrollHeight', { configurable: true, get: () => 200 });
534
- ioCallbacks[0]([makeEntry({ top: 50, bottom: 400, vw: 800, vh: 500 })]);
535
-
536
- const style = get(instance.style);
537
-
538
- expect(style.height).toBe('92px');
539
- });
540
-
541
- it('should compute bottom from rootBounds.height - intersectionRect.bottom for -bottom position (branch 19)', () => {
542
- // 'right-bottom' ends with '-bottom' → bottom = Math.round(vh - intersectionRect.bottom)
543
- const instance = activatePopup(anchor, popup, 'right-bottom');
544
-
545
- // default: top=100, bottom=150, left=50, right=300, vh=600
546
- // bottom = Math.round(600 - 150) = 450
547
- ioCallbacks[0]([makeEntry()]);
548
-
549
- const style = get(instance.style);
550
-
551
- expect(style.inset).toContain('450px');
552
- });
553
-
554
- it('should not update style when intersection callback fires with identical geometry (branch 25)', () => {
555
- const instance = activatePopup(anchor, popup, 'bottom-left');
556
- const entry = makeEntry();
557
-
558
- // First call — style is updated (inset differs from initial empty object)
559
- ioCallbacks[0]([entry]);
560
-
561
- const styleBefore = get(instance.style);
562
-
563
- // Second call with same entry — all comparisons are equal → style.set not called again
564
- ioCallbacks[0]([entry]);
565
-
566
- const styleAfter = get(instance.style);
567
-
568
- expect(styleAfter.inset).toBe(styleBefore.inset);
569
- expect(styleAfter.zIndex).toBe(styleBefore.zIndex);
570
- });
571
- });
572
- describe('Popup - ResizeObserver callback (lines 223-224)', () => {
573
- /** @type {HTMLButtonElement} */
574
- let anchor;
575
- /** @type {HTMLDialogElement} */
576
- let popup;
577
- /** @type {typeof globalThis.ResizeObserver} */
578
- let OrigRObs;
579
- /** @type {((entries: any[]) => void) | undefined} */
580
- let resizeCallback;
581
- /** @type {typeof globalThis.IntersectionObserver | undefined} */
582
- let _OrigIO;
583
-
584
- beforeEach(() => {
585
- vi.useFakeTimers();
586
- anchor = /** @type {HTMLButtonElement} */ (document.createElement('button'));
587
- popup = /** @type {HTMLDialogElement} */ (document.createElement('dialog'));
588
- document.body.appendChild(anchor);
589
- document.body.appendChild(popup);
590
- OrigRObs = globalThis.ResizeObserver;
591
-
592
- // Stub ResizeObserver to capture its callback
593
- globalThis.ResizeObserver = /** @type {any} */ (
594
- class {
595
- /** @param {any} cb */
596
- constructor(cb) {
597
- resizeCallback = cb;
598
- }
599
-
600
- observe() {}
601
- unobserve() {}
602
- disconnect() {}
603
- }
604
- );
605
-
606
- // Also stub IntersectionObserver to avoid errors
607
- const OrigIO = globalThis.IntersectionObserver;
608
-
609
- globalThis.IntersectionObserver = /** @type {any} */ (
610
- class {
611
- // eslint-disable-next-line no-useless-constructor, no-empty-function
612
- constructor() {}
613
- observe() {}
614
- unobserve() {}
615
- disconnect() {}
616
- }
617
- );
618
-
619
- _OrigIO = OrigIO;
620
- });
621
-
622
- afterEach(async () => {
623
- anchor.remove();
624
- popup.remove();
625
- globalThis.ResizeObserver = OrigRObs;
626
- globalThis.IntersectionObserver = /** @type {any} */ (_OrigIO);
627
- await vi.runAllTimersAsync();
628
- vi.useRealTimers();
629
- });
630
-
631
- it('should schedule checkPosition via RAF when resize is observed', async () => {
632
- activatePopup(anchor, popup, 'bottom-left');
633
-
634
- // Trigger the ResizeObserver callback (lines 223-224: cancelAnimationFrame +
635
- // requestAnimationFrame)
636
- /** @type {any} */ (resizeCallback)?.([]);
637
- await vi.advanceTimersByTimeAsync(16); // flush rAF
638
- // No crash; just verifies these lines execute
639
- expect(true).toBe(true);
640
- });
641
- });
@@ -1 +0,0 @@
1
- export {};