From 4c142aecbbb600b9523faa24f86bbb152404a1ee Mon Sep 17 00:00:00 2001 From: Matthieu Date: Thu, 23 Apr 2026 11:46:13 +0200 Subject: [PATCH] refactor(front) : extrait debounce dans shared/utils + tests Vitest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T-013 — sort la fonction debounce inline de audit-log.vue vers frontend/shared/utils/debounce.ts (auto-importe par Nuxt) et ajoute 3 tests Vitest (delay coalesce, derniere invocation gagne, plusieurs executions espacees). Pret pour reutilisation sur les prochaines pages avec recherche/filtres. --- .../modules/core/pages/admin/audit-log.vue | 13 ++--- .../shared/utils/__tests__/debounce.test.ts | 52 +++++++++++++++++++ frontend/shared/utils/debounce.ts | 15 ++++++ 3 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 frontend/shared/utils/__tests__/debounce.test.ts create mode 100644 frontend/shared/utils/debounce.ts diff --git a/frontend/modules/core/pages/admin/audit-log.vue b/frontend/modules/core/pages/admin/audit-log.vue index ebb6aa1..0a88032 100644 --- a/frontend/modules/core/pages/admin/audit-log.vue +++ b/frontend/modules/core/pages/admin/audit-log.vue @@ -301,16 +301,9 @@ async function loadEntries(): Promise { } } -// Debounce utilitaire pour le champ texte performedBy : evite un refetch a -// chaque frappe (reseau + SQL) et laisse l'utilisateur finir sa saisie. -function debounce void>(fn: T, delay: number): T { - let timer: ReturnType | null = null - return ((...args: Parameters) => { - if (null !== timer) clearTimeout(timer) - timer = setTimeout(() => fn(...args), delay) - }) as T -} - +// Debounce auto-importe depuis `frontend/shared/utils/debounce.ts` : evite +// un refetch a chaque frappe sur le champ texte performedBy (reseau + SQL) +// et laisse l'utilisateur finir sa saisie avant de lancer la requete. const debouncedReload = debounce(() => loadEntries(), 300) function toIso(localDateTime: string): string { diff --git a/frontend/shared/utils/__tests__/debounce.test.ts b/frontend/shared/utils/__tests__/debounce.test.ts new file mode 100644 index 0000000..9c5db1d --- /dev/null +++ b/frontend/shared/utils/__tests__/debounce.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect, vi } from 'vitest' +import { debounce } from '../debounce' + +describe('debounce', () => { + it('attend delay ms avant d\'appeler fn une seule fois apres plusieurs invocations rapides', () => { + vi.useFakeTimers() + const fn = vi.fn() + const debounced = debounce(fn, 100) + + debounced() + debounced() + debounced() + expect(fn).not.toHaveBeenCalled() + + vi.advanceTimersByTime(100) + expect(fn).toHaveBeenCalledTimes(1) + + vi.useRealTimers() + }) + + it('passe les arguments du dernier appel a fn', () => { + vi.useFakeTimers() + const fn = vi.fn<(a: string, b: number) => void>() + const debounced = debounce(fn, 50) + + debounced('first', 1) + debounced('second', 2) + debounced('third', 3) + vi.advanceTimersByTime(50) + + expect(fn).toHaveBeenCalledTimes(1) + expect(fn).toHaveBeenCalledWith('third', 3) + + vi.useRealTimers() + }) + + it('autorise plusieurs executions espacees dans le temps', () => { + vi.useFakeTimers() + const fn = vi.fn() + const debounced = debounce(fn, 50) + + debounced() + vi.advanceTimersByTime(50) + expect(fn).toHaveBeenCalledTimes(1) + + debounced() + vi.advanceTimersByTime(50) + expect(fn).toHaveBeenCalledTimes(2) + + vi.useRealTimers() + }) +}) diff --git a/frontend/shared/utils/debounce.ts b/frontend/shared/utils/debounce.ts new file mode 100644 index 0000000..1b652e2 --- /dev/null +++ b/frontend/shared/utils/debounce.ts @@ -0,0 +1,15 @@ +/** + * Utilitaire de debounce partage. + * + * Retarde l'execution d'une fonction : chaque appel reset un timer et + * l'execution reelle n'a lieu qu'apres `delay` ms sans nouvelle invocation. + * Utile pour eviter un spam d'appels reseau sur un champ de recherche + * (une requete par touche -> une seule requete apres la derniere frappe). + */ +export function debounce void>(fn: T, delay: number): T { + let timer: ReturnType | null = null + return ((...args: Parameters) => { + if (null !== timer) clearTimeout(timer) + timer = setTimeout(() => fn(...args), delay) + }) as T +}