Skip to content
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

feat: components v2 in builders #10788

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
815edfd
feat: thumbnail component
vladfrangu Feb 16, 2025
3d4f41b
chore: just a temp file to track remaining components
vladfrangu Feb 16, 2025
d9bd803
feat: file component
vladfrangu Feb 16, 2025
22f98e7
feat: section component
vladfrangu Feb 16, 2025
1c48550
feat: text display component
vladfrangu Feb 16, 2025
b89af97
chore: bump alpha version of dtypes
vladfrangu Feb 23, 2025
3a2f4d4
chore: simplify ComponentBuilder base type
vladfrangu Feb 23, 2025
e3fd0b1
feat: MediaGallery
vladfrangu Mar 3, 2025
5fdb117
feat: Section builder
vladfrangu Mar 3, 2025
2bba3d1
chore: tests for sections
vladfrangu Mar 3, 2025
d0e4e28
chore: forgot you
vladfrangu Mar 3, 2025
a5d0551
chore: docs
Qjuh Mar 3, 2025
fd9e736
fix: missing comma
Qjuh Mar 3, 2025
839a062
fix: my bad
Qjuh Mar 3, 2025
3bcf30f
feat: container builder
vladfrangu Mar 4, 2025
eb9eb66
chore: requested changes
vladfrangu Mar 4, 2025
00ca8e5
chore: missed u
vladfrangu Mar 4, 2025
8e53df4
chore: type tests
vladfrangu Mar 4, 2025
cb4338b
chore: setId/clearId
vladfrangu Mar 4, 2025
d175a40
chore: apply suggestions from code review
Qjuh Mar 5, 2025
ec30aa2
chore: unify pick
vladfrangu Mar 5, 2025
5ebd05c
chore: some requested changes
vladfrangu Mar 5, 2025
a0b8552
chore: fix pnpm
vladfrangu Mar 5, 2025
c9ba1f1
chore: tests and small fixes
Qjuh Mar 11, 2025
75194e7
chore: added tests that need fixing
Qjuh Mar 11, 2025
85e48dc
fix: tests
Qjuh Mar 11, 2025
2ea5a31
chore: cleanup on isle protected
vladfrangu Mar 14, 2025
a2aae6c
docs: remove locale
Jiralite Mar 14, 2025
74345c7
chore: types for new message builder
vladfrangu Mar 14, 2025
d303bf2
chore: fix tests
vladfrangu Mar 14, 2025
3caea12
chore: attempt 1 at message builder assertions
vladfrangu Mar 14, 2025
570eaa9
chore: apply suggestions
Qjuh Mar 17, 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
12 changes: 6 additions & 6 deletions packages/builders/__tests__/components/actionRow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
ButtonStyle,
ComponentType,
type APIActionRowComponent,
type APIMessageActionRowComponent,
type APIComponentInMessageActionRow,
} from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
Expand All @@ -13,7 +13,7 @@ import {
StringSelectMenuOptionBuilder,
} from '../../src/index.js';

const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
type: ComponentType.ActionRow,
components: [
{
Expand All @@ -25,7 +25,7 @@ const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
],
};

const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
type: ComponentType.ActionRow,
components: [
{
Expand All @@ -50,7 +50,7 @@ const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent>
describe('Action Row Components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
const actionRowData: APIActionRowComponent<APIComponentInMessageActionRow> = {
type: ComponentType.ActionRow,
components: [
{
Expand All @@ -73,7 +73,7 @@ describe('Action Row Components', () => {
});

test('GIVEN valid builder options THEN valid JSON output is given', () => {
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
type: ComponentType.ActionRow,
components: [
{
Expand All @@ -85,7 +85,7 @@ describe('Action Row Components', () => {
],
};

const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
type: ComponentType.ActionRow,
components: [
{
Expand Down
4 changes: 2 additions & 2 deletions packages/builders/__tests__/components/components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
ComponentType,
TextInputStyle,
type APIButtonComponent,
type APIMessageActionRowComponent,
type APISelectMenuComponent,
type APITextInputComponent,
type APIActionRowComponent,
type APIComponentInMessageActionRow,
} from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
Expand All @@ -27,7 +27,7 @@ describe('createComponentBuilder', () => {
);

test('GIVEN an action row component THEN returns a ActionRowBuilder', () => {
const actionRow: APIActionRowComponent<APIMessageActionRowComponent> = {
const actionRow: APIActionRowComponent<APIComponentInMessageActionRow> = {
components: [],
type: ComponentType.ActionRow,
};
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
Loading