mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
* refactor: move things to core, imports not fixed yet * work on strategy and lifted Context * remove id from lifted Context * refactor: remove dependence on discord.js for module stoore * moving and fixing imports * chore: move operators into core * chore: fix paths * add wrapper platform field * add deprecation warning * chore:update paths * chore:remove const function * chore: remove deprecated symbols * docs: add documentation to internal function * chore: remove deprecated support for plugins * chore: remove dependence on discord.js Awaitable type * chore: update typings * lift requiredDependencyKeys out of makeFetcher * move strategy to index.ts and add adapters * chore: fix typings * chore: move command args matrix as binding * feat: make Context platform specific, CoreContext as Core * chore: remove extra file * chore: move prettier into package.json * chore(core): update imports and operators * chore(core): add DefaultWrapper as sern classic * move eslint and prettier configs to json * chore: remove utils folder in favor of single file * chore: remove redundant directories for single files * chore: remove redundant directories for single files * refactor: move and update things * chore: move commands into seperate file * chore: serverless work * chore: remove redundant directories for single files * chore: rename, wip refactoring * chore: redundant directory * refactor: internalize operators * feat!: new module resolution algorithm * chore: refactor and move things * chore: refactor and add multiplatform typings * chore: remove leaky import * chore: add agnostic predicates * chore: add old context here until i figure out what to do * chore: update Proccessed typing to ./core * chore: add tweetnacl * revert: multiplatform * revert: multiplatform * chore: modularize and split typings * chore: revert multiplatform * chore: revert multi and mov sernEmitter * chore: revert multi and clean up code * refactor: add createGenericHandler * refactor: remove unneeded signatures and fix imports * feat: add getPublishableCommands to ModuleManager * chore: remove bad imports * style: pretty * revert: remove AnyDependencies type * refactor: fold switch case * docs: specifics * chore: change all file names to camel case * refactor: change all files to camelcase and refactor * revert: remove cloudflare typings * feat: SernEmitter now captures promise rejections * chore: fix InitArgs missing * chore: move typings * chore: move and clean * chore: delete plugins dir * chore: cleanup dispatchers subdirectory for single file * chore: move context into structures directory * refactor: cleaning up code and renaming variables * chore: update name of function to reflect use * revert: multiple entry points * revert: readd discordEvent * refactor: rename, format, move things * feat: types organization and cleaning up code base * fix: unaliased modules would throw error * build: speed up build * revert: readd module store and add contract * add separate id for id processing * chore: progress of globalizing dependencies type * chore: update container and init hook progress * style: format & lint * feat: dev and prod mode * fix: directories ignoring incorrectly * refactor: move metadata outside of module declarations * revert: re export command executable and event executable * refactor: a lot * fix: plugins for class modules and module loader * style: pretty * fix class based module loading * feat: globalize dependencies type * revert: internal name * feat: add new sern emitter event * refactor: remove cast * refactor: add better typings for sern event modules * test: add tests * test: add more tests * feat: change error handling contract * chore: make changes in codebase after error contract change * docs: add purpose of d.ts file * revert removal of crash method and mark deprecated * fix: typings for options- have access to all properties now * refactor: npx knip * 3.0.0-rc1 * chore: fix for version 3 and reexport old types * fix: reexport payload and button modules * fix: component commands incorrectly aligned and ordered * chore: bump version * test: add id generation testing * refactor: algorithm for module resolution * chore: bump vers * test: add eventDispatcher test * *.test.ts * fix: autocomplete nested option * chore: bump vers * add npmignore .yarn * feat: experimental loading sern.config.json * refactor: simplify build * chore: bump vers * chore: add documentation for service api * add since * feat: add possible mode option in file loading mode * refactor: remove two unneeded functions and refactor to throw early * refactor: clean up handler code * fix: undefined this binding * refactor: clean up signatures and types * refactor: make evident the internal api and move around stuff * refactor: remove circular dependencies * fix circulars and imports * oops, moving around mroe stuff * refresh lock * chore: import type and prettier * style: prettier * feat: solidify init logic * fix module-loading.ts --------- Co-authored-by: jacoobes <jacobnguyend@gmail.com>
405 lines
16 KiB
TypeScript
405 lines
16 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { PluginType, SernOptionsData, controller } from '../../src/index';
|
|
import { partitionPlugins, treeSearch } from '../../src/core/functions';
|
|
import { faker } from '@faker-js/faker';
|
|
import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js';
|
|
|
|
vi.mock('discord.js', () => {
|
|
const Collection = Map;
|
|
const ModalSubmitInteraction = class {
|
|
customId;
|
|
type = 5;
|
|
isModalSubmit = vi.fn();
|
|
constructor(customId) {
|
|
this.customId = customId;
|
|
}
|
|
};
|
|
const ButtonInteraction = class {
|
|
customId;
|
|
type = 3;
|
|
componentType = 2;
|
|
isButton = vi.fn();
|
|
constructor(customId) {
|
|
this.customId = customId;
|
|
}
|
|
};
|
|
const AutocompleteInteraction = class {
|
|
type = 4;
|
|
option: string;
|
|
constructor(s: string) {
|
|
this.option = s;
|
|
}
|
|
options = {
|
|
getFocused: vi.fn(),
|
|
getSubcommand: vi.fn(),
|
|
};
|
|
};
|
|
|
|
return {
|
|
Collection,
|
|
ComponentType: {
|
|
Button: 2,
|
|
},
|
|
InteractionType: {
|
|
Ping: 1,
|
|
ApplicationCommand: 2,
|
|
MessageComponent: 3,
|
|
ApplicationCommandAutocomplete: 4,
|
|
ModalSubmit: 5,
|
|
},
|
|
ApplicationCommandOptionType: {
|
|
Subcommand: 1,
|
|
SubcommandGroup: 2,
|
|
String: 3,
|
|
Integer: 4,
|
|
Boolean: 5,
|
|
User: 6,
|
|
Channel: 7,
|
|
Role: 8,
|
|
Mentionable: 9,
|
|
Number: 10,
|
|
Attachment: 11,
|
|
},
|
|
ApplicationCommandType: {
|
|
ChatInput: 1,
|
|
User: 2,
|
|
Message: 3,
|
|
},
|
|
ModalSubmitInteraction,
|
|
ButtonInteraction,
|
|
AutocompleteInteraction,
|
|
};
|
|
});
|
|
|
|
describe('functions', () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
function createRandomPlugins(len: number) {
|
|
const random = () => Math.floor(Math.random() * 2) + 1; // 1 or 2, plugin enum
|
|
return Array.from({ length: len }, () => ({
|
|
type: random(),
|
|
execute: () => (random() === 1 ? controller.next() : controller.stop()),
|
|
}));
|
|
}
|
|
function createRandomChoice() {
|
|
return {
|
|
type: faker.number.int({ min: 1, max: 11 }),
|
|
name: faker.word.noun(),
|
|
description: faker.word.adjective(),
|
|
};
|
|
}
|
|
it('should partition plugins correctly', () => {
|
|
const plugins = createRandomPlugins(100);
|
|
const [onEvent, init] = partitionPlugins(plugins);
|
|
for (const el of onEvent) expect(el.type).to.equal(PluginType.Control);
|
|
|
|
for (const el of init) expect(el.type).to.equal(PluginType.Init);
|
|
});
|
|
|
|
it('should tree search options tree depth 1', () => {
|
|
//@ts-expect-error mocking
|
|
let autocmpInteraction = new AutocompleteInteraction('autocomplete');
|
|
const options: SernOptionsData[] = [
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'autocomplete',
|
|
description: 'here',
|
|
autocomplete: true,
|
|
command: { onEvent: [], execute: vi.fn() },
|
|
},
|
|
];
|
|
autocmpInteraction.options.getFocused.mockReturnValue({
|
|
name: 'autocomplete',
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
const result = treeSearch(autocmpInteraction, options);
|
|
expect(result == undefined).to.be.false;
|
|
expect(result.name).to.be.eq('autocomplete');
|
|
expect(result.command).to.be.not.undefined;
|
|
}),
|
|
it('should tree search depth 2', () => {
|
|
//@ts-expect-error mocking
|
|
let autocmpInteraction = new AutocompleteInteraction('nested');
|
|
const subcommandName = faker.string.alpha();
|
|
const options: SernOptionsData[] = [
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName,
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'nested',
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: () => {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
];
|
|
autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName);
|
|
autocmpInteraction.options.getFocused.mockReturnValue({
|
|
name: 'nested',
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
const result = treeSearch(autocmpInteraction, options);
|
|
expect(result == undefined).to.be.false;
|
|
expect(result.name).to.be.eq('nested');
|
|
expect(result.command).to.be.not.undefined;
|
|
});
|
|
|
|
it('should tree search depth n > 2', () => {
|
|
//@ts-expect-error mocking
|
|
let autocmpInteraction = new AutocompleteInteraction('nested');
|
|
const subcommandName = faker.string.alpha();
|
|
const options: SernOptionsData[] = [
|
|
{
|
|
type: ApplicationCommandOptionType.SubcommandGroup,
|
|
name: faker.string.alpha(),
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName,
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'nested',
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: () => {},
|
|
},
|
|
},
|
|
createRandomChoice(),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName);
|
|
autocmpInteraction.options.getFocused.mockReturnValue({
|
|
name: 'nested',
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
const result = treeSearch(autocmpInteraction, options);
|
|
expect(result == undefined).to.be.false;
|
|
expect(result.name).to.be.eq('nested');
|
|
expect(result.command).to.be.not.undefined;
|
|
});
|
|
it('should correctly resolve suboption of the same name given two subcommands ', () => {
|
|
let autocmpInteraction = new AutocompleteInteraction('nested');
|
|
const subcommandName = faker.string.alpha();
|
|
const options: SernOptionsData[] = [
|
|
{
|
|
type: ApplicationCommandOptionType.SubcommandGroup,
|
|
name: faker.string.alpha(),
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName,
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'nested',
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: () => {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName + 'a',
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'nested',
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: () => {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName);
|
|
autocmpInteraction.options.getFocused.mockReturnValue({
|
|
name: 'nested',
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
const result = treeSearch(autocmpInteraction, options);
|
|
expect(result).toBeTruthy();
|
|
expect(result.name).to.be.eq('nested');
|
|
expect(result.command).to.be.not.undefined;
|
|
});
|
|
it('two subcommands with an option of the same name', () => {
|
|
let autocmpInteraction = new AutocompleteInteraction('nested');
|
|
const subcommandName = faker.string.alpha();
|
|
const options: SernOptionsData[] = [
|
|
{
|
|
type: ApplicationCommandOptionType.SubcommandGroup,
|
|
name: faker.string.alpha(),
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName,
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'nested',
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: () => {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName + 'a',
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: 'nested',
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: () => {},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName);
|
|
autocmpInteraction.options.getFocused.mockReturnValue({
|
|
name: 'nested',
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
const result = treeSearch(autocmpInteraction, options);
|
|
expect(result).toBeTruthy();
|
|
expect(result.name).to.be.eq('nested');
|
|
expect(result.command).to.be.not.undefined;
|
|
|
|
let autocmpInteraction2 = new AutocompleteInteraction('nested');
|
|
autocmpInteraction2.options.getSubcommand.mockReturnValue(subcommandName + 'a');
|
|
autocmpInteraction2.options.getFocused.mockReturnValue({
|
|
name: 'nested',
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
const result2 = treeSearch(autocmpInteraction2, options);
|
|
expect(result2).toBeTruthy();
|
|
expect(result2?.name).toEqual('nested');
|
|
});
|
|
|
|
it('simulates autocomplete typing and resolution', () => {
|
|
const subcommandName = faker.string.alpha();
|
|
const optionName = faker.word.noun();
|
|
const options: SernOptionsData[] = [
|
|
{
|
|
type: ApplicationCommandOptionType.SubcommandGroup,
|
|
name: faker.string.alpha(),
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName,
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: optionName,
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: vi.fn(),
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: ApplicationCommandOptionType.Subcommand,
|
|
name: subcommandName + 'a',
|
|
description: faker.string.alpha(),
|
|
options: [
|
|
createRandomChoice(),
|
|
{
|
|
type: ApplicationCommandOptionType.String,
|
|
name: optionName,
|
|
description: faker.string.alpha(),
|
|
autocomplete: true,
|
|
command: {
|
|
onEvent: [],
|
|
execute: vi.fn(),
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
let accumulator = '';
|
|
let result: unknown;
|
|
for (const char of optionName) {
|
|
accumulator += char;
|
|
|
|
const autocomplete = new AutocompleteInteraction(accumulator);
|
|
autocomplete.options.getSubcommand.mockReturnValue(subcommandName);
|
|
autocomplete.options.getFocused.mockReturnValue({
|
|
name: accumulator,
|
|
value: faker.string.alpha(),
|
|
focused: true,
|
|
});
|
|
result = treeSearch(autocomplete, options);
|
|
}
|
|
expect(result).toBeTruthy();
|
|
});
|
|
});
|