Passer au contenu

Tester les directives Vue

Les directives personnalisées

Les directives personnalisées sont un moyen d’étendre les fonctionnalités HTML dans Vue. Elles peuvent être utiles pour abstraire des manipulations DOM répétitives. Tester ces directives est important pour garantir qu’elles fonctionnent correctement.

Approche de test pour les directives

Contrairement aux composants, les directives sont testées en vérifiant leur effet sur le DOM plutôt que sur le modèle de données Vue. Voici comment procéder:

import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import { createApp } from 'vue'
import ClickOutsideDirective from '@/directives/click-outside'
describe('v-click-outside directive', () => {
it('calls the callback when clicking outside the element', async () => {
// Créer un espion pour la fonction de rappel
const onClickOutside = vi.fn()
// Créer un composant de test qui utilise la directive
const TestComponent = {
template: `
<div>
<div class="inside" v-click-outside="onClickOutside">Inside Element</div>
<div class="outside">Outside Element</div>
</div>
`,
methods: {
onClickOutside
}
}
// Enregistrer la directive
const app = createApp(TestComponent)
app.directive('click-outside', ClickOutsideDirective)
// Monter le composant
const wrapper = mount(TestComponent, {
global: {
directives: {
'click-outside': ClickOutsideDirective
}
}
})
// Simuler un clic à l'intérieur - ne devrait pas déclencher le callback
await wrapper.find('.inside').trigger('click')
expect(onClickOutside).not.toHaveBeenCalled()
// Simuler un clic à l'extérieur - devrait déclencher le callback
await wrapper.find('.outside').trigger('click')
expect(onClickOutside).toHaveBeenCalledTimes(1)
})
})

Tester une directive de formatage de texte

Voici comment tester une directive qui modifie le contenu d’un élément:

import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import FormatCurrencyDirective from '@/directives/format-currency'
describe('v-format-currency directive', () => {
it('formats numbers as currency', () => {
// Créer un composant de test
const TestComponent = {
template: `<div>
<span v-format-currency="1000">1000</span>
<span v-format-currency="1000.5">1000.5</span>
</div>`,
data() {
return {}
}
}
// Monter le composant avec la directive
const wrapper = mount(TestComponent, {
global: {
directives: {
'format-currency': FormatCurrencyDirective
}
}
})
// Vérifier que les valeurs sont correctement formatées
const spans = wrapper.findAll('span')
expect(spans[0].text()).toBe('1 000,00 €')
expect(spans[1].text()).toBe('1 000,50 €')
})
it('updates formatting when value changes', async () => {
// Composant avec une valeur réactive
const TestComponent = {
template: '<div><span v-format-currency="amount">{{ amount }}</span></div>',
data() {
return {
amount: 1000
}
}
}
const wrapper = mount(TestComponent, {
global: {
directives: {
'format-currency': FormatCurrencyDirective
}
}
})
// Vérifier le formatage initial
expect(wrapper.find('span').text()).toBe('1 000,00 €')
// Changer la valeur
await wrapper.setData({ amount: 2000 })
// Vérifier que le formatage est mis à jour
expect(wrapper.find('span').text()).toBe('2 000,00 €')
})
})

Tester une directive avec des paramètres et modificateurs

Si votre directive utilise des paramètres ou des modificateurs, vous pouvez les tester ainsi:

import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import TooltipDirective from '@/directives/tooltip'
describe('v-tooltip directive', () => {
it('applies tooltip with default position', () => {
const TestComponent = {
template: '<button v-tooltip="\'Tooltip text\'">Hover me</button>'
}
const wrapper = mount(TestComponent, {
global: {
directives: {
tooltip: TooltipDirective
}
}
})
// Vérifier les attributs data-* ajoutés par la directive
const button = wrapper.find('button')
expect(button.attributes('data-tooltip')).toBe('Tooltip text')
expect(button.attributes('data-position')).toBe('top') // position par défaut
})
it('uses specified tooltip position', () => {
const TestComponent = {
template: '<button v-tooltip:bottom="\'Tooltip text\'">Hover me</button>'
}
const wrapper = mount(TestComponent, {
global: {
directives: {
tooltip: TooltipDirective
}
}
})
// Vérifier que la position spécifiée est utilisée
const button = wrapper.find('button')
expect(button.attributes('data-position')).toBe('bottom')
})
it('adds "persistent" class with persistent modifier', () => {
const TestComponent = {
template: '<button v-tooltip.persistent="\'Tooltip text\'">Hover me</button>'
}
const wrapper = mount(TestComponent, {
global: {
directives: {
tooltip: TooltipDirective
}
}
})
// Vérifier que la classe est ajoutée en fonction du modificateur
const button = wrapper.find('button')
expect(button.classes()).toContain('tooltip-persistent')
})
})

Tester les hooks de cycle de vie de la directive

Pour les directives qui utilisent différents hooks (comme mounted, updated, unmounted), vous devez tester chaque hook séparément:

import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import TrackVisibilityDirective from '@/directives/track-visibility'
describe('v-track-visibility directive', () => {
// Espionner l'API IntersectionObserver
beforeEach(() => {
// Mock l'API IntersectionObserver
global.IntersectionObserver = class {
constructor(callback) {
this.callback = callback
}
observe = vi.fn()
unobserve = vi.fn()
disconnect = vi.fn()
// Méthode utilitaire pour simuler un changement de visibilité
simulateIntersection(isIntersecting) {
this.callback([{ isIntersecting }])
}
}
})
it('registers observer on mounted', () => {
const onVisible = vi.fn()
const TestComponent = {
template: '<div v-track-visibility="onVisible">Test</div>',
methods: { onVisible }
}
const wrapper = mount(TestComponent, {
global: {
directives: {
'track-visibility': TrackVisibilityDirective
}
}
})
// Vérifier que l'observateur a été configuré
const div = wrapper.find('div').element
expect(global.IntersectionObserver.prototype.observe).toHaveBeenCalledWith(div)
})
it('calls callback when element becomes visible', () => {
const onVisible = vi.fn()
const TestComponent = {
template: '<div v-track-visibility="onVisible">Test</div>',
methods: { onVisible }
}
const wrapper = mount(TestComponent, {
global: {
directives: {
'track-visibility': TrackVisibilityDirective
}
}
})
// Récupérer l'instance d'IntersectionObserver
const observer = global.IntersectionObserver.prototype
// Simuler un élément qui devient visible
observer.simulateIntersection(true)
// Vérifier que le callback est appelé
expect(onVisible).toHaveBeenCalledTimes(1)
expect(onVisible).toHaveBeenCalledWith(true)
})
it('unregisters observer on unmounted', () => {
const TestComponent = {
template: '<div v-track-visibility="() => {}">Test</div>'
}
const wrapper = mount(TestComponent, {
global: {
directives: {
'track-visibility': TrackVisibilityDirective
}
}
})
const div = wrapper.find('div').element
// Détruire le composant
wrapper.unmount()
// Vérifier que l'observateur a été déconnecté
expect(global.IntersectionObserver.prototype.unobserve).toHaveBeenCalledWith(div)
})
})