Create `transition` util to resolve a promise when an animation ends

pull/12675/head
LB 2024-12-05 15:28:59 +10:00 zatwierdzone przez Matt Westcott
rodzic 4760509d65
commit d42af56220
2 zmienionych plików z 110 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,83 @@
import { transition } from './transition';
jest.useFakeTimers();
describe('transition', () => {
beforeAll(() => {
document.body.innerHTML = `<main id="main"></main>`;
});
it('should resolve the immediately if maxDelay is 0 or falsy', async () => {
expect(
await transition(document.getElementById('main'), { maxDelay: 0 }),
).toBe(null);
expect(
await transition(document.getElementById('main'), { maxDelay: false }),
).toBe(null);
expect(
await transition(document.getElementById('main'), { maxDelay: -1 }),
).toBe(null);
});
it('should resolve after the maxDelay (350ms) if no events are fired', async () => {
const resolve = jest.fn();
transition(document.getElementById('main')).then(resolve);
await jest.advanceTimersByTimeAsync(200);
expect(resolve).not.toHaveBeenCalled();
await jest.advanceTimersByTimeAsync(149);
expect(resolve).not.toHaveBeenCalled();
await jest.advanceTimersByTimeAsync(1);
expect(resolve).toHaveBeenCalledWith(null);
});
it('should resolve if transitionend or animationend is fired', async () => {
const resolve = jest.fn();
transition(document.getElementById('main')).then(resolve);
await jest.advanceTimersByTimeAsync(200);
expect(resolve).not.toHaveBeenCalled();
const event = new Event('transitionend', {
bubbles: true,
cancelable: false,
});
document.getElementById('main').dispatchEvent(event);
await jest.advanceTimersByTimeAsync(0);
expect(resolve).toHaveBeenCalledWith(event);
});
it('should resolve if animationend is fired', async () => {
const resolve = jest.fn();
transition(document.getElementById('main')).then(resolve);
await jest.advanceTimersByTimeAsync(200);
expect(resolve).not.toHaveBeenCalled();
const event = new Event('animationend', {
bubbles: true,
cancelable: false,
});
document.getElementById('main').dispatchEvent(event);
await jest.advanceTimersByTimeAsync(0);
expect(resolve).toHaveBeenCalledWith(event);
});
});

Wyświetl plik

@ -0,0 +1,27 @@
/**
* Returns a promise that will resolve after either the animation, transition
* or the max delay of time is reached.
*
* If `maxDelay` is provided as zero or a falsy value, the promise resolve immediately.
*/
export const transition = (element: HTMLElement, { maxDelay = 350 } = {}) =>
new Promise<AnimationEvent | TransitionEvent | null>((resolve) => {
if (!maxDelay || maxDelay <= 0) {
resolve(null);
return;
}
let timer: number | undefined;
const finish = (event: AnimationEvent | TransitionEvent | null) => {
if (event && event.target !== element) return;
window.clearTimeout(timer);
element.removeEventListener('transitionend', finish);
element.removeEventListener('animationend', finish);
resolve(event || null);
};
element.addEventListener('animationend', finish);
element.addEventListener('transitionend', finish);
timer = window.setTimeout(finish, maxDelay);
});