wavesurfer.js 7.9.5 → 7.9.7
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/__tests__/base-plugin.test.d.ts +1 -0
- package/dist/__tests__/base-plugin.test.js +29 -0
- package/dist/__tests__/dom.test.d.ts +1 -0
- package/dist/__tests__/dom.test.js +16 -0
- package/dist/__tests__/draggable.test.d.ts +1 -0
- package/dist/__tests__/draggable.test.js +50 -0
- package/dist/__tests__/event-emitter.test.d.ts +1 -0
- package/dist/__tests__/event-emitter.test.js +26 -0
- package/dist/__tests__/fetcher.test.d.ts +1 -0
- package/dist/__tests__/fetcher.test.js +40 -0
- package/dist/__tests__/player.test.d.ts +1 -0
- package/dist/__tests__/player.test.js +50 -0
- package/dist/__tests__/renderer.test.d.ts +6 -0
- package/dist/__tests__/renderer.test.js +180 -0
- package/dist/__tests__/timer.test.d.ts +1 -0
- package/dist/__tests__/timer.test.js +18 -0
- package/dist/__tests__/wavesurfer.test.d.ts +1 -0
- package/dist/__tests__/wavesurfer.test.js +227 -0
- package/dist/dom.js +8 -5
- package/dist/player.js +2 -2
- 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 +19 -0
- 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/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/spectrogram.cjs +1 -1
- package/dist/plugins/spectrogram.d.ts +2 -0
- package/dist/plugins/spectrogram.esm.js +1 -1
- package/dist/plugins/spectrogram.js +1 -1
- package/dist/plugins/spectrogram.min.js +1 -1
- package/dist/plugins/timeline.cjs +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/renderer.js +11 -4
- package/dist/types.d.ts +1 -1
- package/dist/wavesurfer.cjs +1 -1
- package/dist/wavesurfer.d.ts +1 -1
- package/dist/wavesurfer.esm.js +1 -1
- package/dist/wavesurfer.js +12 -4
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BasePlugin } from '../base-plugin.js';
|
|
2
|
+
class TestPlugin extends BasePlugin {
|
|
3
|
+
constructor() {
|
|
4
|
+
super(...arguments);
|
|
5
|
+
this.initCalled = false;
|
|
6
|
+
}
|
|
7
|
+
onInit() {
|
|
8
|
+
this.initCalled = true;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
describe('BasePlugin', () => {
|
|
12
|
+
test('_init calls onInit and sets wavesurfer', () => {
|
|
13
|
+
const plugin = new TestPlugin({});
|
|
14
|
+
const ws = {};
|
|
15
|
+
plugin._init(ws);
|
|
16
|
+
expect(plugin.wavesurfer).toBe(ws);
|
|
17
|
+
expect(plugin.initCalled).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
test('destroy emits destroy and unsubscribes', () => {
|
|
20
|
+
const plugin = new TestPlugin({});
|
|
21
|
+
const unsub = jest.fn();
|
|
22
|
+
plugin.subscriptions = [unsub];
|
|
23
|
+
const spy = jest.fn();
|
|
24
|
+
plugin.on('destroy', spy);
|
|
25
|
+
plugin.destroy();
|
|
26
|
+
expect(spy).toHaveBeenCalled();
|
|
27
|
+
expect(unsub).toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import createElement from '../dom.js';
|
|
2
|
+
describe('createElement', () => {
|
|
3
|
+
test('creates DOM structure', () => {
|
|
4
|
+
var _a;
|
|
5
|
+
const container = document.createElement('div');
|
|
6
|
+
const el = createElement('div', {
|
|
7
|
+
id: 'root',
|
|
8
|
+
children: {
|
|
9
|
+
span: { textContent: 'child' },
|
|
10
|
+
},
|
|
11
|
+
}, container);
|
|
12
|
+
expect(container.firstChild).toBe(el);
|
|
13
|
+
expect(el.id).toBe('root');
|
|
14
|
+
expect((_a = el.querySelector('span')) === null || _a === void 0 ? void 0 : _a.textContent).toBe('child');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { makeDraggable } from '../draggable.js';
|
|
2
|
+
describe('makeDraggable', () => {
|
|
3
|
+
beforeAll(() => {
|
|
4
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
5
|
+
writable: true,
|
|
6
|
+
value: jest.fn().mockReturnValue({
|
|
7
|
+
matches: false,
|
|
8
|
+
addListener: jest.fn(),
|
|
9
|
+
removeListener: jest.fn(),
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
if (typeof window.PointerEvent === 'undefined') {
|
|
13
|
+
class FakePointerEvent extends MouseEvent {
|
|
14
|
+
constructor(type, props) {
|
|
15
|
+
super(type, props);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
window.PointerEvent = FakePointerEvent;
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
global.PointerEvent = FakePointerEvent;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
test('invokes callbacks on drag', () => {
|
|
25
|
+
const el = document.createElement('div');
|
|
26
|
+
document.body.appendChild(el);
|
|
27
|
+
jest.spyOn(el, 'getBoundingClientRect').mockReturnValue({
|
|
28
|
+
left: 0,
|
|
29
|
+
top: 0,
|
|
30
|
+
width: 100,
|
|
31
|
+
height: 100,
|
|
32
|
+
right: 100,
|
|
33
|
+
bottom: 100,
|
|
34
|
+
x: 0,
|
|
35
|
+
y: 0,
|
|
36
|
+
toJSON: () => { },
|
|
37
|
+
});
|
|
38
|
+
const onDrag = jest.fn();
|
|
39
|
+
const onStart = jest.fn();
|
|
40
|
+
const onEnd = jest.fn();
|
|
41
|
+
const unsubscribe = makeDraggable(el, onDrag, onStart, onEnd, 0);
|
|
42
|
+
el.dispatchEvent(new PointerEvent('pointerdown', { clientX: 10, clientY: 10 }));
|
|
43
|
+
document.dispatchEvent(new PointerEvent('pointermove', { clientX: 20, clientY: 20 }));
|
|
44
|
+
document.dispatchEvent(new PointerEvent('pointerup', { clientX: 20, clientY: 20 }));
|
|
45
|
+
expect(onStart).toHaveBeenCalled();
|
|
46
|
+
expect(onDrag).toHaveBeenCalled();
|
|
47
|
+
expect(onEnd).toHaveBeenCalled();
|
|
48
|
+
unsubscribe();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import EventEmitter from '../event-emitter.js';
|
|
2
|
+
describe('EventEmitter', () => {
|
|
3
|
+
test('on and emit', () => {
|
|
4
|
+
const emitter = new EventEmitter();
|
|
5
|
+
const handler = jest.fn();
|
|
6
|
+
emitter.on('foo', handler);
|
|
7
|
+
emitter.emit('foo', 42);
|
|
8
|
+
expect(handler).toHaveBeenCalledWith(42);
|
|
9
|
+
});
|
|
10
|
+
test('once', () => {
|
|
11
|
+
const emitter = new EventEmitter();
|
|
12
|
+
const handler = jest.fn();
|
|
13
|
+
emitter.once('bar', handler);
|
|
14
|
+
emitter.emit('bar');
|
|
15
|
+
emitter.emit('bar');
|
|
16
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
17
|
+
});
|
|
18
|
+
test('unAll', () => {
|
|
19
|
+
const emitter = new EventEmitter();
|
|
20
|
+
const handler = jest.fn();
|
|
21
|
+
emitter.on('foo', handler);
|
|
22
|
+
emitter.unAll();
|
|
23
|
+
emitter.emit('foo', 1);
|
|
24
|
+
expect(handler).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import Fetcher from '../fetcher.js';
|
|
11
|
+
import { TextEncoder } from 'util';
|
|
12
|
+
import { Blob as NodeBlob } from 'buffer';
|
|
13
|
+
describe('Fetcher', () => {
|
|
14
|
+
test('fetchBlob returns blob and reports progress', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
15
|
+
const data = 'hello';
|
|
16
|
+
const reader = {
|
|
17
|
+
read: jest
|
|
18
|
+
.fn()
|
|
19
|
+
.mockResolvedValueOnce({ done: false, value: new TextEncoder().encode(data) })
|
|
20
|
+
.mockResolvedValueOnce({ done: true, value: undefined }),
|
|
21
|
+
};
|
|
22
|
+
const response = {
|
|
23
|
+
status: 200,
|
|
24
|
+
statusText: 'OK',
|
|
25
|
+
headers: new Headers({ 'Content-Length': data.length.toString() }),
|
|
26
|
+
body: { getReader: () => reader },
|
|
27
|
+
clone() {
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
blob: () => __awaiter(void 0, void 0, void 0, function* () { return new NodeBlob([data]); }),
|
|
31
|
+
};
|
|
32
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
33
|
+
const progress = jest.fn();
|
|
34
|
+
const blob = yield Fetcher.fetchBlob('url', progress);
|
|
35
|
+
expect(yield blob.text()).toBe(data);
|
|
36
|
+
// wait for watchProgress to process
|
|
37
|
+
yield new Promise(process.nextTick);
|
|
38
|
+
expect(progress).toHaveBeenCalledWith(100);
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import Player from '../player.js';
|
|
11
|
+
describe('Player', () => {
|
|
12
|
+
const createMedia = () => {
|
|
13
|
+
const media = document.createElement('audio');
|
|
14
|
+
media.play = jest.fn().mockResolvedValue(undefined);
|
|
15
|
+
media.pause = jest.fn();
|
|
16
|
+
media.setSinkId = jest.fn().mockResolvedValue(undefined);
|
|
17
|
+
return media;
|
|
18
|
+
};
|
|
19
|
+
test('play and pause', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
const media = createMedia();
|
|
21
|
+
const player = new Player({ media });
|
|
22
|
+
yield player.play();
|
|
23
|
+
expect(media.play).toHaveBeenCalled();
|
|
24
|
+
player.pause();
|
|
25
|
+
expect(media.pause).toHaveBeenCalled();
|
|
26
|
+
}));
|
|
27
|
+
test('volume and muted', () => {
|
|
28
|
+
const media = createMedia();
|
|
29
|
+
const player = new Player({ media });
|
|
30
|
+
player.setVolume(0.5);
|
|
31
|
+
expect(player.getVolume()).toBe(0.5);
|
|
32
|
+
player.setMuted(true);
|
|
33
|
+
expect(player.getMuted()).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
test('setTime clamps to duration', () => {
|
|
36
|
+
const media = createMedia();
|
|
37
|
+
Object.defineProperty(media, 'duration', { configurable: true, value: 10 });
|
|
38
|
+
const player = new Player({ media });
|
|
39
|
+
player.setTime(-1);
|
|
40
|
+
expect(player.getCurrentTime()).toBe(0);
|
|
41
|
+
player.setTime(11);
|
|
42
|
+
expect(player.getCurrentTime()).toBe(10);
|
|
43
|
+
});
|
|
44
|
+
test('setSinkId uses media method', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
const media = createMedia();
|
|
46
|
+
const player = new Player({ media });
|
|
47
|
+
yield player.setSinkId('id');
|
|
48
|
+
expect(media.setSinkId).toHaveBeenCalledWith('id');
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import Renderer from '../renderer.js';
|
|
11
|
+
const createAudioBuffer = (channels, duration = 1) => {
|
|
12
|
+
return {
|
|
13
|
+
duration,
|
|
14
|
+
length: channels[0].length,
|
|
15
|
+
sampleRate: channels[0].length / duration,
|
|
16
|
+
numberOfChannels: channels.length,
|
|
17
|
+
getChannelData: (i) => Float32Array.from(channels[i]),
|
|
18
|
+
copyFromChannel: jest.fn(),
|
|
19
|
+
copyToChannel: jest.fn(),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
describe('Renderer', () => {
|
|
23
|
+
let container;
|
|
24
|
+
let renderer;
|
|
25
|
+
const originalGetContext = window.HTMLCanvasElement.prototype.getContext;
|
|
26
|
+
const originalToDataURL = window.HTMLCanvasElement.prototype.toDataURL;
|
|
27
|
+
const originalToBlob = window.HTMLCanvasElement.prototype.toBlob;
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
Object.defineProperty(window, 'devicePixelRatio', { value: 1, writable: true });
|
|
30
|
+
window.HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
|
|
31
|
+
beginPath: jest.fn(),
|
|
32
|
+
rect: jest.fn(),
|
|
33
|
+
roundRect: jest.fn(),
|
|
34
|
+
moveTo: jest.fn(),
|
|
35
|
+
lineTo: jest.fn(),
|
|
36
|
+
closePath: jest.fn(),
|
|
37
|
+
fill: jest.fn(),
|
|
38
|
+
drawImage: jest.fn(),
|
|
39
|
+
fillRect: jest.fn(),
|
|
40
|
+
createLinearGradient: jest.fn(() => ({ addColorStop: jest.fn() })),
|
|
41
|
+
globalCompositeOperation: '',
|
|
42
|
+
canvas: { width: 100, height: 100 },
|
|
43
|
+
}));
|
|
44
|
+
window.HTMLCanvasElement.prototype.toDataURL = jest.fn(() => 'data:mock');
|
|
45
|
+
window.HTMLCanvasElement.prototype.toBlob = jest.fn(cb => cb(new Blob([''])));
|
|
46
|
+
});
|
|
47
|
+
afterAll(() => {
|
|
48
|
+
window.HTMLCanvasElement.prototype.getContext = originalGetContext;
|
|
49
|
+
window.HTMLCanvasElement.prototype.toDataURL = originalToDataURL;
|
|
50
|
+
window.HTMLCanvasElement.prototype.toBlob = originalToBlob;
|
|
51
|
+
});
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
container = document.createElement('div');
|
|
54
|
+
container.id = 'root';
|
|
55
|
+
document.body.appendChild(container);
|
|
56
|
+
renderer = new Renderer({ container });
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
renderer.destroy();
|
|
60
|
+
container.remove();
|
|
61
|
+
jest.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
test('parentFromOptionsContainer returns element and throws', () => {
|
|
64
|
+
expect(renderer.parentFromOptionsContainer(container)).toBe(container);
|
|
65
|
+
expect(renderer.parentFromOptionsContainer('#root')).toBe(container);
|
|
66
|
+
expect(() => renderer.parentFromOptionsContainer('#missing')).toThrow();
|
|
67
|
+
});
|
|
68
|
+
test('initHtml creates shadow root', () => {
|
|
69
|
+
const [el, shadow] = renderer.initHtml();
|
|
70
|
+
expect(el.shadowRoot).toBe(shadow);
|
|
71
|
+
expect(shadow.querySelector('.scroll')).not.toBeNull();
|
|
72
|
+
});
|
|
73
|
+
test('getHeight calculates values', () => {
|
|
74
|
+
;
|
|
75
|
+
renderer.audioData = { numberOfChannels: 2 };
|
|
76
|
+
expect(renderer.getHeight(undefined, undefined)).toBe(128);
|
|
77
|
+
expect(renderer.getHeight(50, undefined)).toBe(50);
|
|
78
|
+
container.style.height = '200px';
|
|
79
|
+
expect(renderer.getHeight('auto', [{ overlay: false }])).toBe(64);
|
|
80
|
+
});
|
|
81
|
+
test('createDelay resolves after time', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
+
jest.useFakeTimers();
|
|
83
|
+
const delay = renderer.createDelay(10);
|
|
84
|
+
const spy = jest.fn();
|
|
85
|
+
const p = delay().then(spy);
|
|
86
|
+
jest.advanceTimersByTime(10);
|
|
87
|
+
yield p;
|
|
88
|
+
expect(spy).toHaveBeenCalled();
|
|
89
|
+
jest.useRealTimers();
|
|
90
|
+
}));
|
|
91
|
+
test('convertColorValues supports gradients', () => {
|
|
92
|
+
const result = renderer.convertColorValues(['red', 'blue']);
|
|
93
|
+
expect(typeof result).toBe('object');
|
|
94
|
+
expect((renderer.convertColorValues('red'))).toBe('red');
|
|
95
|
+
});
|
|
96
|
+
test('getPixelRatio returns positive', () => {
|
|
97
|
+
window.devicePixelRatio = 2;
|
|
98
|
+
expect(renderer.getPixelRatio()).toBe(2);
|
|
99
|
+
});
|
|
100
|
+
test('renderBarWaveform and renderLineWaveform draw on context', () => {
|
|
101
|
+
const ctx = renderer.canvasWrapper.ownerDocument.createElement('canvas').getContext('2d');
|
|
102
|
+
const data = [new Float32Array([0, 0.5, -0.5]), new Float32Array([0, -0.5, 0.5])];
|
|
103
|
+
renderer.renderBarWaveform(data, {}, ctx, 1);
|
|
104
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
105
|
+
renderer.renderLineWaveform(data, {}, ctx, 1);
|
|
106
|
+
expect(ctx.lineTo).toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
test('renderWaveform chooses rendering path', () => {
|
|
109
|
+
const ctx = document.createElement('canvas').getContext('2d');
|
|
110
|
+
const data = [new Float32Array([0, 1])];
|
|
111
|
+
const spyBar = jest.spyOn(renderer, 'renderBarWaveform');
|
|
112
|
+
const spyLine = jest.spyOn(renderer, 'renderLineWaveform');
|
|
113
|
+
renderer.renderWaveform(data, { barWidth: 1 }, ctx);
|
|
114
|
+
expect(spyBar).toHaveBeenCalled();
|
|
115
|
+
renderer.renderWaveform(data, {}, ctx);
|
|
116
|
+
expect(spyLine).toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
test('renderSingleCanvas appends canvases', () => {
|
|
119
|
+
const canvasContainer = document.createElement('div');
|
|
120
|
+
const progressContainer = document.createElement('div');
|
|
121
|
+
const data = [new Float32Array([0, 1])];
|
|
122
|
+
renderer.renderSingleCanvas(data, {}, 10, 10, 0, canvasContainer, progressContainer);
|
|
123
|
+
expect(canvasContainer.querySelector('canvas')).not.toBeNull();
|
|
124
|
+
expect(progressContainer.querySelector('canvas')).not.toBeNull();
|
|
125
|
+
});
|
|
126
|
+
test('renderMultiCanvas draws and subscribes', () => {
|
|
127
|
+
const canvasContainer = document.createElement('div');
|
|
128
|
+
const progressContainer = document.createElement('div');
|
|
129
|
+
const data = [new Float32Array([0, 1])];
|
|
130
|
+
Object.defineProperty(renderer.scrollContainer, 'clientWidth', { configurable: true, value: 200 });
|
|
131
|
+
renderer.renderMultiCanvas(data, { barWidth: 1 }, 200, 10, canvasContainer, progressContainer);
|
|
132
|
+
expect(canvasContainer.querySelector('canvas')).not.toBeNull();
|
|
133
|
+
});
|
|
134
|
+
test('renderChannel creates containers', () => {
|
|
135
|
+
const data = [new Float32Array([0, 1])];
|
|
136
|
+
renderer.renderChannel(data, {}, 10, 0);
|
|
137
|
+
expect(renderer.canvasWrapper.children.length).toBeGreaterThan(0);
|
|
138
|
+
});
|
|
139
|
+
test('render processes audio buffer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
|
+
const buffer = createAudioBuffer([[0, 0.5, -0.5]]);
|
|
141
|
+
const spy = jest.fn();
|
|
142
|
+
renderer.on('render', spy);
|
|
143
|
+
yield renderer.render(buffer);
|
|
144
|
+
expect(spy).toHaveBeenCalled();
|
|
145
|
+
}));
|
|
146
|
+
test('reRender keeps scroll position', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
147
|
+
const buffer = createAudioBuffer([[0, 0.5, -0.5]]);
|
|
148
|
+
yield renderer.render(buffer);
|
|
149
|
+
renderer.setScroll(10);
|
|
150
|
+
renderer.reRender();
|
|
151
|
+
expect(renderer.getScroll()).toBe(10);
|
|
152
|
+
}));
|
|
153
|
+
test('zoom updates option', () => {
|
|
154
|
+
renderer.zoom(20);
|
|
155
|
+
expect(renderer.options.minPxPerSec).toBe(20);
|
|
156
|
+
});
|
|
157
|
+
test('scrollIntoView updates scroll', () => {
|
|
158
|
+
Object.defineProperty(renderer.scrollContainer, 'scrollWidth', { configurable: true, value: 100 });
|
|
159
|
+
Object.defineProperty(renderer.scrollContainer, 'clientWidth', { configurable: true, value: 50 });
|
|
160
|
+
renderer.renderProgress(0);
|
|
161
|
+
renderer.scrollIntoView(0.8);
|
|
162
|
+
expect(renderer.getScroll()).toBeGreaterThanOrEqual(0);
|
|
163
|
+
});
|
|
164
|
+
test('renderProgress updates styles', () => {
|
|
165
|
+
renderer.renderProgress(0.5);
|
|
166
|
+
expect(renderer.progressWrapper.style.width).toBe('50%');
|
|
167
|
+
});
|
|
168
|
+
test('exportImage returns data', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
const canvas = document.createElement('canvas');
|
|
170
|
+
renderer.canvasWrapper.appendChild(canvas);
|
|
171
|
+
const urls = yield renderer.exportImage('image/png', 1, 'dataURL');
|
|
172
|
+
expect(urls).toHaveLength(1);
|
|
173
|
+
const blobs = yield renderer.exportImage('image/png', 1, 'blob');
|
|
174
|
+
expect(blobs).toHaveLength(1);
|
|
175
|
+
}));
|
|
176
|
+
test('destroy cleans up', () => {
|
|
177
|
+
renderer.destroy();
|
|
178
|
+
expect(container.contains(renderer.getWrapper())).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Timer from '../timer.js';
|
|
2
|
+
describe('Timer', () => {
|
|
3
|
+
test('start schedules ticks', () => {
|
|
4
|
+
const timer = new Timer();
|
|
5
|
+
const tick = jest.fn();
|
|
6
|
+
timer.on('tick', tick);
|
|
7
|
+
const raf = jest
|
|
8
|
+
.fn()
|
|
9
|
+
.mockImplementationOnce((cb) => {
|
|
10
|
+
cb(0);
|
|
11
|
+
return 1;
|
|
12
|
+
})
|
|
13
|
+
.mockImplementation(() => 1);
|
|
14
|
+
global.requestAnimationFrame = raf;
|
|
15
|
+
timer.start();
|
|
16
|
+
expect(tick).toHaveBeenCalledTimes(2);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
jest.mock('../renderer.js', () => {
|
|
11
|
+
let lastInstance;
|
|
12
|
+
class Renderer {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.wrapper = document.createElement('div');
|
|
15
|
+
this.renderProgress = jest.fn();
|
|
16
|
+
this.on = jest.fn(() => () => undefined);
|
|
17
|
+
this.setOptions = jest.fn();
|
|
18
|
+
this.getWrapper = jest.fn(() => this.wrapper);
|
|
19
|
+
this.getWidth = jest.fn(() => 100);
|
|
20
|
+
this.getScroll = jest.fn(() => 0);
|
|
21
|
+
this.setScroll = jest.fn();
|
|
22
|
+
this.setScrollPercentage = jest.fn();
|
|
23
|
+
this.render = jest.fn();
|
|
24
|
+
this.zoom = jest.fn();
|
|
25
|
+
this.exportImage = jest.fn(() => []);
|
|
26
|
+
this.destroy = jest.fn();
|
|
27
|
+
this.options = options;
|
|
28
|
+
lastInstance = this;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { __esModule: true, default: Renderer, getLastInstance: () => lastInstance };
|
|
32
|
+
});
|
|
33
|
+
jest.mock('../timer.js', () => {
|
|
34
|
+
let lastInstance;
|
|
35
|
+
class Timer {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.on = jest.fn(() => () => undefined);
|
|
38
|
+
this.start = jest.fn();
|
|
39
|
+
this.stop = jest.fn();
|
|
40
|
+
this.destroy = jest.fn();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const ctor = jest.fn(() => {
|
|
44
|
+
lastInstance = new Timer();
|
|
45
|
+
return lastInstance;
|
|
46
|
+
});
|
|
47
|
+
return { __esModule: true, default: ctor, getLastInstance: () => lastInstance };
|
|
48
|
+
});
|
|
49
|
+
jest.mock('../decoder.js', () => {
|
|
50
|
+
const createBuffer = jest.fn((data, duration) => ({
|
|
51
|
+
duration,
|
|
52
|
+
numberOfChannels: data.length,
|
|
53
|
+
getChannelData: (i) => Float32Array.from(data[i]),
|
|
54
|
+
}));
|
|
55
|
+
return { __esModule: true, default: { decode: jest.fn(), createBuffer } };
|
|
56
|
+
});
|
|
57
|
+
import WaveSurfer from '../wavesurfer.js';
|
|
58
|
+
import { BasePlugin } from '../base-plugin.js';
|
|
59
|
+
import * as RendererModule from '../renderer.js';
|
|
60
|
+
import * as TimerModule from '../timer.js';
|
|
61
|
+
const getRenderer = RendererModule.getLastInstance;
|
|
62
|
+
const getTimer = TimerModule.getLastInstance;
|
|
63
|
+
const createMedia = () => {
|
|
64
|
+
const media = document.createElement('audio');
|
|
65
|
+
media.play = jest.fn().mockResolvedValue(undefined);
|
|
66
|
+
media.pause = jest.fn();
|
|
67
|
+
Object.defineProperty(media, 'duration', { configurable: true, value: 100, writable: true });
|
|
68
|
+
return media;
|
|
69
|
+
};
|
|
70
|
+
const createWs = (opts = {}) => {
|
|
71
|
+
const container = document.createElement('div');
|
|
72
|
+
return WaveSurfer.create(Object.assign({ container, media: createMedia() }, opts));
|
|
73
|
+
};
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
jest.clearAllMocks();
|
|
76
|
+
});
|
|
77
|
+
describe('WaveSurfer public methods', () => {
|
|
78
|
+
test('create returns instance', () => {
|
|
79
|
+
const ws = createWs();
|
|
80
|
+
expect(ws).toBeInstanceOf(WaveSurfer);
|
|
81
|
+
});
|
|
82
|
+
test('setOptions merges options and updates renderer', () => {
|
|
83
|
+
const ws = createWs();
|
|
84
|
+
ws.setOptions({ height: 200, audioRate: 2, mediaControls: true });
|
|
85
|
+
const renderer = getRenderer();
|
|
86
|
+
expect(ws.options.height).toBe(200);
|
|
87
|
+
expect(renderer.setOptions).toHaveBeenCalledWith(ws.options);
|
|
88
|
+
expect(ws.getPlaybackRate()).toBe(2);
|
|
89
|
+
expect(ws.getMediaElement().controls).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
test('registerPlugin adds and removes plugin', () => {
|
|
92
|
+
const ws = createWs();
|
|
93
|
+
class TestPlugin extends BasePlugin {
|
|
94
|
+
}
|
|
95
|
+
const plugin = new TestPlugin({});
|
|
96
|
+
ws.registerPlugin(plugin);
|
|
97
|
+
expect(ws.getActivePlugins()).toContain(plugin);
|
|
98
|
+
plugin.destroy();
|
|
99
|
+
expect(ws.getActivePlugins()).not.toContain(plugin);
|
|
100
|
+
});
|
|
101
|
+
test('wrapper and scroll helpers call renderer', () => {
|
|
102
|
+
const ws = createWs();
|
|
103
|
+
const renderer = getRenderer();
|
|
104
|
+
ws.getWrapper();
|
|
105
|
+
expect(renderer.getWrapper).toHaveBeenCalled();
|
|
106
|
+
ws.getWidth();
|
|
107
|
+
expect(renderer.getWidth).toHaveBeenCalled();
|
|
108
|
+
ws.getScroll();
|
|
109
|
+
expect(renderer.getScroll).toHaveBeenCalled();
|
|
110
|
+
ws.setScroll(42);
|
|
111
|
+
expect(renderer.setScroll).toHaveBeenCalledWith(42);
|
|
112
|
+
jest.spyOn(ws, 'getDuration').mockReturnValue(10);
|
|
113
|
+
ws.setScrollTime(5);
|
|
114
|
+
expect(renderer.setScrollPercentage).toHaveBeenCalledWith(0.5);
|
|
115
|
+
});
|
|
116
|
+
test('load and loadBlob call loadAudio', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
|
+
const ws = createWs();
|
|
118
|
+
const spy = jest.spyOn(ws, 'loadAudio').mockResolvedValue(undefined);
|
|
119
|
+
yield ws.load('url');
|
|
120
|
+
expect(spy).toHaveBeenCalledWith('url', undefined, undefined, undefined);
|
|
121
|
+
const blob = new Blob([]);
|
|
122
|
+
yield ws.loadBlob(blob);
|
|
123
|
+
expect(spy).toHaveBeenCalledWith('', blob, undefined, undefined);
|
|
124
|
+
}));
|
|
125
|
+
test('zoom requires decoded data', () => {
|
|
126
|
+
const ws = createWs();
|
|
127
|
+
expect(() => ws.zoom(10)).toThrow();
|
|
128
|
+
ws.decodedData = { duration: 1 };
|
|
129
|
+
ws.zoom(10);
|
|
130
|
+
expect(getRenderer().zoom).toHaveBeenCalledWith(10);
|
|
131
|
+
});
|
|
132
|
+
test('getDecodedData returns buffer', () => {
|
|
133
|
+
const ws = createWs();
|
|
134
|
+
ws.decodedData = 123;
|
|
135
|
+
expect(ws.getDecodedData()).toBe(123);
|
|
136
|
+
});
|
|
137
|
+
test('exportPeaks reads data from buffer', () => {
|
|
138
|
+
const ws = createWs();
|
|
139
|
+
ws.decodedData = {
|
|
140
|
+
numberOfChannels: 1,
|
|
141
|
+
getChannelData: () => new Float32Array([0, 1, -1]),
|
|
142
|
+
duration: 3,
|
|
143
|
+
};
|
|
144
|
+
const peaks = ws.exportPeaks({ maxLength: 3, precision: 100 });
|
|
145
|
+
expect(peaks[0]).toEqual([0, 1, -1]);
|
|
146
|
+
});
|
|
147
|
+
test('getDuration falls back to decoded data', () => {
|
|
148
|
+
const ws = createWs();
|
|
149
|
+
const media = ws.getMediaElement();
|
|
150
|
+
Object.defineProperty(media, 'duration', { configurable: true, value: Infinity });
|
|
151
|
+
ws.decodedData = { duration: 2 };
|
|
152
|
+
expect(ws.getDuration()).toBe(2);
|
|
153
|
+
});
|
|
154
|
+
test('toggleInteraction sets option', () => {
|
|
155
|
+
const ws = createWs();
|
|
156
|
+
ws.toggleInteraction(false);
|
|
157
|
+
expect(ws.options.interact).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
test('setTime updates progress and emits event', () => {
|
|
160
|
+
const ws = createWs();
|
|
161
|
+
const spy = jest.fn();
|
|
162
|
+
ws.on('timeupdate', spy);
|
|
163
|
+
ws.setTime(1);
|
|
164
|
+
expect(spy).toHaveBeenCalledWith(1);
|
|
165
|
+
expect(getRenderer().renderProgress).toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
test('seekTo calculates correct time', () => {
|
|
168
|
+
const ws = createWs();
|
|
169
|
+
jest.spyOn(ws, 'getDuration').mockReturnValue(10);
|
|
170
|
+
const setTimeSpy = jest.spyOn(ws, 'setTime');
|
|
171
|
+
ws.seekTo(0.5);
|
|
172
|
+
expect(setTimeSpy).toHaveBeenCalledWith(5);
|
|
173
|
+
});
|
|
174
|
+
test('play sets start and end', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
175
|
+
const ws = createWs();
|
|
176
|
+
const spy = jest.spyOn(ws, 'setTime');
|
|
177
|
+
yield ws.play(2, 4);
|
|
178
|
+
expect(spy).toHaveBeenCalledWith(2);
|
|
179
|
+
expect(ws.stopAtPosition).toBe(4);
|
|
180
|
+
}));
|
|
181
|
+
test('playPause toggles play and pause', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
+
const ws = createWs();
|
|
183
|
+
const media = ws.getMediaElement();
|
|
184
|
+
yield ws.playPause();
|
|
185
|
+
expect(media.play).toHaveBeenCalled();
|
|
186
|
+
Object.defineProperty(media, 'paused', { configurable: true, value: false });
|
|
187
|
+
yield ws.playPause();
|
|
188
|
+
expect(media.pause).toHaveBeenCalled();
|
|
189
|
+
}));
|
|
190
|
+
test('stop resets time', () => {
|
|
191
|
+
const ws = createWs();
|
|
192
|
+
ws.setTime(5);
|
|
193
|
+
ws.stop();
|
|
194
|
+
expect(ws.getCurrentTime()).toBe(0);
|
|
195
|
+
});
|
|
196
|
+
test('skip and empty', () => {
|
|
197
|
+
const ws = createWs();
|
|
198
|
+
ws.getMediaElement().currentTime = 1;
|
|
199
|
+
const spy = jest.spyOn(ws, 'setTime');
|
|
200
|
+
ws.skip(2);
|
|
201
|
+
expect(spy).toHaveBeenCalledWith(3);
|
|
202
|
+
const loadSpy = jest.spyOn(ws, 'load').mockResolvedValue(undefined);
|
|
203
|
+
ws.empty();
|
|
204
|
+
expect(loadSpy).toHaveBeenCalledWith('', [[0]], 0.001);
|
|
205
|
+
});
|
|
206
|
+
test('setMediaElement reinitializes events', () => {
|
|
207
|
+
const ws = createWs();
|
|
208
|
+
const unsub = jest.spyOn(ws, 'unsubscribePlayerEvents');
|
|
209
|
+
const init = jest.spyOn(ws, 'initPlayerEvents');
|
|
210
|
+
const el = createMedia();
|
|
211
|
+
ws.setMediaElement(el);
|
|
212
|
+
expect(unsub).toHaveBeenCalled();
|
|
213
|
+
expect(init).toHaveBeenCalled();
|
|
214
|
+
expect(ws.getMediaElement()).toBe(el);
|
|
215
|
+
});
|
|
216
|
+
test('exportImage uses renderer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
217
|
+
const ws = createWs();
|
|
218
|
+
yield ws.exportImage('image/png', 1, 'dataURL');
|
|
219
|
+
expect(getRenderer().exportImage).toHaveBeenCalled();
|
|
220
|
+
}));
|
|
221
|
+
test('destroy cleans up renderer and timer', () => {
|
|
222
|
+
const ws = createWs();
|
|
223
|
+
ws.destroy();
|
|
224
|
+
expect(getRenderer().destroy).toHaveBeenCalled();
|
|
225
|
+
expect(getTimer().destroy).toHaveBeenCalled();
|
|
226
|
+
});
|
|
227
|
+
});
|