使用React包装Lit Element组件时,测试中onClick事件未触发
Lit + React 组件测试问题:ShadowDOM 元素点击事件未触发 & 内部DOM无法调试
问题描述
我们项目用@lit-labs/react将带ShadowDOM的自定义Lit Element组件包装为React组件,线上运行完全正常,但测试Header组件时碰到两个问题:
- 点击
data-testid="theme-icon"的Icon组件后,onClick事件没触发,useState定义的本地firstName状态无法更新(UserIdentifier的name始终停留在John Doe,不会变成Biplab Doe) - 调用
screen.debug()无法显示Lit Element组件的内部DOM结构
组件代码
return ( <> <Desktop> <AccessBar data-testid="access-bar" id="header" label="PV Chat Companion" slot="access"> <Icon size="m" slot="functions" button icon="circle-half" data-testid="theme-icon" onClick={() => setFirstName('Biplab')} /> <UserIdentifier slot="user" initials={initials} data-testid="user-id" name={`${firstName} ${lastName}`} unauthenticated={userStatus !== UserStatus.loggedIn} image={`url(${profilePicture})`} id="user-icon" /> </AccessBar> </Desktop> </> );
测试代码
import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render } from './test-utils'; import { UserStatus } from '../redux/userSlice'; import { Header } from '../components/Header/Header'; import '@testing-library/jest-dom' describe('User event test', () => { test('renders Header component', async () => { const preloadedState = { user: { status: UserStatus.loggedIn, firstName: 'John', lastName: 'Doe', profilePicture: 'profile-pic-url', }, }; const {store} = render(<Header />, { preloadedState }); const accessBarElement = await waitFor(() => screen.getByTestId('access-bar')); expect(accessBarElement).toHaveAttribute('label', 'PV Chat Companion'); const themeIcon = await waitFor(() => screen.getByTestId("theme-icon")); await userEvent.click(themeIcon); await waitFor(() => { const userIdElement = screen.getByTestId('user-id'); expect(userIdElement).toHaveProperty('name', 'Biplab Doe'); }); screen.debug(); }); });
报错信息
预期UserIdentifier的name属性为Biplab Doe,实际为John Doe
问题原因及解决办法
核心原因
Lit组件的ShadowDOM会隔离内部DOM结构:
- Testing Library默认只能访问Light DOM里的元素,直接点击Lit组件本身(比如
theme-icon对应的React包装组件)无法触发其ShadowDOM内实际可点击元素的事件 screen.debug()默认只输出Light DOM内容,看不到ShadowDOM内部结构
具体修改方案
1. 调整测试代码,访问ShadowDOM内的实际交互元素
直接点击Lit组件的包装节点没用,得找到其ShadowDOM里的真实按钮元素再触发点击:
import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render } from './test-utils'; import { UserStatus } from '../redux/userSlice'; import { Header } from '../components/Header/Header'; import '@testing-library/jest-dom' describe('User event test', () => { test('renders Header component and updates name on icon click', async () => { const preloadedState = { user: { status: UserStatus.loggedIn, firstName: 'John', lastName: 'Doe', profilePicture: 'profile-pic-url', }, }; const {store} = render(<Header />, { preloadedState }); const accessBarElement = await waitFor(() => screen.getByTestId('access-bar')); expect(accessBarElement).toHaveAttribute('label', 'PV Chat Companion'); // 获取Lit包装的Icon组件,拿到它的shadowRoot const themeIconComponent = await waitFor(() => screen.getByTestId("theme-icon")); const iconShadowRoot = themeIconComponent.shadowRoot; // 在shadowRoot里找到实际的按钮元素(根据你的Icon组件内部结构调整,比如找button标签或带role="button"的元素) const actualButton = within(iconShadowRoot).getByRole('button'); // 点击真实的按钮元素,触发事件 await userEvent.click(actualButton); // 验证UserIdentifier的更新:两种方式选其一 await waitFor(() => { const userIdComponent = screen.getByTestId('user-id'); // 方式1:检查组件的name属性是否更新 expect(userIdComponent).toHaveAttribute('name', 'Biplab Doe'); // 方式2:如果name是渲染到shadowRoot内的文本,需要访问其shadowRoot查找文本 // const userShadowRoot = userIdComponent.shadowRoot; // expect(within(userShadowRoot).getByText('Biplab Doe')).toBeInTheDocument(); }); // 调试ShadowDOM内容,传入shadowRoot参数即可 screen.debug(iconShadowRoot); // screen.debug(userShadowRoot); }); });
2. 额外注意点
- 如果你不确定Icon组件ShadowDOM内的结构,可以先通过
console.log(themeIconComponent.shadowRoot.innerHTML)打印内部HTML,找到实际可点击元素的选择器 - 确保
@lit-labs/react已经正确将React的onClick事件绑定到Lit组件的原生事件上(线上正常说明这一步没问题) - 若要全局支持ShadowDOM查询,可以在
test-utils的render函数里扩展配置,不过手动访问shadowRoot是最直接的方式
内容的提问来源于stack exchange,提问作者user26512063
相关产品推荐
相关产品推荐

