Тестирование событий и взаимодействий

В Riot.js события являются основным механизмом взаимодействия между компонентами и пользователем. Они описываются декларативно в шаблонах и обрабатываются методами тега. Тестирование событий направлено на проверку корректности вызова обработчиков, изменения состояния (state) и повторного рендеринга компонента.

Ключевая особенность Riot.js — тесная связка DOM, состояния и логики в рамках одного тега. Это влияет на стратегию тестирования: проверяется не только факт срабатывания события, но и итоговое состояние компонента и DOM.


Подходы к тестированию в экосистеме Riot.js

Riot.js не навязывает конкретный инструмент тестирования. На практике используются:

  • Jest — как тестовый раннер и среда выполнения.
  • @riotjs/dom-bindings — для работы с DOM и жизненным циклом.
  • jsdom — эмуляция браузерного окружения.
  • @riotjs/compiler — компиляция .riot-файлов в тестах при необходимости.

Тестирование событий обычно относится к уровню unit или component tests.


Подготовка тестовой среды

Компоненты Riot.js тестируются после монтирования в DOM. Минимальная подготовка включает:

  • создание контейнера (div) в document.body;
  • монтирование тега через riot.mount;
  • очистку DOM после каждого теста.

Пример инициализации:

import { mount } from 'riot'
import MyComponent from './my-component.riot'

let el

beforeEach(() => {
  el = document.createElement('div')
  document.body.appendChild(el)
})

afterEach(() => {
  document.body.removeChild(el)
})

Тестирование пользовательских событий

Клики мыши

Наиболее распространённый сценарий — проверка реакции на click. В Riot.js обработчик описывается прямо в шаблоне:

<button oncl ick={increment}>+</button>

Тестирование включает три этапа:

  1. генерация события;
  2. ожидание обновления состояния;
  3. проверка DOM или состояния.

Пример теста:

test('увеличение счётчика по клику', () => {
  const [component] = mount(el, MyComponent)

  const button = el.querySelector('button')
  button.click()

  expect(component.state.count).toBe(1)
})

Важно учитывать, что обновление состояния в Riot.js происходит синхронно, но рендеринг может быть отложен до следующего тика.


События ввода (input, change)

Для форм и полей ввода проверяется синхронизация значения DOM с состоянием:

<input type="text" onin put={onInput}>

Тест:

test('обновление состояния при вводе текста', () => {
  const [component] = mount(el, MyComponent)

  const input = el.querySelector('input')
  input.value = 'test'
  input.dispatchEvent(new Event('input'))

  expect(component.state.value).toBe('test')
})

Здесь важно использовать dispatchEvent, так как простое изменение value не инициирует событие.


Тестирование кастомных событий

Riot.js поддерживает пользовательские события через this.trigger и this.on. Они применяются для взаимодействия между компонентами или внутри логики.

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

this.trigger('saved', data)

Тестирование таких событий строится на подписке:

test('эмит события saved', done => {
  const [component] = mount(el, MyComponent)

  component.on('saved', data => {
    expect(data.id).toBe(1)
    done()
  })

  component.save()
})

Использование done необходимо для асинхронных сценариев.


Проверка взаимодействия компонентов

При вложенных компонентах тестируется цепочка событий: действие → событие → реакция родителя.

Пример сценария:

  • дочерний компонент эмитит событие;
  • родитель изменяет состояние;
  • DOM обновляется.

Тест:

test('родитель реагирует на событие дочернего компонента', () => {
  const [parent] = mount(el, ParentComponent)

  const childButton = el.querySelector('.child button')
  childButton.click()

  expect(parent.state.childClicked).toBe(true)
})

Такой тест проверяет именно взаимодействие, а не внутреннюю реализацию компонентов.


Асинхронные события и таймеры

Если обработчик события содержит асинхронную логику (fetch, setTimeout, promise), тестирование требует контроля времени и ожиданий.

Пример с таймером:

jest.useFakeTimers()

test('отложенное изменение состояния', () => {
  const [component] = mount(el, MyComponent)

  component.startTimer()
  jest.runAllTimers()

  expect(component.state.done).toBe(true)
})

Для async/await используется await или возврат Promise из теста.


Проверка повторного рендеринга

Событие в Riot.js почти всегда приводит к update(). Тестирование должно учитывать изменение DOM:

test('DOM обновляется после клика', () => {
  mount(el, MyComponent)

  const span = el.querySelector('span')
  const button = el.querySelector('button')

  button.click()

  expect(span.textContent).toBe('1')
})

Проверяется результат, а не сам факт вызова update.


Изоляция и мокирование

Для сложных компонентов рекомендуется:

  • выносить бизнес-логику в отдельные функции;
  • мокировать внешние зависимости (API, хранилища);
  • тестировать события без реальных сетевых запросов.

Пример мока:

jest.mock('./api', () => ({
  saveData: jest.fn(() => Promise.resolve({ ok: true }))
}))

Это позволяет сосредоточиться именно на реакции компонента на событие.


Типичные ошибки при тестировании событий

Отсутствие монтирования в DOM Без реального DOM события не срабатывают.

Ожидание синхронных обновлений там, где есть асинхронность Неучтённые Promise приводят к ложным падениям тестов.

Проверка внутренней реализации вместо поведения Тесты должны проверять результат взаимодействия, а не количество вызовов функций.


Стратегия построения тестов

Эффективная стратегия включает:

  • минимальные unit-тесты для отдельных событий;
  • компонентные тесты для пользовательских сценариев;
  • проверку взаимодействий, а не изолированных методов.

Тестирование событий в Riot.js опирается на простоту фреймворка: минимальный API, прозрачное состояние и прямую связь DOM и логики. Это позволяет писать компактные, но надёжные тесты, полностью покрывающие поведение компонентов.