Shadow DOM является ключевой особенностью веб-компонентов и позволяет инкапсулировать разметку, стили и поведение компонента. В Lit Shadow DOM используется по умолчанию, что создаёт отдельный изолированный DOM-дерево внутри компонента. Тестирование таких компонентов требует специфического подхода, поскольку обычные методы поиска элементов в глобальном 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 упрощает тестирование
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 обеспечивает инкапсуляцию CSS, что делает прямую проверку стилей сложнее. Основные подходы:
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)');
static styles: Если
стили объявлены через static styles в компоненте, можно
протестировать наличие классов или правил CSS без рендеринга в браузере,
анализируя сам объект стилей.Компоненты часто генерируют кастомные события. Для тестирования нужно слушать события на самом компоненте, так как они всплывают из 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;
});
Важные моменты:
{ 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 после изменения
состояния.Наиболее популярные библиотеки и подходы:
shadowRoot.querySelector для поиска
элементов внутри Shadow DOM.await el.updateComplete.getComputedStyle, а не напрямую проверять классы.composed: true
для кастомных событий.Shadow DOM в Lit обеспечивает мощную инкапсуляцию, и правильное тестирование компонентов требует внимательного подхода к доступу к внутренним элементам, синхронизации асинхронных обновлений и обработке кастомных событий.