Skip to content

Commit fa4b1a3

Browse files
author
Daniele Loreto
committed
fix: implement tests to check that animations are really finished when keyframes are removed
1 parent e7766fb commit fa4b1a3

File tree

12 files changed

+840
-34
lines changed

12 files changed

+840
-34
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
export default {
2+
props: {
3+
things: [
4+
{ id: 1, name: 'a' },
5+
{ id: 2, name: 'b' },
6+
{ id: 3, name: 'c' },
7+
{ id: 4, name: 'd' },
8+
{ id: 5, name: 'e' }
9+
]
10+
},
11+
12+
html: `
13+
<div>a</div>
14+
<div>b</div>
15+
<div>c</div>
16+
<div>d</div>
17+
<div>e</div>
18+
`,
19+
20+
async test({ assert, component, target, waitUntil }) {
21+
let divs = target.querySelectorAll('div');
22+
divs.forEach((div) => {
23+
div.getBoundingClientRect = function () {
24+
const index = [...this.parentNode.children].indexOf(this);
25+
const top = index * 30;
26+
27+
return {
28+
left: 0,
29+
right: 100,
30+
top,
31+
bottom: top + 20
32+
};
33+
};
34+
});
35+
36+
component.things = [
37+
{ id: 5, name: 'e' },
38+
{ id: 2, name: 'b' },
39+
{ id: 3, name: 'c' },
40+
{ id: 4, name: 'd' },
41+
{ id: 1, name: 'a' }
42+
];
43+
44+
divs = target.querySelectorAll('div');
45+
assert.ok(~divs[0].style.animation.indexOf('__svelte'));
46+
assert.equal(divs[1].style.animation, '');
47+
assert.equal(divs[2].style.animation, '');
48+
assert.equal(divs[3].style.animation, '');
49+
assert.ok(~divs[4].style.animation.indexOf('__svelte'));
50+
assert.equal(divs[0].getAnimations().length, 1);
51+
assert.equal(divs[1].getAnimations().length, 0);
52+
assert.equal(divs[2].getAnimations().length, 0);
53+
assert.equal(divs[3].getAnimations().length, 0);
54+
assert.equal(divs[4].getAnimations().length, 1);
55+
56+
const animations = [divs[0].getAnimations().at(0), divs[4].getAnimations().at(0)];
57+
58+
animations.forEach((animation) => {
59+
assert.equal(animation.playState, 'running');
60+
});
61+
62+
await Promise.all(animations.map((animation) => animation.finished)).catch((e) => {
63+
if (e.name === 'AbortError') {
64+
throw new Error(
65+
'The animation was aborted, keyframes have been removed before the animation execution end.'
66+
);
67+
}
68+
throw e;
69+
});
70+
assert.ok(~divs[0].style.animation.indexOf('__svelte'));
71+
assert.equal(divs[1].style.animation, '');
72+
assert.equal(divs[2].style.animation, '');
73+
assert.equal(divs[3].style.animation, '');
74+
assert.ok(~divs[4].style.animation.indexOf('__svelte'));
75+
assert.equal(divs[0].getAnimations().length, 1);
76+
assert.equal(divs[1].getAnimations().length, 0);
77+
assert.equal(divs[2].getAnimations().length, 0);
78+
assert.equal(divs[3].getAnimations().length, 0);
79+
assert.equal(divs[4].getAnimations().length, 1);
80+
animations.forEach((animation) => {
81+
assert.equal(animation.playState, 'finished');
82+
});
83+
84+
await waitUntil(() => !divs[4].style.animation);
85+
assert.ok(~divs[0].style.animation, '');
86+
assert.equal(divs[1].style.animation, '');
87+
assert.equal(divs[2].style.animation, '');
88+
assert.equal(divs[3].style.animation, '');
89+
assert.ok(~divs[4].style.animation, '');
90+
assert.equal(divs[0].getAnimations().length, 0);
91+
assert.equal(divs[1].getAnimations().length, 0);
92+
assert.equal(divs[2].getAnimations().length, 0);
93+
assert.equal(divs[3].getAnimations().length, 0);
94+
assert.equal(divs[4].getAnimations().length, 0);
95+
animations.forEach((animation) => {
96+
assert.equal(animation.playState, 'idle');
97+
});
98+
}
99+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
export let things;
3+
4+
function flip(_node, animation, _params) {
5+
const dx = animation.from.left - animation.to.left;
6+
const dy = animation.from.top - animation.to.top;
7+
8+
return {
9+
duration: 100,
10+
css: (_t, u) => `transform: translate(${u + dx}px, ${u * dy}px)`
11+
};
12+
}
13+
</script>
14+
15+
{#each things as thing (thing.id)}
16+
<div animate:flip>{thing.name}</div>
17+
{/each}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
export default {
2+
props: {
3+
visible: true
4+
},
5+
intro: true,
6+
async test({ assert, component, window, waitUntil, target }) {
7+
/** @type {HTMLElement} */
8+
const elAnimatedOnMount = target.querySelector('#animated-on-mount');
9+
/** @type {HTMLElement} */
10+
const elAnimatedOnInit = target.querySelector('#animated-on-init');
11+
12+
assert.equal(elAnimatedOnInit.getAnimations().length, 1);
13+
assert.equal(elAnimatedOnMount.getAnimations().length, 1);
14+
const elAnimatedOnInitAnimation = elAnimatedOnInit.getAnimations().at(0);
15+
const elAnimatedOnMountAnimation = elAnimatedOnMount.getAnimations().at(0);
16+
17+
assert.equal(elAnimatedOnInitAnimation.playState, 'running');
18+
assert.equal(elAnimatedOnMountAnimation.playState, 'running');
19+
assert.equal(!!elAnimatedOnInit.style.animation, true);
20+
assert.equal(!!elAnimatedOnMount.style.animation, true);
21+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 2);
22+
23+
await elAnimatedOnInitAnimation.finished.catch((e) => {
24+
if (e.name === 'AbortError') {
25+
throw new Error(
26+
'The animation was aborted, keyframes have been removed before the animation execution end.'
27+
);
28+
}
29+
throw e;
30+
});
31+
assert.equal(elAnimatedOnInitAnimation.playState, 'finished');
32+
assert.equal(elAnimatedOnMountAnimation.playState, 'running');
33+
assert.equal(!!elAnimatedOnInit.style.animation, true);
34+
assert.equal(!!elAnimatedOnMount.style.animation, true);
35+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 2);
36+
assert.equal(elAnimatedOnInit.getAnimations().length, 1);
37+
assert.equal(elAnimatedOnMount.getAnimations().length, 1);
38+
39+
await waitUntil(() => elAnimatedOnInit.getAnimations().length === 0);
40+
assert.equal(elAnimatedOnInitAnimation.playState, 'idle');
41+
assert.equal(elAnimatedOnMountAnimation.playState, 'running');
42+
assert.equal(!!elAnimatedOnInit.style.animation, false);
43+
assert.equal(!!elAnimatedOnMount.style.animation, true);
44+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 2);
45+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
46+
assert.equal(elAnimatedOnMount.getAnimations().length, 1);
47+
48+
await elAnimatedOnMountAnimation.finished.catch((e) => {
49+
if (e.name === 'AbortError') {
50+
throw new Error(
51+
'The animation was aborted, keyframes have been removed before the animation execution end.'
52+
);
53+
}
54+
throw e;
55+
});
56+
assert.equal(elAnimatedOnInitAnimation.playState, 'idle');
57+
assert.equal(elAnimatedOnMountAnimation.playState, 'finished');
58+
assert.equal(!!elAnimatedOnInit.style.animation, false);
59+
assert.equal(!!elAnimatedOnMount.style.animation, true);
60+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 2);
61+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
62+
assert.equal(elAnimatedOnMount.getAnimations().length, 1);
63+
64+
await waitUntil(() => window.document.head.querySelector('style') === null);
65+
assert.equal(elAnimatedOnInitAnimation.playState, 'idle');
66+
assert.equal(elAnimatedOnMountAnimation.playState, 'idle');
67+
assert.equal(!!elAnimatedOnInit.style.animation, false);
68+
assert.equal(!!elAnimatedOnMount.style.animation, false);
69+
assert.equal(window.document.head.querySelector('style'), null);
70+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
71+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
72+
73+
/** @type {NodeListOf<HTMLElement>} */
74+
let divs = target.querySelectorAll('.fading-div');
75+
/** @type {Animation[]} */
76+
let animations;
77+
assert.equal(divs.length, 1);
78+
divs.forEach((div) => assert.equal(div.getAnimations().length, 0));
79+
80+
component.visible = false;
81+
divs = target.querySelectorAll('.fading-div');
82+
assert.equal(divs.length, 1);
83+
divs.forEach((div) => assert.equal(div.getAnimations().length, 1));
84+
animations = Array.from(divs).map((div) => div.getAnimations().at(0));
85+
animations.forEach((animation) => assert.equal(animation.playState, 'running'));
86+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 1);
87+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
88+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
89+
90+
await animations[0].finished.catch((e) => {
91+
if (e.name === 'AbortError') {
92+
throw new Error(
93+
'The animation was aborted, keyframes have been removed before the animation execution end.'
94+
);
95+
}
96+
throw e;
97+
});
98+
divs = target.querySelectorAll('.fading-div');
99+
assert.equal(divs.length, 1);
100+
divs.forEach((div) => assert.equal(div.getAnimations().length, 1));
101+
animations.forEach((animation) => assert.equal(animation.playState, 'finished'));
102+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 1);
103+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
104+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
105+
106+
await waitUntil(() => window.document.head.querySelector('style') === null);
107+
divs = target.querySelectorAll('.fading-div');
108+
assert.equal(divs.length, 1);
109+
divs.forEach((div) => assert.equal(div.getAnimations().length, 0));
110+
animations.forEach((animation) => assert.equal(animation.playState, 'idle'));
111+
assert.equal(window.document.head.querySelector('style'), null);
112+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
113+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
114+
115+
component.visible = true;
116+
divs = target.querySelectorAll('.fading-div');
117+
assert.equal(divs.length, 1);
118+
divs.forEach((div) => assert.equal(div.getAnimations().length, 1));
119+
animations = Array.from(divs).map((div) => div.getAnimations().at(0));
120+
animations.forEach((animation) => assert.equal(animation.playState, 'running'));
121+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 1);
122+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
123+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
124+
125+
await animations[0].finished.catch((e) => {
126+
if (e.name === 'AbortError') {
127+
throw new Error(
128+
'The animation was aborted, keyframes have been removed before the animation execution end.'
129+
);
130+
}
131+
throw e;
132+
});
133+
divs = target.querySelectorAll('.fading-div');
134+
assert.equal(divs.length, 1);
135+
divs.forEach((div) => assert.equal(div.getAnimations().length, 1));
136+
animations.forEach((animation) => assert.equal(animation.playState, 'finished'));
137+
assert.equal(window.document.head.querySelector('style')?.sheet.rules.length, 1);
138+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
139+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
140+
141+
await waitUntil(() => window.document.head.querySelector('style') === null);
142+
divs = target.querySelectorAll('.fading-div');
143+
assert.equal(divs.length, 1);
144+
divs.forEach((div) => assert.equal(div.getAnimations().length, 0));
145+
animations.forEach((animation) => assert.equal(animation.playState, 'idle'));
146+
assert.equal(window.document.head.querySelector('style'), null);
147+
assert.equal(elAnimatedOnInit.getAnimations().length, 0);
148+
assert.equal(elAnimatedOnMount.getAnimations().length, 0);
149+
}
150+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
4+
export let visible;
5+
6+
let mounted = false;
7+
onMount(() => {
8+
mounted = true;
9+
});
10+
11+
function foo(_) {
12+
return {
13+
duration: 50,
14+
css: (t) => {
15+
return `opacity: ${t}`;
16+
}
17+
};
18+
}
19+
20+
function bar(_) {
21+
return {
22+
duration: 10,
23+
css: (t) => {
24+
return `translate: 0 ${0 - t * 10}px`;
25+
}
26+
};
27+
}
28+
</script>
29+
30+
{#if visible}
31+
<div class="fading-div" in:foo />
32+
{/if}
33+
34+
{#if !visible}
35+
<div class="fading-div" in:foo />
36+
{/if}
37+
38+
<div id="animated-on-init" in:bar|global />
39+
40+
{#if mounted}
41+
<div id="animated-on-mount" in:foo />
42+
{/if}

0 commit comments

Comments
 (0)