Snapshot тестирование компонентов

Snapshot-тестирование компонентов в Riot.js используется для фиксации и контроля структуры отрендеренного интерфейса. Подход основан на сравнении текущего HTML-представления компонента с ранее сохранённым эталонным снимком. При любом расхождении тест сигнализирует об изменении, что позволяет выявлять регрессии в разметке и логике отображения на раннем этапе.

Riot.js генерирует DOM на основе шаблонов, реактивных выражений и состояния компонента. Snapshot-тесты решают несколько задач:

  • Фиксация структуры DOM после компиляции шаблона и выполнения логики компонента
  • Контроль условного рендеринга (if, each, слоты)
  • Проверка влияния состояния и props на итоговую разметку
  • Защита от незапланированных изменений при рефакторинге

Snapshot не проверяет поведение в интерактивном смысле, а именно результат рендеринга.

Инструменты и окружение

Типичный стек для snapshot-тестирования компонентов Riot.js:

  • Jest — тестовый раннер и механизм snapshot-сравнения
  • jsdom — виртуальное DOM-окружение
  • @riotjs/compiler — компиляция .riot-файлов
  • @riotjs/test-utils — утилиты для монтирования компонентов

Установка зависимостей:

npm install --save-dev jest jsdom @riotjs/compiler @riotjs/test-utils

Минимальная конфигурация Jest:

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  transform: {
    '^.+\\.riot$': '@riotjs/compiler'
  }
}

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

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

<!-- hello.riot -->
<hello>
  <p class={ color }>{ message }</p>

  <script>
    export default {
      state: {
        message: 'Hello Riot',
        color: 'primary'
      }
    }
  </script>
</hello>

Компонент содержит реактивное состояние и привязку класса, что делает его подходящим для snapshot-проверки.

Создание snapshot-теста

Тест создаётся как обычный Jest-тест с сохранением HTML-снимка.

// hello.test.js
import { component } from '@riotjs/test-utils'
import Hello from './hello.riot'

describe('Hello component snapshot', () => {
  it('matches the default snapshot', () => {
    const mount = component(Hello)
    const el = mount()

    expect(el).toMatchSnapshot()
  })
})

При первом запуске Jest создаёт файл снимка:

__snapshots__/hello.test.js.snap

Содержимое snapshot:

exports[`Hello component snapshot matches the default snapshot 1`] = `
<div>
  <p class="primary">
    Hello Riot
  </p>
</div>
`;

Snapshot и состояние компонента

Snapshot-тесты особенно полезны при проверке разных состояний.

it('matches snapshot with custom state', () => {
  const mount = component(Hello)
  const el = mount({
    state: {
      message: 'Custom text',
      color: 'danger'
    }
  })

  expect(el).toMatchSnapshot()
})

Каждый вариант состояния сохраняется как отдельный снимок, позволяя контролировать визуальные изменения при разных входных данных.

Условный рендеринг и списки

Компоненты Riot.js часто используют директивы if и each. Snapshot позволяет зафиксировать оба варианта.

<list>
  <ul if={ items.length }>
    <li each={ items }>{ name }</li>
  </ul>
  <p if={ !items.length }>Empty</p>

  <script>
    export default {
      state: {
        items: []
      }
    }
  </script>
</list>

Тестирование пустого и непустого состояния:

it('renders empty state', () => {
  const mount = component(List)
  const el = mount({ state: { items: [] } })

  expect(el).toMatchSnapshot()
})

it('renders list items', () => {
  const mount = component(List)
  const el = mount({
    state: { items: [{ name: 'A' }, { name: 'B' }] }
  })

  expect(el).toMatchSnapshot()
})

Обновление snapshot-ов

Изменения в шаблоне или логике компонента приводят к расхождению snapshot-ов. Jest сообщает об этом и предлагает обновление:

jest -u

Обновление допустимо только при осознанном изменении структуры DOM. Snapshot-тесты не должны использоваться как автоматическое подтверждение любых правок.

Нормализация вывода

Для стабильности snapshot-ов рекомендуется:

  • избегать динамических id, дат, случайных значений
  • не включать в snapshot стили, зависящие от окружения
  • использовать предсказуемые классы и структуру

При необходимости можно сериализовать только часть DOM:

expect(el.querySelector('p')).toMatchSnapshot()

Snapshot против unit-тестов логики

Snapshot-тесты в Riot.js дополняют, но не заменяют проверки логики:

  • Snapshot — форма, структура, условный рендеринг
  • Unit-тесты — методы, вычисляемые значения, события

Комбинация обоих подходов даёт контроль как над внешним видом компонента, так и над его внутренним поведением.

Хранение и поддержка

Snapshot-файлы являются частью исходного кода:

  • хранятся в репозитории
  • участвуют в code review
  • показывают эволюцию интерфейса

Грамотно поддерживаемые snapshot-ы превращаются в документацию реального состояния компонентов и служат надёжным инструментом контроля качества при разработке на Riot.js.