Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: replace jest with vitest #10472

Merged
merged 11 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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', () => {
nyapat marked this conversation as resolved.
Show resolved Hide resolved
// @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