From 0d76f1149f42b439f716e4aa69919e60548405b5 Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Fri, 26 Sep 2025 17:06:22 +0100 Subject: [PATCH] fix: Add `idPredicate` (#11109) * fix: `idPredicate` * fix: add test * test: add negative test --- .../builders/__tests__/components/v2/separator.test.ts | 7 +++++++ .../builders/__tests__/components/v2/textDisplay.test.ts | 5 +++++ packages/builders/src/Assertions.ts | 1 + packages/builders/src/components/Assertions.ts | 4 +++- packages/builders/src/components/label/Assertions.ts | 2 ++ packages/builders/src/components/textInput/Assertions.ts | 3 ++- packages/builders/src/components/v2/Assertions.ts | 9 +++++++++ 7 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/builders/__tests__/components/v2/separator.test.ts b/packages/builders/__tests__/components/v2/separator.test.ts index 737435484..802f81b7b 100644 --- a/packages/builders/__tests__/components/v2/separator.test.ts +++ b/packages/builders/__tests__/components/v2/separator.test.ts @@ -32,4 +32,11 @@ describe('Separator', () => { expect(separator.toJSON()).toEqual({ type: ComponentType.Separator }); }); }); + + describe('Invalid id', () => { + test('GIVEN a separator with a set spacing and an invalid set id THEN throws error', () => { + const separator = new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Large).setId(-1); + expect(() => separator.toJSON()).toThrowError(); + }); + }); }); diff --git a/packages/builders/__tests__/components/v2/textDisplay.test.ts b/packages/builders/__tests__/components/v2/textDisplay.test.ts index 01495c8af..608279e22 100644 --- a/packages/builders/__tests__/components/v2/textDisplay.test.ts +++ b/packages/builders/__tests__/components/v2/textDisplay.test.ts @@ -14,6 +14,11 @@ describe('TextDisplay', () => { expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' }); }); + test('GIVEN a text display with a set content with an invalid id THEN throws error', () => { + const textDisplay = new TextDisplayBuilder().setContent('foo').setId(5.5); + expect(() => textDisplay.toJSON()).toThrowError(); + }); + test('GIVEN a text display with a pre-defined content THEN overwritten content THEN return valid toJSON data', () => { const textDisplay = new TextDisplayBuilder({ content: 'foo' }); textDisplay.setContent('bar'); diff --git a/packages/builders/src/Assertions.ts b/packages/builders/src/Assertions.ts index b4c8c74b1..c672c95c0 100644 --- a/packages/builders/src/Assertions.ts +++ b/packages/builders/src/Assertions.ts @@ -1,6 +1,7 @@ import { Locale } from 'discord-api-types/v10'; import { z } from 'zod'; +export const idPredicate = z.int().min(0).max(2_147_483_647).optional(); export const customIdPredicate = z.string().min(1).max(100); export const memberPermissionsPredicate = z.coerce.bigint(); diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index f1da1607f..427367754 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -1,6 +1,6 @@ import { ButtonStyle, ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10'; import { z } from 'zod'; -import { customIdPredicate } from '../Assertions.js'; +import { idPredicate, customIdPredicate } from '../Assertions.js'; const labelPredicate = z.string().min(1).max(80); @@ -52,6 +52,7 @@ export const buttonPredicate = z.discriminatedUnion('style', [ ]); const selectMenuBasePredicate = z.object({ + id: idPredicate, placeholder: z.string().max(150).optional(), min_values: z.number().min(0).max(25).optional(), max_values: z.number().min(0).max(25).optional(), @@ -135,6 +136,7 @@ export const selectMenuUserPredicate = selectMenuBasePredicate.extend({ }); export const actionRowPredicate = z.object({ + id: idPredicate, type: z.literal(ComponentType.ActionRow), components: z.union([ z diff --git a/packages/builders/src/components/label/Assertions.ts b/packages/builders/src/components/label/Assertions.ts index 0c62da1bf..e9a58af1c 100644 --- a/packages/builders/src/components/label/Assertions.ts +++ b/packages/builders/src/components/label/Assertions.ts @@ -1,5 +1,6 @@ import { ComponentType } from 'discord-api-types/v10'; import { z } from 'zod'; +import { idPredicate } from '../../Assertions'; import { selectMenuChannelPredicate, selectMenuMentionablePredicate, @@ -10,6 +11,7 @@ import { import { textInputPredicate } from '../textInput/Assertions'; export const labelPredicate = z.object({ + id: idPredicate, type: z.literal(ComponentType.Label), label: z.string().min(1).max(45), description: z.string().min(1).max(100).optional(), diff --git a/packages/builders/src/components/textInput/Assertions.ts b/packages/builders/src/components/textInput/Assertions.ts index dd60bf16f..041ecab71 100644 --- a/packages/builders/src/components/textInput/Assertions.ts +++ b/packages/builders/src/components/textInput/Assertions.ts @@ -1,8 +1,9 @@ import { ComponentType, TextInputStyle } from 'discord-api-types/v10'; import { z } from 'zod'; -import { customIdPredicate } from '../../Assertions.js'; +import { customIdPredicate, idPredicate } from '../../Assertions.js'; export const textInputPredicate = z.object({ + id: idPredicate, type: z.literal(ComponentType.TextInput), custom_id: customIdPredicate, style: z.enum(TextInputStyle), diff --git a/packages/builders/src/components/v2/Assertions.ts b/packages/builders/src/components/v2/Assertions.ts index a9b6c0abf..d60e17d41 100644 --- a/packages/builders/src/components/v2/Assertions.ts +++ b/packages/builders/src/components/v2/Assertions.ts @@ -1,5 +1,6 @@ import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10'; import { z } from 'zod'; +import { idPredicate } from '../../Assertions.js'; import { actionRowPredicate } from '../Assertions.js'; const unfurledMediaItemPredicate = z.object({ @@ -8,6 +9,7 @@ const unfurledMediaItemPredicate = z.object({ export const thumbnailPredicate = z.object({ type: z.literal(ComponentType.Thumbnail), + id: idPredicate, media: unfurledMediaItemPredicate, description: z.string().min(1).max(1_024).nullish(), spoiler: z.boolean().optional(), @@ -19,22 +21,26 @@ const unfurledMediaItemAttachmentOnlyPredicate = z.object({ export const filePredicate = z.object({ type: z.literal(ComponentType.File), + id: idPredicate, file: unfurledMediaItemAttachmentOnlyPredicate, spoiler: z.boolean().optional(), }); export const separatorPredicate = z.object({ type: z.literal(ComponentType.Separator), + id: idPredicate, divider: z.boolean().optional(), spacing: z.enum(SeparatorSpacingSize).optional(), }); export const textDisplayPredicate = z.object({ type: z.literal(ComponentType.TextDisplay), + id: idPredicate, content: z.string().min(1).max(4_000), }); export const mediaGalleryItemPredicate = z.object({ + id: idPredicate, media: unfurledMediaItemPredicate, description: z.string().min(1).max(1_024).nullish(), spoiler: z.boolean().optional(), @@ -42,11 +48,13 @@ export const mediaGalleryItemPredicate = z.object({ export const mediaGalleryPredicate = z.object({ type: z.literal(ComponentType.MediaGallery), + id: idPredicate, items: z.array(mediaGalleryItemPredicate).min(1).max(10), }); export const sectionPredicate = z.object({ type: z.literal(ComponentType.Section), + id: idPredicate, components: z.array(textDisplayPredicate).min(1).max(3), accessory: z.union([ z.object({ type: z.literal(ComponentType.Button) }), @@ -56,6 +64,7 @@ export const sectionPredicate = z.object({ export const containerPredicate = z.object({ type: z.literal(ComponentType.Container), + id: idPredicate, components: z .array( z.union([