diff --git a/.drone.yml b/.drone.yml index a2f4d8f..d98fb55 100644 --- a/.drone.yml +++ b/.drone.yml @@ -12,5 +12,4 @@ steps: image: node commands: - yarn install - - yarn build - yarn test \ No newline at end of file diff --git a/package.json b/package.json index 5b05782..603afc8 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ ], "scripts": { "build": "tsc", - "test": "jest", + "test": "tsc && jest", "release": "tsc && yarn publish", "lint": "eslint --fix" } diff --git a/src/classes/Command.ts b/src/classes/Command.ts index 88057e6..0db5e14 100644 --- a/src/classes/Command.ts +++ b/src/classes/Command.ts @@ -15,6 +15,9 @@ abstract class Command implements ICommand { constructor(def: CommandDefinition) { + if (typeof def !== 'object') throw new Error('Missing command definition'); + + if (!def.name) throw new Error('Missing name for command'); this.name = def.name; this.aliases = def.aliases || []; @@ -24,6 +27,8 @@ abstract class Command implements ICommand { if (opt instanceof CommandOption) this.options.push(opt); else this.options.push(new CommandOption(opt)); } + if (this.options.some(opt => [OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP].includes(opt.type)) + && this.options.some(opt => ![OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP].includes(opt.type))) throw new Error('Cannot have subcommand(group)s on the same level as an option') } diff --git a/src/classes/CommandOption.ts b/src/classes/CommandOption.ts index efba9fd..0a96329 100644 --- a/src/classes/CommandOption.ts +++ b/src/classes/CommandOption.ts @@ -49,17 +49,27 @@ class CommandOption implements ICommandOption { this.name = def.name; this.aliases = def.aliases || []; + this.strict = def.strict || false; + this.type = def.type ?? OptionType.STRING; + this.flag = def.flag || false; + this.options = []; for (const opt of def.options || []) { if (opt instanceof CommandOption) this.options.push(opt); else this.options.push(new CommandOption(opt)); } + + if (this.type === OptionType.SUB_COMMAND_GROUP && this.options.some(opt => opt.type === OptionType.SUB_COMMAND_GROUP)) throw new Error('Cannot nest subcommand groups'); + if (this.type === OptionType.SUB_COMMAND && this.options.some(opt => opt.type === OptionType.SUB_COMMAND_GROUP)) throw new Error('Cannot have subcommand groups under a subcommand'); + if (this.type === OptionType.SUB_COMMAND && this.options.some(opt => opt.type === OptionType.SUB_COMMAND)) throw new Error('Cannot nest subcommands'); + if (![OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP].includes(this.type) + && this.options.some(opt => [OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP].includes(opt.type))) throw new Error('Cannot have subcommand(group)s under an option'); + + if (this.options.some(opt => [OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP].includes(opt.type)) + && this.options.some(opt => ![OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP].includes(opt.type))) throw new Error('Cannot have subcommand(group)s on the same level as an option') + this.choices = def.choices || []; - this.strict = def.strict || false; - this.type = def.type ?? OptionType.STRING; - this.flag = def.flag || false; - this.valueOptional = def.valueOptional || false; this.defaultValue = def.defaultValue ?? null; this.valueAsAlias = def.valueAsAlias || false; diff --git a/tests/CommandOption.test.js b/tests/CommandOption.test.js index 1e0b73e..e876e09 100644 --- a/tests/CommandOption.test.js +++ b/tests/CommandOption.test.js @@ -20,7 +20,7 @@ const parser = new Parser({ (async () => { const parsed = await parser.parseMessage('test message.author') - console.log(parsed) + // console.log(parsed) })(); test('', () => { diff --git a/tests/Parser.test.js b/tests/Parser.test.js index b35a775..8b0f8cf 100644 --- a/tests/Parser.test.js +++ b/tests/Parser.test.js @@ -1,11 +1,70 @@ const { inspect } = require('node:util'); const { Parser, Command, OptionType } = require('../build'); +test('Command definition', () => { + expect(() => new Command()).toThrow(); + expect(() => new Command({})).toThrow(); + expect(() => new Command(235345)).toThrow(); + expect(() => new Command({ name: 'create' })).not.toThrow(); + + expect(() => new Command({ + name: 'create', + options: [{ + name: 'test', + type: OptionType.SUB_COMMAND, + options: [{ + name:'dingus', + type: OptionType.SUB_COMMAND + }] + }] + })).toThrow(); + + expect(() => new Command({ + name: 'create', + options: [{ + name: 'test', + type: OptionType.STRING, + options: [{ + name:'dingus', + type: OptionType.SUB_COMMAND + }] + }] + })).toThrow(); + + expect(() => new Command({ + name: 'create', + options: [{ + name: 'test', + type: OptionType.SUB_COMMAND_GROUP, + options: [{ + name:'dingus', + type: OptionType.SUB_COMMAND + }] + }] })).not.toThrow(); +}) test('Command Parser', async () => { - const command = new Command({ name: 'create', options: [{ name: 'registration-code', aliases: ['code'], type: OptionType.SUB_COMMAND }, { name: 'test' }] }); + const command = new Command({ + name: 'create', + options: [{ + name: 'registration-code', + aliases: ['code'], + type: OptionType.SUB_COMMAND, + options: [{ + name: 'amount', + type: OptionType.INTEGER, + defaultValue: 1, + valueOptional: true + }] + }] + }); const parser = new Parser({ commands: [command], prefix: '' }); await expect(parser.parseMessage('create')).rejects.toThrow(); + // Cannot have an option at the sub-command level + await expect(parser.parseMessage('create test')).rejects.toThrow(); + + await expect(parser.parseMessage('create code')).resolves.toHaveProperty('command.name', 'create'); + await expect(parser.parseMessage('create code 1')).resolves.toHaveProperty('args.amount.value', 1); }); \ No newline at end of file