Юнит-тесты для фокуса

Библиотека ally.js предназначена для управления доступностью элементов на веб-странице, в первую очередь с точки зрения фокуса и клавиатурной навигации. Она предоставляет инструменты для проверки доступности, перемещения фокуса и работы с интерактивными элементами.

Главным объектом взаимодействия является ally. Он позволяет:

  • Определять доступные для фокуса элементы.
  • Управлять программным фокусом.
  • Проверять состояние элементов на доступность для клавиатуры.

Пример базового поиска всех элементов, которые могут быть сфокусированы:

const focusableElements = ally.query.focusable({ context: document.body });

Аргумент context позволяет ограничить область поиска определённым контейнером. Функция возвращает массив DOM-элементов, которые можно использовать для управления фокусом.


Проверка доступности элемента для фокуса

Ally.js позволяет определить, может ли конкретный элемент получить фокус. Это важно для юнит-тестов, где нужно убедиться, что элементы интерфейса корректно участвуют в навигации клавиатурой.

const button = document.querySelector('#submit');
const isFocusable = ally.query.is.focusable(button);
console.log(isFocusable); // true или false

Метод ally.query.is.focusable учитывает:

  • Видимость элемента (display: none, visibility: hidden).
  • Атрибут tabindex.
  • Тип элемента (input, button, a с href и т.д.).

Для тестирования интерактивных компонентов это ключевой инструмент, позволяющий автоматически проверять состояние фокуса.


Программное управление фокусом

Для перемещения фокуса между элементами используется функция ally.focus(). Она обеспечивает корректное выделение элемента с учётом всех правил доступности.

const input = document.querySelector('#email');
ally.focus(input);

Важные параметры функции:

  • context — контейнер, в котором производится поиск.
  • ignore — селекторы элементов, которые следует пропустить.
  • strategy — способ выбора элемента (по умолчанию first, можно использовать last, next, previous).

Пример установки фокуса на первый доступный элемент формы:

ally.focus.first({ context: document.querySelector('form') });

Навигация фокуса для динамического контента

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

const modal = document.querySelector('#modal');
ally.focus.trap(modal, {
  ignoreClicks: true
});

ally.focus.trap позволяет:

  • Заблокировать фокус внутри контейнера.
  • Игнорировать клики вне контейнера при необходимости.
  • Автоматически возвращать фокус на предыдущий элемент при закрытии модального окна.

Это критично для юнит-тестов, которые проверяют сценарии взаимодействия с пользователем, включая открытие и закрытие модальных компонентов.


Интеграция ally.js с юнит-тестами

Для автоматических тестов используется комбинация ally.js и тестовых библиотек (например, Jest или Mocha). Основные сценарии тестирования фокуса:

  1. Проверка, что все интерактивные элементы доступны для фокуса.
  2. Проверка правильного порядка табуляции (tabindex).
  3. Проверка программного перемещения фокуса при событиях (открытие модального окна, смена вкладки).
  4. Проверка “ловушек фокуса” (focus trap) в модальных контейнерах.

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

test('Кнопка submit доступна для фокуса', () => {
  document.body.innerHTML = `<button id="submit">Отправить</button>`;
  const button = document.querySelector('#submit');
  expect(ally.query.is.focusable(button)).toBe(true);
});

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

test('Фокус остаётся внутри модального окна', () => {
  document.body.innerHTML = `
    <div id="modal" tabindex="-1">
      <button id="ok">OK</button>
      <button id="cancel">Cancel</button>
    </div>
  `;
  const modal = document.querySelector('#modal');
  ally.focus.trap(modal);
  
  const okButton = document.querySelector('#ok');
  ally.focus(okButton);
  
  expect(document.activeElement).toBe(okButton);
});

Работа с атрибутом tabindex

Ally.js учитывает атрибут tabindex для определения последовательности фокусировки.

  • tabindex="0" — элемент включается в естественный порядок табуляции.
  • tabindex="-1" — элемент можно сфокусировать программно, но он исключён из табуляции.
  • tabindex="positive number" — приоритетный порядок фокуса.

Пример проверки правильного порядка:

const elements = ally.query.focusable({ context: document.body });
const order = elements.map(el => el.id);
console.log(order); // ['firstInput', 'secondInput', 'submitButton']

Юнит-тест может проверять, что элементы в DOM соответствуют ожидаемому порядку.


Особенности тестирования скрытых и неинтерактивных элементов

Ally.js автоматически исключает из фокуса:

  • Элементы с display: none или visibility: hidden.
  • <input disabled>, <button disabled>.
  • Элементы без атрибута href для ссылок.

Это позволяет в тестах фокусироваться только на реально доступных пользователю элементах.

const hiddenButton = document.createElement('button');
hiddenButton.style.display = 'none';
document.body.appendChild(hiddenButton);

expect(ally.query.is.focusable(hiddenButton)).toBe(false);

Асинхронное управление фокусом

Для компонентов, которые динамически загружаются (например, через AJAX), ally.js поддерживает асинхронные сценарии:

setTimeout(() => {
  const newInput = document.createElement('input');
  document.body.appendChild(newInput);
  ally.focus(newInput);
}, 100);

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


Итоговые рекомендации по юнит-тестированию фокуса

  • Всегда использовать ally.query.is.focusable для проверки доступности элемента.
  • Использовать ally.focus для программного перемещения фокуса в тестах.
  • Проверять порядок фокуса и работу с tabindex.
  • Тестировать “ловушки фокуса” для модальных и всплывающих элементов.
  • Исключать скрытые и неинтерактивные элементы из сценариев проверки.

Такой подход обеспечивает надёжное тестирование интерактивных компонентов интерфейса с точки зрения доступности.