mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-19 13:03:31 +01:00
fix: Validate select menu options (#7566)
* fix: validate select menu options * chore: make requested changes * refactor: make requested changes * fix: tests
This commit is contained in:
@@ -35,8 +35,8 @@ describe('Select Menu Components', () => {
|
|||||||
expect(() => selectMenu().setMaxValues(10)).not.toThrowError();
|
expect(() => selectMenu().setMaxValues(10)).not.toThrowError();
|
||||||
expect(() => selectMenu().setMinValues(3)).not.toThrowError();
|
expect(() => selectMenu().setMinValues(3)).not.toThrowError();
|
||||||
expect(() => selectMenu().setDisabled(true)).not.toThrowError();
|
expect(() => selectMenu().setDisabled(true)).not.toThrowError();
|
||||||
|
expect(() => selectMenu().setDisabled()).not.toThrowError();
|
||||||
expect(() => selectMenu().setPlaceholder('description')).not.toThrowError();
|
expect(() => selectMenu().setPlaceholder('description')).not.toThrowError();
|
||||||
|
|
||||||
const option = selectMenuOption()
|
const option = selectMenuOption()
|
||||||
.setLabel('test')
|
.setLabel('test')
|
||||||
.setValue('test')
|
.setValue('test')
|
||||||
@@ -46,6 +46,27 @@ describe('Select Menu Components', () => {
|
|||||||
expect(() => selectMenu().addOptions(option)).not.toThrowError();
|
expect(() => selectMenu().addOptions(option)).not.toThrowError();
|
||||||
expect(() => selectMenu().setOptions(option)).not.toThrowError();
|
expect(() => selectMenu().setOptions(option)).not.toThrowError();
|
||||||
expect(() => selectMenu().setOptions({ label: 'test', value: 'test' })).not.toThrowError();
|
expect(() => selectMenu().setOptions({ label: 'test', value: 'test' })).not.toThrowError();
|
||||||
|
expect(() =>
|
||||||
|
selectMenu().addOptions({
|
||||||
|
label: 'test',
|
||||||
|
value: 'test',
|
||||||
|
emoji: {
|
||||||
|
id: '123',
|
||||||
|
name: 'test',
|
||||||
|
animated: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).not.toThrowError();
|
||||||
|
|
||||||
|
const options = new Array<APISelectMenuOption>(25).fill({ label: 'test', value: 'test' });
|
||||||
|
expect(() => selectMenu().addOptions(...options)).not.toThrowError();
|
||||||
|
expect(() => selectMenu().setOptions(...options)).not.toThrowError();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
selectMenu()
|
||||||
|
.addOptions({ label: 'test', value: 'test' })
|
||||||
|
.addOptions(...new Array<APISelectMenuOption>(24).fill({ label: 'test', value: 'test' })),
|
||||||
|
).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GIVEN invalid inputs THEN Select Menu does throw', () => {
|
test('GIVEN invalid inputs THEN Select Menu does throw', () => {
|
||||||
@@ -55,6 +76,26 @@ describe('Select Menu Components', () => {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
expect(() => selectMenu().setDisabled(0)).toThrowError();
|
expect(() => selectMenu().setDisabled(0)).toThrowError();
|
||||||
expect(() => selectMenu().setPlaceholder(longStr)).toThrowError();
|
expect(() => selectMenu().setPlaceholder(longStr)).toThrowError();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => selectMenu().addOptions({ label: 'test' })).toThrowError();
|
||||||
|
expect(() => selectMenu().addOptions({ label: longStr, value: 'test' })).toThrowError();
|
||||||
|
expect(() => selectMenu().addOptions({ value: longStr, label: 'test' })).toThrowError();
|
||||||
|
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', description: longStr })).toThrowError();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', default: 100 })).toThrowError();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => selectMenu().addOptions({ value: 'test' })).toThrowError();
|
||||||
|
// @ts-expect-error
|
||||||
|
expect(() => selectMenu().addOptions({ default: true })).toThrowError();
|
||||||
|
|
||||||
|
const tooManyOptions = new Array<APISelectMenuOption>(26).fill({ label: 'test', value: 'test' });
|
||||||
|
expect(() => selectMenu().setOptions(...tooManyOptions)).toThrowError();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
selectMenu()
|
||||||
|
.addOptions({ label: 'test', value: 'test' })
|
||||||
|
.addOptions(...tooManyOptions),
|
||||||
|
).toThrowError();
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
selectMenuOption()
|
selectMenuOption()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { APIMessageComponentEmoji, ButtonStyle } from 'discord-api-types/v10';
|
import { APIMessageComponentEmoji, ButtonStyle } from 'discord-api-types/v10';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
|
import type { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
|
||||||
|
import { UnsafeSelectMenuOptionBuilder } from './selectMenu/UnsafeSelectMenuOption';
|
||||||
|
|
||||||
export const customIdValidator = z.string().min(1).max(100);
|
export const customIdValidator = z.string().min(1).max(100);
|
||||||
|
|
||||||
@@ -22,7 +23,19 @@ export const buttonStyleValidator = z.number().int().min(ButtonStyle.Primary).ma
|
|||||||
export const placeholderValidator = z.string().max(150);
|
export const placeholderValidator = z.string().max(150);
|
||||||
export const minMaxValidator = z.number().int().min(0).max(25);
|
export const minMaxValidator = z.number().int().min(0).max(25);
|
||||||
|
|
||||||
export const optionsValidator = z.object({}).array().nonempty();
|
export const labelValueDescriptionValidator = z.string().min(1).max(100);
|
||||||
|
export const optionValidator = z.union([
|
||||||
|
z.object({
|
||||||
|
label: labelValueDescriptionValidator,
|
||||||
|
value: labelValueDescriptionValidator,
|
||||||
|
description: labelValueDescriptionValidator.optional(),
|
||||||
|
emoji: emojiValidator.optional(),
|
||||||
|
default: z.boolean().optional(),
|
||||||
|
}),
|
||||||
|
z.instanceof(UnsafeSelectMenuOptionBuilder),
|
||||||
|
]);
|
||||||
|
export const optionsValidator = optionValidator.array().nonempty();
|
||||||
|
export const optionsLengthValidator = z.number().int().min(0).max(25);
|
||||||
|
|
||||||
export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) {
|
export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) {
|
||||||
customIdValidator.parse(customId);
|
customIdValidator.parse(customId);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import type { APISelectMenuComponent } from 'discord-api-types/v10';
|
import type { APISelectMenuComponent, APISelectMenuOption } from 'discord-api-types/v10';
|
||||||
import {
|
import {
|
||||||
customIdValidator,
|
customIdValidator,
|
||||||
disabledValidator,
|
disabledValidator,
|
||||||
minMaxValidator,
|
minMaxValidator,
|
||||||
|
optionsLengthValidator,
|
||||||
|
optionValidator,
|
||||||
placeholderValidator,
|
placeholderValidator,
|
||||||
validateRequiredSelectMenuParameters,
|
validateRequiredSelectMenuParameters,
|
||||||
} from '../Assertions';
|
} from '../Assertions';
|
||||||
import { UnsafeSelectMenuBuilder } from './UnsafeSelectMenu';
|
import { UnsafeSelectMenuBuilder } from './UnsafeSelectMenu';
|
||||||
|
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a validated select menu component
|
* Represents a validated select menu component
|
||||||
@@ -32,6 +35,32 @@ export class SelectMenuBuilder extends UnsafeSelectMenuBuilder {
|
|||||||
return super.setDisabled(disabledValidator.parse(disabled));
|
return super.setDisabled(disabledValidator.parse(disabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override addOptions(...options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
|
||||||
|
optionsLengthValidator.parse(this.options.length + options.length);
|
||||||
|
this.options.push(
|
||||||
|
...options.map((option) =>
|
||||||
|
option instanceof UnsafeSelectMenuOptionBuilder
|
||||||
|
? option
|
||||||
|
: new UnsafeSelectMenuOptionBuilder(optionValidator.parse(option) as APISelectMenuOption),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override setOptions(...options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
|
||||||
|
optionsLengthValidator.parse(options.length);
|
||||||
|
this.options.splice(
|
||||||
|
0,
|
||||||
|
this.options.length,
|
||||||
|
...options.map((option) =>
|
||||||
|
option instanceof UnsafeSelectMenuOptionBuilder
|
||||||
|
? option
|
||||||
|
: new UnsafeSelectMenuOptionBuilder(optionValidator.parse(option) as APISelectMenuOption),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public override toJSON(): APISelectMenuComponent {
|
public override toJSON(): APISelectMenuComponent {
|
||||||
validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
|
validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
|
||||||
return super.toJSON();
|
return super.toJSON();
|
||||||
|
|||||||
Reference in New Issue
Block a user