Фильтрация и сортировка списков

Фильтрация и сортировка данных — ключевые операции при работе с массивами объектов в Vue-приложениях. Тестирование этих операций с помощью Vue Test Utils обеспечивает корректное поведение компонентов и предотвращает логические ошибки при изменении данных.


Модели данных и реактивность

В Vue список объектов обычно хранится в реактивном состоянии компонента:

data() {
  return {
    items: [
      { id: 1, name: 'Apple', category: 'Fruit', price: 50 },
      { id: 2, name: 'Carrot', category: 'Vegetable', price: 20 },
      { id: 3, name: 'Banana', category: 'Fruit', price: 30 }
    ],
    filterCategory: 'Fruit',
    sortKey: 'price',
    sortOrder: 'asc' // asc или desc
  };
}

Реактивные свойства позволяют автоматически обновлять DOM при изменении данных, что упрощает тестирование. При фильтрации и сортировке создаются вычисляемые свойства (computed), чтобы не мутировать оригинальный массив:

computed: {
  filteredItems() {
    return this.items
      .filter(item => this.filterCategory ? item.category === this.filterCategory : true)
      .sort((a, b) => {
        const modifier = this.sortOrder === 'asc' ? 1 : -1;
        return (a[this.sortKey] - b[this.sortKey]) * modifier;
      });
  }
}

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


Тестирование фильтрации

Vue Test Utils позволяет проверять результат вычисляемых свойств и поведение DOM при изменении фильтров. Например, тест для фильтрации по категории:

import { mount } from '@vue/test-utils';
import ItemList from '@/components/ItemList.vue';

describe('ItemList.vue - фильтрация', () => {
  it('отображает только элементы выбранной категории', async () => {
    const wrapper = mount(ItemList);
    wrapper.setData({ filterCategory: 'Fruit' });
    await wrapper.vm.$nextTick();

    const renderedItems = wrapper.findAll('.item-name').map(i => i.text());
    expect(renderedItems).toEqual(['Apple', 'Banana']);
  });

  it('отображает все элементы, если фильтр пустой', async () => {
    const wrapper = mount(ItemList);
    wrapper.setData({ filterCategory: '' });
    await wrapper.vm.$nextTick();

    const renderedItems = wrapper.findAll('.item-name').map(i => i.text());
    expect(renderedItems).toEqual(['Apple', 'Carrot', 'Banana']);
  });
});

Важный момент: при тестировании фильтрации рекомендуется использовать await wrapper.vm.$nextTick(), чтобы гарантировать обновление DOM после изменения реактивного свойства.


Тестирование сортировки

Сортировка проверяется аналогично, с учетом направления и ключа сортировки:

describe('ItemList.vue - сортировка', () => {
  it('сортирует элементы по цене по возрастанию', async () => {
    const wrapper = mount(ItemList);
    wrapper.setData({ sortKey: 'price', sortOrder: 'asc' });
    await wrapper.vm.$nextTick();

    const renderedItems = wrapper.findAll('.item-name').map(i => i.text());
    expect(renderedItems).toEqual(['Carrot', 'Banana', 'Apple']);
  });

  it('сортирует элементы по цене по убыванию', async () => {
    const wrapper = mount(ItemList);
    wrapper.setData({ sortKey: 'price', sortOrder: 'desc' });
    await wrapper.vm.$nextTick();

    const renderedItems = wrapper.findAll('.item-name').map(i => i.text());
    expect(renderedItems).toEqual(['Apple', 'Banana', 'Carrot']);
  });
});

Особое внимание уделяется корректной работе вычисляемого свойства при изменении данных и параметров сортировки.


Комбинирование фильтрации и сортировки

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

describe('ItemList.vue - фильтрация + сортировка', () => {
  it('фильтрует и сортирует элементы одновременно', async () => {
    const wrapper = mount(ItemList);
    wrapper.setData({ filterCategory: 'Fruit', sortKey: 'price', sortOrder: 'desc' });
    await wrapper.vm.$nextTick();

    const renderedItems = wrapper.findAll('.item-name').map(i => i.text());
    expect(renderedItems).toEqual(['Apple', 'Banana']);
  });
});

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


Тонкости и рекомендации

  • Для тестирования больших списков рекомендуется использовать моки данных, чтобы избежать избыточного рендеринга и ускорить выполнение тестов.
  • Проверка DOM должна быть точной, лучше использовать findAll и методы map для извлечения текстового содержимого, а не просто проверять количество элементов.
  • Для динамических фильтров (например, поиск по строке) можно эмулировать ввод с помощью setValue на <input> и проверять реактивное вычисленное свойство.
  • Следует тестировать граничные условия, такие как пустые массивы, отсутствующие ключи сортировки или фильтры, не соответствующие ни одному элементу.

Практический пример: тестирование поиска и сортировки одновременно

it('поиск по имени и сортировка по цене', async () => {
  const wrapper = mount(ItemList);
  wrapper.setData({ filterCategory: '', sortKey: 'price', sortOrder: 'asc' });
  wrapper.vm.searchQuery = 'a'; // поиск по имени
  await wrapper.vm.$nextTick();

  const renderedItems = wrapper.findAll('.item-name').map(i => i.text());
  expect(renderedItems).toEqual(['Banana', 'Carrot', 'Apple']);
});

Тест охватывает несколько механизмов: фильтрацию по тексту, сортировку и реактивность DOM, что обеспечивает комплексную проверку бизнес-логики.


Фильтрация и сортировка списков в Vue требуют тщательного проектирования вычисляемых свойств и последовательного тестирования с Vue Test Utils. Правильная комбинация фильтров и ключей сортировки вместе с корректным управлением реактивностью обеспечивает предсказуемое поведение компонентов и высокое качество кода.