mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 03:23:29 +01:00
refactor: builders (#10448)
BREAKING CHANGE: formatters export removed (prev. deprecated) BREAKING CHANGE: `SelectMenuBuilder` and `SelectMenuOptionBuilder` have been removed (prev. deprecated) BREAKING CHANGE: `EmbedBuilder` no longer takes camalCase options BREAKING CHANGE: `ActionRowBuilder` now has specialized `[add/set]X` methods as opposed to the current `[add/set]Components` BREAKING CHANGE: Removed `equals` methods BREAKING CHANGE: Sapphire -> zod for validation BREAKING CHANGE: Removed the ability to pass `null`/`undefined` to clear fields, use `clearX()` instead BREAKING CHANGE: Renamed all "slash command" symbols to instead use "chat input command" BREAKING CHANGE: Removed `ContextMenuCommandBuilder` in favor of `MessageCommandBuilder` and `UserCommandBuilder` BREAKING CHANGE: Removed support for passing the "string key"s of enums BREAKING CHANGE: Removed `Button` class in favor for specialized classes depending on the style BREAKING CHANGE: Removed nested `addX` styled-methods in favor of plural `addXs` Co-authored-by: Vlad Frangu <me@vladfrangu.dev> Co-authored-by: Almeida <github@almeidx.dev>
This commit is contained in:
@@ -7,8 +7,8 @@ import {
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
createComponentBuilder,
|
||||
PrimaryButtonBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
StringSelectMenuOptionBuilder,
|
||||
} from '../../src/index.js';
|
||||
@@ -41,21 +41,14 @@ const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent>
|
||||
value: 'two',
|
||||
},
|
||||
],
|
||||
max_values: 10,
|
||||
min_values: 12,
|
||||
max_values: 2,
|
||||
min_values: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('Action Row Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid components THEN do not throw', () => {
|
||||
expect(() => new ActionRowBuilder().addComponents(new ButtonBuilder())).not.toThrowError();
|
||||
expect(() => new ActionRowBuilder().setComponents(new ButtonBuilder())).not.toThrowError();
|
||||
expect(() => new ActionRowBuilder().addComponents([new ButtonBuilder()])).not.toThrowError();
|
||||
expect(() => new ActionRowBuilder().setComponents([new ButtonBuilder()])).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
@@ -72,22 +65,10 @@ describe('Action Row Components', () => {
|
||||
style: ButtonStyle.Link,
|
||||
url: 'https://google.com',
|
||||
},
|
||||
{
|
||||
type: ComponentType.StringSelect,
|
||||
placeholder: 'test',
|
||||
custom_id: 'test',
|
||||
options: [
|
||||
{
|
||||
label: 'option',
|
||||
value: 'option',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(new ActionRowBuilder(actionRowData).toJSON()).toEqual(actionRowData);
|
||||
expect(new ActionRowBuilder().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
|
||||
expect(() => createComponentBuilder({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
@@ -120,24 +101,23 @@ describe('Action Row Components', () => {
|
||||
value: 'two',
|
||||
},
|
||||
],
|
||||
max_values: 10,
|
||||
min_values: 12,
|
||||
max_values: 1,
|
||||
min_values: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(new ActionRowBuilder(rowWithButtonData).toJSON()).toEqual(rowWithButtonData);
|
||||
expect(new ActionRowBuilder(rowWithSelectMenuData).toJSON()).toEqual(rowWithSelectMenuData);
|
||||
expect(new ActionRowBuilder().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
|
||||
expect(() => createComponentBuilder({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||
const button = new PrimaryButtonBuilder().setLabel('test').setCustomId('123');
|
||||
const selectMenu = new StringSelectMenuBuilder()
|
||||
.setCustomId('1234')
|
||||
.setMaxValues(10)
|
||||
.setMinValues(12)
|
||||
.setMaxValues(2)
|
||||
.setMinValues(2)
|
||||
.setOptions(
|
||||
new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'),
|
||||
new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'),
|
||||
@@ -147,10 +127,39 @@ describe('Action Row Components', () => {
|
||||
new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'),
|
||||
]);
|
||||
|
||||
expect(new ActionRowBuilder().addComponents(button).toJSON()).toEqual(rowWithButtonData);
|
||||
expect(new ActionRowBuilder().addComponents(selectMenu).toJSON()).toEqual(rowWithSelectMenuData);
|
||||
expect(new ActionRowBuilder().addComponents([button]).toJSON()).toEqual(rowWithButtonData);
|
||||
expect(new ActionRowBuilder().addComponents([selectMenu]).toJSON()).toEqual(rowWithSelectMenuData);
|
||||
expect(new ActionRowBuilder().addPrimaryButtonComponents(button).toJSON()).toEqual(rowWithButtonData);
|
||||
expect(new ActionRowBuilder().addStringSelectMenuComponent(selectMenu).toJSON()).toEqual(rowWithSelectMenuData);
|
||||
expect(new ActionRowBuilder().addPrimaryButtonComponents([button]).toJSON()).toEqual(rowWithButtonData);
|
||||
});
|
||||
|
||||
test('GIVEN 2 select menus THEN it throws', () => {
|
||||
const selectMenu = new StringSelectMenuBuilder()
|
||||
.setCustomId('1234')
|
||||
.setOptions(
|
||||
new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'),
|
||||
new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'),
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
new ActionRowBuilder()
|
||||
.addStringSelectMenuComponent(selectMenu)
|
||||
.addStringSelectMenuComponent(selectMenu)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a button and a select menu THEN it throws', () => {
|
||||
const button = new PrimaryButtonBuilder().setLabel('test').setCustomId('123');
|
||||
const selectMenu = new StringSelectMenuBuilder()
|
||||
.setCustomId('1234')
|
||||
.setOptions(
|
||||
new StringSelectMenuOptionBuilder().setLabel('one').setValue('one'),
|
||||
new StringSelectMenuOptionBuilder().setLabel('two').setValue('two'),
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
new ActionRowBuilder().addStringSelectMenuComponent(selectMenu).addPrimaryButtonComponents(button).toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,45 +5,21 @@ import {
|
||||
type APIButtonComponentWithURL,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { buttonLabelValidator, buttonStyleValidator } from '../../src/components/Assertions.js';
|
||||
import { ButtonBuilder } from '../../src/components/button/Button.js';
|
||||
|
||||
const buttonComponent = () => new ButtonBuilder();
|
||||
import { PrimaryButtonBuilder, PremiumButtonBuilder, LinkButtonBuilder } from '../../src/index.js';
|
||||
|
||||
const longStr =
|
||||
'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong';
|
||||
|
||||
describe('Button Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid label THEN validator does not throw', () => {
|
||||
expect(() => buttonLabelValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid label THEN validator does throw', () => {
|
||||
expect(() => buttonLabelValidator.parse(null)).toThrowError();
|
||||
expect(() => buttonLabelValidator.parse('')).toThrowError();
|
||||
|
||||
expect(() => buttonLabelValidator.parse(longStr)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid style THEN validator does not throw', () => {
|
||||
expect(() => buttonStyleValidator.parse(3)).not.toThrowError();
|
||||
expect(() => buttonStyleValidator.parse(ButtonStyle.Secondary)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid style THEN validator does throw', () => {
|
||||
expect(() => buttonStyleValidator.parse(7)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid fields THEN builder does not throw', () => {
|
||||
expect(() =>
|
||||
buttonComponent().setCustomId('custom').setStyle(ButtonStyle.Primary).setLabel('test'),
|
||||
).not.toThrowError();
|
||||
expect(() => new PrimaryButtonBuilder().setCustomId('custom').setLabel('test')).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
const button = new PrimaryButtonBuilder()
|
||||
.setCustomId('custom')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel('test')
|
||||
.setDisabled(true)
|
||||
.setEmoji({ name: 'test' });
|
||||
|
||||
@@ -51,111 +27,41 @@ describe('Button Components', () => {
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setSKUId('123456789012345678').setStyle(ButtonStyle.Premium);
|
||||
const button = new PremiumButtonBuilder().setSKUId('123456789012345678');
|
||||
button.toJSON();
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => buttonComponent().setURL('https://google.com')).not.toThrowError();
|
||||
expect(() => new LinkButtonBuilder().setURL('https://google.com')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid fields THEN build does throw', () => {
|
||||
expect(() => {
|
||||
buttonComponent().setCustomId(longStr);
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setCustomId('custom')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setDisabled(true)
|
||||
.setLabel('test')
|
||||
.setURL('https://google.com')
|
||||
.setEmoji({ name: 'test' });
|
||||
|
||||
button.toJSON();
|
||||
new PrimaryButtonBuilder().setCustomId(longStr).toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
// @ts-expect-error: Invalid emoji
|
||||
const button = buttonComponent().setEmoji('test');
|
||||
const button = new PrimaryButtonBuilder().setEmoji('test');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Primary);
|
||||
const button = new PrimaryButtonBuilder();
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Primary).setCustomId('test');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Link);
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Primary).setLabel('test').setURL('https://google.com');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Link).setLabel('test');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent().setStyle(ButtonStyle.Primary).setSKUId('123456789012345678');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setLabel('button')
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setEmoji({ name: '😇' })
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setCustomId('test')
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const button = buttonComponent()
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL('https://google.com')
|
||||
.setSKUId('123456789012345678');
|
||||
|
||||
const button = new PrimaryButtonBuilder().setCustomId('test');
|
||||
button.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid style
|
||||
expect(() => buttonComponent().setStyle(24)).toThrowError();
|
||||
expect(() => buttonComponent().setLabel(longStr)).toThrowError();
|
||||
expect(() => new PrimaryButtonBuilder().setCustomId('hi').setStyle(24).toJSON()).toThrowError();
|
||||
expect(() => new PrimaryButtonBuilder().setCustomId('hi').setLabel(longStr).toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid parameter for disabled
|
||||
expect(() => buttonComponent().setDisabled(0)).toThrowError();
|
||||
expect(() => new PrimaryButtonBuilder().setCustomId('hi').setDisabled(0).toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid emoji
|
||||
expect(() => buttonComponent().setEmoji('foo')).toThrowError();
|
||||
|
||||
expect(() => buttonComponent().setURL('foobar')).toThrowError();
|
||||
expect(() => new PrimaryButtonBuilder().setCustomId('hi').setEmoji('foo').toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GiVEN valid input THEN valid JSON outputs are given', () => {
|
||||
@@ -167,13 +73,12 @@ describe('Button Components', () => {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
expect(new ButtonBuilder(interactionData).toJSON()).toEqual(interactionData);
|
||||
expect(new PrimaryButtonBuilder(interactionData).toJSON()).toEqual(interactionData);
|
||||
|
||||
expect(
|
||||
buttonComponent()
|
||||
new PrimaryButtonBuilder()
|
||||
.setCustomId(interactionData.custom_id)
|
||||
.setLabel(interactionData.label!)
|
||||
.setStyle(interactionData.style)
|
||||
.setDisabled(interactionData.disabled)
|
||||
.toJSON(),
|
||||
).toEqual(interactionData);
|
||||
@@ -186,9 +91,7 @@ describe('Button Components', () => {
|
||||
url: 'https://google.com',
|
||||
};
|
||||
|
||||
expect(new ButtonBuilder(linkData).toJSON()).toEqual(linkData);
|
||||
|
||||
expect(buttonComponent().setLabel(linkData.label!).setDisabled(true).setURL(linkData.url));
|
||||
expect(new LinkButtonBuilder(linkData).toJSON()).toEqual(linkData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
createComponentBuilder,
|
||||
CustomIdButtonBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
TextInputBuilder,
|
||||
} from '../../src/index.js';
|
||||
|
||||
describe('createComponentBuilder', () => {
|
||||
test.each([ButtonBuilder, StringSelectMenuBuilder, TextInputBuilder])(
|
||||
test.each([StringSelectMenuBuilder, TextInputBuilder])(
|
||||
'passing an instance of %j should return itself',
|
||||
(Builder) => {
|
||||
const builder = new Builder();
|
||||
@@ -42,7 +42,7 @@ describe('createComponentBuilder', () => {
|
||||
type: ComponentType.Button,
|
||||
};
|
||||
|
||||
expect(createComponentBuilder(button)).toBeInstanceOf(ButtonBuilder);
|
||||
expect(createComponentBuilder(button)).toBeInstanceOf(CustomIdButtonBuilder);
|
||||
});
|
||||
|
||||
test('GIVEN a select menu component THEN returns a StringSelectMenuBuilder', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, test, expect } from 'vitest';
|
||||
import { StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from '../../src/index.js';
|
||||
|
||||
const selectMenu = () => new StringSelectMenuBuilder();
|
||||
const selectMenuWithId = () => new StringSelectMenuBuilder({ custom_id: 'hi' });
|
||||
const selectMenuOption = () => new StringSelectMenuOptionBuilder();
|
||||
|
||||
const longStr = 'a'.repeat(256);
|
||||
@@ -16,10 +17,10 @@ const selectMenuOptionData: APISelectMenuOption = {
|
||||
};
|
||||
|
||||
const selectMenuDataWithoutOptions = {
|
||||
type: ComponentType.SelectMenu,
|
||||
type: ComponentType.StringSelect,
|
||||
custom_id: 'test',
|
||||
max_values: 10,
|
||||
min_values: 3,
|
||||
max_values: 1,
|
||||
min_values: 1,
|
||||
disabled: true,
|
||||
placeholder: 'test',
|
||||
} as const;
|
||||
@@ -109,49 +110,87 @@ describe('Select Menu Components', () => {
|
||||
});
|
||||
|
||||
test('GIVEN invalid inputs THEN Select Menu does throw', () => {
|
||||
expect(() => selectMenu().setCustomId(longStr)).toThrowError();
|
||||
expect(() => selectMenu().setMaxValues(30)).toThrowError();
|
||||
expect(() => selectMenu().setMinValues(-20)).toThrowError();
|
||||
expect(() => selectMenu().setCustomId(longStr).toJSON()).toThrowError();
|
||||
expect(() => selectMenuWithId().setMaxValues(30).toJSON()).toThrowError();
|
||||
expect(() => selectMenuWithId().setMinValues(-20).toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid disabled value
|
||||
expect(() => selectMenu().setDisabled(0)).toThrowError();
|
||||
expect(() => selectMenu().setPlaceholder(longStr)).toThrowError();
|
||||
expect(() => selectMenuWithId().setDisabled(0).toJSON()).toThrowError();
|
||||
expect(() => selectMenuWithId().setPlaceholder(longStr).toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
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();
|
||||
expect(() => selectMenuWithId().addOptions({ label: 'test' }).toJSON()).toThrowError();
|
||||
expect(() => selectMenuWithId().addOptions({ label: longStr, value: 'test' }).toJSON()).toThrowError();
|
||||
expect(() => selectMenuWithId().addOptions({ value: longStr, label: 'test' }).toJSON()).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId().addOptions({ label: 'test', value: 'test', description: longStr }).toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
// @ts-expect-error: Invalid option
|
||||
selectMenuWithId().addOptions({ label: 'test', value: 'test', default: 100 }).toJSON(),
|
||||
).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', default: 100 })).toThrowError();
|
||||
expect(() => selectMenuWithId().addOptions({ value: 'test' }).toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
expect(() => selectMenu().addOptions({ value: 'test' })).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
expect(() => selectMenu().addOptions({ default: true })).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
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: Invalid option
|
||||
expect(() => selectMenu().addOptions([{ label: 'test', value: 'test', default: 100 }])).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
expect(() => selectMenu().addOptions([{ value: 'test' }])).toThrowError();
|
||||
// @ts-expect-error: Invalid option
|
||||
expect(() => selectMenu().addOptions([{ default: true }])).toThrowError();
|
||||
expect(() => selectMenuWithId().addOptions({ default: true }).toJSON()).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
// @ts-expect-error: Invalid option
|
||||
.addOptions([{ label: 'test' }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
.addOptions([{ label: longStr, value: 'test' }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
.addOptions([{ value: longStr, label: 'test' }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
.addOptions([{ label: 'test', value: 'test', description: longStr }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
// @ts-expect-error: Invalid option
|
||||
.addOptions([{ label: 'test', value: 'test', default: 100 }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
// @ts-expect-error: Invalid option
|
||||
.addOptions([{ value: 'test' }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenuWithId()
|
||||
// @ts-expect-error: Invalid option
|
||||
.addOptions([{ default: true }])
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
const tooManyOptions = Array.from<APISelectMenuOption>({ length: 26 }).fill({ label: 'test', value: 'test' });
|
||||
|
||||
expect(() => selectMenu().setOptions(...tooManyOptions)).toThrowError();
|
||||
expect(() => selectMenu().setOptions(tooManyOptions)).toThrowError();
|
||||
expect(() =>
|
||||
selectMenu()
|
||||
.setOptions(...tooManyOptions)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() => selectMenu().setOptions(tooManyOptions).toJSON()).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
selectMenu()
|
||||
.addOptions({ label: 'test', value: 'test' })
|
||||
.addOptions(...tooManyOptions),
|
||||
.addOptions(...tooManyOptions)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
selectMenu()
|
||||
.addOptions([{ label: 'test', value: 'test' }])
|
||||
.addOptions(tooManyOptions),
|
||||
.addOptions(tooManyOptions)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
@@ -162,7 +201,8 @@ describe('Select Menu Components', () => {
|
||||
.setDefault(-1)
|
||||
// @ts-expect-error: Invalid emoji
|
||||
.setEmoji({ name: 1 })
|
||||
.setDescription(longStr);
|
||||
.setDescription(longStr)
|
||||
.toJSON();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
@@ -212,17 +252,16 @@ describe('Select Menu Components', () => {
|
||||
).toStrictEqual([selectMenuOptionData]);
|
||||
|
||||
expect(() =>
|
||||
makeStringSelectMenuWithOptions().spliceOptions(
|
||||
0,
|
||||
0,
|
||||
...Array.from({ length: 26 }, () => selectMenuOptionData),
|
||||
),
|
||||
makeStringSelectMenuWithOptions()
|
||||
.spliceOptions(0, 0, ...Array.from({ length: 26 }, () => selectMenuOptionData))
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
makeStringSelectMenuWithOptions()
|
||||
.setOptions(Array.from({ length: 25 }, () => selectMenuOptionData))
|
||||
.spliceOptions(-1, 2, selectMenuOptionData, selectMenuOptionData),
|
||||
.spliceOptions(-1, 2, selectMenuOptionData, selectMenuOptionData)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { ComponentType, TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
labelValidator,
|
||||
maxLengthValidator,
|
||||
minLengthValidator,
|
||||
placeholderValidator,
|
||||
valueValidator,
|
||||
textInputStyleValidator,
|
||||
} from '../../src/components/textInput/Assertions.js';
|
||||
import { TextInputBuilder } from '../../src/components/textInput/TextInput.js';
|
||||
|
||||
const superLongStr = 'a'.repeat(5_000);
|
||||
@@ -16,56 +8,6 @@ const textInputComponent = () => new TextInputBuilder();
|
||||
|
||||
describe('Text Input Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid label THEN validator does not throw', () => {
|
||||
expect(() => labelValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid label THEN validator does throw', () => {
|
||||
expect(() => labelValidator.parse(24)).toThrowError();
|
||||
expect(() => labelValidator.parse(undefined)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid style THEN validator does not throw', () => {
|
||||
expect(() => textInputStyleValidator.parse(TextInputStyle.Paragraph)).not.toThrowError();
|
||||
expect(() => textInputStyleValidator.parse(TextInputStyle.Short)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid style THEN validator does throw', () => {
|
||||
expect(() => textInputStyleValidator.parse(24)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid min length THEN validator does not throw', () => {
|
||||
expect(() => minLengthValidator.parse(10)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid min length THEN validator does throw', () => {
|
||||
expect(() => minLengthValidator.parse(-1)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid max length THEN validator does not throw', () => {
|
||||
expect(() => maxLengthValidator.parse(10)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid min length THEN validator does throw 2', () => {
|
||||
expect(() => maxLengthValidator.parse(4_001)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid value THEN validator does not throw', () => {
|
||||
expect(() => valueValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid value THEN validator does throw', () => {
|
||||
expect(() => valueValidator.parse(superLongStr)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid placeholder THEN validator does not throw', () => {
|
||||
expect(() => placeholderValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid value THEN validator does throw 2', () => {
|
||||
expect(() => placeholderValidator.parse(superLongStr)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid fields THEN builder does not throw', () => {
|
||||
expect(() => {
|
||||
textInputComponent().setCustomId('foobar').setLabel('test').setStyle(TextInputStyle.Paragraph).toJSON();
|
||||
@@ -84,9 +26,7 @@ describe('Text Input Components', () => {
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
// Issue #8107
|
||||
// @ts-expect-error: Shapeshift maps the enum key to the value when parsing
|
||||
textInputComponent().setCustomId('Custom').setLabel('Guess').setStyle('Short').toJSON();
|
||||
textInputComponent().setCustomId('Custom').setLabel('Guess').setStyle(TextInputStyle.Short).toJSON();
|
||||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
import {
|
||||
ApplicationCommandType,
|
||||
ApplicationIntegrationType,
|
||||
ChannelType,
|
||||
InteractionContextType,
|
||||
PermissionFlagsBits,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ChatInputCommandBooleanOption,
|
||||
ChatInputCommandBuilder,
|
||||
ChatInputCommandChannelOption,
|
||||
ChatInputCommandIntegerOption,
|
||||
ChatInputCommandMentionableOption,
|
||||
ChatInputCommandNumberOption,
|
||||
ChatInputCommandRoleOption,
|
||||
ChatInputCommandAttachmentOption,
|
||||
ChatInputCommandStringOption,
|
||||
ChatInputCommandSubcommandBuilder,
|
||||
ChatInputCommandSubcommandGroupBuilder,
|
||||
ChatInputCommandUserOption,
|
||||
} from '../../../src/index.js';
|
||||
|
||||
const getBuilder = () => new ChatInputCommandBuilder();
|
||||
const getNamedBuilder = () => getBuilder().setName('example').setDescription('Example command');
|
||||
const getStringOption = () => new ChatInputCommandStringOption().setName('owo').setDescription('Testing 123');
|
||||
const getIntegerOption = () => new ChatInputCommandIntegerOption().setName('owo').setDescription('Testing 123');
|
||||
const getNumberOption = () => new ChatInputCommandNumberOption().setName('owo').setDescription('Testing 123');
|
||||
const getBooleanOption = () => new ChatInputCommandBooleanOption().setName('owo').setDescription('Testing 123');
|
||||
const getUserOption = () => new ChatInputCommandUserOption().setName('owo').setDescription('Testing 123');
|
||||
const getChannelOption = () => new ChatInputCommandChannelOption().setName('owo').setDescription('Testing 123');
|
||||
const getRoleOption = () => new ChatInputCommandRoleOption().setName('owo').setDescription('Testing 123');
|
||||
const getAttachmentOption = () => new ChatInputCommandAttachmentOption().setName('owo').setDescription('Testing 123');
|
||||
const getMentionableOption = () => new ChatInputCommandMentionableOption().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommandGroup = () =>
|
||||
new ChatInputCommandSubcommandGroupBuilder().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommand = () => new ChatInputCommandSubcommandBuilder().setName('owo').setDescription('Testing 123');
|
||||
|
||||
class Collection {
|
||||
public readonly [Symbol.toStringTag] = 'Map';
|
||||
}
|
||||
|
||||
describe('ChatInput Commands', () => {
|
||||
describe('ChatInputCommandBuilder', () => {
|
||||
describe('Builder with no options', () => {
|
||||
test('GIVEN empty builder THEN throw error when calling toJSON', () => {
|
||||
expect(() => getBuilder().toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('example').setDescription('Example command').toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with simple options', () => {
|
||||
test('GIVEN valid builder THEN returns type included', () => {
|
||||
expect(getNamedBuilder().toJSON()).includes({ type: ApplicationCommandType.ChatInput });
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder()
|
||||
.setName('example')
|
||||
.setDescription('Example command')
|
||||
.addBooleanOptions((boolean) =>
|
||||
boolean.setName('iscool').setDescription('Are we cool or what?').setRequired(true),
|
||||
)
|
||||
.addChannelOptions((channel) => channel.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addMentionableOptions((mentionable) =>
|
||||
mentionable.setName('iscool').setDescription('Are we cool or what?'),
|
||||
)
|
||||
.addRoleOptions((role) => role.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addUserOptions((user) => user.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addIntegerOptions((integer) =>
|
||||
integer
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices({ name: 'Very cool', value: 1_000 })
|
||||
.addChoices([{ name: 'Even cooler', value: 2_000 }]),
|
||||
)
|
||||
.addNumberOptions((number) =>
|
||||
number
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices({ name: 'Very cool', value: 1.5 })
|
||||
.addChoices([{ name: 'Even cooler', value: 2.5 }]),
|
||||
)
|
||||
.addStringOptions((string) =>
|
||||
string
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices({ name: 'Fancy Pants', value: 'fp_1' }, { name: 'Fancy Shoes', value: 'fs_1' })
|
||||
.addChoices([{ name: 'The Whole shebang', value: 'all' }]),
|
||||
)
|
||||
.addIntegerOptions((integer) =>
|
||||
integer.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.addNumberOptions((number) =>
|
||||
number.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.addStringOptions((string) =>
|
||||
string.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid autocomplete THEN does throw an error', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
getNamedBuilder().addStringOptions(getStringOption().setAutocomplete('not a boolean')).toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with both choices and autocomplete THEN does throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addStringOptions(
|
||||
getStringOption().setAutocomplete(true).addChoices({ name: 'Fancy Pants', value: 'fp_1' }),
|
||||
)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addStringOptions(
|
||||
getStringOption()
|
||||
.setAutocomplete(true)
|
||||
.addChoices(
|
||||
{ name: 'Fancy Pants', value: 'fp_1' },
|
||||
{ name: 'Fancy Shoes', value: 'fs_1' },
|
||||
{ name: 'The Whole shebang', value: 'all' },
|
||||
),
|
||||
)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addStringOptions(
|
||||
getStringOption().addChoices({ name: 'Fancy Pants', value: 'fp_1' }).setAutocomplete(true),
|
||||
)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addChannelOptions(
|
||||
getChannelOption().addChannelTypes(ChannelType.GuildText).addChannelTypes([ChannelType.GuildVoice]),
|
||||
)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
getNamedBuilder()
|
||||
.addChannelOptions(getChannelOption().addChannelTypes(ChannelType.GuildAnnouncement, ChannelType.GuildText))
|
||||
.toJSON();
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid channel options and channel_types THEN does throw an error', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error: Invalid channel type
|
||||
getNamedBuilder().addChannelOptions(getChannelOption().addChannelTypes(100)).toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
// @ts-expect-error: Invalid channel types
|
||||
getNamedBuilder().addChannelOptions(getChannelOption().addChannelTypes(100, 200)).toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid number min/max options THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid max value
|
||||
expect(() => getNamedBuilder().addNumberOptions(getNumberOption().setMaxValue('test')).toJSON()).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
// @ts-expect-error: Invalid max value
|
||||
getNamedBuilder().addIntegerOptions(getIntegerOption().setMaxValue('test')).toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid min value
|
||||
expect(() => getNamedBuilder().addNumberOptions(getNumberOption().setMinValue('test')).toJSON()).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
// @ts-expect-error: Invalid min value
|
||||
getNamedBuilder().addIntegerOptions(getIntegerOption().setMinValue('test')).toJSON(),
|
||||
).toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addIntegerOptions(getIntegerOption().setMinValue(1.5)).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid number min/max options THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addIntegerOptions(getIntegerOption().setMinValue(1)).toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder().addNumberOptions(getNumberOption().setMinValue(1.5)).toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder().addIntegerOptions(getIntegerOption().setMaxValue(1)).toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder().addNumberOptions(getNumberOption().setMaxValue(1.5)).toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an already built builder THEN does not throw an error', () => {
|
||||
expect(() => getNamedBuilder().addStringOptions(getStringOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addIntegerOptions(getIntegerOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addNumberOptions(getNumberOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addBooleanOptions(getBooleanOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addUserOptions(getUserOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addChannelOptions(getChannelOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addRoleOptions(getRoleOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addAttachmentOptions(getAttachmentOption()).toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addMentionableOptions(getMentionableOption()).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => getBuilder().setName('TEST_COMMAND').setDescription(':3').toJSON()).toThrowError();
|
||||
expect(() => getBuilder().setName('ĂĂĂĂĂĂ').setDescription(':3').toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid names THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('hi_there').setDescription(':3')).not.toThrowError();
|
||||
expect(() => getBuilder().setName('o_comandă').setDescription(':3')).not.toThrowError();
|
||||
expect(() => getBuilder().setName('どうも').setDescription(':3')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid returns for builder THEN throw error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getNamedBuilder().addBooleanOptions(true).toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getNamedBuilder().addBooleanOptions(null).toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getNamedBuilder().addBooleanOptions(undefined).toJSON()).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
.addBooleanOptions(() => ChatInputCommandStringOption)
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
.addBooleanOptions(() => new Collection())
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option that is autocompletable and has choices, THEN passing nothing to setChoices should not throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addStringOptions(getStringOption().setAutocomplete(true).setChoices()).toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option that is autocompletable, THEN setting choices should throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addStringOptions(getStringOption().setAutocomplete(true).setChoices({ name: 'owo', value: 'uwu' }))
|
||||
.toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option, THEN setting choices should not throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addStringOptions(getStringOption().setChoices({ name: 'owo', value: 'uwu' }))
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with NSFW, THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().setName('foo').setDescription('foo').setNSFW(true).toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with subcommand (group) options', () => {
|
||||
test('GIVEN builder with subcommand group THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addSubcommandGroups((group) =>
|
||||
group.setName('group').setDescription('Group us together!').addSubcommands(getSubcommand()),
|
||||
)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with subcommand THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.addSubcommands((subcommand) => subcommand.setName('boop').setDescription('Boops a fellow nerd (you)'))
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with subcommand THEN has regular ChatInput command fields', () => {
|
||||
expect(() =>
|
||||
getBuilder()
|
||||
.setName('name')
|
||||
.setDescription('description')
|
||||
.addSubcommands((option) => option.setName('ye').setDescription('ye'))
|
||||
.addSubcommands((option) => option.setName('no').setDescription('no'))
|
||||
.setDefaultMemberPermissions(1n)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand group THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommandGroups(getSubcommandGroup().addSubcommands(getSubcommand())).toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().addSubcommands(getSubcommand()).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommands(getSubcommand().addBooleanOptions(getBooleanOption())).toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with a subcommand that tries to add an invalid result THEN throw error', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error: Checking if check works JS-side too
|
||||
getNamedBuilder().addSubcommands(getSubcommand()).addIntegerOptions(getInteger()).toJSON(),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN no valid return for an addSubcommand(Group) method THEN throw error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getNamedBuilder().addSubcommands(getSubcommandGroup()).toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subcommand group builder', () => {
|
||||
test('GIVEN no valid subcommand THEN throw error', () => {
|
||||
expect(() => getSubcommandGroup().addSubcommands().toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getSubcommandGroup().addSubcommands(getSubcommandGroup()).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a valid subcommand THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getSubcommandGroup()
|
||||
.addSubcommands((sub) => sub.setName('sub').setDescription('Testing 123'))
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subcommand builder', () => {
|
||||
test('GIVEN a valid subcommand with options THEN does not throw error', () => {
|
||||
expect(() => getSubcommand().addBooleanOptions(getBooleanOption()).toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatInput command localizations', () => {
|
||||
const expectedSingleLocale = { 'en-US': 'foobar' };
|
||||
const expectedMultipleLocales = {
|
||||
...expectedSingleLocale,
|
||||
bg: 'test',
|
||||
};
|
||||
|
||||
test('GIVEN valid name localizations THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setNameLocalization('en-US', 'foobar')).not.toThrowError();
|
||||
expect(() => getBuilder().setNameLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name localizations THEN does throw error', () => {
|
||||
// @ts-expect-error: Invalid localization
|
||||
expect(() => getNamedBuilder().setNameLocalization('en-U', 'foobar').toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid localization
|
||||
expect(() => getNamedBuilder().setNameLocalizations({ 'en-U': 'foobar' }).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid name localizations THEN valid data is stored', () => {
|
||||
expect(getNamedBuilder().setNameLocalization('en-US', 'foobar').toJSON().name_localizations).toEqual(
|
||||
expectedSingleLocale,
|
||||
);
|
||||
expect(
|
||||
getNamedBuilder().setNameLocalizations({ 'en-US': 'foobar', bg: 'test' }).toJSON().name_localizations,
|
||||
).toEqual(expectedMultipleLocales);
|
||||
expect(getNamedBuilder().clearNameLocalizations().toJSON().name_localizations).toBeUndefined();
|
||||
expect(getNamedBuilder().clearNameLocalization('en-US').toJSON().name_localizations).toEqual({
|
||||
'en-US': undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN valid description localizations THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().setDescriptionLocalization('en-US', 'foobar').toJSON()).not.toThrowError();
|
||||
expect(() => getNamedBuilder().setDescriptionLocalizations({ 'en-US': 'foobar' }).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid description localizations THEN does throw error', () => {
|
||||
// @ts-expect-error: Invalid localization description
|
||||
expect(() => getNamedBuilder().setDescriptionLocalization('en-U', 'foobar').toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid localization description
|
||||
expect(() => getNamedBuilder().setDescriptionLocalizations({ 'en-U': 'foobar' }).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid description localizations THEN valid data is stored', () => {
|
||||
expect(
|
||||
getNamedBuilder().setDescriptionLocalization('en-US', 'foobar').toJSON(false).description_localizations,
|
||||
).toEqual(expectedSingleLocale);
|
||||
expect(
|
||||
getNamedBuilder().setDescriptionLocalizations({ 'en-US': 'foobar', bg: 'test' }).toJSON(false)
|
||||
.description_localizations,
|
||||
).toEqual(expectedMultipleLocales);
|
||||
expect(
|
||||
getNamedBuilder().clearDescriptionLocalizations().toJSON(false).description_localizations,
|
||||
).toBeUndefined();
|
||||
expect(getNamedBuilder().clearDescriptionLocalization('en-US').toJSON(false).description_localizations).toEqual(
|
||||
{
|
||||
'en-US': undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions', () => {
|
||||
test('GIVEN valid permission string THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().setDefaultMemberPermissions('1')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid permission bitfield THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().setDefaultMemberPermissions(
|
||||
PermissionFlagsBits.AddReactions | PermissionFlagsBits.AttachFiles,
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN null permissions THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().clearDefaultMemberPermissions()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid inputs THEN does throw error', () => {
|
||||
expect(() => getNamedBuilder().setDefaultMemberPermissions('1.1').toJSON()).toThrowError();
|
||||
expect(() => getNamedBuilder().setDefaultMemberPermissions(1.1).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid permission with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addBooleanOptions(getBooleanOption()).setDefaultMemberPermissions('1').toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => getNamedBuilder().addChannelOptions(getChannelOption())).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('contexts', () => {
|
||||
test('GIVEN a builder with valid contexts THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().setContexts([InteractionContextType.Guild, InteractionContextType.BotDM]).toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder().setContexts(InteractionContextType.Guild, InteractionContextType.BotDM).toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid contexts THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getNamedBuilder().setContexts(999).toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getNamedBuilder().setContexts([999, 998]).toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration types', () => {
|
||||
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall])
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getNamedBuilder()
|
||||
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid integration types THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getNamedBuilder().setIntegrationTypes(999).toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getNamedBuilder().setIntegrationTypes([999, 998]).toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,32 +13,32 @@ import {
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
SlashCommandAttachmentOption,
|
||||
SlashCommandBooleanOption,
|
||||
SlashCommandChannelOption,
|
||||
SlashCommandIntegerOption,
|
||||
SlashCommandMentionableOption,
|
||||
SlashCommandNumberOption,
|
||||
SlashCommandRoleOption,
|
||||
SlashCommandStringOption,
|
||||
SlashCommandUserOption,
|
||||
ChatInputCommandAttachmentOption,
|
||||
ChatInputCommandBooleanOption,
|
||||
ChatInputCommandChannelOption,
|
||||
ChatInputCommandIntegerOption,
|
||||
ChatInputCommandMentionableOption,
|
||||
ChatInputCommandNumberOption,
|
||||
ChatInputCommandRoleOption,
|
||||
ChatInputCommandStringOption,
|
||||
ChatInputCommandUserOption,
|
||||
} from '../../../src/index.js';
|
||||
|
||||
const getBooleanOption = () =>
|
||||
new SlashCommandBooleanOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
new ChatInputCommandBooleanOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getChannelOption = () =>
|
||||
new SlashCommandChannelOption()
|
||||
new ChatInputCommandChannelOption()
|
||||
.setName('owo')
|
||||
.setDescription('Testing 123')
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText);
|
||||
|
||||
const getStringOption = () =>
|
||||
new SlashCommandStringOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
new ChatInputCommandStringOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getIntegerOption = () =>
|
||||
new SlashCommandIntegerOption()
|
||||
new ChatInputCommandIntegerOption()
|
||||
.setName('owo')
|
||||
.setDescription('Testing 123')
|
||||
.setRequired(true)
|
||||
@@ -46,22 +46,24 @@ const getIntegerOption = () =>
|
||||
.setMaxValue(10);
|
||||
|
||||
const getNumberOption = () =>
|
||||
new SlashCommandNumberOption()
|
||||
new ChatInputCommandNumberOption()
|
||||
.setName('owo')
|
||||
.setDescription('Testing 123')
|
||||
.setRequired(true)
|
||||
.setMinValue(-1.23)
|
||||
.setMaxValue(10);
|
||||
|
||||
const getUserOption = () => new SlashCommandUserOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
const getUserOption = () =>
|
||||
new ChatInputCommandUserOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getRoleOption = () => new SlashCommandRoleOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
const getRoleOption = () =>
|
||||
new ChatInputCommandRoleOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getMentionableOption = () =>
|
||||
new SlashCommandMentionableOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
new ChatInputCommandMentionableOption().setName('owo').setDescription('Testing 123').setRequired(true);
|
||||
|
||||
const getAttachmentOption = () =>
|
||||
new SlashCommandAttachmentOption().setName('attachment').setDescription('attachment').setRequired(true);
|
||||
new ChatInputCommandAttachmentOption().setName('attachment').setDescription('attachment').setRequired(true);
|
||||
|
||||
describe('Application Command toJSON() results', () => {
|
||||
test('GIVEN a boolean option THEN calling toJSON should return a valid JSON', () => {
|
||||
@@ -101,7 +103,6 @@ describe('Application Command toJSON() results', () => {
|
||||
max_value: 10,
|
||||
min_value: -1,
|
||||
autocomplete: true,
|
||||
// TODO
|
||||
choices: [],
|
||||
});
|
||||
|
||||
@@ -1,74 +1,31 @@
|
||||
import { ApplicationIntegrationType, InteractionContextType, PermissionFlagsBits } from 'discord-api-types/v10';
|
||||
import {
|
||||
ApplicationCommandType,
|
||||
ApplicationIntegrationType,
|
||||
InteractionContextType,
|
||||
PermissionFlagsBits,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { ContextMenuCommandAssertions, ContextMenuCommandBuilder } from '../../src/index.js';
|
||||
import { MessageContextCommandBuilder } from '../../src/index.js';
|
||||
|
||||
const getBuilder = () => new ContextMenuCommandBuilder();
|
||||
const getBuilder = () => new MessageContextCommandBuilder();
|
||||
|
||||
describe('Context Menu Commands', () => {
|
||||
describe('Assertions tests', () => {
|
||||
test('GIVEN valid name THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateName('ping')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateName(null)).toThrowError();
|
||||
|
||||
// Too short of a name
|
||||
expect(() => ContextMenuCommandAssertions.validateName('')).toThrowError();
|
||||
|
||||
// Invalid characters used
|
||||
expect(() => ContextMenuCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
||||
|
||||
// Too long of a name
|
||||
expect(() =>
|
||||
ContextMenuCommandAssertions.validateName('qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid type THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateType(3)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid type THEN throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateType(null)).toThrowError();
|
||||
|
||||
// Out of range
|
||||
expect(() => ContextMenuCommandAssertions.validateType(1)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid required parameters THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateRequiredParameters('owo', 2)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid default_permission THEN does not throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateDefaultPermission(true)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid default_permission THEN throw error', () => {
|
||||
expect(() => ContextMenuCommandAssertions.validateDefaultPermission(null)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ContextMenuCommandBuilder', () => {
|
||||
describe('Builder tests', () => {
|
||||
test('GIVEN empty builder THEN throw error when calling toJSON', () => {
|
||||
expect(() => getBuilder().toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('example').setType(3).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => getBuilder().setName('$$$')).toThrowError();
|
||||
expect(() => getBuilder().setName('$$$').toJSON()).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName(' ')).toThrowError();
|
||||
expect(() => getBuilder().setName(' ').toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid names THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('hi_there')).not.toThrowError();
|
||||
expect(() => getBuilder().setName('hi_there').toJSON()).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName('A COMMAND')).not.toThrowError();
|
||||
expect(() => getBuilder().setName('A COMMAND').toJSON()).not.toThrowError();
|
||||
|
||||
// Translation: a_command
|
||||
expect(() => getBuilder().setName('o_comandă')).not.toThrowError();
|
||||
@@ -76,20 +33,6 @@ describe('Context Menu Commands', () => {
|
||||
// Translation: thx (according to GTranslate)
|
||||
expect(() => getBuilder().setName('どうも')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid types THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setType(2)).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().setType(3)).not.toThrowError();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
@@ -106,19 +49,22 @@ describe('Context Menu Commands', () => {
|
||||
|
||||
test('GIVEN invalid name localizations THEN does throw error', () => {
|
||||
// @ts-expect-error: Invalid localization
|
||||
expect(() => getBuilder().setNameLocalization('en-U', 'foobar')).toThrowError();
|
||||
expect(() => getBuilder().setNameLocalization('en-U', 'foobar').toJSON()).toThrowError();
|
||||
// @ts-expect-error: Invalid localization
|
||||
expect(() => getBuilder().setNameLocalizations({ 'en-U': 'foobar' })).toThrowError();
|
||||
expect(() => getBuilder().setNameLocalizations({ 'en-U': 'foobar' }).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid name localizations THEN valid data is stored', () => {
|
||||
expect(getBuilder().setNameLocalization('en-US', 'foobar').name_localizations).toEqual(expectedSingleLocale);
|
||||
expect(getBuilder().setNameLocalizations({ 'en-US': 'foobar', bg: 'test' }).name_localizations).toEqual(
|
||||
expectedMultipleLocales,
|
||||
expect(getBuilder().setName('hi').setNameLocalization('en-US', 'foobar').toJSON().name_localizations).toEqual(
|
||||
expectedSingleLocale,
|
||||
);
|
||||
expect(getBuilder().setNameLocalizations(null).name_localizations).toBeNull();
|
||||
expect(getBuilder().setNameLocalization('en-US', null).name_localizations).toEqual({
|
||||
'en-US': null,
|
||||
expect(
|
||||
getBuilder().setName('hi').setNameLocalizations({ 'en-US': 'foobar', bg: 'test' }).toJSON()
|
||||
.name_localizations,
|
||||
).toEqual(expectedMultipleLocales);
|
||||
expect(getBuilder().setName('hi').clearNameLocalizations().toJSON().name_localizations).toBeUndefined();
|
||||
expect(getBuilder().setName('hi').clearNameLocalization('en-US').toJSON().name_localizations).toEqual({
|
||||
'en-US': undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -134,14 +80,10 @@ describe('Context Menu Commands', () => {
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN null permissions THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setDefaultMemberPermissions(null)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid inputs THEN does throw error', () => {
|
||||
expect(() => getBuilder().setDefaultMemberPermissions('1.1')).toThrowError();
|
||||
expect(() => getBuilder().setName('hi').setDefaultMemberPermissions('1.1').toJSON()).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setDefaultMemberPermissions(1.1)).toThrowError();
|
||||
expect(() => getBuilder().setName('hi').setDefaultMemberPermissions(1.1).toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,10 +100,10 @@ describe('Context Menu Commands', () => {
|
||||
|
||||
test('GIVEN a builder with invalid contexts THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts(999)).toThrowError();
|
||||
expect(() => getBuilder().setName('hi').setContexts(999).toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts([999, 998])).toThrowError();
|
||||
expect(() => getBuilder().setName('hi').setContexts([999, 998]).toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,10 +126,10 @@ describe('Context Menu Commands', () => {
|
||||
|
||||
test('GIVEN a builder with invalid integration types THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes(999)).toThrowError();
|
||||
expect(() => getBuilder().setName('hi').setIntegrationTypes(999).toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes([999, 998])).toThrowError();
|
||||
expect(() => getBuilder().setName('hi').setIntegrationTypes([999, 998]).toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,593 +0,0 @@
|
||||
import {
|
||||
ApplicationCommandType,
|
||||
ApplicationIntegrationType,
|
||||
ChannelType,
|
||||
InteractionContextType,
|
||||
PermissionFlagsBits,
|
||||
type APIApplicationCommandOptionChoice,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
SlashCommandAssertions,
|
||||
SlashCommandBooleanOption,
|
||||
SlashCommandBuilder,
|
||||
SlashCommandChannelOption,
|
||||
SlashCommandIntegerOption,
|
||||
SlashCommandMentionableOption,
|
||||
SlashCommandNumberOption,
|
||||
SlashCommandRoleOption,
|
||||
SlashCommandAttachmentOption,
|
||||
SlashCommandStringOption,
|
||||
SlashCommandSubcommandBuilder,
|
||||
SlashCommandSubcommandGroupBuilder,
|
||||
SlashCommandUserOption,
|
||||
} from '../../../src/index.js';
|
||||
|
||||
const largeArray = Array.from({ length: 26 }, () => 1 as unknown as APIApplicationCommandOptionChoice);
|
||||
|
||||
const getBuilder = () => new SlashCommandBuilder();
|
||||
const getNamedBuilder = () => getBuilder().setName('example').setDescription('Example command');
|
||||
const getStringOption = () => new SlashCommandStringOption().setName('owo').setDescription('Testing 123');
|
||||
const getIntegerOption = () => new SlashCommandIntegerOption().setName('owo').setDescription('Testing 123');
|
||||
const getNumberOption = () => new SlashCommandNumberOption().setName('owo').setDescription('Testing 123');
|
||||
const getBooleanOption = () => new SlashCommandBooleanOption().setName('owo').setDescription('Testing 123');
|
||||
const getUserOption = () => new SlashCommandUserOption().setName('owo').setDescription('Testing 123');
|
||||
const getChannelOption = () => new SlashCommandChannelOption().setName('owo').setDescription('Testing 123');
|
||||
const getRoleOption = () => new SlashCommandRoleOption().setName('owo').setDescription('Testing 123');
|
||||
const getAttachmentOption = () => new SlashCommandAttachmentOption().setName('owo').setDescription('Testing 123');
|
||||
const getMentionableOption = () => new SlashCommandMentionableOption().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommandGroup = () => new SlashCommandSubcommandGroupBuilder().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommand = () => new SlashCommandSubcommandBuilder().setName('owo').setDescription('Testing 123');
|
||||
|
||||
class Collection {
|
||||
public readonly [Symbol.toStringTag] = 'Map';
|
||||
}
|
||||
|
||||
describe('Slash Commands', () => {
|
||||
describe('Assertions tests', () => {
|
||||
test('GIVEN valid name THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateName('ping')).not.toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('hello-world_command')).not.toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('aˇ㐆1٢〣²अก')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateName(null)).toThrowError();
|
||||
|
||||
// Too short of a name
|
||||
expect(() => SlashCommandAssertions.validateName('')).toThrowError();
|
||||
|
||||
// Invalid characters used
|
||||
expect(() => SlashCommandAssertions.validateName('ABC')).toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('ABC123$%^&')).toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('help ping')).toThrowError();
|
||||
expect(() => SlashCommandAssertions.validateName('🦦')).toThrowError();
|
||||
|
||||
// Too long of a name
|
||||
expect(() =>
|
||||
SlashCommandAssertions.validateName('qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid description THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDescription('This is an OwO moment fur sure!~')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid description THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDescription(null)).toThrowError();
|
||||
|
||||
// Too short of a description
|
||||
expect(() => SlashCommandAssertions.validateDescription('')).toThrowError();
|
||||
|
||||
// Too long of a description
|
||||
expect(() =>
|
||||
SlashCommandAssertions.validateDescription(
|
||||
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Magnam autem libero expedita vitae accusamus nostrum ipsam tempore repudiandae deserunt ipsum facilis, velit fugiat facere accusantium, explicabo corporis aliquam non quos.',
|
||||
),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid default_permission THEN does not throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDefaultPermission(true)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid default_permission THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateDefaultPermission(null)).toThrowError();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('GIVEN invalid options or choices THEN throw error', () => {
|
||||
expect(() => SlashCommandAssertions.validateMaxOptionsLength(null)).toThrowError();
|
||||
|
||||
// Given an array that's too big
|
||||
expect(() => SlashCommandAssertions.validateMaxOptionsLength(largeArray)).toThrowError();
|
||||
|
||||
expect(() => SlashCommandAssertions.validateChoicesLength(1, largeArray)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid required parameters THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
SlashCommandAssertions.validateRequiredParameters(
|
||||
'owo',
|
||||
'My fancy command that totally exists, to test assertions',
|
||||
[],
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SlashCommandBuilder', () => {
|
||||
describe('Builder with no options', () => {
|
||||
test('GIVEN empty builder THEN throw error when calling toJSON', () => {
|
||||
expect(() => getBuilder().toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('example').setDescription('Example command').toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with simple options', () => {
|
||||
test('GIVEN valid builder THEN returns type included', () => {
|
||||
expect(getNamedBuilder().toJSON()).includes({ type: ApplicationCommandType.ChatInput });
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder()
|
||||
.setName('example')
|
||||
.setDescription('Example command')
|
||||
.setDMPermission(false)
|
||||
.addBooleanOption((boolean) =>
|
||||
boolean.setName('iscool').setDescription('Are we cool or what?').setRequired(true),
|
||||
)
|
||||
.addChannelOption((channel) => channel.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addMentionableOption((mentionable) => mentionable.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addRoleOption((role) => role.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addUserOption((user) => user.setName('iscool').setDescription('Are we cool or what?'))
|
||||
.addIntegerOption((integer) =>
|
||||
integer
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices({ name: 'Very cool', value: 1_000 })
|
||||
.addChoices([{ name: 'Even cooler', value: 2_000 }]),
|
||||
)
|
||||
.addNumberOption((number) =>
|
||||
number
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices({ name: 'Very cool', value: 1.5 })
|
||||
.addChoices([{ name: 'Even cooler', value: 2.5 }]),
|
||||
)
|
||||
.addStringOption((string) =>
|
||||
string
|
||||
.setName('iscool')
|
||||
.setDescription('Are we cool or what?')
|
||||
.addChoices({ name: 'Fancy Pants', value: 'fp_1' }, { name: 'Fancy Shoes', value: 'fs_1' })
|
||||
.addChoices([{ name: 'The Whole shebang', value: 'all' }]),
|
||||
)
|
||||
.addIntegerOption((integer) =>
|
||||
integer.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.addNumberOption((number) =>
|
||||
number.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.addStringOption((string) =>
|
||||
string.setName('iscool').setDescription('Are we cool or what?').setAutocomplete(true),
|
||||
)
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid autocomplete THEN does throw an error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addStringOption(getStringOption().setAutocomplete('not a boolean'))).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with both choices and autocomplete THEN does throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
getStringOption().setAutocomplete(true).addChoices({ name: 'Fancy Pants', value: 'fp_1' }),
|
||||
),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
getStringOption()
|
||||
.setAutocomplete(true)
|
||||
.addChoices(
|
||||
{ name: 'Fancy Pants', value: 'fp_1' },
|
||||
{ name: 'Fancy Shoes', value: 'fs_1' },
|
||||
{ name: 'The Whole shebang', value: 'all' },
|
||||
),
|
||||
),
|
||||
).toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
getStringOption().addChoices({ name: 'Fancy Pants', value: 'fp_1' }).setAutocomplete(true),
|
||||
),
|
||||
).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const option = getStringOption();
|
||||
Reflect.set(option, 'autocomplete', true);
|
||||
Reflect.set(option, 'choices', [{ name: 'Fancy Pants', value: 'fp_1' }]);
|
||||
return option.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const option = getNumberOption();
|
||||
Reflect.set(option, 'autocomplete', true);
|
||||
Reflect.set(option, 'choices', [{ name: 'Fancy Pants', value: 'fp_1' }]);
|
||||
return option.toJSON();
|
||||
}).toThrowError();
|
||||
|
||||
expect(() => {
|
||||
const option = getIntegerOption();
|
||||
Reflect.set(option, 'autocomplete', true);
|
||||
Reflect.set(option, 'choices', [{ name: 'Fancy Pants', value: 'fp_1' }]);
|
||||
return option.toJSON();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid channel options and channel_types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addChannelOption(
|
||||
getChannelOption().addChannelTypes(ChannelType.GuildText).addChannelTypes([ChannelType.GuildVoice]),
|
||||
),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
getBuilder().addChannelOption(
|
||||
getChannelOption().addChannelTypes(ChannelType.GuildAnnouncement, ChannelType.GuildText),
|
||||
);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid channel options and channel_types THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid channel type
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption().addChannelTypes(100))).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid channel types
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption().addChannelTypes(100, 200))).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid number min/max options THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid max value
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMaxValue('test'))).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid max value
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMaxValue('test'))).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid min value
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMinValue('test'))).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid min value
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMinValue('test'))).toThrowError();
|
||||
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMinValue(1.5))).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with valid number min/max options THEN does not throw an error', () => {
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMinValue(1))).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMinValue(1.5))).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption().setMaxValue(1))).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption().setMaxValue(1.5))).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an already built builder THEN does not throw an error', () => {
|
||||
expect(() => getBuilder().addStringOption(getStringOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addIntegerOption(getIntegerOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addNumberOption(getNumberOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addBooleanOption(getBooleanOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addUserOption(getUserOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addRoleOption(getRoleOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addAttachmentOption(getAttachmentOption())).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addMentionableOption(getMentionableOption())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN no valid return for an addOption method THEN throw error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(getRoleOption())).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name THEN throw error', () => {
|
||||
expect(() => getBuilder().setName('TEST_COMMAND')).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setName('ĂĂĂĂĂĂ')).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid names THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('hi_there')).not.toThrowError();
|
||||
|
||||
// Translation: a_command
|
||||
expect(() => getBuilder().setName('o_comandă')).not.toThrowError();
|
||||
|
||||
// Translation: thx (according to GTranslate)
|
||||
expect(() => getBuilder().setName('どうも')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid returns for builder THEN throw error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(true)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(null)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(undefined)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(() => SlashCommandStringOption)).toThrowError();
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addBooleanOption(() => new Collection())).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with defaultPermission false THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDescription('foo').setDefaultPermission(false)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option that is autocompletable and has choices, THEN passing nothing to setChoices should not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(getStringOption().setAutocomplete(true).setChoices()),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option that is autocompletable, THEN setting choices should throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(
|
||||
getStringOption().setAutocomplete(true).setChoices({ name: 'owo', value: 'uwu' }),
|
||||
),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an option, THEN setting choices should not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addStringOption(getStringOption().setChoices({ name: 'owo', value: 'uwu' })),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder with NSFW, THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setName('foo').setDescription('foo').setNSFW(true)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builder with subcommand (group) options', () => {
|
||||
test('GIVEN builder with subcommand group THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommandGroup((group) => group.setName('group').setDescription('Group us together!')),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with subcommand THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommand((subcommand) =>
|
||||
subcommand.setName('boop').setDescription('Boops a fellow nerd (you)'),
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with subcommand THEN has regular slash command fields', () => {
|
||||
expect(() =>
|
||||
getBuilder()
|
||||
.setName('name')
|
||||
.setDescription('description')
|
||||
.addSubcommand((option) => option.setName('ye').setDescription('ye'))
|
||||
.addSubcommand((option) => option.setName('no').setDescription('no'))
|
||||
.setDMPermission(false)
|
||||
.setDefaultMemberPermissions(1n),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand group THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().addSubcommandGroup(getSubcommandGroup())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand THEN does not throw error', () => {
|
||||
expect(() => getNamedBuilder().addSubcommand(getSubcommand())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with already built subcommand with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getNamedBuilder().addSubcommand(getSubcommand().addBooleanOption(getBooleanOption())),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN builder with a subcommand that tries to add an invalid result THEN throw error', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error: Checking if check works JS-side too
|
||||
getNamedBuilder().addSubcommand(getSubcommand()).addInteger(getInteger()),
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN no valid return for an addSubcommand(Group) method THEN throw error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addSubcommandGroup()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addSubcommand()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getBuilder().addSubcommand(getSubcommandGroup())).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subcommand group builder', () => {
|
||||
test('GIVEN no valid subcommand THEN throw error', () => {
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getSubcommandGroup().addSubcommand()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Checking if not providing anything, or an invalid return type causes an error
|
||||
expect(() => getSubcommandGroup().addSubcommand(getSubcommandGroup())).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a valid subcommand THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getSubcommandGroup()
|
||||
.addSubcommand((sub) => sub.setName('sub').setDescription('Testing 123'))
|
||||
.toJSON(),
|
||||
).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subcommand builder', () => {
|
||||
test('GIVEN a valid subcommand with options THEN does not throw error', () => {
|
||||
expect(() => getSubcommand().addBooleanOption(getBooleanOption()).toJSON()).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Slash command localizations', () => {
|
||||
const expectedSingleLocale = { 'en-US': 'foobar' };
|
||||
const expectedMultipleLocales = {
|
||||
...expectedSingleLocale,
|
||||
bg: 'test',
|
||||
};
|
||||
|
||||
test('GIVEN valid name localizations THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setNameLocalization('en-US', 'foobar')).not.toThrowError();
|
||||
expect(() => getBuilder().setNameLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid name localizations THEN does throw error', () => {
|
||||
// @ts-expect-error: Invalid localization
|
||||
expect(() => getBuilder().setNameLocalization('en-U', 'foobar')).toThrowError();
|
||||
// @ts-expect-error: Invalid localization
|
||||
expect(() => getBuilder().setNameLocalizations({ 'en-U': 'foobar' })).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid name localizations THEN valid data is stored', () => {
|
||||
expect(getBuilder().setNameLocalization('en-US', 'foobar').name_localizations).toEqual(expectedSingleLocale);
|
||||
expect(getBuilder().setNameLocalizations({ 'en-US': 'foobar', bg: 'test' }).name_localizations).toEqual(
|
||||
expectedMultipleLocales,
|
||||
);
|
||||
expect(getBuilder().setNameLocalizations(null).name_localizations).toBeNull();
|
||||
expect(getBuilder().setNameLocalization('en-US', null).name_localizations).toEqual({
|
||||
'en-US': null,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN valid description localizations THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setDescriptionLocalization('en-US', 'foobar')).not.toThrowError();
|
||||
expect(() => getBuilder().setDescriptionLocalizations({ 'en-US': 'foobar' })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid description localizations THEN does throw error', () => {
|
||||
// @ts-expect-error: Invalid localization description
|
||||
expect(() => getBuilder().setDescriptionLocalization('en-U', 'foobar')).toThrowError();
|
||||
// @ts-expect-error: Invalid localization description
|
||||
expect(() => getBuilder().setDescriptionLocalizations({ 'en-U': 'foobar' })).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid description localizations THEN valid data is stored', () => {
|
||||
expect(getBuilder().setDescriptionLocalization('en-US', 'foobar').description_localizations).toEqual(
|
||||
expectedSingleLocale,
|
||||
);
|
||||
expect(
|
||||
getBuilder().setDescriptionLocalizations({ 'en-US': 'foobar', bg: 'test' }).description_localizations,
|
||||
).toEqual(expectedMultipleLocales);
|
||||
expect(getBuilder().setDescriptionLocalizations(null).description_localizations).toBeNull();
|
||||
expect(getBuilder().setDescriptionLocalization('en-US', null).description_localizations).toEqual({
|
||||
'en-US': null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions', () => {
|
||||
test('GIVEN valid permission string THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setDefaultMemberPermissions('1')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid permission bitfield THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setDefaultMemberPermissions(PermissionFlagsBits.AddReactions | PermissionFlagsBits.AttachFiles),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN null permissions THEN does not throw error', () => {
|
||||
expect(() => getBuilder().setDefaultMemberPermissions(null)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid inputs THEN does throw error', () => {
|
||||
expect(() => getBuilder().setDefaultMemberPermissions('1.1')).toThrowError();
|
||||
|
||||
expect(() => getBuilder().setDefaultMemberPermissions(1.1)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid permission with options THEN does not throw error', () => {
|
||||
expect(() =>
|
||||
getBuilder().addBooleanOption(getBooleanOption()).setDefaultMemberPermissions('1'),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() => getBuilder().addChannelOption(getChannelOption()).setDMPermission(false)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('contexts', () => {
|
||||
test('GIVEN a builder with valid contexts THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setContexts([InteractionContextType.Guild, InteractionContextType.BotDM]),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().setContexts(InteractionContextType.Guild, InteractionContextType.BotDM),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid contexts THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts(999)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid contexts
|
||||
expect(() => getBuilder().setContexts([999, 998])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration types', () => {
|
||||
test('GIVEN a builder with valid integraton types THEN does not throw an error', () => {
|
||||
expect(() =>
|
||||
getBuilder().setIntegrationTypes([
|
||||
ApplicationIntegrationType.GuildInstall,
|
||||
ApplicationIntegrationType.UserInstall,
|
||||
]),
|
||||
).not.toThrowError();
|
||||
|
||||
expect(() =>
|
||||
getBuilder().setIntegrationTypes(
|
||||
ApplicationIntegrationType.GuildInstall,
|
||||
ApplicationIntegrationType.UserInstall,
|
||||
),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN a builder with invalid integration types THEN does throw an error', () => {
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes(999)).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid integration types
|
||||
expect(() => getBuilder().setIntegrationTypes([999, 998])).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,71 +1,21 @@
|
||||
import {
|
||||
ComponentType,
|
||||
TextInputStyle,
|
||||
type APIModalInteractionResponseCallbackData,
|
||||
type APITextInputComponent,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ComponentType, TextInputStyle, type APIModalInteractionResponseCallbackData } from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ModalBuilder,
|
||||
TextInputBuilder,
|
||||
type ModalActionRowComponentBuilder,
|
||||
} from '../../src/index.js';
|
||||
import {
|
||||
componentsValidator,
|
||||
titleValidator,
|
||||
validateRequiredParameters,
|
||||
} from '../../src/interactions/modals/Assertions.js';
|
||||
import { ActionRowBuilder, ModalBuilder, TextInputBuilder } from '../../src/index.js';
|
||||
|
||||
const modal = () => new ModalBuilder();
|
||||
const textInput = () =>
|
||||
new ActionRowBuilder().addTextInputComponent(
|
||||
new TextInputBuilder().setCustomId('text').setLabel(':3').setStyle(TextInputStyle.Short),
|
||||
);
|
||||
|
||||
describe('Modals', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid title THEN validator does not throw', () => {
|
||||
expect(() => titleValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid title THEN validator does throw', () => {
|
||||
expect(() => titleValidator.parse(42)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid components THEN validator does not throw', () => {
|
||||
expect(() => componentsValidator.parse([new ActionRowBuilder(), new ActionRowBuilder()])).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid components THEN validator does throw', () => {
|
||||
expect(() => componentsValidator.parse([new ButtonBuilder(), new TextInputBuilder()])).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid required parameters THEN validator does not throw', () => {
|
||||
expect(() =>
|
||||
validateRequiredParameters('123', 'title', [new ActionRowBuilder(), new ActionRowBuilder()]),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid required parameters THEN validator does throw', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error: Missing required parameter
|
||||
validateRequiredParameters('123', undefined, [new ActionRowBuilder(), new ButtonBuilder()]),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN valid fields THEN builder does not throw', () => {
|
||||
expect(() =>
|
||||
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();
|
||||
expect(() => modal().setTitle('test').setCustomId('foobar').setActionRows(textInput()).toJSON()).not.toThrowError();
|
||||
expect(() => modal().setTitle('test').setCustomId('foobar').addActionRows(textInput()).toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid fields THEN builder does throw', () => {
|
||||
expect(() => modal().setTitle('test').setCustomId('foobar').toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: CustomId is invalid
|
||||
expect(() => modal().setTitle('test').setCustomId(42).toJSON()).toThrowError();
|
||||
});
|
||||
@@ -106,68 +56,17 @@ describe('Modals', () => {
|
||||
modal()
|
||||
.setTitle(modalData.title)
|
||||
.setCustomId('custom id')
|
||||
.setComponents(
|
||||
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
|
||||
.setActionRows(
|
||||
new ActionRowBuilder().addTextInputComponent(
|
||||
new TextInputBuilder().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
|
||||
),
|
||||
)
|
||||
.addComponents([
|
||||
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
|
||||
.addActionRows([
|
||||
new ActionRowBuilder().addTextInputComponent(
|
||||
new TextInputBuilder().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
|
||||
),
|
||||
])
|
||||
.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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,16 @@ import { EmbedBuilder, embedLength } from '../../src/index.js';
|
||||
|
||||
const alpha = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
const dummy = {
|
||||
title: 'ooooo aaaaa uuuuuu aaaa',
|
||||
};
|
||||
|
||||
const base = {
|
||||
author: undefined,
|
||||
fields: [],
|
||||
footer: undefined,
|
||||
};
|
||||
|
||||
describe('Embed', () => {
|
||||
describe('Embed getters', () => {
|
||||
test('GIVEN an embed with specific amount of characters THEN returns amount of characters', () => {
|
||||
@@ -14,127 +24,136 @@ describe('Embed', () => {
|
||||
footer: { text: alpha },
|
||||
});
|
||||
|
||||
expect(embedLength(embed.data)).toEqual(alpha.length * 6);
|
||||
expect(embedLength(embed.toJSON())).toEqual(alpha.length * 6);
|
||||
});
|
||||
|
||||
test('GIVEN an embed with zero characters THEN returns amount of characters', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(embedLength(embed.data)).toEqual(0);
|
||||
expect(embedLength(embed.toJSON(false))).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed title', () => {
|
||||
test('GIVEN an embed with a pre-defined title THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ title: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ title: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, title: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setTitle THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setTitle('foo');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ title: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, title: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined title THEN unset title THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ title: 'foo' });
|
||||
embed.setTitle(null);
|
||||
const embed = new EmbedBuilder({ title: 'foo', description: ':3' });
|
||||
embed.clearTitle();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ title: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, description: ':3', title: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid title THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setTitle('a'.repeat(257))).toThrowError();
|
||||
embed.setTitle('a'.repeat(257));
|
||||
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed description', () => {
|
||||
test('GIVEN an embed with a pre-defined description THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ description: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ description: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setDescription THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setDescription('foo');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ description: 'foo' });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined description THEN unset description THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ description: 'foo' });
|
||||
embed.setDescription(null);
|
||||
const embed = new EmbedBuilder({ description: 'foo', ...dummy });
|
||||
embed.clearDescription();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ description: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, description: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid description THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setDescription('a'.repeat(4_097))).toThrowError();
|
||||
embed.setDescription('a'.repeat(4_097));
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed URL', () => {
|
||||
test('GIVEN an embed with a pre-defined url THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ url: 'https://discord.js.org/' });
|
||||
const embed = new EmbedBuilder({ url: 'https://discord.js.org/', ...dummy });
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
...dummy,
|
||||
url: 'https://discord.js.org/',
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setURL THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
const embed = new EmbedBuilder(dummy);
|
||||
embed.setURL('https://discord.js.org/');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
...dummy,
|
||||
url: 'https://discord.js.org/',
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined title THEN unset title THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ url: 'https://discord.js.org' });
|
||||
embed.setURL(null);
|
||||
const embed = new EmbedBuilder({ url: 'https://discord.js.org', ...dummy });
|
||||
embed.clearURL();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ url: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, url: undefined });
|
||||
});
|
||||
|
||||
test.each(['owo', 'discord://user'])('GIVEN an embed with an invalid URL THEN throws error', (input) => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setURL(input)).toThrowError();
|
||||
embed.setURL(input);
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Color', () => {
|
||||
test('GIVEN an embed with a pre-defined color THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ color: 0xff0000 });
|
||||
expect(embed.toJSON()).toStrictEqual({ color: 0xff0000 });
|
||||
const embed = new EmbedBuilder({ color: 0xff0000, ...dummy });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, color: 0xff0000 });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setColor THEN returns valid toJSON data', () => {
|
||||
expect(new EmbedBuilder().setColor(0xff0000).toJSON()).toStrictEqual({ color: 0xff0000 });
|
||||
expect(new EmbedBuilder().setColor([242, 66, 245]).toJSON()).toStrictEqual({ color: 0xf242f5 });
|
||||
expect(new EmbedBuilder(dummy).setColor(0xff0000).toJSON()).toStrictEqual({ ...base, ...dummy, color: 0xff0000 });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined color THEN unset color THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ color: 0xff0000 });
|
||||
embed.setColor(null);
|
||||
const embed = new EmbedBuilder({ ...dummy, color: 0xff0000 });
|
||||
embed.clearColor();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ color: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, color: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid color THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
// @ts-expect-error: Invalid color
|
||||
expect(() => embed.setColor('RED')).toThrowError();
|
||||
embed.setColor('RED');
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
|
||||
// @ts-expect-error: Invalid color
|
||||
expect(() => embed.setColor([42, 36])).toThrowError();
|
||||
expect(() => embed.setColor([42, 36, 1_000])).toThrowError();
|
||||
embed.setColor([42, 36]);
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -142,98 +161,92 @@ describe('Embed', () => {
|
||||
const now = new Date();
|
||||
|
||||
test('GIVEN an embed with a pre-defined timestamp THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ timestamp: now.toISOString() });
|
||||
expect(embed.toJSON()).toStrictEqual({ timestamp: now.toISOString() });
|
||||
const embed = new EmbedBuilder({ timestamp: now.toISOString(), ...dummy });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, timestamp: now.toISOString() });
|
||||
});
|
||||
|
||||
test('given an embed using Embed#setTimestamp (with Date) THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
test('GIVEN an embed using Embed#setTimestamp (with Date) THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder(dummy);
|
||||
embed.setTimestamp(now);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ timestamp: now.toISOString() });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, timestamp: now.toISOString() });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setTimestamp (with int) THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
const embed = new EmbedBuilder(dummy);
|
||||
embed.setTimestamp(now.getTime());
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ timestamp: now.toISOString() });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, timestamp: now.toISOString() });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setTimestamp (default) THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
const embed = new EmbedBuilder(dummy);
|
||||
embed.setTimestamp();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ timestamp: embed.data.timestamp });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, timestamp: embed.toJSON().timestamp });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined timestamp THEN unset timestamp THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ timestamp: now.toISOString() });
|
||||
embed.setTimestamp(null);
|
||||
const embed = new EmbedBuilder({ timestamp: now.toISOString(), ...dummy });
|
||||
embed.clearTimestamp();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ timestamp: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, timestamp: undefined });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Thumbnail', () => {
|
||||
test('GIVEN an embed with a pre-defined thumbnail THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ thumbnail: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
thumbnail: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, thumbnail: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setThumbnail THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setThumbnail('https://discord.js.org/static/logo.svg');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
thumbnail: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, thumbnail: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined thumbnail THEN unset thumbnail THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ thumbnail: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
embed.setThumbnail(null);
|
||||
const embed = new EmbedBuilder({ thumbnail: { url: 'https://discord.js.org/static/logo.svg' }, ...dummy });
|
||||
embed.clearThumbnail();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ thumbnail: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, thumbnail: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid thumbnail THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setThumbnail('owo')).toThrowError();
|
||||
embed.setThumbnail('owo');
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Embed Image', () => {
|
||||
test('GIVEN an embed with a pre-defined image THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ image: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
image: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, image: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setImage THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setImage('https://discord.js.org/static/logo.svg');
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
image: { url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, image: { url: 'https://discord.js.org/static/logo.svg' } });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined image THEN unset image THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({ image: { url: 'https://discord.js/org/static/logo.svg' } });
|
||||
embed.setImage(null);
|
||||
const embed = new EmbedBuilder({ image: { url: 'https://discord.js/org/static/logo.svg' }, ...dummy });
|
||||
embed.clearImage();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ image: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, image: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid image THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setImage('owo')).toThrowError();
|
||||
embed.setImage('owo');
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -243,19 +256,19 @@ describe('Embed', () => {
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setAuthor THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setAuthor({
|
||||
name: 'Wumpus',
|
||||
iconURL: 'https://discord.js.org/static/logo.svg',
|
||||
url: 'https://discord.js.org',
|
||||
});
|
||||
embed.setAuthor((author) =>
|
||||
author.setName('Wumpus').setIconURL('https://discord.js.org/static/logo.svg').setURL('https://discord.js.org'),
|
||||
);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
});
|
||||
});
|
||||
@@ -263,16 +276,18 @@ describe('Embed', () => {
|
||||
test('GIVEN an embed with a pre-defined author THEN unset author THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({
|
||||
author: { name: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg', url: 'https://discord.js.org' },
|
||||
...dummy,
|
||||
});
|
||||
embed.setAuthor(null);
|
||||
embed.clearAuthor();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ author: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, author: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with an invalid author name THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setAuthor({ name: 'a'.repeat(257) })).toThrowError();
|
||||
embed.setAuthor({ name: 'a'.repeat(257) });
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -282,32 +297,36 @@ describe('Embed', () => {
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setAuthor THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setFooter({ text: 'Wumpus', iconURL: 'https://discord.js.org/static/logo.svg' });
|
||||
embed.setFooter({ text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' });
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN an embed with a pre-defined footer THEN unset footer THEN return valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder({
|
||||
...dummy,
|
||||
footer: { text: 'Wumpus', icon_url: 'https://discord.js.org/static/logo.svg' },
|
||||
});
|
||||
embed.setFooter(null);
|
||||
embed.clearFooter();
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({ footer: undefined });
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, ...dummy, footer: undefined });
|
||||
});
|
||||
|
||||
test('GIVEN an embed with invalid footer text THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.setFooter({ text: 'a'.repeat(2_049) })).toThrowError();
|
||||
embed.setFooter({ text: 'a'.repeat(2_049) });
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -316,9 +335,7 @@ describe('Embed', () => {
|
||||
const embed = new EmbedBuilder({
|
||||
fields: [{ name: 'foo', value: 'bar' }],
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
fields: [{ name: 'foo', value: 'bar' }],
|
||||
});
|
||||
expect(embed.toJSON()).toStrictEqual({ ...base, fields: [{ name: 'foo', value: 'bar' }] });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
|
||||
@@ -327,6 +344,7 @@ describe('Embed', () => {
|
||||
embed.addFields([{ name: 'foo', value: 'bar' }]);
|
||||
|
||||
expect(embed.toJSON()).toStrictEqual({
|
||||
...base,
|
||||
fields: [
|
||||
{ name: 'foo', value: 'bar' },
|
||||
{ name: 'foo', value: 'bar' },
|
||||
@@ -338,56 +356,51 @@ describe('Embed', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.addFields({ name: 'foo', value: 'bar' }, { name: 'foo', value: 'baz' });
|
||||
|
||||
expect(embed.spliceFields(0, 1).toJSON()).toStrictEqual({
|
||||
fields: [{ name: 'foo', value: 'baz' }],
|
||||
});
|
||||
expect(embed.spliceFields(0, 1).toJSON()).toStrictEqual({ ...base, fields: [{ name: 'foo', value: 'baz' }] });
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data 2', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
|
||||
expect(() =>
|
||||
embed.spliceFields(0, 3, ...Array.from({ length: 5 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).not.toThrowError();
|
||||
embed.spliceFields(0, 3, ...Array.from({ length: 5 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#spliceFields that adds additional fields resulting in fields > 25 THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
|
||||
expect(() =>
|
||||
embed.spliceFields(0, 3, ...Array.from({ length: 8 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
embed.spliceFields(0, 3, ...Array.from({ length: 8 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setFields THEN returns valid toJSON data', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() =>
|
||||
embed.setFields(...Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).not.toThrowError();
|
||||
expect(() =>
|
||||
embed.setFields(Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).not.toThrowError();
|
||||
embed.setFields(...Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).not.toThrowError();
|
||||
|
||||
embed.setFields(Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN an embed using Embed#setFields that sets more than 25 fields THEN throws error', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() =>
|
||||
embed.setFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
|
||||
embed.setFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
|
||||
embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
describe('GIVEN invalid field amount THEN throws error', () => {
|
||||
test('1', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() =>
|
||||
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
|
||||
).toThrowError();
|
||||
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })));
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -395,7 +408,8 @@ describe('Embed', () => {
|
||||
test('2', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
|
||||
embed.addFields({ name: '', value: 'bar' });
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,7 +417,8 @@ describe('Embed', () => {
|
||||
test('3', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
|
||||
embed.addFields({ name: 'a'.repeat(257), value: 'bar' });
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -411,7 +426,8 @@ describe('Embed', () => {
|
||||
test('4', () => {
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1_025) })).toThrowError();
|
||||
embed.addFields({ name: '', value: 'a'.repeat(1_025) });
|
||||
expect(() => embed.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { expectTypeOf } from 'vitest';
|
||||
import { SlashCommandBuilder, SlashCommandStringOption, SlashCommandSubcommandBuilder } from '../src/index.js';
|
||||
import {
|
||||
ChatInputCommandBuilder,
|
||||
ChatInputCommandStringOption,
|
||||
ChatInputCommandSubcommandBuilder,
|
||||
} from '../src/index.js';
|
||||
|
||||
const getBuilder = () => new SlashCommandBuilder();
|
||||
const getStringOption = () => new SlashCommandStringOption().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommand = () => new SlashCommandSubcommandBuilder().setName('owo').setDescription('Testing 123');
|
||||
const getBuilder = () => new ChatInputCommandBuilder();
|
||||
const getStringOption = () => new ChatInputCommandStringOption().setName('owo').setDescription('Testing 123');
|
||||
const getSubcommand = () => new ChatInputCommandSubcommandBuilder().setName('owo').setDescription('Testing 123');
|
||||
|
||||
type BuilderPropsOnly<Type = SlashCommandBuilder> = Pick<
|
||||
type BuilderPropsOnly<Type = ChatInputCommandBuilder> = Pick<
|
||||
Type,
|
||||
keyof {
|
||||
[Key in keyof Type as Type[Key] extends (...args: any) => any ? never : Key]: any;
|
||||
|
||||
Reference in New Issue
Block a user