Files
video_socket-server/client/test/unit/meeting-recorder.test.js

125 lines
3.8 KiB
JavaScript
Raw Normal View History

2026-05-25 22:58:11 +08:00
import { jest } from '@jest/globals';
2026-05-25 20:37:36 +08:00
import { MeetingRecorder } from '../../public/call/media/meeting-recorder.js';
2026-05-25 16:39:13 +08:00
class MediaStreamMock {
constructor(tracks = []) {
this.tracks = tracks;
}
getTracks() {
return this.tracks;
}
getAudioTracks() {
return this.tracks.filter(track => track.kind === 'audio');
}
getVideoTracks() {
return this.tracks.filter(track => track.kind === 'video');
}
}
class MediaRecorderMock {
static isTypeSupported() {
return true;
}
constructor(stream, options) {
this.stream = stream;
this.mimeType = options.mimeType;
this.state = 'inactive';
}
start() {
this.state = 'recording';
}
stop() {
this.state = 'inactive';
this.ondataavailable({ data: new Blob(['recording'], { type: this.mimeType }) });
this.onstop();
}
}
function createTrack(kind) {
return {
kind,
readyState: 'live',
stop: jest.fn(),
clone: jest.fn(() => createTrack(kind))
};
}
function createWindowMock({ mediaRecorder = MediaRecorderMock } = {}) {
return {
MediaRecorder: mediaRecorder,
MediaStream: MediaStreamMock,
URL: {
createObjectURL: jest.fn(() => 'blob:recording'),
revokeObjectURL: jest.fn()
},
requestAnimationFrame: jest.fn(() => 1),
cancelAnimationFrame: jest.fn(),
setTimeout: jest.fn((callback) => {
callback();
return 1;
})
};
}
describe('MeetingRecorder', () => {
beforeEach(() => {
HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
fillStyle: '',
font: '',
textAlign: '',
textBaseline: '',
fillRect: jest.fn(),
fillText: jest.fn(),
drawImage: jest.fn()
}));
HTMLCanvasElement.prototype.captureStream = jest.fn(() => new MediaStreamMock([createTrack('video')]));
});
afterEach(() => {
jest.restoreAllMocks();
document.body.innerHTML = '';
});
test('starts and stops recording a meeting file', async () => {
const windowRef = createWindowMock();
const recorder = new MeetingRecorder({ documentRef: document, windowRef });
const localVideo = document.createElement('video');
localVideo.id = 'localVideo';
localVideo.srcObject = new MediaStreamMock([createTrack('video')]);
Object.defineProperty(localVideo, 'readyState', { value: 2 });
Object.defineProperty(localVideo, 'videoWidth', { value: 640 });
Object.defineProperty(localVideo, 'videoHeight', { value: 360 });
localVideo.getBoundingClientRect = () => ({ width: 320, height: 180 });
document.body.appendChild(localVideo);
await recorder.start({
localStream: new MediaStreamMock([createTrack('audio')]),
connectionId: '123-456-789'
});
expect(recorder.isRecording()).toBe(true);
const result = await recorder.stop();
expect(result.filename).toContain('meeting-recording-123-456-789');
2026-05-25 22:58:11 +08:00
expect(result.mimeType.toLowerCase()).toBe('video/mp4;codecs=avc1.42e01e,mp4a.40.2');
2026-05-25 16:39:13 +08:00
expect(result.filename).toMatch(/\.mp4$/);
expect(windowRef.URL.createObjectURL).not.toHaveBeenCalled();
expect(recorder.isRecording()).toBe(false);
});
test('reports unsupported browsers', async () => {
HTMLCanvasElement.prototype.captureStream = undefined;
const windowRef = createWindowMock({ mediaRecorder: undefined });
const recorder = new MeetingRecorder({ documentRef: document, windowRef });
await expect(recorder.start()).rejects.toThrow('当前浏览器不支持会议录制');
});
});