diff --git a/packages/builders/__tests__/components/components.test.ts b/packages/builders/__tests__/components/components.test.ts new file mode 100644 index 000000000..baa21ea66 --- /dev/null +++ b/packages/builders/__tests__/components/components.test.ts @@ -0,0 +1,73 @@ +import { + APIActionRowComponent, + APIButtonComponent, + APIMessageActionRowComponent, + APISelectMenuComponent, + APITextInputComponent, + ButtonStyle, + ComponentType, + TextInputStyle, +} from 'discord-api-types/v10'; +import { describe, test, expect } from 'vitest'; +import { + ActionRowBuilder, + ButtonBuilder, + createComponentBuilder, + SelectMenuBuilder, + TextInputBuilder, +} from '../../src/index'; + +describe('createComponentBuilder', () => { + test.each([ButtonBuilder, SelectMenuBuilder, TextInputBuilder])( + 'passing an instance of %j should return itself', + (Builder) => { + const builder = new Builder(); + expect(createComponentBuilder(builder)).toBe(builder); + }, + ); + + test('GIVEN an action row component THEN returns a ActionRowBuilder', () => { + const actionRow: APIActionRowComponent = { + components: [], + type: ComponentType.ActionRow, + }; + + expect(createComponentBuilder(actionRow)).toBeInstanceOf(ActionRowBuilder); + }); + + test('GIVEN a button component THEN returns a ButtonBuilder', () => { + const button: APIButtonComponent = { + custom_id: 'abc', + style: ButtonStyle.Primary, + type: ComponentType.Button, + }; + + expect(createComponentBuilder(button)).toBeInstanceOf(ButtonBuilder); + }); + + test('GIVEN a select menu component THEN returns a SelectMenuBuilder', () => { + const selectMenu: APISelectMenuComponent = { + custom_id: 'abc', + options: [], + type: ComponentType.SelectMenu, + }; + + expect(createComponentBuilder(selectMenu)).toBeInstanceOf(SelectMenuBuilder); + }); + + test('GIVEN a text input component THEN returns a TextInputBuilder', () => { + const textInput: APITextInputComponent = { + custom_id: 'abc', + label: 'abc', + style: TextInputStyle.Short, + type: ComponentType.TextInput, + }; + + expect(createComponentBuilder(textInput)).toBeInstanceOf(TextInputBuilder); + }); + + test('GIVEN an unknown component type THEN throws error', () => { + // @ts-expect-error + expect(() => createComponentBuilder({ type: 'invalid' })).toThrowError(); + }); +}); diff --git a/packages/builders/__tests__/interactions/ContextMenuCommands.test.ts b/packages/builders/__tests__/interactions/ContextMenuCommands.test.ts index e98c4df04..afa352ae1 100644 --- a/packages/builders/__tests__/interactions/ContextMenuCommands.test.ts +++ b/packages/builders/__tests__/interactions/ContextMenuCommands.test.ts @@ -86,6 +86,10 @@ describe('Context Menu Commands', () => { test('GIVEN valid builder with defaultPermission false THEN does not throw error', () => { expect(() => getBuilder().setName('foo').setDefaultPermission(false)).not.toThrowError(); }); + + test('GIVEN valid builder with dmPermission false THEN does not throw error', () => { + expect(() => getBuilder().setName('foo').setDMPermission(false)).not.toThrowError(); + }); }); describe('Context menu command localizations', () => { diff --git a/packages/builders/__tests__/interactions/SlashCommands/Options.test.ts b/packages/builders/__tests__/interactions/SlashCommands/Options.test.ts index fa5c71670..2c18fc696 100644 --- a/packages/builders/__tests__/interactions/SlashCommands/Options.test.ts +++ b/packages/builders/__tests__/interactions/SlashCommands/Options.test.ts @@ -1,4 +1,5 @@ import { + APIApplicationCommandAttachmentOption, APIApplicationCommandBooleanOption, APIApplicationCommandChannelOption, APIApplicationCommandIntegerOption, @@ -12,6 +13,7 @@ import { } from 'discord-api-types/v10'; import { describe, test, expect } from 'vitest'; import { + SlashCommandAttachmentOption, SlashCommandBooleanOption, SlashCommandChannelOption, SlashCommandIntegerOption, @@ -58,6 +60,9 @@ const getRoleOption = () => new SlashCommandRoleOption().setName('owo').setDescr const getMentionableOption = () => new SlashCommandMentionableOption().setName('owo').setDescription('Testing 123').setRequired(true); +const getAttachmentOption = () => + new SlashCommandAttachmentOption().setName('attachment').setDescription('attachment').setRequired(true); + describe('Application Command toJSON() results', () => { test('GIVEN a boolean option THEN calling toJSON should return a valid JSON', () => { expect(getBooleanOption().toJSON()).toEqual({ @@ -205,4 +210,13 @@ describe('Application Command toJSON() results', () => { required: true, }); }); + + test('GIVEN an attachment option THEN calling toJSON should return a valid JSON', () => { + expect(getAttachmentOption().toJSON()).toEqual({ + name: 'attachment', + description: 'attachment', + type: ApplicationCommandOptionType.Attachment, + required: true, + }); + }); }); diff --git a/packages/builders/__tests__/interactions/SlashCommands/SlashCommands.test.ts b/packages/builders/__tests__/interactions/SlashCommands/SlashCommands.test.ts index 9145af900..642eb1129 100644 --- a/packages/builders/__tests__/interactions/SlashCommands/SlashCommands.test.ts +++ b/packages/builders/__tests__/interactions/SlashCommands/SlashCommands.test.ts @@ -93,6 +93,7 @@ describe('Slash Commands', () => { test('GIVEN valid array of options or choices THEN does not throw error', () => { expect(() => SlashCommandAssertions.validateMaxOptionsLength([])).not.toThrowError(); + expect(() => SlashCommandAssertions.validateChoicesLength(25)).not.toThrowError(); expect(() => SlashCommandAssertions.validateChoicesLength(25, [])).not.toThrowError(); }); @@ -133,6 +134,7 @@ describe('Slash Commands', () => { getBuilder() .setName('example') .setDescription('Example command') + .setDMPermission(false) .addBooleanOption((boolean) => boolean.setName('iscool').setDescription('Are we cool or what?').setRequired(true), ) diff --git a/packages/builders/__tests__/interactions/modal.test.ts b/packages/builders/__tests__/interactions/modal.test.ts index 6c4eb9904..63a6adb12 100644 --- a/packages/builders/__tests__/interactions/modal.test.ts +++ b/packages/builders/__tests__/interactions/modal.test.ts @@ -1,4 +1,9 @@ -import { APIModalInteractionResponseCallbackData, ComponentType, TextInputStyle } from 'discord-api-types/v10'; +import { + APIModalInteractionResponseCallbackData, + APITextInputComponent, + ComponentType, + TextInputStyle, +} from 'discord-api-types/v10'; import { describe, test, expect } from 'vitest'; import { ActionRowBuilder, @@ -49,16 +54,18 @@ describe('Modals', () => { test('GIVEN valid fields THEN builder does not throw', () => { expect(() => - modal() - .setTitle('test') - .setCustomId('foobar') - .setComponents(new ActionRowBuilder()) - .addComponents([new ActionRowBuilder()]), + modal().setTitle('test').setCustomId('foobar').setComponents(new ActionRowBuilder()), + ).not.toThrowError(); + + expect(() => + // @ts-expect-error: You can pass a TextInputBuilder and it will add it to an action row + modal().setTitle('test').setCustomId('foobar').addComponents(new TextInputBuilder()), ).not.toThrowError(); }); test('GIVEN invalid fields THEN builder does throw', () => { expect(() => modal().setTitle('test').setCustomId('foobar').toJSON()).toThrowError(); + // @ts-expect-error expect(() => modal().setTitle('test').setCustomId(42).toJSON()).toThrowError(); }); @@ -112,4 +119,55 @@ describe('Modals', () => { .toJSON(), ).toEqual(modalData); }); + + describe('equals()', () => { + const textInput1 = new TextInputBuilder() + .setCustomId('custom id') + .setLabel('label') + .setStyle(TextInputStyle.Paragraph); + + const textInput2: APITextInputComponent = { + type: ComponentType.TextInput, + custom_id: 'custom id', + label: 'label', + style: TextInputStyle.Paragraph, + }; + + test('GIVEN equal builders THEN returns true', () => { + const equalTextInput = new TextInputBuilder() + .setCustomId('custom id') + .setLabel('label') + .setStyle(TextInputStyle.Paragraph); + + expect(textInput1.equals(equalTextInput)).toBeTruthy(); + }); + + test('GIVEN the same builder THEN returns true', () => { + expect(textInput1.equals(textInput1)).toBeTruthy(); + }); + + test('GIVEN equal builder and data THEN returns true', () => { + expect(textInput1.equals(textInput2)).toBeTruthy(); + }); + + test('GIVEN different builders THEN returns false', () => { + const diffTextInput = new TextInputBuilder() + .setCustomId('custom id') + .setLabel('label 2') + .setStyle(TextInputStyle.Paragraph); + + expect(textInput1.equals(diffTextInput)).toBeFalsy(); + }); + + test('GIVEN different text input builder and data THEN returns false', () => { + const diffTextInputData: APITextInputComponent = { + type: ComponentType.TextInput, + custom_id: 'custom id', + label: 'label 2', + style: TextInputStyle.Short, + }; + + expect(textInput1.equals(diffTextInputData)).toBeFalsy(); + }); + }); }); diff --git a/packages/builders/__tests__/util.test.ts b/packages/builders/__tests__/util.test.ts new file mode 100644 index 000000000..893345754 --- /dev/null +++ b/packages/builders/__tests__/util.test.ts @@ -0,0 +1,42 @@ +import { describe, test, expect } from 'vitest'; +import { + isJSONEncodable, + isEquatable, + ActionRowBuilder, + enableValidators, + disableValidators, + isValidationEnabled, +} from '../src/index'; + +describe('isEquatable', () => { + test('returns true if the object is equatable', () => { + expect(isEquatable({ equals: () => true })).toBeTruthy(); + }); + + test('returns false if the object is not equatable', () => { + expect(isEquatable({})).toBeFalsy(); + }); +}); + +describe('isJSONEncodable', () => { + test('returns true if the object is JSON encodable', () => { + expect(isJSONEncodable({ toJSON: () => ({}) })).toBeTruthy(); + expect(isJSONEncodable(new ActionRowBuilder())).toBeTruthy(); + }); + + test('returns false if the object is not JSON encodable', () => { + expect(isJSONEncodable({})).toBeFalsy(); + }); +}); + +describe('validation', () => { + test('enables validation', () => { + enableValidators(); + expect(isValidationEnabled()).toBeTruthy(); + }); + + test('disables validation', () => { + disableValidators(); + expect(isValidationEnabled()).toBeFalsy(); + }); +});