Skip to content

Commit 1e2b75b

Browse files
committed
Move the logic responsible for associating animations with elements inside the Animation class itself
1 parent 0c56721 commit 1e2b75b

File tree

4 files changed

+76
-36
lines changed

4 files changed

+76
-36
lines changed

src/mocks/web-animations-api/Animation.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { mockKeyframeEffect } from './KeyframeEffect';
22
import { mockAnimationPlaybackEvent } from './AnimationPlaybackEvent';
33
import { mockDocumentTimeline } from './DocumentTimeline';
44
import { getEasingFunctionFromString } from './easingFunctions';
5+
import { addAnimation, removeAnimation } from './elementAnimations';
56

67
type ActiveAnimationTimeline = AnimationTimeline & {
78
currentTime: NonNullable<AnimationTimeline['currentTime']>;
@@ -136,15 +137,33 @@ class MockedAnimation extends EventTarget implements Animation {
136137
return null;
137138
}
138139

140+
#addToTarget() {
141+
if (!this.#hasKeyframeEffect() || this.effect.target == null) {
142+
return;
143+
}
144+
145+
addAnimation(this.effect.target, this);
146+
}
147+
148+
#removeFromTarget() {
149+
if (!this.#hasKeyframeEffect() || this.effect.target == null) {
150+
return;
151+
}
152+
153+
removeAnimation(this.effect.target, this);
154+
}
155+
139156
#getNewFinishedPromise() {
140157
this.#promiseStates.finished = 'pending';
141158

142159
return new Promise<Animation>((resolve, reject) => {
143160
this.#resolvers.finished.resolve = (animation) => {
161+
this.#removeFromTarget();
144162
this.#promiseStates.finished = 'resolved';
145163
resolve(animation);
146164
};
147165
this.#resolvers.finished.reject = (error) => {
166+
this.#removeFromTarget();
148167
this.#promiseStates.finished = 'rejected';
149168
reject(error);
150169
};
@@ -153,6 +172,7 @@ class MockedAnimation extends EventTarget implements Animation {
153172

154173
#getNewReadyPromise() {
155174
this.#promiseStates.ready = 'pending';
175+
this.#addToTarget();
156176

157177
return new Promise<Animation>((resolve, reject) => {
158178
this.#resolvers.ready.resolve = (animation) => {

src/mocks/web-animations-api/__tests__/index.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ describe('Animations API', () => {
3636
expect(document.getAnimations().length).toBe(0);
3737
});
3838

39+
it('should add/delete an animation to/from element and document lists if created manually', async () => {
40+
const element = document.createElement('div');
41+
42+
const keyframeEffect = new KeyframeEffect(element, { opacity: 0 }, 100);
43+
const animation = new Animation(keyframeEffect);
44+
animation.play();
45+
46+
expect(element.getAnimations().length).toBe(1);
47+
expect(element.getAnimations()).toContain(animation);
48+
expect(document.getAnimations().length).toBe(1);
49+
expect(document.getAnimations()).toContain(animation);
50+
51+
await animation.finished;
52+
53+
expect(element.getAnimations().length).toBe(0);
54+
expect(document.getAnimations().length).toBe(0);
55+
});
56+
3957
it('should read id from options and attach it to the returned animation', async () => {
4058
const element = document.createElement('div');
4159
const testId = 'testId';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const elementAnimations = new Map<Element, Animation[]>();
2+
3+
export function removeAnimation(element: Element, animation: Animation) {
4+
const animations = elementAnimations.get(element);
5+
6+
if (animations) {
7+
const index = animations.indexOf(animation);
8+
9+
if (index !== -1) {
10+
animations.splice(index, 1);
11+
}
12+
}
13+
}
14+
15+
export function addAnimation(element: Element, animation: Animation) {
16+
const animations = elementAnimations.get(element) ?? [];
17+
animations.push(animation);
18+
19+
elementAnimations.set(element, animations);
20+
}
21+
22+
export function getAnimations(this: Element) {
23+
return elementAnimations.get(this) ?? [];
24+
}
25+
26+
export function getAllAnimations() {
27+
return Array.from(elementAnimations.values()).flat();
28+
}
29+
30+
export function clearAnimations() {
31+
elementAnimations.clear();
32+
}

src/mocks/web-animations-api/index.ts

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
import { mockAnimation } from './Animation';
2-
3-
const elementAnimations = new Map<Element, Animation[]>();
4-
5-
function removeFromAnimations(element: Element, animation: Animation) {
6-
const animations = elementAnimations.get(element);
7-
8-
if (animations) {
9-
const index = animations.indexOf(animation);
10-
11-
if (index !== -1) {
12-
animations.splice(index, 1);
13-
}
14-
}
15-
}
2+
import {
3+
getAnimations,
4+
getAllAnimations,
5+
clearAnimations,
6+
} from './elementAnimations';
167

178
function animate(
189
this: Element,
@@ -26,32 +17,11 @@ function animate(
2617
animation.id = options.id;
2718
}
2819

29-
const animations = elementAnimations.get(this) ?? [];
30-
31-
animations.push(animation);
32-
33-
elementAnimations.set(this, animations);
34-
35-
animation.addEventListener('finish', () =>
36-
removeFromAnimations(this, animation)
37-
);
38-
animation.addEventListener('cancel', () =>
39-
removeFromAnimations(this, animation)
40-
);
41-
4220
animation.play();
4321

4422
return animation;
4523
}
4624

47-
function getAnimations(this: Element) {
48-
return elementAnimations.get(this) ?? [];
49-
}
50-
51-
function getAllAnimations() {
52-
return Array.from(elementAnimations.values()).flat();
53-
}
54-
5525
function mockAnimationsApi() {
5626
const savedAnimate = Element.prototype.animate;
5727
const savedGetAnimations = Element.prototype.getAnimations;
@@ -79,7 +49,7 @@ function mockAnimationsApi() {
7949
});
8050

8151
afterEach(() => {
82-
elementAnimations.clear();
52+
clearAnimations();
8353
});
8454

8555
afterAll(() => {

0 commit comments

Comments
 (0)