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 <github@almeidx.dev>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
pat
2024-10-07 01:26:53 +11:00
committed by GitHub
parent bb04e09f8b
commit 24128a3c45
21 changed files with 267 additions and 219 deletions

View File

@@ -1 +0,0 @@
export { WebSocket as default } from 'mock-socket';

View File

@@ -5,6 +5,7 @@ import { Buffer } from 'node:buffer';
import { once } from 'node:events'; import { once } from 'node:events';
import process from 'node:process'; import process from 'node:process';
import { Readable } from 'node:stream'; import { Readable } from 'node:stream';
import { describe, test, expect, vitest, type Mock, beforeEach, afterEach } from 'vitest';
import { addAudioPlayer, deleteAudioPlayer } from '../src/DataStore'; import { addAudioPlayer, deleteAudioPlayer } from '../src/DataStore';
import { VoiceConnection, VoiceConnectionStatus } from '../src/VoiceConnection'; import { VoiceConnection, VoiceConnectionStatus } from '../src/VoiceConnection';
import type { AudioPlayer } from '../src/audio/AudioPlayer'; import type { AudioPlayer } from '../src/audio/AudioPlayer';
@@ -13,14 +14,31 @@ import { AudioPlayerError } from '../src/audio/AudioPlayerError';
import { AudioResource } from '../src/audio/AudioResource'; import { AudioResource } from '../src/audio/AudioResource';
import { NoSubscriberBehavior } from '../src/index'; import { NoSubscriberBehavior } from '../src/index';
jest.mock('../src/DataStore'); vitest.mock('../src/DataStore', () => {
jest.mock('../src/VoiceConnection'); return {
jest.mock('../src/audio/AudioPlayerError'); addAudioPlayer: vitest.fn(),
deleteAudioPlayer: vitest.fn(),
};
});
const addAudioPlayerMock = addAudioPlayer as unknown as jest.Mock<typeof addAudioPlayer>; vitest.mock('../src/VoiceConnection', async (importOriginal) => {
const deleteAudioPlayerMock = deleteAudioPlayer as unknown as jest.Mock<typeof deleteAudioPlayer>; // eslint-disable-next-line @typescript-eslint/consistent-type-imports
const AudioPlayerErrorMock = AudioPlayerError as unknown as jest.Mock<typeof AudioPlayerError>; const actual = await importOriginal<typeof import('../src/VoiceConnection')>();
const VoiceConnectionMock = VoiceConnection as unknown as jest.Mock<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() { function* silence() {
while (true) { while (true) {
@@ -29,15 +47,15 @@ function* silence() {
} }
function createVoiceConnectionMock() { function createVoiceConnectionMock() {
const connection = new VoiceConnectionMock(); const connection = new VoiceConnection({} as any, {} as any);
connection.state = { connection.state = {
status: VoiceConnectionStatus.Signalling, status: VoiceConnectionStatus.Signalling,
adapter: { adapter: {
sendPayload: jest.fn(), sendPayload: vitest.fn(),
destroy: jest.fn(), destroy: vitest.fn(),
}, },
}; };
connection.subscribe = jest.fn((player) => player['subscribe'](connection)); connection.subscribe = vitest.fn((player) => player['subscribe'](connection));
return connection; return connection;
} }
@@ -57,10 +75,7 @@ async function started(resource: AudioResource) {
let player: AudioPlayer | undefined; let player: AudioPlayer | undefined;
beforeEach(() => { beforeEach(() => {
AudioPlayerErrorMock.mockReset(); vitest.resetAllMocks();
VoiceConnectionMock.mockReset();
addAudioPlayerMock.mockReset();
deleteAudioPlayerMock.mockReset();
}); });
afterEach(() => { afterEach(() => {
@@ -71,8 +86,8 @@ describe('State transitions', () => {
test('Starts in Idle state', () => { test('Starts in Idle state', () => {
player = createAudioPlayer(); player = createAudioPlayer();
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(addAudioPlayerMock).toBeCalledTimes(0); expect(addAudioPlayer).toBeCalledTimes(0);
expect(deleteAudioPlayerMock).toBeCalledTimes(0); expect(deleteAudioPlayer).toBeCalledTimes(0);
}); });
test('Playing resource with pausing and resuming', async () => { test('Playing resource with pausing and resuming', async () => {
@@ -86,11 +101,11 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(player.unpause()).toEqual(false); expect(player.unpause()).toEqual(false);
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(addAudioPlayerMock).toBeCalledTimes(0); expect(addAudioPlayer).toBeCalledTimes(0);
player.play(resource); player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); 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 pause() to return true and transition to paused state
expect(player.pause()).toEqual(true); expect(player.pause()).toEqual(true);
@@ -109,7 +124,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
// The audio player should not have been deleted throughout these changes // The audio player should not have been deleted throughout these changes
expect(deleteAudioPlayerMock).toBeCalledTimes(0); expect(deleteAudioPlayer).toBeCalledTimes(0);
}); });
test('Playing to Stopping', async () => { test('Playing to Stopping', async () => {
@@ -122,13 +137,13 @@ describe('State transitions', () => {
player.play(resource); player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1); expect(addAudioPlayer).toBeCalledTimes(1);
expect(deleteAudioPlayerMock).toBeCalledTimes(0); expect(deleteAudioPlayer).toBeCalledTimes(0);
expect(player.stop()).toEqual(true); expect(player.stop()).toEqual(true);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1); expect(addAudioPlayer).toBeCalledTimes(1);
expect(deleteAudioPlayerMock).toBeCalledTimes(0); expect(deleteAudioPlayer).toBeCalledTimes(0);
expect(resource.silenceRemaining).toEqual(5); expect(resource.silenceRemaining).toEqual(5);
}); });
@@ -142,8 +157,8 @@ describe('State transitions', () => {
await started(resource); await started(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toHaveBeenCalled(); expect(addAudioPlayer).toHaveBeenCalled();
expect(deleteAudioPlayerMock).not.toHaveBeenCalled(); expect(deleteAudioPlayer).not.toHaveBeenCalled();
}); });
describe('NoSubscriberBehavior transitions', () => { describe('NoSubscriberBehavior transitions', () => {
@@ -188,11 +203,11 @@ describe('State transitions', () => {
player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Stop } }); player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Stop } });
player.play(resource); player.play(resource);
expect(addAudioPlayerMock).toBeCalledTimes(1); expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true); expect(player.checkPlayable()).toEqual(true);
player['_stepPrepare'](); player['_stepPrepare']();
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); 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); player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1); expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true); expect(player.checkPlayable()).toEqual(true);
// Run through a few packet cycles // Run through a few packet cycles
@@ -241,7 +256,8 @@ describe('State transitions', () => {
expect(connection.dispatchAudio).toHaveBeenCalledTimes(6); expect(connection.dispatchAudio).toHaveBeenCalledTimes(6);
await wait(); await wait();
player['_stepPrepare'](); player['_stepPrepare']();
const prepareAudioPacket = connection.prepareAudioPacket as unknown as jest.Mock< const prepareAudioPacket = connection.prepareAudioPacket as unknown as Mock<
[Buffer],
typeof connection.prepareAudioPacket typeof connection.prepareAudioPacket
>; >;
expect(prepareAudioPacket).toHaveBeenCalledTimes(6); expect(prepareAudioPacket).toHaveBeenCalledTimes(6);
@@ -251,7 +267,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(connection.setSpeaking).toBeCalledTimes(1); expect(connection.setSpeaking).toBeCalledTimes(1);
expect(connection.setSpeaking).toHaveBeenLastCalledWith(false); expect(connection.setSpeaking).toHaveBeenLastCalledWith(false);
expect(deleteAudioPlayerMock).toHaveBeenCalledTimes(1); expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);
}); });
test('stop() causes resource to use silence padding frames', async () => { test('stop() causes resource to use silence padding frames', async () => {
@@ -275,7 +291,7 @@ describe('State transitions', () => {
player.play(resource); player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1); expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true); expect(player.checkPlayable()).toEqual(true);
player.stop(); player.stop();
@@ -298,7 +314,8 @@ describe('State transitions', () => {
await wait(); await wait();
expect(player.checkPlayable()).toEqual(false); 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 typeof connection.prepareAudioPacket
>; >;
expect(prepareAudioPacket).toHaveBeenCalledTimes(5); expect(prepareAudioPacket).toHaveBeenCalledTimes(5);
@@ -306,7 +323,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(connection.setSpeaking).toBeCalledTimes(1); expect(connection.setSpeaking).toBeCalledTimes(1);
expect(connection.setSpeaking).toHaveBeenLastCalledWith(false); expect(connection.setSpeaking).toHaveBeenLastCalledWith(false);
expect(deleteAudioPlayerMock).toHaveBeenCalledTimes(1); expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);
}); });
test('Plays silence 5 times for unreadable stream before quitting', async () => { test('Plays silence 5 times for unreadable stream before quitting', async () => {
@@ -328,10 +345,11 @@ describe('State transitions', () => {
player.play(resource); player.play(resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Playing); expect(player.state.status).toEqual(AudioPlayerStatus.Playing);
expect(addAudioPlayerMock).toBeCalledTimes(1); expect(addAudioPlayer).toBeCalledTimes(1);
expect(player.checkPlayable()).toEqual(true); 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 typeof connection.prepareAudioPacket
>; >;
@@ -351,7 +369,7 @@ describe('State transitions', () => {
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
expect(connection.setSpeaking).toBeCalledTimes(1); expect(connection.setSpeaking).toBeCalledTimes(1);
expect(connection.setSpeaking).toHaveBeenLastCalledWith(false); expect(connection.setSpeaking).toHaveBeenLastCalledWith(false);
expect(deleteAudioPlayerMock).toHaveBeenCalledTimes(1); expect(deleteAudioPlayer).toHaveBeenCalledTimes(1);
}); });
test('checkPlayable() transitions to Idle for unreadable stream', async () => { 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 res = await once(player, 'error');
const playerError = res[0] as AudioPlayerError; const playerError = res[0] as AudioPlayerError;
expect(playerError).toBeInstanceOf(AudioPlayerError); expect(playerError).toBeInstanceOf(AudioPlayerError);
expect(AudioPlayerErrorMock).toHaveBeenCalledWith(error, resource); expect(AudioPlayerError).toHaveBeenCalledWith(error, resource);
expect(player.state.status).toEqual(AudioPlayerStatus.Idle); expect(player.state.status).toEqual(AudioPlayerStatus.Idle);
}); });

View File

@@ -1,5 +1,6 @@
/* eslint-disable no-promise-executor-return */ /* eslint-disable no-promise-executor-return */
import { Buffer } from 'node:buffer'; import { Buffer } from 'node:buffer';
import { describe, test, expect } from 'vitest';
import { SILENCE_FRAME } from '../src/audio/AudioPlayer'; import { SILENCE_FRAME } from '../src/audio/AudioPlayer';
import { AudioReceiveStream, EndBehaviorType } from '../src/receive/AudioReceiveStream'; import { AudioReceiveStream, EndBehaviorType } from '../src/receive/AudioReceiveStream';

View File

@@ -2,12 +2,12 @@ import { Buffer } from 'node:buffer';
import process from 'node:process'; import process from 'node:process';
import { PassThrough, Readable } from 'node:stream'; import { PassThrough, Readable } from 'node:stream';
import { opus, VolumeTransformer } from 'prism-media'; 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 { SILENCE_FRAME } from '../src/audio/AudioPlayer';
import { AudioResource, createAudioResource, NO_CONSTRAINT, VOLUME_CONSTRAINT } from '../src/audio/AudioResource'; import { AudioResource, createAudioResource, NO_CONSTRAINT, VOLUME_CONSTRAINT } from '../src/audio/AudioResource';
import { findPipeline as _findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph'; import { findPipeline as _findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph';
jest.mock('prism-media'); vitest.mock('../src/audio/TransformerGraph');
jest.mock('../src/audio/TransformerGraph');
async function wait() { async function wait() {
// eslint-disable-next-line no-promise-executor-return // eslint-disable-next-line no-promise-executor-return
@@ -22,7 +22,7 @@ async function started(resource: AudioResource) {
return resource; return resource;
} }
const findPipeline = _findPipeline as unknown as jest.MockedFunction<typeof _findPipeline>; const findPipeline = _findPipeline as unknown as MockedFunction<typeof _findPipeline>;
beforeAll(() => { beforeAll(() => {
// @ts-expect-error: No type // @ts-expect-error: No type
@@ -37,7 +37,8 @@ beforeAll(() => {
if (constraint === VOLUME_CONSTRAINT) { if (constraint === VOLUME_CONSTRAINT) {
base.push({ base.push({
cost: 1, 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, type: TransformerType.InlineVolume,
}); });
} }
@@ -96,7 +97,8 @@ describe('createAudioResource', () => {
}); });
test('Infers from VolumeTransformer', () => { 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 }); const resource = createAudioResource(stream, { inlineVolume: true });
expect(findPipeline).toHaveBeenCalledWith(StreamType.Raw, NO_CONSTRAINT); expect(findPipeline).toHaveBeenCalledWith(StreamType.Raw, NO_CONSTRAINT);
expect(resource.volume).toEqual(stream); expect(resource.volume).toEqual(stream);

View File

@@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/dot-notation */ /* eslint-disable @typescript-eslint/dot-notation */
import { GatewayOpcodes } from 'discord-api-types/v10'; import { GatewayOpcodes } from 'discord-api-types/v10';
import { describe, test, expect, vitest, type Mocked, beforeEach } from 'vitest';
import * as DataStore from '../src/DataStore'; import * as DataStore from '../src/DataStore';
import type { VoiceConnection } from '../src/VoiceConnection'; import type { VoiceConnection } from '../src/VoiceConnection';
import * as _AudioPlayer from '../src/audio/AudioPlayer'; import * as _AudioPlayer from '../src/audio/AudioPlayer';
jest.mock('../src/VoiceConnection'); vitest.mock('../src/VoiceConnection');
jest.mock('../src/audio/AudioPlayer'); 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 { function createVoiceConnection(joinConfig: Pick<DataStore.JoinConfig, 'group' | 'guildId'>): VoiceConnection {
return { return {
@@ -71,8 +72,8 @@ describe('DataStore', () => {
}); });
test('Managing Audio Players', async () => { test('Managing Audio Players', async () => {
const player = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer()); const player = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer());
const dispatchSpy = jest.spyOn(player as any, '_stepDispatch'); const dispatchSpy = vitest.spyOn(player as any, '_stepDispatch');
const prepareSpy = jest.spyOn(player as any, '_stepPrepare'); const prepareSpy = vitest.spyOn(player as any, '_stepPrepare');
expect(DataStore.hasAudioPlayer(player)).toEqual(true); expect(DataStore.hasAudioPlayer(player)).toEqual(true);
expect(DataStore.addAudioPlayer(player)).toEqual(player); expect(DataStore.addAudioPlayer(player)).toEqual(player);
DataStore.deleteAudioPlayer(player); DataStore.deleteAudioPlayer(player);
@@ -87,12 +88,12 @@ describe('DataStore', () => {
test('Preparing Audio Frames', async () => { test('Preparing Audio Frames', async () => {
// Test functional player // Test functional player
const player2 = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer()); 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 player3 = DataStore.addAudioPlayer(new AudioPlayer.AudioPlayer());
const dispatchSpy2 = jest.spyOn(player2 as any, '_stepDispatch'); const dispatchSpy2 = vitest.spyOn(player2 as any, '_stepDispatch');
const prepareSpy2 = jest.spyOn(player2 as any, '_stepPrepare'); const prepareSpy2 = vitest.spyOn(player2 as any, '_stepPrepare');
const dispatchSpy3 = jest.spyOn(player3 as any, '_stepDispatch'); const dispatchSpy3 = vitest.spyOn(player3 as any, '_stepDispatch');
const prepareSpy3 = jest.spyOn(player3 as any, '_stepPrepare'); const prepareSpy3 = vitest.spyOn(player3 as any, '_stepPrepare');
await waitForEventLoop(); await waitForEventLoop();
DataStore.deleteAudioPlayer(player2); DataStore.deleteAudioPlayer(player2);
await waitForEventLoop(); await waitForEventLoop();

View File

@@ -1,5 +1,6 @@
import { type EventEmitter, once } from 'node:events'; import { type EventEmitter, once } from 'node:events';
import process from 'node:process'; import process from 'node:process';
import { describe, test, expect } from 'vitest';
import { SSRCMap, type VoiceUserData } from '../src/receive/SSRCMap'; import { SSRCMap, type VoiceUserData } from '../src/receive/SSRCMap';
async function onceOrThrow<Emitter extends EventEmitter>(target: Emitter, event: string, after: number) { async function onceOrThrow<Emitter extends EventEmitter>(target: Emitter, event: string, after: number) {

View File

@@ -1,8 +1,9 @@
import { test, expect, vitest } from 'vitest';
import { methods } from '../src/util/Secretbox'; import { methods } from '../src/util/Secretbox';
jest.mock('tweetnacl'); vitest.mock('tweetnacl');
test('Does not throw error with a package installed', () => { test('Does not throw error with a package installed', () => {
// @ts-expect-error: Unknown type // @ts-expect-error We are testing
expect(() => methods.open()).not.toThrowError(); expect(() => methods.open()).toThrow(TypeError);
}); });

View File

@@ -1,7 +1,8 @@
import { describe, test, expect, vitest } from 'vitest';
import { SpeakingMap } from '../src/receive/SpeakingMap'; import { SpeakingMap } from '../src/receive/SpeakingMap';
import { noop } from '../src/util/util'; import { noop } from '../src/util/util';
jest.useFakeTimers(); vitest.useFakeTimers();
describe('SpeakingMap', () => { describe('SpeakingMap', () => {
test('Emits start and end', () => { test('Emits start and end', () => {
@@ -17,17 +18,17 @@ describe('SpeakingMap', () => {
for (let index = 0; index < 10; index++) { for (let index = 0; index < 10; index++) {
speaking.onPacket(userId); speaking.onPacket(userId);
setTimeout(noop, SpeakingMap.DELAY / 2); setTimeout(noop, SpeakingMap.DELAY / 2);
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
expect(starts).toEqual([userId]); expect(starts).toEqual([userId]);
expect(ends).toEqual([]); expect(ends).toEqual([]);
} }
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
expect(ends).toEqual([userId]); expect(ends).toEqual([userId]);
speaking.onPacket(userId); speaking.onPacket(userId);
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
expect(starts).toEqual([userId, userId]); expect(starts).toEqual([userId, userId]);
}); });
}); });

View File

@@ -1,4 +1,5 @@
// @ts-nocheck // @ts-nocheck
import { describe, test, expect } from 'vitest';
import { findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph'; import { findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph';
const noConstraint = () => true; const noConstraint = () => true;

View File

@@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/dot-notation */ /* eslint-disable @typescript-eslint/dot-notation */
// @ts-nocheck // @ts-nocheck
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { vitest, describe, test, expect, beforeEach } from 'vitest';
import * as _DataStore from '../src/DataStore'; import * as _DataStore from '../src/DataStore';
import { import {
createVoiceConnection, createVoiceConnection,
@@ -14,34 +15,42 @@ import {
} from '../src/VoiceConnection'; } from '../src/VoiceConnection';
import * as _AudioPlayer from '../src/audio/AudioPlayer'; import * as _AudioPlayer from '../src/audio/AudioPlayer';
import { PlayerSubscription as _PlayerSubscription } from '../src/audio/PlayerSubscription'; 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'; import type { DiscordGatewayAdapterLibraryMethods } from '../src/util/adapter';
jest.mock('../src/audio/AudioPlayer'); vitest.mock('../src/audio/AudioPlayer');
jest.mock('../src/audio/PlayerSubscription'); vitest.mock('../src/audio/PlayerSubscription');
jest.mock('../src/DataStore'); vitest.mock('../src/DataStore');
jest.mock('../src/networking/Networking'); vitest.mock('../src/networking/Networking', async (importOriginal) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = await importOriginal<typeof import('../src/networking/Networking')>();
const Networking = actual.Networking;
Networking.prototype.createWebSocket = vitest.fn();
return {
...actual,
Networking,
};
});
const DataStore = _DataStore as unknown as jest.Mocked<typeof _DataStore>; const DataStore = _DataStore as unknown as vitest.Mocked<typeof _DataStore>;
const Networking = _Networking as unknown as jest.Mocked<typeof _Networking>; const AudioPlayer = _AudioPlayer as unknown as vitest.Mocked<typeof _AudioPlayer>;
const AudioPlayer = _AudioPlayer as unknown as jest.Mocked<typeof _AudioPlayer>; const PlayerSubscription = _PlayerSubscription as unknown as vitest.Mock<_PlayerSubscription>;
const PlayerSubscription = _PlayerSubscription as unknown as jest.Mock<_PlayerSubscription>;
Networking.Networking.mockImplementation(function mockedConstructor() { const _NetworkingClass = Networking.Networking;
this.state = {}; vitest.spyOn(Networking, 'Networking').mockImplementation((...args) => {
return this; return new _NetworkingClass(...args);
}); });
function createFakeAdapter() { function createFakeAdapter() {
const sendPayload = jest.fn(); const sendPayload = vitest.fn();
sendPayload.mockReturnValue(true); sendPayload.mockReturnValue(true);
const destroy = jest.fn(); const destroy = vitest.fn();
const libMethods: Partial<DiscordGatewayAdapterLibraryMethods> = {}; const libMethods: Partial<DiscordGatewayAdapterLibraryMethods> = {};
return { return {
sendPayload, sendPayload,
destroy, destroy,
libMethods, libMethods,
creator: jest.fn((methods) => { creator: vitest.fn((methods) => {
Object.assign(libMethods, methods); Object.assign(libMethods, methods);
return { return {
sendPayload, sendPayload,
@@ -124,7 +133,7 @@ describe('createVoiceConnection', () => {
adapterCreator: existingAdapter.creator, adapterCreator: existingAdapter.creator,
}); });
const stateSetter = jest.spyOn(existingVoiceConnection, 'state', 'set'); const stateSetter = vitest.spyOn(existingVoiceConnection, 'state', 'set');
// @ts-expect-error: We're testing // @ts-expect-error: We're testing
DataStore.getVoiceConnection.mockImplementation((guildId, group = 'default') => DataStore.getVoiceConnection.mockImplementation((guildId, group = 'default') =>
@@ -163,7 +172,7 @@ describe('createVoiceConnection', () => {
reason: VoiceConnectionDisconnectReason.EndpointRemoved, reason: VoiceConnectionDisconnectReason.EndpointRemoved,
}; };
const rejoinSpy = jest.spyOn(existingVoiceConnection, 'rejoin'); const rejoinSpy = vitest.spyOn(existingVoiceConnection, 'rejoin');
// @ts-expect-error: We're testing // @ts-expect-error: We're testing
DataStore.getVoiceConnection.mockImplementation((guildId, group = 'default') => DataStore.getVoiceConnection.mockImplementation((guildId, group = 'default') =>
@@ -222,7 +231,7 @@ describe('createVoiceConnection', () => {
describe('VoiceConnection#addServerPacket', () => { describe('VoiceConnection#addServerPacket', () => {
test('Stores the packet and attempts to configure networking', () => { test('Stores the packet and attempts to configure networking', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
voiceConnection.configureNetworking = jest.fn(); voiceConnection.configureNetworking = vitest.fn();
const dummy = { const dummy = {
endpoint: 'discord.com', endpoint: 'discord.com',
guild_id: 123, guild_id: 123,
@@ -236,7 +245,7 @@ describe('VoiceConnection#addServerPacket', () => {
test('Overwrites existing packet', () => { test('Overwrites existing packet', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
voiceConnection['packets'].server = Symbol('old') as any; voiceConnection['packets'].server = Symbol('old') as any;
voiceConnection.configureNetworking = jest.fn(); voiceConnection.configureNetworking = vitest.fn();
const dummy = { const dummy = {
endpoint: 'discord.com', endpoint: 'discord.com',
guild_id: 123, guild_id: 123,
@@ -250,7 +259,7 @@ describe('VoiceConnection#addServerPacket', () => {
test('Disconnects when given a null endpoint', () => { test('Disconnects when given a null endpoint', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
voiceConnection['packets'].server = Symbol('old') as any; voiceConnection['packets'].server = Symbol('old') as any;
voiceConnection.configureNetworking = jest.fn(); voiceConnection.configureNetworking = vitest.fn();
const dummy = { const dummy = {
endpoint: null, endpoint: null,
guild_id: 123, guild_id: 123,
@@ -344,7 +353,7 @@ describe('VoiceConnection#configureNetworking', () => {
adapter, adapter,
}); });
expect((voiceConnection.state as unknown as VoiceConnectionConnectingState).networking).toBeInstanceOf( expect((voiceConnection.state as unknown as VoiceConnectionConnectingState).networking).toBeInstanceOf(
Networking.Networking, _NetworkingClass,
); );
}); });
}); });
@@ -399,24 +408,24 @@ describe('VoiceConnection#onNetworkingClose', () => {
describe('VoiceConnection#onNetworkingStateChange', () => { describe('VoiceConnection#onNetworkingStateChange', () => {
test('Does nothing when status code identical', () => { test('Does nothing when status code identical', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set');
voiceConnection['onNetworkingStateChange']( 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']( 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(); expect(stateSetter).not.toHaveBeenCalled();
}); });
test('Does nothing when not in Ready or Connecting states', () => { test('Does nothing when not in Ready or Connecting states', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set');
const call = [ const call = [
{ code: _Networking.NetworkingStatusCode.Ready } as any, { code: Networking.NetworkingStatusCode.Ready } as any,
{ code: _Networking.NetworkingStatusCode.Closed } as any, { code: Networking.NetworkingStatusCode.Closed } as any,
]; ];
voiceConnection['_state'] = { status: VoiceConnectionStatus.Signalling } as any; voiceConnection['_state'] = { status: VoiceConnectionStatus.Signalling } as any;
voiceConnection['onNetworkingStateChange'](call[0], call[1]); voiceConnection['onNetworkingStateChange'](call[0], call[1]);
@@ -429,7 +438,7 @@ describe('VoiceConnection#onNetworkingStateChange', () => {
test('Transitions to Ready', () => { test('Transitions to Ready', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set');
voiceConnection['_state'] = { voiceConnection['_state'] = {
...(voiceConnection.state as VoiceConnectionSignallingState), ...(voiceConnection.state as VoiceConnectionSignallingState),
status: VoiceConnectionStatus.Connecting, status: VoiceConnectionStatus.Connecting,
@@ -437,8 +446,8 @@ describe('VoiceConnection#onNetworkingStateChange', () => {
}; };
voiceConnection['onNetworkingStateChange']( voiceConnection['onNetworkingStateChange'](
{ code: _Networking.NetworkingStatusCode.Closed } as any, { code: Networking.NetworkingStatusCode.Closed } as any,
{ code: _Networking.NetworkingStatusCode.Ready } as any, { code: Networking.NetworkingStatusCode.Ready } as any,
); );
expect(stateSetter).toHaveBeenCalledTimes(1); expect(stateSetter).toHaveBeenCalledTimes(1);
@@ -447,7 +456,7 @@ describe('VoiceConnection#onNetworkingStateChange', () => {
test('Transitions to Connecting', () => { test('Transitions to Connecting', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const stateSetter = jest.spyOn(voiceConnection, 'state', 'set'); const stateSetter = vitest.spyOn(voiceConnection, 'state', 'set');
voiceConnection['_state'] = { voiceConnection['_state'] = {
...(voiceConnection.state as VoiceConnectionSignallingState), ...(voiceConnection.state as VoiceConnectionSignallingState),
status: VoiceConnectionStatus.Connecting, status: VoiceConnectionStatus.Connecting,
@@ -455,8 +464,8 @@ describe('VoiceConnection#onNetworkingStateChange', () => {
}; };
voiceConnection['onNetworkingStateChange']( voiceConnection['onNetworkingStateChange'](
{ code: _Networking.NetworkingStatusCode.Ready } as any, { code: Networking.NetworkingStatusCode.Ready } as any,
{ code: _Networking.NetworkingStatusCode.Identifying } as any, { code: Networking.NetworkingStatusCode.Identifying } as any,
); );
expect(stateSetter).toHaveBeenCalledTimes(1); expect(stateSetter).toHaveBeenCalledTimes(1);
@@ -598,7 +607,7 @@ describe('VoiceConnection#subscribe', () => {
test('Does nothing in Destroyed state', () => { test('Does nothing in Destroyed state', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const player = new AudioPlayer.AudioPlayer(); const player = new AudioPlayer.AudioPlayer();
player['subscribe'] = jest.fn(); player['subscribe'] = vitest.fn();
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed }; voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
expect(voiceConnection.subscribe(player)).toBeUndefined(); expect(voiceConnection.subscribe(player)).toBeUndefined();
expect(player['subscribe']).not.toHaveBeenCalled(); expect(player['subscribe']).not.toHaveBeenCalled();
@@ -610,7 +619,7 @@ describe('VoiceConnection#subscribe', () => {
const adapter = (voiceConnection.state as VoiceConnectionSignallingState).adapter; const adapter = (voiceConnection.state as VoiceConnectionSignallingState).adapter;
const player = new AudioPlayer.AudioPlayer(); const player = new AudioPlayer.AudioPlayer();
const dummy = Symbol('dummy'); const dummy = Symbol('dummy');
player['subscribe'] = jest.fn().mockImplementation(() => dummy); player['subscribe'] = vitest.fn().mockImplementation(() => dummy);
expect(voiceConnection.subscribe(player)).toEqual(dummy); expect(voiceConnection.subscribe(player)).toEqual(dummy);
expect(player['subscribe']).toHaveBeenCalledWith(voiceConnection); expect(player['subscribe']).toHaveBeenCalledWith(voiceConnection);
expect(voiceConnection.state).toMatchObject({ expect(voiceConnection.state).toMatchObject({
@@ -624,7 +633,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
test('Does nothing in Destroyed state', () => { test('Does nothing in Destroyed state', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer()); const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer());
subscription.unsubscribe = jest.fn(); subscription.unsubscribe = vitest.fn();
voiceConnection.state = { status: VoiceConnectionStatus.Destroyed }; voiceConnection.state = { status: VoiceConnectionStatus.Destroyed };
voiceConnection['onSubscriptionRemoved'](subscription); voiceConnection['onSubscriptionRemoved'](subscription);
@@ -635,7 +644,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
test('Does nothing when subscription is not the same as the stored one', () => { test('Does nothing when subscription is not the same as the stored one', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer()); const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer());
subscription.unsubscribe = jest.fn(); subscription.unsubscribe = vitest.fn();
voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription }; voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription };
voiceConnection['onSubscriptionRemoved'](Symbol('new subscription') as any); voiceConnection['onSubscriptionRemoved'](Symbol('new subscription') as any);
@@ -649,7 +658,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
test('Unsubscribes in a live state with matching subscription', () => { test('Unsubscribes in a live state with matching subscription', () => {
const { voiceConnection } = createFakeVoiceConnection(); const { voiceConnection } = createFakeVoiceConnection();
const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer()); const subscription = new PlayerSubscription(voiceConnection, new AudioPlayer.AudioPlayer());
subscription.unsubscribe = jest.fn(); subscription.unsubscribe = vitest.fn();
voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription }; voiceConnection.state = { ...(voiceConnection.state as VoiceConnectionSignallingState), subscription };
voiceConnection['onSubscriptionRemoved'](subscription); voiceConnection['onSubscriptionRemoved'](subscription);
@@ -667,7 +676,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
const oldNetworking = new Networking.Networking({} as any, false); const oldNetworking = new Networking.Networking({} as any, false);
oldNetworking.state = { oldNetworking.state = {
code: _Networking.NetworkingStatusCode.Ready, code: Networking.NetworkingStatusCode.Ready,
connectionData: {} as any, connectionData: {} as any,
connectionOptions: {} as any, connectionOptions: {} as any,
udp: new EventEmitter() as any, udp: new EventEmitter() as any,
@@ -697,7 +706,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
const oldNetworking = new Networking.Networking({} as any, false); const oldNetworking = new Networking.Networking({} as any, false);
oldNetworking.state = { oldNetworking.state = {
code: _Networking.NetworkingStatusCode.Ready, code: Networking.NetworkingStatusCode.Ready,
connectionData: {} as any, connectionData: {} as any,
connectionOptions: {} as any, connectionOptions: {} as any,
udp, udp,
@@ -726,7 +735,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
const newNetworking = new Networking.Networking({} as any, false); const newNetworking = new Networking.Networking({} as any, false);
newNetworking.state = { newNetworking.state = {
code: _Networking.NetworkingStatusCode.Ready, code: Networking.NetworkingStatusCode.Ready,
connectionData: {} as any, connectionData: {} as any,
connectionOptions: {} as any, connectionOptions: {} as any,
udp: new EventEmitter() as any, udp: new EventEmitter() as any,
@@ -749,7 +758,7 @@ describe('VoiceConnection#onSubscriptionRemoved', () => {
describe('Adapter', () => { describe('Adapter', () => {
test('onVoiceServerUpdate', () => { test('onVoiceServerUpdate', () => {
const { adapter, voiceConnection } = createFakeVoiceConnection(); const { adapter, voiceConnection } = createFakeVoiceConnection();
voiceConnection['addServerPacket'] = jest.fn(); voiceConnection['addServerPacket'] = vitest.fn();
const dummy = Symbol('dummy') as any; const dummy = Symbol('dummy') as any;
adapter.libMethods.onVoiceServerUpdate!(dummy); adapter.libMethods.onVoiceServerUpdate!(dummy);
expect(voiceConnection['addServerPacket']).toHaveBeenCalledWith(dummy); expect(voiceConnection['addServerPacket']).toHaveBeenCalledWith(dummy);
@@ -757,7 +766,7 @@ describe('Adapter', () => {
test('onVoiceStateUpdate', () => { test('onVoiceStateUpdate', () => {
const { adapter, voiceConnection } = createFakeVoiceConnection(); const { adapter, voiceConnection } = createFakeVoiceConnection();
voiceConnection['addStatePacket'] = jest.fn(); voiceConnection['addStatePacket'] = vitest.fn();
const dummy = Symbol('dummy') as any; const dummy = Symbol('dummy') as any;
adapter.libMethods.onVoiceStateUpdate!(dummy); adapter.libMethods.onVoiceStateUpdate!(dummy);
expect(voiceConnection['addStatePacket']).toHaveBeenCalledWith(dummy); expect(voiceConnection['addStatePacket']).toHaveBeenCalledWith(dummy);

View File

@@ -5,20 +5,27 @@ import { Buffer } from 'node:buffer';
import { once } from 'node:events'; import { once } from 'node:events';
import process from 'node:process'; import process from 'node:process';
import { VoiceOpcodes } from 'discord-api-types/voice/v4'; 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 { 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 { VoiceReceiver } from '../src/receive/VoiceReceiver';
import { methods } from '../src/util/Secretbox'; import { methods } from '../src/util/Secretbox';
jest.mock('../src/VoiceConnection'); vitest.mock('../src/VoiceConnection', async (importOriginal) => {
jest.mock('../src/receive/SSRCMap'); // eslint-disable-next-line @typescript-eslint/consistent-type-imports
const actual = await importOriginal<typeof import('../src/VoiceConnection')>();
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); openSpy.mockImplementation((buffer) => buffer);
const VoiceConnection = _VoiceConnection as unknown as jest.Mocked<typeof _VoiceConnection>;
async function nextTick() { async function nextTick() {
// eslint-disable-next-line no-promise-executor-return // eslint-disable-next-line no-promise-executor-return
return new Promise((resolve) => process.nextTick(resolve)); return new Promise((resolve) => process.nextTick(resolve));
@@ -56,9 +63,9 @@ describe('VoiceReceiver', () => {
['RTP Packet Chrome', RTP_PACKET_CHROME], ['RTP Packet Chrome', RTP_PACKET_CHROME],
['RTP Packet Android', RTP_PACKET_ANDROID], ['RTP Packet Android', RTP_PACKET_ANDROID],
])('onUdpMessage: %s', async (testName, RTP_PACKET) => { ])('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(() => ({ spy.mockImplementation(() => ({
audioSSRC: RTP_PACKET.ssrc, audioSSRC: RTP_PACKET.ssrc,
userId: '123', userId: '123',
@@ -76,9 +83,9 @@ describe('VoiceReceiver', () => {
}); });
test('onUdpMessage: destroys stream on decrypt failure', async () => { 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(() => ({ spy.mockImplementation(() => ({
audioSSRC: RTP_PACKET_DESKTOP.ssrc, audioSSRC: RTP_PACKET_DESKTOP.ssrc,
userId: '123', userId: '123',
@@ -95,7 +102,7 @@ describe('VoiceReceiver', () => {
}); });
test('subscribe: only allows one subscribe stream per SSRC', () => { 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(() => ({ spy.mockImplementation(() => ({
audioSSRC: RTP_PACKET_DESKTOP.ssrc, audioSSRC: RTP_PACKET_DESKTOP.ssrc,
userId: '123', userId: '123',
@@ -107,7 +114,7 @@ describe('VoiceReceiver', () => {
describe('onWsPacket', () => { describe('onWsPacket', () => {
test('CLIENT_DISCONNECT packet', () => { test('CLIENT_DISCONNECT packet', () => {
const spy = jest.spyOn(receiver.ssrcMap, 'delete'); const spy = vitest.spyOn(receiver.ssrcMap, 'delete');
receiver['onWsPacket']({ receiver['onWsPacket']({
op: VoiceOpcodes.ClientDisconnect, op: VoiceOpcodes.ClientDisconnect,
d: { d: {
@@ -118,7 +125,7 @@ describe('VoiceReceiver', () => {
}); });
test('SPEAKING packet', () => { test('SPEAKING packet', () => {
const spy = jest.spyOn(receiver.ssrcMap, 'update'); const spy = vitest.spyOn(receiver.ssrcMap, 'update');
receiver['onWsPacket']({ receiver['onWsPacket']({
op: VoiceOpcodes.Speaking, op: VoiceOpcodes.Speaking,
d: { d: {
@@ -134,7 +141,7 @@ describe('VoiceReceiver', () => {
}); });
test('CLIENT_CONNECT packet', () => { test('CLIENT_CONNECT packet', () => {
const spy = jest.spyOn(receiver.ssrcMap, 'update'); const spy = vitest.spyOn(receiver.ssrcMap, 'update');
receiver['onWsPacket']({ receiver['onWsPacket']({
op: VoiceOpcodes.ClientConnect, op: VoiceOpcodes.ClientConnect,
d: { d: {

View File

@@ -2,12 +2,13 @@
import { Buffer } from 'node:buffer'; import { Buffer } from 'node:buffer';
import { createSocket as _createSocket } from 'node:dgram'; import { createSocket as _createSocket } from 'node:dgram';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { describe, test, expect, vitest, beforeEach, afterEach } from 'vitest';
import { VoiceUDPSocket } from '../src/networking/VoiceUDPSocket'; import { VoiceUDPSocket } from '../src/networking/VoiceUDPSocket';
jest.mock('node:dgram'); vitest.mock('node:dgram');
jest.useFakeTimers(); vitest.useFakeTimers();
const createSocket = _createSocket as unknown as jest.Mock<typeof _createSocket>; const createSocket = _createSocket as unknown as vitest.Mock<typeof _createSocket>;
beforeEach(() => { beforeEach(() => {
createSocket.mockReset(); createSocket.mockReset();
@@ -32,7 +33,7 @@ const VALID_RESPONSE = Buffer.from([
async function wait() { async function wait() {
return new Promise((resolve) => { return new Promise((resolve) => {
setImmediate(resolve); setImmediate(resolve);
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
}); });
} }
@@ -48,7 +49,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
*/ */
test('Resolves and cleans up with a successful flow', async () => { test('Resolves and cleans up with a successful flow', async () => {
const fake = new FakeSocket(); 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); fake.emit('message', VALID_RESPONSE);
}); });
createSocket.mockImplementation((type) => fake as any); createSocket.mockImplementation((type) => fake as any);
@@ -71,7 +72,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
test('Waits for a valid response in an unexpected flow', async () => { test('Waits for a valid response in an unexpected flow', async () => {
const fake = new FakeSocket(); const fake = new FakeSocket();
const fakeResponse = Buffer.from([1, 2, 3, 4, 5]); 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); fake.emit('message', fakeResponse);
await wait(); await wait();
fake.emit('message', VALID_RESPONSE); fake.emit('message', VALID_RESPONSE);
@@ -91,7 +92,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
test('Rejects if socket closes before IP discovery can be completed', async () => { test('Rejects if socket closes before IP discovery can be completed', async () => {
const fake = new FakeSocket(); 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(); await wait();
fake.close(); fake.close();
}); });
@@ -104,7 +105,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
test('Stays alive when messages are echoed back', async () => { test('Stays alive when messages are echoed back', async () => {
const fake = new FakeSocket(); const fake = new FakeSocket();
fake.send = jest.fn().mockImplementation(async (buffer: Buffer) => { fake.send = vitest.fn().mockImplementation(async (buffer: Buffer) => {
await wait(); await wait();
fake.emit('message', buffer); fake.emit('message', buffer);
}); });
@@ -115,7 +116,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
socket.on('close', () => (closed = true)); socket.on('close', () => (closed = true));
for (let index = 0; index < 30; index++) { for (let index = 0; index < 30; index++) {
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
await wait(); await wait();
} }
@@ -124,7 +125,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
test('Recovers from intermittent responses', async () => { test('Recovers from intermittent responses', async () => {
const fake = new FakeSocket(); const fake = new FakeSocket();
const fakeSend = jest.fn(); const fakeSend = vitest.fn();
fake.send = fakeSend; fake.send = fakeSend;
createSocket.mockImplementation(() => fake as any); createSocket.mockImplementation(() => fake as any);
socket = new VoiceUDPSocket({ ip: '1.2.3.4', port: 25_565 }); socket = new VoiceUDPSocket({ ip: '1.2.3.4', port: 25_565 });
@@ -134,7 +135,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
socket.on('close', () => (closed = true)); socket.on('close', () => (closed = true));
for (let index = 0; index < 10; index++) { for (let index = 0; index < 10; index++) {
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
await wait(); await wait();
} }
@@ -144,7 +145,7 @@ describe('VoiceUDPSocket#performIPDiscovery', () => {
}); });
expect(closed).toEqual(false); expect(closed).toEqual(false);
for (let index = 0; index < 30; index++) { for (let index = 0; index < 30; index++) {
jest.advanceTimersToNextTimer(); vitest.advanceTimersToNextTimer();
await wait(); await wait();
} }

View File

@@ -1,6 +1,7 @@
import { type EventEmitter, once } from 'node:events'; import { type EventEmitter, once } from 'node:events';
import { VoiceOpcodes } from 'discord-api-types/voice/v4'; 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'; import { VoiceWebSocket } from '../src/networking/VoiceWebSocket';
beforeEach(() => { beforeEach(() => {

View File

@@ -1,15 +1,16 @@
import { describe, test, expect, vitest } from 'vitest';
import { abortAfter } from '../src/util/abortAfter'; import { abortAfter } from '../src/util/abortAfter';
jest.useFakeTimers(); vitest.useFakeTimers();
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); const clearTimeoutSpy = vitest.spyOn(global, 'clearTimeout');
describe('abortAfter', () => { describe('abortAfter', () => {
test('Aborts after the given delay', () => { test('Aborts after the given delay', () => {
const [ac, signal] = abortAfter(100); const [ac, signal] = abortAfter(100);
expect(ac.signal).toEqual(signal); expect(ac.signal).toEqual(signal);
expect(signal.aborted).toEqual(false); expect(signal.aborted).toEqual(false);
jest.runAllTimers(); vitest.runAllTimers();
expect(signal.aborted).toEqual(true); expect(signal.aborted).toEqual(true);
}); });

View File

@@ -4,13 +4,14 @@ import EventEmitter, { once } from 'node:events';
import process from 'node:process'; import process from 'node:process';
import { Readable } from 'node:stream'; import { Readable } from 'node:stream';
import { opus as _opus } from 'prism-media'; 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 { StreamType } from '../src/audio/index';
import { demuxProbe } from '../src/util/demuxProbe'; 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 WebmDemuxer = _opus.WebmDemuxer as unknown as Mock<_opus.WebmDemuxer>;
const OggDemuxer = _opus.OggDemuxer as unknown as jest.Mock<_opus.OggDemuxer>; const OggDemuxer = _opus.OggDemuxer as unknown as Mock<_opus.OggDemuxer>;
async function nextTick() { async function nextTick() {
// eslint-disable-next-line no-promise-executor-return // eslint-disable-next-line no-promise-executor-return
@@ -47,8 +48,8 @@ async function collectStream(stream: Readable): Promise<Buffer> {
} }
describe('demuxProbe', () => { describe('demuxProbe', () => {
const webmWrite: jest.Mock<(buffer: Buffer) => void> = jest.fn(); const webmWrite: Mock<(buffer: Buffer) => void> = vitest.fn();
const oggWrite: jest.Mock<(buffer: Buffer) => void> = jest.fn(); const oggWrite: Mock<(buffer: Buffer) => void> = vitest.fn();
beforeAll(() => { beforeAll(() => {
WebmDemuxer.prototype = { WebmDemuxer.prototype = {

View File

@@ -1,5 +1,6 @@
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import process from 'node:process'; import process from 'node:process';
import { describe, test, expect, vitest, beforeEach } from 'vitest';
import { VoiceConnectionStatus, type VoiceConnection } from '../src/VoiceConnection'; import { VoiceConnectionStatus, type VoiceConnection } from '../src/VoiceConnection';
import { entersState } from '../src/util/entersState'; import { entersState } from '../src/util/entersState';
@@ -10,12 +11,12 @@ function createFakeVoiceConnection(status = VoiceConnectionStatus.Signalling) {
} }
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); vitest.useFakeTimers();
}); });
describe('entersState', () => { describe('entersState', () => {
test('Returns the target once the state has been entered before timeout', async () => { test('Returns the target once the state has been entered before timeout', async () => {
jest.useRealTimers(); vitest.useRealTimers();
const vc = createFakeVoiceConnection(); const vc = createFakeVoiceConnection();
process.nextTick(() => vc.emit(VoiceConnectionStatus.Ready, null as any, null as any)); process.nextTick(() => vc.emit(VoiceConnectionStatus.Ready, null as any, null as any));
const result = await entersState(vc, VoiceConnectionStatus.Ready, 1_000); const result = await entersState(vc, VoiceConnectionStatus.Ready, 1_000);
@@ -25,12 +26,12 @@ describe('entersState', () => {
test('Rejects once the timeout is exceeded', async () => { test('Rejects once the timeout is exceeded', async () => {
const vc = createFakeVoiceConnection(); const vc = createFakeVoiceConnection();
const promise = entersState(vc, VoiceConnectionStatus.Ready, 1_000); const promise = entersState(vc, VoiceConnectionStatus.Ready, 1_000);
jest.runAllTimers(); vitest.runAllTimers();
await expect(promise).rejects.toThrowError(); await expect(promise).rejects.toThrowError();
}); });
test('Returns the target once the state has been entered before signal is aborted', async () => { test('Returns the target once the state has been entered before signal is aborted', async () => {
jest.useRealTimers(); vitest.useRealTimers();
const vc = createFakeVoiceConnection(); const vc = createFakeVoiceConnection();
const ac = new AbortController(); const ac = new AbortController();
process.nextTick(() => vc.emit(VoiceConnectionStatus.Ready, null as any, null as any)); process.nextTick(() => vc.emit(VoiceConnectionStatus.Ready, null as any, null as any));

View File

@@ -1,9 +1,10 @@
// @ts-nocheck // @ts-nocheck
import { describe, test, expect, vitest, beforeAll, beforeEach } from 'vitest';
import * as VoiceConnection from '../src/VoiceConnection'; import * as VoiceConnection from '../src/VoiceConnection';
import { joinVoiceChannel } from '../src/joinVoiceChannel'; import { joinVoiceChannel } from '../src/joinVoiceChannel';
const adapterCreator = () => ({ destroy: jest.fn(), send: jest.fn() }) as any; const adapterCreator = () => ({ destroy: vitest.fn(), send: vitest.fn() }) as any;
const createVoiceConnection = jest.spyOn(VoiceConnection, 'createVoiceConnection'); const createVoiceConnection = vitest.spyOn(VoiceConnection, 'createVoiceConnection');
beforeAll(() => { beforeAll(() => {
createVoiceConnection.mockImplementation(() => null as any); createVoiceConnection.mockImplementation(() => null as any);

View File

@@ -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',
],
};

View File

@@ -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'],
};

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"build": "tsc --noEmit && tsup && node scripts/postbuild.mjs", "build": "tsc --noEmit && tsup && node scripts/postbuild.mjs",
"build:docs": "tsc -p tsconfig.docs.json", "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__", "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__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__",
"fmt": "pnpm run format", "fmt": "pnpm run format",
@@ -70,27 +70,24 @@
"ws": "^8.18.0" "ws": "^8.18.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@babel/preset-typescript": "^7.24.6",
"@discordjs/api-extractor": "workspace:^", "@discordjs/api-extractor": "workspace:^",
"@discordjs/opus": "^0.9.0",
"@discordjs/scripts": "workspace:^", "@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^4.1.0", "@favware/cliff-jumper": "^4.1.0",
"@types/jest": "^29.5.12", "@types/node": "18.19.45",
"@types/node": "^16.18.105", "@vitest/coverage-v8": "2.0.5",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esbuild-plugin-version-injector": "^1.2.1", "esbuild-plugin-version-injector": "^1.2.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-neon": "^0.1.62", "eslint-config-neon": "^0.1.62",
"eslint-formatter-pretty": "^6.0.1", "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", "prettier": "^3.3.3",
"tsup": "^8.2.4", "tsup": "^8.2.4",
"turbo": "^2.0.14", "turbo": "^2.0.14",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"typescript": "~5.5.4" "typescript": "~5.5.4",
"vitest": "^2.0.5",
"vitest-websocket-mock": "^0.3.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"

100
pnpm-lock.yaml generated
View File

@@ -1602,7 +1602,7 @@ importers:
version: 0.37.101 version: 0.37.101
prism-media: prism-media:
specifier: ^1.3.5 specifier: ^1.3.5
version: 1.3.5 version: 1.3.5(@discordjs/opus@0.9.0(encoding@0.1.13))
tslib: tslib:
specifier: ^2.6.3 specifier: ^2.6.3
version: 2.6.3 version: 2.6.3
@@ -1610,30 +1610,24 @@ importers:
specifier: ^8.18.0 specifier: ^8.18.0
version: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) version: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
devDependencies: 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': '@discordjs/api-extractor':
specifier: workspace:^ specifier: workspace:^
version: link:../api-extractor version: link:../api-extractor
'@discordjs/opus':
specifier: ^0.9.0
version: 0.9.0(encoding@0.1.13)
'@discordjs/scripts': '@discordjs/scripts':
specifier: workspace:^ specifier: workspace:^
version: link:../scripts version: link:../scripts
'@favware/cliff-jumper': '@favware/cliff-jumper':
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
'@types/jest':
specifier: ^29.5.12
version: 29.5.12
'@types/node': '@types/node':
specifier: ^16.18.105 specifier: 18.19.45
version: 16.18.105 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: cross-env:
specifier: ^7.0.3 specifier: ^7.0.3
version: 7.0.3 version: 7.0.3
@@ -1649,21 +1643,12 @@ importers:
eslint-formatter-pretty: eslint-formatter-pretty:
specifier: ^6.0.1 specifier: ^6.0.1
version: 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: prettier:
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.3.3 version: 3.3.3
tsup: tsup:
specifier: ^8.2.4 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: turbo:
specifier: ^2.0.14 specifier: ^2.0.14
version: 2.0.14 version: 2.0.14
@@ -1673,6 +1658,12 @@ importers:
typescript: typescript:
specifier: ~5.5.4 specifier: ~5.5.4
version: 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: packages/ws:
dependencies: dependencies:
@@ -2623,6 +2614,14 @@ packages:
resolution: {integrity: sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==} resolution: {integrity: sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==}
engines: {node: '>=18'} 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': '@discordjs/rest@2.3.0':
resolution: {integrity: sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==} resolution: {integrity: sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==}
engines: {node: '>=16.11.0'} engines: {node: '>=16.11.0'}
@@ -9553,9 +9552,6 @@ packages:
resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 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: jest-worker@29.7.0:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -10665,6 +10661,9 @@ packages:
no-case@3.0.4: no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
node-dir@0.1.17: node-dir@0.1.17:
resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==}
engines: {node: '>= 0.10.5'} engines: {node: '>= 0.10.5'}
@@ -13375,6 +13374,11 @@ packages:
terser: terser:
optional: true optional: true
vitest-websocket-mock@0.3.0:
resolution: {integrity: sha512-kTEFtfHIUDiiiEBj/CR6WajugqObjnuNdolGRJA3vo3Xt+fmfd1Ghwe+NpJytG6OE57noHOCUXzs2R9XUF0cwg==}
peerDependencies:
vitest: '>=1 <2'
vitest@2.0.5: vitest@2.0.5:
resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
@@ -14850,6 +14854,29 @@ snapshots:
dependencies: dependencies:
discord-api-types: 0.37.97 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': '@discordjs/rest@2.3.0':
dependencies: dependencies:
'@discordjs/collection': 2.1.0 '@discordjs/collection': 2.1.0
@@ -24231,11 +24258,6 @@ snapshots:
jest-util: 29.7.0 jest-util: 29.7.0
string-length: 4.0.2 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: jest-worker@29.7.0:
dependencies: dependencies:
'@types/node': 18.19.45 '@types/node': 18.19.45
@@ -25954,6 +25976,8 @@ snapshots:
lower-case: 2.0.2 lower-case: 2.0.2
tslib: 2.6.3 tslib: 2.6.3
node-addon-api@5.1.0: {}
node-dir@0.1.17: node-dir@0.1.17:
dependencies: dependencies:
minimatch: 3.1.2 minimatch: 3.1.2
@@ -26667,7 +26691,9 @@ snapshots:
dependencies: dependencies:
parse-ms: 4.0.0 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: {} proc-log@3.0.0: {}
@@ -29328,6 +29354,12 @@ snapshots:
fsevents: 2.3.3 fsevents: 2.3.3
terser: 5.31.6 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): 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: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0