Unit тесты

Библиотека Tabbable предназначена для определения элементов на странице, доступных для навигации с помощью клавиши Tab. Она обеспечивает точное определение фокусируемых элементов, учитывая различные состояния DOM, стили и атрибуты. Основной задачей является упрощение работы с доступностью (accessibility) в веб-приложениях.

Импорт и базовое использование

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

import tabbable from 'tabbable';

Функция tabbable(container) возвращает массив всех элементов внутри контейнера, которые могут получить фокус с помощью Tab. Если контейнер не указан, анализируется весь документ:

const focusableElements = tabbable(document.body);
console.log(focusableElements);

Ключевое свойство — учет CSS-свойств, таких как display: none или visibility: hidden, а также атрибутов disabled и tabindex=-1. Элементы с этими свойствами исключаются из возвращаемого массива.

Различие между tabbable и focusable

Библиотека предоставляет два метода:

  • tabbable(container) — возвращает элементы, доступные для навигации по Tab.
  • focusable(container) — возвращает элементы, которые теоретически могут получить фокус, но не обязательно через Tab.

Пример использования:

import { tabbable, focusable } from 'tabbable';

const allFocusable = focusable(document.body);
const tabOrder = tabbable(document.body);

focusable полезен при программной установке фокуса, а tabbable — для управления последовательностью Tab.

Обработка сложных DOM-структур

Tabbable корректно работает с вложенными и скрытыми элементами, учитывая следующие нюансы:

  • Элементы внутри iframe.
  • fieldset с атрибутом disabled блокирует все вложенные элементы.
  • Псевдо-элементы и скрытые блоки игнорируются.

Атрибут tabindex

Элементы с явным tabindex обрабатываются отдельно:

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

Пример сортировки элементов по tabindex:

const ordered = tabbable(document.body).sort((a, b) => {
  const aIndex = a.tabIndex || 0;
  const bIndex = b.tabIndex || 0;
  return aIndex - bIndex;
});

Объектные и функциональные возможности

Tabbable возвращает массив элементов DOM, но для сложной логики часто используется дополнительная фильтрация и обработка:

const buttons = tabbable(document.body).filter(el => el.tagName === 'BUTTON');
buttons.forEach(btn => btn.addEventListener('focus', () => console.log('Button focused')));

Это позволяет:

  • Реализовать кастомные клавиатурные циклы.
  • Обеспечить корректное управление модальными окнами.
  • Игнорировать элементы, скрытые с помощью CSS или атрибутов.

Обработка динамических изменений DOM

Для динамически изменяющихся страниц важно повторно вызывать tabbable после добавления или удаления элементов:

function updateFocusableElements() {
  return tabbable(document.querySelector('#dynamic-container'));
}

Это гарантирует актуальность массива элементов, доступных для Tab.

Интеграция с Unit тестами

В тестах на доступность важно проверять:

  1. Корректность порядка Tab.
  2. Исключение скрытых и отключенных элементов.
  3. Правильную работу с tabindex.

Пример теста с использованием Jest:

import tabbable from 'tabbable';

test('Проверка элементов, доступных по Tab', () => {
  document.body.innerHTML = `
    <button>Кнопка 1</button>
    <div style="display:none">
      <button>Скрытая кнопка</button>
    </div>
    <input type="text" tabindex="2">
    <button tabindex="1">Кнопка с tabindex</button>
  `;

  const elements = tabbable(document.body);
  const tags = elements.map(el => el.tagName);
  
  expect(tags).toEqual(['BUTTON', 'BUTTON', 'INPUT']);
});

Проверка модальных окон

Для модальных окон важно блокировать доступ к элементам вне окна:

const modal = document.getElementById('modal');
const modalElements = tabbable(modal);

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

Настройка кастомных фильтров

Tabbable позволяет использовать кастомные функции для фильтрации элементов:

const focusableLinks = tabbable(document.body, {
  getShadowRoot: el => el.shadowRoot
}).filter(el => el.tagName === 'A');

Это особенно полезно при работе с Web Components и Shadow DOM, где стандартные методы могут не обнаружить элементы внутри теневых корней.

Производительность и оптимизация

Для больших DOM-структур рекомендуется:

  • Ограничивать контейнер сканирования.
  • Использовать кеширование результатов, если элементы не изменяются.
  • Комбинировать с focusable для программной установки фокуса без полного пересчета порядка Tab.
let cachedTabbable = tabbable(container);
function refreshTabbable() {
  cachedTabbable = tabbable(container);
}

Итоги применения в разработке

Tabbable является основой для:

  • Доступной навигации по Tab.
  • Создания модальных окон и всплывающих интерфейсов.
  • Тестирования доступности компонентов.
  • Управления фокусом в сложных SPA и динамических интерфейсах.

Её интеграция с Unit тестами позволяет уверенно контролировать поведение фокуса, предотвращая ошибки навигации и повышая общую доступность веб-приложений.