Skip to content

feat: components v2 in builders #10788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4891f73
feat: thumbnail component
vladfrangu Feb 16, 2025
c738208
chore: just a temp file to track remaining components
vladfrangu Feb 16, 2025
e22ce45
feat: file component
vladfrangu Feb 16, 2025
d00f2eb
feat: section component
vladfrangu Feb 16, 2025
1a1986c
feat: text display component
vladfrangu Feb 16, 2025
96f3c9f
chore: bump alpha version of dtypes
vladfrangu Feb 23, 2025
08925d6
chore: simplify ComponentBuilder base type
vladfrangu Feb 23, 2025
0cffc3b
feat: MediaGallery
vladfrangu Mar 3, 2025
b14061e
feat: Section builder
vladfrangu Mar 3, 2025
2a2c4b3
chore: tests for sections
vladfrangu Mar 3, 2025
2775989
chore: forgot you
vladfrangu Mar 3, 2025
af46cb6
chore: docs
Qjuh Mar 3, 2025
7456d29
fix: missing comma
Qjuh Mar 3, 2025
c27e2a8
fix: my bad
Qjuh Mar 3, 2025
08fcd93
feat: container builder
vladfrangu Mar 4, 2025
7ed5bba
chore: requested changes
vladfrangu Mar 4, 2025
86b12c3
chore: missed u
vladfrangu Mar 4, 2025
a78648c
chore: type tests
vladfrangu Mar 4, 2025
0e176dc
chore: setId/clearId
vladfrangu Mar 4, 2025
ea1d596
chore: apply suggestions from code review
Qjuh Mar 5, 2025
22cd699
chore: unify pick
vladfrangu Mar 5, 2025
575f95a
chore: some requested changes
vladfrangu Mar 5, 2025
5abf31f
chore: tests and small fixes
Qjuh Mar 11, 2025
f340352
chore: added tests that need fixing
Qjuh Mar 11, 2025
fbb5fd1
fix: tests
Qjuh Mar 11, 2025
108da86
chore: cleanup on isle protected
vladfrangu Mar 14, 2025
ca097a7
docs: remove locale
Jiralite Mar 14, 2025
3fe62a2
chore: types for new message builder
vladfrangu Mar 14, 2025
9b3a7ee
chore: fix tests
vladfrangu Mar 14, 2025
3fda09d
chore: attempt 1 at message builder assertions
vladfrangu Mar 14, 2025
2bb20b2
chore: apply suggestions
Qjuh Mar 17, 2025
1445b1a
Update packages/builders/src/messages/Assertions.ts
vladfrangu Apr 17, 2025
29d8b81
Update packages/builders/src/components/v2/Thumbnail.ts
vladfrangu Apr 17, 2025
2da9e17
fix: tests
vladfrangu Apr 17, 2025
12d2d12
chore: fmt
vladfrangu Apr 22, 2025
9291c6b
Apply suggestions from code review
vladfrangu Apr 23, 2025
1f7ba47
chore: fix pnpm lockfile revert
vladfrangu Apr 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
237 changes: 237 additions & 0 deletions packages/builders/__tests__/components/v2/container.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 45 additions & 0 deletions packages/builders/__tests__/components/v2/file.test.ts
Original file line number Diff line number Diff line change
@@ -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 });
});
});
});
Loading