Skip to content

Commit 6efbbd0

Browse files
committed
feat: introduce possibility to limit audio playback to a single audio
1 parent 8d101e9 commit 6efbbd0

File tree

14 files changed

+1061
-436
lines changed

14 files changed

+1061
-436
lines changed

src/components/Attachment/__tests__/Audio.test.js

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ const renderComponent = (
5656
const playButton = () => screen.queryByTestId('play-audio');
5757
const pauseButton = () => screen.queryByTestId('pause-audio');
5858

59+
const clickToPlay = async () => {
60+
await act(async () => {
61+
await fireEvent.click(playButton());
62+
});
63+
};
64+
65+
const clickToPause = async () => {
66+
await act(async () => {
67+
await fireEvent.click(pauseButton());
68+
});
69+
};
70+
5971
const expectAddErrorMessage = (message) => {
6072
expect(addErrorSpy).toHaveBeenCalled();
6173
const hit = addErrorSpy.mock.calls.find((c) => c?.[0]?.message === message);
@@ -87,15 +99,16 @@ describe('Audio', () => {
8799
expect(container.querySelector('img')).not.toBeInTheDocument();
88100
});
89101

90-
it('creates a playback Audio() with the right src', () => {
102+
it('creates a playback Audio() with the right src only after clicked to play', async () => {
91103
renderComponent({ og: audioAttachment });
104+
await clickToPlay();
92105
expect(createdAudios.length).toBe(1);
93106
expect(createdAudios[0].src).toBe(audioAttachment.asset_url);
94107
});
95108

96109
it('shows the correct progress after clicking to the middle of a progress bar (seeking)', async () => {
97110
const { getByTestId } = renderComponent({ og: audioAttachment });
98-
111+
await clickToPlay();
99112
jest
100113
.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect')
101114
.mockImplementationOnce(() => ({ width: 120, x: 0 }));
@@ -119,19 +132,16 @@ describe('Audio', () => {
119132
it('shows the correct button if the song is paused/playing', async () => {
120133
renderComponent({ og: { ...audioAttachment } });
121134

122-
const audioPausedMock = jest.spyOn(createdAudios[0], 'paused', 'get');
123-
124135
expect(playButton()).toBeInTheDocument();
125136

126-
await act(async () => {
127-
await fireEvent.click(playButton());
128-
});
137+
await clickToPlay();
138+
const audioPausedMock = jest.spyOn(createdAudios[0], 'paused', 'get');
139+
129140
expect(pauseButton()).toBeInTheDocument();
130141

131142
audioPausedMock.mockReturnValueOnce(false);
132-
await act(async () => {
133-
await fireEvent.click(pauseButton());
134-
});
143+
144+
await clickToPause();
135145
expect(playButton()).toBeInTheDocument();
136146

137147
expect(addErrorSpy).not.toHaveBeenCalled();
@@ -143,20 +153,19 @@ describe('Audio', () => {
143153
renderComponent({
144154
og: audioAttachment,
145155
});
146-
const audio = createdAudios[0];
147-
audio.play.mockImplementationOnce(() => sleep(3000));
148-
149156
expect(playButton()).toBeInTheDocument();
150157
expect(pauseButton()).not.toBeInTheDocument();
158+
jest
159+
.spyOn(HTMLAudioElement.prototype, 'play')
160+
.mockImplementationOnce(() => sleep(3000));
161+
await clickToPlay();
151162

152-
await act(async () => {
153-
await fireEvent.click(playButton());
163+
await waitFor(() => {
164+
expect(playButton()).toBeInTheDocument();
165+
expect(pauseButton()).not.toBeInTheDocument();
154166
});
155-
expect(playButton()).toBeInTheDocument();
156-
expect(pauseButton()).not.toBeInTheDocument();
157167

158168
jest.advanceTimersByTime(2000);
159-
160169
await waitFor(() => {
161170
expect(playButton()).toBeInTheDocument();
162171
expect(pauseButton()).not.toBeInTheDocument();
@@ -169,18 +178,18 @@ describe('Audio', () => {
169178
it('registers error if pausing the audio after 2000ms of inactivity failed', async () => {
170179
jest.useFakeTimers('modern');
171180
renderComponent({ og: audioAttachment });
172-
const audio = createdAudios[0];
173-
audio.play.mockImplementationOnce(() => sleep(3000));
174-
audio.pause.mockImplementationOnce(() => {
181+
182+
jest
183+
.spyOn(HTMLAudioElement.prototype, 'play')
184+
.mockImplementationOnce(() => sleep(3000));
185+
jest.spyOn(HTMLAudioElement.prototype, 'pause').mockImplementationOnce(() => {
175186
throw new Error('');
176187
});
177188

178-
await act(() => {
179-
fireEvent.click(playButton());
180-
});
189+
await clickToPlay();
190+
181191
jest.advanceTimersByTime(2000);
182192
await waitFor(() => {
183-
expect(audio.pause).toHaveBeenCalledWith();
184193
expectAddErrorMessage('Failed to play the recording');
185194
});
186195

@@ -192,48 +201,37 @@ describe('Audio', () => {
192201
renderComponent({
193202
og: audioAttachment,
194203
});
195-
const audio = createdAudios[0];
196-
audio.play.mockRejectedValueOnce(new Error(errorText));
197-
const audioCanPlayTypeMock = jest
198-
.spyOn(audio, 'canPlayType')
204+
jest
205+
.spyOn(HTMLAudioElement.prototype, 'play')
206+
.mockRejectedValueOnce(new Error(errorText));
207+
const canPlaySpy = jest
208+
.spyOn(HTMLAudioElement.prototype, 'canPlayType')
199209
.mockReturnValue('maybe');
200210

201211
expect(playButton()).toBeInTheDocument();
202212
expect(pauseButton()).not.toBeInTheDocument();
203213

204-
await act(async () => {
205-
await fireEvent.click(playButton());
206-
});
214+
await clickToPlay();
207215
expect(playButton()).toBeInTheDocument();
208216
expect(pauseButton()).not.toBeInTheDocument();
209217
expectAddErrorMessage(errorText);
210-
audioCanPlayTypeMock.mockRestore();
218+
canPlaySpy.mockRestore();
211219
});
212220

213221
it('should register error if the audio MIME type is not playable', async () => {
214222
renderComponent({ og: { ...audioAttachment, mime_type: 'audio/mp4' } });
215-
const audio = createdAudios[0];
216-
const audioCanPlayTypeMock = jest.spyOn(audio, 'canPlayType').mockReturnValue('');
217-
218-
expect(audio.play).not.toHaveBeenCalled();
219-
220-
expect(playButton()).toBeInTheDocument();
221-
expect(pauseButton()).not.toBeInTheDocument();
223+
const spy = jest.spyOn(HTMLAudioElement.prototype, 'canPlayType').mockReturnValue('');
222224

223-
await act(async () => {
224-
await fireEvent.click(playButton());
225-
});
226-
expect(audio.play).not.toHaveBeenCalled();
225+
await clickToPlay();
227226
expect(playButton()).toBeInTheDocument();
228227
expect(pauseButton()).not.toBeInTheDocument();
229228
expectAddErrorMessage('Recording format is not supported and cannot be reproduced');
230-
231-
audioCanPlayTypeMock.mockRestore();
229+
spy.mockRestore();
232230
});
233231

234232
it('shows the correct progress on timeupdate', async () => {
235233
renderComponent({ og: audioAttachment });
236-
234+
await clickToPlay();
237235
const audio = createdAudios[0];
238236
jest.spyOn(audio, 'duration', 'get').mockReturnValue(100);
239237
jest.spyOn(audio, 'currentTime', 'get').mockReturnValue(50);
@@ -245,7 +243,7 @@ describe('Audio', () => {
245243
});
246244
});
247245

248-
it('differentiates between in thread and in channel audio player', () => {
246+
it('differentiates between in thread and in channel audio player', async () => {
249247
const message = generateMessage();
250248
render(
251249
<WithAudioPlayback>
@@ -257,10 +255,17 @@ describe('Audio', () => {
257255
</MessageProvider>
258256
</WithAudioPlayback>,
259257
);
258+
const playButtons = screen.queryAllByTestId('play-audio');
259+
expect(playButtons.length).toBe(2);
260+
await Promise.all(
261+
playButtons.map(async (button) => {
262+
await fireEvent.click(button);
263+
}),
264+
);
260265
expect(createdAudios).toHaveLength(2);
261266
});
262267

263-
it('keeps a single copy of audio player for the same requester', () => {
268+
it('keeps a single copy of audio player for the same requester', async () => {
264269
const message = generateMessage();
265270
render(
266271
<WithAudioPlayback>
@@ -272,6 +277,13 @@ describe('Audio', () => {
272277
</MessageProvider>
273278
</WithAudioPlayback>,
274279
);
280+
const playButtons = screen.queryAllByTestId('play-audio');
281+
expect(playButtons.length).toBe(2);
282+
await Promise.all(
283+
playButtons.map(async (button) => {
284+
await fireEvent.click(button);
285+
}),
286+
);
275287
expect(createdAudios).toHaveLength(1);
276288
});
277289
});

src/components/Attachment/__tests__/Card.test.js

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { cleanup, render, waitFor } from '@testing-library/react';
2+
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
33
import '@testing-library/jest-dom';
44

55
import { Card } from '../Card';
@@ -30,7 +30,9 @@ let chatClient;
3030
let channel;
3131
const user = generateUser({ id: 'userId', name: 'username' });
3232

33+
jest.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation();
3334
jest.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation();
35+
jest.spyOn(window.HTMLMediaElement.prototype, 'load').mockImplementation();
3436
const addNotificationSpy = jest.fn();
3537
const channelActionContext = { addNotification: addNotificationSpy };
3638

@@ -294,10 +296,12 @@ describe('Card', () => {
294296
},
295297
chatContext: { chatClient },
296298
});
297-
expect(getByText('theverge.com')).toBeInTheDocument();
299+
await waitFor(() => {
300+
expect(getByText('theverge.com')).toBeInTheDocument();
301+
});
298302
});
299303

300-
it('differentiates between in thread and in channel audio player', () => {
304+
it('differentiates between in thread and in channel audio player', async () => {
301305
const createdAudios = []; //HTMLAudioElement[]
302306
const RealAudio = window.Audio;
303307
const spy = jest.spyOn(window, 'Audio').mockImplementation(function AudioMock(
@@ -319,20 +323,33 @@ describe('Card', () => {
319323
const message = generateMessage();
320324

321325
render(
322-
<WithAudioPlayback>
323-
<MessageProvider value={{ message }}>
324-
<Card {...audioAttachment} />
325-
</MessageProvider>
326-
<MessageProvider value={{ message, threadList: true }}>
327-
<Card {...audioAttachment} />
328-
</MessageProvider>
329-
</WithAudioPlayback>,
326+
<ChatProvider value={{}}>
327+
<ChannelStateProvider value={{}}>
328+
<WithAudioPlayback>
329+
<MessageProvider value={{ message }}>
330+
<Card {...audioAttachment} />
331+
</MessageProvider>
332+
<MessageProvider value={{ message, threadList: true }}>
333+
<Card {...audioAttachment} />
334+
</MessageProvider>
335+
</WithAudioPlayback>
336+
</ChannelStateProvider>
337+
</ChatProvider>,
330338
);
331-
expect(createdAudios).toHaveLength(2);
339+
const playButtons = screen.queryAllByTestId('play-audio');
340+
expect(playButtons.length).toBe(2);
341+
await Promise.all(
342+
playButtons.map(async (button) => {
343+
await fireEvent.click(button);
344+
}),
345+
);
346+
await waitFor(() => {
347+
expect(createdAudios).toHaveLength(2);
348+
});
332349
spy.mockRestore();
333350
});
334351

335-
it('keeps a single copy of audio player for the same requester', () => {
352+
it('keeps a single copy of audio player for the same requester', async () => {
336353
const createdAudios = []; //HTMLAudioElement[]
337354
const RealAudio = window.Audio;
338355
const spy = jest.spyOn(window, 'Audio').mockImplementation(function AudioMock(
@@ -353,16 +370,29 @@ describe('Card', () => {
353370

354371
const message = generateMessage();
355372
render(
356-
<WithAudioPlayback>
357-
<MessageProvider value={{ message }}>
358-
<Card {...audioAttachment} />
359-
</MessageProvider>
360-
<MessageProvider value={{ message }}>
361-
<Card {...audioAttachment} />
362-
</MessageProvider>
363-
</WithAudioPlayback>,
373+
<ChatProvider value={{}}>
374+
<ChannelStateProvider value={{}}>
375+
<WithAudioPlayback>
376+
<MessageProvider value={{ message }}>
377+
<Card {...audioAttachment} />
378+
</MessageProvider>
379+
<MessageProvider value={{ message }}>
380+
<Card {...audioAttachment} />
381+
</MessageProvider>
382+
</WithAudioPlayback>
383+
</ChannelStateProvider>
384+
</ChatProvider>,
385+
);
386+
const playButtons = screen.queryAllByTestId('play-audio');
387+
expect(playButtons.length).toBe(2);
388+
await Promise.all(
389+
playButtons.map(async (button) => {
390+
await fireEvent.click(button);
391+
}),
364392
);
365-
expect(createdAudios).toHaveLength(1);
393+
await waitFor(() => {
394+
expect(createdAudios).toHaveLength(1);
395+
});
366396
spy.mockRestore();
367397
});
368398
});

src/components/Attachment/__tests__/VoiceRecording.test.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
generateVoiceRecordingAttachment,
88
} from '../../../mock-builders';
99
import { VoiceRecording, VoiceRecordingPlayer } from '../VoiceRecording';
10-
import { MessageProvider } from '../../../context';
10+
import { ChatProvider, MessageProvider } from '../../../context';
1111
import { ResizeObserverMock } from '../../../mock-builders/browser';
1212
import { WithAudioPlayback } from '../../AudioPlayer';
1313

@@ -35,9 +35,11 @@ jest.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation(() =>
3535

3636
const renderComponent = (props, VoiceRecordingComponent = VoiceRecording) =>
3737
render(
38-
<WithAudioPlayback>
39-
<VoiceRecordingComponent {...props} />
40-
</WithAudioPlayback>,
38+
<ChatProvider value={{ client: {} }}>
39+
<WithAudioPlayback>
40+
<VoiceRecordingComponent {...props} />
41+
</WithAudioPlayback>
42+
</ChatProvider>,
4143
);
4244

4345
describe('VoiceRecording', () => {
@@ -200,15 +202,16 @@ describe('VoiceRecordingPlayer', () => {
200202
});
201203

202204
renderComponent({ attachment });
203-
205+
await clickPlay();
204206
jest
205207
.spyOn(HTMLAudioElement.prototype, 'duration', 'get')
206208
.mockImplementationOnce(() => 100);
207209
jest
208210
.spyOn(HTMLAudioElement.prototype, 'currentTime', 'get')
209211
.mockImplementationOnce(() => 50);
210-
expect(createdAudios.length).toBe(1);
211-
fireEvent.timeUpdate(createdAudios[0]);
212+
expect(createdAudios.length).toBe(2);
213+
const actualPlayingAudio = createdAudios[1];
214+
fireEvent.timeUpdate(actualPlayingAudio);
212215

213216
await waitFor(() => {
214217
expect(screen.getByTestId('wave-progress-bar-progress-indicator')).toHaveStyle({

0 commit comments

Comments
 (0)