diff --git a/packages/builders/__tests__/components/selectMenu.test.ts b/packages/builders/__tests__/components/selectMenu.test.ts index 5ea2fdae4..d087409a6 100644 --- a/packages/builders/__tests__/components/selectMenu.test.ts +++ b/packages/builders/__tests__/components/selectMenu.test.ts @@ -35,8 +35,8 @@ describe('Select Menu Components', () => { expect(() => selectMenu().setMaxValues(10)).not.toThrowError(); expect(() => selectMenu().setMinValues(3)).not.toThrowError(); expect(() => selectMenu().setDisabled(true)).not.toThrowError(); + expect(() => selectMenu().setDisabled()).not.toThrowError(); expect(() => selectMenu().setPlaceholder('description')).not.toThrowError(); - const option = selectMenuOption() .setLabel('test') .setValue('test') @@ -46,6 +46,27 @@ describe('Select Menu Components', () => { expect(() => selectMenu().addOptions(option)).not.toThrowError(); expect(() => selectMenu().setOptions(option)).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(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(24).fill({ label: 'test', value: 'test' })), + ).not.toThrowError(); }); test('GIVEN invalid inputs THEN Select Menu does throw', () => { @@ -55,6 +76,26 @@ describe('Select Menu Components', () => { // @ts-expect-error expect(() => selectMenu().setDisabled(0)).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(26).fill({ label: 'test', value: 'test' }); + expect(() => selectMenu().setOptions(...tooManyOptions)).toThrowError(); + + expect(() => + selectMenu() + .addOptions({ label: 'test', value: 'test' }) + .addOptions(...tooManyOptions), + ).toThrowError(); expect(() => { selectMenuOption() diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index 79e9ea632..4e1fd7b8e 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -1,6 +1,7 @@ import { APIMessageComponentEmoji, ButtonStyle } from 'discord-api-types/v10'; import { z } from 'zod'; import type { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption'; +import { UnsafeSelectMenuOptionBuilder } from './selectMenu/UnsafeSelectMenuOption'; 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 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) { customIdValidator.parse(customId); diff --git a/packages/builders/src/components/selectMenu/SelectMenu.ts b/packages/builders/src/components/selectMenu/SelectMenu.ts index 3f10f7dd0..ed64c2240 100644 --- a/packages/builders/src/components/selectMenu/SelectMenu.ts +++ b/packages/builders/src/components/selectMenu/SelectMenu.ts @@ -1,12 +1,15 @@ -import type { APISelectMenuComponent } from 'discord-api-types/v10'; +import type { APISelectMenuComponent, APISelectMenuOption } from 'discord-api-types/v10'; import { customIdValidator, disabledValidator, minMaxValidator, + optionsLengthValidator, + optionValidator, placeholderValidator, validateRequiredSelectMenuParameters, } from '../Assertions'; import { UnsafeSelectMenuBuilder } from './UnsafeSelectMenu'; +import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption'; /** * Represents a validated select menu component @@ -32,6 +35,32 @@ export class SelectMenuBuilder extends UnsafeSelectMenuBuilder { 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 { validateRequiredSelectMenuParameters(this.options, this.data.custom_id); return super.toJSON();