wavesurfer.js 7.11.1 → 7.12.1
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/README.md +1 -1
- package/dist/__tests__/memory-leaks.test.d.ts +7 -0
- package/dist/__tests__/memory-leaks.test.js +281 -0
- package/dist/__tests__/player.test.js +107 -0
- package/dist/__tests__/renderer-utils.test.js +55 -2
- package/dist/draggable.d.ts +4 -0
- package/dist/draggable.js +4 -0
- package/dist/player.d.ts +21 -0
- package/dist/player.js +86 -0
- package/dist/plugins/envelope.cjs +1 -1
- package/dist/plugins/envelope.esm.js +1 -1
- package/dist/plugins/envelope.js +1 -1
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/hover.cjs +1 -1
- package/dist/plugins/hover.d.ts +0 -3
- package/dist/plugins/hover.esm.js +1 -1
- package/dist/plugins/hover.js +1 -1
- package/dist/plugins/hover.min.js +1 -1
- package/dist/plugins/minimap.cjs +1 -1
- package/dist/plugins/minimap.esm.js +1 -1
- package/dist/plugins/minimap.js +1 -1
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/record.cjs +1 -1
- package/dist/plugins/record.esm.js +1 -1
- package/dist/plugins/record.js +1 -1
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.cjs +1 -1
- package/dist/plugins/regions.esm.js +1 -1
- package/dist/plugins/regions.js +1 -1
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/timeline.cjs +1 -1
- package/dist/plugins/timeline.d.ts +1 -1
- package/dist/plugins/timeline.esm.js +1 -1
- package/dist/plugins/timeline.js +1 -1
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/plugins/zoom.cjs +1 -1
- package/dist/plugins/zoom.esm.js +1 -1
- package/dist/plugins/zoom.js +1 -1
- package/dist/plugins/zoom.min.js +1 -1
- package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
- package/dist/reactive/__tests__/drag-stream.test.js +199 -0
- package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
- package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
- package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
- package/dist/reactive/__tests__/event-streams.test.js +278 -0
- package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
- package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
- package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
- package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
- package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
- package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
- package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
- package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
- package/dist/reactive/__tests__/store.test.d.ts +1 -0
- package/dist/reactive/__tests__/store.test.js +271 -0
- package/dist/reactive/drag-stream.d.ts +48 -0
- package/dist/reactive/drag-stream.js +146 -0
- package/dist/reactive/event-stream-emitter.d.ts +104 -0
- package/dist/reactive/event-stream-emitter.js +151 -0
- package/dist/reactive/event-streams.d.ts +65 -0
- package/dist/reactive/event-streams.js +146 -0
- package/dist/reactive/media-event-bridge.d.ts +51 -0
- package/dist/reactive/media-event-bridge.js +152 -0
- package/dist/reactive/render-scheduler.d.ts +43 -0
- package/dist/reactive/render-scheduler.js +75 -0
- package/dist/reactive/scroll-stream.d.ts +93 -0
- package/dist/reactive/scroll-stream.js +131 -0
- package/dist/reactive/state-event-emitter.d.ts +105 -0
- package/dist/reactive/state-event-emitter.js +213 -0
- package/dist/reactive/store.d.ts +66 -0
- package/dist/reactive/store.js +115 -0
- package/dist/renderer-utils.d.ts +13 -4
- package/dist/renderer-utils.js +35 -15
- package/dist/renderer.d.ts +2 -1
- package/dist/renderer.js +51 -48
- package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
- package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
- package/dist/state/wavesurfer-state.d.ts +90 -0
- package/dist/state/wavesurfer-state.js +124 -0
- package/dist/types.d.ts +165 -1
- package/dist/wavesurfer.cjs +1 -1
- package/dist/wavesurfer.d.ts +16 -0
- package/dist/wavesurfer.esm.js +1 -1
- package/dist/wavesurfer.js +32 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +2 -3
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { signal } from '../store';
|
|
2
|
+
import { fromEvent, map, filter, debounce, throttle, cleanup } from '../event-streams';
|
|
3
|
+
describe('fromEvent', () => {
|
|
4
|
+
let button;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
button = document.createElement('button');
|
|
7
|
+
});
|
|
8
|
+
it('should convert DOM events to signal', () => {
|
|
9
|
+
const clicks = fromEvent(button, 'click');
|
|
10
|
+
const callback = jest.fn();
|
|
11
|
+
clicks.subscribe(callback);
|
|
12
|
+
button.click();
|
|
13
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining({ type: 'click' }));
|
|
14
|
+
});
|
|
15
|
+
it('should start with null value', () => {
|
|
16
|
+
const clicks = fromEvent(button, 'click');
|
|
17
|
+
expect(clicks.value).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
it('should update signal on each event', () => {
|
|
20
|
+
const clicks = fromEvent(button, 'click');
|
|
21
|
+
const values = [];
|
|
22
|
+
clicks.subscribe((event) => values.push(event));
|
|
23
|
+
button.click();
|
|
24
|
+
button.click();
|
|
25
|
+
expect(values).toHaveLength(2);
|
|
26
|
+
expect(values[0]).toMatchObject({ type: 'click' });
|
|
27
|
+
expect(values[1]).toMatchObject({ type: 'click' });
|
|
28
|
+
});
|
|
29
|
+
it('should cleanup event listener on cleanup()', () => {
|
|
30
|
+
const clicks = fromEvent(button, 'click');
|
|
31
|
+
const callback = jest.fn();
|
|
32
|
+
clicks.subscribe(callback);
|
|
33
|
+
button.click();
|
|
34
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
35
|
+
cleanup(clicks);
|
|
36
|
+
button.click();
|
|
37
|
+
expect(callback).toHaveBeenCalledTimes(1); // Should not increase
|
|
38
|
+
});
|
|
39
|
+
it('should work with different event types', () => {
|
|
40
|
+
const input = document.createElement('input');
|
|
41
|
+
const changes = fromEvent(input, 'change');
|
|
42
|
+
const callback = jest.fn();
|
|
43
|
+
changes.subscribe(callback);
|
|
44
|
+
input.dispatchEvent(new Event('change'));
|
|
45
|
+
expect(callback).toHaveBeenCalledWith(expect.objectContaining({ type: 'change' }));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('map', () => {
|
|
49
|
+
it('should transform stream values', () => {
|
|
50
|
+
const numbers = signal(5);
|
|
51
|
+
const doubled = map(numbers, (n) => n * 2);
|
|
52
|
+
expect(doubled.value).toBe(10);
|
|
53
|
+
numbers.set(10);
|
|
54
|
+
expect(doubled.value).toBe(20);
|
|
55
|
+
});
|
|
56
|
+
it('should notify subscribers when mapped value changes', () => {
|
|
57
|
+
const numbers = signal(5);
|
|
58
|
+
const doubled = map(numbers, (n) => n * 2);
|
|
59
|
+
const callback = jest.fn();
|
|
60
|
+
doubled.subscribe(callback);
|
|
61
|
+
numbers.set(10);
|
|
62
|
+
expect(callback).toHaveBeenCalledWith(20);
|
|
63
|
+
});
|
|
64
|
+
it('should work with complex transformations', () => {
|
|
65
|
+
const button = document.createElement('button');
|
|
66
|
+
const clicks = fromEvent(button, 'click');
|
|
67
|
+
const positions = map(clicks, (e) => (e ? e.clientX : 0));
|
|
68
|
+
expect(positions.value).toBe(0);
|
|
69
|
+
button.dispatchEvent(new MouseEvent('click', { clientX: 100 }));
|
|
70
|
+
expect(positions.value).toBe(100);
|
|
71
|
+
});
|
|
72
|
+
it('should cleanup subscriptions on cleanup()', () => {
|
|
73
|
+
const numbers = signal(5);
|
|
74
|
+
const doubled = map(numbers, (n) => n * 2);
|
|
75
|
+
const callback = jest.fn();
|
|
76
|
+
doubled.subscribe(callback);
|
|
77
|
+
cleanup(doubled);
|
|
78
|
+
numbers.set(10);
|
|
79
|
+
expect(callback).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('filter', () => {
|
|
83
|
+
it('should filter stream values by predicate', () => {
|
|
84
|
+
const numbers = signal(5);
|
|
85
|
+
const evens = filter(numbers, (n) => n % 2 === 0);
|
|
86
|
+
expect(evens.value).toBeNull(); // 5 is odd
|
|
87
|
+
numbers.set(6);
|
|
88
|
+
expect(evens.value).toBe(6); // 6 is even
|
|
89
|
+
numbers.set(7);
|
|
90
|
+
expect(evens.value).toBeNull(); // 7 is odd
|
|
91
|
+
});
|
|
92
|
+
it('should emit null for filtered values', () => {
|
|
93
|
+
const numbers = signal(2);
|
|
94
|
+
const evens = filter(numbers, (n) => n % 2 === 0);
|
|
95
|
+
const values = [];
|
|
96
|
+
evens.subscribe((v) => values.push(v));
|
|
97
|
+
numbers.set(3); // Odd - should emit null
|
|
98
|
+
numbers.set(4); // Even - should emit 4
|
|
99
|
+
numbers.set(5); // Odd - should emit null
|
|
100
|
+
expect(values).toEqual([null, 4, null]);
|
|
101
|
+
});
|
|
102
|
+
it('should start with correct initial value', () => {
|
|
103
|
+
const evens1 = filter(signal(2), (n) => n % 2 === 0);
|
|
104
|
+
expect(evens1.value).toBe(2);
|
|
105
|
+
const evens2 = filter(signal(3), (n) => n % 2 === 0);
|
|
106
|
+
expect(evens2.value).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
it('should cleanup subscriptions on cleanup()', () => {
|
|
109
|
+
const numbers = signal(2);
|
|
110
|
+
const evens = filter(numbers, (n) => n % 2 === 0);
|
|
111
|
+
const callback = jest.fn();
|
|
112
|
+
evens.subscribe(callback);
|
|
113
|
+
cleanup(evens);
|
|
114
|
+
numbers.set(4);
|
|
115
|
+
expect(callback).not.toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('debounce', () => {
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
jest.useFakeTimers();
|
|
121
|
+
});
|
|
122
|
+
afterEach(() => {
|
|
123
|
+
jest.useRealTimers();
|
|
124
|
+
});
|
|
125
|
+
it('should delay updates', () => {
|
|
126
|
+
const numbers = signal(0);
|
|
127
|
+
const debounced = debounce(numbers, 300);
|
|
128
|
+
const callback = jest.fn();
|
|
129
|
+
debounced.subscribe(callback);
|
|
130
|
+
numbers.set(1);
|
|
131
|
+
expect(callback).not.toHaveBeenCalled();
|
|
132
|
+
jest.advanceTimersByTime(200);
|
|
133
|
+
expect(callback).not.toHaveBeenCalled();
|
|
134
|
+
jest.advanceTimersByTime(100);
|
|
135
|
+
expect(callback).toHaveBeenCalledWith(1);
|
|
136
|
+
});
|
|
137
|
+
it('should reset timer on new update', () => {
|
|
138
|
+
const numbers = signal(0);
|
|
139
|
+
const debounced = debounce(numbers, 300);
|
|
140
|
+
const callback = jest.fn();
|
|
141
|
+
debounced.subscribe(callback);
|
|
142
|
+
numbers.set(1);
|
|
143
|
+
jest.advanceTimersByTime(200);
|
|
144
|
+
numbers.set(2); // Reset timer
|
|
145
|
+
jest.advanceTimersByTime(200);
|
|
146
|
+
expect(callback).not.toHaveBeenCalled();
|
|
147
|
+
jest.advanceTimersByTime(100);
|
|
148
|
+
expect(callback).toHaveBeenCalledWith(2);
|
|
149
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
150
|
+
});
|
|
151
|
+
it('should emit latest value after delay', () => {
|
|
152
|
+
const numbers = signal(0);
|
|
153
|
+
const debounced = debounce(numbers, 100);
|
|
154
|
+
const values = [];
|
|
155
|
+
debounced.subscribe((v) => values.push(v));
|
|
156
|
+
numbers.set(1);
|
|
157
|
+
numbers.set(2);
|
|
158
|
+
numbers.set(3);
|
|
159
|
+
jest.advanceTimersByTime(100);
|
|
160
|
+
expect(values).toEqual([3]); // Only last value
|
|
161
|
+
});
|
|
162
|
+
it('should cleanup timeout on cleanup()', () => {
|
|
163
|
+
const numbers = signal(0);
|
|
164
|
+
const debounced = debounce(numbers, 300);
|
|
165
|
+
const callback = jest.fn();
|
|
166
|
+
debounced.subscribe(callback);
|
|
167
|
+
numbers.set(1);
|
|
168
|
+
cleanup(debounced);
|
|
169
|
+
jest.advanceTimersByTime(300);
|
|
170
|
+
expect(callback).not.toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
it('should have initial value', () => {
|
|
173
|
+
const numbers = signal(5);
|
|
174
|
+
const debounced = debounce(numbers, 300);
|
|
175
|
+
expect(debounced.value).toBe(5);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe('throttle', () => {
|
|
179
|
+
beforeEach(() => {
|
|
180
|
+
jest.useFakeTimers();
|
|
181
|
+
});
|
|
182
|
+
afterEach(() => {
|
|
183
|
+
jest.useRealTimers();
|
|
184
|
+
});
|
|
185
|
+
it('should emit immediately on first update', () => {
|
|
186
|
+
const numbers = signal(0);
|
|
187
|
+
const throttled = throttle(numbers, 1000);
|
|
188
|
+
const callback = jest.fn();
|
|
189
|
+
throttled.subscribe(callback);
|
|
190
|
+
numbers.set(1);
|
|
191
|
+
expect(callback).toHaveBeenCalledWith(1);
|
|
192
|
+
});
|
|
193
|
+
it('should throttle subsequent updates', () => {
|
|
194
|
+
const numbers = signal(0);
|
|
195
|
+
const throttled = throttle(numbers, 1000);
|
|
196
|
+
const values = [];
|
|
197
|
+
throttled.subscribe((v) => values.push(v));
|
|
198
|
+
numbers.set(1); // Immediate
|
|
199
|
+
numbers.set(2); // Too soon, scheduled
|
|
200
|
+
numbers.set(3); // Too soon, scheduled (replaces 2)
|
|
201
|
+
jest.advanceTimersByTime(1000);
|
|
202
|
+
expect(values).toEqual([1, 3]); // 1 immediate, 3 after throttle
|
|
203
|
+
});
|
|
204
|
+
it('should allow emission after throttle period', () => {
|
|
205
|
+
const numbers = signal(0);
|
|
206
|
+
const throttled = throttle(numbers, 100);
|
|
207
|
+
const values = [];
|
|
208
|
+
throttled.subscribe((v) => values.push(v));
|
|
209
|
+
numbers.set(1); // Immediate
|
|
210
|
+
jest.advanceTimersByTime(100);
|
|
211
|
+
numbers.set(2); // Enough time passed, immediate
|
|
212
|
+
expect(values).toEqual([1, 2]);
|
|
213
|
+
});
|
|
214
|
+
it('should cleanup timeout on cleanup()', () => {
|
|
215
|
+
const numbers = signal(0);
|
|
216
|
+
const throttled = throttle(numbers, 1000);
|
|
217
|
+
const callback = jest.fn();
|
|
218
|
+
throttled.subscribe(callback);
|
|
219
|
+
numbers.set(1);
|
|
220
|
+
numbers.set(2);
|
|
221
|
+
cleanup(throttled);
|
|
222
|
+
jest.advanceTimersByTime(1000);
|
|
223
|
+
expect(callback).toHaveBeenCalledTimes(1); // Only immediate call
|
|
224
|
+
});
|
|
225
|
+
it('should have initial value', () => {
|
|
226
|
+
const numbers = signal(5);
|
|
227
|
+
const throttled = throttle(numbers, 1000);
|
|
228
|
+
expect(throttled.value).toBe(5);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
describe('stream composition', () => {
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
jest.useFakeTimers();
|
|
234
|
+
});
|
|
235
|
+
afterEach(() => {
|
|
236
|
+
jest.useRealTimers();
|
|
237
|
+
});
|
|
238
|
+
it('should compose map + filter', () => {
|
|
239
|
+
const numbers = signal(1);
|
|
240
|
+
const doubled = map(numbers, (n) => n * 2);
|
|
241
|
+
const evens = filter(doubled, (n) => n > 5);
|
|
242
|
+
expect(evens.value).toBeNull(); // 2 is not > 5
|
|
243
|
+
numbers.set(3);
|
|
244
|
+
expect(evens.value).toBe(6); // 6 is > 5
|
|
245
|
+
numbers.set(2);
|
|
246
|
+
expect(evens.value).toBeNull(); // 4 is not > 5
|
|
247
|
+
});
|
|
248
|
+
it('should compose map + debounce', () => {
|
|
249
|
+
const numbers = signal(0);
|
|
250
|
+
const doubled = map(numbers, (n) => n * 2);
|
|
251
|
+
const debounced = debounce(doubled, 100);
|
|
252
|
+
const values = [];
|
|
253
|
+
debounced.subscribe((v) => values.push(v));
|
|
254
|
+
numbers.set(1);
|
|
255
|
+
numbers.set(2);
|
|
256
|
+
numbers.set(3);
|
|
257
|
+
jest.advanceTimersByTime(100);
|
|
258
|
+
expect(values).toEqual([6]); // 3 * 2 = 6
|
|
259
|
+
});
|
|
260
|
+
it('should compose fromEvent + map + filter + debounce', () => {
|
|
261
|
+
const button = document.createElement('button');
|
|
262
|
+
const clicks = fromEvent(button, 'click');
|
|
263
|
+
const positions = map(clicks, (e) => (e ? e.clientX : 0));
|
|
264
|
+
const filtered = filter(positions, (x) => x > 50);
|
|
265
|
+
const debounced = debounce(filtered, 200);
|
|
266
|
+
const values = [];
|
|
267
|
+
debounced.subscribe((v) => values.push(v));
|
|
268
|
+
button.dispatchEvent(new MouseEvent('click', { clientX: 100 }));
|
|
269
|
+
button.dispatchEvent(new MouseEvent('click', { clientX: 30 })); // Filtered
|
|
270
|
+
button.dispatchEvent(new MouseEvent('click', { clientX: 75 }));
|
|
271
|
+
jest.advanceTimersByTime(200);
|
|
272
|
+
expect(values).toEqual([75]);
|
|
273
|
+
cleanup(clicks);
|
|
274
|
+
cleanup(positions);
|
|
275
|
+
cleanup(filtered);
|
|
276
|
+
cleanup(debounced);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { bridgeMediaEvents, bridgeMediaEventsWithHandler } from '../media-event-bridge';
|
|
2
|
+
import { createWaveSurferState } from '../../state/wavesurfer-state';
|
|
3
|
+
describe('media-event-bridge', () => {
|
|
4
|
+
let media;
|
|
5
|
+
let actions;
|
|
6
|
+
let state;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
media = document.createElement('audio');
|
|
9
|
+
const stateAndActions = createWaveSurferState();
|
|
10
|
+
actions = stateAndActions.actions;
|
|
11
|
+
state = stateAndActions.state;
|
|
12
|
+
});
|
|
13
|
+
describe('bridgeMediaEvents', () => {
|
|
14
|
+
it('should create a bridge and return cleanup function', () => {
|
|
15
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
16
|
+
expect(typeof cleanup).toBe('function');
|
|
17
|
+
cleanup();
|
|
18
|
+
});
|
|
19
|
+
it('should update playing state on play event', () => {
|
|
20
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
21
|
+
expect(state.isPlaying.value).toBe(false);
|
|
22
|
+
media.dispatchEvent(new Event('play'));
|
|
23
|
+
expect(state.isPlaying.value).toBe(true);
|
|
24
|
+
cleanup();
|
|
25
|
+
});
|
|
26
|
+
it('should update playing state on pause event', () => {
|
|
27
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
28
|
+
actions.setPlaying(true);
|
|
29
|
+
expect(state.isPlaying.value).toBe(true);
|
|
30
|
+
media.dispatchEvent(new Event('pause'));
|
|
31
|
+
expect(state.isPlaying.value).toBe(false);
|
|
32
|
+
cleanup();
|
|
33
|
+
});
|
|
34
|
+
it('should update playing state and time on ended event', () => {
|
|
35
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
36
|
+
// Mock duration
|
|
37
|
+
Object.defineProperty(media, 'duration', {
|
|
38
|
+
value: 100,
|
|
39
|
+
writable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
});
|
|
42
|
+
actions.setPlaying(true);
|
|
43
|
+
actions.setDuration(100);
|
|
44
|
+
media.dispatchEvent(new Event('ended'));
|
|
45
|
+
expect(state.isPlaying.value).toBe(false);
|
|
46
|
+
expect(state.currentTime.value).toBe(100);
|
|
47
|
+
cleanup();
|
|
48
|
+
});
|
|
49
|
+
it('should update current time on timeupdate event', () => {
|
|
50
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
51
|
+
Object.defineProperty(media, 'currentTime', {
|
|
52
|
+
value: 42,
|
|
53
|
+
writable: true,
|
|
54
|
+
configurable: true,
|
|
55
|
+
});
|
|
56
|
+
media.dispatchEvent(new Event('timeupdate'));
|
|
57
|
+
expect(state.currentTime.value).toBe(42);
|
|
58
|
+
cleanup();
|
|
59
|
+
});
|
|
60
|
+
it('should update duration on durationchange event', () => {
|
|
61
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
62
|
+
Object.defineProperty(media, 'duration', {
|
|
63
|
+
value: 120,
|
|
64
|
+
writable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
});
|
|
67
|
+
media.dispatchEvent(new Event('durationchange'));
|
|
68
|
+
expect(state.duration.value).toBe(120);
|
|
69
|
+
cleanup();
|
|
70
|
+
});
|
|
71
|
+
it('should update duration on loadedmetadata event', () => {
|
|
72
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
73
|
+
Object.defineProperty(media, 'duration', {
|
|
74
|
+
value: 150,
|
|
75
|
+
writable: true,
|
|
76
|
+
configurable: true,
|
|
77
|
+
});
|
|
78
|
+
media.dispatchEvent(new Event('loadedmetadata'));
|
|
79
|
+
expect(state.duration.value).toBe(150);
|
|
80
|
+
cleanup();
|
|
81
|
+
});
|
|
82
|
+
it('should not update duration if not finite', () => {
|
|
83
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
84
|
+
Object.defineProperty(media, 'duration', {
|
|
85
|
+
value: Infinity,
|
|
86
|
+
writable: true,
|
|
87
|
+
configurable: true,
|
|
88
|
+
});
|
|
89
|
+
media.dispatchEvent(new Event('durationchange'));
|
|
90
|
+
expect(state.duration.value).toBe(0); // Should remain initial value
|
|
91
|
+
cleanup();
|
|
92
|
+
});
|
|
93
|
+
it('should update seeking state on seeking event', () => {
|
|
94
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
95
|
+
expect(state.isSeeking.value).toBe(false);
|
|
96
|
+
media.dispatchEvent(new Event('seeking'));
|
|
97
|
+
expect(state.isSeeking.value).toBe(true);
|
|
98
|
+
cleanup();
|
|
99
|
+
});
|
|
100
|
+
it('should update seeking state on seeked event', () => {
|
|
101
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
102
|
+
actions.setSeeking(true);
|
|
103
|
+
expect(state.isSeeking.value).toBe(true);
|
|
104
|
+
media.dispatchEvent(new Event('seeked'));
|
|
105
|
+
expect(state.isSeeking.value).toBe(false);
|
|
106
|
+
cleanup();
|
|
107
|
+
});
|
|
108
|
+
it('should update volume on volumechange event', () => {
|
|
109
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
110
|
+
Object.defineProperty(media, 'volume', {
|
|
111
|
+
value: 0.5,
|
|
112
|
+
writable: true,
|
|
113
|
+
configurable: true,
|
|
114
|
+
});
|
|
115
|
+
media.dispatchEvent(new Event('volumechange'));
|
|
116
|
+
expect(state.volume.value).toBe(0.5);
|
|
117
|
+
cleanup();
|
|
118
|
+
});
|
|
119
|
+
it('should update playback rate on ratechange event', () => {
|
|
120
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
121
|
+
Object.defineProperty(media, 'playbackRate', {
|
|
122
|
+
value: 1.5,
|
|
123
|
+
writable: true,
|
|
124
|
+
configurable: true,
|
|
125
|
+
});
|
|
126
|
+
media.dispatchEvent(new Event('ratechange'));
|
|
127
|
+
expect(state.playbackRate.value).toBe(1.5);
|
|
128
|
+
cleanup();
|
|
129
|
+
});
|
|
130
|
+
it('should cleanup all event listeners', () => {
|
|
131
|
+
const addSpy = jest.spyOn(media, 'addEventListener');
|
|
132
|
+
const removeSpy = jest.spyOn(media, 'removeEventListener');
|
|
133
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
134
|
+
const addCallCount = addSpy.mock.calls.length;
|
|
135
|
+
expect(addCallCount).toBeGreaterThan(0);
|
|
136
|
+
cleanup();
|
|
137
|
+
expect(removeSpy).toHaveBeenCalledTimes(addCallCount);
|
|
138
|
+
addSpy.mockRestore();
|
|
139
|
+
removeSpy.mockRestore();
|
|
140
|
+
});
|
|
141
|
+
it('should handle multiple events in sequence', () => {
|
|
142
|
+
const cleanup = bridgeMediaEvents(media, actions);
|
|
143
|
+
// Mock properties
|
|
144
|
+
Object.defineProperty(media, 'duration', {
|
|
145
|
+
value: 100,
|
|
146
|
+
writable: true,
|
|
147
|
+
configurable: true,
|
|
148
|
+
});
|
|
149
|
+
Object.defineProperty(media, 'currentTime', {
|
|
150
|
+
value: 0,
|
|
151
|
+
writable: true,
|
|
152
|
+
configurable: true,
|
|
153
|
+
});
|
|
154
|
+
// Simulate playback sequence
|
|
155
|
+
media.dispatchEvent(new Event('loadedmetadata'));
|
|
156
|
+
expect(state.duration.value).toBe(100);
|
|
157
|
+
media.dispatchEvent(new Event('play'));
|
|
158
|
+
expect(state.isPlaying.value).toBe(true);
|
|
159
|
+
Object.defineProperty(media, 'currentTime', { value: 50 });
|
|
160
|
+
media.dispatchEvent(new Event('timeupdate'));
|
|
161
|
+
expect(state.currentTime.value).toBe(50);
|
|
162
|
+
media.dispatchEvent(new Event('pause'));
|
|
163
|
+
expect(state.isPlaying.value).toBe(false);
|
|
164
|
+
cleanup();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('bridgeMediaEventsWithHandler', () => {
|
|
168
|
+
it('should call handler for media events', () => {
|
|
169
|
+
const handler = jest.fn();
|
|
170
|
+
const cleanup = bridgeMediaEventsWithHandler(media, handler);
|
|
171
|
+
media.dispatchEvent(new Event('play'));
|
|
172
|
+
expect(handler).toHaveBeenCalledWith('play', expect.any(Event));
|
|
173
|
+
media.dispatchEvent(new Event('pause'));
|
|
174
|
+
expect(handler).toHaveBeenCalledWith('pause', expect.any(Event));
|
|
175
|
+
cleanup();
|
|
176
|
+
});
|
|
177
|
+
it('should handle multiple events', () => {
|
|
178
|
+
const events = [];
|
|
179
|
+
const handler = (event) => {
|
|
180
|
+
events.push(event);
|
|
181
|
+
};
|
|
182
|
+
const cleanup = bridgeMediaEventsWithHandler(media, handler);
|
|
183
|
+
media.dispatchEvent(new Event('loadstart'));
|
|
184
|
+
media.dispatchEvent(new Event('loadedmetadata'));
|
|
185
|
+
media.dispatchEvent(new Event('canplay'));
|
|
186
|
+
media.dispatchEvent(new Event('play'));
|
|
187
|
+
expect(events).toContain('loadstart');
|
|
188
|
+
expect(events).toContain('loadedmetadata');
|
|
189
|
+
expect(events).toContain('canplay');
|
|
190
|
+
expect(events).toContain('play');
|
|
191
|
+
cleanup();
|
|
192
|
+
});
|
|
193
|
+
it('should cleanup all listeners', () => {
|
|
194
|
+
const handler = jest.fn();
|
|
195
|
+
const removeSpy = jest.spyOn(media, 'removeEventListener');
|
|
196
|
+
const cleanup = bridgeMediaEventsWithHandler(media, handler);
|
|
197
|
+
cleanup();
|
|
198
|
+
expect(removeSpy.mock.calls.length).toBeGreaterThan(0);
|
|
199
|
+
removeSpy.mockRestore();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|