Множественные v-model привязки

v-model является синтаксическим сахаром для двусторонней привязки данных между компонентом и его состоянием. Начиная с Vue 3, появилась возможность использовать множественные v-model для разных свойств компонента, что позволяет создавать более гибкие и переиспользуемые компоненты. Тестирование таких компонентов требует особого внимания к правильной настройке и проверке обновлений состояния.


Принцип работы множественных v-model

В Vue 3 компонент может иметь несколько v-model через определение отдельных пропов и событий. Например:

export default {
  props: {
    modelValue: String,
    title: String
  },
  emits: ['update:modelValue', 'update:title']
};

Использование в шаблоне:

  • v-model="text" связывает modelValue с переменной text и ожидает событие update:modelValue.
  • v-model:title="header" связывает title с переменной header и ожидает событие update:title.

Это разделение позволяет каждому v-model управлять своей частью состояния независимо.


Монтирование компонента с множественными v-model

При тестировании с помощью Vue Test Utils необходимо корректно передавать начальные значения пропов:

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

const wrapper = mount(MyComponent, {
  props: {
    modelValue: 'initial text',
    title: 'initial title'
  }
});
  • props задают начальное состояние для каждого v-model.
  • Проверка изменения значений должна учитывать, что каждый v-model инициирует отдельное событие обновления.

Проверка обновления состояния

Для тестирования множественных v-model используется метод emitted():

wrapper.vm.$emit('update:modelValue', 'new text');
wrapper.vm.$emit('update:title', 'new title');

expect(wrapper.emitted('update:modelValue')[0]).toEqual(['new text']);
expect(wrapper.emitted('update:title')[0]).toEqual(['new title']);
  • emitted() возвращает объект с ключами по имени событий.
  • Каждое событие содержит массив массивов, где каждый вложенный массив — аргументы, переданные при эмите.
  • Важно проверять каждое событие отдельно, чтобы убедиться, что множественные v-model не конфликтуют.

Изменение данных через взаимодействие с DOM

Если компонент имеет форму ввода для каждого v-model, можно симулировать взаимодействие пользователя:

const inputModel = wrapper.find('input[name="modelValue"]');
const inputTitle = wrapper.find('input[name="title"]');

await inputModel.setValue('updated text');
await inputTitle.setValue('updated title');

expect(wrapper.emitted('update:modelValue')[0]).toEqual(['updated text']);
expect(wrapper.emitted('update:title')[0]).toEqual(['updated title']);
  • setValue() автоматически триггерит событие input.
  • Это позволяет проверить реакцию компонента на реальные действия пользователя.
  • Обязательно тестировать все v-model поля, чтобы убедиться в независимости их работы.

Особенности при работе с глубокими объектами

Когда v-model связан с объектом, важно помнить о реактивности:

const wrapper = mount(MyComponent, {
  props: {
    modelValue: { text: 'initial' },
    title: { header: 'initial' }
  }
});

wrapper.vm.$emit('update:modelValue', { text: 'changed' });
expect(wrapper.emitted('update:modelValue')[0][0].text).toBe('changed');
  • Для объектов v-model создается новая ссылка при каждом обновлении.
  • Мутирование объекта напрямую не будет корректно отражаться на пропах, если компонент не использует reactive или toRefs.

Тестирование компонентов с несколькими v-model в составе родителя

Иногда компонент используется внутри родителя, где v-model привязан к локальному состоянию:

const Parent = {
  template: ``,
  components: { MyComponent },
  data() {
    return { text: 'parent text', header: 'parent header' };
  }
};

const wrapper = mount(Parent);
const child = wrapper.findComponent(MyComponent);

child.vm.$emit('update:modelValue', 'child text');
child.vm.$emit('update:title', 'child header');

expect(wrapper.vm.text).toBe('child text');
expect(wrapper.vm.header).toBe('child header');
  • При тестировании важно монтировать компонент с родителем, чтобы проверить передачу событий вверх.
  • Без этого проверка v-model ограничивается локальным состоянием дочернего компонента.

Рекомендации по покрытию тестами

  1. Тестировать начальные значения: убедиться, что каждый v-model правильно получает prop.
  2. Тестировать пользовательские действия: использовать setValue, trigger для проверки реактивности.
  3. Тестировать события update: проверять аргументы и порядок эмитов.
  4. Тестировать сложные объекты: убедиться, что изменения объектов или массивов корректно передаются через v-model.
  5. Тестировать взаимодействие с родителем: проверить, что родительский state обновляется корректно при эмите дочернего компонента.

Итоговые моменты

  • Vue Test Utils позволяет легко проверять множественные v-model, если правильно настраивать props и события.
  • Каждое обновление должно быть проверено отдельно, особенно при работе с объектами.
  • Взаимодействие через DOM или эмуляцию событий гарантирует, что компонент работает так же, как и в реальном приложении.