element-web/test/audio/VoiceMessageRecording-test.ts

212 lines
8.0 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { mocked } from "jest-mock";
import { UploadOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
import { EncryptedFile } from "matrix-js-sdk/src/types";
import { createVoiceMessageRecording, VoiceMessageRecording } from "../../src/audio/VoiceMessageRecording";
import { RecordingState, VoiceRecording } from "../../src/audio/VoiceRecording";
import { uploadFile } from "../../src/ContentMessages";
import { stubClient } from "../test-utils";
import { Playback } from "../../src/audio/Playback";
jest.mock("../../src/ContentMessages", () => ({
uploadFile: jest.fn(),
}));
jest.mock("../../src/audio/Playback", () => ({
Playback: jest.fn(),
}));
describe("VoiceMessageRecording", () => {
const roomId = "!room:example.com";
const contentType = "test content type";
const durationSeconds = 23;
const testBuf = new Uint8Array([1, 2, 3]);
const testAmplitudes = [4, 5, 6];
let voiceRecording: VoiceRecording;
let voiceMessageRecording: VoiceMessageRecording;
let client: MatrixClient;
beforeEach(() => {
client = stubClient();
voiceRecording = {
contentType,
durationSeconds,
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
isRecording: true,
isSupported: true,
liveData: jest.fn(),
amplitudes: testAmplitudes,
} as unknown as VoiceRecording;
voiceMessageRecording = new VoiceMessageRecording(client, voiceRecording);
});
it("hasRecording should return false", () => {
expect(voiceMessageRecording.hasRecording).toBe(false);
});
it("createVoiceMessageRecording should return a VoiceMessageRecording", () => {
expect(createVoiceMessageRecording(client)).toBeInstanceOf(VoiceMessageRecording);
});
it("durationSeconds should return the VoiceRecording value", () => {
expect(voiceMessageRecording.durationSeconds).toBe(durationSeconds);
});
it("contentType should return the VoiceRecording value", () => {
expect(voiceMessageRecording.contentType).toBe(contentType);
});
it.each([true, false])("isRecording should return %s from VoiceRecording", (value: boolean) => {
// @ts-ignore
voiceRecording.isRecording = value;
expect(voiceMessageRecording.isRecording).toBe(value);
});
it.each([true, false])("isSupported should return %s from VoiceRecording", (value: boolean) => {
// @ts-ignore
voiceRecording.isSupported = value;
expect(voiceMessageRecording.isSupported).toBe(value);
});
it("should return liveData from VoiceRecording", () => {
expect(voiceMessageRecording.liveData).toBe(voiceRecording.liveData);
});
it("start should forward the call to VoiceRecording.start", async () => {
await voiceMessageRecording.start();
expect(voiceRecording.start).toHaveBeenCalled();
});
it("on should forward the call to VoiceRecording", () => {
const callback = () => {};
const result = voiceMessageRecording.on("test on", callback);
expect(voiceRecording.on).toHaveBeenCalledWith("test on", callback);
expect(result).toBe(voiceMessageRecording);
});
it("off should forward the call to VoiceRecording", () => {
const callback = () => {};
const result = voiceMessageRecording.off("test off", callback);
expect(voiceRecording.off).toHaveBeenCalledWith("test off", callback);
expect(result).toBe(voiceMessageRecording);
});
it("emit should forward the call to VoiceRecording", () => {
voiceMessageRecording.emit("test emit", 42);
expect(voiceRecording.emit).toHaveBeenCalledWith("test emit", 42);
});
it("upload should raise an error", async () => {
await expect(voiceMessageRecording.upload(roomId)).rejects.toThrow("No recording available to upload");
});
describe("when the first data has been received", () => {
const uploadUrl = "https://example.com/content123";
const encryptedFile = {} as unknown as EncryptedFile;
beforeEach(() => {
voiceRecording.onDataAvailable!(testBuf);
});
it("contentLength should return the buffer length", () => {
expect(voiceMessageRecording.contentLength).toBe(testBuf.length);
});
it("stop should return a copy of the data buffer", async () => {
const result = await voiceMessageRecording.stop();
expect(voiceRecording.stop).toHaveBeenCalled();
expect(result).toEqual(testBuf);
});
it("hasRecording should return true", () => {
expect(voiceMessageRecording.hasRecording).toBe(true);
});
describe("upload", () => {
let uploadFileClient: MatrixClient | null;
let uploadFileRoomId: string | null;
let uploadBlob: Blob | null;
beforeEach(() => {
uploadFileClient = null;
uploadFileRoomId = null;
uploadBlob = null;
mocked(uploadFile).mockImplementation(
(
matrixClient: MatrixClient,
roomId: string,
file: File | Blob,
_progressHandler?: UploadOpts["progressHandler"],
): Promise<{ url?: string; file?: EncryptedFile }> => {
uploadFileClient = matrixClient;
uploadFileRoomId = roomId;
uploadBlob = file;
// @ts-ignore
return Promise.resolve({
url: uploadUrl,
file: encryptedFile,
});
},
);
});
it("should upload the file and trigger the upload events", async () => {
const result = await voiceMessageRecording.upload(roomId);
expect(voiceRecording.emit).toHaveBeenNthCalledWith(1, RecordingState.Uploading);
expect(voiceRecording.emit).toHaveBeenNthCalledWith(2, RecordingState.Uploaded);
expect(result.mxc).toBe(uploadUrl);
expect(result.encrypted).toBe(encryptedFile);
expect(mocked(uploadFile)).toHaveBeenCalled();
expect(uploadFileClient).toBe(client);
expect(uploadFileRoomId).toBe(roomId);
expect(uploadBlob?.type).toBe(contentType);
const blobArray = await uploadBlob!.arrayBuffer();
expect(new Uint8Array(blobArray)).toEqual(testBuf);
});
it("should reuse the result", async () => {
const result1 = await voiceMessageRecording.upload(roomId);
const result2 = await voiceMessageRecording.upload(roomId);
expect(result1).toBe(result2);
});
});
describe("getPlayback", () => {
beforeEach(() => {
mocked(Playback).mockImplementation((buf: ArrayBuffer, seedWaveform): any => {
expect(new Uint8Array(buf)).toEqual(testBuf);
expect(seedWaveform).toEqual(testAmplitudes);
return {} as Playback;
});
});
it("should return a Playback with the data", () => {
voiceMessageRecording.getPlayback();
expect(mocked(Playback)).toHaveBeenCalled();
});
it("should reuse the result", () => {
const playback1 = voiceMessageRecording.getPlayback();
const playback2 = voiceMessageRecording.getPlayback();
expect(playback1).toBe(playback2);
});
});
});
});