@editframe/elements 0.18.26-beta.0 → 0.19.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +10 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +13 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +1 -5
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +10 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +16 -12
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -4
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +3 -2
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +16 -12
- package/dist/elements/EFMedia.d.ts +2 -3
- package/dist/elements/EFMedia.js +0 -4
- package/dist/elements/EFTemporal.d.ts +9 -6
- package/dist/elements/EFTemporal.js +15 -12
- package/dist/elements/EFTimegroup.browsertest.d.ts +26 -0
- package/dist/elements/EFTimegroup.d.ts +12 -9
- package/dist/elements/EFTimegroup.js +114 -65
- package/dist/elements/EFVideo.d.ts +5 -1
- package/dist/elements/EFVideo.js +16 -8
- package/dist/elements/EFWaveform.js +2 -3
- package/dist/elements/FetchContext.browsertest.d.ts +0 -0
- package/dist/elements/FetchMixin.js +14 -9
- package/dist/elements/TimegroupController.js +2 -1
- package/dist/elements/updateAnimations.browsertest.d.ts +0 -0
- package/dist/elements/updateAnimations.d.ts +19 -9
- package/dist/elements/updateAnimations.js +64 -25
- package/dist/gui/ContextMixin.js +34 -27
- package/dist/gui/EFConfiguration.d.ts +1 -1
- package/dist/gui/EFConfiguration.js +1 -0
- package/dist/gui/EFFilmstrip.d.ts +1 -0
- package/dist/gui/EFFilmstrip.js +12 -14
- package/dist/gui/TWMixin.js +1 -1
- package/dist/style.css +1 -1
- package/dist/transcoding/cache/URLTokenDeduplicator.d.ts +38 -0
- package/dist/transcoding/cache/URLTokenDeduplicator.js +66 -0
- package/dist/transcoding/cache/URLTokenDeduplicator.test.d.ts +1 -0
- package/dist/transcoding/types/index.d.ts +10 -0
- package/package.json +2 -2
- package/src/elements/EFMedia/AssetMediaEngine.ts +16 -2
- package/src/elements/EFMedia/BaseMediaEngine.ts +0 -6
- package/src/elements/EFMedia/JitMediaEngine.ts +14 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -1
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +11 -4
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -4
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +4 -1
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +2 -2
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +7 -3
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +11 -4
- package/src/elements/EFMedia.browsertest.ts +13 -4
- package/src/elements/EFMedia.ts +6 -10
- package/src/elements/EFTemporal.ts +21 -26
- package/src/elements/EFTimegroup.browsertest.ts +186 -2
- package/src/elements/EFTimegroup.ts +190 -94
- package/src/elements/EFVideo.browsertest.ts +53 -132
- package/src/elements/EFVideo.ts +26 -13
- package/src/elements/EFWaveform.ts +2 -3
- package/src/elements/FetchContext.browsertest.ts +396 -0
- package/src/elements/FetchMixin.ts +25 -8
- package/src/elements/TimegroupController.ts +2 -1
- package/src/elements/updateAnimations.browsertest.ts +559 -0
- package/src/elements/updateAnimations.ts +113 -50
- package/src/gui/ContextMixin.browsertest.ts +4 -9
- package/src/gui/ContextMixin.ts +52 -33
- package/src/gui/EFConfiguration.ts +1 -1
- package/src/gui/EFFilmstrip.ts +15 -18
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +182 -0
- package/src/transcoding/cache/URLTokenDeduplicator.ts +101 -0
- package/src/transcoding/types/index.ts +11 -0
- package/test/EFVideo.framegen.browsertest.ts +1 -1
- package/test/setup.ts +2 -0
- package/types.json +1 -1
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import { assert, beforeEach, describe, test } from "vitest";
|
|
2
|
+
import type { EFTimegroup } from "./EFTimegroup.js";
|
|
3
|
+
import {
|
|
4
|
+
type AnimatableElement,
|
|
5
|
+
updateAnimations,
|
|
6
|
+
} from "./updateAnimations.js";
|
|
7
|
+
|
|
8
|
+
import "./EFTimegroup.js";
|
|
9
|
+
|
|
10
|
+
// Import the constant for tests that need to check animation precision
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Clean up DOM
|
|
14
|
+
while (document.body.children.length) {
|
|
15
|
+
document.body.children[0]?.remove();
|
|
16
|
+
}
|
|
17
|
+
// Clean up localStorage
|
|
18
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
19
|
+
const key = localStorage.key(i);
|
|
20
|
+
if (typeof key !== "string") continue;
|
|
21
|
+
localStorage.removeItem(key);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function createTestElement(
|
|
26
|
+
props: Partial<AnimatableElement> = {},
|
|
27
|
+
): AnimatableElement {
|
|
28
|
+
const element = document.createElement("div") as unknown as AnimatableElement;
|
|
29
|
+
// Override readonly properties for testing
|
|
30
|
+
Object.defineProperty(element, "currentTimeMs", {
|
|
31
|
+
value: props.currentTimeMs ?? 0,
|
|
32
|
+
writable: true,
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(element, "durationMs", {
|
|
35
|
+
value: props.durationMs ?? 1000,
|
|
36
|
+
writable: true,
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(element, "startTimeMs", {
|
|
39
|
+
value: props.startTimeMs ?? 0,
|
|
40
|
+
writable: true,
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(element, "endTimeMs", {
|
|
43
|
+
value: props.endTimeMs ?? 1000,
|
|
44
|
+
writable: true,
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(element, "rootTimegroup", {
|
|
47
|
+
value: props.rootTimegroup,
|
|
48
|
+
writable: true,
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(element, "parentTimegroup", {
|
|
51
|
+
value: props.parentTimegroup,
|
|
52
|
+
writable: true,
|
|
53
|
+
});
|
|
54
|
+
document.body.appendChild(element);
|
|
55
|
+
return element;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("Timeline Element Synchronizer", () => {
|
|
59
|
+
describe("CSS custom properties", () => {
|
|
60
|
+
test("sets --ef-progress based on currentTimeMs/durationMs ratio", () => {
|
|
61
|
+
const element = createTestElement({
|
|
62
|
+
currentTimeMs: 250,
|
|
63
|
+
durationMs: 1000,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
updateAnimations(element);
|
|
67
|
+
|
|
68
|
+
assert.equal(element.style.getPropertyValue("--ef-progress"), "25%");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("clamps --ef-progress to 0-100% range", () => {
|
|
72
|
+
const element1 = createTestElement({
|
|
73
|
+
currentTimeMs: -100,
|
|
74
|
+
durationMs: 1000,
|
|
75
|
+
});
|
|
76
|
+
const element2 = createTestElement({
|
|
77
|
+
currentTimeMs: 1500,
|
|
78
|
+
durationMs: 1000,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
updateAnimations(element1);
|
|
82
|
+
updateAnimations(element2);
|
|
83
|
+
|
|
84
|
+
assert.equal(element1.style.getPropertyValue("--ef-progress"), "0%");
|
|
85
|
+
assert.equal(element2.style.getPropertyValue("--ef-progress"), "100%");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("sets --ef-duration to element durationMs", () => {
|
|
89
|
+
const element = createTestElement({
|
|
90
|
+
durationMs: 2000,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
updateAnimations(element);
|
|
94
|
+
|
|
95
|
+
assert.equal(element.style.getPropertyValue("--ef-duration"), "2000ms");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("sets --ef-transition-duration based on parentTimegroup overlapMs", () => {
|
|
99
|
+
const parentTimegroup = document.createElement(
|
|
100
|
+
"ef-timegroup",
|
|
101
|
+
) as EFTimegroup;
|
|
102
|
+
parentTimegroup.overlapMs = 500;
|
|
103
|
+
|
|
104
|
+
const element = createTestElement({
|
|
105
|
+
parentTimegroup,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
updateAnimations(element);
|
|
109
|
+
|
|
110
|
+
assert.equal(
|
|
111
|
+
element.style.getPropertyValue("--ef-transition-duration"),
|
|
112
|
+
"500ms",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("sets --ef-transition-duration to 0ms when no parentTimegroup", () => {
|
|
117
|
+
const element = createTestElement();
|
|
118
|
+
|
|
119
|
+
updateAnimations(element);
|
|
120
|
+
|
|
121
|
+
assert.equal(
|
|
122
|
+
element.style.getPropertyValue("--ef-transition-duration"),
|
|
123
|
+
"0ms",
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("sets --ef-transition-out-start correctly", () => {
|
|
128
|
+
const parentTimegroup = document.createElement(
|
|
129
|
+
"ef-timegroup",
|
|
130
|
+
) as EFTimegroup;
|
|
131
|
+
parentTimegroup.overlapMs = 200;
|
|
132
|
+
|
|
133
|
+
const element = createTestElement({
|
|
134
|
+
durationMs: 1000,
|
|
135
|
+
parentTimegroup,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
updateAnimations(element);
|
|
139
|
+
|
|
140
|
+
assert.equal(
|
|
141
|
+
element.style.getPropertyValue("--ef-transition-out-start"),
|
|
142
|
+
"800ms",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("sets animation-related CSS properties when animations are present", () => {
|
|
147
|
+
const element = createTestElement({
|
|
148
|
+
durationMs: 2000,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Create an animation to trigger CSS property setting
|
|
152
|
+
element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 });
|
|
153
|
+
|
|
154
|
+
updateAnimations(element);
|
|
155
|
+
|
|
156
|
+
assert.equal(element.style.getPropertyValue("--ef-duration"), "2000ms");
|
|
157
|
+
assert.equal(
|
|
158
|
+
element.style.getPropertyValue("--ef-transition-duration"),
|
|
159
|
+
"0ms",
|
|
160
|
+
);
|
|
161
|
+
assert.equal(
|
|
162
|
+
element.style.getPropertyValue("--ef-transition-out-start"),
|
|
163
|
+
"2000ms",
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("element visibility", () => {
|
|
169
|
+
test("hides element when timeline is before startTimeMs", () => {
|
|
170
|
+
const rootTimegroup = document.createElement(
|
|
171
|
+
"ef-timegroup",
|
|
172
|
+
) as EFTimegroup;
|
|
173
|
+
rootTimegroup.currentTimeMs = 100;
|
|
174
|
+
|
|
175
|
+
const element = createTestElement({
|
|
176
|
+
startTimeMs: 200,
|
|
177
|
+
endTimeMs: 800,
|
|
178
|
+
rootTimegroup,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
updateAnimations(element);
|
|
182
|
+
|
|
183
|
+
assert.equal(element.style.display, "none");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("hides element when timeline is after endTimeMs", () => {
|
|
187
|
+
const rootTimegroup = document.createElement(
|
|
188
|
+
"ef-timegroup",
|
|
189
|
+
) as EFTimegroup;
|
|
190
|
+
rootTimegroup.currentTimeMs = 900;
|
|
191
|
+
|
|
192
|
+
const element = createTestElement({
|
|
193
|
+
startTimeMs: 200,
|
|
194
|
+
endTimeMs: 800,
|
|
195
|
+
rootTimegroup,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
updateAnimations(element);
|
|
199
|
+
|
|
200
|
+
assert.equal(element.style.display, "none");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("shows element when timeline is within start/end range (using element currentTimeMs)", () => {
|
|
204
|
+
const element = createTestElement({
|
|
205
|
+
currentTimeMs: 500,
|
|
206
|
+
startTimeMs: 200,
|
|
207
|
+
endTimeMs: 800,
|
|
208
|
+
});
|
|
209
|
+
// Start with element hidden
|
|
210
|
+
element.style.display = "none";
|
|
211
|
+
|
|
212
|
+
updateAnimations(element);
|
|
213
|
+
|
|
214
|
+
assert.equal(element.style.display, "");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("uses element currentTimeMs when no rootTimegroup", () => {
|
|
218
|
+
const element = createTestElement({
|
|
219
|
+
currentTimeMs: 500,
|
|
220
|
+
startTimeMs: 200,
|
|
221
|
+
endTimeMs: 800,
|
|
222
|
+
});
|
|
223
|
+
element.style.display = "none";
|
|
224
|
+
|
|
225
|
+
updateAnimations(element);
|
|
226
|
+
|
|
227
|
+
assert.equal(element.style.display, "");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("element at exact start boundary is visible (using element currentTimeMs)", () => {
|
|
231
|
+
const element = createTestElement({
|
|
232
|
+
currentTimeMs: 200,
|
|
233
|
+
startTimeMs: 200,
|
|
234
|
+
endTimeMs: 800,
|
|
235
|
+
});
|
|
236
|
+
element.style.display = "none";
|
|
237
|
+
|
|
238
|
+
updateAnimations(element);
|
|
239
|
+
|
|
240
|
+
assert.equal(element.style.display, "");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("element at exact end boundary is hidden (using element currentTimeMs)", () => {
|
|
244
|
+
const element = createTestElement({
|
|
245
|
+
currentTimeMs: 800,
|
|
246
|
+
startTimeMs: 200,
|
|
247
|
+
endTimeMs: 800,
|
|
248
|
+
});
|
|
249
|
+
element.style.display = "";
|
|
250
|
+
|
|
251
|
+
updateAnimations(element);
|
|
252
|
+
|
|
253
|
+
assert.equal(element.style.display, "none");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("element just before start boundary is hidden", () => {
|
|
257
|
+
const element = createTestElement({
|
|
258
|
+
currentTimeMs: 199,
|
|
259
|
+
startTimeMs: 200,
|
|
260
|
+
endTimeMs: 800,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
updateAnimations(element);
|
|
264
|
+
|
|
265
|
+
assert.equal(element.style.display, "none");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("element just after end boundary is hidden", () => {
|
|
269
|
+
const element = createTestElement({
|
|
270
|
+
currentTimeMs: 801,
|
|
271
|
+
startTimeMs: 200,
|
|
272
|
+
endTimeMs: 800,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
updateAnimations(element);
|
|
276
|
+
|
|
277
|
+
assert.equal(element.style.display, "none");
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("Web Animations API integration", () => {
|
|
282
|
+
test("skips animation processing when getAnimations is not available", () => {
|
|
283
|
+
const element = createTestElement();
|
|
284
|
+
// Mock missing getAnimations
|
|
285
|
+
delete (element as any).getAnimations;
|
|
286
|
+
|
|
287
|
+
// Should not throw and should still set CSS properties
|
|
288
|
+
updateAnimations(element);
|
|
289
|
+
|
|
290
|
+
assert.equal(element.style.getPropertyValue("--ef-progress"), "0%");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("pauses running animations", async () => {
|
|
294
|
+
const element = createTestElement();
|
|
295
|
+
|
|
296
|
+
// Create a test animation
|
|
297
|
+
const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
298
|
+
duration: 1000,
|
|
299
|
+
});
|
|
300
|
+
animation.play();
|
|
301
|
+
|
|
302
|
+
assert.equal(animation.playState, "running");
|
|
303
|
+
|
|
304
|
+
updateAnimations(element);
|
|
305
|
+
|
|
306
|
+
assert.equal(animation.playState, "paused");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("ignores animations without KeyframeEffect", async () => {
|
|
310
|
+
const element = createTestElement();
|
|
311
|
+
|
|
312
|
+
// Create animation with non-KeyframeEffect (this is tricky to test, but we can verify no errors)
|
|
313
|
+
const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
314
|
+
duration: 1000,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Mock the effect to not be a KeyframeEffect
|
|
318
|
+
Object.defineProperty(animation, "effect", {
|
|
319
|
+
value: {},
|
|
320
|
+
writable: false,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Should not throw
|
|
324
|
+
updateAnimations(element);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("ignores animations without target", async () => {
|
|
328
|
+
const element = createTestElement();
|
|
329
|
+
|
|
330
|
+
const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
331
|
+
duration: 1000,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Mock the effect target to be null
|
|
335
|
+
if (animation.effect instanceof KeyframeEffect) {
|
|
336
|
+
Object.defineProperty(animation.effect, "target", {
|
|
337
|
+
value: null,
|
|
338
|
+
writable: false,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Should not throw
|
|
343
|
+
updateAnimations(element);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("handles missing timeTarget gracefully", async () => {
|
|
347
|
+
const element = createTestElement();
|
|
348
|
+
|
|
349
|
+
const target = document.createElement("div");
|
|
350
|
+
element.appendChild(target);
|
|
351
|
+
|
|
352
|
+
// Should not throw when target has no temporal parent
|
|
353
|
+
updateAnimations(element);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("processes multiple animations on same element", async () => {
|
|
357
|
+
const element = createTestElement();
|
|
358
|
+
|
|
359
|
+
const animation1 = element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
360
|
+
duration: 1000,
|
|
361
|
+
});
|
|
362
|
+
const animation2 = element.animate(
|
|
363
|
+
[{ transform: "scale(1)" }, { transform: "scale(2)" }],
|
|
364
|
+
{ duration: 500 },
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
animation1.play();
|
|
368
|
+
animation2.play();
|
|
369
|
+
|
|
370
|
+
assert.equal(animation1.playState, "running");
|
|
371
|
+
assert.equal(animation2.playState, "running");
|
|
372
|
+
|
|
373
|
+
updateAnimations(element);
|
|
374
|
+
|
|
375
|
+
// Both animations should be paused
|
|
376
|
+
assert.equal(animation1.playState, "paused");
|
|
377
|
+
assert.equal(animation2.playState, "paused");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("handles animations with zero duration", async () => {
|
|
381
|
+
const element = createTestElement();
|
|
382
|
+
|
|
383
|
+
// Should not throw
|
|
384
|
+
updateAnimations(element);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("handles animations with zero iterations", async () => {
|
|
388
|
+
const element = createTestElement();
|
|
389
|
+
|
|
390
|
+
const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
391
|
+
duration: 1000,
|
|
392
|
+
iterations: 0,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
updateAnimations(element);
|
|
396
|
+
|
|
397
|
+
// With 0 iterations and no timeTarget, currentIteration (0) >= iterations (0), so should be set to duration - epsilon
|
|
398
|
+
// But since there's no timeTarget, the code path continues and doesn't set currentTime
|
|
399
|
+
// The animation will continue with its default behavior
|
|
400
|
+
assert.equal(animation.currentTime, 0);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("handles animations that are already paused", async () => {
|
|
404
|
+
const element = createTestElement();
|
|
405
|
+
|
|
406
|
+
const animation = element.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
407
|
+
duration: 1000,
|
|
408
|
+
});
|
|
409
|
+
animation.pause();
|
|
410
|
+
|
|
411
|
+
assert.equal(animation.playState, "paused");
|
|
412
|
+
|
|
413
|
+
// Should not throw and should still set currentTime
|
|
414
|
+
updateAnimations(element);
|
|
415
|
+
|
|
416
|
+
assert.equal(animation.playState, "paused");
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe("edge cases", () => {
|
|
421
|
+
test("handles zero duration gracefully", () => {
|
|
422
|
+
const element = createTestElement({
|
|
423
|
+
currentTimeMs: 100,
|
|
424
|
+
durationMs: 0,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
updateAnimations(element);
|
|
428
|
+
|
|
429
|
+
// Should handle division by zero
|
|
430
|
+
assert.equal(element.style.getPropertyValue("--ef-progress"), "100%");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("handles negative currentTimeMs", () => {
|
|
434
|
+
const element = createTestElement({
|
|
435
|
+
currentTimeMs: -100,
|
|
436
|
+
durationMs: 1000,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
updateAnimations(element);
|
|
440
|
+
|
|
441
|
+
assert.equal(element.style.getPropertyValue("--ef-progress"), "0%");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("handles missing parentTimegroup overlapMs", () => {
|
|
445
|
+
const parentTimegroup = {} as EFTimegroup; // Missing overlapMs property
|
|
446
|
+
|
|
447
|
+
const element = createTestElement({
|
|
448
|
+
parentTimegroup,
|
|
449
|
+
durationMs: 1000,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
updateAnimations(element);
|
|
453
|
+
|
|
454
|
+
assert.equal(
|
|
455
|
+
element.style.getPropertyValue("--ef-transition-duration"),
|
|
456
|
+
"0ms",
|
|
457
|
+
);
|
|
458
|
+
assert.equal(
|
|
459
|
+
element.style.getPropertyValue("--ef-transition-out-start"),
|
|
460
|
+
"1000ms",
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("handles large duration values", () => {
|
|
465
|
+
const element = createTestElement({
|
|
466
|
+
currentTimeMs: 5000,
|
|
467
|
+
durationMs: 1000000, // 1000 seconds
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
updateAnimations(element);
|
|
471
|
+
|
|
472
|
+
assert.equal(element.style.getPropertyValue("--ef-progress"), "0.5%");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("handles very small time values", () => {
|
|
476
|
+
const element = createTestElement({
|
|
477
|
+
currentTimeMs: 0.5,
|
|
478
|
+
durationMs: 10,
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
updateAnimations(element);
|
|
482
|
+
|
|
483
|
+
assert.equal(element.style.getPropertyValue("--ef-progress"), "5%");
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("does not modify display when element should remain visible", () => {
|
|
487
|
+
const element = createTestElement({
|
|
488
|
+
currentTimeMs: 500,
|
|
489
|
+
startTimeMs: 200,
|
|
490
|
+
endTimeMs: 800,
|
|
491
|
+
});
|
|
492
|
+
// Element starts visible (default state)
|
|
493
|
+
const initialDisplay = element.style.display;
|
|
494
|
+
|
|
495
|
+
updateAnimations(element);
|
|
496
|
+
|
|
497
|
+
// Should not have been modified
|
|
498
|
+
assert.equal(element.style.display, initialDisplay);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("does not modify display when element should remain hidden", () => {
|
|
502
|
+
const element = createTestElement({
|
|
503
|
+
currentTimeMs: 100,
|
|
504
|
+
startTimeMs: 200,
|
|
505
|
+
endTimeMs: 800,
|
|
506
|
+
});
|
|
507
|
+
element.style.display = "none";
|
|
508
|
+
|
|
509
|
+
updateAnimations(element);
|
|
510
|
+
|
|
511
|
+
// Should still be hidden
|
|
512
|
+
assert.equal(element.style.display, "none");
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe("CSS variables with zero overlap", () => {
|
|
517
|
+
test("correctly sets transition duration to 0ms when overlap is 0ms", () => {
|
|
518
|
+
const element = createTestElement({
|
|
519
|
+
currentTimeMs: 500,
|
|
520
|
+
startTimeMs: 0,
|
|
521
|
+
endTimeMs: 1000,
|
|
522
|
+
durationMs: 1000,
|
|
523
|
+
parentTimegroup: {
|
|
524
|
+
overlapMs: 0, // Zero overlap case
|
|
525
|
+
} as any,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
updateAnimations(element);
|
|
529
|
+
|
|
530
|
+
// When overlap is 0, transition duration should be 0ms (no overlap means no transition)
|
|
531
|
+
const transitionDuration = element.style.getPropertyValue(
|
|
532
|
+
"--ef-transition-duration",
|
|
533
|
+
);
|
|
534
|
+
assert.equal(
|
|
535
|
+
transitionDuration,
|
|
536
|
+
"0ms",
|
|
537
|
+
"Transition duration should be 0ms when no overlap",
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
// Transition out start should equal full duration (no transition period)
|
|
541
|
+
const transitionOutStart = element.style.getPropertyValue(
|
|
542
|
+
"--ef-transition-out-start",
|
|
543
|
+
);
|
|
544
|
+
assert.equal(
|
|
545
|
+
transitionOutStart,
|
|
546
|
+
"1000ms",
|
|
547
|
+
"Transition out start should equal full duration when no overlap",
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// Duration variable should still be set correctly for within-clip animations
|
|
551
|
+
const duration = element.style.getPropertyValue("--ef-duration");
|
|
552
|
+
assert.equal(
|
|
553
|
+
duration,
|
|
554
|
+
"1000ms",
|
|
555
|
+
"Duration should be set for within-clip animation calculations",
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
});
|