diff --git a/packages/legacy/package.json b/packages/legacy/package.json index 2656e7e3..074bc96a 100644 --- a/packages/legacy/package.json +++ b/packages/legacy/package.json @@ -9,7 +9,8 @@ ], "scripts": { "check-types": "tsc --noEmit", - "build": "tsc" + "build": "tsc", + "test": "vitest" }, "repository": { "type": "git", @@ -33,6 +34,7 @@ "commandkit": "workspace:*", "discord.js": "catalog:discordjs", "tsconfig": "workspace:*", - "typescript": "catalog:build" + "typescript": "catalog:build", + "vitest": "^4.0.18" } } diff --git a/packages/legacy/src/plugin.test.ts b/packages/legacy/src/plugin.test.ts new file mode 100644 index 00000000..9759cff7 --- /dev/null +++ b/packages/legacy/src/plugin.test.ts @@ -0,0 +1,160 @@ +import { afterEach, describe, expect, test } from 'vitest'; +import { ApplicationCommandType } from 'discord.js'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { join, relative } from 'node:path'; +import { LegacyHandlerPlugin } from './plugin'; +import { loadLegacyCommands } from './loadLegacyCommands'; +import { CommandRegistrar, type LoadedCommand } from 'commandkit'; + +const tmpRoots: string[] = []; +const commandkitBaseDir = join(process.cwd(), 'dist'); +const tempBaseDir = join(commandkitBaseDir, '.tmp'); + +async function createLegacyCommandFixture( + fileName: string, + type: ApplicationCommandType, +) { + await mkdir(tempBaseDir, { recursive: true }); + const root = await mkdtemp(join(tempBaseDir, 'legacy-commands-')); + tmpRoots.push(root); + + const filePath = join(root, fileName); + await writeFile( + filePath, + ` +import { ApplicationCommandType } from 'discord.js'; + +export const data = { + name: ${JSON.stringify(fileName.replace(/\.[^.]+$/, ''))}, + type: ApplicationCommandType.${type === ApplicationCommandType.User ? 'User' : 'Message'}, +}; + +export async function run() { + return ${JSON.stringify(type === ApplicationCommandType.User ? 'user' : 'message')}; +} +`, + ); + + return root; +} + +async function loadPluginCommands(commandsRoot: string) { + const registered: LoadedCommand[] = []; + const plugin = new LegacyHandlerPlugin({ + commandsPath: relative(commandkitBaseDir, commandsRoot).replace(/\\/g, '/'), + eventsPath: './events', + validationsPath: './validations', + skipBuiltInValidations: true, + devUserIds: [], + devGuildIds: [], + devRoleIds: [], + }); + + await (plugin as any).loadCommands( + { + commandkit: { + commandHandler: { + registerExternalLoadedCommands: async (commands: LoadedCommand[]) => { + registered.push(...commands); + }, + }, + }, + }, + [], + ); + + return registered; +} + +function createRegistrarData(commands: LoadedCommand[]) { + const registrar = new CommandRegistrar({ + client: { + token: 'test-token', + }, + commandHandler: { + getCommandsArray: () => commands, + }, + commandsRouter: null, + plugins: { + execute: async () => undefined, + }, + } as any); + + return registrar.getCommandsData(); +} + +afterEach(async () => { + await Promise.all( + tmpRoots + .splice(0) + .map((root) => rm(root, { recursive: true, force: true })), + ); +}); + +describe('legacy context menu command loading', () => { + test('maps legacy message context menu commands to modern loaded commands', async () => { + const root = await createLegacyCommandFixture( + 'inspect-message.mjs', + ApplicationCommandType.Message, + ); + + const [legacyCommand] = await loadLegacyCommands(root); + const [loadedCommand] = await loadPluginCommands(root); + + expect(legacyCommand.messageContextMenu).toBeTypeOf('function'); + expect(legacyCommand.userContextMenu).toBeUndefined(); + expect(loadedCommand.data.messageContextMenu).toBeTypeOf('function'); + expect(loadedCommand.data.userContextMenu).toBeUndefined(); + }); + + test('maps legacy user context menu commands to modern loaded commands', async () => { + const root = await createLegacyCommandFixture( + 'inspect-user.mjs', + ApplicationCommandType.User, + ); + + const [legacyCommand] = await loadLegacyCommands(root); + const [loadedCommand] = await loadPluginCommands(root); + + expect(legacyCommand.userContextMenu).toBeTypeOf('function'); + expect(legacyCommand.messageContextMenu).toBeUndefined(); + expect(loadedCommand.data.userContextMenu).toBeTypeOf('function'); + expect(loadedCommand.data.messageContextMenu).toBeUndefined(); + }); + + test('emits user context menu registration data for legacy loaded commands', async () => { + const root = await createLegacyCommandFixture( + 'inspect-user.mjs', + ApplicationCommandType.User, + ); + + const [legacyCommand] = await loadLegacyCommands(root); + const [loadedCommand] = await loadPluginCommands(root); + const registrationCommands = createRegistrarData([loadedCommand]); + + expect(registrationCommands).toHaveLength(1); + expect(registrationCommands[0]).toMatchObject({ + name: 'inspect-user', + type: ApplicationCommandType.User, + }); + expect(registrationCommands[0].description).toBeUndefined(); + expect(registrationCommands[0].options).toBeUndefined(); + }); + + test('executes the wrapped legacy user context menu runner', async () => { + const root = await createLegacyCommandFixture( + 'inspect-user.mjs', + ApplicationCommandType.User, + ); + + const [legacyCommand] = await loadLegacyCommands(root); + const [loadedCommand] = await loadPluginCommands(root); + const result = await loadedCommand.data.userContextMenu?.({ + client: {}, + interaction: {}, + commandkit: {}, + } as any); + + expect(result).toBe('user'); + }); +}); diff --git a/packages/legacy/src/plugin.ts b/packages/legacy/src/plugin.ts index 9be90f49..8473d680 100644 --- a/packages/legacy/src/plugin.ts +++ b/packages/legacy/src/plugin.ts @@ -192,7 +192,7 @@ export class LegacyHandlerPlugin extends RuntimePlugin command.userContextMenu?.({ client: ctx.client as any, diff --git a/packages/legacy/tsconfig.json b/packages/legacy/tsconfig.json index 53fdb2c1..9df2d979 100644 --- a/packages/legacy/tsconfig.json +++ b/packages/legacy/tsconfig.json @@ -14,5 +14,5 @@ "noEmit": false }, "include": ["src/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "src/**/*.test.ts", "src/**/*.spec.ts"] } diff --git a/packages/legacy/vitest.config.ts b/packages/legacy/vitest.config.ts new file mode 100644 index 00000000..4dbaf432 --- /dev/null +++ b/packages/legacy/vitest.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vitest/config'; +import { join } from 'node:path'; + +export default defineConfig({ + test: { + include: ['./src/**/*.{test,spec}.?(c|m)[jt]s?(x)'], + exclude: ['dist/**', 'node_modules/**'], + watch: false, + env: { + COMMANDKIT_TEST: 'true', + }, + }, + resolve: { + alias: { + commandkit: join( + import.meta.dirname, + '..', + 'commandkit', + 'src', + 'index.ts', + ), + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8695b1a4..f13f353a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -673,6 +673,9 @@ importers: typescript: specifier: catalog:build version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3) packages/queue: devDependencies: