@editframe/elements 0.26.3-beta.0 → 0.26.4-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.
Files changed (132) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-css.js +3 -3
  3. package/tsdown.config.ts +1 -1
  4. package/src/elements/ContextProxiesController.ts +0 -124
  5. package/src/elements/CrossUpdateController.ts +0 -22
  6. package/src/elements/EFAudio.browsertest.ts +0 -706
  7. package/src/elements/EFAudio.ts +0 -56
  8. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  9. package/src/elements/EFCaptions.ts +0 -823
  10. package/src/elements/EFImage.browsertest.ts +0 -120
  11. package/src/elements/EFImage.ts +0 -113
  12. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  13. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  14. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  15. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  16. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  17. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  18. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  19. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  20. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  21. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  22. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  23. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  24. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  25. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  26. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  27. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  28. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  29. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  30. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  31. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  32. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  33. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  34. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  35. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  36. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  37. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  38. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  39. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  40. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  41. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  42. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  43. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  44. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  45. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  46. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  47. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  48. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  49. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  51. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  52. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  53. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  54. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  55. package/src/elements/EFMedia.browsertest.ts +0 -872
  56. package/src/elements/EFMedia.ts +0 -341
  57. package/src/elements/EFSourceMixin.ts +0 -60
  58. package/src/elements/EFSurface.browsertest.ts +0 -151
  59. package/src/elements/EFSurface.ts +0 -142
  60. package/src/elements/EFTemporal.browsertest.ts +0 -215
  61. package/src/elements/EFTemporal.ts +0 -800
  62. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  63. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  64. package/src/elements/EFThumbnailStrip.ts +0 -906
  65. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  66. package/src/elements/EFTimegroup.ts +0 -882
  67. package/src/elements/EFVideo.browsertest.ts +0 -1482
  68. package/src/elements/EFVideo.ts +0 -564
  69. package/src/elements/EFWaveform.ts +0 -547
  70. package/src/elements/FetchContext.browsertest.ts +0 -401
  71. package/src/elements/FetchMixin.ts +0 -38
  72. package/src/elements/SampleBuffer.ts +0 -94
  73. package/src/elements/TargetController.browsertest.ts +0 -230
  74. package/src/elements/TargetController.ts +0 -224
  75. package/src/elements/TimegroupController.ts +0 -26
  76. package/src/elements/durationConverter.ts +0 -35
  77. package/src/elements/parseTimeToMs.ts +0 -9
  78. package/src/elements/printTaskStatus.ts +0 -16
  79. package/src/elements/renderTemporalAudio.ts +0 -108
  80. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  81. package/src/elements/updateAnimations.ts +0 -217
  82. package/src/elements/util.ts +0 -24
  83. package/src/gui/ContextMixin.browsertest.ts +0 -860
  84. package/src/gui/ContextMixin.ts +0 -562
  85. package/src/gui/Controllable.browsertest.ts +0 -258
  86. package/src/gui/Controllable.ts +0 -41
  87. package/src/gui/EFConfiguration.ts +0 -40
  88. package/src/gui/EFControls.browsertest.ts +0 -389
  89. package/src/gui/EFControls.ts +0 -195
  90. package/src/gui/EFDial.browsertest.ts +0 -84
  91. package/src/gui/EFDial.ts +0 -172
  92. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  93. package/src/gui/EFFilmstrip.ts +0 -1349
  94. package/src/gui/EFFitScale.ts +0 -152
  95. package/src/gui/EFFocusOverlay.ts +0 -79
  96. package/src/gui/EFPause.browsertest.ts +0 -202
  97. package/src/gui/EFPause.ts +0 -73
  98. package/src/gui/EFPlay.browsertest.ts +0 -202
  99. package/src/gui/EFPlay.ts +0 -73
  100. package/src/gui/EFPreview.ts +0 -74
  101. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  102. package/src/gui/EFResizableBox.ts +0 -898
  103. package/src/gui/EFScrubber.ts +0 -151
  104. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  105. package/src/gui/EFTimeDisplay.ts +0 -55
  106. package/src/gui/EFToggleLoop.ts +0 -35
  107. package/src/gui/EFTogglePlay.ts +0 -70
  108. package/src/gui/EFWorkbench.ts +0 -115
  109. package/src/gui/PlaybackController.ts +0 -527
  110. package/src/gui/TWMixin.css +0 -6
  111. package/src/gui/TWMixin.ts +0 -61
  112. package/src/gui/TargetOrContextMixin.ts +0 -185
  113. package/src/gui/currentTimeContext.ts +0 -5
  114. package/src/gui/durationContext.ts +0 -3
  115. package/src/gui/efContext.ts +0 -6
  116. package/src/gui/fetchContext.ts +0 -5
  117. package/src/gui/focusContext.ts +0 -7
  118. package/src/gui/focusedElementContext.ts +0 -5
  119. package/src/gui/playingContext.ts +0 -5
  120. package/src/otel/BridgeSpanExporter.ts +0 -150
  121. package/src/otel/setupBrowserTracing.ts +0 -73
  122. package/src/otel/tracingHelpers.ts +0 -251
  123. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  124. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  125. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  126. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  127. package/src/transcoding/types/index.ts +0 -312
  128. package/src/transcoding/utils/MediaUtils.ts +0 -63
  129. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  130. package/src/transcoding/utils/constants.ts +0 -36
  131. package/src/utils/LRUCache.test.ts +0 -274
  132. package/src/utils/LRUCache.ts +0 -696
@@ -1,1960 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "vitest";
2
- import "../gui/EFPreview.js";
3
- import "./EFCaptions.js";
4
- import "./EFTimegroup.js";
5
- import "./EFVideo.js";
6
- import { v4 } from "uuid";
7
-
8
- beforeEach(() => {
9
- window.localStorage.clear();
10
- });
11
-
12
- describe("EFCaptions", () => {
13
- describe("when rendering", () => {
14
- beforeEach(() => {
15
- // @ts-expect-error
16
- window.FRAMEGEN_BRIDGE = true;
17
- });
18
- afterEach(() => {
19
- delete window.FRAMEGEN_BRIDGE;
20
- });
21
- test("captionsPath uses http:// protocol", () => {
22
- const id = v4();
23
- const workbench = document.createElement("ef-workbench");
24
-
25
- const target = document.createElement("ef-video");
26
- target.setAttribute("id", id);
27
- target.assetId = "550e8400-e29b-41d4-a716-446655440000";
28
- document.body.appendChild(target);
29
- const captions = document.createElement("ef-captions");
30
- captions.setAttribute("target", id);
31
- document.body.appendChild(captions);
32
- workbench.appendChild(captions);
33
- expect(captions.captionsPath()).toBe(
34
- "https://editframe.dev/api/v1/caption_files/550e8400-e29b-41d4-a716-446655440000",
35
- );
36
- });
37
- });
38
-
39
- describe("attribute: asset-id", () => {
40
- test("determines assetPath", () => {
41
- const id = v4();
42
- const target = document.createElement("ef-video");
43
- target.setAttribute("id", id);
44
- target.assetId = id;
45
- document.body.appendChild(target);
46
- const captions = document.createElement("ef-captions");
47
- captions.setAttribute("target", id);
48
- document.body.appendChild(captions);
49
- expect(captions.captionsPath()).toBe(
50
- `https://editframe.dev/api/v1/caption_files/${id}`,
51
- );
52
- });
53
-
54
- test("Honors provided apiHost", () => {
55
- const preview = document.createElement("ef-preview");
56
-
57
- const id = v4();
58
- const target = document.createElement("ef-video");
59
- target.setAttribute("id", id);
60
- target.assetId = id;
61
- document.body.appendChild(target);
62
- const captions = document.createElement("ef-captions");
63
- captions.setAttribute("target", id);
64
- preview.appendChild(captions);
65
- document.body.appendChild(preview);
66
- preview.apiHost = "test://";
67
- expect(captions.captionsPath()).toBe(
68
- `test:///api/v1/caption_files/${id}`,
69
- );
70
- });
71
- });
72
-
73
- describe("custom captions data loading", () => {
74
- test("loads captions from external JSON file (captions-src)", async () => {
75
- const id = v4();
76
- const target = document.createElement("ef-video");
77
- target.setAttribute("id", id);
78
- target.src = "bars-n-tone.mp4";
79
- document.body.appendChild(target);
80
-
81
- const captions = document.createElement("ef-captions");
82
- captions.setAttribute("target", id);
83
- captions.captionsSrc = "test-captions-simple.json";
84
- document.body.appendChild(captions);
85
-
86
- await captions.frameTask.taskComplete;
87
- // @ts-expect-error accessing private property for testing
88
- const captionsTask = captions.customCaptionsDataTask;
89
-
90
- await captionsTask.taskComplete;
91
-
92
- expect(captionsTask.value).toBeTruthy();
93
- expect(captionsTask.value?.segments).toHaveLength(3);
94
- expect(captionsTask.value?.word_segments).toHaveLength(9);
95
- expect(captionsTask.value?.segments[0]?.text).toBe("First test segment");
96
- });
97
-
98
- test("loads captions from script element (captions-script)", async () => {
99
- const id = v4();
100
- const scriptId = v4();
101
-
102
- const target = document.createElement("ef-video");
103
- target.setAttribute("id", id);
104
- target.src = "bars-n-tone.mp4";
105
- document.body.appendChild(target);
106
-
107
- // Create script element with captions data
108
- const script = document.createElement("script");
109
- script.id = scriptId;
110
- script.type = "application/json";
111
- script.textContent = JSON.stringify({
112
- segments: [{ start: 0, end: 2, text: "Script-based captions" }],
113
- word_segments: [
114
- { text: "Script-based", start: 0, end: 1 },
115
- { text: " captions", start: 1, end: 2 },
116
- ],
117
- });
118
- document.body.appendChild(script);
119
-
120
- const captions = document.createElement("ef-captions");
121
- captions.setAttribute("target", id);
122
- captions.captionsScript = scriptId;
123
- document.body.appendChild(captions);
124
-
125
- await captions.frameTask.taskComplete;
126
-
127
- // @ts-expect-error accessing private property for testing
128
- const captionsTask = captions.customCaptionsDataTask;
129
- await captionsTask.taskComplete;
130
-
131
- expect(captionsTask.value).toEqual({
132
- segments: [{ start: 0, end: 2, text: "Script-based captions" }],
133
- word_segments: [
134
- { text: "Script-based", start: 0, end: 1 },
135
- { text: " captions", start: 1, end: 2 },
136
- ],
137
- });
138
- });
139
-
140
- test("uses direct captionsData property", async () => {
141
- const id = v4();
142
- const target = document.createElement("ef-video");
143
- target.setAttribute("id", id);
144
- target.src = "bars-n-tone.mp4";
145
- document.body.appendChild(target);
146
-
147
- const captions = document.createElement("ef-captions");
148
- captions.setAttribute("target", id);
149
-
150
- const testData = {
151
- segments: [{ start: 0, end: 2, text: "Direct property caption" }],
152
- word_segments: [
153
- { text: "Direct", start: 0, end: 0.6 },
154
- { text: " property", start: 0.6, end: 1.3 },
155
- { text: " caption", start: 1.3, end: 2 },
156
- ],
157
- };
158
-
159
- captions.captionsData = testData;
160
- document.body.appendChild(captions);
161
-
162
- await captions.frameTask.taskComplete;
163
- await captions.unifiedCaptionsDataTask.taskComplete;
164
-
165
- expect(captions.unifiedCaptionsDataTask.value).toEqual(testData);
166
- });
167
-
168
- test("prioritizes captionsData > captions-script > captions-src", async () => {
169
- const id = v4();
170
- const scriptId = v4();
171
-
172
- const target = document.createElement("ef-video");
173
- target.setAttribute("id", id);
174
- target.src = "bars-n-tone.mp4";
175
- document.body.appendChild(target);
176
-
177
- // Create script element
178
- const script = document.createElement("script");
179
- script.id = scriptId;
180
- script.type = "application/json";
181
- script.textContent = JSON.stringify({
182
- segments: [{ start: 0, end: 2, text: "Script caption" }],
183
- word_segments: [
184
- { text: "Script", start: 0, end: 1 },
185
- { text: " caption", start: 1, end: 2 },
186
- ],
187
- });
188
- document.body.appendChild(script);
189
-
190
- const captions = document.createElement("ef-captions");
191
- captions.setAttribute("target", id);
192
- captions.captionsSrc = "test-captions-simple.json";
193
- captions.captionsScript = scriptId;
194
-
195
- const directData = {
196
- segments: [{ start: 0, end: 2, text: "Direct property wins" }],
197
- word_segments: [
198
- { text: "Direct", start: 0, end: 1 },
199
- { text: " property", start: 1, end: 1.5 },
200
- { text: " wins", start: 1.5, end: 2 },
201
- ],
202
- };
203
-
204
- captions.captionsData = directData;
205
- document.body.appendChild(captions);
206
-
207
- await captions.frameTask.taskComplete;
208
- await captions.unifiedCaptionsDataTask.taskComplete;
209
-
210
- // Should use direct property data, not script or file
211
- expect(captions.unifiedCaptionsDataTask.value).toEqual(directData);
212
- expect(captions.unifiedCaptionsDataTask.value?.segments[0]?.text).toBe(
213
- "Direct property wins",
214
- );
215
- });
216
-
217
- test("handles fetch errors gracefully", async () => {
218
- const id = v4();
219
- const target = document.createElement("ef-video");
220
- target.setAttribute("id", id);
221
- target.src = "bars-n-tone.mp4";
222
- document.body.appendChild(target);
223
-
224
- const captions = document.createElement("ef-captions");
225
- captions.setAttribute("target", id);
226
- captions.captionsSrc = "nonexistent-file.json";
227
- document.body.appendChild(captions);
228
-
229
- await captions.frameTask.taskComplete;
230
- await captions.unifiedCaptionsDataTask.taskComplete;
231
-
232
- // @ts-expect-error accessing private property for testing
233
- const captionsTask = captions.customCaptionsDataTask;
234
-
235
- expect(captionsTask.value ?? null).toBeNull();
236
- });
237
-
238
- test("handles invalid JSON in script gracefully", async () => {
239
- const id = v4();
240
- const scriptId = v4();
241
-
242
- const target = document.createElement("ef-video");
243
- target.setAttribute("id", id);
244
- target.src = "bars-n-tone.mp4";
245
- document.body.appendChild(target);
246
-
247
- const script = document.createElement("script");
248
- script.id = scriptId;
249
- script.type = "application/json";
250
- script.textContent = "invalid json {";
251
- document.body.appendChild(script);
252
-
253
- const captions = document.createElement("ef-captions");
254
- captions.setAttribute("target", id);
255
- captions.captionsScript = scriptId;
256
- document.body.appendChild(captions);
257
-
258
- await captions.frameTask.taskComplete;
259
-
260
- // @ts-expect-error accessing private property for testing
261
- const captionsTask = captions.customCaptionsDataTask;
262
- await captionsTask.taskComplete;
263
-
264
- expect(captionsTask.value).toBeNull();
265
- });
266
- });
267
-
268
- describe("text visibility and timing", () => {
269
- test("displays correct segment text at different time points", async () => {
270
- const id = v4();
271
- const timegroup = document.createElement("ef-timegroup");
272
- const target = document.createElement("ef-video");
273
- target.setAttribute("id", id);
274
- target.src = "bars-n-tone.mp4";
275
- timegroup.appendChild(target);
276
-
277
- const captions = document.createElement("ef-captions");
278
- captions.setAttribute("target", id);
279
- captions.captionsSrc = "test-captions-simple.json";
280
-
281
- // Create segment container
282
- const segmentContainer = document.createElement("ef-captions-segment");
283
- captions.appendChild(segmentContainer);
284
- timegroup.appendChild(captions);
285
- document.body.appendChild(timegroup);
286
-
287
- await captions.unifiedCaptionsDataTask.taskComplete;
288
-
289
- // Test at t=0 (first segment)
290
- timegroup.currentTimeMs = 0;
291
- await timegroup.seekTask.taskComplete;
292
- await captions.frameTask.taskComplete;
293
- await segmentContainer.updateComplete;
294
- expect(segmentContainer.segmentText).toBe("First test segment");
295
- expect(segmentContainer.segmentStartMs).toBe(0);
296
- expect(segmentContainer.segmentEndMs).toBe(3000);
297
-
298
- // Test at t=4000ms (second segment)
299
- timegroup.currentTimeMs = 4000;
300
- await timegroup.seekTask.taskComplete;
301
- await captions.frameTask.taskComplete;
302
- await segmentContainer.updateComplete;
303
- expect(segmentContainer.segmentText).toBe("Second test segment");
304
- expect(segmentContainer.segmentStartMs).toBe(3000);
305
- expect(segmentContainer.segmentEndMs).toBe(6000);
306
-
307
- // Test at t=7500ms (third segment)
308
- timegroup.currentTimeMs = 7500;
309
- await timegroup.seekTask.taskComplete;
310
- await captions.frameTask.taskComplete;
311
- await segmentContainer.updateComplete;
312
-
313
- expect(segmentContainer.segmentText).toBe("Third test segment");
314
- expect(segmentContainer.segmentStartMs).toBe(6000);
315
- expect(segmentContainer.segmentEndMs).toBe(9000);
316
- });
317
-
318
- test("displays correct word text and timing", async () => {
319
- const id = v4();
320
- const timegroup = document.createElement("ef-timegroup");
321
- const target = document.createElement("ef-video");
322
- target.setAttribute("id", id);
323
- target.src = "bars-n-tone.mp4";
324
- timegroup.appendChild(target);
325
-
326
- const captions = document.createElement("ef-captions");
327
- captions.setAttribute("target", id);
328
- captions.captionsSrc = "test-captions-simple.json";
329
-
330
- const wordContainer = document.createElement("ef-captions-active-word");
331
- captions.appendChild(wordContainer);
332
- timegroup.appendChild(captions);
333
- document.body.appendChild(timegroup);
334
-
335
- // @ts-expect-error accessing private property for testing
336
- const captionsTask = captions.customCaptionsDataTask;
337
- await captionsTask.taskComplete;
338
-
339
- // Test at t=0.3s (should be "First")
340
- timegroup.currentTimeMs = 300;
341
- await timegroup.seekTask.taskComplete;
342
- await captions.frameTask.taskComplete;
343
- await wordContainer.updateComplete;
344
- expect(wordContainer.wordText).toBe("First");
345
- expect(wordContainer.wordStartMs).toBe(0);
346
- expect(wordContainer.wordEndMs).toBe(600);
347
-
348
- // Test at t=0.9s (should be " test")
349
- timegroup.currentTimeMs = 900;
350
- await timegroup.seekTask.taskComplete;
351
- await captions.frameTask.taskComplete;
352
- await wordContainer.updateComplete;
353
- expect(wordContainer.wordText).toBe(" test");
354
- expect(wordContainer.wordStartMs).toBe(600);
355
- expect(wordContainer.wordEndMs).toBe(1200);
356
-
357
- // Test at t=1.8s (should be " segment")
358
- timegroup.currentTimeMs = 1800;
359
- await timegroup.seekTask.taskComplete;
360
- await captions.frameTask.taskComplete;
361
- await wordContainer.updateComplete;
362
- expect(wordContainer.wordText).toBe(" segment");
363
- expect(wordContainer.wordStartMs).toBe(1200);
364
- expect(wordContainer.wordEndMs).toBe(3000);
365
- });
366
-
367
- test("displays context words correctly", async () => {
368
- const id = v4();
369
- const timegroup = document.createElement("ef-timegroup");
370
- const target = document.createElement("ef-video");
371
- target.setAttribute("id", id);
372
- target.src = "bars-n-tone.mp4";
373
- timegroup.appendChild(target);
374
-
375
- const captions = document.createElement("ef-captions");
376
- captions.setAttribute("target", id);
377
- captions.captionsSrc = "test-captions-complex.json";
378
-
379
- const beforeContainer = document.createElement(
380
- "ef-captions-before-active-word",
381
- );
382
- const activeContainer = document.createElement("ef-captions-active-word");
383
- const afterContainer = document.createElement(
384
- "ef-captions-after-active-word",
385
- );
386
-
387
- captions.appendChild(beforeContainer);
388
- captions.appendChild(activeContainer);
389
- captions.appendChild(afterContainer);
390
- timegroup.appendChild(captions);
391
- document.body.appendChild(timegroup);
392
- await timegroup.waitForMediaDurations();
393
-
394
- // @ts-expect-error accessing private property for testing
395
- const captionsTask = captions.customCaptionsDataTask;
396
- await captionsTask.taskComplete;
397
-
398
- // Test at t=1.0s (active word: "longer", context should be available)
399
- timegroup.currentTimeMs = 1000;
400
- await timegroup.seekTask.taskComplete;
401
- await captions.frameTask.taskComplete;
402
- await activeContainer.updateComplete;
403
-
404
- expect(activeContainer.wordText).toBe(" longer");
405
- expect(beforeContainer.segmentText).toBe("This is a");
406
- expect(afterContainer.segmentText).toBe(
407
- "segment with multiple words for testing context",
408
- );
409
-
410
- // Verify timing properties - all context containers sync with active word
411
- expect(beforeContainer.segmentStartMs).toBe(800); // active word start
412
- expect(beforeContainer.segmentEndMs).toBe(1300); // active word end
413
- expect(afterContainer.segmentStartMs).toBe(800); // active word start
414
- expect(afterContainer.segmentEndMs).toBe(1300); // active word end
415
- });
416
-
417
- test("handles stop words correctly", async () => {
418
- const id = v4();
419
- const timegroup = document.createElement("ef-timegroup");
420
- const target = document.createElement("ef-video");
421
- target.setAttribute("id", id);
422
- target.src = "bars-n-tone.mp4";
423
- timegroup.appendChild(target);
424
-
425
- const captions = document.createElement("ef-captions");
426
- captions.setAttribute("target", id);
427
-
428
- // Add data with stop words
429
- captions.captionsData = {
430
- segments: [{ start: 0, end: 2, text: "Hello, world!" }],
431
- word_segments: [
432
- { text: "Hello", start: 0, end: 0.5 },
433
- { text: ",", start: 0.5, end: 0.6 },
434
- { text: " world", start: 0.6, end: 1.2 },
435
- { text: "!", start: 1.2, end: 1.5 },
436
- ],
437
- };
438
-
439
- const wordContainer = document.createElement("ef-captions-active-word");
440
- captions.appendChild(wordContainer);
441
- timegroup.appendChild(captions);
442
- document.body.appendChild(timegroup);
443
-
444
- // @ts-expect-error accessing private property for testing
445
- const captionsTask = captions.customCaptionsDataTask;
446
- await captionsTask.taskComplete;
447
-
448
- // Test punctuation is hidden
449
- timegroup.currentTimeMs = 550; // comma time
450
- await timegroup.seekTask.taskComplete;
451
- await wordContainer.updateComplete;
452
- expect(wordContainer.hidden).toBe(true);
453
-
454
- timegroup.currentTimeMs = 1350; // exclamation time
455
- await timegroup.seekTask.taskComplete;
456
- await wordContainer.updateComplete;
457
- expect(wordContainer.hidden).toBe(true);
458
-
459
- // Test regular word is visible
460
- timegroup.currentTimeMs = 250; // "Hello" time
461
- await timegroup.seekTask.taskComplete;
462
- await wordContainer.updateComplete;
463
- expect(wordContainer.hidden).toBe(false);
464
- expect(wordContainer.wordText).toBe("Hello");
465
- });
466
- });
467
-
468
- describe("child element types", () => {
469
- test("segment containers show segment text", async () => {
470
- const id = v4();
471
- const timegroup = document.createElement("ef-timegroup");
472
- const target = document.createElement("ef-video");
473
- target.setAttribute("id", id);
474
- target.src = "bars-n-tone.mp4";
475
- timegroup.appendChild(target);
476
-
477
- const captions = document.createElement("ef-captions");
478
- captions.setAttribute("target", id);
479
- captions.captionsSrc = "test-captions-simple.json";
480
-
481
- const segmentContainer = document.createElement("ef-captions-segment");
482
- captions.appendChild(segmentContainer);
483
- timegroup.appendChild(captions);
484
- document.body.appendChild(timegroup);
485
-
486
- // @ts-expect-error accessing private property for testing
487
- const captionsTask = captions.customCaptionsDataTask;
488
- await captionsTask.taskComplete;
489
-
490
- timegroup.currentTimeMs = 1500;
491
- await timegroup.seekTask.taskComplete;
492
- await captions.frameTask.taskComplete;
493
- expect(segmentContainer.segmentText).toBe("First test segment");
494
- });
495
-
496
- test("word containers show active word", async () => {
497
- const id = v4();
498
- const timegroup = document.createElement("ef-timegroup");
499
- const target = document.createElement("ef-video");
500
- target.setAttribute("id", id);
501
- target.src = "bars-n-tone.mp4";
502
- timegroup.appendChild(target);
503
-
504
- const captions = document.createElement("ef-captions");
505
- captions.setAttribute("target", id);
506
- captions.captionsSrc = "test-captions-simple.json";
507
-
508
- const wordContainer = document.createElement("ef-captions-active-word");
509
- captions.appendChild(wordContainer);
510
- timegroup.appendChild(captions);
511
- document.body.appendChild(timegroup);
512
-
513
- await timegroup.waitForMediaDurations();
514
-
515
- // @ts-expect-error accessing private property for testing
516
- const captionsTask = captions.customCaptionsDataTask;
517
- await captionsTask.taskComplete;
518
-
519
- timegroup.currentTimeMs = 900;
520
- await timegroup.seekTask.taskComplete;
521
- await captions.frameTask.taskComplete;
522
- expect(wordContainer.wordText).toBe(" test");
523
- });
524
-
525
- test("context containers show before/active/after words", async () => {
526
- const id = v4();
527
- const timegroup = document.createElement("ef-timegroup");
528
- const target = document.createElement("ef-video");
529
- target.setAttribute("id", id);
530
- target.src = "bars-n-tone.mp4";
531
- timegroup.appendChild(target);
532
-
533
- const captions = document.createElement("ef-captions");
534
- captions.setAttribute("target", id);
535
- captions.captionsSrc = "test-captions-complex.json";
536
-
537
- const beforeContainer = document.createElement(
538
- "ef-captions-before-active-word",
539
- );
540
- const activeContainer = document.createElement("ef-captions-active-word");
541
- const afterContainer = document.createElement(
542
- "ef-captions-after-active-word",
543
- );
544
-
545
- captions.appendChild(beforeContainer);
546
- captions.appendChild(activeContainer);
547
- captions.appendChild(afterContainer);
548
- timegroup.appendChild(captions);
549
- document.body.appendChild(timegroup);
550
- await timegroup.waitForMediaDurations();
551
-
552
- // @ts-expect-error accessing private property for testing
553
- const captionsTask = captions.customCaptionsDataTask;
554
- await captionsTask.taskComplete;
555
-
556
- // Test middle of first segment
557
- timegroup.currentTimeMs = 2400; // during "multiple"
558
- await timegroup.seekTask.taskComplete;
559
- await captions.frameTask.taskComplete;
560
-
561
- expect(activeContainer.wordText).toBe(" multiple");
562
- expect(beforeContainer.segmentText).toBeTruthy();
563
- expect(afterContainer.segmentText).toBeTruthy();
564
-
565
- // Verify all three components have content
566
- expect(beforeContainer.segmentText.length).toBeGreaterThan(0);
567
- expect(activeContainer.wordText.length).toBeGreaterThan(0);
568
- expect(afterContainer.segmentText.length).toBeGreaterThan(0);
569
- });
570
- });
571
-
572
- describe("animation properties validation", () => {
573
- test("word containers have correct startTimeMs and durationMs", async () => {
574
- const id = v4();
575
- const timegroup = document.createElement("ef-timegroup");
576
- const target = document.createElement("ef-video");
577
- target.setAttribute("id", id);
578
- target.src = "bars-n-tone.mp4";
579
- timegroup.appendChild(target);
580
-
581
- const captions = document.createElement("ef-captions");
582
- captions.setAttribute("target", id);
583
- captions.captionsSrc = "test-captions-simple.json";
584
-
585
- const wordContainer = document.createElement("ef-captions-active-word");
586
- captions.appendChild(wordContainer);
587
- timegroup.appendChild(captions);
588
- document.body.appendChild(timegroup);
589
-
590
- // @ts-expect-error accessing private property for testing
591
- const captionsTask = captions.customCaptionsDataTask;
592
- await captionsTask.taskComplete;
593
-
594
- // Test first word "First" (0-0.6s)
595
- timegroup.currentTimeMs = 300;
596
- await timegroup.seekTask.taskComplete;
597
- await captions.frameTask.taskComplete;
598
-
599
- expect(wordContainer.startTimeMs).toBe(0);
600
- expect(wordContainer.durationMs).toBe(600);
601
-
602
- // Test second word " test" (0.6-1.2s)
603
- timegroup.currentTimeMs = 900;
604
- await timegroup.seekTask.taskComplete;
605
- await captions.frameTask.taskComplete;
606
-
607
- expect(wordContainer.startTimeMs).toBe(600);
608
- expect(wordContainer.durationMs).toBe(600);
609
- });
610
-
611
- test("segment containers have correct startTimeMs and durationMs", async () => {
612
- const id = v4();
613
- const timegroup = document.createElement("ef-timegroup");
614
- const target = document.createElement("ef-video");
615
- target.setAttribute("id", id);
616
- target.src = "bars-n-tone.mp4";
617
- timegroup.appendChild(target);
618
-
619
- const captions = document.createElement("ef-captions");
620
- captions.setAttribute("target", id);
621
- captions.captionsSrc = "test-captions-simple.json";
622
-
623
- const segmentContainer = document.createElement("ef-captions-segment");
624
- captions.appendChild(segmentContainer);
625
- timegroup.appendChild(captions);
626
- document.body.appendChild(timegroup);
627
-
628
- // @ts-expect-error accessing private property for testing
629
- const captionsTask = captions.customCaptionsDataTask;
630
- await captionsTask.taskComplete;
631
-
632
- // Test first segment (0-3s)
633
- timegroup.currentTimeMs = 1500;
634
- await timegroup.seekTask.taskComplete;
635
- await captions.frameTask.taskComplete;
636
-
637
- expect(segmentContainer.startTimeMs).toBe(0);
638
- expect(segmentContainer.durationMs).toBe(3000);
639
-
640
- // Test second segment (3-6s)
641
- timegroup.currentTimeMs = 4500;
642
- await timegroup.seekTask.taskComplete;
643
- await captions.frameTask.taskComplete;
644
-
645
- expect(segmentContainer.startTimeMs).toBe(3000);
646
- expect(segmentContainer.durationMs).toBe(3000);
647
- });
648
-
649
- test("context containers have correct timing boundaries", async () => {
650
- const id = v4();
651
- const timegroup = document.createElement("ef-timegroup");
652
- const target = document.createElement("ef-video");
653
- target.setAttribute("id", id);
654
- target.src = "bars-n-tone.mp4";
655
- timegroup.appendChild(target);
656
-
657
- const captions = document.createElement("ef-captions");
658
- captions.setAttribute("target", id);
659
- captions.captionsSrc = "test-captions-complex.json";
660
-
661
- const beforeContainer = document.createElement(
662
- "ef-captions-before-active-word",
663
- );
664
- const activeContainer = document.createElement("ef-captions-active-word");
665
- const afterContainer = document.createElement(
666
- "ef-captions-after-active-word",
667
- );
668
-
669
- captions.appendChild(beforeContainer);
670
- captions.appendChild(activeContainer);
671
- captions.appendChild(afterContainer);
672
- timegroup.appendChild(captions);
673
- document.body.appendChild(timegroup);
674
- await timegroup.waitForMediaDurations();
675
-
676
- // @ts-expect-error accessing private property for testing
677
- const captionsTask = captions.customCaptionsDataTask;
678
- await captionsTask.taskComplete;
679
-
680
- // Test during "longer" word (0.8-1.3s) in first segment (0-4s)
681
- timegroup.currentTimeMs = 1000;
682
- await timegroup.seekTask.taskComplete;
683
- await captions.frameTask.taskComplete;
684
-
685
- // Active word timing
686
- expect(activeContainer.startTimeMs).toBe(800);
687
- expect(activeContainer.durationMs).toBe(500);
688
-
689
- // Before and after context: synchronized with active word timing
690
- expect(beforeContainer.startTimeMs).toBe(800);
691
- expect(beforeContainer.durationMs).toBe(500); // 1300 - 800
692
-
693
- // After context: same timing as active word
694
- expect(afterContainer.startTimeMs).toBe(800);
695
- expect(afterContainer.durationMs).toBe(500); // 1300 - 800
696
- });
697
-
698
- test("timing properties update correctly as time progresses", async () => {
699
- const id = v4();
700
- const timegroup = document.createElement("ef-timegroup");
701
- const target = document.createElement("ef-video");
702
- target.setAttribute("id", id);
703
- target.src = "bars-n-tone.mp4";
704
- timegroup.appendChild(target);
705
-
706
- const captions = document.createElement("ef-captions");
707
- captions.setAttribute("target", id);
708
- captions.captionsSrc = "test-captions-simple.json";
709
-
710
- const wordContainer = document.createElement("ef-captions-active-word");
711
- captions.appendChild(wordContainer);
712
- timegroup.appendChild(captions);
713
- document.body.appendChild(timegroup);
714
-
715
- // @ts-expect-error accessing private property for testing
716
- const captionsTask = captions.customCaptionsDataTask;
717
- await captionsTask.taskComplete;
718
-
719
- // Track timing changes across different words
720
- const timingSteps = [
721
- { time: 300, expectedStart: 0, expectedDuration: 600 }, // "First"
722
- { time: 900, expectedStart: 600, expectedDuration: 600 }, // " test"
723
- { time: 2100, expectedStart: 1200, expectedDuration: 1800 }, // " segment"
724
- { time: 3500, expectedStart: 3000, expectedDuration: 800 }, // "Second"
725
- { time: 4100, expectedStart: 3800, expectedDuration: 600 }, // " test"
726
- ];
727
-
728
- for (const step of timingSteps) {
729
- timegroup.currentTimeMs = step.time;
730
- await timegroup.seekTask.taskComplete;
731
- await captions.frameTask.taskComplete;
732
-
733
- expect(wordContainer.startTimeMs).toBe(step.expectedStart);
734
- expect(wordContainer.durationMs).toBe(step.expectedDuration);
735
- }
736
- });
737
- });
738
-
739
- describe("captions duration integration (EFMedia pattern)", () => {
740
- test("calculates intrinsicDurationMs from captions data", async () => {
741
- const captions = document.createElement("ef-captions");
742
- captions.captionsData = {
743
- segments: [
744
- { start: 0, end: 2, text: "First segment" },
745
- { start: 2, end: 5, text: "Second segment" },
746
- ],
747
- word_segments: [
748
- { text: "First", start: 0, end: 1 },
749
- { text: " segment", start: 1, end: 2 },
750
- { text: "Second", start: 2, end: 3.5 },
751
- { text: " segment", start: 3.5, end: 5 },
752
- ],
753
- };
754
-
755
- document.body.appendChild(captions);
756
- // @ts-expect-error accessing private property for testing
757
- const captionsTask = captions.customCaptionsDataTask;
758
- await captionsTask.taskComplete;
759
-
760
- // Duration should be calculated from captions data (5 seconds = 5000ms)
761
- expect(captions.intrinsicDurationMs).toBe(5000);
762
- expect(captions.durationMs).toBe(5000);
763
- expect(captions.hasOwnDuration).toBe(true);
764
- });
765
-
766
- test("handles empty captions data gracefully", async () => {
767
- const captions = document.createElement("ef-captions");
768
- captions.captionsData = {
769
- segments: [],
770
- word_segments: [],
771
- };
772
-
773
- document.body.appendChild(captions);
774
- // @ts-expect-error accessing private property for testing
775
- const captionsTask = captions.customCaptionsDataTask;
776
- await captionsTask.taskComplete;
777
-
778
- expect(captions.intrinsicDurationMs).toBe(0);
779
- expect(captions.durationMs).toBe(0);
780
- expect(captions.hasOwnDuration).toBe(true);
781
- });
782
-
783
- test("reports no own duration when no custom captions data", () => {
784
- const captions = document.createElement("ef-captions");
785
-
786
- expect(captions.hasCustomCaptionsData).toBe(false);
787
- expect(captions.hasOwnDuration).toBe(false);
788
- expect(captions.intrinsicDurationMs).toBeUndefined();
789
- });
790
-
791
- test("sequence timegroup includes captions duration", async () => {
792
- const timegroup = document.createElement("ef-timegroup");
793
- timegroup.mode = "sequence";
794
-
795
- // Add captions with 3s duration
796
- const captions = document.createElement("ef-captions");
797
- captions.captionsData = {
798
- segments: [{ start: 0, end: 3, text: "Caption test" }],
799
- word_segments: [
800
- { text: "Caption", start: 0, end: 1.5 },
801
- { text: " test", start: 1.5, end: 3 },
802
- ],
803
- };
804
- timegroup.appendChild(captions);
805
-
806
- document.body.appendChild(timegroup);
807
-
808
- // @ts-expect-error accessing private property for testing
809
- const captionsTask = captions.customCaptionsDataTask;
810
- await captionsTask.taskComplete;
811
-
812
- // Captions should have proper duration properties
813
- expect(captions.hasOwnDuration).toBe(true);
814
- expect(captions.intrinsicDurationMs).toBe(3000);
815
- expect(captions.durationMs).toBe(3000);
816
-
817
- // Sequence timegroup should include captions duration
818
- expect(timegroup.durationMs).toBe(3000);
819
- });
820
-
821
- test("contain timegroup uses max duration including captions", async () => {
822
- const timegroup = document.createElement("ef-timegroup");
823
- timegroup.mode = "contain";
824
-
825
- // Add captions with 4s duration
826
- const captions = document.createElement("ef-captions");
827
- captions.captionsData = {
828
- segments: [
829
- { start: 0, end: 2, text: "First part" },
830
- { start: 2, end: 4, text: "Second part" },
831
- ],
832
- word_segments: [
833
- { text: "First", start: 0, end: 1 },
834
- { text: " part", start: 1, end: 2 },
835
- { text: "Second", start: 2, end: 3 },
836
- { text: " part", start: 3, end: 4 },
837
- ],
838
- };
839
- timegroup.appendChild(captions);
840
-
841
- document.body.appendChild(timegroup);
842
- // @ts-expect-error accessing private property for testing
843
- const captionsTask = captions.customCaptionsDataTask;
844
- await captionsTask.taskComplete;
845
-
846
- // Captions should have proper duration properties
847
- expect(captions.hasOwnDuration).toBe(true);
848
- expect(captions.intrinsicDurationMs).toBe(4000);
849
- expect(captions.durationMs).toBe(4000);
850
-
851
- // Contain timegroup should use captions duration
852
- expect(timegroup.durationMs).toBe(4000);
853
- });
854
-
855
- test("handles exact boundary timing correctly", async () => {
856
- const timegroup = document.createElement("ef-timegroup");
857
-
858
- const captions = document.createElement("ef-captions");
859
- captions.captionsData = {
860
- segments: [{ start: 0, end: 4, text: "Boundary timing test" }],
861
- word_segments: [
862
- { text: "Boundary", start: 0, end: 1.5 },
863
- { text: " timing", start: 1.5, end: 2.6 },
864
- { text: " test", start: 2.6, end: 4 },
865
- ],
866
- };
867
-
868
- const wordContainer = document.createElement("ef-captions-active-word");
869
- captions.appendChild(wordContainer);
870
- timegroup.appendChild(captions);
871
- document.body.appendChild(timegroup);
872
-
873
- // @ts-expect-error accessing private property for testing
874
- const captionsTask = captions.customCaptionsDataTask;
875
- await captionsTask.taskComplete;
876
-
877
- // Test at frame before boundary (frame 77 at 30fps = ~2567ms) - should show " timing"
878
- timegroup.currentTimeMs = 2567;
879
- await timegroup.seekTask.taskComplete;
880
- await captions.frameTask.taskComplete;
881
- await wordContainer.updateComplete;
882
-
883
- console.log(`At 2567ms: wordText="${wordContainer.wordText}"`);
884
- expect(wordContainer.wordText).toBe(" timing");
885
-
886
- // Test at exact boundary frame (frame 78 at 30fps = 2600ms) - should show " test" (the starting word)
887
- timegroup.currentTimeMs = 2600;
888
- await timegroup.seekTask.taskComplete;
889
- await captions.frameTask.taskComplete;
890
- await wordContainer.updateComplete;
891
-
892
- console.log(`At 2600ms: wordText="${wordContainer.wordText}"`);
893
- expect(wordContainer.wordText).toBe(" test");
894
- });
895
-
896
- test("handles demo captions data boundary correctly", async () => {
897
- const timegroup = document.createElement("ef-timegroup");
898
-
899
- const captions = document.createElement("ef-captions");
900
- captions.captionsData = {
901
- segments: [
902
- { start: 0, end: 4, text: "Welcome to the custom captions demo!" },
903
- ],
904
- word_segments: [
905
- { text: " captions", start: 1.8, end: 2.6 },
906
- { text: " demo!", start: 2.6, end: 4 },
907
- ],
908
- };
909
-
910
- const wordContainer = document.createElement("ef-captions-active-word");
911
- captions.appendChild(wordContainer);
912
- timegroup.appendChild(captions);
913
- document.body.appendChild(timegroup);
914
-
915
- // @ts-expect-error accessing private property for testing
916
- const captionsTask = captions.customCaptionsDataTask;
917
- await captionsTask.taskComplete;
918
-
919
- // Test at frame before boundary (frame 77 at 30fps = ~2567ms)
920
- timegroup.currentTimeMs = 2567;
921
- await timegroup.seekTask.taskComplete;
922
- await captions.frameTask.taskComplete;
923
- await wordContainer.updateComplete;
924
-
925
- console.log(
926
- `Demo case - At 2567ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
927
- );
928
- expect(wordContainer.wordText).toBe(" captions");
929
-
930
- // Test at exact boundary 2.6s from user's example
931
- timegroup.currentTimeMs = 2600;
932
- await timegroup.seekTask.taskComplete;
933
- await captions.frameTask.taskComplete;
934
- await wordContainer.updateComplete;
935
-
936
- console.log(
937
- `Demo case - At 2600ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
938
- );
939
- expect(wordContainer.wordText).toBe(" demo!");
940
- expect(wordContainer.hidden).toBe(false);
941
- });
942
-
943
- test("standalone captions sync with timegroup (demo structure)", async () => {
944
- // Mimic exact demo structure: sequence > contain > captions
945
- const rootTimegroup = document.createElement("ef-timegroup");
946
- rootTimegroup.mode = "sequence";
947
-
948
- const containTimegroup = document.createElement("ef-timegroup");
949
- containTimegroup.mode = "contain";
950
- rootTimegroup.appendChild(containTimegroup);
951
-
952
- // Create script element like in demo
953
- const scriptId = "test-demo-script";
954
- const script = document.createElement("script");
955
- script.id = scriptId;
956
- script.type = "application/json";
957
- script.textContent = JSON.stringify({
958
- segments: [
959
- { start: 0, end: 4, text: "Welcome to the custom captions demo!" },
960
- ],
961
- word_segments: [
962
- { text: " captions", start: 1.8, end: 2.6 },
963
- { text: " demo!", start: 2.6, end: 4 },
964
- ],
965
- });
966
- document.body.appendChild(script);
967
-
968
- // Standalone captions (no target) like in updated demo
969
- const captions = document.createElement("ef-captions");
970
- captions.captionsScript = scriptId;
971
-
972
- const wordContainer = document.createElement("ef-captions-active-word");
973
- captions.appendChild(wordContainer);
974
- containTimegroup.appendChild(captions);
975
- document.body.appendChild(rootTimegroup);
976
-
977
- // @ts-expect-error accessing private property for testing
978
- const captionsTask = captions.customCaptionsDataTask;
979
- await captionsTask.taskComplete;
980
-
981
- // Debug timeline sync
982
- console.log(
983
- `Initial: rootTimegroup.currentTimeMs=${rootTimegroup.currentTimeMs}, captions.ownCurrentTimeMs=${captions.ownCurrentTimeMs}`,
984
- );
985
-
986
- // Test the problematic timing
987
- rootTimegroup.currentTimeMs = 2600;
988
- await rootTimegroup.seekTask.taskComplete;
989
- await captions.frameTask.taskComplete;
990
- await wordContainer.updateComplete;
991
-
992
- console.log(
993
- `After seek: rootTimegroup.currentTimeMs=${rootTimegroup.currentTimeMs}, captions.ownCurrentTimeMs=${captions.ownCurrentTimeMs}`,
994
- );
995
- console.log(
996
- `Standalone demo - At 2600ms: wordText="${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
997
- );
998
-
999
- expect(wordContainer.wordText).toBe(" demo!");
1000
- expect(wordContainer.hidden).toBe(false);
1001
- });
1002
-
1003
- test("context words (before/after) work correctly", async () => {
1004
- const timegroup = document.createElement("ef-timegroup");
1005
-
1006
- const captions = document.createElement("ef-captions");
1007
- captions.captionsData = {
1008
- segments: [
1009
- { start: 0, end: 4, text: "Welcome to the custom captions demo!" },
1010
- ],
1011
- word_segments: [
1012
- { text: "Welcome", start: 0, end: 0.6 },
1013
- { text: " to", start: 0.6, end: 0.9 },
1014
- { text: " the", start: 0.9, end: 1.2 },
1015
- { text: " custom", start: 1.2, end: 1.8 },
1016
- { text: " captions", start: 1.8, end: 2.6 },
1017
- { text: " demo!", start: 2.6, end: 4 },
1018
- ],
1019
- };
1020
-
1021
- const beforeContainer = document.createElement(
1022
- "ef-captions-before-active-word",
1023
- );
1024
- const activeContainer = document.createElement("ef-captions-active-word");
1025
- const afterContainer = document.createElement(
1026
- "ef-captions-after-active-word",
1027
- );
1028
-
1029
- captions.appendChild(beforeContainer);
1030
- captions.appendChild(activeContainer);
1031
- captions.appendChild(afterContainer);
1032
- timegroup.appendChild(captions);
1033
- document.body.appendChild(timegroup);
1034
-
1035
- // @ts-expect-error accessing private property for testing
1036
- const captionsTask = captions.customCaptionsDataTask;
1037
- await captionsTask.taskComplete;
1038
-
1039
- // Test during " custom" word (1.2-1.8s) - should have before/after context
1040
- timegroup.currentTimeMs = 1500;
1041
- await timegroup.seekTask.taskComplete;
1042
- await captions.frameTask.taskComplete;
1043
- await activeContainer.updateComplete;
1044
- await beforeContainer.updateComplete;
1045
- await afterContainer.updateComplete;
1046
-
1047
- console.log("Context test - At 1500ms:");
1048
- console.log(` activeWord: "${activeContainer.wordText}"`);
1049
- console.log(` beforeWords: "${beforeContainer.segmentText}"`);
1050
- console.log(` afterWords: "${afterContainer.segmentText}"`);
1051
- console.log(
1052
- ` beforeHidden: ${beforeContainer.hidden}, afterHidden: ${afterContainer.hidden}`,
1053
- );
1054
-
1055
- expect(activeContainer.wordText).toBe(" custom");
1056
- expect(beforeContainer.segmentText).toBeTruthy();
1057
- expect(afterContainer.segmentText).toBeTruthy();
1058
- expect(beforeContainer.segmentText.length).toBeGreaterThan(0);
1059
- expect(afterContainer.segmentText.length).toBeGreaterThan(0);
1060
- });
1061
-
1062
- test("debug context words with demo data structure", async () => {
1063
- const timegroup = document.createElement("ef-timegroup");
1064
-
1065
- // Create script element with EXACT demo data
1066
- const scriptId = "demo-debug-script";
1067
- const script = document.createElement("script");
1068
- script.id = scriptId;
1069
- script.type = "application/json";
1070
- script.textContent = `{
1071
- "segments": [
1072
- { "start": 0, "end": 4, "text": "Welcome to the custom captions demo!" },
1073
- { "start": 4, "end": 8, "text": "This demonstrates word-by-word highlighting." },
1074
- { "start": 8, "end": 12, "text": "You can provide your own timing data." }
1075
- ],
1076
- "word_segments": [
1077
- {"text": "Welcome", "start": 0, "end": 0.6},
1078
- {"text": " to", "start": 0.6, "end": 0.9},
1079
- {"text": " the", "start": 0.9, "end": 1.2},
1080
- {"text": " custom", "start": 1.2, "end": 1.8},
1081
- {"text": " captions", "start": 1.8, "end": 2.6},
1082
- {"text": " demo!", "start": 2.6, "end": 4},
1083
-
1084
- {"text": "This", "start": 4, "end": 4.3},
1085
- {"text": " demonstrates", "start": 4.3, "end": 5.3},
1086
- {"text": " word-by-word", "start": 5.3, "end": 6.3},
1087
- {"text": " highlighting.", "start": 6.3, "end": 8}
1088
- ]
1089
- }`;
1090
- document.body.appendChild(script);
1091
-
1092
- const captions = document.createElement("ef-captions");
1093
- captions.captionsScript = scriptId;
1094
-
1095
- const beforeContainer = document.createElement(
1096
- "ef-captions-before-active-word",
1097
- );
1098
- const activeContainer = document.createElement("ef-captions-active-word");
1099
- const afterContainer = document.createElement(
1100
- "ef-captions-after-active-word",
1101
- );
1102
-
1103
- captions.appendChild(beforeContainer);
1104
- captions.appendChild(activeContainer);
1105
- captions.appendChild(afterContainer);
1106
- timegroup.appendChild(captions);
1107
- document.body.appendChild(timegroup);
1108
-
1109
- // @ts-expect-error accessing private property for testing
1110
- const captionsTask = captions.customCaptionsDataTask;
1111
- await captionsTask.taskComplete;
1112
-
1113
- // Test during " custom" word (1.2-1.8s) - within first segment (0-4s)
1114
- timegroup.currentTimeMs = 1500;
1115
- await timegroup.seekTask.taskComplete;
1116
- await captions.frameTask.taskComplete;
1117
-
1118
- console.log("Demo debug - At 1500ms:");
1119
- console.log(
1120
- ` Current segment found: ${!!captions.segmentContainers.length}`,
1121
- );
1122
- console.log(
1123
- ` Current word found: ${activeContainer.wordText ? "yes" : "no"}`,
1124
- );
1125
- console.log(` activeWord: "${activeContainer.wordText}"`);
1126
- console.log(` beforeWords: "${beforeContainer.segmentText}"`);
1127
- console.log(` afterWords: "${afterContainer.segmentText}"`);
1128
- console.log(
1129
- ` beforeHidden: ${beforeContainer.hidden}, afterHidden: ${afterContainer.hidden}`,
1130
- );
1131
-
1132
- // Try different timing in second segment
1133
- timegroup.currentTimeMs = 5000; // During "demonstrates" in second segment
1134
- await timegroup.seekTask.taskComplete;
1135
- await captions.frameTask.taskComplete;
1136
-
1137
- console.log("Demo debug - At 5000ms (second segment):");
1138
- console.log(` activeWord: "${activeContainer.wordText}"`);
1139
- console.log(` beforeWords: "${beforeContainer.segmentText}"`);
1140
- console.log(` afterWords: "${afterContainer.segmentText}"`);
1141
- console.log(
1142
- ` beforeHidden: ${beforeContainer.hidden}, afterHidden: ${afterContainer.hidden}`,
1143
- );
1144
- });
1145
-
1146
- test("context containers have synchronized timing with active word", async () => {
1147
- const timegroup = document.createElement("ef-timegroup");
1148
-
1149
- const captions = document.createElement("ef-captions");
1150
- captions.captionsData = {
1151
- segments: [
1152
- {
1153
- start: 0,
1154
- end: 8,
1155
- text: "This is a test segment with multiple words",
1156
- },
1157
- ],
1158
- word_segments: [
1159
- { text: "This", start: 0, end: 1 },
1160
- { text: " is", start: 1, end: 2 },
1161
- { text: " a", start: 2, end: 2.5 },
1162
- { text: " test", start: 2.5, end: 3.5 },
1163
- { text: " segment", start: 3.5, end: 4.5 },
1164
- { text: " with", start: 4.5, end: 5 },
1165
- { text: " multiple", start: 5, end: 6 },
1166
- { text: " words", start: 6, end: 8 },
1167
- ],
1168
- };
1169
-
1170
- const beforeContainer = document.createElement(
1171
- "ef-captions-before-active-word",
1172
- );
1173
- const activeContainer = document.createElement("ef-captions-active-word");
1174
- const afterContainer = document.createElement(
1175
- "ef-captions-after-active-word",
1176
- );
1177
-
1178
- captions.appendChild(beforeContainer);
1179
- captions.appendChild(activeContainer);
1180
- captions.appendChild(afterContainer);
1181
- timegroup.appendChild(captions);
1182
- document.body.appendChild(timegroup);
1183
-
1184
- // @ts-expect-error accessing private property for testing
1185
- const captionsTask = captions.customCaptionsDataTask;
1186
- await captionsTask.taskComplete;
1187
-
1188
- // Test during " test" word (2.5-3.5s)
1189
- timegroup.currentTimeMs = 3000;
1190
- await timegroup.seekTask.taskComplete;
1191
- await captions.frameTask.taskComplete;
1192
- await activeContainer.updateComplete;
1193
- await beforeContainer.updateComplete;
1194
- await afterContainer.updateComplete;
1195
-
1196
- console.log("Timing sync test - At 3000ms:");
1197
- console.log(
1198
- ` Before: ${beforeContainer.segmentStartMs}-${beforeContainer.segmentEndMs}`,
1199
- );
1200
- console.log(
1201
- ` Active: ${activeContainer.wordStartMs}-${activeContainer.wordEndMs}`,
1202
- );
1203
- console.log(
1204
- ` After: ${afterContainer.segmentStartMs}-${afterContainer.segmentEndMs}`,
1205
- );
1206
-
1207
- // All three should have the same timing as the active word
1208
- expect(beforeContainer.segmentStartMs).toBe(activeContainer.wordStartMs);
1209
- expect(beforeContainer.segmentEndMs).toBe(activeContainer.wordEndMs);
1210
- expect(afterContainer.segmentStartMs).toBe(activeContainer.wordStartMs);
1211
- expect(afterContainer.segmentEndMs).toBe(activeContainer.wordEndMs);
1212
-
1213
- // And they should all have the expected active word timing
1214
- expect(activeContainer.wordStartMs).toBe(2500);
1215
- expect(activeContainer.wordEndMs).toBe(3500);
1216
- });
1217
-
1218
- test("measures actual DOM widths for layout stability", async () => {
1219
- const timegroup = document.createElement("ef-timegroup");
1220
-
1221
- // Create exact demo structure
1222
- const scriptId = "width-test-script";
1223
- const script = document.createElement("script");
1224
- script.id = scriptId;
1225
- script.type = "application/json";
1226
- script.textContent = JSON.stringify({
1227
- segments: [
1228
- { start: 0, end: 4, text: "Welcome to the custom captions demo!" },
1229
- ],
1230
- word_segments: [
1231
- { text: "Welcome", start: 0, end: 0.6 },
1232
- { text: " to", start: 0.6, end: 0.9 },
1233
- { text: " the", start: 0.9, end: 1.2 },
1234
- { text: " custom", start: 1.2, end: 1.8 },
1235
- ],
1236
- });
1237
- document.body.appendChild(script);
1238
-
1239
- const captions = document.createElement("ef-captions");
1240
- captions.captionsScript = scriptId;
1241
- captions.style.cssText =
1242
- "display: block !important; text-align: center; background: green; padding: 16px;";
1243
-
1244
- const beforeContainer = document.createElement(
1245
- "ef-captions-before-active-word",
1246
- );
1247
- beforeContainer.className = "text-green-200";
1248
-
1249
- const activeContainer = document.createElement("ef-captions-active-word");
1250
- activeContainer.className =
1251
- "bg-lime-400 text-green-900 rounded font-bold";
1252
-
1253
- const afterContainer = document.createElement(
1254
- "ef-captions-after-active-word",
1255
- );
1256
- afterContainer.className = "text-green-200";
1257
-
1258
- captions.appendChild(beforeContainer);
1259
- captions.appendChild(activeContainer);
1260
- captions.appendChild(afterContainer);
1261
- timegroup.appendChild(captions);
1262
- document.body.appendChild(timegroup);
1263
-
1264
- // @ts-expect-error accessing private property for testing
1265
- const captionsTask = captions.customCaptionsDataTask;
1266
- await captionsTask.taskComplete;
1267
-
1268
- // Measure at different word timings and check for width/position changes
1269
- const measurements: Array<{
1270
- time: number;
1271
- word: string;
1272
- measurements: any;
1273
- }> = [];
1274
-
1275
- const measureElements = () => {
1276
- const captionsRect = captions.getBoundingClientRect();
1277
- const beforeRect = beforeContainer.getBoundingClientRect();
1278
- const activeRect = activeContainer.getBoundingClientRect();
1279
- const afterRect = afterContainer.getBoundingClientRect();
1280
-
1281
- return {
1282
- captions: { width: captionsRect.width, x: captionsRect.x },
1283
- before: {
1284
- width: beforeRect.width,
1285
- x: beforeRect.x,
1286
- text: beforeContainer.segmentText,
1287
- },
1288
- active: {
1289
- width: activeRect.width,
1290
- x: activeRect.x,
1291
- text: activeContainer.wordText,
1292
- },
1293
- after: {
1294
- width: afterRect.width,
1295
- x: afterRect.x,
1296
- text: afterContainer.segmentText,
1297
- },
1298
- totalContentWidth:
1299
- beforeRect.width + activeRect.width + afterRect.width,
1300
- };
1301
- };
1302
-
1303
- // Test each word and measure layout
1304
- const testWords = [
1305
- { time: 300, expectedWord: "Welcome" },
1306
- { time: 750, expectedWord: " to" },
1307
- { time: 1050, expectedWord: " the" },
1308
- { time: 1500, expectedWord: " custom" },
1309
- ];
1310
-
1311
- for (const test of testWords) {
1312
- timegroup.currentTimeMs = test.time;
1313
- await timegroup.seekTask.taskComplete;
1314
- await captions.frameTask.taskComplete;
1315
- await activeContainer.updateComplete;
1316
- await beforeContainer.updateComplete;
1317
- await afterContainer.updateComplete;
1318
-
1319
- // Wait for browser to paint before measuring
1320
- await new Promise((resolve) => requestAnimationFrame(resolve));
1321
-
1322
- const measurement = measureElements();
1323
- measurements.push({
1324
- time: test.time,
1325
- word: test.expectedWord,
1326
- measurements: measurement,
1327
- });
1328
-
1329
- console.log(`At ${test.time}ms (${test.expectedWord}):`);
1330
- console.log(
1331
- ` Active word: "${measurement.active.text}" width=${measurement.active.width}px x=${measurement.active.x}`,
1332
- );
1333
- console.log(
1334
- ` Before: "${measurement.before.text}" width=${measurement.before.width}px`,
1335
- );
1336
- console.log(
1337
- ` After: "${measurement.after.text}" width=${measurement.after.width}px`,
1338
- );
1339
- console.log(
1340
- ` Total content: ${measurement.totalContentWidth}px, Container: ${measurement.captions.width}px`,
1341
- );
1342
- console.log("");
1343
- }
1344
-
1345
- // Check if total width stays consistent
1346
- // const firstTotal = measurements[0]?.measurements.totalContentWidth;
1347
- // const allTotalsConsistent = measurements.every(
1348
- // (m) => Math.abs(m.measurements.totalContentWidth - firstTotal) < 2, // Allow 1-2px tolerance
1349
- // );
1350
- //
1351
- // if (!allTotalsConsistent) {
1352
- // console.log("Width inconsistency detected:");
1353
- // measurements.forEach((m) => {
1354
- // console.log(` ${m.word}: ${m.measurements.totalContentWidth}px`);
1355
- // });
1356
- // }
1357
- //
1358
- // expect(allTotalsConsistent).toBe(true);
1359
-
1360
- // Check if the overall container width stays consistent (the real measure of stability)
1361
- const firstContainerWidth = measurements[0]?.measurements.captions.width;
1362
- const allContainerWidthsConsistent = measurements.every(
1363
- (m) =>
1364
- Math.abs(m.measurements.captions.width - firstContainerWidth) < 0.1,
1365
- );
1366
-
1367
- if (!allContainerWidthsConsistent) {
1368
- console.log("Container width inconsistency detected:");
1369
- measurements.forEach((m) => {
1370
- console.log(` ${m.word}: ${m.measurements.captions.width}px`);
1371
- });
1372
- }
1373
-
1374
- expect(allContainerWidthsConsistent).toBe(true);
1375
- });
1376
-
1377
- test("measures font weight differences causing layout shifts", async () => {
1378
- const timegroup = document.createElement("ef-timegroup");
1379
-
1380
- const scriptId = "font-test-script";
1381
- const script = document.createElement("script");
1382
- script.id = scriptId;
1383
- script.type = "application/json";
1384
- script.textContent = JSON.stringify({
1385
- segments: [{ start: 0, end: 4, text: "Welcome to the custom" }],
1386
- word_segments: [
1387
- { text: "Welcome", start: 0, end: 0.6 },
1388
- { text: " to", start: 0.6, end: 0.9 },
1389
- { text: " the", start: 0.9, end: 1.2 },
1390
- { text: " custom", start: 1.2, end: 1.8 },
1391
- ],
1392
- });
1393
- document.body.appendChild(script);
1394
-
1395
- const captions = document.createElement("ef-captions");
1396
- captions.captionsScript = scriptId;
1397
- captions.style.cssText =
1398
- "display: block !important; text-align: center; background: green; padding: 16px; font-weight: bold;";
1399
-
1400
- const beforeContainer = document.createElement(
1401
- "ef-captions-before-active-word",
1402
- );
1403
- beforeContainer.className = "text-green-200";
1404
-
1405
- const activeContainer = document.createElement("ef-captions-active-word");
1406
- activeContainer.className = "bg-lime-400 text-green-900 rounded";
1407
-
1408
- const afterContainer = document.createElement(
1409
- "ef-captions-after-active-word",
1410
- );
1411
- afterContainer.className = "text-green-200";
1412
-
1413
- captions.appendChild(beforeContainer);
1414
- captions.appendChild(activeContainer);
1415
- captions.appendChild(afterContainer);
1416
- timegroup.appendChild(captions);
1417
- document.body.appendChild(timegroup);
1418
-
1419
- // @ts-expect-error accessing private property for testing
1420
- const captionsTask = captions.customCaptionsDataTask;
1421
- await captionsTask.taskComplete;
1422
-
1423
- // Test with consistent font weight
1424
- timegroup.currentTimeMs = 300;
1425
- await timegroup.seekTask.taskComplete;
1426
- await captions.frameTask.taskComplete;
1427
- await activeContainer.updateComplete;
1428
-
1429
- const welcomeRect = activeContainer.getBoundingClientRect();
1430
- console.log(
1431
- `"Welcome" with consistent bold: width=${welcomeRect.width}px`,
1432
- );
1433
-
1434
- timegroup.currentTimeMs = 750;
1435
- await timegroup.seekTask.taskComplete;
1436
- await captions.frameTask.taskComplete;
1437
- await activeContainer.updateComplete;
1438
-
1439
- const toRect = activeContainer.getBoundingClientRect();
1440
- console.log(`" to" with consistent bold: width=${toRect.width}px`);
1441
-
1442
- // Check if the text positioning stays stable with consistent font weight
1443
- const totalWidth1 =
1444
- beforeContainer.getBoundingClientRect().width +
1445
- activeContainer.getBoundingClientRect().width +
1446
- afterContainer.getBoundingClientRect().width;
1447
-
1448
- timegroup.currentTimeMs = 1050;
1449
- await timegroup.seekTask.taskComplete;
1450
- await captions.frameTask.taskComplete;
1451
- await activeContainer.updateComplete;
1452
- await beforeContainer.updateComplete;
1453
- await afterContainer.updateComplete;
1454
-
1455
- const totalWidth2 =
1456
- beforeContainer.getBoundingClientRect().width +
1457
- activeContainer.getBoundingClientRect().width +
1458
- afterContainer.getBoundingClientRect().width;
1459
-
1460
- console.log(
1461
- `Total width consistency: ${totalWidth1}px vs ${totalWidth2}px (diff: ${Math.abs(totalWidth1 - totalWidth2)}px)`,
1462
- );
1463
-
1464
- expect(Math.abs(totalWidth1 - totalWidth2)).toBeLessThan(1);
1465
- });
1466
-
1467
- test("captions work naturally without CSS overrides", async () => {
1468
- const timegroup = document.createElement("ef-timegroup");
1469
-
1470
- const scriptId = "natural-test-script";
1471
- const script = document.createElement("script");
1472
- script.id = scriptId;
1473
- script.type = "application/json";
1474
- script.textContent = JSON.stringify({
1475
- segments: [{ start: 0, end: 4, text: "Natural flow test" }],
1476
- word_segments: [
1477
- { text: "Natural", start: 0, end: 1 },
1478
- { text: " flow", start: 1, end: 2 },
1479
- { text: " test", start: 2, end: 4 },
1480
- ],
1481
- });
1482
- document.body.appendChild(script);
1483
-
1484
- // Test with NO CSS overrides - should just work naturally
1485
- const captions = document.createElement("ef-captions");
1486
- captions.captionsScript = scriptId;
1487
- captions.className = "font-bold text-center p-4"; // Simple, natural styling
1488
-
1489
- const beforeContainer = document.createElement(
1490
- "ef-captions-before-active-word",
1491
- );
1492
- const activeContainer = document.createElement("ef-captions-active-word");
1493
- activeContainer.className = "bg-yellow-400 text-black rounded"; // Only background change
1494
- const afterContainer = document.createElement(
1495
- "ef-captions-after-active-word",
1496
- );
1497
-
1498
- captions.appendChild(beforeContainer);
1499
- captions.appendChild(activeContainer);
1500
- captions.appendChild(afterContainer);
1501
- timegroup.appendChild(captions);
1502
- document.body.appendChild(timegroup);
1503
-
1504
- // @ts-expect-error accessing private property for testing
1505
- const captionsTask = captions.customCaptionsDataTask;
1506
- await captionsTask.taskComplete;
1507
-
1508
- // Measure widths with natural component behavior
1509
- timegroup.currentTimeMs = 500;
1510
- await timegroup.seekTask.taskComplete;
1511
- await captions.frameTask.taskComplete;
1512
-
1513
- const naturalRect1 = captions.getBoundingClientRect();
1514
- console.log(
1515
- `Natural captions width at "Natural": ${naturalRect1.width}px`,
1516
- );
1517
-
1518
- timegroup.currentTimeMs = 1500;
1519
- await timegroup.seekTask.taskComplete;
1520
- await captions.frameTask.taskComplete;
1521
-
1522
- const naturalRect2 = captions.getBoundingClientRect();
1523
- console.log(`Natural captions width at " flow": ${naturalRect2.width}px`);
1524
-
1525
- const widthDiff = Math.abs(naturalRect2.width - naturalRect1.width);
1526
- console.log(`Width difference: ${widthDiff}px`);
1527
-
1528
- // Should have minimal width difference with natural component behavior
1529
- expect(widthDiff).toBeLessThan(2); // Allow small rounding tolerance
1530
- });
1531
-
1532
- test("transform scaling keeps surrounding text positions stable", async () => {
1533
- const timegroup = document.createElement("ef-timegroup");
1534
-
1535
- const captions = document.createElement("ef-captions");
1536
- captions.captionsData = {
1537
- segments: [{ start: 0, end: 4, text: "Before active after text" }],
1538
- word_segments: [
1539
- { text: "Before", start: 0, end: 1 },
1540
- { text: " active", start: 1, end: 2 },
1541
- { text: " after", start: 2, end: 3 },
1542
- { text: " text", start: 3, end: 4 },
1543
- ],
1544
- };
1545
-
1546
- const beforeContainer = document.createElement(
1547
- "ef-captions-before-active-word",
1548
- );
1549
- const activeContainer = document.createElement("ef-captions-active-word");
1550
- activeContainer.style.cssText =
1551
- "background: yellow; color: black; transform: scale(1.2); transform-origin: center; transition: transform 200ms;";
1552
- const afterContainer = document.createElement(
1553
- "ef-captions-after-active-word",
1554
- );
1555
-
1556
- captions.appendChild(beforeContainer);
1557
- captions.appendChild(activeContainer);
1558
- captions.appendChild(afterContainer);
1559
- timegroup.appendChild(captions);
1560
- document.body.appendChild(timegroup);
1561
-
1562
- // @ts-expect-error accessing private property for testing
1563
- const captionsTask = captions.customCaptionsDataTask;
1564
- await captionsTask.taskComplete;
1565
-
1566
- // Test different words and ensure before/after text positions don't jump
1567
- const positionTests = [
1568
- { time: 500, word: "Before" },
1569
- { time: 1500, word: " active" },
1570
- { time: 2500, word: " after" },
1571
- ];
1572
-
1573
- let previousBeforeX: number | null = null;
1574
- let previousAfterX: number | null = null;
1575
-
1576
- for (const test of positionTests) {
1577
- timegroup.currentTimeMs = test.time;
1578
- await timegroup.seekTask.taskComplete;
1579
- await captions.frameTask.taskComplete;
1580
-
1581
- const beforeRect = beforeContainer.getBoundingClientRect();
1582
- const activeRect = activeContainer.getBoundingClientRect();
1583
- const afterRect = afterContainer.getBoundingClientRect();
1584
-
1585
- console.log(`At ${test.time}ms (active word: "${test.word}"):`);
1586
- console.log(` Before text X: ${beforeRect.x.toFixed(1)}px`);
1587
- console.log(` Active text X: ${activeRect.x.toFixed(1)}px (scaled)`);
1588
- console.log(` After text X: ${afterRect.x.toFixed(1)}px`);
1589
-
1590
- if (previousBeforeX !== null && beforeContainer.textContent) {
1591
- const beforeXDiff = Math.abs(beforeRect.x - previousBeforeX);
1592
- console.log(` Before X movement: ${beforeXDiff.toFixed(1)}px`);
1593
- expect(beforeXDiff).toBeLessThan(2); // Should be very stable
1594
- }
1595
-
1596
- if (previousAfterX !== null && afterContainer.textContent) {
1597
- const afterXDiff = Math.abs(afterRect.x - previousAfterX);
1598
- console.log(` After X movement: ${afterXDiff.toFixed(1)}px`);
1599
- expect(afterXDiff).toBeLessThan(2); // Should be very stable
1600
- }
1601
-
1602
- previousBeforeX = beforeRect.x;
1603
- previousAfterX = afterRect.x;
1604
- console.log("");
1605
- }
1606
- });
1607
-
1608
- test("CSS animations trigger with timegroup timing", async () => {
1609
- const timegroup = document.createElement("ef-timegroup");
1610
-
1611
- // Add bounce animation keyframes to test environment
1612
- const style = document.createElement("style");
1613
- style.textContent = `
1614
- @keyframes bounceIn {
1615
- 0% { transform: scale(calc(0.6 + var(--ef-word-seed, 0.5) * 0.3))
1616
- rotate(calc(-10deg + var(--ef-word-seed, 0.5) * 20deg))
1617
- skewX(calc(-15deg + var(--ef-word-seed, 0.5) * 30deg)); }
1618
- 50% { transform: scale(calc(1.05 + var(--ef-word-seed, 0.5) * 0.2))
1619
- rotate(calc(-3deg + var(--ef-word-seed, 0.5) * 6deg))
1620
- skewX(calc(-5deg + var(--ef-word-seed, 0.5) * 10deg)); }
1621
- 100% { transform: scale(1) rotate(0deg) skewX(0deg); }
1622
- }
1623
- .bounce-in { animation: 0.3s ease-out 0s 1 normal none running bounceIn; }
1624
- .bounce-scale-125 {
1625
- animation: 0.3s ease-out 0s 1 normal none running bounceIn;
1626
- transform: scale(1.25);
1627
- }
1628
- `;
1629
- document.head.appendChild(style);
1630
-
1631
- const captions = document.createElement("ef-captions");
1632
- captions.captionsData = {
1633
- segments: [{ start: 0, end: 3, text: "Bounce test animation" }],
1634
- word_segments: [
1635
- { text: "Bounce", start: 0, end: 1 },
1636
- { text: " test", start: 1, end: 2 },
1637
- { text: " animation", start: 2, end: 3 },
1638
- ],
1639
- };
1640
-
1641
- const activeContainer = document.createElement("ef-captions-active-word");
1642
- activeContainer.className = "bounce-scale-125";
1643
- captions.appendChild(activeContainer);
1644
- timegroup.appendChild(captions);
1645
- document.body.appendChild(timegroup);
1646
-
1647
- // @ts-expect-error accessing private property for testing
1648
- const captionsTask = captions.customCaptionsDataTask;
1649
- await captionsTask.taskComplete;
1650
-
1651
- // Test animation properties when different words become active
1652
- const animationTests = [
1653
- { time: 500, word: "Bounce" },
1654
- { time: 1500, word: " test" },
1655
- { time: 2500, word: " animation" },
1656
- ];
1657
-
1658
- for (const test of animationTests) {
1659
- console.log(
1660
- `\nTesting animation at ${test.time}ms (word: "${test.word}"):`,
1661
- );
1662
-
1663
- timegroup.currentTimeMs = test.time;
1664
- await timegroup.seekTask.taskComplete;
1665
- await captions.frameTask.taskComplete;
1666
- await activeContainer.updateComplete;
1667
-
1668
- // Check that element is visible and has animation properties
1669
- const computedStyle = getComputedStyle(activeContainer);
1670
- const animationName = computedStyle.animationName;
1671
- const animationDuration = computedStyle.animationDuration;
1672
- const animationTimingFunction = computedStyle.animationTimingFunction;
1673
-
1674
- console.log(` Element text: "${activeContainer.textContent}"`);
1675
- console.log(` Animation name: ${animationName}`);
1676
- console.log(` Animation duration: ${animationDuration}`);
1677
- console.log(` Animation timing: ${animationTimingFunction}`);
1678
- console.log(` Element visible: ${!activeContainer.hidden}`);
1679
-
1680
- // Element should be visible when in its time range
1681
- expect(activeContainer.hidden).toBe(false);
1682
-
1683
- // Should have the bounce animation applied correctly
1684
- expect(animationName).toBe("bounceIn");
1685
- expect(animationDuration).toBe("0.3s");
1686
- }
1687
-
1688
- // Test that element is hidden when outside time range
1689
- timegroup.currentTimeMs = 3500; // After all words
1690
- await timegroup.seekTask.taskComplete;
1691
- await captions.frameTask.taskComplete;
1692
-
1693
- console.log("\nAfter time range (3500ms):");
1694
- console.log(` Element hidden: ${activeContainer.hidden}`);
1695
- console.log(` Element text: "${activeContainer.textContent}"`);
1696
-
1697
- // Should be hidden when no word is active
1698
- expect(activeContainer.hidden).toBe(true);
1699
-
1700
- document.head.removeChild(style); // Clean up
1701
- });
1702
- });
1703
-
1704
- describe("sequence timing integration", () => {
1705
- test("sequence duration updates when captions data is set via captionsData property", async () => {
1706
- // Test the exact scenario: sequence > contain > captions, contain > captions
1707
- const sequence = document.createElement("ef-timegroup");
1708
- sequence.mode = "sequence";
1709
-
1710
- const container1 = document.createElement("ef-timegroup");
1711
- container1.mode = "contain";
1712
- const captions1 = document.createElement("ef-captions");
1713
- const word1 = document.createElement("ef-captions-active-word");
1714
- captions1.appendChild(word1);
1715
- container1.appendChild(captions1);
1716
-
1717
- const container2 = document.createElement("ef-timegroup");
1718
- container2.mode = "contain";
1719
- const captions2 = document.createElement("ef-captions");
1720
- const word2 = document.createElement("ef-captions-active-word");
1721
- captions2.appendChild(word2);
1722
- container2.appendChild(captions2);
1723
-
1724
- sequence.appendChild(container1);
1725
- sequence.appendChild(container2);
1726
- document.body.appendChild(sequence);
1727
-
1728
- // Initially, sequence should have no duration
1729
- await sequence.updateComplete;
1730
- expect(sequence.durationMs).toBe(0);
1731
-
1732
- // Set captions data for both elements
1733
- captions1.captionsData = {
1734
- segments: [{ start: 0, end: 5, text: "First" }],
1735
- word_segments: [{ text: "First", start: 0, end: 5 }],
1736
- };
1737
-
1738
- captions2.captionsData = {
1739
- segments: [{ start: 0, end: 3, text: "Second" }],
1740
- word_segments: [{ text: "Second", start: 0, end: 3 }],
1741
- };
1742
-
1743
- // Wait for updates to propagate
1744
- await captions1.updateComplete;
1745
- await captions2.updateComplete;
1746
- await sequence.updateComplete;
1747
-
1748
- // Sequence should now have combined duration (5s + 3s = 8s)
1749
- expect(sequence.durationMs).toBe(8000);
1750
-
1751
- try {
1752
- document.body.removeChild(sequence);
1753
- } catch (_e) {
1754
- // Cleanup may fail in test environment, ignore
1755
- }
1756
- });
1757
-
1758
- test("second captions timegroup is visible when timeline is positioned in second segment", async () => {
1759
- // Create exact demo structure: sequence > contain > captions, contain > captions
1760
- const sequence = document.createElement("ef-timegroup");
1761
- sequence.mode = "sequence";
1762
-
1763
- const container1 = document.createElement("ef-timegroup");
1764
- container1.mode = "contain";
1765
- const captions1 = document.createElement("ef-captions");
1766
- captions1.captionsData = {
1767
- segments: [{ start: 0, end: 5, text: "First" }],
1768
- word_segments: [{ text: "First", start: 0, end: 5 }],
1769
- };
1770
- const word1 = document.createElement("ef-captions-active-word");
1771
- captions1.appendChild(word1);
1772
- container1.appendChild(captions1);
1773
-
1774
- const container2 = document.createElement("ef-timegroup");
1775
- container2.mode = "contain";
1776
- const captions2 = document.createElement("ef-captions");
1777
- captions2.captionsData = {
1778
- segments: [{ start: 0, end: 3, text: "Second" }],
1779
- word_segments: [{ text: "Second", start: 0, end: 3 }],
1780
- };
1781
- const word2 = document.createElement("ef-captions-active-word");
1782
- captions2.appendChild(word2);
1783
- container2.appendChild(captions2);
1784
-
1785
- sequence.appendChild(container1);
1786
- sequence.appendChild(container2);
1787
- document.body.appendChild(sequence);
1788
-
1789
- // Set timeline to be in second timegroup (7s = 2s into second captions)
1790
- sequence.currentTimeMs = 7000;
1791
- await sequence.seekTask.taskComplete;
1792
- await captions2.frameTask.taskComplete;
1793
-
1794
- console.log(
1795
- `Timeline at 7000ms - Second captions word: "${word2.wordText}", hidden: ${word2.hidden}`,
1796
- );
1797
-
1798
- // The second captions must be visible
1799
- expect(word2.wordText).toBe("Second");
1800
- expect(word2.hidden).toBe(false);
1801
- });
1802
-
1803
- test("shows completed words in before container when segment extends beyond words", async () => {
1804
- // Test case where segment duration extends beyond last word
1805
- const timegroup = document.createElement("ef-timegroup");
1806
-
1807
- const captions = document.createElement("ef-captions");
1808
- captions.captionsData = {
1809
- segments: [
1810
- { start: 0, end: 5, text: "Complete segment text" }, // 5 second segment
1811
- ],
1812
- word_segments: [
1813
- { text: "Complete", start: 0, end: 1 },
1814
- { text: " segment", start: 1, end: 2 },
1815
- { text: " text", start: 2, end: 3 },
1816
- // Words end at 3s but segment continues until 5s
1817
- ],
1818
- };
1819
-
1820
- const beforeContainer = document.createElement(
1821
- "ef-captions-before-active-word",
1822
- );
1823
- const wordContainer = document.createElement("ef-captions-active-word");
1824
- const segmentContainer = document.createElement("ef-captions-segment");
1825
-
1826
- captions.appendChild(beforeContainer);
1827
- captions.appendChild(wordContainer);
1828
- captions.appendChild(segmentContainer);
1829
- timegroup.appendChild(captions);
1830
- document.body.appendChild(timegroup);
1831
-
1832
- // Test at 2.5s - should show " text" word normally
1833
- timegroup.currentTimeMs = 2500;
1834
- await timegroup.seekTask.taskComplete;
1835
- await captions.frameTask.taskComplete;
1836
- await wordContainer.updateComplete;
1837
-
1838
- expect(wordContainer.wordText).toBe(" text");
1839
- expect(wordContainer.hidden).toBe(false);
1840
-
1841
- // Test at 4s - after all words finished but segment still active
1842
- timegroup.currentTimeMs = 4000;
1843
- await timegroup.seekTask.taskComplete;
1844
- await captions.frameTask.taskComplete;
1845
- await wordContainer.updateComplete;
1846
- await beforeContainer.updateComplete;
1847
- await segmentContainer.updateComplete;
1848
-
1849
- console.log(
1850
- ` Active word: "${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
1851
- );
1852
- console.log(
1853
- ` Before container: "${beforeContainer.segmentText}", hidden=${beforeContainer.hidden}`,
1854
- );
1855
- console.log(
1856
- ` Segment: "${segmentContainer.segmentText}", hidden=${segmentContainer.hidden}`,
1857
- );
1858
-
1859
- // Word container should be empty/hidden since no active word
1860
- expect(wordContainer.wordText).toBe("");
1861
- expect(wordContainer.hidden).toBe(true);
1862
-
1863
- // Before container should show all completed words to maintain visual continuity
1864
- expect(beforeContainer.segmentText).toBe("Complete segment text");
1865
- expect(beforeContainer.hidden).toBe(false);
1866
-
1867
- // Segment should still be active
1868
- expect(segmentContainer.segmentText).toBe("Complete segment text");
1869
- expect(segmentContainer.hidden).toBe(false);
1870
-
1871
- try {
1872
- document.body.removeChild(timegroup);
1873
- } catch (_e) {
1874
- // Cleanup may fail in test environment, ignore
1875
- }
1876
- });
1877
-
1878
- test("shows all words in after container when in segment but before first word", async () => {
1879
- // Test case where we're in a segment but before the first word starts
1880
- const timegroup = document.createElement("ef-timegroup");
1881
-
1882
- const captions = document.createElement("ef-captions");
1883
- captions.captionsData = {
1884
- segments: [
1885
- { start: 0, end: 10, text: "Complete test segment" }, // 10 second segment
1886
- ],
1887
- word_segments: [
1888
- { text: "Complete", start: 2, end: 4 }, // First word starts at 2s
1889
- { text: " test", start: 5, end: 7 },
1890
- { text: " segment", start: 8, end: 9 },
1891
- // Gap from 0-2s before first word
1892
- ],
1893
- };
1894
-
1895
- const beforeContainer = document.createElement(
1896
- "ef-captions-before-active-word",
1897
- );
1898
- const wordContainer = document.createElement("ef-captions-active-word");
1899
- const afterContainer = document.createElement(
1900
- "ef-captions-after-active-word",
1901
- );
1902
- const segmentContainer = document.createElement("ef-captions-segment");
1903
-
1904
- captions.appendChild(beforeContainer);
1905
- captions.appendChild(wordContainer);
1906
- captions.appendChild(afterContainer);
1907
- captions.appendChild(segmentContainer);
1908
- timegroup.appendChild(captions);
1909
- document.body.appendChild(timegroup);
1910
-
1911
- // @ts-expect-error accessing private property for testing
1912
- const captionsTask = captions.customCaptionsDataTask;
1913
- await captionsTask.taskComplete;
1914
-
1915
- // Test at 1s - in segment but before first word starts
1916
- timegroup.currentTimeMs = 1000;
1917
- await timegroup.seekTask.taskComplete;
1918
- await captions.frameTask.taskComplete;
1919
- await wordContainer.updateComplete;
1920
- await beforeContainer.updateComplete;
1921
- await afterContainer.updateComplete;
1922
- await segmentContainer.updateComplete;
1923
-
1924
- console.log(
1925
- ` Active word: "${wordContainer.wordText}", hidden=${wordContainer.hidden}`,
1926
- );
1927
- console.log(
1928
- ` Before container: "${beforeContainer.segmentText}", hidden=${beforeContainer.hidden}`,
1929
- );
1930
- console.log(
1931
- ` After container: "${afterContainer.segmentText}", hidden=${afterContainer.hidden}`,
1932
- );
1933
- console.log(
1934
- ` Segment: "${segmentContainer.segmentText}", hidden=${segmentContainer.hidden}`,
1935
- );
1936
-
1937
- // Active word should be empty since no word is active yet
1938
- expect(wordContainer.wordText).toBe("");
1939
- expect(wordContainer.hidden).toBe(true);
1940
-
1941
- // Before container should be empty (nothing has happened yet)
1942
- expect(beforeContainer.segmentText).toBe("");
1943
- expect(beforeContainer.hidden).toBe(true);
1944
-
1945
- // After container should show all upcoming words
1946
- expect(afterContainer.segmentText).toBe("Complete test segment");
1947
- expect(afterContainer.hidden).toBe(false);
1948
-
1949
- // Segment should be active
1950
- expect(segmentContainer.segmentText).toBe("Complete test segment");
1951
- expect(segmentContainer.hidden).toBe(false);
1952
-
1953
- try {
1954
- document.body.removeChild(timegroup);
1955
- } catch (_e) {
1956
- // Cleanup may fail in test environment, ignore
1957
- }
1958
- });
1959
- });
1960
- });