mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-15 19:13:31 +01:00
feat: components v2 in builders (#10788)
* feat: thumbnail component * chore: just a temp file to track remaining components * feat: file component * feat: section component * feat: text display component * chore: bump alpha version of dtypes * chore: simplify ComponentBuilder base type * feat: MediaGallery * feat: Section builder * chore: tests for sections * chore: forgot you * chore: docs * fix: missing comma * fix: my bad * feat: container builder * chore: requested changes * chore: missed u * chore: type tests * chore: setId/clearId * chore: apply suggestions from code review * chore: unify pick * chore: some requested changes * chore: tests and small fixes * chore: added tests that need fixing * fix: tests * chore: cleanup on isle protected * docs: remove locale * chore: types for new message builder * chore: fix tests * chore: attempt 1 at message builder assertions * chore: apply suggestions * Update packages/builders/src/messages/Assertions.ts Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * Update packages/builders/src/components/v2/Thumbnail.ts Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * fix: tests * chore: fmt * Apply suggestions from code review Co-authored-by: Denis-Adrian Cristea <didinele.dev@gmail.com> * chore: fix pnpm lockfile revert --------- Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Denis-Adrian Cristea <didinele.dev@gmail.com>
This commit is contained in:
@@ -3,10 +3,10 @@ import {
|
||||
ComponentType,
|
||||
TextInputStyle,
|
||||
type APIButtonComponent,
|
||||
type APIComponentInMessageActionRow,
|
||||
type APISelectMenuComponent,
|
||||
type APITextInputComponent,
|
||||
type APIActionRowComponent,
|
||||
type APIComponentInMessageActionRow,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
|
||||
237
packages/builders/__tests__/components/v2/container.test.ts
Normal file
237
packages/builders/__tests__/components/v2/container.test.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import {
|
||||
type APIActionRowComponent,
|
||||
type APIButtonComponent,
|
||||
type APIContainerComponent,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
SeparatorSpacingSize,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { createComponentBuilder } from '../../../src/components/Components.js';
|
||||
import { ContainerBuilder } from '../../../src/components/v2/Container.js';
|
||||
import { SeparatorBuilder } from '../../../src/components/v2/Separator.js';
|
||||
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay.js';
|
||||
import { MediaGalleryBuilder, SectionBuilder } from '../../../src/index.js';
|
||||
|
||||
const containerWithTextDisplay: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 123,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const button = {
|
||||
type: ComponentType.Button as const,
|
||||
style: ButtonStyle.Primary as const,
|
||||
custom_id: 'test',
|
||||
label: 'test',
|
||||
};
|
||||
|
||||
const actionRow: APIActionRowComponent<APIButtonComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [button],
|
||||
};
|
||||
|
||||
const containerWithSeparatorData: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
id: 1_234,
|
||||
spacing: SeparatorSpacingSize.Small,
|
||||
divider: false,
|
||||
},
|
||||
],
|
||||
accent_color: 0x00ff00,
|
||||
};
|
||||
|
||||
const containerWithSeparatorDataNoColor: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
id: 1_234,
|
||||
spacing: SeparatorSpacingSize.Small,
|
||||
divider: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('Container Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid components THEN do not throw', () => {
|
||||
expect(() => new ContainerBuilder().addSeparatorComponents(new SeparatorBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().spliceComponents(0, 0, new SeparatorBuilder())).not.toThrowError();
|
||||
expect(() => new ContainerBuilder().addSeparatorComponents([new SeparatorBuilder()])).not.toThrowError();
|
||||
expect(() =>
|
||||
new ContainerBuilder().spliceComponents(0, 0, [{ type: ComponentType.Separator }]),
|
||||
).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const containerData: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
spacing: SeparatorSpacingSize.Large,
|
||||
divider: true,
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
type: ComponentType.File,
|
||||
file: {
|
||||
url: 'attachment://file.png',
|
||||
},
|
||||
spoiler: false,
|
||||
},
|
||||
],
|
||||
accent_color: 0xff00ff,
|
||||
spoiler: true,
|
||||
};
|
||||
|
||||
expect(new ContainerBuilder(containerData).toJSON()).toEqual(containerData);
|
||||
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const containerWithTextDisplay: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
id: 123,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const containerWithSeparatorData: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Separator,
|
||||
id: 1_234,
|
||||
spacing: SeparatorSpacingSize.Small,
|
||||
divider: false,
|
||||
},
|
||||
],
|
||||
accent_color: 0x00ff00,
|
||||
};
|
||||
|
||||
expect(new ContainerBuilder(containerWithTextDisplay).toJSON()).toEqual(containerWithTextDisplay);
|
||||
expect(new ContainerBuilder(containerWithSeparatorData).toJSON()).toEqual(containerWithSeparatorData);
|
||||
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
|
||||
const textDisplay = new TextDisplayBuilder().setContent('test').setId(123);
|
||||
const separator = new SeparatorBuilder().setId(1_234).setSpacing(SeparatorSpacingSize.Small).setDivider(false);
|
||||
|
||||
expect(new ContainerBuilder().addTextDisplayComponents(textDisplay).toJSON()).toEqual(containerWithTextDisplay);
|
||||
expect(new ContainerBuilder().addSeparatorComponents(separator).toJSON()).toEqual(
|
||||
containerWithSeparatorDataNoColor,
|
||||
);
|
||||
expect(new ContainerBuilder().addTextDisplayComponents([textDisplay]).toJSON()).toEqual(containerWithTextDisplay);
|
||||
expect(new ContainerBuilder().addSeparatorComponents([separator]).toJSON()).toEqual(
|
||||
containerWithSeparatorDataNoColor,
|
||||
);
|
||||
});
|
||||
|
||||
test('GIVEN valid accent color THEN valid JSON output is given', () => {
|
||||
expect(
|
||||
new ContainerBuilder({
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
})
|
||||
.setAccentColor(0xff00ff)
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accent_color: 0xff00ff,
|
||||
});
|
||||
expect(new ContainerBuilder(containerWithSeparatorData).clearAccentColor().toJSON()).toEqual(
|
||||
containerWithSeparatorDataNoColor,
|
||||
);
|
||||
});
|
||||
|
||||
test('GIVEN valid method parameters THEN valid JSON is given', () => {
|
||||
expect(
|
||||
new ContainerBuilder()
|
||||
.addMediaGalleryComponents(
|
||||
new MediaGalleryBuilder()
|
||||
.addItems({ media: { url: 'https://discord.com' } })
|
||||
.setId(3)
|
||||
.clearId(),
|
||||
)
|
||||
.setSpoiler()
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [{ media: { url: 'https://discord.com' } }],
|
||||
},
|
||||
],
|
||||
spoiler: true,
|
||||
});
|
||||
expect(
|
||||
new ContainerBuilder()
|
||||
.addSectionComponents(
|
||||
new SectionBuilder()
|
||||
.addTextDisplayComponents({ type: ComponentType.TextDisplay, content: 'test' })
|
||||
.setPrimaryButtonAccessory(button),
|
||||
)
|
||||
.addFileComponents({ type: ComponentType.File, file: { url: 'attachment://discord.png' } })
|
||||
.setSpoiler(false)
|
||||
.setId(5)
|
||||
.toJSON(),
|
||||
).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'test',
|
||||
},
|
||||
],
|
||||
accessory: button,
|
||||
},
|
||||
{
|
||||
type: ComponentType.File,
|
||||
file: { url: 'attachment://discord.png' },
|
||||
},
|
||||
],
|
||||
spoiler: false,
|
||||
id: 5,
|
||||
});
|
||||
expect(new ContainerBuilder().addActionRowComponents(actionRow).setSpoiler(true).toJSON()).toEqual({
|
||||
type: ComponentType.Container,
|
||||
components: [actionRow],
|
||||
spoiler: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
45
packages/builders/__tests__/components/v2/file.test.ts
Normal file
45
packages/builders/__tests__/components/v2/file.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { FileBuilder } from '../../../src/components/v2/File';
|
||||
|
||||
const dummy = {
|
||||
type: ComponentType.File as const,
|
||||
file: { url: 'attachment://owo.png' },
|
||||
};
|
||||
|
||||
describe('File', () => {
|
||||
describe('File url', () => {
|
||||
test('GIVEN a file with a pre-defined url THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder({ file: { url: 'attachment://owo.png' } });
|
||||
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://owo.png' } });
|
||||
});
|
||||
|
||||
test('GIVEN a file using File#setURL THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder();
|
||||
file.setURL('attachment://uwu.png');
|
||||
|
||||
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://uwu.png' } });
|
||||
});
|
||||
|
||||
test('GIVEN a file with an invalid url THEN throws error', () => {
|
||||
const file = new FileBuilder();
|
||||
file.setURL('https://google.com');
|
||||
|
||||
expect(() => file.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('File spoiler', () => {
|
||||
test('GIVEN a file with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder({ ...dummy, spoiler: true });
|
||||
expect(file.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||
});
|
||||
|
||||
test('GIVEN a file using File#setSpoiler THEN return valid toJSON data', () => {
|
||||
const file = new FileBuilder({ ...dummy });
|
||||
file.setSpoiler(false);
|
||||
|
||||
expect(file.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
117
packages/builders/__tests__/components/v2/mediaGallery.test.ts
Normal file
117
packages/builders/__tests__/components/v2/mediaGallery.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { MediaGalleryBuilder } from '../../../src/components/v2/MediaGallery';
|
||||
import { MediaGalleryItemBuilder } from '../../../src/components/v2/MediaGalleryItem';
|
||||
|
||||
describe('MediaGallery', () => {
|
||||
test('GIVEN an empty media gallery THEN throws error', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
expect(() => gallery.toJSON()).toThrow();
|
||||
});
|
||||
|
||||
describe('MediaGallery items', () => {
|
||||
test('GIVEN a media gallery with pre-defined items THEN return valid toJSON data', () => {
|
||||
const items = [
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.com' }, description: 'Discord' },
|
||||
];
|
||||
|
||||
const gallery = new MediaGalleryBuilder({
|
||||
type: ComponentType.MediaGallery,
|
||||
items,
|
||||
});
|
||||
|
||||
expect(gallery.toJSON()).toEqual({
|
||||
type: ComponentType.MediaGallery,
|
||||
items,
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery with items added via addItems THEN return valid toJSON data', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
const item1 = new MediaGalleryItemBuilder().setURL('https://google.com');
|
||||
const item2 = new MediaGalleryItemBuilder().setURL('https://discord.com').setDescription('Discord');
|
||||
|
||||
gallery.addItems(item1, item2);
|
||||
|
||||
expect(gallery.toJSON()).toEqual({
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.com' }, description: 'Discord' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery with items added via addItems with raw objects THEN return valid toJSON data', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
|
||||
gallery.addItems(
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.com' }, description: 'Discord' },
|
||||
);
|
||||
|
||||
expect(gallery.toJSON()).toEqual({
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.com' }, description: 'Discord' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery with items added via addItems with builder functions THEN return valid toJSON data', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
|
||||
gallery.addItems(
|
||||
(builder) => builder.setURL('https://google.com'),
|
||||
(builder) => builder.setURL('https://discord.com').setDescription('Discord'),
|
||||
);
|
||||
|
||||
expect(gallery.toJSON()).toEqual({
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.com' }, description: 'Discord' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery with array of items passed to addItems THEN return valid toJSON data', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
const items = [
|
||||
new MediaGalleryItemBuilder().setURL('https://google.com'),
|
||||
new MediaGalleryItemBuilder().setURL('https://discord.com').setDescription('Discord'),
|
||||
];
|
||||
|
||||
gallery.addItems(items);
|
||||
|
||||
expect(gallery.toJSON()).toEqual({
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.com' }, description: 'Discord' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery with items added via addItems with builder functions THEN return valid toJSON data', () => {
|
||||
const gallery = new MediaGalleryBuilder();
|
||||
|
||||
gallery
|
||||
.addItems(
|
||||
new MediaGalleryItemBuilder().setURL('https://google.com'),
|
||||
new MediaGalleryItemBuilder().setURL('https://discord.com').setDescription('Discord'),
|
||||
)
|
||||
.spliceItems(1, 1, new MediaGalleryItemBuilder().setURL('https://discord.js.org').setDescription('Discord.JS'));
|
||||
|
||||
expect(gallery.toJSON()).toEqual({
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{ media: { url: 'https://google.com' } },
|
||||
{ media: { url: 'https://discord.js.org' }, description: 'Discord.JS' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { MediaGalleryItemBuilder } from '../../../src/components/v2/MediaGalleryItem';
|
||||
|
||||
const dummy = {
|
||||
media: { url: 'https://google.com' },
|
||||
};
|
||||
|
||||
describe('MediaGalleryItem', () => {
|
||||
describe('MediaGalleryItem url', () => {
|
||||
test('GIVEN a media gallery item with a pre-defined url THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder({ media: { url: 'https://google.com' } });
|
||||
expect(item.toJSON()).toEqual({ media: { url: 'https://google.com' } });
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery item with a set url THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder().setURL('https://google.com');
|
||||
expect(item.toJSON()).toEqual({ media: { url: 'https://google.com' } });
|
||||
});
|
||||
|
||||
test.each(['owo', 'discord://user'])(
|
||||
'GIVEN a media gallery item with an invalid URL (%s) THEN throws error',
|
||||
(input) => {
|
||||
const item = new MediaGalleryItemBuilder();
|
||||
|
||||
item.setURL(input);
|
||||
expect(() => item.toJSON()).toThrowError();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('MediaGalleryItem description', () => {
|
||||
test('GIVEN a media gallery item with a pre-defined description THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder({ ...dummy, description: 'foo' });
|
||||
expect(item.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery item with a set description THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder({ ...dummy });
|
||||
item.setDescription('foo');
|
||||
|
||||
expect(item.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery item with a pre-defined description THEN unset description THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder({ description: 'foo', ...dummy });
|
||||
item.clearDescription();
|
||||
|
||||
expect(item.toJSON()).toEqual({ ...dummy });
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery item with an invalid description THEN throws error', () => {
|
||||
const item = new MediaGalleryItemBuilder();
|
||||
|
||||
item.setDescription('a'.repeat(1_025));
|
||||
expect(() => item.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('MediaGalleryItem spoiler', () => {
|
||||
test('GIVEN a media gallery item with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder({ ...dummy, spoiler: true });
|
||||
expect(item.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||
});
|
||||
|
||||
test('GIVEN a media gallery item with a set spoiler status THEN return valid toJSON data', () => {
|
||||
const item = new MediaGalleryItemBuilder({ ...dummy });
|
||||
item.setSpoiler(false);
|
||||
|
||||
expect(item.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
166
packages/builders/__tests__/components/v2/section.test.ts
Normal file
166
packages/builders/__tests__/components/v2/section.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { ButtonStyle, ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { PrimaryButtonBuilder } from '../../../src/components/button/CustomIdButton';
|
||||
import { SectionBuilder } from '../../../src/components/v2/Section';
|
||||
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay';
|
||||
import { ThumbnailBuilder } from '../../../src/components/v2/Thumbnail';
|
||||
|
||||
describe('Section', () => {
|
||||
describe('Validation', () => {
|
||||
test('GIVEN empty section builder THEN throws error on toJSON', () => {
|
||||
const section = new SectionBuilder();
|
||||
expect(() => section.toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN section with text components but no accessory THEN throws error on toJSON', () => {
|
||||
const section = new SectionBuilder().addTextDisplayComponents(new TextDisplayBuilder().setContent('Hello world'));
|
||||
expect(() => section.toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN section with accessory but no text components THEN throws error on toJSON', () => {
|
||||
const section = new SectionBuilder().setThumbnailAccessory(
|
||||
new ThumbnailBuilder().setURL('https://example.com/image.png'),
|
||||
);
|
||||
expect(() => section.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Text display components', () => {
|
||||
test('GIVEN section with predefined text components THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder({
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://example.com/image.png' } },
|
||||
});
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://example.com/image.png' } },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN section with added text components THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(new TextDisplayBuilder().setContent('Hello world'))
|
||||
.setThumbnailAccessory(new ThumbnailBuilder().setURL('https://example.com/image.png'));
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://example.com/image.png' } },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN section with multiple text components THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(
|
||||
new TextDisplayBuilder().setContent('Line 1'),
|
||||
new TextDisplayBuilder().setContent('Line 2'),
|
||||
new TextDisplayBuilder().setContent('Line 3'),
|
||||
)
|
||||
.setThumbnailAccessory(new ThumbnailBuilder().setURL('https://example.com/image.png'));
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{ type: ComponentType.TextDisplay, content: 'Line 1' },
|
||||
{ type: ComponentType.TextDisplay, content: 'Line 2' },
|
||||
{ type: ComponentType.TextDisplay, content: 'Line 3' },
|
||||
],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://example.com/image.png' } },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN section with spliced text components THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(
|
||||
new TextDisplayBuilder().setContent('Original 1'),
|
||||
new TextDisplayBuilder().setContent('Will be removed'),
|
||||
new TextDisplayBuilder().setContent('Original 3'),
|
||||
)
|
||||
.spliceTextDisplayComponents(1, 1, new TextDisplayBuilder().setContent('Replacement'))
|
||||
.setThumbnailAccessory(new ThumbnailBuilder().setURL('https://example.com/image.png'));
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [
|
||||
{ type: ComponentType.TextDisplay, content: 'Original 1' },
|
||||
{ type: ComponentType.TextDisplay, content: 'Replacement' },
|
||||
{ type: ComponentType.TextDisplay, content: 'Original 3' },
|
||||
],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://example.com/image.png' } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessory components', () => {
|
||||
test('GIVEN section with thumbnail accessory THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(new TextDisplayBuilder().setContent('Hello world'))
|
||||
.setThumbnailAccessory(new ThumbnailBuilder().setURL('https://example.com/image.png'));
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: { type: ComponentType.Thumbnail, media: { url: 'https://example.com/image.png' } },
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN section with primary button accessory THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(new TextDisplayBuilder().setContent('Hello world'))
|
||||
.setPrimaryButtonAccessory(new PrimaryButtonBuilder().setCustomId('click_me').setLabel('Click me'));
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: {
|
||||
type: ComponentType.Button,
|
||||
style: 1,
|
||||
custom_id: 'click_me',
|
||||
label: 'Click me',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN section with primary button accessory JSON THEN returns valid toJSON data', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(new TextDisplayBuilder().setContent('Hello world'))
|
||||
.setPrimaryButtonAccessory({
|
||||
type: ComponentType.Button,
|
||||
style: ButtonStyle.Primary,
|
||||
custom_id: 'click_me',
|
||||
label: 'Click me',
|
||||
});
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: {
|
||||
type: ComponentType.Button,
|
||||
style: 1,
|
||||
custom_id: 'click_me',
|
||||
label: 'Click me',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN changing accessory type THEN returns the latest accessory in toJSON', () => {
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(new TextDisplayBuilder().setContent('Hello world'))
|
||||
.setThumbnailAccessory(new ThumbnailBuilder().setURL('https://example.com/image.png'))
|
||||
.setPrimaryButtonAccessory(new PrimaryButtonBuilder().setCustomId('click_me').setLabel('Click me'));
|
||||
|
||||
expect(section.toJSON()).toEqual({
|
||||
type: ComponentType.Section,
|
||||
components: [{ type: ComponentType.TextDisplay, content: 'Hello world' }],
|
||||
accessory: {
|
||||
type: ComponentType.Button,
|
||||
style: 1,
|
||||
custom_id: 'click_me',
|
||||
label: 'Click me',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
35
packages/builders/__tests__/components/v2/separator.test.ts
Normal file
35
packages/builders/__tests__/components/v2/separator.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { SeparatorBuilder } from '../../../src/components/v2/Separator';
|
||||
|
||||
describe('Separator', () => {
|
||||
describe('Divider', () => {
|
||||
test('GIVEN a separator with a pre-defined divider THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder({ divider: true });
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, divider: true });
|
||||
});
|
||||
|
||||
test('GIVEN a separator with a set divider THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder().setDivider(false);
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, divider: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Spacing', () => {
|
||||
test('GIVEN a separator with a pre-defined spacing THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder({ spacing: SeparatorSpacingSize.Small });
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, spacing: SeparatorSpacingSize.Small });
|
||||
});
|
||||
|
||||
test('GIVEN a separator with a set spacing THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder().setSpacing(SeparatorSpacingSize.Large);
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator, spacing: SeparatorSpacingSize.Large });
|
||||
});
|
||||
|
||||
test('GIVEN a separator with a set spacing THEN clear spacing THEN return valid toJSON data', () => {
|
||||
const separator = new SeparatorBuilder({ spacing: SeparatorSpacingSize.Small });
|
||||
separator.clearSpacing();
|
||||
expect(separator.toJSON()).toEqual({ type: ComponentType.Separator });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay';
|
||||
|
||||
describe('TextDisplay', () => {
|
||||
describe('TextDisplay content', () => {
|
||||
test('GIVEN a text display with a pre-defined content THEN return valid toJSON data', () => {
|
||||
const textDisplay = new TextDisplayBuilder({ content: 'foo' });
|
||||
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a text display with a set content THEN return valid toJSON data', () => {
|
||||
const textDisplay = new TextDisplayBuilder().setContent('foo');
|
||||
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a text display with a pre-defined content THEN overwritten content THEN return valid toJSON data', () => {
|
||||
const textDisplay = new TextDisplayBuilder({ content: 'foo' });
|
||||
textDisplay.setContent('bar');
|
||||
expect(textDisplay.toJSON()).toEqual({ type: ComponentType.TextDisplay, content: 'bar' });
|
||||
});
|
||||
});
|
||||
});
|
||||
71
packages/builders/__tests__/components/v2/thumbnail.test.ts
Normal file
71
packages/builders/__tests__/components/v2/thumbnail.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { ComponentType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { ThumbnailBuilder } from '../../../src/components/v2/Thumbnail';
|
||||
|
||||
const dummy = {
|
||||
type: ComponentType.Thumbnail as const,
|
||||
media: { url: 'https://google.com' },
|
||||
};
|
||||
|
||||
describe('Thumbnail', () => {
|
||||
describe('Thumbnail url', () => {
|
||||
test('GIVEN a thumbnail with a pre-defined url THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ media: { url: 'https://google.com' } });
|
||||
expect(thumbnail.toJSON()).toEqual({ type: ComponentType.Thumbnail, media: { url: 'https://google.com' } });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a set url THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder().setURL('https://google.com');
|
||||
expect(thumbnail.toJSON()).toEqual({ type: ComponentType.Thumbnail, media: { url: 'https://google.com' } });
|
||||
});
|
||||
|
||||
test.each(['owo', 'discord://user'])('GIVEN an embed with an invalid URL (%s) THEN throws error', (input) => {
|
||||
const thumbnail = new ThumbnailBuilder();
|
||||
|
||||
thumbnail.setURL(input);
|
||||
expect(() => thumbnail.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Thumbnail description', () => {
|
||||
test('GIVEN a thumbnail with a pre-defined description THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy, description: 'foo' });
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a set description THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy });
|
||||
thumbnail.setDescription('foo');
|
||||
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, description: 'foo' });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a pre-defined description THEN unset description THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ description: 'foo', ...dummy });
|
||||
thumbnail.clearDescription();
|
||||
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with an invalid description THEN throws error', () => {
|
||||
const thumbnail = new ThumbnailBuilder();
|
||||
|
||||
thumbnail.setDescription('a'.repeat(1_025));
|
||||
expect(() => thumbnail.toJSON()).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Thumbnail spoiler', () => {
|
||||
test('GIVEN a thumbnail with a pre-defined spoiler status THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy, spoiler: true });
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, spoiler: true });
|
||||
});
|
||||
|
||||
test('GIVEN a thumbnail with a set spoiler status THEN return valid toJSON data', () => {
|
||||
const thumbnail = new ThumbnailBuilder({ ...dummy });
|
||||
thumbnail.setSpoiler(false);
|
||||
|
||||
expect(thumbnail.toJSON()).toEqual({ ...dummy, spoiler: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,7 +18,7 @@ describe('Message', () => {
|
||||
});
|
||||
|
||||
test('GIVEN bad action row THEN it throws', () => {
|
||||
const message = new MessageBuilder().setComponents((row) =>
|
||||
const message = new MessageBuilder().addActionRowComponents((row) =>
|
||||
row.addTextInputComponent((input) => input.setCustomId('abc').setLabel('def')),
|
||||
);
|
||||
expect(() => message.toJSON()).toThrow();
|
||||
@@ -32,7 +32,9 @@ describe('Message', () => {
|
||||
.addEmbeds(new EmbedBuilder().setTitle('foo').setDescription('bar'))
|
||||
.setAllowedMentions({ parse: [AllowedMentionsTypes.Role], roles: ['123'] })
|
||||
.setMessageReference({ channel_id: '123', message_id: '123' })
|
||||
.setComponents((row) => row.addPrimaryButtonComponents((button) => button.setCustomId('abc').setLabel('def')))
|
||||
.addActionRowComponents((row) =>
|
||||
row.addPrimaryButtonComponents((button) => button.setCustomId('abc').setLabel('def')),
|
||||
)
|
||||
.setStickerIds('123', '456')
|
||||
.addAttachments((attachment) => attachment.setId('hi!').setFilename('abc'))
|
||||
.setFlags(MessageFlags.Ephemeral)
|
||||
|
||||
Reference in New Issue
Block a user