diff --git a/src/core/id.ts b/src/core/id.ts index 9591b3e0..8a269a0c 100644 --- a/src/core/id.ts +++ b/src/core/id.ts @@ -1,10 +1,10 @@ -import { ApplicationCommandType, ComponentType, Interaction, InteractionType } from 'discord.js'; +import { ApplicationCommandType, ComponentType, type Interaction, InteractionType } from 'discord.js'; import { CommandType, EventType } from './structures/enums'; const parseParams = (event: { customId: string }, id: string, append: string) => { const hasSlash = event.customId.indexOf('/') if(hasSlash === -1) { - return { id }; + return { id:id+append }; } const baseid = event.customId.substring(0, hasSlash); const params = event.customId.substring(hasSlash+1); diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index 7c0c4809..2fe45ff8 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -1,4 +1,3 @@ -import type { DependencyConfiguration } from '../../types/ioc'; import { Container } from './container'; import * as __Services from '../structures/default-services'; import type { Logging } from '../interfaces'; @@ -11,11 +10,11 @@ export function disposeAll(logger: Logging|undefined) { .then(() => logger?.info({ message: 'Cleaning container and crashing' })); } - type Insertable = | ((container: Dependencies) => object) | object -const dependencyBuilder = (container: Container, excluded: string[] ) => { + +const dependencyBuilder = (container: Container) => { return { /** * Insert a dependency into your container. @@ -29,14 +28,6 @@ const dependencyBuilder = (container: Container, excluded: string[] ) => { container.addWiredSingleton(key, (cntr) => v(cntr)) } }, - /** - * Exclude any dependencies from being added. - * Warning: this could lead to bad errors if not used correctly - */ - exclude(...keys: (keyof Dependencies)[]) { - keys.forEach(key => excluded.push(key)); - }, - /** * @param key the key of the dependency * @param v The dependency to swap out. @@ -49,60 +40,28 @@ const dependencyBuilder = (container: Container, excluded: string[] ) => { }; }; - +/** + * + * + * + */ type ValidDependencyConfig = | ((c: ReturnType) => any) - | DependencyConfiguration; -/** - * Given the user's conf, check for any excluded/included dependency keys. - * Then, call conf.build to get the rest of the users' dependencies. - * Finally, update the containerSubject with the new container state - * @param conf - */ -async function composeRoot( - container: Container, - conf: DependencyConfiguration, -) { - //container should have no client or logger yet. - const hasLogger = container.hasKey('@sern/logger'); - if (!hasLogger) { - __add_container('@sern/logger', new __Services.DefaultLogging()); - } - __add_container('@sern/errors', new __Services.DefaultErrorHandling()); - __add_container('@sern/modules', new Map()) - __add_container('@sern/emitter', new EventEmitter()) - __add_wiredcontainer('@sern/cron', deps => new __Services.Cron(deps)) - //Build the container based on the callback provided by the user - conf.build(container as Container); - - if (!hasLogger) { - container.get('@sern/logger') - ?.info({ message: 'All dependencies loaded successfully.' }); - } - await container.ready(); -} export async function makeDependencies (conf: ValidDependencyConfig) { const container = await __init_container({ autowire: false }); - if(typeof conf === 'function') { - const excluded: string[] = []; - conf(dependencyBuilder(container, excluded)); - //We only include logger if it does not exist - const includeLogger = - !excluded.includes('@sern/logger') - && !container.hasKey('@sern/logger'); + conf(dependencyBuilder(container)); + //We only include logger if it does not exist + const includeLogger = !container.hasKey('@sern/logger'); - if(includeLogger) { - __add_container('@sern/logger', new __Services.DefaultLogging); - } - __add_container('@sern/errors', new __Services.DefaultErrorHandling()); - __add_container('@sern/modules', new Map()) - __add_container('@sern/emitter', new EventEmitter()) - __add_wiredcontainer('@sern/cron', deps => new __Services.Cron(deps)) - await container.ready(); - } else { - await composeRoot(container, conf); + if(includeLogger) { + __add_container('@sern/logger', new __Services.DefaultLogging); } + __add_container('@sern/errors', new __Services.DefaultErrorHandling); + __add_container('@sern/modules', new Map) + __add_container('@sern/emitter', new EventEmitter) + __add_wiredcontainer('@sern/cron', deps => new __Services.Cron(deps)) + await container.ready(); } diff --git a/src/core/structures/context.ts b/src/core/structures/context.ts index 40292df6..e19055d0 100644 --- a/src/core/structures/context.ts +++ b/src/core/structures/context.ts @@ -28,7 +28,7 @@ export class Context extends CoreContext { get options() { return this.interaction.options; } - + //TODO args(type: 'message'|'interaction', parser?: Function) { switch(type) { case 'message': { @@ -42,10 +42,12 @@ export class Context extends CoreContext { } protected constructor(protected ctx: Result, - public prefix?: string) { + private __prefix?: string) { super(ctx); } - + public get prefix() { + return this.__prefix; + } public get id(): Snowflake { return safeUnwrap(this.ctx .map(m => m.id) diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index 1a876a15..d5a7245c 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -181,7 +181,7 @@ export function executeModule( * - if all results are ok, the stream is converted to { config.onNext } * config.onNext will be returned if everything is okay. * @param config - * @returns receiver function for flattening a stream of data + * @returns function which calls all plugins and returns onNext or fail */ export function createResultResolver(config: { onStop?: (module: Module, err?: string) => unknown; @@ -198,11 +198,11 @@ export function createResultResolver(config: { }; }; -export async function callInitPlugins(module: Module, deps: Dependencies, sEmitter?: Emitter) { +export async function callInitPlugins(module: Module, deps: Dependencies, emit?: boolean ) { let _module = module; for(const plugin of _module.plugins ?? []) { const res = await plugin.execute({ - module, absPath: _module.meta.absPath , + module, absPath: _module.meta.absPath, updateModule: (partial: Partial) => { _module = { ..._module, ...partial }; return _module; @@ -210,7 +210,10 @@ export async function callInitPlugins(module: Module, deps: Dependencies, sEmitt deps }); if(res.isErr()) { - sEmitter?.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); + if(emit) { + deps['@sern/emitter'] + ?.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); + } throw Error("Plugin failed with controller.stop()"); } } diff --git a/src/handlers/ready.ts b/src/handlers/ready.ts index 4dbbdee2..6651a3ee 100644 --- a/src/handlers/ready.ts +++ b/src/handlers/ready.ts @@ -24,7 +24,7 @@ export default async function(dir: string, deps : UnpackedDependencies) { if(!validType) { throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``); } - const resultModule = await callInitPlugins(module, deps, sEmitter); + const resultModule = await callInitPlugins(module, deps, true); // FREEZE! no more writing!! commands.set(resultModule.meta.id, Object.freeze(resultModule)); sEmitter.emit('module.register', resultPayload(PayloadType.Success, resultModule)); diff --git a/src/handlers/user-defined-events.ts b/src/handlers/user-defined-events.ts index fecee051..ebbe4b9b 100644 --- a/src/handlers/user-defined-events.ts +++ b/src/handlers/user-defined-events.ts @@ -33,10 +33,7 @@ export default async function(deps: UnpackedDependencies, eventDir: string) { } from(eventModules) .pipe(map(intoDispatcher(deps)), - /** - * Where all events are turned on - */ - mergeAll(), + mergeAll(), // all eventListeners are turned on handleCrash(deps)) .subscribe(); } diff --git a/test/core/functions.test.ts b/test/core/functions.test.ts index 512d34a7..8a54ec7d 100644 --- a/test/core/functions.test.ts +++ b/test/core/functions.test.ts @@ -1,11 +1,13 @@ +//@ts-nocheck + 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; +vi.mock('discord.js', async (importOriginal) => { + const mod = await importOriginal() const ModalSubmitInteraction = class { customId; type = 5; @@ -36,35 +38,11 @@ vi.mock('discord.js', () => { }; 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, - }, + Collection: mod.Collection, + ComponentType: mod.ComponentType, + InteractionType: mod.InteractionType, + ApplicationCommandOptionType: mod.ApplicationCommandOptionType, + ApplicationCommandType: mod.ApplicationCommandType, ModalSubmitInteraction, ButtonInteraction, AutocompleteInteraction, diff --git a/test/core/id.test.ts b/test/core/id.test.ts index db34ea71..84433c1b 100644 --- a/test/core/id.test.ts +++ b/test/core/id.test.ts @@ -1,7 +1,51 @@ +//@ts-nocheck +import { expect, test, vi } from 'vitest' import { CommandType } from '../../src/core/structures/enums'; + import * as Id from '../../src/core/id' -import { expect, test } from 'vitest' +import { ButtonInteraction, ModalSubmitInteraction } from 'discord.js'; +vi.mock('discord.js', async (importOriginal) => { + const mod = await importOriginal() + 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: mod.Collection, + ComponentType: mod.ComponentType, + InteractionType: mod.InteractionType, + ApplicationCommandOptionType: mod.ApplicationCommandOptionType, + ApplicationCommandType: mod.ApplicationCommandType, + ModalSubmitInteraction, + ButtonInteraction, + AutocompleteInteraction, + }; +}); test('id -> Text', () => { const bothCmdId = Id.create("ping", CommandType.Text) expect(bothCmdId).toBe("ping_T") @@ -60,5 +104,37 @@ test('id -> ChannelSelect', () => { expect(modal).toBe("mychannelselect_C8"); }) +test('id reconstruct button', () => { + const idload = Id.reconstruct(new ButtonInteraction("btn")) + expect(idload[0].id).toBe("btn_C2") +}) + +test('id reconstruct button with params', () => { + const idload = Id.reconstruct(new ButtonInteraction("btn/asdf")) + expect(idload[0].id).toBe("btn_C2") + expect(idload[0].params).toBe("asdf") +}) +test('id reconstruct modal with params', () => { + const idload = Id.reconstruct(new ModalSubmitInteraction("btn/asdf")) + expect(idload[0].id).toBe("btn_M") + expect(idload[0].params).toBe("asdf") +}) +test('id reconstruct modal', () => { + const idload = Id.reconstruct(new ModalSubmitInteraction("btn")) + expect(idload[0].id).toBe("btn_M") + expect(idload[0].params).toBe(undefined) +}) +test('id reconstruct button with empty params', () => { + const idload = Id.reconstruct(new ButtonInteraction("btn/")) + expect(idload[0].id).toBe("btn_C2") + expect(idload[0].params).toBe("") +}) + +test('id reconstruct button', () => { + const idload = Id.reconstruct(new ButtonInteraction("btn")) + expect(idload[0].id).toBe("btn_C2") + expect(idload[0].params).toBe(undefined) +}) + diff --git a/test/handlers/dispatchers.test.ts b/test/handlers/dispatchers.test.ts deleted file mode 100644 index 9337d7e8..00000000 --- a/test/handlers/dispatchers.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { beforeEach, describe, expect, vi, it } from 'vitest'; -import { eventDispatcher } from '../../src/handlers/event-utils'; -import { faker } from '@faker-js/faker'; -import { Module } from '../../src/types/core-modules'; -import { Processed } from '../../src/types/core-modules'; -import { EventEmitter } from 'events'; -import { EventType } from '../../dist/core/structures/enums'; - -function createRandomModule(): Processed { - return { - type: EventType.Discord, - meta: { id:"", absPath: "" }, - description: faker.string.alpha(), - name: "abc", - onEvent: [], - plugins: [], - execute: vi.fn(), - }; -} - -function mockDeps() { - return { - '@sern/client': {} - } -} - - -describe('eventDispatcher standard', () => { - let m: Processed; - let ee: EventEmitter; - beforeEach(() => { - ee = new EventEmitter(); - m = createRandomModule(); - }); - - it('should throw', () => { - expect(() => eventDispatcher(mockDeps(), m, 'not event emitter')).toThrowError(); - }); - - it("Shouldn't throw", () => { - expect(() => eventDispatcher(mockDeps(), m, ee)).not.toThrowError(); - }); -}); diff --git a/test/handlers/index.test.ts b/test/handlers/index.test.ts new file mode 100644 index 00000000..5c6a6de7 --- /dev/null +++ b/test/handlers/index.test.ts @@ -0,0 +1,136 @@ +//@ts-nocheck +import { beforeEach, describe, expect, vi, it } from 'vitest'; +import { callInitPlugins, eventDispatcher } from '../../src/handlers/event-utils'; + +import { Client } from 'discord.js' +import { faker } from '@faker-js/faker'; +import { Module } from '../../src/types/core-modules'; +import { Processed } from '../../src/types/core-modules'; +import { EventEmitter } from 'events'; +import { EventType } from '../../dist/core/structures/enums'; +import { CommandInitPlugin, controller } from '../../src'; + +vi.mock('discord.js', () => { + const Client = vi.fn() + Client.prototype.login= vi.fn() + 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 { + Client, + 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, + }; +}) +function createRandomPlugin (s: 'go', mut?: Partial) { + return CommandInitPlugin(({ module, updateModule }) => { + if(mut) { + updateModule(mut) + } + return s == 'go' + ? controller.next() + : controller.stop() + }) +} +function createRandomModule(plugins: any[]): Processed { + return { + type: EventType.Discord, + meta: { id:"", absPath: "" }, + description: faker.string.alpha(), + plugins, + name: "cheese", + onEvent: [], + execute: vi.fn(), + }; +} + +function mockDeps() { + return { + '@sern/client': new Client(), + '@sern/emitter': new EventEmitter() + } +} + +describe('eventDispatcher standard', () => { + let m: Processed; + let ee: EventEmitter; + beforeEach(() => { + ee = new EventEmitter(); + m = createRandomModule(); + }); + + it('should throw', () => { + expect(() => eventDispatcher(mockDeps(), m, 'not event emitter')).toThrowError(); + }); + + it("Shouldn't throw", () => { + expect(() => eventDispatcher(mockDeps(), m, ee)).not.toThrowError(); + }); + it('mutate with init plugins', async () => { + const deps = mockDeps() + const plugins = createRandomPlugin('go', { name: "abc" }) + const mod = createRandomModule([plugins]) + const s = await callInitPlugins(mod, deps, false) + expect(s.name).not.equal(mod.name) + }) + +}); + +