Skip to content

Commit

Permalink
refactor: remove obsolete builder methods (#7590)
Browse files Browse the repository at this point in the history
  • Loading branch information
almeidx committed Mar 6, 2022
1 parent 79d6c04 commit 10607db
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 109 deletions.
Expand Up @@ -29,7 +29,7 @@ const getChannelOption = () =>
.setName('owo')
.setDescription('Testing 123')
.setRequired(true)
.addChannelType(ChannelType.GuildText);
.addChannelTypes(ChannelType.GuildText);

const getStringOption = () =>
new SlashCommandStringOption().setName('owo').setDescription('Testing 123').setRequired(true);
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('Application Command toJSON() results', () => {
});

expect(
getIntegerOption().addChoice({ name: 'uwu', value: 1 }).toJSON(),
getIntegerOption().addChoices({ name: 'uwu', value: 1 }).toJSON(),
).toEqual<APIApplicationCommandIntegerOption>({
name: 'owo',
description: 'Testing 123',
Expand Down Expand Up @@ -143,15 +143,17 @@ describe('Application Command toJSON() results', () => {
choices: [],
});

expect(getNumberOption().addChoice({ name: 'uwu', value: 1 }).toJSON()).toEqual<APIApplicationCommandNumberOption>({
name: 'owo',
description: 'Testing 123',
type: ApplicationCommandOptionType.Number,
required: true,
max_value: 10,
min_value: -1.23,
choices: [{ name: 'uwu', value: 1 }],
});
expect(getNumberOption().addChoices({ name: 'uwu', value: 1 }).toJSON()).toEqual<APIApplicationCommandNumberOption>(
{
name: 'owo',
description: 'Testing 123',
type: ApplicationCommandOptionType.Number,
required: true,
max_value: 10,
min_value: -1.23,
choices: [{ name: 'uwu', value: 1 }],
},
);
});

test('GIVEN a role option THEN calling toJSON should return a valid JSON', () => {
Expand Down Expand Up @@ -182,7 +184,7 @@ describe('Application Command toJSON() results', () => {
});

expect(
getStringOption().addChoice({ name: 'uwu', value: '1' }).toJSON(),
getStringOption().addChoices({ name: 'uwu', value: '1' }).toJSON(),
).toEqual<APIApplicationCommandStringOption>({
name: 'owo',
description: 'Testing 123',
Expand Down
Expand Up @@ -87,18 +87,16 @@ describe('Slash Commands', () => {
test('GIVEN valid array of options or choices THEN does not throw error', () => {
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', () => {
expect(() => SlashCommandAssertions.validateMaxOptionsLength(null)).toThrowError();

expect(() => SlashCommandAssertions.validateMaxChoicesLength(null)).toThrowError();

// Given an array that's too big
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', () => {
Expand Down Expand Up @@ -179,31 +177,25 @@ describe('Slash Commands', () => {
test('GIVEN a builder with both choices and autocomplete THEN does throw an error', () => {
expect(() =>
getBuilder().addStringOption(
// @ts-expect-error Checking if check works JS-side too
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
getStringOption().setAutocomplete(true).addChoice('Fancy Pants', 'fp_1'),
getStringOption().setAutocomplete(true).addChoices({ name: 'Fancy Pants', value: 'fp_1' }),
),
).toThrowError();

expect(() =>
getBuilder().addStringOption(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
getStringOption()
.setAutocomplete(true)
// @ts-expect-error Checking if check works JS-side too
.addChoices([
['Fancy Pants', 'fp_1'],
['Fancy Shoes', 'fs_1'],
['The Whole shebang', 'all'],
]),
.addChoices(
{ name: 'Fancy Pants', value: 'fp_1' },
{ name: 'Fancy Shoes', value: 'fs_1' },
{ name: 'The Whole shebang', value: 'all' },
),
),
).toThrowError();

expect(() =>
getBuilder().addStringOption(
// @ts-expect-error Checking if check works JS-side too
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
getStringOption().addChoice('Fancy Pants', 'fp_1').setAutocomplete(true),
getStringOption().addChoices({ name: 'Fancy Pants', value: 'fp_1' }).setAutocomplete(true),
),
).toThrowError();

Expand Down Expand Up @@ -231,20 +223,20 @@ describe('Slash Commands', () => {

test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => {
expect(() =>
getBuilder().addChannelOption(getChannelOption().addChannelType(ChannelType.GuildText)),
getBuilder().addChannelOption(getChannelOption().addChannelTypes(ChannelType.GuildText)),
).not.toThrowError();

expect(() => {
getBuilder().addChannelOption(
getChannelOption().addChannelTypes([ChannelType.GuildNews, ChannelType.GuildText]),
getChannelOption().addChannelTypes(ChannelType.GuildNews, ChannelType.GuildText),
);
}).not.toThrowError();
});

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', () => {
Expand Down
20 changes: 7 additions & 13 deletions packages/builders/docs/examples/Slash Command Builders.md
Expand Up @@ -33,13 +33,7 @@ const boopCommand = new SlashCommandBuilder()
option
.setName('boop_reminder')
.setDescription('How often should we remind you to boop the user')
.addChoice('Every day', 1)
.addChoice('Weekly', 7)
// Or, if you prefer adding more choices at once, you can use an array
.addChoices([
['Every three months', 90],
['Yearly', 365],
]),
.addChoices({ name: 'Every day', value: 1 }, { name: 'Weekly', value: 7 }),
);

// Get the final raw data that can be sent to Discord
Expand Down Expand Up @@ -71,11 +65,11 @@ const pointsCommand = new SlashCommandBuilder()
option
.setName('action')
.setDescription('What action should be taken with the users points?')
.addChoices([
['Add points', 'add'],
['Remove points', 'remove'],
['Reset points', 'reset'],
])
.addChoices(
{ name: 'Add points', value: 'add' },
{ name: 'Remove points', value: 'remove' },
{ name: 'Reset points', value: 'reset' },
)
.setRequired(true),
)
.addIntegerOption((option) => option.setName('points').setDescription('Points to add or remove')),
Expand All @@ -102,4 +96,4 @@ const pointsCommand = new SlashCommandBuilder()

// Get the final raw data that can be sent to Discord
const rawData = pointsCommand.toJSON();
```
```
Expand Up @@ -52,8 +52,10 @@ export function validateRequired(required: unknown): asserts required is boolean
booleanPredicate.parse(required);
}

export function validateMaxChoicesLength(choices: APIApplicationCommandOptionChoice[]) {
maxArrayLengthPredicate.parse(choices);
const choicesLengthPredicate = z.number().lte(25);

export function validateChoicesLength(amountAdding: number, choices?: APIApplicationCommandOptionChoice[]): void {
choicesLengthPredicate.parse((choices?.length ?? 0) + amountAdding);
}

export function assertReturnOfBuilder<
Expand Down
Expand Up @@ -16,40 +16,33 @@ const allowedChannelTypes = [

export type ApplicationCommandOptionAllowedChannelTypes = typeof allowedChannelTypes[number];

const channelTypePredicate = z.union(
allowedChannelTypes.map((type) => z.literal(type)) as [
ZodLiteral<ChannelType>,
ZodLiteral<ChannelType>,
...ZodLiteral<ChannelType>[]
],
const channelTypesPredicate = z.array(
z.union(
allowedChannelTypes.map((type) => z.literal(type)) as [
ZodLiteral<ChannelType>,
ZodLiteral<ChannelType>,
...ZodLiteral<ChannelType>[]
],
),
);

export class ApplicationCommandOptionChannelTypesMixin {
public readonly channel_types?: ApplicationCommandOptionAllowedChannelTypes[];

/**
* Adds a channel type to this option
* Adds channel types to this option
*
* @param channelType The type of channel to allow
* @param channelTypes The channel types to add
*/
public addChannelType(channelType: ApplicationCommandOptionAllowedChannelTypes) {
public addChannelTypes(...channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) {
if (this.channel_types === undefined) {
Reflect.set(this, 'channel_types', []);
}

channelTypePredicate.parse(channelType);
this.channel_types!.push(channelType);
channelTypesPredicate.parse(channelTypes);

return this;
}
this.channel_types!.push(...channelTypes);

/**
* Adds channel types to this option
*
* @param channelTypes The channel types to add
*/
public addChannelTypes(channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) {
channelTypes.forEach((channelType) => this.addChannelType(channelType));
return this;
}
}
@@ -1,6 +1,6 @@
import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType } from 'discord-api-types/v9';
import { z } from 'zod';
import { validateMaxChoicesLength } from '../Assertions';
import { validateChoicesLength } from '../Assertions';

const stringPredicate = z.string().min(1).max(100);
const numberPredicate = z.number().gt(-Infinity).lt(Infinity);
Expand All @@ -17,50 +17,34 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
public readonly type!: ApplicationCommandOptionType;

/**
* Adds a choice for this option
* Adds multiple choices for this option
*
* @param choice The choice to add
* @param choices The choices to add
*/
public addChoice(choice: APIApplicationCommandOptionChoice<T>): this {
const { name, value } = choice;
if (this.autocomplete) {
public addChoices(...choices: APIApplicationCommandOptionChoice<T>[]): this {
if (choices.length > 0 && this.autocomplete) {
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
}

choicesPredicate.parse(choices);

if (this.choices === undefined) {
Reflect.set(this, 'choices', []);
}

validateMaxChoicesLength(this.choices!);
validateChoicesLength(choices.length, this.choices);

// Validate name
stringPredicate.parse(name);
for (const { name, value } of choices) {
// Validate the value
if (this.type === ApplicationCommandOptionType.String) {
stringPredicate.parse(value);
} else {
numberPredicate.parse(value);
}

// Validate the value
if (this.type === ApplicationCommandOptionType.String) {
stringPredicate.parse(value);
} else {
numberPredicate.parse(value);
this.choices!.push({ name, value });
}

this.choices!.push({ name, value });

return this;
}

/**
* Adds multiple choices for this option
*
* @param choices The choices to add
*/
public addChoices(...choices: APIApplicationCommandOptionChoice<T>[]): this {
if (this.autocomplete) {
throw new RangeError('Autocomplete and choices are mutually exclusive to each other.');
}

choicesPredicate.parse(choices);

for (const entry of choices) this.addChoice(entry);
return this;
}

Expand All @@ -72,7 +56,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
choicesPredicate.parse(choices);

Reflect.set(this, 'choices', []);
for (const entry of choices) this.addChoice(entry);
this.addChoices(...choices);

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

0 comments on commit 10607db

Please sign in to comment.