You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试useEffect内的updateCurrentTitle滚动逻辑?Jest/RTL遇阻

问题描述

我正在使用Jest和React Testing Library测试组件,当前代码通过滚动位置更新TopBar组件中的标题,组件核心逻辑如下:

const [currentTitle, setTitle] = useState<string>(defaultProps.collectionName)

useEffect(() => {
    const collections = document.getElementsByClassName('cc-look-collection')
    const childNodes = collections[0].childNodes

    const updateCurrentTitle = () => {
        const currentPosition = window.scrollY
        let accumulatedHeight = 0

        for (let i = 0; i < childNodes.length; i++) {
            const collection = childNodes[i]
            const collectionHeight = (collection as HTMLInputElement)?.offsetHeight
            accumulatedHeight += collectionHeight

            if (currentPosition < accumulatedHeight) {
                const title = collection.childNodes[0].textContent!
                setTitle(title)
                break
            }
        }
    }

    window.addEventListener('scroll', updateCurrentTitle)
}, [collections])

我想测试updateCurrentTitle函数,但测试无法正常运行,模拟滚动事件后标题没有更新。我的测试用例如下:

it('should update TopBar collectionName based on scroll position', () => {
    // Given
    const props = lookCollectionProps
    const { container } = render(<LookCollection {...props} />)

    // eslint-disable-next-line testing-library/no-node-access
    const content = container.firstChild as HTMLElement

    jest.useFakeTimers()
    window.dispatchEvent(new Event('scroll'))
    const baseText = within(content).getByText('Collection n°1')
    expect(baseText).toBeInTheDocument()

    act(() => {
        jest.advanceTimersByTime(1500)
    })

    const expectedText = within(content).getByText('Collection n°2')
    expect(expectedText).toBeInTheDocument()
})

运行测试时遇到错误:Unable to find an element with the text: Collection n°2。

解决方案

你的测试和组件逻辑存在几个关键问题,逐一修复即可:

  • 修复useEffect依赖问题
    document.getElementsByClassName返回的是动态HTMLCollection,每次渲染都会生成新引用,导致useEffect无限触发。建议把DOM查询逻辑移到updateCurrentTitle内部,避免依赖动态集合,同时加上清理函数移除监听:

    useEffect(() => {
        const updateCurrentTitle = () => {
            const collections = document.getElementsByClassName('cc-look-collection')
            if (!collections[0]) return;
            const childNodes = collections[0].childNodes
            
            const currentPosition = window.scrollY
            let accumulatedHeight = 0
    
            for (let i = 0; i < childNodes.length; i++) {
                const collection = childNodes[i] as HTMLElement
                const collectionHeight = collection.offsetHeight
                if (!collectionHeight) continue;
                
                accumulatedHeight += collectionHeight
    
                if (currentPosition < accumulatedHeight) {
                    const title = collection.childNodes[0].textContent!
                    setTitle(title)
                    break
                }
            }
        }
    
        window.addEventListener('scroll', updateCurrentTitle)
        return () => window.removeEventListener('scroll', updateCurrentTitle)
    }, [])
    
  • 模拟正确的滚动位置
    测试中仅触发scroll事件但未修改window.scrollY,滚动位置始终为0,只会匹配第一个集合。需要先计算滚动到第二个集合的位置,再模拟滚动并触发事件:

    it('should update TopBar collectionName based on scroll position', () => {
        const props = lookCollectionProps
        const { container } = render(<LookCollection {...props} />)
    
        // 获取第一个集合的高度,计算滚动位置
        // eslint-disable-next-line testing-library/no-node-access
        const firstCollection = container.querySelector('.cc-look-collection > *') as HTMLElement
        const scrollToPosition = firstCollection.offsetHeight + 10
    
        // 模拟滚动到指定位置
        window.scrollTo(0, scrollToPosition)
        window.dispatchEvent(new Event('scroll'))
    
        // 验证标题更新
        expect(within(container).getByText('Collection n°2')).toBeInTheDocument()
    })
    
  • 移除无用的fake timers
    组件逻辑中没有定时器,测试里的jest.useFakeTimers()和advanceTimersByTime完全多余,直接删除即可。

  • 修正类型断言
    组件中将collection断言为HTMLInputElement不合理,改为HTMLElement,避免类型错误导致offsetHeight获取失败。

内容的提问来源于stack exchange,提问作者Kimimo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 18:45:08