feat(builders)!: Support select in modals (#11034)

BREAKING CHANGE: Text inputs no longer accept a label.
BREAKING CHANGE: Modals now only set labels instead of action rows.
This commit is contained in:
Jiralite
2025-09-05 20:56:14 +04:00
committed by GitHub
parent ddf9f818e8
commit f7c77a73de
13 changed files with 364 additions and 115 deletions

View File

@@ -0,0 +1,109 @@
import type { APILabelComponent, APIStringSelectComponent, APITextInputComponent } from 'discord-api-types/v10';
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { LabelBuilder } from '../../src/index.js';
describe('Label components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid fields THEN builder does not throw', () => {
expect(() =>
new LabelBuilder()
.setLabel('label')
.setStringSelectMenuComponent((stringSelectMenu) =>
stringSelectMenu
.setCustomId('test')
.setOptions((stringSelectMenuOption) => stringSelectMenuOption.setLabel('label').setValue('value'))
.setRequired(),
)
.toJSON(),
).not.toThrow();
expect(() =>
new LabelBuilder()
.setLabel('label')
.setId(5)
.setTextInputComponent((textInput) =>
textInput.setCustomId('test').setStyle(TextInputStyle.Paragraph).setRequired(),
)
.toJSON(),
).not.toThrow();
});
test('GIVEN invalid fields THEN build does throw', () => {
expect(() => new LabelBuilder().toJSON()).toThrow();
expect(() => new LabelBuilder().setId(5).toJSON()).toThrow();
expect(() => new LabelBuilder().setLabel('label').toJSON()).toThrow();
expect(() =>
new LabelBuilder()
.setLabel('l'.repeat(1_000))
.setStringSelectMenuComponent((stringSelectMenu) => stringSelectMenu)
.toJSON(),
).toThrow();
});
test('GIVEN valid input THEN valid JSON outputs are given', () => {
const labelWithTextInputData = {
type: ComponentType.Label,
component: {
type: ComponentType.TextInput,
custom_id: 'custom_id',
placeholder: 'placeholder',
style: TextInputStyle.Paragraph,
} satisfies APITextInputComponent,
label: 'label',
description: 'description',
id: 5,
} satisfies APILabelComponent;
const labelWithStringSelectData = {
type: ComponentType.Label,
component: {
type: ComponentType.StringSelect,
custom_id: 'custom_id',
placeholder: 'placeholder',
options: [
{ label: 'first', value: 'first' },
{ label: 'second', value: 'second' },
],
required: true,
} satisfies APIStringSelectComponent,
label: 'label',
description: 'description',
id: 5,
} satisfies APILabelComponent;
expect(new LabelBuilder(labelWithTextInputData).toJSON()).toEqual(labelWithTextInputData);
expect(new LabelBuilder(labelWithStringSelectData).toJSON()).toEqual(labelWithStringSelectData);
expect(
new LabelBuilder()
.setTextInputComponent((textInput) =>
textInput.setCustomId('custom_id').setPlaceholder('placeholder').setStyle(TextInputStyle.Paragraph),
)
.setLabel('label')
.setDescription('description')
.setId(5)
.toJSON(),
).toEqual(labelWithTextInputData);
expect(
new LabelBuilder()
.setStringSelectMenuComponent((stringSelectMenu) =>
stringSelectMenu
.setCustomId('custom_id')
.setPlaceholder('placeholder')
.setOptions(
(stringSelectMenuOption) => stringSelectMenuOption.setLabel('first').setValue('first'),
(stringSelectMenuOption) => stringSelectMenuOption.setLabel('second').setValue('second'),
)
.setRequired(),
)
.setLabel('label')
.setDescription('description')
.setId(5)
.toJSON(),
).toEqual(labelWithStringSelectData);
});
});
});

View File

@@ -8,13 +8,12 @@ describe('Text Input Components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid fields THEN builder does not throw', () => {
expect(() => {
textInputComponent().setCustomId('foobar').setLabel('test').setStyle(TextInputStyle.Paragraph).toJSON();
textInputComponent().setCustomId('foobar').setStyle(TextInputStyle.Paragraph).toJSON();
}).not.toThrowError();
expect(() => {
textInputComponent()
.setCustomId('foobar')
.setLabel('test')
.setMaxLength(100)
.setMinLength(1)
.setPlaceholder('bar')
@@ -24,7 +23,7 @@ describe('Text Input Components', () => {
}).not.toThrowError();
expect(() => {
textInputComponent().setCustomId('Custom').setLabel('Guess').setStyle(TextInputStyle.Short).toJSON();
textInputComponent().setCustomId('Custom').setStyle(TextInputStyle.Short).toJSON();
}).not.toThrowError();
});
});
@@ -33,10 +32,10 @@ describe('Text Input Components', () => {
expect(() => textInputComponent().toJSON()).toThrowError();
expect(() => {
textInputComponent()
.setCustomId('test')
.setCustomId('a'.repeat(500))
.setMaxLength(100)
.setPlaceholder('hello')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('a'.repeat(500))
.setStyle(3 as TextInputStyle)
.toJSON();
}).toThrowError();
});
@@ -44,7 +43,6 @@ describe('Text Input Components', () => {
test('GIVEN valid input THEN valid JSON outputs are given', () => {
const textInputData = {
type: ComponentType.TextInput,
label: 'label',
custom_id: 'custom id',
placeholder: 'placeholder',
max_length: 100,
@@ -58,11 +56,10 @@ describe('Text Input Components', () => {
expect(
textInputComponent()
.setCustomId(textInputData.custom_id)
.setLabel(textInputData.label)
.setPlaceholder(textInputData.placeholder!)
.setMaxLength(textInputData.max_length!)
.setMinLength(textInputData.min_length!)
.setValue(textInputData.value!)
.setPlaceholder(textInputData.placeholder)
.setMaxLength(textInputData.max_length)
.setMinLength(textInputData.min_length)
.setValue(textInputData.value)
.setRequired(textInputData.required)
.setStyle(textInputData.style)
.toJSON(),