Prevent voice message from displaying spurious errors (#30736)
* fix: avoid to render `AudioPlayerViewModel` when `MAudioBody` is inherited * fix: avoid `Playback.prepare` to fail when called twice * fix: add `decoding` to playback type * refactor: fix circular deps * refactor: extract `MockedPlayback` from `AudioPlayerViewModel` * test: add `MAudioBody` basic test * test: add tests for `MVoiceMessageBody` * fix: lint
This commit is contained in:
58
test/unit-tests/audio/MockedPlayback.ts
Normal file
58
test/unit-tests/audio/MockedPlayback.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import { SimpleObservable } from "matrix-widget-api";
|
||||
|
||||
import { PlaybackState } from "../../../src/audio/Playback";
|
||||
|
||||
/**
|
||||
* A mocked playback implementation for testing purposes.
|
||||
* It simulates a playback with a fixed size and allows state changes.
|
||||
*/
|
||||
export class MockedPlayback extends EventEmitter {
|
||||
public sizeBytes = 8000;
|
||||
private waveformObservable = new SimpleObservable<number[]>();
|
||||
public liveData = new SimpleObservable<number[]>();
|
||||
|
||||
public constructor(
|
||||
public currentState: PlaybackState,
|
||||
public durationSeconds: number,
|
||||
public timeSeconds: number,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public setState(state: PlaybackState): void {
|
||||
this.currentState = state;
|
||||
this.emit("update", state);
|
||||
}
|
||||
|
||||
public get isPlaying(): boolean {
|
||||
return this.currentState === PlaybackState.Playing;
|
||||
}
|
||||
|
||||
public get clockInfo() {
|
||||
return {
|
||||
liveData: this.liveData,
|
||||
populatePlaceholdersFrom: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public get waveform(): number[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public get waveformData(): SimpleObservable<number[]> {
|
||||
return this.waveformObservable;
|
||||
}
|
||||
|
||||
public prepare = jest.fn().mockResolvedValue(undefined);
|
||||
public skipTo = jest.fn();
|
||||
public toggle = jest.fn();
|
||||
public destroy = jest.fn().mockResolvedValue(undefined);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen, act } from "jest-matrix-react";
|
||||
|
||||
import { MockedPlayback } from "../../../audio/MockedPlayback";
|
||||
import { type Playback, PlaybackState } from "../../../../../src/audio/Playback";
|
||||
import MAudioBody from "../../../../../src/components/views/messages/MAudioBody";
|
||||
import { PlaybackManager } from "../../../../../src/audio/PlaybackManager";
|
||||
import { type MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
|
||||
describe("<MAudioBody />", () => {
|
||||
let event: MatrixEvent;
|
||||
beforeEach(() => {
|
||||
const playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as Playback;
|
||||
jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockReturnValue(playback);
|
||||
|
||||
event = new MatrixEvent({
|
||||
room_id: "!room:server",
|
||||
sender: "@alice.example.org",
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "audio name ",
|
||||
msgtype: "m.audio",
|
||||
url: "mxc://server/audio",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should render", async () => {
|
||||
const mediaEventHelper = {
|
||||
sourceBlob: {
|
||||
value: {
|
||||
arrayBuffer: () => new ArrayBuffer(8),
|
||||
},
|
||||
},
|
||||
} as unknown as MediaEventHelper;
|
||||
|
||||
await act(() => render(<MAudioBody mxEvent={event} mediaEventHelper={mediaEventHelper} />));
|
||||
expect(await screen.findByRole("region", { name: "Audio player" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { act, render, screen } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
|
||||
import { MockedPlayback } from "../../../audio/MockedPlayback";
|
||||
import { type Playback, PlaybackState } from "../../../../../src/audio/Playback";
|
||||
import { PlaybackManager } from "../../../../../src/audio/PlaybackManager";
|
||||
import type { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
import MVoiceMessageBody from "../../../../../src/components/views/messages/MVoiceMessageBody";
|
||||
import { PlaybackQueue } from "../../../../../src/audio/PlaybackQueue";
|
||||
import { createTestClient } from "../../../../test-utils";
|
||||
|
||||
describe("<MVvoiceMessageBody />", () => {
|
||||
let event: MatrixEvent;
|
||||
beforeEach(() => {
|
||||
const playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as Playback;
|
||||
jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockReturnValue(playback);
|
||||
|
||||
const matrixClient = createTestClient();
|
||||
const room = new Room("!TESTROOM", matrixClient, "@alice:example.org");
|
||||
const playbackQueue = new PlaybackQueue(room);
|
||||
|
||||
jest.spyOn(PlaybackQueue, "forRoom").mockReturnValue(playbackQueue);
|
||||
jest.spyOn(playbackQueue, "unsortedEnqueue").mockReturnValue(undefined);
|
||||
|
||||
event = new MatrixEvent({
|
||||
room_id: "!room:server",
|
||||
sender: "@alice.example.org",
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
"body": "audio name ",
|
||||
"msgtype": "m.audio",
|
||||
"url": "mxc://server/audio",
|
||||
"org.matrix.msc3946.voice": true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should render", async () => {
|
||||
const mediaEventHelper = {
|
||||
sourceBlob: {
|
||||
value: {
|
||||
arrayBuffer: () => new ArrayBuffer(8),
|
||||
},
|
||||
},
|
||||
} as unknown as MediaEventHelper;
|
||||
|
||||
await act(() => render(<MVoiceMessageBody mxEvent={event} mediaEventHelper={mediaEventHelper} />));
|
||||
expect(await screen.findByTestId("recording-playback")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -5,18 +5,17 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import { SimpleObservable } from "matrix-widget-api";
|
||||
import { type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import { type Playback, PlaybackState } from "../../../src/audio/Playback";
|
||||
import { AudioPlayerViewModel } from "../../../src/viewmodels/audio/AudioPlayerViewModel";
|
||||
import { MockedPlayback } from "../../unit-tests/audio/MockedPlayback";
|
||||
|
||||
describe("AudioPlayerViewModel", () => {
|
||||
let playback: MockedPlayback & Playback;
|
||||
let playback: Playback;
|
||||
beforeEach(() => {
|
||||
playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as MockedPlayback & Playback;
|
||||
playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as Playback;
|
||||
});
|
||||
|
||||
it("should return the snapshot", () => {
|
||||
@@ -66,38 +65,3 @@ describe("AudioPlayerViewModel", () => {
|
||||
expect(playback.skipTo).toHaveBeenCalledWith(10 + 5); // 5 seconds forward
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A mocked playback implementation for testing purposes.
|
||||
* It simulates a playback with a fixed size and allows state changes.
|
||||
*/
|
||||
class MockedPlayback extends EventEmitter {
|
||||
public sizeBytes = 8000;
|
||||
|
||||
public constructor(
|
||||
public currentState: PlaybackState,
|
||||
public durationSeconds: number,
|
||||
public timeSeconds: number,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public setState(state: PlaybackState): void {
|
||||
this.currentState = state;
|
||||
this.emit("update", state);
|
||||
}
|
||||
|
||||
public get isPlaying(): boolean {
|
||||
return this.currentState === PlaybackState.Playing;
|
||||
}
|
||||
|
||||
public get clockInfo() {
|
||||
return {
|
||||
liveData: new SimpleObservable(),
|
||||
};
|
||||
}
|
||||
|
||||
public prepare = jest.fn().mockResolvedValue(undefined);
|
||||
public skipTo = jest.fn();
|
||||
public toggle = jest.fn();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user