Using CSS-Modules in Vue single-file components is simple (compared to other frontend libraries/frameworks). We add the module attribute to the style tag and we’re good to go. However, there may be some scenarios where we might want to use these CSS-Module classes in our unit tests. Let’s see how to test Vitest CSS Module classes.
CSS Classes in Unit Tests
There are two main reasons for using CSS classes in unit tests. The first is to find elements, however, this approach is not a good practice, since these classes can be renamed, or even removed. A better approach is using another attribute, which has one purpose of finding elements. Furthermore, it can also be used for e2e tests. For example:
<ChatBubble
direction="right"
data-test-id="chat-bubble"
>
message goes here
</ChatBubble>
The second reason is asserting that an element has a certain class. Mostly these classes are dynamically applied depending on a condition, that’s what we will be testing in this tutorial.
ChatBubble Example Component
The example we are going to use is a basic ChatBubble
component similar to what we use in messaging apps. It will have a wrapper
class applied to the root element, and either a left
or right
class applied dynamically to change its direction:
<script setup lang="ts">
import { type PropType } from 'vue'
type direction = 'left' | 'right'
defineProps({
direction: {
type: String as PropType<Side>,
default: 'left',
},
})
</script>
<template>
<div
:class="[
$style.wrapper,
$style[direction]
]"
>
<slot />
</div>
</template>
<style lang="scss" module>
$color-gray: #a8b2b3;
.wrapper {
position: relative;
background: $color-gray;
border-radius: 0.4em;
padding: 1rem;
max-width: 10rem;
color: black;
font-weight: bold;
text-align: left;
}
.left:after,
.right:after {
content: '';
position: absolute;
top: 50%;
width: 0;
height: 0;
border: 18px solid transparent;
margin-top: -18px;
}
.left:after {
right: 0;
border-left-color: $color-gray;
border-right: 0;
margin-right: -18px;
}
.right:after {
left: 0;
border-right-color: $color-gray;
border-left: 0;
margin-left: -18px;
}
</style>
After using the component we can end up with something like this:
I am not sure if this conversation is accurate, as I’ve never purchased a Pizza from Papa John’s.
Test Vitest CSS Modules
To get started, we add configuration to Vite
:
/// <reference types="vitest" />
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
test: {
environment: 'happy-dom',
css: {
modules: {
classNameStrategy: 'non-scoped',
},
},
},
plugins: [vue()],
})
Since CSS-Module classes will be hashed to make them scoped to the current component, that produces issues for us when trying to use them in our tests, as we don’t know what the hash will be. classNameStrategy
tells Vite not to scope the classes in our test environment.
Next, we create the test file:
import { describe, test, expect } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import ChatBubble from './ChatBubble.vue'
describe('ChatBubble Component', () => {
test('applies "left" class name when direction prop value is "left"', () => {
const wrapper = shallowMount(ChatBubble, {
props: {
direction: 'left',
},
})
expect(wrapper.classes()).toContain('left')
expect(wrapper.classes()).not.toContain('right')
})
test('applies "right" class name when direction prop value is "right"', () => {
const wrapper = shallowMount(ChatBubble, {
props: {
direction: 'right',
},
})
expect(wrapper.classes()).toContain('right')
expect(wrapper.classes()).not.toContain('left')
})
})
As we can see, we are checking that the right class name is applied depending on the value of the direction
prop.