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

Stencil — это современный фреймворк для создания веб-компонентов, который использует возможности стандартов Web Components. Одной из ключевых особенностей работы с компонентами является обработка событий. Тестирование событий в Stencil требует учета нескольких факторов, таких как асинхронность, реакция на пользовательские действия и взаимодействие компонентов. В этом разделе рассматриваются основные подходы и инструменты для тестирования событий в приложениях, построенных на Stencil.

Основы работы с событиями в Stencil

В Stencil компоненты могут генерировать и слушать события, которые играют важную роль в взаимодействии с другими частями приложения. События могут быть как кастомными (созданными вручную), так и стандартными (например, click, change, input).

С помощью Stencil можно создать кастомные события, используя синтаксис, схожий с нативным Jav * aScript:

@Component({
  tag: 'my-component',
  shadow: true,
})
export class MyComponent {
  @Event() myEvent: EventEmitter;

  triggerEvent() {
    this.myEvent.emit({ data: 'some data' });
  }
}

В данном примере, когда вызывается метод triggerEvent, генерируется кастомное событие myEvent. Такие события можно слушать внутри других компонентов или на уровне DOM.

Инструменты для тестирования событий

Stencil использует Jest для тестирования. Для имитации взаимодействия с событиями и компонентов используется несколько ключевых инструментов:

  1. @stencil/core/testing — этот пакет включает в себя утилиты для тестирования компонентов, включая методы для имитации событий.
  2. Jest — тестовый фреймворк для выполнения юнит-тестов и асинхронных проверок.

Stencil предоставляет утилиту для рендеринга компонентов в тестах. Используя эту утилиту, можно тестировать обработку событий с помощью нескольких простых шагов.

Пример тестирования событий

Рассмотрим пример компонента, который генерирует событие по клику на кнопку.

@Component({
  tag: 'my-button',
  shadow: true,
})
export class MyButton {
  @Event() buttonClicked: EventEmitter;

  handleClick() {
    this.buttonClicked.emit('clicked');
  }

  render() {
    return (
      <button onCl ick={() => this.handleClick()}>Click Me</button>
    );
  }
}

В этом примере компонент генерирует событие buttonClicked при клике на кнопку. Теперь можно написать тест для проверки того, что событие действительно эмиттируется.

import { newSpecPage } from '@stencil/core/testing';
import { MyButton } from './my-button';

describe('my-button', () => {
  it('emits buttonClicked event on click', async () => {
    const page = await newSpecPage({
      components: [MyButton],
      html: `<my-button></my-button>`,
    });

    const spy = jest.fn();
    page.root.addEventListener('buttonClicked', spy);

    const button = page.root.shadowRoot.querySelector('button');
    button.click();

    expect(spy).toHaveBeenCalled();
    expect(spy).toHaveBeenCalledWith(expect.objectContaining({
      detail: 'clicked',
    }));
  });
});

Этот тест выполняет несколько шагов:

  1. Рендерит компонент my-button с помощью newSpecPage.
  2. Добавляет слушатель на кастомное событие buttonClicked.
  3. Имитирует клик на кнопке.
  4. Проверяет, что событие было вызвано, а также что оно содержит ожидаемое значение в detail.

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

В Stencil можно работать с асинхронными событиями. Это полезно в случае, когда необходимо протестировать события, которые происходят с задержкой, например, при вызове API или асинхронной операции.

Пример компонента, который генерирует асинхронное событие:

@Component({
  tag: 'async-button',
  shadow: true,
})
export class AsyncButton {
  @Event() asyncEvent: EventEmitter;

  async handleClick() {
    const result = await new Promise((resolve) => setTimeout(() => resolve('done'), 1000));
    this.asyncEvent.emit(result);
  }

  render() {
    return (
      <button onCl ick={() => this.handleClick()}>Click Me</button>
    );
  }
}

Теперь можно протестировать асинхронное событие:

describe('async-button', () => {
  it('emits asyncEvent with result after delay', async () => {
    const page = await newSpecPage({
      components: [AsyncButton],
      html: `<async-button></async-button>`,
    });

    const spy = jest.fn();
    page.root.addEventListener('asyncEvent', spy);

    const button = page.root.shadowRoot.querySelector('button');
    button.click();

    await new Promise(resolve => setTimeout(resolve, 1100)); // Ожидаем завершения асинхронной операции

    expect(spy).toHaveBeenCalled();
    expect(spy).toHaveBeenCalledWith(expect.objectContaining({
      detail: 'done',
    }));
  });
});

В этом тесте:

  1. Имитация клика на кнопку.
  2. Ожидание завершения асинхронной операции с помощью setTimeout.
  3. Проверка, что событие было сгенерировано с ожидаемым результатом.

Мокирование событий

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

Пример мокирования:

it('mocks event listener', async () => {
  const page = await newSpecPage({
    components: [MyButton],
    html: `<my-button></my-button>`,
  });

  const mock = jest.fn();
  const spy = jest.spyOn(page.root, 'dispatchEvent').mockImplementation(mock);

  const button = page.root.shadowRoot.querySelector('button');
  button.click();

  expect(mock).toHaveBeenCalled();
});

В этом примере используется jest.spyOn для того, чтобы отслеживать вызовы dispatchEvent и мокировать его поведение. Это полезно для тестирования, когда нужно проверить, были ли сгенерированы события в определенных ситуациях.

Работа с пользовательскими событиями

В Stencil можно использовать пользовательские события, которые предоставляют дополнительную гибкость при тестировании. Такие события могут не только передавать данные через detail, но и иметь дополнительные параметры, такие как bubbles, cancelable и т. д.

Пример создания события с дополнительными параметрами:

@Component({
  tag: 'custom-button',
  shadow: true,
})
export class CustomButton {
  @Event() customEvent: EventEmitter;

  handleClick() {
    const event = new CustomEvent('customEvent', {
      bubbles: true,
      cancelable: true,
      detail: { customData: 'data' },
    });
    this.customEvent.emit(event);
  }

  render() {
    return (
      <button onCl ick={() => this.handleClick()}>Click Me</button>
    );
  }
}

Тестирование пользовательского события:

it('emits custom event with custom data', async () => {
  const page = await newSpecPage({
    components: [CustomButton],
    html: `<custom-button></custom-button>`,
  });

  const spy = jest.fn();
  page.root.addEventListener('customEvent', spy);

  const button = page.root.shadowRoot.querySelector('button');
  button.click();

  expect(spy).toHaveBeenCalledWith(expect.objectContaining({
    detail: { customData: 'data' },
  }));
});

В этом примере тестируется, что событие содержит нужные данные и имеет правильные параметры.

Заключение

Тестирование событий в Stencil требует внимательного подхода, особенно когда речь идет о кастомных и асинхронных событиях. С помощью инструментов Stencil и Jest можно эффективно создавать тесты, которые проверяют правильность эмитации событий, их обработку и взаимодействие с другими компонентами. Важно учитывать особенности работы с асинхронностью и мокированием, чтобы создать надежные и воспроизводимые тесты.