Тестирование CSS модулей

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


Импорт и настройка CSS модулей

CSS модули обычно подключаются через Webpack или Vite с конфигурацией module: true. В компоненте Vue импорт CSS модуля выглядит так:

<script setup>
import styles from './Button.module.css';
</script>

<template>
  <button :class="styles.primary">Click me</button>
</template>

В тестах необходимо учитывать, что классы CSS модулей обычно преобразуются в уникальные строки. Например, класс primary может превратиться в Button_primary__3XyZ.

Чтобы тесты были стабильными, существует два подхода:

  1. Использовать реальные имена классов через мок CSS модулей.
  2. Сравнивать наличие классов через динамические ключи из мока.

Мок CSS модулей

При тестировании Jest часто применяется мок для CSS модулей. Это позволяет вернуть оригинальные имена классов без их преобразования в уникальные строки. В конфигурации Jest можно добавить:

moduleNameMapper: {
  '\\.module\\.css$': 'identity-obj-proxy'
}

identity-obj-proxy возвращает объект, где каждый ключ CSS модуля совпадает с его значением, что упрощает проверку классов:

import { mount } from '@vue/test-utils';
import Button from '@/components/Button.vue';
import styles from '@/components/Button.module.css';

test('кнопка содержит класс primary', () => {
  const wrapper = mount(Button);
  expect(wrapper.classes()).toContain(styles.primary);
});

Такой подход позволяет проверять наличие классов без зависимости от внутреннего хэширования Webpack.


Проверка динамических классов

Компоненты Vue часто используют динамическое присвоение классов:

<template>
  <button :class="[styles.button, { [styles.active]: isActive }]">Click</button>
</template>

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

test('кнопка активна при isActive=true', async () => {
  const wrapper = mount(Button, {
    props: { isActive: true }
  });
  expect(wrapper.classes()).toContain(styles.active);

  await wrapper.setProps({ isActive: false });
  expect(wrapper.classes()).not.toContain(styles.active);
});

Использование wrapper.classes() возвращает массив классов, что упрощает проверку как статических, так и динамических CSS модулей.


Интеграционные проверки стилей

Иногда требуется убедиться, что конкретный стиль применён к элементу. CSS модули из-за локальных классов усложняют прямое сравнение element.style. Для таких случаев применяются методы:

  1. Проверка класса через classes().
  2. Проверка рендеринга через getComputedStyle.

Пример:

test('кнопка имеет правильный цвет фона', () => {
  const wrapper = mount(Button);
  document.body.appendChild(wrapper.element); // нужен для getComputedStyle
  const bgColor = getComputedStyle(wrapper.element).backgroundColor;
  expect(bgColor).toBe('rgb(0, 123, 255)');
});

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


Работа с scoped CSS и CSS модулями

Scoped CSS автоматически добавляет атрибуты вроде data-v-xxxx к элементам. При комбинации с CSS модулями необходимо учитывать:

  • Классы из модуля остаются локальными.
  • Scoped стили проверяются через wrapper.attributes().

Пример проверки:

<template>
  <div :class="styles.container"></div>
</template>

<style scoped>
div[data-v-1234] {
  padding: 10px;
}
</style>

В тесте можно проверить как наличие класса, так и атрибут:

const wrapper = mount(Container);
expect(wrapper.classes()).toContain(styles.container);
expect(wrapper.attributes()).toHaveProperty('data-v-1234');

Это гарантирует корректное взаимодействие CSS модулей и scoped стилей.


Рекомендации по поддержке тестов CSS модулей

  • Использовать identity-obj-proxy для стабильных классов в Jest.
  • Проверять динамические классы через wrapper.classes() вместо прямой строки класса.
  • Для интеграционных тестов применять getComputedStyle только при необходимости.
  • В документации проекта хранить соглашение о стиле имен классов, чтобы избежать конфликтов с моками.
  • Избегать привязки тестов к конкретным хэшированным именам, они могут изменяться при сборке.

Проверка взаимодействия с библиотеками стилей

При использовании библиотек вроде Tailwind с CSS модулями проверка классов становится ключевой. В этом случае:

  • Проверять именно классы через classes().
  • Не полагаться на визуальные свойства в getComputedStyle, так как они могут изменяться в зависимости от темы.

Пример:

const wrapper = mount(Button);
expect(wrapper.classes()).toEqual(expect.arrayContaining(['btn', 'btn-primary']));

Использование arrayContaining позволяет проверять наличие необходимых классов без полной зависимости от всех классов элемента.


Тщательная настройка и тестирование CSS модулей в Vue Test Utils обеспечивает стабильные и предсказуемые тесты компонентов, минимизируя риск ошибок при изменении сборки или стилей.