element-web/test/viewmodels/audio/AudioPlayerViewModel-test.tsx
Florian Duros b6710d19c0
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
2025-09-12 08:24:51 +00:00

68 lines
2.8 KiB
TypeScript

/*
* 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 { 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: Playback;
beforeEach(() => {
playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as Playback;
});
it("should return the snapshot", () => {
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
expect(vm.getSnapshot()).toMatchObject({
mediaName: "mediaName",
sizeBytes: 8000,
playbackState: "decoding",
durationSeconds: 50,
playedSeconds: 10,
percentComplete: 20,
error: false,
});
});
it("should toggle the playback state", async () => {
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
await vm.togglePlay();
expect(playback.toggle).toHaveBeenCalled();
});
it("should move the playback on seekbar change", async () => {
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
await vm.onSeekbarChange({ target: { value: "20" } } as ChangeEvent<HTMLInputElement>);
expect(playback.skipTo).toHaveBeenCalledWith(10); // 20% of 50 seconds
});
it("should has error=true when playback.prepare fails", async () => {
jest.spyOn(playback, "prepare").mockRejectedValue(new Error("Failed to prepare playback"));
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
await waitFor(() => expect(vm.getSnapshot().error).toBe(true));
});
it("should handle key down events", () => {
const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
let event = new KeyboardEvent("keydown", { key: " " }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
vm.onKeyDown(event);
expect(playback.toggle).toHaveBeenCalled();
event = new KeyboardEvent("keydown", { key: "ArrowLeft" }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
vm.onKeyDown(event);
expect(playback.skipTo).toHaveBeenCalledWith(10 - 5); // 5 seconds back
event = new KeyboardEvent("keydown", { key: "ArrowRight" }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
vm.onKeyDown(event);
expect(playback.skipTo).toHaveBeenCalledWith(10 + 5); // 5 seconds forward
});
});