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

使用React包装Lit Element组件时,测试中onClick事件未触发

Lit + React 组件测试问题:ShadowDOM 元素点击事件未触发 & 内部DOM无法调试

问题描述

我们项目用@lit-labs/react将带ShadowDOM的自定义Lit Element组件包装为React组件,线上运行完全正常,但测试Header组件时碰到两个问题:

  1. 点击data-testid="theme-icon"的Icon组件后,onClick事件没触发,useState定义的本地firstName状态无法更新(UserIdentifier的name始终停留在John Doe,不会变成Biplab Doe)
  2. 调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 11:05:55