@editframe/elements 0.26.2-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.
- package/dist/elements/EFTimegroup.js +7 -2
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/package.json +2 -2
- package/scripts/build-css.js +3 -3
- package/tsdown.config.ts +1 -1
- package/types.json +1 -1
- package/src/elements/ContextProxiesController.ts +0 -124
- package/src/elements/CrossUpdateController.ts +0 -22
- package/src/elements/EFAudio.browsertest.ts +0 -706
- package/src/elements/EFAudio.ts +0 -56
- package/src/elements/EFCaptions.browsertest.ts +0 -1960
- package/src/elements/EFCaptions.ts +0 -823
- package/src/elements/EFImage.browsertest.ts +0 -120
- package/src/elements/EFImage.ts +0 -113
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
- package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
- package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
- package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
- package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
- package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
- package/src/elements/EFMedia.browsertest.ts +0 -872
- package/src/elements/EFMedia.ts +0 -341
- package/src/elements/EFSourceMixin.ts +0 -60
- package/src/elements/EFSurface.browsertest.ts +0 -151
- package/src/elements/EFSurface.ts +0 -142
- package/src/elements/EFTemporal.browsertest.ts +0 -215
- package/src/elements/EFTemporal.ts +0 -800
- package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
- package/src/elements/EFThumbnailStrip.ts +0 -906
- package/src/elements/EFTimegroup.browsertest.ts +0 -870
- package/src/elements/EFTimegroup.ts +0 -878
- package/src/elements/EFVideo.browsertest.ts +0 -1482
- package/src/elements/EFVideo.ts +0 -564
- package/src/elements/EFWaveform.ts +0 -547
- package/src/elements/FetchContext.browsertest.ts +0 -401
- package/src/elements/FetchMixin.ts +0 -38
- package/src/elements/SampleBuffer.ts +0 -94
- package/src/elements/TargetController.browsertest.ts +0 -230
- package/src/elements/TargetController.ts +0 -224
- package/src/elements/TimegroupController.ts +0 -26
- package/src/elements/durationConverter.ts +0 -35
- package/src/elements/parseTimeToMs.ts +0 -9
- package/src/elements/printTaskStatus.ts +0 -16
- package/src/elements/renderTemporalAudio.ts +0 -108
- package/src/elements/updateAnimations.browsertest.ts +0 -1884
- package/src/elements/updateAnimations.ts +0 -217
- package/src/elements/util.ts +0 -24
- package/src/gui/ContextMixin.browsertest.ts +0 -860
- package/src/gui/ContextMixin.ts +0 -562
- package/src/gui/Controllable.browsertest.ts +0 -258
- package/src/gui/Controllable.ts +0 -41
- package/src/gui/EFConfiguration.ts +0 -40
- package/src/gui/EFControls.browsertest.ts +0 -389
- package/src/gui/EFControls.ts +0 -195
- package/src/gui/EFDial.browsertest.ts +0 -84
- package/src/gui/EFDial.ts +0 -172
- package/src/gui/EFFilmstrip.browsertest.ts +0 -712
- package/src/gui/EFFilmstrip.ts +0 -1349
- package/src/gui/EFFitScale.ts +0 -152
- package/src/gui/EFFocusOverlay.ts +0 -79
- package/src/gui/EFPause.browsertest.ts +0 -202
- package/src/gui/EFPause.ts +0 -73
- package/src/gui/EFPlay.browsertest.ts +0 -202
- package/src/gui/EFPlay.ts +0 -73
- package/src/gui/EFPreview.ts +0 -74
- package/src/gui/EFResizableBox.browsertest.ts +0 -79
- package/src/gui/EFResizableBox.ts +0 -898
- package/src/gui/EFScrubber.ts +0 -151
- package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
- package/src/gui/EFTimeDisplay.ts +0 -55
- package/src/gui/EFToggleLoop.ts +0 -35
- package/src/gui/EFTogglePlay.ts +0 -70
- package/src/gui/EFWorkbench.ts +0 -115
- package/src/gui/PlaybackController.ts +0 -527
- package/src/gui/TWMixin.css +0 -6
- package/src/gui/TWMixin.ts +0 -61
- package/src/gui/TargetOrContextMixin.ts +0 -185
- package/src/gui/currentTimeContext.ts +0 -5
- package/src/gui/durationContext.ts +0 -3
- package/src/gui/efContext.ts +0 -6
- package/src/gui/fetchContext.ts +0 -5
- package/src/gui/focusContext.ts +0 -7
- package/src/gui/focusedElementContext.ts +0 -5
- package/src/gui/playingContext.ts +0 -5
- package/src/otel/BridgeSpanExporter.ts +0 -150
- package/src/otel/setupBrowserTracing.ts +0 -73
- package/src/otel/tracingHelpers.ts +0 -251
- package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
- package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
- package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
- package/src/transcoding/types/index.ts +0 -312
- package/src/transcoding/utils/MediaUtils.ts +0 -63
- package/src/transcoding/utils/UrlGenerator.ts +0 -68
- package/src/transcoding/utils/constants.ts +0 -36
- package/src/utils/LRUCache.test.ts +0 -274
- 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
|
-
});
|