Skip to content

Commit

Permalink
test: replace jest with vitest (#10472)
Browse files Browse the repository at this point in the history
* chore: vitest config

* feat: vitest

* fix: do not actually create ws

* chore: config

* chore: lockfile

* chore: revert downgrade, up node

* chore: package - 'git add -A'

* chore: delete mock-socket

* chore: delete mock-socket

* fix: lockfile

---------

Co-authored-by: almeidx <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 6, 2024
1 parent bb04e09 commit 24128a3
Show file tree
Hide file tree
Showing 21 changed files with 267 additions and 219 deletions.
1 change: 0 additions & 1 deletion packages/voice/__mocks__/ws.js

This file was deleted.

94 changes: 56 additions & 38 deletions packages/voice/__tests__/AudioPlayer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Buffer } from 'node:buffer';
import { once } from 'node:events';
import process from 'node:process';
import { Readable } from 'node:stream';
import { describe, test, expect, vitest, type Mock, beforeEach, afterEach } from 'vitest';
import { addAudioPlayer, deleteAudioPlayer } from '../src/DataStore';
import { VoiceConnection, VoiceConnectionStatus } from '../src/VoiceConnection';
import type { AudioPlayer } from '../src/audio/AudioPlayer';
Expand All @@ -13,14 +14,31 @@ import { AudioPlayerError } from '../src/audio/AudioPlayerError';
import { AudioResource } from '../src/audio/AudioResource';
import { NoSubscriberBehavior } from '../src/index';

jest.mock('../src/DataStore');
jest.mock('../src/VoiceConnection');
jest.mock('../src/audio/AudioPlayerError');
vitest.mock('../src/DataStore', () => {
return {
addAudioPlayer: vitest.fn(),
deleteAudioPlayer: vitest.fn(),
};
});

const addAudioPlayerMock = addAudioPlayer as unknown as jest.Mock<typeof addAudioPlayer>;
const deleteAudioPlayerMock = deleteAudioPlayer as unknown as jest.Mock<typeof deleteAudioPlayer>;
const AudioPlayerErrorMock = AudioPlayerError as unknown as jest.Mock<typeof AudioPlayerError>;
const VoiceConnectionMock = VoiceConnection as unknown as jest.Mock<VoiceConnection>;
vitest.mock('../src/VoiceConnection', async (importOriginal) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = await importOriginal<typeof import('../src/VoiceConnection')>();
const VoiceConnection = vitest.fn();
VoiceConnection.prototype.setSpeaking = vitest.fn();
VoiceConnection.prototype.dispatchAudio = vitest.fn();
VoiceConnection.prototype.prepareAudioPacket = vitest.fn();
return {
...actual,
VoiceConnection,
};
});

vitest.mock('../src/audio/AudioPlayerError', () => {
return {
AudioPlayerError: vitest.fn(),
};
});

function* silence() {
while (true) {
Expand All @@ -29,15 +47,15 @@ function* silence() {
}

function createVoiceConnectionMock() {
const connection = new VoiceConnectionMock();
const connection = new VoiceConnection({} as any, {} as any);
connection.state = {
status: VoiceConnectionStatus.Signalling,
adapter: {
sendPayload: jest.fn(),
destroy: jest.fn(),
sendPayload: vitest.fn(),
destroy: vitest.fn(),
},
};
connection.subscribe = jest.fn((player) => player['subscribe'](connection));
connection.subscribe = vitest.fn((player) => player['subscribe'](connection));
return connection;
}

Expand All @@ -57,10 +75,7 @@ async function started(resource: AudioResource) {
let player: AudioPlayer | undefined;

beforeEach(() => {
AudioPlayerErrorMock.mockReset();
VoiceConnectionMock.mockReset();
addAudioPlayerMock.mockReset();
deleteAudioPlayerMock.mockReset();
vitest.resetAllMocks();
});

afterEach(() => {
Expand All @@ -71,8 +86,8 @@ describe('State transitions', () => {
test('Starts in Idle state', () => {
player = createAudioPlayer();
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(addAudioPlayerMock).toBeCalledTimes(0);
expect(deleteAudioPlayerMock).toBeCalledTimes(0);
expect(addAudioPlayer).toBeCalledTimes(0);
expect(deleteAudioPlayer).toBeCalledTimes(0);
});

test('Playing resource with pausing and resuming', async () => {
Expand All @@ -86,11 +101,11 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(player.unpause()).toEqual(false);
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(addAudioPlayerMock).toBeCalledTimes(0);
expect(addAudioPlayer).toBeCalledTimes(0);

player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(addAudioPlayer).toBeCalledTimes(1);

// Expect pause() to return true and transition to paused state
expect(player.pause()).toEqual(true);
Expand All @@ -109,7 +124,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);

// The audio player should not have been deleted throughout these changes
expect(deleteAudioPlayerMock).toBeCalledTimes(0);
expect(deleteAudioPlayer).toBeCalledTimes(0);
});

test('Playing to Stopping', async () => {
Expand All @@ -122,13 +137,13 @@ describe('State transitions', () => {

player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(deleteAudioPlayerMock).toBeCalledTimes(0);
expect(addAudioPlayer).toBeCalledTimes(1);
expect(deleteAudioPlayer).toBeCalledTimes(0);

expect(player.stop()).toEqual(true);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(deleteAudioPlayerMock).toBeCalledTimes(0);
expect(addAudioPlayer).toBeCalledTimes(1);
expect(deleteAudioPlayer).toBeCalledTimes(0);
expect(resource.silenceRemaining).toEqual(5);
});

Expand All @@ -142,8 +157,8 @@ describe('State transitions', () => {
await started(resource);

expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toHaveBeenCalled();
expect(deleteAudioPlayerMock).not.toHaveBeenCalled();
expect(addAudioPlayer).toHaveBeenCalled();
expect(deleteAudioPlayer).not.toHaveBeenCalled();
});

describe('NoSubscriberBehavior transitions', () => {
Expand Down Expand Up @@ -188,11 +203,11 @@ describe('State transitions', () => {
player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Stop } });

player.play(resource);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true);
player['_stepPrepare']();
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(deleteAudioPlayerMock).toBeCalledTimes(1);
expect(deleteAudioPlayer).toBeCalledTimes(1);
});
});

Expand All @@ -217,7 +232,7 @@ describe('State transitions', () => {

player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true);

// Run through a few packet cycles
Expand All @@ -241,7 +256,8 @@ describe('State transitions', () => {
expect(connection.dispatchAudio).toHaveBeenCalledTimes(6);
await wait();
player['_stepPrepare']();
const prepareAudioPacket = connection.prepareAudioPacket as unknown as jest.Mock<
const prepareAudioPacket = connection.prepareAudioPacket as unknown as Mock<
[Buffer],
typeof connection.prepareAudioPacket
>;
expect(prepareAudioPacket).toHaveBeenCalledTimes(6);
Expand All @@ -251,7 +267,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(connection.setSpeaking).toBeCalledTimes(1);
expect(connection.setSpeaking).toHaveBeenLastCalledWith(false);
expect(deleteAudioPlayerMock).toHaveBeenCalledTimes(1);
expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);
});

test('stop() causes resource to use silence padding frames', async () => {
Expand All @@ -275,7 +291,7 @@ describe('State transitions', () => {

player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true);

player.stop();
Expand All @@ -298,15 +314,16 @@ describe('State transitions', () => {

await wait();
expect(player.checkPlayable()).toEqual(false);
const prepareAudioPacket = connection.prepareAudioPacket as unknown as jest.Mock<
const prepareAudioPacket = connection.prepareAudioPacket as unknown as Mock<
[Buffer],
typeof connection.prepareAudioPacket
>;
expect(prepareAudioPacket).toHaveBeenCalledTimes(5);

expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(connection.setSpeaking).toBeCalledTimes(1);
expect(connection.setSpeaking).toHaveBeenLastCalledWith(false);
expect(deleteAudioPlayerMock).toHaveBeenCalledTimes(1);
expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);
});

test('Plays silence 5 times for unreadable stream before quitting', async () => {
Expand All @@ -328,10 +345,11 @@ describe('State transitions', () => {

player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1);
expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true);

const prepareAudioPacket = connection.prepareAudioPacket as unknown as jest.Mock<
const prepareAudioPacket = connection.prepareAudioPacket as unknown as Mock<
[Buffer],
typeof connection.prepareAudioPacket
>;

Expand All @@ -351,7 +369,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(connection.setSpeaking).toBeCalledTimes(1);
expect(connection.setSpeaking).toHaveBeenLastCalledWith(false);
expect(deleteAudioPlayerMock).toHaveBeenCalledTimes(1);
expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);
});

test('checkPlayable() transitions to Idle for unreadable stream', async () => {
Expand Down Expand Up @@ -397,6 +415,6 @@ test('Propagates errors from streams', async () => {
const res = await once(player, 'error');
const playerError = res[0] as AudioPlayerError;
expect(playerError).toBeInstanceOf(AudioPlayerError);
expect(AudioPlayerErrorMock).toHaveBeenCalledWith(error, resource);
expect(AudioPlayerError).toHaveBeenCalledWith(error, resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
});
1 change: 1 addition & 0 deletions packages/voice/__tests__/AudioReceiveStream.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-promise-executor-return */
import { Buffer } from 'node:buffer';
import { describe, test, expect } from 'vitest';
import { SILENCE_FRAME } from '../src/audio/AudioPlayer';
import { AudioReceiveStream, EndBehaviorType } from '../src/receive/AudioReceiveStream';

Expand Down
12 changes: 7 additions & 5 deletions packages/voice/__tests__/AudioResource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { Buffer } from 'node:buffer';
import process from 'node:process';
import { PassThrough, Readable } from 'node:stream';
import { opus, VolumeTransformer } from 'prism-media';
import { describe, test, expect, vitest, type MockedFunction, beforeAll, beforeEach } from 'vitest';
import { SILENCE_FRAME } from '../src/audio/AudioPlayer';
import { AudioResource, createAudioResource, NO_CONSTRAINT, VOLUME_CONSTRAINT } from '../src/audio/AudioResource';
import { findPipeline as _findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph';

jest.mock('prism-media');
jest.mock('../src/audio/TransformerGraph');
vitest.mock('../src/audio/TransformerGraph');

async function wait() {
// eslint-disable-next-line no-promise-executor-return
Expand All @@ -22,7 +22,7 @@ async function started(resource: AudioResource) {
return resource;
}

const findPipeline = _findPipeline as unknown as jest.MockedFunction<typeof _findPipeline>;
const findPipeline = _findPipeline as unknown as MockedFunction<typeof _findPipeline>;

beforeAll(() => {
// @ts-expect-error: No type
Expand All @@ -37,7 +37,8 @@ beforeAll(() => {
if (constraint === VOLUME_CONSTRAINT) {
base.push({
cost: 1,
transformer: () => new VolumeTransformer({} as any),
// Transformer type shouldn't matter: we are not testing prism-media, but rather the expectation that the stream is VolumeTransformer
transformer: () => new VolumeTransformer({ type: 's16le' } as any),
type: TransformerType.InlineVolume,
});
}
Expand Down Expand Up @@ -96,7 +97,8 @@ describe('createAudioResource', () => {
});

test('Infers from VolumeTransformer', () => {
const stream = new VolumeTransformer({} as any);
// Transformer type shouldn't matter: we are not testing prism-media, but rather the expectation that the stream is VolumeTransformer
const stream = new VolumeTransformer({ type: 's16le' } as any);
const resource = createAudioResource(stream, { inlineVolume: true });
expect(findPipeline).toHaveBeenCalledWith(StreamType.Raw, NO_CONSTRAINT);
expect(resource.volume).toEqual(stream);
Expand Down
21 changes: 11 additions & 10 deletions packages/voice/__tests__/DataStore.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/dot-notation */
import { GatewayOpcodes } from 'discord-api-types/v10';
import { describe, test, expect, vitest, type Mocked, beforeEach } from 'vitest';
import * as DataStore from '../src/DataStore';
import type { VoiceConnection } from '../src/VoiceConnection';
import * as _AudioPlayer from '../src/audio/AudioPlayer';

jest.mock('../src/VoiceConnection');
jest.mock('../src/audio/AudioPlayer');
vitest.mock('../src/VoiceConnection');
vitest.mock('../src/audio/AudioPlayer');

const AudioPlayer = _AudioPlayer as unknown as jest.Mocked<typeof _AudioPlayer>;
const AudioPlayer = _AudioPlayer as unknown as Mocked<typeof _AudioPlayer>;

function createVoiceConnection(joinConfig: Pick<DataStore.JoinConfig, 'group' | 'guildId'>): VoiceConnection {
return {
Expand Down Expand Up @@ -71,8 +72,8 @@ describe('DataStore', () => {
});
test('Managing Audio Players', async () => {
const player = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer());
const dispatchSpy = jest.spyOn(player as any, '_stepDispatch');
const prepareSpy = jest.spyOn(player as any, '_stepPrepare');
const dispatchSpy = vitest.spyOn(player as any, '_stepDispatch');
const prepareSpy = vitest.spyOn(player as any, '_stepPrepare');
expect(DataStore.hasAudioPlayer(player)).toEqual(true);
expect(DataStore.addAudioPlayer(player)).toEqual(player);
DataStore.deleteAudioPlayer(player);
Expand All @@ -87,12 +88,12 @@ describe('DataStore', () => {
test('Preparing Audio Frames', async () => {
// Test functional player
const player2 = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer());
player2['checkPlayable'] = jest.fn(() => true);
player2['checkPlayable'] = vitest.fn(() => true);
const player3 = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer());
const dispatchSpy2 = jest.spyOn(player2 as any, '_stepDispatch');
const prepareSpy2 = jest.spyOn(player2 as any, '_stepPrepare');
const dispatchSpy3 = jest.spyOn(player3 as any, '_stepDispatch');
const prepareSpy3 = jest.spyOn(player3 as any, '_stepPrepare');
const dispatchSpy2 = vitest.spyOn(player2 as any, '_stepDispatch');
const prepareSpy2 = vitest.spyOn(player2 as any, '_stepPrepare');
const dispatchSpy3 = vitest.spyOn(player3 as any, '_stepDispatch');
const prepareSpy3 = vitest.spyOn(player3 as any, '_stepPrepare');
await waitForEventLoop();
DataStore.deleteAudioPlayer(player2);
await waitForEventLoop();
Expand Down
1 change: 1 addition & 0 deletions packages/voice/__tests__/SSRCMap.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type EventEmitter, once } from 'node:events';
import process from 'node:process';
import { describe, test, expect } from 'vitest';
import { SSRCMap, type VoiceUserData } from '../src/receive/SSRCMap';

async function onceOrThrow<Emitter extends EventEmitter>(target: Emitter, event: string, after: number) {
Expand Down
7 changes: 4 additions & 3 deletions packages/voice/__tests__/Secretbox.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { test, expect, vitest } from 'vitest';
import { methods } from '../src/util/Secretbox';

jest.mock('tweetnacl');
vitest.mock('tweetnacl');

test('Does not throw error with a package installed', () => {
// @ts-expect-error: Unknown type
expect(() => methods.open()).not.toThrowError();
// @ts-expect-error We are testing
expect(() => methods.open()).toThrow(TypeError);
});
9 changes: 5 additions & 4 deletions packages/voice/__tests__/SpeakingMap.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, test, expect, vitest } from 'vitest';
import { SpeakingMap } from '../src/receive/SpeakingMap';
import { noop } from '../src/util/util';

jest.useFakeTimers();
vitest.useFakeTimers();

describe('SpeakingMap', () => {
test('Emits start and end', () => {
Expand All @@ -17,17 +18,17 @@ describe('SpeakingMap', () => {
for (let index = 0; index < 10; index++) {
speaking.onPacket(userId);
setTimeout(noop, SpeakingMap.DELAY / 2);
jest.advanceTimersToNextTimer();
vitest.advanceTimersToNextTimer();

expect(starts).toEqual([userId]);
expect(ends).toEqual([]);
}

jest.advanceTimersToNextTimer();
vitest.advanceTimersToNextTimer();
expect(ends).toEqual([userId]);

speaking.onPacket(userId);
jest.advanceTimersToNextTimer();
vitest.advanceTimersToNextTimer();
expect(starts).toEqual([userId, userId]);
});
});
1 change: 1 addition & 0 deletions packages/voice/__tests__/TransformerGraph.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-nocheck
import { describe, test, expect } from 'vitest';
import { findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph';

const noConstraint = () => true;
Expand Down
Loading

0 comments on commit 24128a3

Please sign in to comment.