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.
Files changed (86) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/memory-leaks.test.d.ts +7 -0
  3. package/dist/__tests__/memory-leaks.test.js +281 -0
  4. package/dist/__tests__/player.test.js +107 -0
  5. package/dist/__tests__/renderer-utils.test.js +55 -2
  6. package/dist/draggable.d.ts +4 -0
  7. package/dist/draggable.js +4 -0
  8. package/dist/player.d.ts +21 -0
  9. package/dist/player.js +86 -0
  10. package/dist/plugins/envelope.cjs +1 -1
  11. package/dist/plugins/envelope.esm.js +1 -1
  12. package/dist/plugins/envelope.js +1 -1
  13. package/dist/plugins/envelope.min.js +1 -1
  14. package/dist/plugins/hover.cjs +1 -1
  15. package/dist/plugins/hover.d.ts +0 -3
  16. package/dist/plugins/hover.esm.js +1 -1
  17. package/dist/plugins/hover.js +1 -1
  18. package/dist/plugins/hover.min.js +1 -1
  19. package/dist/plugins/minimap.cjs +1 -1
  20. package/dist/plugins/minimap.esm.js +1 -1
  21. package/dist/plugins/minimap.js +1 -1
  22. package/dist/plugins/minimap.min.js +1 -1
  23. package/dist/plugins/record.cjs +1 -1
  24. package/dist/plugins/record.esm.js +1 -1
  25. package/dist/plugins/record.js +1 -1
  26. package/dist/plugins/record.min.js +1 -1
  27. package/dist/plugins/regions.cjs +1 -1
  28. package/dist/plugins/regions.esm.js +1 -1
  29. package/dist/plugins/regions.js +1 -1
  30. package/dist/plugins/regions.min.js +1 -1
  31. package/dist/plugins/timeline.cjs +1 -1
  32. package/dist/plugins/timeline.d.ts +1 -1
  33. package/dist/plugins/timeline.esm.js +1 -1
  34. package/dist/plugins/timeline.js +1 -1
  35. package/dist/plugins/timeline.min.js +1 -1
  36. package/dist/plugins/zoom.cjs +1 -1
  37. package/dist/plugins/zoom.esm.js +1 -1
  38. package/dist/plugins/zoom.js +1 -1
  39. package/dist/plugins/zoom.min.js +1 -1
  40. package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
  41. package/dist/reactive/__tests__/drag-stream.test.js +199 -0
  42. package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
  43. package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
  44. package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
  45. package/dist/reactive/__tests__/event-streams.test.js +278 -0
  46. package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
  47. package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
  48. package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
  49. package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
  50. package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
  51. package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
  52. package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
  53. package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
  54. package/dist/reactive/__tests__/store.test.d.ts +1 -0
  55. package/dist/reactive/__tests__/store.test.js +271 -0
  56. package/dist/reactive/drag-stream.d.ts +48 -0
  57. package/dist/reactive/drag-stream.js +146 -0
  58. package/dist/reactive/event-stream-emitter.d.ts +104 -0
  59. package/dist/reactive/event-stream-emitter.js +151 -0
  60. package/dist/reactive/event-streams.d.ts +65 -0
  61. package/dist/reactive/event-streams.js +146 -0
  62. package/dist/reactive/media-event-bridge.d.ts +51 -0
  63. package/dist/reactive/media-event-bridge.js +152 -0
  64. package/dist/reactive/render-scheduler.d.ts +43 -0
  65. package/dist/reactive/render-scheduler.js +75 -0
  66. package/dist/reactive/scroll-stream.d.ts +93 -0
  67. package/dist/reactive/scroll-stream.js +131 -0
  68. package/dist/reactive/state-event-emitter.d.ts +105 -0
  69. package/dist/reactive/state-event-emitter.js +213 -0
  70. package/dist/reactive/store.d.ts +66 -0
  71. package/dist/reactive/store.js +115 -0
  72. package/dist/renderer-utils.d.ts +13 -4
  73. package/dist/renderer-utils.js +35 -15
  74. package/dist/renderer.d.ts +2 -1
  75. package/dist/renderer.js +51 -48
  76. package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
  77. package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
  78. package/dist/state/wavesurfer-state.d.ts +90 -0
  79. package/dist/state/wavesurfer-state.js +124 -0
  80. package/dist/types.d.ts +165 -1
  81. package/dist/wavesurfer.cjs +1 -1
  82. package/dist/wavesurfer.d.ts +16 -0
  83. package/dist/wavesurfer.esm.js +1 -1
  84. package/dist/wavesurfer.js +32 -0
  85. package/dist/wavesurfer.min.js +1 -1
  86. 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,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 {};