test: Split test utilities to improve test times (#2622)
This commit is contained in:
@@ -16,16 +16,15 @@ import type { TriggerOfType } from '../../../src/condition-trigger/triggers/trig
|
||||
import type { RingtoneConfig } from '../../../src/config/schema/live';
|
||||
import type { AdvancedCameraCardConfig } from '../../../src/config/schema/types';
|
||||
import { View } from '../../../src/view/view';
|
||||
import { createTriggerEvaluatorContext } from '../../condition-trigger/triggers/triggers/test-utils';
|
||||
import {
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
createCapabilities,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createStore,
|
||||
createView,
|
||||
} from '../../test-utils';
|
||||
} from '../../camera-manager/test-utils';
|
||||
import { createTriggerEvaluatorContext } from '../../condition-trigger/triggers/triggers/test-utils';
|
||||
import { createCameraConfig, createConfig } from '../../config/test-utils';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
import { createView } from '../../view/test-utils';
|
||||
|
||||
// Replace Ringtone with a fresh `mock<Ringtone>()` per construction so each
|
||||
// CallManager gets an isolated, type-safe ringtone we can assert on. The
|
||||
@@ -130,7 +129,7 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing when already active for the camera', async () => {
|
||||
@@ -141,7 +140,7 @@ describe('start', () => {
|
||||
vi.mocked(api.getViewManager().setViewByParameters).mockClear();
|
||||
expect(await manager.start()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should start a call on the selected camera', async () => {
|
||||
@@ -149,7 +148,7 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -162,7 +161,7 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
params: { view: 'live', camera: 'camera.office' },
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
@@ -176,11 +175,11 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalledWith(
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ params: expect.anything() }),
|
||||
);
|
||||
});
|
||||
@@ -251,7 +250,7 @@ describe('start', () => {
|
||||
expect(call?.cameraID).toBe('camera.office');
|
||||
expect(call?.previousView?.view).toBe('folder');
|
||||
expect(call?.previousView?.camera).toBeNull();
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
params: { view: 'live', camera: 'camera.office' },
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
@@ -280,7 +279,7 @@ describe('start', () => {
|
||||
expect(call?.cameraID).toBe('camera.garage');
|
||||
expect(call?.previousView?.view).toBe('live');
|
||||
expect(call?.previousView?.camera).toBe('camera.office');
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
params: { view: 'live', camera: 'camera.garage' },
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
@@ -321,8 +320,8 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start({ cameraID: 'camera.unknown' })).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should abort when the requested stream is not 2-way audio of the parent camera', async () => {
|
||||
@@ -347,8 +346,8 @@ describe('start', () => {
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should supersede an active call on a different camera', async () => {
|
||||
@@ -439,8 +438,8 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should engage the active substream when it is call-capable', async () => {
|
||||
@@ -464,7 +463,7 @@ describe('start', () => {
|
||||
|
||||
expect(await manager.start()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -493,7 +492,7 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -507,8 +506,8 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should abort when the microphone is forbidden', async () => {
|
||||
@@ -519,8 +518,8 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should connect the microphone when not already connected', async () => {
|
||||
@@ -532,8 +531,8 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(true);
|
||||
|
||||
expect(api.getMicrophoneManager().connect).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalled();
|
||||
expect(api.getMicrophoneManager().connect).toHaveBeenCalled();
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should abort when connecting the microphone fails', async () => {
|
||||
@@ -545,8 +544,8 @@ describe('start', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ring an inbound call without connecting the microphone', async () => {
|
||||
@@ -562,8 +561,8 @@ describe('start', () => {
|
||||
// that would fail must not stop the call from ringing.
|
||||
expect(await manager.start({ inbound: true })).toBe(true);
|
||||
|
||||
expect(api.getMicrophoneManager().connect).not.toBeCalled();
|
||||
expect(getRingtone().start).toBeCalled();
|
||||
expect(api.getMicrophoneManager().connect).not.toHaveBeenCalled();
|
||||
expect(getRingtone().start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ring an inbound call when the microphone is forbidden', async () => {
|
||||
@@ -578,7 +577,7 @@ describe('start', () => {
|
||||
// microphone, and answering retries the connect.
|
||||
expect(await manager.start({ inbound: true })).toBe(true);
|
||||
|
||||
expect(getRingtone().start).toBeCalled();
|
||||
expect(getRingtone().start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should abort an inbound call when the microphone is unsupported', async () => {
|
||||
@@ -593,7 +592,7 @@ describe('start', () => {
|
||||
// supporting it while the page is loaded, so the call can never be taken.
|
||||
expect(await manager.start({ inbound: true })).toBe(false);
|
||||
|
||||
expect(getRingtone().start).not.toBeCalled();
|
||||
expect(getRingtone().start).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -692,7 +691,7 @@ describe('end', () => {
|
||||
|
||||
expect(new CallManager(api).end()).toBe(false);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should end an active call', async () => {
|
||||
@@ -704,7 +703,7 @@ describe('end', () => {
|
||||
expect(manager.end()).toBe(true);
|
||||
|
||||
expect(manager.isActive()).toBe(false);
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -734,7 +733,7 @@ describe('end', () => {
|
||||
expect(manager.end()).toBe(true);
|
||||
|
||||
// The recorded pre-call substream (`camera.sub`) is reinstated.
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -749,7 +748,9 @@ describe('end', () => {
|
||||
|
||||
expect(manager.end()).toBe(true);
|
||||
|
||||
expect(api.getViewManager().setViewByParametersWithExistingQuery).toBeCalledWith({
|
||||
expect(
|
||||
api.getViewManager().setViewByParametersWithExistingQuery,
|
||||
).toHaveBeenCalledWith({
|
||||
baseView: expect.any(View),
|
||||
force: true,
|
||||
});
|
||||
@@ -770,11 +771,13 @@ describe('end', () => {
|
||||
expect(manager.end()).toBe(true);
|
||||
|
||||
// No navigation: only the substream is undone.
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
expect(api.getViewManager().setViewByParametersWithExistingQuery).not.toBeCalled();
|
||||
expect(
|
||||
api.getViewManager().setViewByParametersWithExistingQuery,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return to a camera-less pre-call view on an explicit end', async () => {
|
||||
@@ -957,7 +960,7 @@ describe('condition state changes', () => {
|
||||
});
|
||||
|
||||
expect(manager.isActive()).toBe(false);
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -979,7 +982,9 @@ describe('condition state changes', () => {
|
||||
});
|
||||
|
||||
expect(manager.isActive()).toBe(false);
|
||||
expect(api.getViewManager().setViewByParametersWithExistingQuery).not.toBeCalled();
|
||||
expect(
|
||||
api.getViewManager().setViewByParametersWithExistingQuery,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should end the call when the view leaves live', async () => {
|
||||
@@ -996,7 +1001,7 @@ describe('condition state changes', () => {
|
||||
});
|
||||
|
||||
expect(manager.isActive()).toBe(false);
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
|
||||
modifiers: [expect.any(SubstreamViewModifier)],
|
||||
force: true,
|
||||
});
|
||||
@@ -1027,7 +1032,7 @@ describe('condition state changes', () => {
|
||||
new: { camera: 'camera.other' },
|
||||
});
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should end the call when the substream changes away', async () => {
|
||||
@@ -1103,7 +1108,7 @@ describe('initialize / uninitialize', () => {
|
||||
const api = createAPI();
|
||||
new CallManager(api);
|
||||
|
||||
expect(api.getConditionStateManager().addListener).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().addListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should register the condition state listener on initialize', () => {
|
||||
@@ -1112,7 +1117,7 @@ describe('initialize / uninitialize', () => {
|
||||
|
||||
manager.initialize();
|
||||
|
||||
expect(api.getConditionStateManager().addListener).toBeCalled();
|
||||
expect(api.getConditionStateManager().addListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove the condition state listener on uninitialize', () => {
|
||||
@@ -1123,7 +1128,7 @@ describe('initialize / uninitialize', () => {
|
||||
|
||||
manager.uninitialize();
|
||||
|
||||
expect(api.getConditionStateManager().removeListener).toBeCalledWith(listener);
|
||||
expect(api.getConditionStateManager().removeListener).toHaveBeenCalledWith(listener);
|
||||
});
|
||||
|
||||
it('should tear down any active call session on uninitialize', async () => {
|
||||
@@ -1156,7 +1161,7 @@ describe('initialize / uninitialize', () => {
|
||||
new: { camera: 'camera.other', view: 'live' },
|
||||
});
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1179,7 +1184,7 @@ describe('inbound option', () => {
|
||||
|
||||
expect(await new CallManager(api).start({ inbound: true })).toBe(false);
|
||||
|
||||
expect(api.getNotificationManager().setNotification).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should suppress notification when the microphone is unsupported', async () => {
|
||||
@@ -1190,7 +1195,7 @@ describe('inbound option', () => {
|
||||
|
||||
expect(await new CallManager(api).start({ inbound: true })).toBe(false);
|
||||
|
||||
expect(api.getNotificationManager().setNotification).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should suppress notification when an explicit stream is not 2-way audio', async () => {
|
||||
@@ -1203,7 +1208,7 @@ describe('inbound option', () => {
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(api.getNotificationManager().setNotification).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should suppress notification when no stream supports 2-way audio', async () => {
|
||||
@@ -1219,7 +1224,7 @@ describe('inbound option', () => {
|
||||
|
||||
expect(await new CallManager(api).start({ inbound: true })).toBe(false);
|
||||
|
||||
expect(api.getNotificationManager().setNotification).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should record the call as inbound on the session', async () => {
|
||||
@@ -1274,7 +1279,7 @@ describe('answer', () => {
|
||||
expect(await manager.start({ inbound: true })).toBe(true);
|
||||
|
||||
expect(manager.getCall()?.answered).toBe(false);
|
||||
expect(getRingtone().start).toBeCalled();
|
||||
expect(getRingtone().start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should no-op when no call is active', async () => {
|
||||
@@ -1298,8 +1303,8 @@ describe('answer', () => {
|
||||
|
||||
expect(await manager.answer()).toBe(false);
|
||||
|
||||
expect(getRingtone().stop).not.toBeCalled();
|
||||
expect(api.getCardElementManager().update).not.toBeCalled();
|
||||
expect(getRingtone().stop).not.toHaveBeenCalled();
|
||||
expect(api.getCardElementManager().update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should mark answered and replace the session immutably', async () => {
|
||||
@@ -1345,7 +1350,7 @@ describe('answer', () => {
|
||||
|
||||
expect(await manager.answer()).toBe(true);
|
||||
|
||||
expect(getRingtone().stop).toBeCalled();
|
||||
expect(getRingtone().stop).toHaveBeenCalled();
|
||||
|
||||
// Timer was armed and should now be cancelled: advancing past the
|
||||
// timeout must not end the (now-answered) call.
|
||||
@@ -1369,7 +1374,7 @@ describe('answer', () => {
|
||||
|
||||
expect(await manager.answer()).toBe(true);
|
||||
|
||||
expect(api.getMicrophoneManager().connect).toBeCalled();
|
||||
expect(api.getMicrophoneManager().connect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should silence the ringtone before the microphone connect', async () => {
|
||||
@@ -1393,7 +1398,7 @@ describe('answer', () => {
|
||||
|
||||
// The user has acknowledged the ring, so it must stop without waiting for a
|
||||
// microphone permission prompt to be dealt with.
|
||||
expect(getRingtone().stop).toBeCalled();
|
||||
expect(getRingtone().stop).toHaveBeenCalled();
|
||||
|
||||
resolveConnect();
|
||||
expect(await answerPromise).toBe(true);
|
||||
@@ -1414,7 +1419,7 @@ describe('answer', () => {
|
||||
|
||||
// Answering is an explicit user gesture, so the failure is surfaced and the
|
||||
// call remains answerable.
|
||||
expect(api.getNotificationManager().setNotification).toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
|
||||
expect(manager.getCall()?.answered).toBe(false);
|
||||
});
|
||||
|
||||
@@ -1433,7 +1438,7 @@ describe('answer', () => {
|
||||
// The card subtree depends on `getCall().answered`, which the manager
|
||||
// mutates outside the view-manager epoch -- so `update()` is what drives
|
||||
// the re-render through to the call-controls overlay.
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not mark non-inbound (outbound) calls via answer (already answered)', async () => {
|
||||
@@ -1483,12 +1488,12 @@ describe('microphone usage', () => {
|
||||
|
||||
expect(await manager.start()).toBe(true);
|
||||
|
||||
expect(api.getMicrophoneManager().startUsing).toBeCalledTimes(1);
|
||||
expect(api.getMicrophoneManager().stopUsing).not.toBeCalled();
|
||||
expect(api.getMicrophoneManager().startUsing).toHaveBeenCalledTimes(1);
|
||||
expect(api.getMicrophoneManager().stopUsing).not.toHaveBeenCalled();
|
||||
|
||||
manager.end();
|
||||
|
||||
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
|
||||
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not mark the microphone in use when the start is aborted', async () => {
|
||||
@@ -1500,7 +1505,7 @@ describe('microphone usage', () => {
|
||||
|
||||
expect(await new CallManager(api).start()).toBe(false);
|
||||
|
||||
expect(api.getMicrophoneManager().startUsing).not.toBeCalled();
|
||||
expect(api.getMicrophoneManager().startUsing).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should keep the microphone in use when a call supersedes another', async () => {
|
||||
@@ -1522,8 +1527,8 @@ describe('microphone usage', () => {
|
||||
expect(await manager.start()).toBe(true);
|
||||
expect(await manager.start({ cameraID: 'camera.garage' })).toBe(true);
|
||||
|
||||
expect(api.getMicrophoneManager().startUsing).toBeCalledTimes(2);
|
||||
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
|
||||
expect(api.getMicrophoneManager().startUsing).toHaveBeenCalledTimes(2);
|
||||
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should mark the microphone unused on uninitialization', async () => {
|
||||
@@ -1533,7 +1538,7 @@ describe('microphone usage', () => {
|
||||
expect(await manager.start()).toBe(true);
|
||||
manager.uninitialize();
|
||||
|
||||
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
|
||||
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not mark the microphone unused when uninitializing without a call', () => {
|
||||
@@ -1541,7 +1546,7 @@ describe('microphone usage', () => {
|
||||
|
||||
new CallManager(api).uninitialize();
|
||||
|
||||
expect(api.getMicrophoneManager().stopUsing).not.toBeCalled();
|
||||
expect(api.getMicrophoneManager().stopUsing).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1559,7 +1564,7 @@ describe('ringtone', () => {
|
||||
|
||||
expect(await manager.start({ inbound: true })).toBe(true);
|
||||
|
||||
expect(getRingtone().start).toBeCalledWith(expect.objectContaining(ringtone));
|
||||
expect(getRingtone().start).toHaveBeenCalledWith(expect.objectContaining(ringtone));
|
||||
});
|
||||
|
||||
it('should not start the ringtone for a non-inbound call', async () => {
|
||||
@@ -1572,7 +1577,7 @@ describe('ringtone', () => {
|
||||
|
||||
expect(await manager.start()).toBe(true);
|
||||
|
||||
expect(getRingtone().start).not.toBeCalled();
|
||||
expect(getRingtone().start).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not start the ringtone when type is 'none'", async () => {
|
||||
@@ -1585,7 +1590,7 @@ describe('ringtone', () => {
|
||||
|
||||
expect(await manager.start({ inbound: true })).toBe(true);
|
||||
|
||||
expect(getRingtone().start).not.toBeCalled();
|
||||
expect(getRingtone().start).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should stop the ringtone when the call ends', async () => {
|
||||
@@ -1600,7 +1605,7 @@ describe('ringtone', () => {
|
||||
|
||||
expect(manager.end()).toBe(true);
|
||||
|
||||
expect(getRingtone().stop).toBeCalled();
|
||||
expect(getRingtone().stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should stop the ringtone on uninitialize', async () => {
|
||||
@@ -1615,7 +1620,7 @@ describe('ringtone', () => {
|
||||
|
||||
manager.uninitialize();
|
||||
|
||||
expect(getRingtone().stop).toBeCalled();
|
||||
expect(getRingtone().stop).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1764,7 +1769,7 @@ describe('session end during setState', () => {
|
||||
|
||||
expect(await manager.start({ inbound: true })).toBe(false);
|
||||
|
||||
expect(getRingtone().start).not.toBeCalled();
|
||||
expect(getRingtone().start).not.toHaveBeenCalled();
|
||||
expect(manager.isActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1790,7 +1795,7 @@ describe('state changes during in-flight start', () => {
|
||||
|
||||
expect(await startPromise).toBe(false);
|
||||
expect(manager.isActive()).toBe(false);
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not install a session when uninitialized and re-initialized mid-await', async () => {
|
||||
@@ -1814,7 +1819,7 @@ describe('state changes during in-flight start', () => {
|
||||
|
||||
expect(await startPromise).toBe(false);
|
||||
expect(manager.isActive()).toBe(false);
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should supersede a session installed by another start mid-await', async () => {
|
||||
@@ -1853,8 +1858,8 @@ describe('state changes during in-flight start', () => {
|
||||
// overwrite it, leaving exactly one live session and no stranded microphone
|
||||
// marking.
|
||||
expect(manager.getCall()?.cameraID).toBe('camera.garage');
|
||||
expect(api.getMicrophoneManager().startUsing).toBeCalledTimes(2);
|
||||
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
|
||||
expect(api.getMicrophoneManager().startUsing).toHaveBeenCalledTimes(2);
|
||||
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should suppress the microphone-failure notification when uninitialized mid-await', async () => {
|
||||
@@ -1876,7 +1881,7 @@ describe('state changes during in-flight start', () => {
|
||||
rejectConnect(new Error('denied'));
|
||||
|
||||
expect(await startPromise).toBe(false);
|
||||
expect(api.getNotificationManager().setNotification).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1939,7 +1944,7 @@ describe('state changes during in-flight answer', () => {
|
||||
rejectConnect(new Error('denied'));
|
||||
|
||||
expect(await answerPromise).toBe(false);
|
||||
expect(api.getNotificationManager().setNotification).not.toBeCalled();
|
||||
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not answer a session that ended mid-await', async () => {
|
||||
|
||||
@@ -59,25 +59,25 @@ describe('factory dispatch', () => {
|
||||
it('should construct a ChimeTone for type "chime"', () => {
|
||||
new Ringtone(new Set()).start({ type: 'chime', repeat: 3 });
|
||||
|
||||
expect(ChimeTone).toBeCalledWith(3);
|
||||
expect(ChimeTone).toHaveBeenCalledWith(3);
|
||||
});
|
||||
|
||||
it('should construct a WestminsterTone for type "westminster"', () => {
|
||||
new Ringtone(new Set()).start({ type: 'westminster', repeat: 2 });
|
||||
|
||||
expect(WestminsterTone).toBeCalledWith(2);
|
||||
expect(WestminsterTone).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('should construct an ArpeggioTone for type "arpeggio"', () => {
|
||||
new Ringtone(new Set()).start({ type: 'arpeggio', repeat: 1 });
|
||||
|
||||
expect(ArpeggioTone).toBeCalledWith(1);
|
||||
expect(ArpeggioTone).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should construct a MelodyTone for type "melody"', () => {
|
||||
new Ringtone(new Set()).start({ type: 'melody', repeat: 5 });
|
||||
|
||||
expect(MelodyTone).toBeCalledWith(5);
|
||||
expect(MelodyTone).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('should construct a CustomTone for type "custom" with a URL', () => {
|
||||
@@ -87,7 +87,7 @@ describe('factory dispatch', () => {
|
||||
repeat: 0,
|
||||
});
|
||||
|
||||
expect(CustomTone).toBeCalledWith('http://localhost/ring.mp3', 0);
|
||||
expect(CustomTone).toHaveBeenCalledWith('http://localhost/ring.mp3', 0);
|
||||
});
|
||||
|
||||
it('should construct no tone for type "custom" without a URL', () => {
|
||||
@@ -95,7 +95,7 @@ describe('factory dispatch', () => {
|
||||
|
||||
ringtone.start({ type: 'custom', repeat: 0 });
|
||||
|
||||
expect(CustomTone).not.toBeCalled();
|
||||
expect(CustomTone).not.toHaveBeenCalled();
|
||||
expect(ringtone.isPlaying()).toBe(false);
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('factory dispatch', () => {
|
||||
|
||||
ringtone.start({ type: 'none', repeat: 0 });
|
||||
|
||||
expect(ChimeTone).not.toBeCalled();
|
||||
expect(ChimeTone).not.toHaveBeenCalled();
|
||||
expect(ringtone.isPlaying()).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -115,7 +115,7 @@ describe('start', () => {
|
||||
|
||||
ringtone.start(chimeConfig);
|
||||
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).start).toBeCalled();
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).start).toHaveBeenCalled();
|
||||
expect(ringtone.isPlaying()).toBe(true);
|
||||
});
|
||||
|
||||
@@ -125,8 +125,8 @@ describe('start', () => {
|
||||
ringtone.start(chimeConfig);
|
||||
ringtone.start(chimeConfig);
|
||||
|
||||
expect(ChimeTone).toBeCalledTimes(1);
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).start).toBeCalledTimes(1);
|
||||
expect(ChimeTone).toHaveBeenCalledTimes(1);
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should claim the lock when a tone starts', () => {
|
||||
@@ -158,7 +158,7 @@ describe('lock', () => {
|
||||
vi.mocked(ChimeTone).mockClear();
|
||||
second.start(chimeConfig);
|
||||
|
||||
expect(ChimeTone).not.toBeCalled();
|
||||
expect(ChimeTone).not.toHaveBeenCalled();
|
||||
expect(second.isPlaying()).toBe(false);
|
||||
expect(first.isPlaying()).toBe(true);
|
||||
});
|
||||
@@ -171,11 +171,11 @@ describe('lock', () => {
|
||||
first.start(chimeConfig);
|
||||
const firstTone = lastInstance(vi.mocked(ChimeTone));
|
||||
first.stop();
|
||||
expect(firstTone.stop).toBeCalled();
|
||||
expect(firstTone.stop).toHaveBeenCalled();
|
||||
|
||||
second.start(chimeConfig);
|
||||
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).start).toBeCalled();
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).start).toHaveBeenCalled();
|
||||
expect(second.isPlaying()).toBe(true);
|
||||
});
|
||||
|
||||
@@ -227,7 +227,7 @@ describe('stop', () => {
|
||||
ringtone.start(chimeConfig);
|
||||
ringtone.stop();
|
||||
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).stop).toBeCalled();
|
||||
expect(lastInstance(vi.mocked(ChimeTone)).stop).toHaveBeenCalled();
|
||||
expect(lock.has(ringtone)).toBe(false);
|
||||
expect(ringtone.isPlaying()).toBe(false);
|
||||
});
|
||||
@@ -250,7 +250,7 @@ describe('default lock', () => {
|
||||
vi.mocked(ChimeTone).mockClear();
|
||||
second.start(chimeConfig);
|
||||
|
||||
expect(ChimeTone).not.toBeCalled();
|
||||
expect(ChimeTone).not.toHaveBeenCalled();
|
||||
first.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,15 +19,15 @@ describe('ArpeggioTone', () => {
|
||||
|
||||
// G5 (783.99) at t=0.
|
||||
expect(audio.oscillators[1].frequency.value).toBe(783.99);
|
||||
expect(audio.oscillators[1].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[1].start).toHaveBeenCalledWith(0);
|
||||
|
||||
// E5 (659.25) at t=0.25.
|
||||
expect(audio.oscillators[4].frequency.value).toBe(659.25);
|
||||
expect(audio.oscillators[4].start).toBeCalledWith(0.25);
|
||||
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0.25);
|
||||
|
||||
// C5 (523.25) at t=0.5.
|
||||
expect(audio.oscillators[7].frequency.value).toBe(523.25);
|
||||
expect(audio.oscillators[7].start).toBeCalledWith(0.5);
|
||||
expect(audio.oscillators[7].start).toHaveBeenCalledWith(0.5);
|
||||
});
|
||||
|
||||
it('should use the lighter PLUCK envelope for every strike', () => {
|
||||
@@ -36,15 +36,15 @@ describe('ArpeggioTone', () => {
|
||||
// Sparkle / fundamental / hum peaks for every strike.
|
||||
for (let strike = 0; strike < 3; strike++) {
|
||||
const i = strike * 3;
|
||||
expect(audio.gainParams[i].linearRampToValueAtTime).toBeCalledWith(
|
||||
expect(audio.gainParams[i].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.05,
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(audio.gainParams[i + 1].linearRampToValueAtTime).toBeCalledWith(
|
||||
expect(audio.gainParams[i + 1].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.13,
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(audio.gainParams[i + 2].linearRampToValueAtTime).toBeCalledWith(
|
||||
expect(audio.gainParams[i + 2].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.04,
|
||||
expect.any(Number),
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('start', () => {
|
||||
it('should construct an AudioContext and play one iteration', () => {
|
||||
new ChimeTone(0).start();
|
||||
|
||||
expect(audio.audioContextCtor).toBeCalledTimes(1);
|
||||
expect(audio.audioContextCtor).toHaveBeenCalledTimes(1);
|
||||
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('start', () => {
|
||||
tone.start();
|
||||
tone.start();
|
||||
|
||||
expect(audio.audioContextCtor).toBeCalledTimes(1);
|
||||
expect(audio.audioContextCtor).toHaveBeenCalledTimes(1);
|
||||
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS);
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('start', () => {
|
||||
|
||||
new ChimeTone(0).start(onFinished);
|
||||
|
||||
expect(onFinished).toBeCalled();
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
expect(audio.oscillators).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -52,7 +52,7 @@ describe('stop', () => {
|
||||
|
||||
tone.stop();
|
||||
|
||||
expect(audio.audioContext.close).toBeCalled();
|
||||
expect(audio.audioContext.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not fire finishedHandler on external stop', () => {
|
||||
@@ -62,7 +62,7 @@ describe('stop', () => {
|
||||
|
||||
tone.stop();
|
||||
|
||||
expect(onFinished).not.toBeCalled();
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should swallow AudioContext.close rejections silently', () => {
|
||||
@@ -91,7 +91,7 @@ describe('repeat counter', () => {
|
||||
vi.advanceTimersByTime(ITERATION_INTERVAL_MS);
|
||||
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS * i);
|
||||
}
|
||||
expect(audio.audioContext.close).not.toBeCalled();
|
||||
expect(audio.audioContext.close).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should play exactly `repeat` iterations and then finish', () => {
|
||||
@@ -110,8 +110,8 @@ describe('repeat counter', () => {
|
||||
// decay tail, then fires the finished handler and stops.
|
||||
vi.advanceTimersByTime(ITERATION_INTERVAL_MS);
|
||||
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS * 3);
|
||||
expect(onFinished).toBeCalledTimes(1);
|
||||
expect(audio.audioContext.close).toBeCalled();
|
||||
expect(onFinished).toHaveBeenCalledTimes(1);
|
||||
expect(audio.audioContext.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not fire finishedHandler when stopped mid-sequence', () => {
|
||||
@@ -124,6 +124,6 @@ describe('repeat counter', () => {
|
||||
// Even if any stale scheduled work fires, finishedHandler stays silent.
|
||||
vi.advanceTimersByTime(ITERATION_INTERVAL_MS * 10);
|
||||
|
||||
expect(onFinished).not.toBeCalled();
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,41 +21,53 @@ describe('ChimeTone', () => {
|
||||
expect(audio.oscillators[0].frequency.value).toBe(622.25 * 2);
|
||||
expect(audio.oscillators[1].frequency.value).toBe(622.25);
|
||||
expect(audio.oscillators[2].frequency.value).toBe(622.25 / 2);
|
||||
expect(audio.oscillators[0].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[1].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[2].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[0].start).toHaveBeenCalledWith(0);
|
||||
expect(audio.oscillators[1].start).toHaveBeenCalledWith(0);
|
||||
expect(audio.oscillators[2].start).toHaveBeenCalledWith(0);
|
||||
|
||||
// DOOOOONG -- B4 (493.88Hz) at t=0.5.
|
||||
expect(audio.oscillators[3].frequency.value).toBe(493.88 * 2);
|
||||
expect(audio.oscillators[4].frequency.value).toBe(493.88);
|
||||
expect(audio.oscillators[5].frequency.value).toBe(493.88 / 2);
|
||||
expect(audio.oscillators[3].start).toBeCalledWith(0.5);
|
||||
expect(audio.oscillators[4].start).toBeCalledWith(0.5);
|
||||
expect(audio.oscillators[5].start).toBeCalledWith(0.5);
|
||||
expect(audio.oscillators[3].start).toHaveBeenCalledWith(0.5);
|
||||
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0.5);
|
||||
expect(audio.oscillators[5].start).toHaveBeenCalledWith(0.5);
|
||||
});
|
||||
|
||||
it('should give DING a brighter, shorter bell envelope', () => {
|
||||
new ChimeTone(0).start();
|
||||
|
||||
// Sparkle / fundamental / hum peaks for DING.
|
||||
expect(audio.gainParams[0].linearRampToValueAtTime).toBeCalledWith(0.1, 0.005);
|
||||
expect(audio.gainParams[1].linearRampToValueAtTime).toBeCalledWith(0.22, 0.005);
|
||||
expect(audio.gainParams[2].linearRampToValueAtTime).toBeCalledWith(0.08, 0.005);
|
||||
expect(audio.gainParams[0].linearRampToValueAtTime).toHaveBeenCalledWith(0.1, 0.005);
|
||||
expect(audio.gainParams[1].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.22,
|
||||
0.005,
|
||||
);
|
||||
expect(audio.gainParams[2].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.08,
|
||||
0.005,
|
||||
);
|
||||
// Decay constants (sparkle fades fastest, hum lingers).
|
||||
expect(audio.gainParams[0].setTargetAtTime).toBeCalledWith(0, 0.005, 0.3);
|
||||
expect(audio.gainParams[1].setTargetAtTime).toBeCalledWith(0, 0.005, 0.8);
|
||||
expect(audio.gainParams[2].setTargetAtTime).toBeCalledWith(0, 0.005, 1.2);
|
||||
expect(audio.gainParams[0].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.3);
|
||||
expect(audio.gainParams[1].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.8);
|
||||
expect(audio.gainParams[2].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 1.2);
|
||||
});
|
||||
|
||||
it('should give DOOOOONG a fuller, longer bell envelope', () => {
|
||||
new ChimeTone(0).start();
|
||||
|
||||
expect(audio.gainParams[3].linearRampToValueAtTime).toBeCalledWith(0.11, 0.505);
|
||||
expect(audio.gainParams[4].linearRampToValueAtTime).toBeCalledWith(0.28, 0.505);
|
||||
expect(audio.gainParams[5].linearRampToValueAtTime).toBeCalledWith(0.1, 0.505);
|
||||
expect(audio.gainParams[3].setTargetAtTime).toBeCalledWith(0, 0.505, 0.5);
|
||||
expect(audio.gainParams[4].setTargetAtTime).toBeCalledWith(0, 0.505, 1.3);
|
||||
expect(audio.gainParams[5].setTargetAtTime).toBeCalledWith(0, 0.505, 1.8);
|
||||
expect(audio.gainParams[3].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.11,
|
||||
0.505,
|
||||
);
|
||||
expect(audio.gainParams[4].linearRampToValueAtTime).toHaveBeenCalledWith(
|
||||
0.28,
|
||||
0.505,
|
||||
);
|
||||
expect(audio.gainParams[5].linearRampToValueAtTime).toHaveBeenCalledWith(0.1, 0.505);
|
||||
expect(audio.gainParams[3].setTargetAtTime).toHaveBeenCalledWith(0, 0.505, 0.5);
|
||||
expect(audio.gainParams[4].setTargetAtTime).toHaveBeenCalledWith(0, 0.505, 1.3);
|
||||
expect(audio.gainParams[5].setTargetAtTime).toHaveBeenCalledWith(0, 0.505, 1.8);
|
||||
});
|
||||
|
||||
describe('with fake timers', () => {
|
||||
|
||||
@@ -59,14 +59,14 @@ describe('start', () => {
|
||||
it('should construct an Audio element with the configured URL', () => {
|
||||
new CustomTone('http://example/ring.mp3', 0).start();
|
||||
|
||||
expect(audio.ctor).toBeCalledWith('http://example/ring.mp3');
|
||||
expect(audio.ctor).toHaveBeenCalledWith('http://example/ring.mp3');
|
||||
});
|
||||
|
||||
it('should loop indefinitely when repeat is 0', () => {
|
||||
new CustomTone('http://example/ring.mp3', 0).start();
|
||||
|
||||
expect(audio.instances[0].loop).toBe(true);
|
||||
expect(audio.instances[0].play).toBeCalled();
|
||||
expect(audio.instances[0].play).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should re-play (not loop natively) when repeat is finite', () => {
|
||||
@@ -76,14 +76,14 @@ describe('start', () => {
|
||||
// Observable proof the 'ended' listener was registered: dispatching the
|
||||
// event triggers a second play().
|
||||
audio.instances[0].dispatchEvent(new Event('ended'));
|
||||
expect(audio.instances[0].play).toBeCalledTimes(2);
|
||||
expect(audio.instances[0].play).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should reset currentTime before play', () => {
|
||||
new CustomTone('http://example/ring.mp3', 0).start();
|
||||
|
||||
expect(audio.instances[0].currentTime).toBe(0);
|
||||
expect(audio.instances[0].play).toBeCalled();
|
||||
expect(audio.instances[0].play).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should no-op when called twice without stop', () => {
|
||||
@@ -92,7 +92,7 @@ describe('start', () => {
|
||||
tone.start();
|
||||
tone.start();
|
||||
|
||||
expect(audio.ctor).toBeCalledTimes(1);
|
||||
expect(audio.ctor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should fire finishedHandler when Audio construction throws', () => {
|
||||
@@ -103,7 +103,7 @@ describe('start', () => {
|
||||
|
||||
new CustomTone('http://example/ring.mp3', 0).start(onFinished);
|
||||
|
||||
expect(onFinished).toBeCalled();
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fire finishedHandler when play() rejects (e.g. autoplay block)', async () => {
|
||||
@@ -116,7 +116,7 @@ describe('start', () => {
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(onFinished).toBeCalled();
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,15 +128,15 @@ describe('repeat counter', () => {
|
||||
|
||||
// Iteration 1 already started by `start()`. Two more 'ended' events
|
||||
// should re-play, and the third 'ended' should finish.
|
||||
expect(audio.instances[0].play).toBeCalledTimes(1);
|
||||
expect(audio.instances[0].play).toHaveBeenCalledTimes(1);
|
||||
audio.instances[0].dispatchEvent(new Event('ended'));
|
||||
expect(audio.instances[0].play).toBeCalledTimes(2);
|
||||
expect(audio.instances[0].play).toHaveBeenCalledTimes(2);
|
||||
audio.instances[0].dispatchEvent(new Event('ended'));
|
||||
expect(audio.instances[0].play).toBeCalledTimes(3);
|
||||
expect(onFinished).not.toBeCalled();
|
||||
expect(audio.instances[0].play).toHaveBeenCalledTimes(3);
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
audio.instances[0].dispatchEvent(new Event('ended'));
|
||||
|
||||
expect(onFinished).toBeCalledTimes(1);
|
||||
expect(onFinished).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should ignore ended events after stop', () => {
|
||||
@@ -150,7 +150,7 @@ describe('repeat counter', () => {
|
||||
// no-op as far as the source is concerned.
|
||||
element.dispatchEvent(new Event('ended'));
|
||||
|
||||
expect(onFinished).not.toBeCalled();
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -162,11 +162,11 @@ describe('stop', () => {
|
||||
|
||||
tone.stop();
|
||||
|
||||
expect(element.pause).toBeCalled();
|
||||
expect(element.pause).toHaveBeenCalled();
|
||||
// Confirm the 'ended' listener is gone: dispatching it must not re-play.
|
||||
vi.mocked(HTMLMediaElement.prototype.play).mockClear();
|
||||
element.dispatchEvent(new Event('ended'));
|
||||
expect(element.play).not.toBeCalled();
|
||||
expect(element.play).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not fire finishedHandler on external stop', () => {
|
||||
@@ -176,7 +176,7 @@ describe('stop', () => {
|
||||
|
||||
tone.stop();
|
||||
|
||||
expect(onFinished).not.toBeCalled();
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be safe to call before start', () => {
|
||||
|
||||
@@ -22,8 +22,8 @@ describe('MelodyTone', () => {
|
||||
expect(audio.oscillators[2].frequency.value).toBe(659.25);
|
||||
expect(audio.oscillators[3].frequency.value).toBe(783.99);
|
||||
expect(audio.oscillators[4].frequency.value).toBe(261.63);
|
||||
expect(audio.oscillators[0].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[4].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[0].start).toHaveBeenCalledWith(0);
|
||||
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0);
|
||||
|
||||
// --- V chord (G major) at t=1: sparkle D6, G4 + B4 + D5, hum G3. ---
|
||||
expect(audio.oscillators[5].frequency.value).toBe(1174.66);
|
||||
@@ -31,8 +31,8 @@ describe('MelodyTone', () => {
|
||||
expect(audio.oscillators[7].frequency.value).toBe(493.88);
|
||||
expect(audio.oscillators[8].frequency.value).toBe(587.33);
|
||||
expect(audio.oscillators[9].frequency.value).toBe(196.0);
|
||||
expect(audio.oscillators[5].start).toBeCalledWith(1);
|
||||
expect(audio.oscillators[9].start).toBeCalledWith(1);
|
||||
expect(audio.oscillators[5].start).toHaveBeenCalledWith(1);
|
||||
expect(audio.oscillators[9].start).toHaveBeenCalledWith(1);
|
||||
|
||||
// --- I chord (resolution, an octave higher) at t=2. ---
|
||||
expect(audio.oscillators[10].frequency.value).toBe(2093.0);
|
||||
@@ -40,21 +40,21 @@ describe('MelodyTone', () => {
|
||||
expect(audio.oscillators[12].frequency.value).toBe(783.99);
|
||||
expect(audio.oscillators[13].frequency.value).toBe(1046.5);
|
||||
expect(audio.oscillators[14].frequency.value).toBe(329.63);
|
||||
expect(audio.oscillators[10].start).toBeCalledWith(2);
|
||||
expect(audio.oscillators[14].start).toBeCalledWith(2);
|
||||
expect(audio.oscillators[10].start).toHaveBeenCalledWith(2);
|
||||
expect(audio.oscillators[14].start).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('should give the resolving chord a longer tail than the I and V chords', () => {
|
||||
new MelodyTone(0).start();
|
||||
|
||||
// I and V chords use default fundDecay=0.6, humDecay=1.1.
|
||||
expect(audio.gainParams[1].setTargetAtTime).toBeCalledWith(0, 0.005, 0.6);
|
||||
expect(audio.gainParams[4].setTargetAtTime).toBeCalledWith(0, 0.005, 1.1);
|
||||
expect(audio.gainParams[6].setTargetAtTime).toBeCalledWith(0, 1.005, 0.6);
|
||||
expect(audio.gainParams[9].setTargetAtTime).toBeCalledWith(0, 1.005, 1.1);
|
||||
expect(audio.gainParams[1].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.6);
|
||||
expect(audio.gainParams[4].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 1.1);
|
||||
expect(audio.gainParams[6].setTargetAtTime).toHaveBeenCalledWith(0, 1.005, 0.6);
|
||||
expect(audio.gainParams[9].setTargetAtTime).toHaveBeenCalledWith(0, 1.005, 1.1);
|
||||
|
||||
// Final I chord overrides to fundDecay=0.9, humDecay=1.4.
|
||||
expect(audio.gainParams[11].setTargetAtTime).toBeCalledWith(0, 2.005, 0.9);
|
||||
expect(audio.gainParams[14].setTargetAtTime).toBeCalledWith(0, 2.005, 1.4);
|
||||
expect(audio.gainParams[11].setTargetAtTime).toHaveBeenCalledWith(0, 2.005, 0.9);
|
||||
expect(audio.gainParams[14].setTargetAtTime).toHaveBeenCalledWith(0, 2.005, 1.4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,30 +19,38 @@ describe('WestminsterTone', () => {
|
||||
|
||||
// E5 (659.25) at t=0.
|
||||
expect(audio.oscillators[1].frequency.value).toBe(659.25);
|
||||
expect(audio.oscillators[1].start).toBeCalledWith(0);
|
||||
expect(audio.oscillators[1].start).toHaveBeenCalledWith(0);
|
||||
|
||||
// D5 (587.33) at t=0.55.
|
||||
expect(audio.oscillators[4].frequency.value).toBe(587.33);
|
||||
expect(audio.oscillators[4].start).toBeCalledWith(0.55);
|
||||
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0.55);
|
||||
|
||||
// C5 (523.25) at t=1.1.
|
||||
expect(audio.oscillators[7].frequency.value).toBe(523.25);
|
||||
expect(audio.oscillators[7].start).toBeCalledWith(1.1);
|
||||
expect(audio.oscillators[7].start).toHaveBeenCalledWith(1.1);
|
||||
|
||||
// G4 (392.0) at t=1.65 -- the resolution.
|
||||
expect(audio.oscillators[10].frequency.value).toBe(392.0);
|
||||
expect(audio.oscillators[10].start).toBeCalledWith(1.65);
|
||||
expect(audio.oscillators[10].start).toHaveBeenCalledWith(1.65);
|
||||
});
|
||||
|
||||
it('should give the resolving G4 a longer bell tail than the other strikes', () => {
|
||||
new WestminsterTone(0).start();
|
||||
|
||||
// First three strikes use default decay (fundDecay=0.6, humDecay=1.0).
|
||||
expect(audio.gainParams[1].setTargetAtTime).toBeCalledWith(0, 0.005, 0.6);
|
||||
expect(audio.gainParams[2].setTargetAtTime).toBeCalledWith(0, 0.005, 1.0);
|
||||
expect(audio.gainParams[1].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.6);
|
||||
expect(audio.gainParams[2].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 1.0);
|
||||
|
||||
// G4 (final strike) overrides to fundDecay=0.9, humDecay=1.4.
|
||||
expect(audio.gainParams[10].setTargetAtTime).toBeCalledWith(0, 1.65 + 0.005, 0.9);
|
||||
expect(audio.gainParams[11].setTargetAtTime).toBeCalledWith(0, 1.65 + 0.005, 1.4);
|
||||
expect(audio.gainParams[10].setTargetAtTime).toHaveBeenCalledWith(
|
||||
0,
|
||||
1.65 + 0.005,
|
||||
0.9,
|
||||
);
|
||||
expect(audio.gainParams[11].setTargetAtTime).toHaveBeenCalledWith(
|
||||
0,
|
||||
1.65 + 0.005,
|
||||
1.4,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user