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) })})