返回 oh-my-ppt
use-visible-item-ids.test.ts
根目录 / tests / unit / renderer / use-visible-item-ids.test.ts
1 /**
2 * @vitest-environment happy-dom
3 */
4 import React, { act, useMemo } from 'react'
5 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6 import { createRoot } from 'react-dom/client'
7 import { useVisibleItemIds } from '../../../src/renderer/src/hooks/useVisibleItemIds'
8
9 type ObserverEntry = Pick<IntersectionObserverEntry, 'target' | 'isIntersecting'>
10
11 class MockIntersectionObserver {
12 static instances: MockIntersectionObserver[] = []
13 readonly observed = new Set<Element>()
14
15 constructor(private readonly callback: IntersectionObserverCallback) {
16 MockIntersectionObserver.instances.push(this)
17 }
18
19 observe = (element: Element): void => {
20 this.observed.add(element)
21 }
22
23 unobserve = (element: Element): void => {
24 this.observed.delete(element)
25 }
26
27 disconnect = (): void => {
28 this.observed.clear()
29 }
30
31 emit(entries: ObserverEntry[]): void {
32 this.callback(entries as IntersectionObserverEntry[], this as unknown as IntersectionObserver)
33 }
34 }
35
36 function Harness(): React.JSX.Element {
37 const ids = useMemo(
38 () => new Set(Array.from({ length: 10 }, (_, index) => `item-${index + 1}`)),
39 []
40 )
41 const { visibleIds, setItemRef } = useVisibleItemIds(ids, 8)
42 return React.createElement(
43 'div',
44 null,
45 React.createElement('output', { 'data-testid': 'visible' }, Array.from(visibleIds).join(',')),
46 ...Array.from(ids).map((id) =>
47 React.createElement('div', { key: id, 'data-item-id': id, ref: setItemRef(id) })
48 )
49 )
50 }
51
52 describe('useVisibleItemIds', () => {
53 beforeEach(() => {
54 MockIntersectionObserver.instances = []
55 vi.stubGlobal('IntersectionObserver', MockIntersectionObserver)
56 })
57
58 afterEach(() => {
59 vi.unstubAllGlobals()
60 document.body.innerHTML = ''
61 })
62
63 it('keeps only the eight most recently visible items and removes exited items', async () => {
64 const container = document.createElement('div')
65 document.body.appendChild(container)
66 const root = createRoot(container)
67 await act(async () => root.render(React.createElement(Harness)))
68 const observer = MockIntersectionObserver.instances[0]
69 const elements = Array.from(observer.observed)
70
71 await act(async () => {
72 observer.emit(elements.map((target) => ({ target, isIntersecting: true })))
73 })
74 expect(container.querySelector('[data-testid="visible"]')?.textContent).toBe(
75 'item-3,item-4,item-5,item-6,item-7,item-8,item-9,item-10'
76 )
77
78 const item10 = elements.find(
79 (element) => (element as HTMLElement).dataset.itemId === 'item-10'
80 )
81 await act(async () => observer.emit([{ target: item10!, isIntersecting: false }]))
82 expect(container.querySelector('[data-testid="visible"]')?.textContent).not.toContain('item-10')
83 await act(async () => root.unmount())
84 })
85 })
86
86 lines TYPESCRIPT