Юнит-тестирование компонентов, построенных вокруг библиотеки Tom Select, позволяет контролировать корректность поведения пользовательского интерфейса при изменении конфигурации, подключении плагинов, обновлении зависимостей и масштабировании приложения. Основная задача тестов — гарантировать, что компонент выбора элементов работает одинаково стабильно вне зависимости от внешних условий.
Компоненты Tom Sel ect часто содержат:
Без тестирования такие компоненты быстро становятся источником регрессий.
Юнит-тестирование компонентов Tom Select обычно охватывает следующие области:
Проверяется:
Пример:
const select = document.createElement('select');
select.innerHTML = `
<option value="js">JavaScript</option>
`;
document.body.appendChild(select);
const tom = new TomSelect(select);
expect(tom).toBeDefined();
expect(tom.options.js.text).toBe('JavaScript');
Проверяется:
tom.setValue('js');
expect(tom.getValue()).toBe('js');
tom.clear();
expect(tom.getValue()).toBe('');
Проверяется:
const results = tom.search('java');
expect(results.items.length).toBeGreaterThan(0);
Проверяется:
const onCha nge = jest.fn();
const tom = new TomSelect(select, {
onChange
});
tom.setValue('js');
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith('js');
Проверяется:
Наиболее популярный инструмент для unit-тестов JavaScript-компонентов.
Основные возможности:
Установка:
npm install --save-dev jest
Современная альтернатива Jest, особенно популярная в проектах с Vite.
Установка:
npm install --save-dev vitest
Конфигурация:
// vite.config.js
export default defineConfig({
test: {
environment: 'jsdom'
}
});
Tom Select активно взаимодействует с DOM, поэтому тестовая среда должна эмулировать браузер.
JSDOM предоставляет:
Без JSDOM значительная часть тестов работать не будет.
При тестировании UI-компонентов удобно использовать:
npm install --save-dev @testing-library/dom
или:
npm install --save-dev @testing-library/react
Testing Library позволяет тестировать компонент ближе к пользовательскому поведению.
Типичный unit-тест Tom Select состоит из нескольких этапов:
Пример:
describe('TomSelect', () => {
let select;
let tom;
beforeEach(() => {
document.body.innerHTML = `
<select id="skills">
<option value="js">JavaScript</option>
<option value="ts">TypeScript</option>
</select>
`;
select = document.querySelector('#skills');
tom = new TomSelect(select);
});
afterEach(() => {
tom.destroy();
document.body.innerHTML = '';
});
test('setValue устанавливает значение', () => {
tom.setValue('ts');
expect(tom.getValue()).toBe('ts');
});
});
Tom Select создаёт большое количество дополнительных элементов:
Проверка DOM позволяет убедиться, что интерфейс строится корректно.
expect(
document.querySelector('.ts-wrapper')
).not.toBeNull();
expect(
document.querySelector('.ts-dropdown')
).not.toBeNull();
Tom Select динамически изменяет классы состояния.
Проверяются:
Пример:
tom.focus();
expect(
tom.wrapper.classList.contains('focus')
).toBe(true);
document.body.innerHTML = `
<select multiple id="tags">
<option value="js">JS</option>
<option value="vue">Vue</option>
<option value="react">React</option>
</select>
`;
const tom = new TomSelect('#tags');
tom.setValue(['js', 'vue']);
expect(tom.items.length).toBe(2);
expect(tom.getValue()).toEqual(['js', 'vue']);
tom.removeItem('js');
expect(tom.items.includes('js')).toBe(false);
Tom Select активно работает с текстовым input.
tom.control_input.value = 'react';
tom.onInput();
expect(tom.lastValue).toBe('react');
tom.open();
expect(tom.isOpen).toBe(true);
Проверка закрытия:
tom.close();
expect(tom.isOpen).toBe(false);
Если включён режим создания новых элементов:
const tom = new TomSelect(select, {
create: true
});
tom.createItem('Node.js');
expect(tom.options['Node.js']).toBeDefined();
Tom Select позволяет полностью переопределять шаблоны.
Пример:
const tom = new TomSelect(select, {
render: {
option(data, escape) {
return `
<div class="custom-option">
${escape(data.text)}
</div>
`;
}
}
});
Тест:
tom.refreshOptions();
expect(
document.querySelector('.custom-option')
).not.toBeNull();
Snapshot позволяет отслеживать изменения DOM.
Пример для Jest:
expect(tom.wrapper.outerHTML).toMatchSnapshot();
Snapshot особенно полезен:
const loadMock = jest.fn((query, callback) => {
callback([
{ value: '1', text: 'JavaScript' }
]);
});
const tom = new TomSelect(select, {
load: loadMock
});
tom.load('java');
expect(loadMock).toHaveBeenCalled();
expect(tom.options['1']).toBeDefined();
Асинхронная загрузка обычно использует fetch.
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([
{ id: 1, name: 'JavaScript' }
])
})
);
Tom Select может использовать задержку запросов.
jest.useFakeTimers();
const load = jest.fn();
const tom = new TomSelect(select, {
loadThrottle: 300,
load
});
tom.onInput();
jest.advanceTimersByTime(300);
expect(load).toHaveBeenCalled();
Destroy особенно важен для SPA-приложений.
Проверяется:
tom.destroy();
expect(
document.querySelector('.ts-wrapper')
).toBeNull();
tom.disable();
expect(tom.isDisabled).toBe(true);
Повторное включение:
tom.enable();
expect(tom.isDisabled).toBe(false);
const tom = new TomSelect(select, {
placeholder: 'Выберите язык'
});
expect(
tom.control_input.placeholder
).toBe('Выберите язык');
document.body.innerHTML = `
<select id="skills">
<optgroup label="Frontend">
<option value="js">JS</option>
</optgroup>
</select>
`;
const tom = new TomSelect('#skills');
expect(
tom.optgroups
).toBeDefined();
Плагины должны тестироваться отдельно от основного компонента.
Пример:
TomSelect.define('test_plugin', function() {
this.testMethod = () => 'plugin';
});
Тест:
const tom = new TomSelect(select, {
plugins: ['test_plugin']
});
expect(tom.testMethod()).toBe('plugin');
Одна из ключевых задач — исключение влияния тестов друг на друга.
Для этого:
Пример:
afterEach(() => {
jest.clearAllMocks();
document.body.innerHTML = '';
});
Tom Select часто оборачивается в React-компонент.
Пример:
function SelectComponent() {
useEffect(() => {
const tom = new TomSelect('#skills');
return () => tom.destroy();
}, []);
return (
<select id="skills">
<option value="js">JS</option>
</select>
);
}
Тест:
import { render } fr om '@testing-library/react';
test('рендер компонента', () => {
render(<SelectComponent />);
expect(
document.querySelector('.ts-wrapper')
).not.toBeNull();
});
Tom Select генерирует события:
Пример:
const handler = jest.fn();
tom.on('item_add', handler);
tom.addItem('js');
expect(handler).toHaveBeenCalled();
Tom Select активно использует клавиатурное управление.
Тестируются:
Пример:
const event = new KeyboardEvent('keydown', {
key: 'ArrowDown'
});
tom.control.dispatchEvent(event);
const result = tom.search('react');
expect(result.items.length).toBe(1);
const tom = new TomSelect(select, {
searchField: ['title', 'description']
});
Тест:
expect(tom.settings.searchField)
.toContain('description');
const load = jest.fn();
const tom = new TomSelect(select, {
preload: true,
load
});
expect(load).toHaveBeenCalled();
const tom = new TomSelect(select, {
maxItems: 2
});
tom.addItem('js');
tom.addItem('vue');
tom.addItem('react');
expect(tom.items.length).toBe(2);
const tom = new TomSelect(select, {
create: true,
persist: false
});
tom.createItem('Custom');
tom.clear();
expect(tom.options.Custom).toBeUndefined();
Предпочтительно проверять:
Менее надёжны тесты внутренних приватных методов.
Избыточные snapshot-тесты усложняют поддержку.
Лучше:
Каждый тест должен запускаться отдельно без зависимости от предыдущих.
Чрезмерное мокирование делает тесты искусственными.
Следует мокировать:
Не следует мокировать сам DOM без необходимости.
Без destroy:
Неправильная работа с Promise вызывает:
Например:
expect(tom.sifter).toBeDefined();
Такие тесты плохо переживают обновления библиотеки.
Хотя unit-тесты важны, многие сценарии Tom Select лучше проверяются интеграционно:
Для этого часто используют:
Типичная структура:
tests/
├── unit/
│ ├── tom-select.spec.js
│ ├── plugins.spec.js
│ ├── async.spec.js
│ └── render.spec.js
│
├── integration/
│ ├── forms.spec.js
│ └── api.spec.js
Наиболее критичные зоны:
| Область | Приоритет |
|---|---|
| Выбор значений | Высокий |
| События | Высокий |
| Async load | Высокий |
| destroy | Высокий |
| Keyboard navigation | Средний |
| Render templates | Средний |
| CSS-классы | Низкий |
Тесты обычно запускаются:
Пример GitHub Actions:
name: tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm install
- run: npm test
Jest:
npm test -- --coverage
Vitest:
vitest --coverage
Покрытие помогает обнаруживать:
Однако высокий coverage не гарантирует качество тестов, если assertions не проверяют реальное поведение компонента.