Тестирование Shadow DOM

Shadow DOM является ключевой особенностью веб-компонентов и позволяет инкапсулировать разметку, стили и поведение компонента. В Lit Shadow DOM используется по умолчанию, что создаёт отдельный изолированный DOM-дерево внутри компонента. Тестирование таких компонентов требует специфического подхода, поскольку обычные методы поиска элементов в глобальном DOM не работают напрямую.


Доступ к Shadow DOM

В Lit компоненты обычно создаются с помощью наследования от LitElement. Элементы внутри Shadow DOM недоступны напрямую через document.querySelector, поэтому для тестирования необходимо использовать свойства shadowRoot или методы, предоставляемые тестовыми библиотеками.

Пример доступа к элементам:

import { fixture, html } from '@open-wc/testing';
import './my-element.js';

it('проверяет текст внутри Shadow DOM', async () => {
  const el = await fixture(html`<my-element></my-element>`);
  const span = el.shadowRoot.querySelector('span');
  assert.equal(span.textContent, 'Привет, Lit!');
});

Ключевые моменты:

  • shadowRoot возвращает корень Shadow DOM.
  • Все запросы к внутренним элементам должны выполняться через shadowRoot.

Использование @open-wc/testing

Библиотека @open-wc/testing упрощает тестирование Lit-компонентов. Она предоставляет функции fixture, html и асинхронные утилиты для рендеринга компонентов в тестовой среде. Основное преимущество — возможность легко получать доступ к Shadow DOM и выполнять ассерты.

Пример проверки свойства компонента:

import { fixture, html, expect } from '@open-wc/testing';
import './counter-element.js';

it('увеличивает счётчик при клике', async () => {
  const el = await fixture(html`<counter-element></counter-element>`);
  const button = el.shadowRoot.querySelector('button');
  
  button.click();
  await el.updateComplete; // ожидает обновления рендеринга
  
  const count = el.shadowRoot.querySelector('.count');
  expect(count.textContent).to.equal('1');
});

Особенности:

  • updateComplete — промис, который резолвится после завершения обновления шаблона.
  • Для асинхронных изменений состояния компонента тест должен использовать await для корректного рендера.

Проверка стилей внутри Shadow DOM

Shadow DOM обеспечивает инкапсуляцию CSS, что делает прямую проверку стилей сложнее. Основные подходы:

  1. Проверка через computed styles:
const el = await fixture(html`<my-element></my-element>`);
const div = el.shadowRoot.querySelector('div');
const styles = getComputedStyle(div);
expect(styles.color).to.equal('rgb(255, 0, 0)');
  1. Использование Lit static styles: Если стили объявлены через static styles в компоненте, можно протестировать наличие классов или правил CSS без рендеринга в браузере, анализируя сам объект стилей.

Мокинг событий в Shadow DOM

Компоненты часто генерируют кастомные события. Для тестирования нужно слушать события на самом компоненте, так как они всплывают из Shadow DOM:

it('вызывает событие submit', async () => {
  const el = await fixture(html`<form-element></form-element>`);
  
  const listener = sinon.spy();
  el.addEventListener('submit-event', listener);
  
  const button = el.shadowRoot.querySelector('button');
  button.click();
  
  expect(listener.calledOnce).to.be.true;
});

Важные моменты:

  • События, созданные внутри Shadow DOM с { bubbles: true }, всплывают наружу и могут быть перехвачены тестами.
  • Для синхронизации асинхронных событий используют await el.updateComplete.

Интеграционное тестирование компонентов

Shadow DOM может взаимодействовать с внешними элементами или другими компонентами. Интеграционные тесты проверяют это взаимодействие:

it('отправляет данные родителю', async () => {
  const parent = await fixture(html`
    <parent-component></parent-component>
  `);
  
  const child = parent.shadowRoot.querySelector('child-component');
  const input = child.shadowRoot.querySelector('input');
  
  input.value = 'test';
  input.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
  
  await parent.updateComplete;
  expect(parent.data).to.equal('test');
});

Особенности:

  • composed: true позволяет событиям пройти через Shadow DOM.
  • Важно использовать await updateComplete после изменения состояния.

Инструменты для тестирования Shadow DOM

Наиболее популярные библиотеки и подходы:

  • @open-wc/testing — универсальная библиотека для рендеринга и проверки Lit-компонентов.
  • Web Test Runner — запуск тестов в реальном браузере с поддержкой Shadow DOM.
  • Sinon — для мокинга функций и проверки вызовов событий.
  • Chai — для ассертов значений элементов, текстов и стилей.

Практические рекомендации

  • Всегда использовать shadowRoot.querySelector для поиска элементов внутри Shadow DOM.
  • Асинхронные изменения состояния компонента тестировать через await el.updateComplete.
  • Проверку событий выполнять на компоненте, а не на внутренних элементах Shadow DOM.
  • Для стилей предпочтительно использовать getComputedStyle, а не напрямую проверять классы.
  • Интеграционные тесты должны учитывать composed: true для кастомных событий.

Shadow DOM в Lit обеспечивает мощную инкапсуляцию, и правильное тестирование компонентов требует внимательного подхода к доступу к внутренним элементам, синхронизации асинхронных обновлений и обработке кастомных событий.