Unit-тестирование туров

Shepherd.js предоставляет гибкий API для создания интерактивных пользовательских туров, и тестирование таких туров требует внимания к нескольким ключевым аспектам: рендеринг шагов, триггеры событий и корректное завершение тура. Основной подход — изолировать каждый шаг и проверять его поведение независимо от всего приложения.

Для начала необходимо подключить Shepherd.js и инструмент для unit-тестирования, например Jest или Mocha. Важно создать DOM-элемент, к которому будет привязываться тур, так как без реального контейнера шаги не смогут корректно отобразиться:

import Shepherd from 'shepherd.js';
import 'shepherd.js/dist/css/shepherd.css';

beforeEach(() => {
  document.body.innerHTML = '<div id="app"></div>';
});

Создание тура для тестирования

Тур создаётся через конструктор Shepherd.Tour. Для unit-тестирования рекомендуется задавать минимально необходимые шаги и отключать анимацию и авто-скролл, чтобы тесты выполнялись предсказуемо:

const tour = new Shepherd.Tour({
  useModalOverlay: true,
  defaultStepOptions: {
    cancelIcon: {
      enabled: true
    },
    scrollTo: false,
    canClickTarget: false
  }
});

tour.addStep({
  id: 'step1',
  text: 'Приветственный шаг',
  attachTo: {
    element: '#app',
    on: 'bottom'
  }
});

tour.addStep({
  id: 'step2',
  text: 'Второй шаг',
  attachTo: {
    element: '#app',
    on: 'top'
  }
});

Проверка рендеринга шагов

Каждый шаг можно проверять на корректное отображение с помощью методов show() и isOpen(). В тесте важно убедиться, что шаг появляется после вызова show() и закрывается после hide() или перехода к следующему шагу:

test('Проверка отображения шага', () => {
  const step = tour.getById('step1');
  
  expect(step.isOpen()).toBe(false);
  
  step.show();
  
  expect(step.isOpen()).toBe(true);
  
  step.hide();
  
  expect(step.isOpen()).toBe(false);
});

Проверка навигации между шагами

Shepherd.js предоставляет методы next(), back(), complete() и события show, hide, complete, которые позволяют имитировать пользовательское взаимодействие:

test('Переход между шагами', () => {
  tour.start();
  
  expect(tour.currentStep.id).toBe('step1');
  
  tour.next();
  expect(tour.currentStep.id).toBe('step2');
  
  tour.back();
  expect(tour.currentStep.id).toBe('step1');
});

События можно подписывать через .on() и проверять, были ли они вызваны:

test('События шагов', () => {
  const showHandler = jest.fn();
  const hideHandler = jest.fn();
  
  const step = tour.getById('step1');
  step.on('show', showHandler);
  step.on('hide', hideHandler);
  
  step.show();
  expect(showHandler).toHaveBeenCalled();
  
  step.hide();
  expect(hideHandler).toHaveBeenCalled();
});

Мокирование DOM и взаимодействий

Для unit-тестов часто используется jsdom для эмуляции DOM. Если шаг привязан к элементу, которого нет в DOM, он не сможет отобразиться. Также можно мокировать методы scrollIntoView или другие действия браузера:

Element.prototype.scrollIntoView = jest.fn();

Это гарантирует, что тесты не будут падать из-за отсутствия браузерной реализации.

Тестирование пользовательских опций шага

Shepherd.js позволяет задавать уникальные свойства для каждого шага: кнопки, классы, условия перехода. Все эти элементы можно проверять:

tour.addStep({
  id: 'step3',
  text: 'Шаг с кнопкой',
  buttons: [
    {
      text: 'Далее',
      action: tour.next
    }
  ]
});

test('Кнопка шага вызывает переход', () => {
  const step = tour.getById('step3');
  const nextSpy = jest.spyOn(tour, 'next');
  
  step.show();
  step.options.buttons[0].action();
  
  expect(nextSpy).toHaveBeenCalled();
});

Проверка завершения тура

Метод complete() должен корректно завершать тур, закрывать шаги и вызывать событие complete. В тестах это можно проверять через мок-функции:

test('Завершение тура', () => {
  const completeHandler = jest.fn();
  tour.on('complete', completeHandler);
  
  tour.start();
  tour.complete();
  
  expect(completeHandler).toHaveBeenCalled();
  expect(tour.steps.every(step => !step.isOpen())).toBe(true);
});

Параллельное тестирование нескольких туров

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

const tourA = new Shepherd.Tour({ useModalOverlay: true });
const tourB = new Shepherd.Tour({ useModalOverlay: true });

tourA.addStep({ id: 'a1', text: 'Тур A' });
tourB.addStep({ id: 'b1', text: 'Тур B' });

tourA.start();
expect(tourA.currentStep.id).toBe('a1');
expect(tourB.currentStep).toBeNull();

tourB.start();
expect(tourB.currentStep.id).toBe('b1');

Использование snapshot-тестов

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

import { render } from '@testing-library/react';

test('Snapshot шага', () => {
  tour.start();
  const step = tour.getById('step1');
  
  const { container } = render(step.el);
  expect(container).toMatchSnapshot();
});

Snapshot фиксирует HTML и CSS шагов, предотвращая непреднамеренные изменения внешнего вида туров.


Такой подход к unit-тестированию Shepherd.js позволяет полностью покрыть рендеринг шагов, навигацию, пользовательские события и завершение тура, обеспечивая стабильность интерактивных инструкций и упрощая сопровождение кода при изменении интерфейса.