diff --git a/src/components/ai-edition/TransportBar.test.tsx b/src/components/ai-edition/TransportBar.test.tsx index fa155430d..517f94d97 100644 --- a/src/components/ai-edition/TransportBar.test.tsx +++ b/src/components/ai-edition/TransportBar.test.tsx @@ -1,5 +1,5 @@ import "@testing-library/jest-dom"; -import { act, cleanup, render, screen } from "@testing-library/react"; +import { act, cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { I18nProvider } from "@/contexts/I18nContext"; import { useProjectStore } from "@/lib/ai-edition/store/projectStore"; @@ -92,3 +92,167 @@ describe("TransportBar reads the playhead from the store", () => { expect(screen.getByText("0:04.5")).toBeInTheDocument(); }); }); + +describe("le drag de la barre de progression est coalescé en rAF", () => { + beforeEach(() => { + useProjectStore.setState({ currentTimeSec: 0 }); + }); + + afterEach(() => { + cleanup(); + vi.clearAllMocks(); + vi.unstubAllGlobals(); + }); + + /** rAF piloté à la main : les callbacks ne partent que sur `flush()`, ce qui permet de + * compter ce qui se passe DANS une frame — impossible avec le vrai rAF en test. */ + function stubRaf() { + const pending: FrameRequestCallback[] = []; + vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) => { + pending.push(cb); + return pending.length; + }); + vi.stubGlobal("cancelAnimationFrame", () => { + // rien à annuler : `flush()` ne rejoue que ce qui reste en attente + }); + return () => { + const due = pending.splice(0, pending.length); + for (const cb of due) cb(0); + }; + } + + // C'est LE défaut que ce chemin avait : `onChange` d'un `` se + // déclenche à la cadence du pointeur (jusqu'à 1000 Hz) et appelait `onSeek` à chaque + // fois. Or `onSeek` repose un `seekTarget` dans l'état de la racine de l'éditeur, donc + // re-rend tout et fait poser `