From 24128a3c459ed0c3eb0932308f03ecc55e3c60f1 Mon Sep 17 00:00:00 2001 From: pat <73502164+nyapat@users.noreply.github.com> Date: Mon, 7 Oct 2024 01:26:53 +1100 Subject: [PATCH] test: replace jest with vitest (#10472) * 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 Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- packages/voice/__mocks__/ws.js | 1 - packages/voice/__tests__/AudioPlayer.test.ts | 94 +++++++++------- .../__tests__/AudioReceiveStream.test.ts | 1 + .../voice/__tests__/AudioResource.test.ts | 12 ++- packages/voice/__tests__/DataStore.test.ts | 21 ++-- packages/voice/__tests__/SSRCMap.test.ts | 1 + packages/voice/__tests__/Secretbox.test.ts | 7 +- packages/voice/__tests__/SpeakingMap.test.ts | 9 +- .../voice/__tests__/TransformerGraph.test.ts | 1 + .../voice/__tests__/VoiceConnection.test.ts | 99 +++++++++-------- .../voice/__tests__/VoiceReceiver.test.ts | 35 +++--- .../voice/__tests__/VoiceUDPSocket.test.ts | 25 ++--- .../voice/__tests__/VoiceWebSocket.test.ts | 3 +- packages/voice/__tests__/abortAfter.test.ts | 7 +- packages/voice/__tests__/demuxProbe.test.ts | 11 +- packages/voice/__tests__/entersState.test.ts | 9 +- .../voice/__tests__/joinVoiceChannel.test.ts | 5 +- packages/voice/babel.config.js | 17 --- packages/voice/jest.config.js | 11 -- packages/voice/package.json | 17 ++- pnpm-lock.yaml | 100 ++++++++++++------ 21 files changed, 267 insertions(+), 219 deletions(-) delete mode 100644 packages/voice/__mocks__/ws.js delete mode 100644 packages/voice/babel.config.js delete mode 100644 packages/voice/jest.config.js diff --git a/packages/voice/__mocks__/ws.js b/packages/voice/__mocks__/ws.js deleted file mode 100644 index be3a43823..000000000 --- a/packages/voice/__mocks__/ws.js +++ /dev/null @@ -1 +0,0 @@ -export { WebSocket as default } from 'mock-socket'; diff --git a/packages/voice/__tests__/AudioPlayer.test.ts b/packages/voice/__tests__/AudioPlayer.test.ts index 6b254ad48..7d417d8b8 100644 --- a/packages/voice/__tests__/AudioPlayer.test.ts +++ b/packages/voice/__tests__/AudioPlayer.test.ts @@ -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'; @@ -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; -const deleteAudioPlayerMock = deleteAudioPlayer as unknown as jest.Mock; -const AudioPlayerErrorMock = AudioPlayerError as unknown as jest.Mock; -const VoiceConnectionMock = VoiceConnection as unknown as jest.Mock; +vitest.mock('../src/VoiceConnection', async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importOriginal(); + 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) { @@ -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; } @@ -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(() => { @@ -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 () => { @@ -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); @@ -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 () => { @@ -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); }); @@ -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', () => { @@ -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); }); }); @@ -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 @@ -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); @@ -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 () => { @@ -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(); @@ -298,7 +314,8 @@ 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); @@ -306,7 +323,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('Plays silence 5 times for unreadable stream before quitting', async () => { @@ -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 >; @@ -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 () => { @@ -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); }); diff --git a/packages/voice/__tests__/AudioReceiveStream.test.ts b/packages/voice/__tests__/AudioReceiveStream.test.ts index 415ec1bf2..8823ff641 100644 --- a/packages/voice/__tests__/AudioReceiveStream.test.ts +++ b/packages/voice/__tests__/AudioReceiveStream.test.ts @@ -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'; diff --git a/packages/voice/__tests__/AudioResource.test.ts b/packages/voice/__tests__/AudioResource.test.ts index 383195eac..21043c5e1 100644 --- a/packages/voice/__tests__/AudioResource.test.ts +++ b/packages/voice/__tests__/AudioResource.test.ts @@ -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 @@ -22,7 +22,7 @@ async function started(resource: AudioResource) { return resource; } -const findPipeline = _findPipeline as unknown as jest.MockedFunction; +const findPipeline = _findPipeline as unknown as MockedFunction; beforeAll(() => { // @ts-expect-error: No type @@ -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, }); } @@ -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); diff --git a/packages/voice/__tests__/DataStore.test.ts b/packages/voice/__tests__/DataStore.test.ts index 5ed445ab8..38aeec7fd 100644 --- a/packages/voice/__tests__/DataStore.test.ts +++ b/packages/voice/__tests__/DataStore.test.ts @@ -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; +const AudioPlayer = _AudioPlayer as unknown as Mocked; function createVoiceConnection(joinConfig: Pick): VoiceConnection { return { @@ -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); @@ -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(); diff --git a/packages/voice/__tests__/SSRCMap.test.ts b/packages/voice/__tests__/SSRCMap.test.ts index 252445f46..4927ee091 100644 --- a/packages/voice/__tests__/SSRCMap.test.ts +++ b/packages/voice/__tests__/SSRCMap.test.ts @@ -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(target: Emitter, event: string, after: number) { diff --git a/packages/voice/__tests__/Secretbox.test.ts b/packages/voice/__tests__/Secretbox.test.ts index a01dd2f3b..0f523d90d 100644 --- a/packages/voice/__tests__/Secretbox.test.ts +++ b/packages/voice/__tests__/Secretbox.test.ts @@ -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); }); diff --git a/packages/voice/__tests__/SpeakingMap.test.ts b/packages/voice/__tests__/SpeakingMap.test.ts index 1a7bb9cc9..e0a668b81 100644 --- a/packages/voice/__tests__/SpeakingMap.test.ts +++ b/packages/voice/__tests__/SpeakingMap.test.ts @@ -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', () => { @@ -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]); }); }); diff --git a/packages/voice/__tests__/TransformerGraph.test.ts b/packages/voice/__tests__/TransformerGraph.test.ts index ac3567328..48fcb4c85 100644 --- a/packages/voice/__tests__/TransformerGraph.test.ts +++ b/packages/voice/__tests__/TransformerGraph.test.ts @@ -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; diff --git a/packages/voice/__tests__/VoiceConnection.test.ts b/packages/voice/__tests__/VoiceConnection.test.ts index 43ca2d59b..7e20facb6 100644 --- a/packages/voice/__tests__/VoiceConnection.test.ts +++ b/packages/voice/__tests__/VoiceConnection.test.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/dot-notation */ // @ts-nocheck import { EventEmitter } from 'node:events'; +import { vitest, describe, test, expect, beforeEach } from 'vitest'; import * as _DataStore from '../src/DataStore'; import { createVoiceConnection, @@ -14,34 +15,42 @@ import { } from '../src/VoiceConnection'; import * as _AudioPlayer from '../src/audio/AudioPlayer'; import { PlayerSubscription as _PlayerSubscription } from '../src/audio/PlayerSubscription'; -import * as _Networking from '../src/networking/Networking'; +import * as Networking from '../src/networking/Networking'; import type { DiscordGatewayAdapterLibraryMethods } from '../src/util/adapter'; -jest.mock('../src/audio/AudioPlayer'); -jest.mock('../src/audio/PlayerSubscription'); -jest.mock('../src/DataStore'); -jest.mock('../src/networking/Networking'); +vitest.mock('../src/audio/AudioPlayer'); +vitest.mock('../src/audio/PlayerSubscription'); +vitest.mock('../src/DataStore'); +vitest.mock('../src/networking/Networking', async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importOriginal(); + const Networking = actual.Networking; + Networking.prototype.createWebSocket = vitest.fn(); + return { + ...actual, + Networking, + }; +}); -const DataStore = _DataStore as unknown as jest.Mocked; -const Networking = _Networking as unknown as jest.Mocked; -const AudioPlayer = _AudioPlayer as unknown as jest.Mocked; -const PlayerSubscription = _PlayerSubscription as unknown as jest.Mock<_PlayerSubscription>; +const DataStore = _DataStore as unknown as vitest.Mocked; +const AudioPlayer = _AudioPlayer as unknown as vitest.Mocked; +const PlayerSubscription = _PlayerSubscription as unknown as vitest.Mock<_PlayerSubscription>; -Networking.Networking.mockImplementation(function mockedConstructor() { - this.state = {}; - return this; +const _NetworkingClass = Networking.Networking; +vitest.spyOn(Networking, 'Networking').mockImplementation((...args) => { + return new _NetworkingClass(...args); }); function createFakeAdapter() { - const sendPayload = jest.fn(); + const sendPayload = vitest.fn(); sendPayload.mockReturnValue(true); - const destroy = jest.fn(); + const destroy = vitest.fn(); const libMethods: Partial = {}; return { sendPayload, destroy, libMethods, - creator: jest.fn((methods) => { + creator: vitest.fn((methods) => { Object.assign(libMethods, methods); return { sendPayload, @@ -124,7 +133,7 @@ describe('createVoiceConnection', () => { adapterCreator: existingAdapter.creator, }); - const stateSetter = jest.spyOn(existingVoiceConnection, 'state', 'set'); + const stateSetter = vitest.spyOn(existingVoiceConnection, 'state', 'set'); // @ts-expect-error: We're testing DataStore.getVoiceConnection.mockImplementation((guildId, group = 'default') => @@ -163,7 +172,7 @@ describe('createVoiceConnection', () => { reason: VoiceConnectionDisconnectReason.EndpointRemoved, }; - const rejoinSpy = jest.spyOn(existingVoiceConnection, 'rejoin'); + const rejoinSpy = vitest.spyOn(existingVoiceConnection, 'rejoin'); // @ts-expect-error: We're testing DataStore.getVoiceConnection.mockImplementation((guildId, group = 'default') => @@ -222,7 +231,7 @@ describe('createVoiceConnection', () => { describe('VoiceConnection#addServerPacket', () => { test('Stores the packet and attempts to configure networking', () => { const { voiceConnection } = createFakeVoiceConnection(); - voiceConnection.configureNetworking = jest.fn(); + voiceConnection.configureNetworking = vitest.fn(); const dummy = { endpoint: 'discord.com', guild_id: 123, @@ -236,7 +245,7 @@ describe('VoiceConnection#addServerPacket', () => { test('Overwrites existing packet', () => { const { voiceConnection } = createFakeVoiceConnection(); voiceConnection['packets'].server = Symbol('old') as any; - voiceConnection.configureNetworking = jest.fn(); + voiceConnection.configureNetworking = vitest.fn(); const dummy = { endpoint: 'discord.com', guild_id: 123, @@ -250,7 +259,7 @@ describe('VoiceConnection#addServerPacket', () => { test('Disconnects when given a null endpoint', () => { const { voiceConnection } = createFakeVoiceConnection(); voiceConnection['packets'].server = Symbol('old') as any; - voiceConnection.configureNetworking = jest.fn(); + voiceConnection.configureNetworking = vitest.fn(); const dummy = { endpoint: null, guild_id: 123, @@ -344,7 +353,7 @@ describe('VoiceConnection#configureNetworking', () => { adapter, }); expect((voiceConnection.state as unknown as VoiceConnectionConnectingState).networking).toBeInstanceOf( - Networking.Networking, + _NetworkingClass, ); }); }); @@ -399,24 +408,24 @@ describe('VoiceConnection#onNetworkingClose', () => { describe('VoiceConnection#onNetworkingStateChange', () => { test('Does nothing when status code identical', () => { const { voiceConnection } = createFakeVoiceConnection(); - const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); + const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set'); voiceConnection['onNetworkingStateChange']( - { code: _Networking.NetworkingStatusCode.Ready } as any, - { code: _Networking.NetworkingStatusCode.Ready } as any, + { code: Networking.NetworkingStatusCode.Ready } as any, + { code: Networking.NetworkingStatusCode.Ready } as any, ); voiceConnection['onNetworkingStateChange']( - { code: _Networking.NetworkingStatusCode.Closed } as any, - { code: _Networking.NetworkingStatusCode.Closed } as any, + { code: Networking.NetworkingStatusCode.Closed } as any, + { code: Networking.NetworkingStatusCode.Closed } as any, ); expect(stateSetter).not.toHaveBeenCalled(); }); test('Does nothing when not in Ready or Connecting states', () => { const { voiceConnection } = createFakeVoiceConnection(); - const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); + const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set'); const call = [ - { code: _Networking.NetworkingStatusCode.Ready } as any, - { code: _Networking.NetworkingStatusCode.Closed } as any, + { code: Networking.NetworkingStatusCode.Ready } as any, + { code: Networking.NetworkingStatusCode.Closed } as any, ]; voiceConnection['_state'] = { status: VoiceConnectionStatus.Signalling } as any; voiceConnection['onNetworkingStateChange'](call[0], call[1]); @@ -429,7 +438,7 @@ describe('VoiceConnection#onNetworkingStateChange', () => { test('Transitions to Ready', () => { const { voiceConnection } = createFakeVoiceConnection(); - const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); + const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set'); voiceConnection['_state'] = { ...(voiceConnection.state as VoiceConnectionSignallingState), status: VoiceConnectionStatus.Connecting, @@ -437,8 +446,8 @@ describe('VoiceConnection#onNetworkingStateChange', () => { }; voiceConnection['onNetworkingStateChange']( - { code: _Networking.NetworkingStatusCode.Closed } as any, - { code: _Networking.NetworkingStatusCode.Ready } as any, + { code: Networking.NetworkingStatusCode.Closed } as any, + { code: Networking.NetworkingStatusCode.Ready } as any, ); expect(stateSetter).toHaveBeenCalledTimes(1); @@ -447,7 +456,7 @@ describe('VoiceConnection#onNetworkingStateChange', () => { test('Transitions to Connecting', () => { const { voiceConnection } = createFakeVoiceConnection(); - const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); + const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set'); voiceConnection['_state'] = { ...(voiceConnection.state as VoiceConnectionSignallingState), status: VoiceConnectionStatus.Connecting, @@ -455,8 +464,8 @@ describe('VoiceConnection#onNetworkingStateChange', () => { }; voiceConnection['onNetworkingStateChange']( - { code: _Networking.NetworkingStatusCode.Ready } as any, - { code: _Networking.NetworkingStatusCode.Identifying } as any, + { code: Networking.NetworkingStatusCode.Ready } as any, + { code: Networking.NetworkingStatusCode.Identifying } as any, ); expect(stateSetter).toHaveBeenCalledTimes(1); @@ -598,7 +607,7 @@ describe('VoiceConnection#subscribe', () => { test('Does nothing in Destroyed state', () => { const { voiceConnection } = createFakeVoiceConnection(); const player = new AudioPlayer.AudioPlayer(); - player['subscribe'] = jest.fn(); + player['subscribe'] = vitest.fn(); voiceConnection.state = { status: VoiceConnectionStatus.Destroyed }; expect(voiceConnection.subscribe(player)).toBeUndefined(); expect(player['subscribe']).not.toHaveBeenCalled(); @@ -610,7 +619,7 @@ describe('VoiceConnection#subscribe', () => { const adapter = (voiceConnection.state as VoiceConnectionSignallingState).adapter; const player = new AudioPlayer.AudioPlayer(); const dummy = Symbol('dummy'); - player['subscribe'] = jest.fn().mockImplementation(() => dummy); + player['subscribe'] = vitest.fn().mockImplementation(() => dummy); expect(voiceConnection.subscribe(player)).toEqual(dummy); expect(player['subscribe']).toHaveBeenCalledWith(voiceConnection); expect(voiceConnection.state).toMatchObject({ @@ -624,7 +633,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { test('Does nothing in Destroyed state', () => { const { voiceConnection } = createFakeVoiceConnection(); const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer()); - subscription.unsubscribe = jest.fn(); + subscription.unsubscribe = vitest.fn(); voiceConnection.state = { status: VoiceConnectionStatus.Destroyed }; voiceConnection['onSubscriptionRemoved'](subscription); @@ -635,7 +644,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { test('Does nothing when subscription is not the same as the stored one', () => { const { voiceConnection } = createFakeVoiceConnection(); const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer()); - subscription.unsubscribe = jest.fn(); + subscription.unsubscribe = vitest.fn(); voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription }; voiceConnection['onSubscriptionRemoved'](Symbol('new subscription') as any); @@ -649,7 +658,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { test('Unsubscribes in a live state with matching subscription', () => { const { voiceConnection } = createFakeVoiceConnection(); const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer()); - subscription.unsubscribe = jest.fn(); + subscription.unsubscribe = vitest.fn(); voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription }; voiceConnection['onSubscriptionRemoved'](subscription); @@ -667,7 +676,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { const oldNetworking = new Networking.Networking({} as any, false); oldNetworking.state = { - code: _Networking.NetworkingStatusCode.Ready, + code: Networking.NetworkingStatusCode.Ready, connectionData: {} as any, connectionOptions: {} as any, udp: new EventEmitter() as any, @@ -697,7 +706,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { const oldNetworking = new Networking.Networking({} as any, false); oldNetworking.state = { - code: _Networking.NetworkingStatusCode.Ready, + code: Networking.NetworkingStatusCode.Ready, connectionData: {} as any, connectionOptions: {} as any, udp, @@ -726,7 +735,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { const newNetworking = new Networking.Networking({} as any, false); newNetworking.state = { - code: _Networking.NetworkingStatusCode.Ready, + code: Networking.NetworkingStatusCode.Ready, connectionData: {} as any, connectionOptions: {} as any, udp: new EventEmitter() as any, @@ -749,7 +758,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => { describe('Adapter', () => { test('onVoiceServerUpdate', () => { const { adapter, voiceConnection } = createFakeVoiceConnection(); - voiceConnection['addServerPacket'] = jest.fn(); + voiceConnection['addServerPacket'] = vitest.fn(); const dummy = Symbol('dummy') as any; adapter.libMethods.onVoiceServerUpdate!(dummy); expect(voiceConnection['addServerPacket']).toHaveBeenCalledWith(dummy); @@ -757,7 +766,7 @@ describe('Adapter', () => { test('onVoiceStateUpdate', () => { const { adapter, voiceConnection } = createFakeVoiceConnection(); - voiceConnection['addStatePacket'] = jest.fn(); + voiceConnection['addStatePacket'] = vitest.fn(); const dummy = Symbol('dummy') as any; adapter.libMethods.onVoiceStateUpdate!(dummy); expect(voiceConnection['addStatePacket']).toHaveBeenCalledWith(dummy); diff --git a/packages/voice/__tests__/VoiceReceiver.test.ts b/packages/voice/__tests__/VoiceReceiver.test.ts index f19c868c5..6cb2ec717 100644 --- a/packages/voice/__tests__/VoiceReceiver.test.ts +++ b/packages/voice/__tests__/VoiceReceiver.test.ts @@ -5,20 +5,27 @@ import { Buffer } from 'node:buffer'; import { once } from 'node:events'; import process from 'node:process'; import { VoiceOpcodes } from 'discord-api-types/voice/v4'; +import { describe, test, expect, vitest, beforeEach } from 'vitest'; import { RTP_PACKET_DESKTOP, RTP_PACKET_CHROME, RTP_PACKET_ANDROID } from '../__mocks__/rtp'; -import { VoiceConnection as _VoiceConnection, VoiceConnectionStatus } from '../src/VoiceConnection'; +import { VoiceConnection, VoiceConnectionStatus } from '../src/VoiceConnection'; import { VoiceReceiver } from '../src/receive/VoiceReceiver'; import { methods } from '../src/util/Secretbox'; -jest.mock('../src/VoiceConnection'); -jest.mock('../src/receive/SSRCMap'); +vitest.mock('../src/VoiceConnection', async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importOriginal(); + return { + ...actual, + VoiceConnection: vitest.fn(), + }; +}); -const openSpy = jest.spyOn(methods, 'open'); +vitest.mock('../src/receive/SSRCMap'); + +const openSpy = vitest.spyOn(methods, 'open'); openSpy.mockImplementation((buffer) => buffer); -const VoiceConnection = _VoiceConnection as unknown as jest.Mocked; - async function nextTick() { // eslint-disable-next-line no-promise-executor-return return new Promise((resolve) => process.nextTick(resolve)); @@ -56,9 +63,9 @@ describe('VoiceReceiver', () => { ['RTP Packet Chrome', RTP_PACKET_CHROME], ['RTP Packet Android', RTP_PACKET_ANDROID], ])('onUdpMessage: %s', async (testName, RTP_PACKET) => { - receiver['decrypt'] = jest.fn().mockImplementationOnce(() => RTP_PACKET.decrypted); + receiver['decrypt'] = vitest.fn().mockImplementationOnce(() => RTP_PACKET.decrypted); - const spy = jest.spyOn(receiver.ssrcMap, 'get'); + const spy = vitest.spyOn(receiver.ssrcMap, 'get'); spy.mockImplementation(() => ({ audioSSRC: RTP_PACKET.ssrc, userId: '123', @@ -76,9 +83,9 @@ describe('VoiceReceiver', () => { }); test('onUdpMessage: destroys stream on decrypt failure', async () => { - receiver['decrypt'] = jest.fn().mockImplementationOnce(() => null); + receiver['decrypt'] = vitest.fn().mockImplementationOnce(() => null); - const spy = jest.spyOn(receiver.ssrcMap, 'get'); + const spy = vitest.spyOn(receiver.ssrcMap, 'get'); spy.mockImplementation(() => ({ audioSSRC: RTP_PACKET_DESKTOP.ssrc, userId: '123', @@ -95,7 +102,7 @@ describe('VoiceReceiver', () => { }); test('subscribe: only allows one subscribe stream per SSRC', () => { - const spy = jest.spyOn(receiver.ssrcMap, 'get'); + const spy = vitest.spyOn(receiver.ssrcMap, 'get'); spy.mockImplementation(() => ({ audioSSRC: RTP_PACKET_DESKTOP.ssrc, userId: '123', @@ -107,7 +114,7 @@ describe('VoiceReceiver', () => { describe('onWsPacket', () => { test('CLIENT_DISCONNECT packet', () => { - const spy = jest.spyOn(receiver.ssrcMap, 'delete'); + const spy = vitest.spyOn(receiver.ssrcMap, 'delete'); receiver['onWsPacket']({ op: VoiceOpcodes.ClientDisconnect, d: { @@ -118,7 +125,7 @@ describe('VoiceReceiver', () => { }); test('SPEAKING packet', () => { - const spy = jest.spyOn(receiver.ssrcMap, 'update'); + const spy = vitest.spyOn(receiver.ssrcMap, 'update'); receiver['onWsPacket']({ op: VoiceOpcodes.Speaking, d: { @@ -134,7 +141,7 @@ describe('VoiceReceiver', () => { }); test('CLIENT_CONNECT packet', () => { - const spy = jest.spyOn(receiver.ssrcMap, 'update'); + const spy = vitest.spyOn(receiver.ssrcMap, 'update'); receiver['onWsPacket']({ op: VoiceOpcodes.ClientConnect, d: { diff --git a/packages/voice/__tests__/VoiceUDPSocket.test.ts b/packages/voice/__tests__/VoiceUDPSocket.test.ts index 0e65ad7f6..62e7b57de 100644 --- a/packages/voice/__tests__/VoiceUDPSocket.test.ts +++ b/packages/voice/__tests__/VoiceUDPSocket.test.ts @@ -2,12 +2,13 @@ import { Buffer } from 'node:buffer'; import { createSocket as _createSocket } from 'node:dgram'; import { EventEmitter } from 'node:events'; +import { describe, test, expect, vitest, beforeEach, afterEach } from 'vitest'; import { VoiceUDPSocket } from '../src/networking/VoiceUDPSocket'; -jest.mock('node:dgram'); -jest.useFakeTimers(); +vitest.mock('node:dgram'); +vitest.useFakeTimers(); -const createSocket = _createSocket as unknown as jest.Mock; +const createSocket = _createSocket as unknown as vitest.Mock; beforeEach(() => { createSocket.mockReset(); @@ -32,7 +33,7 @@ const VALID_RESPONSE = Buffer.from([ async function wait() { return new Promise((resolve) => { setImmediate(resolve); - jest.advanceTimersToNextTimer(); + vitest.advanceTimersToNextTimer(); }); } @@ -48,7 +49,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { */ test('Resolves and cleans up with a successful flow', async () => { const fake = new FakeSocket(); - fake.send = jest.fn().mockImplementation((buffer: Buffer, port: number, address: string) => { + fake.send = vitest.fn().mockImplementation((buffer: Buffer, port: number, address: string) => { fake.emit('message', VALID_RESPONSE); }); createSocket.mockImplementation((type) => fake as any); @@ -71,7 +72,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { test('Waits for a valid response in an unexpected flow', async () => { const fake = new FakeSocket(); const fakeResponse = Buffer.from([1, 2, 3, 4, 5]); - fake.send = jest.fn().mockImplementation(async (buffer: Buffer, port: number, address: string) => { + fake.send = vitest.fn().mockImplementation(async (buffer: Buffer, port: number, address: string) => { fake.emit('message', fakeResponse); await wait(); fake.emit('message', VALID_RESPONSE); @@ -91,7 +92,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { test('Rejects if socket closes before IP discovery can be completed', async () => { const fake = new FakeSocket(); - fake.send = jest.fn().mockImplementation(async (buffer: Buffer, port: number, address: string) => { + fake.send = vitest.fn().mockImplementation(async (buffer: Buffer, port: number, address: string) => { await wait(); fake.close(); }); @@ -104,7 +105,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { test('Stays alive when messages are echoed back', async () => { const fake = new FakeSocket(); - fake.send = jest.fn().mockImplementation(async (buffer: Buffer) => { + fake.send = vitest.fn().mockImplementation(async (buffer: Buffer) => { await wait(); fake.emit('message', buffer); }); @@ -115,7 +116,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { socket.on('close', () => (closed = true)); for (let index = 0; index < 30; index++) { - jest.advanceTimersToNextTimer(); + vitest.advanceTimersToNextTimer(); await wait(); } @@ -124,7 +125,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { test('Recovers from intermittent responses', async () => { const fake = new FakeSocket(); - const fakeSend = jest.fn(); + const fakeSend = vitest.fn(); fake.send = fakeSend; createSocket.mockImplementation(() => fake as any); socket = new VoiceUDPSocket({ ip: '1.2.3.4', port: 25_565 }); @@ -134,7 +135,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { socket.on('close', () => (closed = true)); for (let index = 0; index < 10; index++) { - jest.advanceTimersToNextTimer(); + vitest.advanceTimersToNextTimer(); await wait(); } @@ -144,7 +145,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => { }); expect(closed).toEqual(false); for (let index = 0; index < 30; index++) { - jest.advanceTimersToNextTimer(); + vitest.advanceTimersToNextTimer(); await wait(); } diff --git a/packages/voice/__tests__/VoiceWebSocket.test.ts b/packages/voice/__tests__/VoiceWebSocket.test.ts index 402139c48..87fc72ecb 100644 --- a/packages/voice/__tests__/VoiceWebSocket.test.ts +++ b/packages/voice/__tests__/VoiceWebSocket.test.ts @@ -1,6 +1,7 @@ import { type EventEmitter, once } from 'node:events'; import { VoiceOpcodes } from 'discord-api-types/voice/v4'; -import WS from 'jest-websocket-mock'; +import { describe, test, expect, beforeEach } from 'vitest'; +import WS from 'vitest-websocket-mock'; import { VoiceWebSocket } from '../src/networking/VoiceWebSocket'; beforeEach(() => { diff --git a/packages/voice/__tests__/abortAfter.test.ts b/packages/voice/__tests__/abortAfter.test.ts index be98b4e5e..9c7b6eaea 100644 --- a/packages/voice/__tests__/abortAfter.test.ts +++ b/packages/voice/__tests__/abortAfter.test.ts @@ -1,15 +1,16 @@ +import { describe, test, expect, vitest } from 'vitest'; import { abortAfter } from '../src/util/abortAfter'; -jest.useFakeTimers(); +vitest.useFakeTimers(); -const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); +const clearTimeoutSpy = vitest.spyOn(global, 'clearTimeout'); describe('abortAfter', () => { test('Aborts after the given delay', () => { const [ac, signal] = abortAfter(100); expect(ac.signal).toEqual(signal); expect(signal.aborted).toEqual(false); - jest.runAllTimers(); + vitest.runAllTimers(); expect(signal.aborted).toEqual(true); }); diff --git a/packages/voice/__tests__/demuxProbe.test.ts b/packages/voice/__tests__/demuxProbe.test.ts index d9c65a004..cec2107c5 100644 --- a/packages/voice/__tests__/demuxProbe.test.ts +++ b/packages/voice/__tests__/demuxProbe.test.ts @@ -4,13 +4,14 @@ import EventEmitter, { once } from 'node:events'; import process from 'node:process'; import { Readable } from 'node:stream'; import { opus as _opus } from 'prism-media'; +import { describe, test, expect, vitest, type Mock, beforeAll, beforeEach } from 'vitest'; import { StreamType } from '../src/audio/index'; import { demuxProbe } from '../src/util/demuxProbe'; -jest.mock('prism-media'); +vitest.mock('prism-media'); -const WebmDemuxer = _opus.WebmDemuxer as unknown as jest.Mock<_opus.WebmDemuxer>; -const OggDemuxer = _opus.OggDemuxer as unknown as jest.Mock<_opus.OggDemuxer>; +const WebmDemuxer = _opus.WebmDemuxer as unknown as Mock<_opus.WebmDemuxer>; +const OggDemuxer = _opus.OggDemuxer as unknown as Mock<_opus.OggDemuxer>; async function nextTick() { // eslint-disable-next-line no-promise-executor-return @@ -47,8 +48,8 @@ async function collectStream(stream: Readable): Promise { } describe('demuxProbe', () => { - const webmWrite: jest.Mock<(buffer: Buffer) => void> = jest.fn(); - const oggWrite: jest.Mock<(buffer: Buffer) => void> = jest.fn(); + const webmWrite: Mock<(buffer: Buffer) => void> = vitest.fn(); + const oggWrite: Mock<(buffer: Buffer) => void> = vitest.fn(); beforeAll(() => { WebmDemuxer.prototype = { diff --git a/packages/voice/__tests__/entersState.test.ts b/packages/voice/__tests__/entersState.test.ts index 3b8cb626f..b95c68e4f 100644 --- a/packages/voice/__tests__/entersState.test.ts +++ b/packages/voice/__tests__/entersState.test.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'node:events'; import process from 'node:process'; +import { describe, test, expect, vitest, beforeEach } from 'vitest'; import { VoiceConnectionStatus, type VoiceConnection } from '../src/VoiceConnection'; import { entersState } from '../src/util/entersState'; @@ -10,12 +11,12 @@ function createFakeVoiceConnection(status = VoiceConnectionStatus.Signalling) { } beforeEach(() => { - jest.useFakeTimers(); + vitest.useFakeTimers(); }); describe('entersState', () => { test('Returns the target once the state has been entered before timeout', async () => { - jest.useRealTimers(); + vitest.useRealTimers(); const vc = createFakeVoiceConnection(); process.nextTick(() => vc.emit(VoiceConnectionStatus.Ready, null as any, null as any)); const result = await entersState(vc, VoiceConnectionStatus.Ready, 1_000); @@ -25,12 +26,12 @@ describe('entersState', () => { test('Rejects once the timeout is exceeded', async () => { const vc = createFakeVoiceConnection(); const promise = entersState(vc, VoiceConnectionStatus.Ready, 1_000); - jest.runAllTimers(); + vitest.runAllTimers(); await expect(promise).rejects.toThrowError(); }); test('Returns the target once the state has been entered before signal is aborted', async () => { - jest.useRealTimers(); + vitest.useRealTimers(); const vc = createFakeVoiceConnection(); const ac = new AbortController(); process.nextTick(() => vc.emit(VoiceConnectionStatus.Ready, null as any, null as any)); diff --git a/packages/voice/__tests__/joinVoiceChannel.test.ts b/packages/voice/__tests__/joinVoiceChannel.test.ts index 5f1e94902..1a6bff329 100644 --- a/packages/voice/__tests__/joinVoiceChannel.test.ts +++ b/packages/voice/__tests__/joinVoiceChannel.test.ts @@ -1,9 +1,10 @@ // @ts-nocheck +import { describe, test, expect, vitest, beforeAll, beforeEach } from 'vitest'; import * as VoiceConnection from '../src/VoiceConnection'; import { joinVoiceChannel } from '../src/joinVoiceChannel'; -const adapterCreator = () => ({ destroy: jest.fn(), send: jest.fn() }) as any; -const createVoiceConnection = jest.spyOn(VoiceConnection, 'createVoiceConnection'); +const adapterCreator = () => ({ destroy: vitest.fn(), send: vitest.fn() }) as any; +const createVoiceConnection = vitest.spyOn(VoiceConnection, 'createVoiceConnection'); beforeAll(() => { createVoiceConnection.mockImplementation(() => null as any); diff --git a/packages/voice/babel.config.js b/packages/voice/babel.config.js deleted file mode 100644 index bbaa0e8cb..000000000 --- a/packages/voice/babel.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @type {import('@babel/core').TransformOptions} - */ -module.exports = { - parserOpts: { strictMode: true }, - sourceMaps: 'inline', - presets: [ - [ - '@babel/preset-env', - { - targets: { node: 'current' }, - modules: 'commonjs', - }, - ], - '@babel/preset-typescript', - ], -}; diff --git a/packages/voice/jest.config.js b/packages/voice/jest.config.js deleted file mode 100644 index dd29a6258..000000000 --- a/packages/voice/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @type {import('@jest/types').Config.InitialOptions} - */ -module.exports = { - testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], - testEnvironment: 'node', - collectCoverage: true, - collectCoverageFrom: ['src/**/*.ts'], - coverageDirectory: 'coverage', - coverageReporters: ['text', 'lcov', 'cobertura'], -}; diff --git a/packages/voice/package.json b/packages/voice/package.json index 101961ae7..f33cee649 100644 --- a/packages/voice/package.json +++ b/packages/voice/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "tsc --noEmit && tsup && node scripts/postbuild.mjs", "build:docs": "tsc -p tsconfig.docs.json", - "test": "jest --coverage", + "test": "vitest run", "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", @@ -70,27 +70,24 @@ "ws": "^8.18.0" }, "devDependencies": { - "@babel/core": "^7.24.6", - "@babel/preset-env": "^7.24.6", - "@babel/preset-typescript": "^7.24.6", "@discordjs/api-extractor": "workspace:^", + "@discordjs/opus": "^0.9.0", "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^4.1.0", - "@types/jest": "^29.5.12", - "@types/node": "^16.18.105", + "@types/node": "18.19.45", + "@vitest/coverage-v8": "2.0.5", "cross-env": "^7.0.3", "esbuild-plugin-version-injector": "^1.2.1", "eslint": "^8.57.0", "eslint-config-neon": "^0.1.62", "eslint-formatter-pretty": "^6.0.1", - "jest": "^29.7.0", - "jest-websocket-mock": "^2.5.0", - "mock-socket": "^9.3.1", "prettier": "^3.3.3", "tsup": "^8.2.4", "turbo": "^2.0.14", "tweetnacl": "^1.0.3", - "typescript": "~5.5.4" + "typescript": "~5.5.4", + "vitest": "^2.0.5", + "vitest-websocket-mock": "^0.3.0" }, "engines": { "node": ">=18" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3665411b2..42abf3613 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1602,7 +1602,7 @@ importers: version: 0.37.101 prism-media: specifier: ^1.3.5 - version: 1.3.5 + version: 1.3.5(@discordjs/opus@0.9.0(encoding@0.1.13)) tslib: specifier: ^2.6.3 version: 2.6.3 @@ -1610,30 +1610,24 @@ importers: specifier: ^8.18.0 version: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) devDependencies: - '@babel/core': - specifier: ^7.24.6 - version: 7.25.2 - '@babel/preset-env': - specifier: ^7.24.6 - version: 7.25.4(@babel/core@7.25.2) - '@babel/preset-typescript': - specifier: ^7.24.6 - version: 7.24.7(@babel/core@7.25.2) '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/opus': + specifier: ^0.9.0 + version: 0.9.0(encoding@0.1.13) '@discordjs/scripts': specifier: workspace:^ version: link:../scripts '@favware/cliff-jumper': specifier: ^4.1.0 version: 4.1.0 - '@types/jest': - specifier: ^29.5.12 - version: 29.5.12 '@types/node': - specifier: ^16.18.105 - version: 16.18.105 + specifier: 18.19.45 + version: 18.19.45 + '@vitest/coverage-v8': + specifier: 2.0.5 + version: 2.0.5(vitest@2.0.5(@edge-runtime/vm@3.2.0)(@types/node@18.19.45)(happy-dom@14.12.3)(terser@5.31.6)) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -1649,21 +1643,12 @@ importers: eslint-formatter-pretty: specifier: ^6.0.1 version: 6.0.1 - jest: - specifier: ^29.7.0 - version: 29.7.0(@types/node@16.18.105)(ts-node@10.9.2(@types/node@16.18.105)(typescript@5.5.4)) - jest-websocket-mock: - specifier: ^2.5.0 - version: 2.5.0 - mock-socket: - specifier: ^9.3.1 - version: 9.3.1 prettier: specifier: ^3.3.3 version: 3.3.3 tsup: specifier: ^8.2.4 - version: 8.2.4(@microsoft/api-extractor@7.43.0(@types/node@16.18.105))(jiti@1.21.6)(postcss@8.4.41)(typescript@5.5.4)(yaml@2.5.0) + version: 8.2.4(@microsoft/api-extractor@7.43.0(@types/node@18.19.45))(jiti@1.21.6)(postcss@8.4.41)(typescript@5.5.4)(yaml@2.5.0) turbo: specifier: ^2.0.14 version: 2.0.14 @@ -1673,6 +1658,12 @@ importers: typescript: specifier: ~5.5.4 version: 5.5.4 + vitest: + specifier: ^2.0.5 + version: 2.0.5(@edge-runtime/vm@3.2.0)(@types/node@18.19.45)(happy-dom@14.12.3)(terser@5.31.6) + vitest-websocket-mock: + specifier: ^0.3.0 + version: 0.3.0(vitest@2.0.5(@edge-runtime/vm@3.2.0)(@types/node@18.19.45)(happy-dom@14.12.3)(terser@5.31.6)) packages/ws: dependencies: @@ -2623,6 +2614,14 @@ packages: resolution: {integrity: sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==} engines: {node: '>=18'} + '@discordjs/node-pre-gyp@0.4.5': + resolution: {integrity: sha512-YJOVVZ545x24mHzANfYoy0BJX5PDyeZlpiJjDkUBM/V/Ao7TFX9lcUvCN4nr0tbr5ubeaXxtEBILUrHtTphVeQ==} + hasBin: true + + '@discordjs/opus@0.9.0': + resolution: {integrity: sha512-NEE76A96FtQ5YuoAVlOlB3ryMPrkXbUCTQICHGKb8ShtjXyubGicjRMouHtP1RpuDdm16cDa+oI3aAMo1zQRUQ==} + engines: {node: '>=12.0.0'} + '@discordjs/rest@2.3.0': resolution: {integrity: sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==} engines: {node: '>=16.11.0'} @@ -9553,9 +9552,6 @@ packages: resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-websocket-mock@2.5.0: - resolution: {integrity: sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==} - jest-worker@29.7.0: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10665,6 +10661,9 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-dir@0.1.17: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} @@ -13375,6 +13374,11 @@ packages: terser: optional: true + vitest-websocket-mock@0.3.0: + resolution: {integrity: sha512-kTEFtfHIUDiiiEBj/CR6WajugqObjnuNdolGRJA3vo3Xt+fmfd1Ghwe+NpJytG6OE57noHOCUXzs2R9XUF0cwg==} + peerDependencies: + vitest: '>=1 <2' + vitest@2.0.5: resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -14850,6 +14854,29 @@ snapshots: dependencies: discord-api-types: 0.37.97 + '@discordjs/node-pre-gyp@0.4.5(encoding@0.1.13)': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0(encoding@0.1.13) + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@discordjs/opus@0.9.0(encoding@0.1.13)': + dependencies: + '@discordjs/node-pre-gyp': 0.4.5(encoding@0.1.13) + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + '@discordjs/rest@2.3.0': dependencies: '@discordjs/collection': 2.1.0 @@ -24231,11 +24258,6 @@ snapshots: jest-util: 29.7.0 string-length: 4.0.2 - jest-websocket-mock@2.5.0: - dependencies: - jest-diff: 29.7.0 - mock-socket: 9.3.1 - jest-worker@29.7.0: dependencies: '@types/node': 18.19.45 @@ -25954,6 +25976,8 @@ snapshots: lower-case: 2.0.2 tslib: 2.6.3 + node-addon-api@5.1.0: {} + node-dir@0.1.17: dependencies: minimatch: 3.1.2 @@ -26667,7 +26691,9 @@ snapshots: dependencies: parse-ms: 4.0.0 - prism-media@1.3.5: {} + prism-media@1.3.5(@discordjs/opus@0.9.0(encoding@0.1.13)): + optionalDependencies: + '@discordjs/opus': 0.9.0(encoding@0.1.13) proc-log@3.0.0: {} @@ -29328,6 +29354,12 @@ snapshots: fsevents: 2.3.3 terser: 5.31.6 + vitest-websocket-mock@0.3.0(vitest@2.0.5(@edge-runtime/vm@3.2.0)(@types/node@18.19.45)(happy-dom@14.12.3)(terser@5.31.6)): + dependencies: + jest-diff: 29.7.0 + mock-socket: 9.3.1 + vitest: 2.0.5(@edge-runtime/vm@3.2.0)(@types/node@18.19.45)(happy-dom@14.12.3)(terser@5.31.6) + vitest@2.0.5(@edge-runtime/vm@3.2.0)(@types/node@16.18.105)(happy-dom@14.12.3)(terser@5.31.6): dependencies: '@ampproject/remapping': 2.3.0