refactor: remove obsolete builder methods (#7590)

This commit is contained in:
Almeida
2022-03-06 15:27:17 +00:00
committed by GitHub
parent 79d6c0489c
commit 10607dbdaf
7 changed files with 83 additions and 116 deletions

View File

@@ -29,7 +29,7 @@ const getChannelOption = () =>
.setName('owo') .setName('owo')
.setDescription('Testing 123') .setDescription('Testing 123')
.setRequired(true) .setRequired(true)
.addChannelType(ChannelType.GuildText); .addChannelTypes(ChannelType.GuildText);
const getStringOption = () => const getStringOption = () =>
new SlashCommandStringOption().setName('owo').setDescription('Testing 123').setRequired(true); new SlashCommandStringOption().setName('owo').setDescription('Testing 123').setRequired(true);
@@ -100,7 +100,7 @@ describe('Application Command toJSON() results', () => {
}); });
expect( expect(
getIntegerOption().addChoice({ name: 'uwu', value: 1 }).toJSON(), getIntegerOption().addChoices({ name: 'uwu', value: 1 }).toJSON(),
).toEqual<APIApplicationCommandIntegerOption>({ ).toEqual<APIApplicationCommandIntegerOption>({
name: 'owo', name: 'owo',
description: 'Testing 123', description: 'Testing 123',
@@ -143,15 +143,17 @@ describe('Application Command toJSON() results', () => {
choices: [], choices: [],
}); });
expect(getNumberOption().addChoice({ name: 'uwu', value: 1 }).toJSON()).toEqual<APIApplicationCommandNumberOption>({ expect(getNumberOption().addChoices({ name: 'uwu', value: 1 }).toJSON()).toEqual<APIApplicationCommandNumberOption>(
name: 'owo', {
description: 'Testing 123', name: 'owo',
type: ApplicationCommandOptionType.Number, description: 'Testing 123',
required: true, type: ApplicationCommandOptionType.Number,
max_value: 10, required: true,
min_value: -1.23, max_value: 10,
choices: [{ name: 'uwu', value: 1 }], min_value: -1.23,
}); choices: [{ name: 'uwu', value: 1 }],
},
);
}); });
test('GIVEN a role option THEN calling toJSON should return a valid JSON', () => { test('GIVEN a role option THEN calling toJSON should return a valid JSON', () => {
@@ -182,7 +184,7 @@ describe('Application Command toJSON() results', () => {
}); });
expect( expect(
getStringOption().addChoice({ name: 'uwu', value: '1' }).toJSON(), getStringOption().addChoices({ name: 'uwu', value: '1' }).toJSON(),
).toEqual<APIApplicationCommandStringOption>({ ).toEqual<APIApplicationCommandStringOption>({
name: 'owo', name: 'owo',
description: 'Testing 123', description: 'Testing 123',

View File

@@ -87,18 +87,16 @@ describe('Slash Commands', () => {
test('GIVEN valid array of options or choices THEN does not throw error', () => { test('GIVEN valid array of options or choices THEN does not throw error', () => {
expect(() => SlashCommandAssertions.validateMaxOptionsLength([])).not.toThrowError(); expect(() => SlashCommandAssertions.validateMaxOptionsLength([])).not.toThrowError();
expect(() => SlashCommandAssertions.validateMaxChoicesLength([])).not.toThrowError(); expect(() => SlashCommandAssertions.validateChoicesLength(25, [])).not.toThrowError();
}); });
test('GIVEN invalid options or choices THEN throw error', () => { test('GIVEN invalid options or choices THEN throw error', () => {
expect(() => SlashCommandAssertions.validateMaxOptionsLength(null)).toThrowError(); expect(() => SlashCommandAssertions.validateMaxOptionsLength(null)).toThrowError();
expect(() => SlashCommandAssertions.validateMaxChoicesLength(null)).toThrowError();
// Given an array that's too big // Given an array that's too big
expect(() => SlashCommandAssertions.validateMaxOptionsLength(largeArray)).toThrowError(); expect(() => SlashCommandAssertions.validateMaxOptionsLength(largeArray)).toThrowError();
expect(() => SlashCommandAssertions.validateMaxChoicesLength(largeArray)).toThrowError(); expect(() => SlashCommandAssertions.validateChoicesLength(1, largeArray)).toThrowError();
}); });
test('GIVEN valid required parameters THEN does not throw error', () => { test('GIVEN valid required parameters THEN does not throw error', () => {
@@ -179,31 +177,25 @@ describe('Slash Commands', () => {
test('GIVEN a builder with both choices and autocomplete THEN does throw an error', () => { test('GIVEN a builder with both choices and autocomplete THEN does throw an error', () => {
expect(() => expect(() =>
getBuilder().addStringOption( getBuilder().addStringOption(
// @ts-expect-error Checking if check works JS-side too getStringOption().setAutocomplete(true).addChoices({ name: 'Fancy Pants', value: 'fp_1' }),
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
getStringOption().setAutocomplete(true).addChoice('Fancy Pants', 'fp_1'),
), ),
).toThrowError(); ).toThrowError();
expect(() => expect(() =>
getBuilder().addStringOption( getBuilder().addStringOption(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
getStringOption() getStringOption()
.setAutocomplete(true) .setAutocomplete(true)
// @ts-expect-error Checking if check works JS-side too .addChoices(
.addChoices([ { name: 'Fancy Pants', value: 'fp_1' },
['Fancy Pants', 'fp_1'], { name: 'Fancy Shoes', value: 'fs_1' },
['Fancy Shoes', 'fs_1'], { name: 'The Whole shebang', value: 'all' },
['The Whole shebang', 'all'], ),
]),
), ),
).toThrowError(); ).toThrowError();
expect(() => expect(() =>
getBuilder().addStringOption( getBuilder().addStringOption(
// @ts-expect-error Checking if check works JS-side too getStringOption().addChoices({ name: 'Fancy Pants', value: 'fp_1' }).setAutocomplete(true),
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
getStringOption().addChoice('Fancy Pants', 'fp_1').setAutocomplete(true),
), ),
).toThrowError(); ).toThrowError();
@@ -231,20 +223,20 @@ describe('Slash Commands', () => {
test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => { test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => {
expect(() => expect(() =>
getBuilder().addChannelOption(getChannelOption().addChannelType(ChannelType.GuildText)), getBuilder().addChannelOption(getChannelOption().addChannelTypes(ChannelType.GuildText)),
).not.toThrowError(); ).not.toThrowError();
expect(() => { expect(() => {
getBuilder().addChannelOption( getBuilder().addChannelOption(
getChannelOption().addChannelTypes([ChannelType.GuildNews, ChannelType.GuildText]), getChannelOption().addChannelTypes(ChannelType.GuildNews, ChannelType.GuildText),
); );
}).not.toThrowError(); }).not.toThrowError();
}); });
test('GIVEN a builder with valid channel options and channel_types THEN does throw an error', () => { test('GIVEN a builder with valid channel options and channel_types THEN does throw an error', () => {
expect(() => getBuilder().addChannelOption(getChannelOption().addChannelType(100))).toThrowError(); expect(() => getBuilder().addChannelOption(getChannelOption().addChannelTypes(100))).toThrowError();
expect(() => getBuilder().addChannelOption(getChannelOption().addChannelTypes([100, 200]))).toThrowError(); expect(() => getBuilder().addChannelOption(getChannelOption().addChannelTypes(100, 200))).toThrowError();
}); });
test('GIVEN a builder with invalid number min/max options THEN does throw an error', () => { test('GIVEN a builder with invalid number min/max options THEN does throw an error', () => {

View File

@@ -33,13 +33,7 @@ const boopCommand = new SlashCommandBuilder()
option option
.setName('boop_reminder') .setName('boop_reminder')
.setDescription('How often should we remind you to boop the user') .setDescription('How often should we remind you to boop the user')
.addChoice('Every day', 1) .addChoices({ name: 'Every day', value: 1 }, { name: 'Weekly', value: 7 }),
.addChoice('Weekly', 7)
// Or, if you prefer adding more choices at once, you can use an array
.addChoices([
['Every three months', 90],
['Yearly', 365],
]),
); );
// Get the final raw data that can be sent to Discord // Get the final raw data that can be sent to Discord
@@ -71,11 +65,11 @@ const pointsCommand = new SlashCommandBuilder()
option option
.setName('action') .setName('action')
.setDescription('What action should be taken with the users points?') .setDescription('What action should be taken with the users points?')
.addChoices([ .addChoices(
['Add points', 'add'], { name: 'Add points', value: 'add' },
['Remove points', 'remove'], { name: 'Remove points', value: 'remove' },
['Reset points', 'reset'], { name: 'Reset points', value: 'reset' },
]) )
.setRequired(true), .setRequired(true),
) )
.addIntegerOption((option) => option.setName('points').setDescription('Points to add or remove')), .addIntegerOption((option) => option.setName('points').setDescription('Points to add or remove')),
@@ -102,4 +96,4 @@ const pointsCommand = new SlashCommandBuilder()
// Get the final raw data that can be sent to Discord // Get the final raw data that can be sent to Discord
const rawData = pointsCommand.toJSON(); const rawData = pointsCommand.toJSON();
``` ```

View File

@@ -52,8 +52,10 @@ export function validateRequired(required: unknown): asserts required is boolean
booleanPredicate.parse(required); booleanPredicate.parse(required);
} }
export function validateMaxChoicesLength(choices: APIApplicationCommandOptionChoice[]) { const choicesLengthPredicate = z.number().lte(25);
maxArrayLengthPredicate.parse(choices);
export function validateChoicesLength(amountAdding: number, choices?: APIApplicationCommandOptionChoice[]): void {
choicesLengthPredicate.parse((choices?.length ?? 0) + amountAdding);
} }
export function assertReturnOfBuilder< export function assertReturnOfBuilder<

View File

@@ -16,40 +16,33 @@ const allowedChannelTypes = [
export type ApplicationCommandOptionAllowedChannelTypes = typeof allowedChannelTypes[number]; export type ApplicationCommandOptionAllowedChannelTypes = typeof allowedChannelTypes[number];
const channelTypePredicate = z.union( const channelTypesPredicate = z.array(
allowedChannelTypes.map((type) => z.literal(type)) as [ z.union(
ZodLiteral<ChannelType>, allowedChannelTypes.map((type) => z.literal(type)) as [
ZodLiteral<ChannelType>, ZodLiteral<ChannelType>,
...ZodLiteral<ChannelType>[] ZodLiteral<ChannelType>,
], ...ZodLiteral<ChannelType>[]
],
),
); );
export class ApplicationCommandOptionChannelTypesMixin { export class ApplicationCommandOptionChannelTypesMixin {
public readonly channel_types?: ApplicationCommandOptionAllowedChannelTypes[]; public readonly channel_types?: ApplicationCommandOptionAllowedChannelTypes[];
/**
* Adds a channel type to this option
*
* @param channelType The type of channel to allow
*/
public addChannelType(channelType: ApplicationCommandOptionAllowedChannelTypes) {
if (this.channel_types === undefined) {
Reflect.set(this, 'channel_types', []);
}
channelTypePredicate.parse(channelType);
this.channel_types!.push(channelType);
return this;
}
/** /**
* Adds channel types to this option * Adds channel types to this option
* *
* @param channelTypes The channel types to add * @param channelTypes The channel types to add
*/ */
public addChannelTypes(channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) { public addChannelTypes(...channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) {
channelTypes.forEach((channelType) => this.addChannelType(channelType)); if (this.channel_types === undefined) {
Reflect.set(this, 'channel_types', []);
}
channelTypesPredicate.parse(channelTypes);
this.channel_types!.push(...channelTypes);
return this; return this;
} }
} }

View File

@@ -1,6 +1,6 @@
import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType } from 'discord-api-types/v9'; import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType } from 'discord-api-types/v9';
import { z } from 'zod'; import { z } from 'zod';
import { validateMaxChoicesLength } from '../Assertions'; import { validateChoicesLength } from '../Assertions';
const stringPredicate = z.string().min(1).max(100); const stringPredicate = z.string().min(1).max(100);
const numberPredicate = z.number().gt(-Infinity).lt(Infinity); const numberPredicate = z.number().gt(-Infinity).lt(Infinity);
@@ -16,51 +16,35 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
// Since this is present and this is a mixin, this is needed // Since this is present and this is a mixin, this is needed
public readonly type!: ApplicationCommandOptionType; public readonly type!: ApplicationCommandOptionType;
/**
* Adds a choice for this option
*
* @param choice The choice to add
*/
public addChoice(choice: APIApplicationCommandOptionChoice<T>): this {
const { name, value } = choice;
if (this.autocomplete) {
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
}
if (this.choices === undefined) {
Reflect.set(this, 'choices', []);
}
validateMaxChoicesLength(this.choices!);
// Validate name
stringPredicate.parse(name);
// Validate the value
if (this.type === ApplicationCommandOptionType.String) {
stringPredicate.parse(value);
} else {
numberPredicate.parse(value);
}
this.choices!.push({ name, value });
return this;
}
/** /**
* Adds multiple choices for this option * Adds multiple choices for this option
* *
* @param choices The choices to add * @param choices The choices to add
*/ */
public addChoices(...choices: APIApplicationCommandOptionChoice<T>[]): this { public addChoices(...choices: APIApplicationCommandOptionChoice<T>[]): this {
if (this.autocomplete) { if (choices.length > 0 && this.autocomplete) {
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.'); throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
} }
choicesPredicate.parse(choices); choicesPredicate.parse(choices);
for (const entry of choices) this.addChoice(entry); if (this.choices === undefined) {
Reflect.set(this, 'choices', []);
}
validateChoicesLength(choices.length, this.choices);
for (const { name, value } of choices) {
// Validate the value
if (this.type === ApplicationCommandOptionType.String) {
stringPredicate.parse(value);
} else {
numberPredicate.parse(value);
}
this.choices!.push({ name, value });
}
return this; return this;
} }
@@ -72,7 +56,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
choicesPredicate.parse(choices); choicesPredicate.parse(choices);
Reflect.set(this, 'choices', []); Reflect.set(this, 'choices', []);
for (const entry of choices) this.addChoice(entry); this.addChoices(...choices);
return this; return this;
} }

View File

@@ -85,13 +85,13 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
input: input:
| SlashCommandStringOption | SlashCommandStringOption
| Omit<SlashCommandStringOption, 'setAutocomplete'> | Omit<SlashCommandStringOption, 'setAutocomplete'>
| Omit<SlashCommandStringOption, 'addChoice' | 'addChoices'> | Omit<SlashCommandStringOption, 'addChoices'>
| (( | ((
builder: SlashCommandStringOption, builder: SlashCommandStringOption,
) => ) =>
| SlashCommandStringOption | SlashCommandStringOption
| Omit<SlashCommandStringOption, 'setAutocomplete'> | Omit<SlashCommandStringOption, 'setAutocomplete'>
| Omit<SlashCommandStringOption, 'addChoice' | 'addChoices'>), | Omit<SlashCommandStringOption, 'addChoices'>),
) { ) {
return this._sharedAddOptionMethod(input, SlashCommandStringOption); return this._sharedAddOptionMethod(input, SlashCommandStringOption);
} }
@@ -105,13 +105,13 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
input: input:
| SlashCommandIntegerOption | SlashCommandIntegerOption
| Omit<SlashCommandIntegerOption, 'setAutocomplete'> | Omit<SlashCommandIntegerOption, 'setAutocomplete'>
| Omit<SlashCommandIntegerOption, 'addChoice' | 'addChoices'> | Omit<SlashCommandIntegerOption, 'addChoices'>
| (( | ((
builder: SlashCommandIntegerOption, builder: SlashCommandIntegerOption,
) => ) =>
| SlashCommandIntegerOption | SlashCommandIntegerOption
| Omit<SlashCommandIntegerOption, 'setAutocomplete'> | Omit<SlashCommandIntegerOption, 'setAutocomplete'>
| Omit<SlashCommandIntegerOption, 'addChoice' | 'addChoices'>), | Omit<SlashCommandIntegerOption, 'addChoices'>),
) { ) {
return this._sharedAddOptionMethod(input, SlashCommandIntegerOption); return this._sharedAddOptionMethod(input, SlashCommandIntegerOption);
} }
@@ -125,13 +125,13 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
input: input:
| SlashCommandNumberOption | SlashCommandNumberOption
| Omit<SlashCommandNumberOption, 'setAutocomplete'> | Omit<SlashCommandNumberOption, 'setAutocomplete'>
| Omit<SlashCommandNumberOption, 'addChoice' | 'addChoices'> | Omit<SlashCommandNumberOption, 'addChoices'>
| (( | ((
builder: SlashCommandNumberOption, builder: SlashCommandNumberOption,
) => ) =>
| SlashCommandNumberOption | SlashCommandNumberOption
| Omit<SlashCommandNumberOption, 'setAutocomplete'> | Omit<SlashCommandNumberOption, 'setAutocomplete'>
| Omit<SlashCommandNumberOption, 'addChoice' | 'addChoices'>), | Omit<SlashCommandNumberOption, 'addChoices'>),
) { ) {
return this._sharedAddOptionMethod(input, SlashCommandNumberOption); return this._sharedAddOptionMethod(input, SlashCommandNumberOption);
} }
@@ -140,8 +140,8 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
input: input:
| T | T
| Omit<T, 'setAutocomplete'> | Omit<T, 'setAutocomplete'>
| Omit<T, 'addChoice' | 'addChoices'> | Omit<T, 'addChoices'>
| ((builder: T) => T | Omit<T, 'setAutocomplete'> | Omit<T, 'addChoice' | 'addChoices'>), | ((builder: T) => T | Omit<T, 'setAutocomplete'> | Omit<T, 'addChoices'>),
Instance: new () => T, Instance: new () => T,
): ShouldOmitSubcommandFunctions extends true ? Omit<this, 'addSubcommand' | 'addSubcommandGroup'> : this { ): ShouldOmitSubcommandFunctions extends true ? Omit<this, 'addSubcommand' | 'addSubcommandGroup'> : this {
const { options } = this; const { options } = this;