fix: Microphone should still work after a view change (#1835)
- Closes #1810
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { MicrophoneManager } from '../../src/card-controller/microphone-manager';
|
||||
import { MicrophoneState } from '../../src/card-controller/types';
|
||||
import { createCardAPI, createConfig } from '../test-utils';
|
||||
|
||||
const navigatorMock: Navigator = {
|
||||
@@ -162,7 +163,7 @@ describe('MicrophoneManager', () => {
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(manager.isMuted()).toBeFalsy();
|
||||
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(2);
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should disconnect', async () => {
|
||||
@@ -296,95 +297,82 @@ describe('MicrophoneManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect listeners', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
vi.mocked(navigatorMock.mediaDevices.getUserMedia).mockResolvedValue(
|
||||
createMockStream(),
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
await manager.connect();
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
|
||||
manager.mute();
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
|
||||
await manager.unmute();
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toHaveBeenLastCalledWith('unmuted');
|
||||
|
||||
await manager.unmute();
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
|
||||
manager.mute();
|
||||
expect(listener).toHaveBeenCalledTimes(2);
|
||||
expect(listener).toHaveBeenLastCalledWith('muted');
|
||||
|
||||
manager.removeListener(listener);
|
||||
|
||||
await manager.unmute();
|
||||
expect(listener).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should initialize', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
|
||||
manager.initialize();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
microphone: { connected: false, muted: true },
|
||||
microphone: { connected: false, muted: true, forbidden: false, stream: undefined },
|
||||
});
|
||||
});
|
||||
|
||||
it('should set condition state', async () => {
|
||||
it('should set state', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
vi.mocked(navigatorMock.mediaDevices.getUserMedia).mockResolvedValue(
|
||||
createMockStream(),
|
||||
);
|
||||
const stream = createMockStream();
|
||||
vi.mocked(navigatorMock.mediaDevices.getUserMedia).mockResolvedValue(stream);
|
||||
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
|
||||
await manager.connect();
|
||||
|
||||
let expectedState: MicrophoneState = {
|
||||
forbidden: false,
|
||||
stream: stream,
|
||||
connected: true,
|
||||
muted: true,
|
||||
};
|
||||
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: {
|
||||
connected: true,
|
||||
muted: true,
|
||||
},
|
||||
microphone: expectedState,
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.unmute();
|
||||
|
||||
expectedState = {
|
||||
forbidden: false,
|
||||
stream: stream,
|
||||
connected: true,
|
||||
muted: false,
|
||||
};
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: {
|
||||
connected: true,
|
||||
muted: false,
|
||||
},
|
||||
microphone: expectedState,
|
||||
}),
|
||||
);
|
||||
|
||||
manager.mute();
|
||||
|
||||
expectedState = {
|
||||
forbidden: false,
|
||||
stream: stream,
|
||||
connected: true,
|
||||
muted: true,
|
||||
};
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: {
|
||||
connected: true,
|
||||
muted: true,
|
||||
},
|
||||
microphone: expectedState,
|
||||
}),
|
||||
);
|
||||
|
||||
manager.disconnect();
|
||||
|
||||
expectedState = {
|
||||
forbidden: false,
|
||||
stream: undefined,
|
||||
connected: false,
|
||||
muted: true,
|
||||
};
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: {
|
||||
connected: false,
|
||||
muted: true,
|
||||
},
|
||||
microphone: expectedState,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
MicrophoneManagerListenerChange,
|
||||
ReadonlyMicrophoneManager,
|
||||
} from '../../src/card-controller/microphone-manager';
|
||||
import { MicrophoneState } from '../../src/card-controller/types';
|
||||
import {
|
||||
MediaActionsController,
|
||||
MediaActionsControllerOptions,
|
||||
@@ -17,7 +14,6 @@ import {
|
||||
flushPromises,
|
||||
} from '../test-utils';
|
||||
import { callVisibilityHandler, createTestSlideNodes } from '../utils/embla/test-utils';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
const getPlayer = (
|
||||
element: HTMLElement,
|
||||
@@ -50,15 +46,6 @@ const createPlayerSlideNodes = (n = 10): HTMLElement[] => {
|
||||
return divs;
|
||||
};
|
||||
|
||||
const callMicrophoneListener = (
|
||||
microphoneManager: ReadonlyMicrophoneManager,
|
||||
action: MicrophoneManagerListenerChange,
|
||||
n = 0,
|
||||
): void => {
|
||||
const mock = vi.mocked(microphoneManager.addListener).mock;
|
||||
mock.calls[n][0](action);
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('MediaActionsController', () => {
|
||||
beforeAll(() => {
|
||||
@@ -541,7 +528,7 @@ describe('MediaActionsController', () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('should take action on microphone changes', () => {
|
||||
describe('should take action on microphone state changes', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
@@ -550,14 +537,24 @@ describe('MediaActionsController', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
const createMicrophoneState = (
|
||||
state?: Partial<MicrophoneState>,
|
||||
): MicrophoneState => {
|
||||
return {
|
||||
muted: true,
|
||||
forbidden: false,
|
||||
connected: false,
|
||||
...state,
|
||||
};
|
||||
};
|
||||
|
||||
it('should unmute when microphone unmuted', async () => {
|
||||
const microphoneManager = mock<ReadonlyMicrophoneManager>();
|
||||
const controller = new MediaActionsController();
|
||||
|
||||
controller.setOptions({
|
||||
autoUnmuteConditions: ['microphone' as const],
|
||||
playerSelector: 'video',
|
||||
microphoneManager: microphoneManager,
|
||||
microphoneState: createMicrophoneState({ muted: true }),
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
@@ -565,19 +562,22 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
callMicrophoneListener(microphoneManager, 'unmuted');
|
||||
controller.setOptions({
|
||||
autoUnmuteConditions: ['microphone' as const],
|
||||
playerSelector: 'video',
|
||||
microphoneState: createMicrophoneState({ muted: false }),
|
||||
});
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalled();
|
||||
});
|
||||
|
||||
it('should re-mute after delay after microphone unmuted', async () => {
|
||||
const microphoneManager = mock<ReadonlyMicrophoneManager>();
|
||||
it('should mute after delay after microphone muted', async () => {
|
||||
const controller = new MediaActionsController();
|
||||
|
||||
controller.setOptions({
|
||||
autoMuteConditions: ['microphone' as const],
|
||||
playerSelector: 'video',
|
||||
microphoneManager: microphoneManager,
|
||||
microphoneState: createMicrophoneState({ muted: false }),
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
@@ -585,21 +585,24 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
callMicrophoneListener(microphoneManager, 'muted');
|
||||
controller.setOptions({
|
||||
autoMuteConditions: ['microphone' as const],
|
||||
playerSelector: 'video',
|
||||
microphoneState: createMicrophoneState({ muted: true }),
|
||||
});
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.mute).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not re-mute after delay after microphone unmuted', async () => {
|
||||
const microphoneManager = mock<ReadonlyMicrophoneManager>();
|
||||
it('should not mute after delay after microphone muted', async () => {
|
||||
const controller = new MediaActionsController();
|
||||
|
||||
controller.setOptions({
|
||||
autoMuteConditions: [],
|
||||
playerSelector: 'video',
|
||||
microphoneManager: microphoneManager,
|
||||
microphoneState: createMicrophoneState({ muted: false }),
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
@@ -607,7 +610,11 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
callMicrophoneListener(microphoneManager, 'muted');
|
||||
controller.setOptions({
|
||||
autoMuteConditions: ['microphone' as const],
|
||||
playerSelector: 'video',
|
||||
microphoneState: createMicrophoneState({ muted: true }),
|
||||
});
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user