From 698d468acbad63682eaff953052061a12f250929 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Fri, 19 May 2023 19:43:05 -0500 Subject: [PATCH] test: add more tests --- src/core/structures/container.ts | 2 +- test/core/contracts.test.ts | 14 +++ test/core/functions.test.ts | 171 ++++++++++++++++++++++++++++++- test/core/ioc.test.ts | 52 ++++++++++ test/core/services.test.ts | 73 +++++++++++++ 5 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 test/core/contracts.test.ts create mode 100644 test/core/ioc.test.ts create mode 100644 test/core/services.test.ts diff --git a/src/core/structures/container.ts b/src/core/structures/container.ts index 65cc41a..f13b581 100644 --- a/src/core/structures/container.ts +++ b/src/core/structures/container.ts @@ -40,7 +40,7 @@ export class CoreContainer> extends Container { + it('should satisfy contracts', () => { + assertType(new DefaultContracts.DefaultLogging()) + assertType(new DefaultContracts.DefaultErrorHandling()) + assertType(new DefaultContracts.DefaultModuleManager(new ModuleStore())) + assertType(new ModuleStore()) + }) + +}) diff --git a/test/core/functions.test.ts b/test/core/functions.test.ts index fc70447..361c9a2 100644 --- a/test/core/functions.test.ts +++ b/test/core/functions.test.ts @@ -1,11 +1,74 @@ -import { describe, it } from "vitest"; +import { afterEach, describe, it, vi } from "vitest"; import { PluginType, SernOptionsData, controller } from '../../src/index' import { partitionPlugins, treeSearch } from "../../src/core/functions"; import { expect } from "chai"; import { faker } from '@faker-js/faker'; -import { ApplicationCommandOptionType } from "discord.js"; +import { ApplicationCommandOptionType, AutocompleteInteraction } from "discord.js"; -describe('functions', () => { +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() + } + } + + 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 + }, + 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() })) @@ -28,19 +91,119 @@ describe('functions', () => { }) 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, + { + type: ApplicationCommandOptionType.String, name: 'autocomplete', description: 'here', autocomplete: true, command : { onEvent: [], execute:(a) => {} } } ]; + 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 options : SernOptionsData[] = [ + { + type: ApplicationCommandOptionType.Subcommand, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute:() => {} + } + } + ] + } + + ]; + 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 options : SernOptionsData[] = [ + { + + type: ApplicationCommandOptionType.SubcommandGroup, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute:() => {} + } + } + ] + }] + }, + ]; + 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; + }) + + }) + + diff --git a/test/core/ioc.test.ts b/test/core/ioc.test.ts new file mode 100644 index 0000000..8a95fe8 --- /dev/null +++ b/test/core/ioc.test.ts @@ -0,0 +1,52 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { CoreContainer } from '../../src/core/structures/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); + }) + + + +}) diff --git a/test/core/services.test.ts b/test/core/services.test.ts new file mode 100644 index 0000000..5fa4c11 --- /dev/null +++ b/test/core/services.test.ts @@ -0,0 +1,73 @@ +import { SpyInstance, afterAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { CoreContainer } from '../../src/core/structures/container' +import { DefaultLogging } from "../../src/core"; +import { faker } from '@faker-js/faker' +import { commandModule } from "../../src"; +import { uniqueId } from '../../src/handler/id' +import { CommandMeta } from "../../src/core/types/modules"; + +describe('services', () => { + //@ts-ignore + let container: CoreContainer; + 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: cm.name+'_'+uniqueId(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(m.name+'_'+uniqueId(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'); + expect(() => errorHandler.crash(new Error("poo"))).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' }) + }) + + + +})