Skip to content

Commit fd24455

Browse files
Add tests for animation stopping
1 parent 9ade03c commit fd24455

File tree

1 file changed

+101
-1
lines changed

1 file changed

+101
-1
lines changed

packages/x-charts/src/internals/useAnimate.test.tsx

+101-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createRenderer, waitFor } from '@mui/internal-test-utils';
1+
import { createRenderer, screen, waitFor } from '@mui/internal-test-utils';
22
import { expect } from 'chai';
33
import * as React from 'react';
44
import { useAnimate } from '@mui/x-charts/internals/useAnimate';
@@ -226,4 +226,104 @@ describe('useAnimate', () => {
226226
// Should jump to 0 immediately after first call
227227
expect(lastCall).to.equal(0);
228228
});
229+
230+
it('stops animation when its ref is removed from the DOM', async () => {
231+
let calls = 0;
232+
let firstCall: number | null = null;
233+
let lastCall: number | null = null;
234+
235+
function applyProps(element: SVGPathElement, props: { width: number }) {
236+
calls += 1;
237+
238+
if (firstCall === null) {
239+
firstCall = props.width;
240+
}
241+
242+
lastCall = props.width;
243+
}
244+
245+
function TestComponent({ width }: { width: number }) {
246+
const [mountPath, setMountPath] = React.useState(true);
247+
const ref = useAnimate(
248+
{ width },
249+
{ createInterpolator: interpolateWidth, applyProps, initialProps: { width: 0 } },
250+
);
251+
252+
return (
253+
<React.Fragment>
254+
<svg>{mountPath ? <path ref={ref} /> : null}</svg>
255+
<button onClick={() => setMountPath(false)}>Unmount Path</button>
256+
</React.Fragment>
257+
);
258+
}
259+
260+
const { user } = render(<TestComponent width={1000} />);
261+
262+
await waitFor(() => {
263+
expect(lastCall).to.be.greaterThan(10);
264+
});
265+
const lastCallBeforeUnmount = lastCall;
266+
const numCallsBeforeUnmount = calls;
267+
expect(lastCallBeforeUnmount).to.be.lessThan(1000);
268+
269+
await user.click(screen.getByRole('button'));
270+
271+
const { promise: twoAnimationsFrames, resolve } = Promise.withResolvers<void>();
272+
// Wait two frames to ensure the transition is stopped
273+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
274+
275+
await twoAnimationsFrames;
276+
277+
expect(lastCall).to.equal(lastCallBeforeUnmount);
278+
expect(calls).to.equal(numCallsBeforeUnmount);
279+
});
280+
281+
it('stops animation when the hook is unmounted', async () => {
282+
let calls = 0;
283+
let firstCall: number | null = null;
284+
let lastCall: number | null = null;
285+
286+
function applyProps(element: SVGPathElement, props: { width: number }) {
287+
calls += 1;
288+
289+
if (firstCall === null) {
290+
firstCall = props.width;
291+
}
292+
293+
lastCall = props.width;
294+
}
295+
296+
function TestComponent({ width }: { width: number }) {
297+
const ref = useAnimate(
298+
{ width },
299+
{ createInterpolator: interpolateWidth, applyProps, initialProps: { width: 0 } },
300+
);
301+
302+
return (
303+
<svg>
304+
<path ref={ref} />
305+
</svg>
306+
);
307+
}
308+
309+
const { unmount } = render(<TestComponent width={1000} />);
310+
311+
await waitFor(() => {
312+
expect(lastCall).to.be.greaterThan(10);
313+
});
314+
const lastCallBeforeUnmount = lastCall;
315+
const numCallsBeforeUnmount = calls;
316+
expect(lastCallBeforeUnmount).to.be.lessThan(1000);
317+
318+
unmount();
319+
320+
const { promise: twoAnimationFrames, resolve } = Promise.withResolvers<void>();
321+
// Wait two frames to ensure the transition is stopped
322+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
323+
324+
await twoAnimationFrames;
325+
326+
expect(lastCall).to.equal(lastCallBeforeUnmount);
327+
expect(calls).to.equal(numCallsBeforeUnmount);
328+
});
229329
});

0 commit comments

Comments
 (0)