mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
feat!: v3 (#294)
* 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>
This commit is contained in:
16
test/core/contracts.test.ts
Normal file
16
test/core/contracts.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { assertType, describe, it } from 'vitest';
|
||||
|
||||
import { ModuleStore } from '../../src';
|
||||
import * as DefaultContracts from '../../src/core/structures/services';
|
||||
import * as Contracts from '../../src/core/contracts/index.js';
|
||||
|
||||
describe('default contracts', () => {
|
||||
it('should satisfy contracts', () => {
|
||||
assertType<Contracts.Logging>(new DefaultContracts.DefaultLogging());
|
||||
assertType<Contracts.ErrorHandling>(new DefaultContracts.DefaultErrorHandling());
|
||||
assertType<Contracts.ModuleManager>(
|
||||
new DefaultContracts.DefaultModuleManager(new ModuleStore()),
|
||||
);
|
||||
assertType<Contracts.CoreModuleStore>(new ModuleStore());
|
||||
});
|
||||
});
|
||||
32
test/core/create-plugin.test.ts
Normal file
32
test/core/create-plugin.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
CommandControlPlugin,
|
||||
CommandInitPlugin,
|
||||
EventControlPlugin,
|
||||
EventInitPlugin,
|
||||
} from '../../src/core/create-plugins';
|
||||
import { PluginType, controller } from '../../src';
|
||||
|
||||
describe('create-plugins', () => {
|
||||
it('should make proper control plugins', () => {
|
||||
const pl = EventControlPlugin(() => controller.next());
|
||||
expect(pl).to.have.all.keys(['type', 'execute']);
|
||||
expect(pl.type).toBe(PluginType.Control);
|
||||
expect(pl.execute).an('function');
|
||||
const pl2 = CommandControlPlugin(() => controller.next());
|
||||
expect(pl2).to.have.all.keys(['type', 'execute']);
|
||||
expect(pl2.type).toBe(PluginType.Control);
|
||||
expect(pl2.execute).an('function');
|
||||
});
|
||||
it('should make proper init plugins', () => {
|
||||
const pl = EventInitPlugin(() => controller.next());
|
||||
expect(pl).to.have.all.keys(['type', 'execute']);
|
||||
expect(pl.type).toBe(PluginType.Init);
|
||||
expect(pl.execute).an('function');
|
||||
|
||||
const pl2 = CommandInitPlugin(() => controller.next());
|
||||
expect(pl2).to.have.all.keys(['type', 'execute']);
|
||||
expect(pl2.type).toBe(PluginType.Init);
|
||||
expect(pl2.execute).an('function');
|
||||
});
|
||||
});
|
||||
404
test/core/functions.test.ts
Normal file
404
test/core/functions.test.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
52
test/core/ioc.test.ts
Normal file
52
test/core/ioc.test.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CoreContainer } from '../../src/core/ioc/container';
|
||||
import { CoreDependencies } from '../../src/core/ioc';
|
||||
import { EventEmitter } from 'events';
|
||||
import { DefaultLogging, Init, Logging } from '../../src/core';
|
||||
|
||||
describe('ioc container', () => {
|
||||
let container: CoreContainer<{}>;
|
||||
let initDependency: Logging & Init;
|
||||
beforeEach(() => {
|
||||
initDependency = {
|
||||
init: vi.fn(),
|
||||
error(): void {},
|
||||
warning(): void {},
|
||||
info(): void {},
|
||||
debug(): void {},
|
||||
};
|
||||
container = new CoreContainer();
|
||||
});
|
||||
|
||||
it('should be ready after calling container.ready()', () => {
|
||||
container.ready();
|
||||
expect(container.isReady()).toBe(true);
|
||||
});
|
||||
it('should container all core dependencies', async () => {
|
||||
const keys = [
|
||||
'@sern/modules',
|
||||
'@sern/emitter',
|
||||
'@sern/logger',
|
||||
'@sern/errors',
|
||||
] satisfies (keyof CoreDependencies)[];
|
||||
container.add({
|
||||
'@sern/logger': () => new DefaultLogging(),
|
||||
'@sern/client': () => new EventEmitter(),
|
||||
});
|
||||
for (const k of keys) {
|
||||
//@ts-expect-error typings for iti are strict
|
||||
expect(() => container.get(k)).not.toThrow();
|
||||
}
|
||||
});
|
||||
it('should init modules', () => {
|
||||
container.upsert({ '@sern/logger': initDependency });
|
||||
container.ready();
|
||||
expect(initDependency.init).to.toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should not lazy module', () => {
|
||||
container.upsert({ '@sern/logger': () => initDependency });
|
||||
container.ready();
|
||||
expect(initDependency.init).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
77
test/core/services.test.ts
Normal file
77
test/core/services.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { SpyInstance, afterAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CoreContainer } from '../../src/core/ioc/container';
|
||||
import { DefaultLogging } from '../../src/core';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { commandModule } from '../../src';
|
||||
import * as Id from '../../src/core/id';
|
||||
import { CommandMeta } from '../../src/types/core-modules';
|
||||
|
||||
describe('services', () => {
|
||||
//@ts-ignore
|
||||
let container: CoreContainer<Dependencies>;
|
||||
let consoleMock: SpyInstance;
|
||||
beforeEach(() => {
|
||||
container = new CoreContainer();
|
||||
container.add({ '@sern/logger': () => new DefaultLogging() });
|
||||
container.ready();
|
||||
consoleMock = vi.spyOn(container.get('@sern/logger'), 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
consoleMock.mockReset();
|
||||
});
|
||||
it('module-store.ts', async () => {
|
||||
function createRandomCommandModules() {
|
||||
return commandModule({
|
||||
type: faker.number.int({ min: 1 << 0, max: 1 << 10 }),
|
||||
description: faker.string.alpha(),
|
||||
name: faker.string.alpha(),
|
||||
execute: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
const modules = faker.helpers.multiple(createRandomCommandModules, {
|
||||
count: 40,
|
||||
});
|
||||
|
||||
const paths = faker.helpers
|
||||
.multiple(faker.system.directoryPath, { count: 40 })
|
||||
.map((path, i) => `${path}/${modules[i]}.js`);
|
||||
|
||||
const metadata: CommandMeta[] = modules.map((cm, i) => ({
|
||||
id: Id.create(cm.name, cm.type),
|
||||
isClass: false,
|
||||
fullPath: `${paths[i]}/${cm.name}.js`,
|
||||
}));
|
||||
const moduleManager = container.get('@sern/modules');
|
||||
let i = 0;
|
||||
for (const m of modules) {
|
||||
moduleManager.set(Id.create(m.name, m.type), paths[i]);
|
||||
moduleManager.setMetadata(m, metadata[i]);
|
||||
i++;
|
||||
}
|
||||
for (const m of modules) {
|
||||
expect(moduleManager.getMetadata(m), 'module references do not exist').toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
//todo add more
|
||||
it('error-handling', () => {
|
||||
const errorHandler = container.get('@sern/errors');
|
||||
const lifetime = errorHandler.keepAlive;
|
||||
for (let i = 0; i < lifetime; i++) {
|
||||
if (i == lifetime - 1) {
|
||||
expect(() => errorHandler.updateAlive(new Error('poo'))).toThrowError();
|
||||
} else {
|
||||
expect(() => errorHandler.updateAlive(new Error('poo'))).not.toThrowError();
|
||||
}
|
||||
}
|
||||
});
|
||||
//todo add more, spy on every instance?
|
||||
it('logger', () => {
|
||||
container.get('@sern/logger').error({ message: 'error' });
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledOnce();
|
||||
expect(consoleMock).toHaveBeenLastCalledWith({ message: 'error' });
|
||||
});
|
||||
});
|
||||
45
test/handlers/dispatchers.test.ts
Normal file
45
test/handlers/dispatchers.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { beforeEach, describe, expect, vi, it } from 'vitest';
|
||||
import { createResultResolver, eventDispatcher } from '../../src/handlers/_internal';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { Module } from '../../src/core/types/modules';
|
||||
import { Processed } from '../../src/handlers/types';
|
||||
import { CommandType } from '../../src/core';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
function createRandomModule(): Processed<Module> {
|
||||
return {
|
||||
type: faker.number.int({
|
||||
min: CommandType.Text,
|
||||
max: CommandType.ChannelSelect,
|
||||
}),
|
||||
description: faker.string.alpha(),
|
||||
name: faker.string.alpha(),
|
||||
onEvent: [],
|
||||
plugins: [],
|
||||
execute: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
describe('eventDispatcher standard', () => {
|
||||
let m: Processed<Module>;
|
||||
let ee: EventEmitter;
|
||||
beforeEach(() => {
|
||||
ee = new EventEmitter();
|
||||
m = createRandomModule();
|
||||
});
|
||||
|
||||
it('should throw', () => {
|
||||
expect(() => eventDispatcher(m, 'not event emitter')).toThrowError();
|
||||
});
|
||||
it("Shouldn't throw", () => {
|
||||
expect(() => eventDispatcher(m, ee)).not.toThrowError();
|
||||
});
|
||||
|
||||
it('Should be called once', () => {
|
||||
const s = eventDispatcher(m, ee);
|
||||
s.subscribe();
|
||||
ee.emit(m.name, faker.string.alpha());
|
||||
|
||||
expect(m.execute).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
72
test/handlers/id.test.ts
Normal file
72
test/handlers/id.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import * as Id from '../../src/core/id';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { CommandModule, CommandType, commandModule } from '../../src';
|
||||
import { CommandTypeDiscordApi } from '../../src/core/id';
|
||||
|
||||
function createRandomCommandModules() {
|
||||
const randomCommandType = [
|
||||
CommandType.Text,
|
||||
CommandType.Both,
|
||||
CommandType.CtxMsg,
|
||||
CommandType.CtxUser,
|
||||
CommandType.Modal,
|
||||
CommandType.ChannelSelect,
|
||||
CommandType.RoleSelect,
|
||||
CommandType.UserSelect,
|
||||
CommandType.StringSelect,
|
||||
CommandType.Button,
|
||||
];
|
||||
return commandModule({
|
||||
type: randomCommandType[Math.floor(Math.random() * randomCommandType.length)],
|
||||
description: faker.string.alpha(),
|
||||
name: faker.string.alpha(),
|
||||
execute: () => {},
|
||||
});
|
||||
}
|
||||
function createMetadata(c: CommandModule) {
|
||||
return {
|
||||
fullPath: faker.system.filePath(),
|
||||
id: Id.create(c.name, c.type),
|
||||
isClass: Boolean(Math.floor(Math.random())),
|
||||
};
|
||||
}
|
||||
const appBitField = 0b000000001111;
|
||||
|
||||
describe('id resolution', () => {
|
||||
it('should resolve application commands correctly', () => {
|
||||
const modules = faker.helpers.multiple(createRandomCommandModules, {
|
||||
count: 20,
|
||||
});
|
||||
const metadata = modules.map(createMetadata);
|
||||
metadata.forEach((meta, idx) => {
|
||||
const associatedModule = modules[idx];
|
||||
const am = (appBitField & associatedModule.type) !== 0 ? 'A' : 'C';
|
||||
let uid = 0;
|
||||
if (
|
||||
associatedModule.type === CommandType.Both ||
|
||||
associatedModule.type === CommandType.Modal
|
||||
) {
|
||||
uid = 1;
|
||||
} else {
|
||||
uid = CommandTypeDiscordApi[Math.log2(associatedModule.type)];
|
||||
}
|
||||
expect(meta.id).toBe(associatedModule.name + '_' + am + uid);
|
||||
});
|
||||
});
|
||||
|
||||
it('maps commands type to discord components or application commands', () => {
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.Text)]).toBe(1);
|
||||
|
||||
expect(CommandTypeDiscordApi[1]).toBe(1);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.CtxUser)]).toBe(2);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.CtxMsg)]).toBe(3);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.Button)]).toBe(2);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.StringSelect)]).toBe(3);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.UserSelect)]).toBe(5);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.RoleSelect)]).toBe(6);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.MentionableSelect)]).toBe(7);
|
||||
expect(CommandTypeDiscordApi[Math.log2(CommandType.ChannelSelect)]).toBe(8);
|
||||
expect(CommandTypeDiscordApi[6]).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user