Тестирование DOM-манипуляций

Тестирование DOM-манипуляций — проверка кода, взаимодействующего с DOM (добавление/удаление элементов, изменение классов, атрибутов, стилей), через jsdom-окружение Jest или реальный браузер в E2E тестах.

Зачем нужно

Ванильный JavaScript и jQuery-код активно работает с DOM. Unit-тесты для такого кода требуют jsdom-окружения — симуляции браузерного DOM в Node.js. Без тестов DOM-манипуляции проверяются только вручную в браузере.

Где используется

  • Тестирование ванильных JS утилит (toggle, addClass, show/hide)
  • Тестирование Web Components
  • Проверка функций, работающих с document, window, localStorage
  • Тестирование кастомных директив в Vue/Angular

Основной контент

Настройка jsdom в Jest

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom', // браузерное окружение
};

Тестирование простой DOM-функции

// toggleClass.js
export function toggleClass(element, className) {
  if (element.classList.contains(className)) {
    element.classList.remove(className);
  } else {
    element.classList.add(className);
  }
}

// toggleClass.test.js
import { toggleClass } from './toggleClass';

test('добавляет класс если его нет', () => {
  const el = document.createElement('div');
  toggleClass(el, 'active');
  expect(el.classList.contains('active')).toBe(true);
});

test('убирает класс если он есть', () => {
  const el = document.createElement('div');
  el.classList.add('active');
  toggleClass(el, 'active');
  expect(el.classList.contains('active')).toBe(false);
});

Тестирование с HTML-фиксатурой

// dropdown.test.js
import { initDropdown } from './dropdown';

beforeEach(() => {
  document.body.innerHTML = `
    <div class="dropdown">
      <button class="dropdown-toggle">Меню</button>
      <ul class="dropdown-menu hidden">
        <li>Пункт 1</li>
      </ul>
    </div>
  `;
  initDropdown;
});

test('открывает меню при клике на кнопку', () => {
  const button = document.querySelector('.dropdown-toggle');
  const menu = document.querySelector('.dropdown-menu');

  button.click();

  expect(menu.classList.contains('hidden')).toBe(false);
});

test('закрывает меню при повторном клике', () => {
  const button = document.querySelector('.dropdown-toggle');
  const menu = document.querySelector('.dropdown-menu');

  button.click();
  button.click();

  expect(menu.classList.contains('hidden')).toBe(true);
});

Тестирование localStorage

// storage.test.js
import { saveTheme, getTheme } from './storage';

beforeEach(() => {
  localStorage.clear();
});

test('сохраняет тему в localStorage', () => {
  saveTheme('dark');
  expect(localStorage.getItem('theme')).toBe('dark');
});

test('возвращает null если тема не установлена', () => {
  expect(getTheme).toBeNull();
});

Тестирование с Testing Library (для React)

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Accordion } from './Accordion';

test('раскрывает панель при клике', async () => {
  render(<Accordion title="Секция 1" content="Содержимое" />);

  const panel = screen.queryByText('Содержимое');
  expect(panel).not.toBeVisible;

  await userEvent.click(screen.getByText('Секция 1'));

  expect(screen.getByText('Содержимое')).toBeVisible;
});

Частые ошибки

  • Тест без testEnvironment: 'jsdom' — в Node-окружении нет document; Jest выдаст ReferenceError: document is not defined
  • Нет beforeEach(() => { document.body.innerHTML = '' }) — DOM накапливается между тестами и ломает изоляцию
  • Тест проверяет стиль через .style.display — лучше проверять через класс или toBeVisible из jest-dom

Связанные темы

Ресурсы