Skip to content

Commit 118e682

Browse files
Qjuhdidinele
andauthored
feat: components v2 in builders v1 (#10787)
* feat(builders): components v2 in builders v1 * feat: implemented the first components * fix: tests * fix: tests * fix: export the new stuff * feat: add rest of components * feat: add callback syntax * feat: callback syntax for section * fix: missing implements * fix: accessory property * fix: apply suggestions from v2 PR * chore: bring in line with builders v2 * fix: add missing type * chore: split accessory methods * chore: backport changes from v2 PR * fix: accent_color is nullish * fix: allow passing raw json to MediaGallery methods * fix: add test * chore: add Container#addXComponents * fix: docs * chore: bump discord-api-types * Update packages/builders/src/components/Assertions.ts Co-authored-by: Denis-Adrian Cristea <[email protected]> --------- Co-authored-by: Denis-Adrian Cristea <[email protected]>
1 parent 53dbc96 commit 118e682

31 files changed

+1955
-120
lines changed

packages/builders/__tests__/components/actionRow.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
ButtonStyle,
33
ComponentType,
44
type APIActionRowComponent,
5-
type APIMessageActionRowComponent,
5+
type APIComponentInMessageActionRow,
66
} from 'discord-api-types/v10';
77
import { describe, test, expect } from 'vitest';
88
import {
@@ -13,7 +13,7 @@ import {
1313
StringSelectMenuOptionBuilder,
1414
} from '../../src/index.js';
1515

16-
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
16+
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
1717
type: ComponentType.ActionRow,
1818
components: [
1919
{
@@ -25,7 +25,7 @@ const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
2525
],
2626
};
2727

28-
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
28+
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
2929
type: ComponentType.ActionRow,
3030
components: [
3131
{
@@ -57,7 +57,7 @@ describe('Action Row Components', () => {
5757
});
5858

5959
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
60-
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
60+
const actionRowData: APIActionRowComponent<APIComponentInMessageActionRow> = {
6161
type: ComponentType.ActionRow,
6262
components: [
6363
{
@@ -92,7 +92,7 @@ describe('Action Row Components', () => {
9292
});
9393

9494
test('GIVEN valid builder options THEN valid JSON output is given', () => {
95-
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
95+
const rowWithButtonData: APIActionRowComponent<APIComponentInMessageActionRow> = {
9696
type: ComponentType.ActionRow,
9797
components: [
9898
{
@@ -104,7 +104,7 @@ describe('Action Row Components', () => {
104104
],
105105
};
106106

107-
const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent> = {
107+
const rowWithSelectMenuData: APIActionRowComponent<APIComponentInMessageActionRow> = {
108108
type: ComponentType.ActionRow,
109109
components: [
110110
{

packages/builders/__tests__/components/components.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
ComponentType,
44
TextInputStyle,
55
type APIButtonComponent,
6-
type APIMessageActionRowComponent,
6+
type APIComponentInMessageActionRow,
77
type APISelectMenuComponent,
88
type APITextInputComponent,
99
type APIActionRowComponent,
@@ -27,7 +27,7 @@ describe('createComponentBuilder', () => {
2727
);
2828

2929
test('GIVEN an action row component THEN returns a ActionRowBuilder', () => {
30-
const actionRow: APIActionRowComponent<APIMessageActionRowComponent> = {
30+
const actionRow: APIActionRowComponent<APIComponentInMessageActionRow> = {
3131
components: [],
3232
type: ComponentType.ActionRow,
3333
};
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { type APIContainerComponent, ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
2+
import { describe, test, expect } from 'vitest';
3+
import { ButtonBuilder } from '../../../dist/index.mjs';
4+
import { ActionRowBuilder } from '../../../src/components/ActionRow.js';
5+
import { createComponentBuilder } from '../../../src/components/Components.js';
6+
import { ContainerBuilder } from '../../../src/components/v2/Container.js';
7+
import { FileBuilder } from '../../../src/components/v2/File.js';
8+
import { MediaGalleryBuilder } from '../../../src/components/v2/MediaGallery.js';
9+
import { SectionBuilder } from '../../../src/components/v2/Section.js';
10+
import { SeparatorBuilder } from '../../../src/components/v2/Separator.js';
11+
import { TextDisplayBuilder } from '../../../src/components/v2/TextDisplay.js';
12+
13+
const containerWithTextDisplay: APIContainerComponent = {
14+
type: ComponentType.Container,
15+
components: [
16+
{
17+
type: ComponentType.TextDisplay,
18+
content: 'test',
19+
id: 123,
20+
},
21+
],
22+
};
23+
24+
const containerWithSeparatorData: APIContainerComponent = {
25+
type: ComponentType.Container,
26+
components: [
27+
{
28+
type: ComponentType.Separator,
29+
id: 1_234,
30+
spacing: SeparatorSpacingSize.Small,
31+
divider: false,
32+
},
33+
],
34+
accent_color: 0x00ff00,
35+
};
36+
37+
const containerWithSeparatorDataNoColor: APIContainerComponent = {
38+
type: ComponentType.Container,
39+
components: [
40+
{
41+
type: ComponentType.Separator,
42+
id: 1_234,
43+
spacing: SeparatorSpacingSize.Small,
44+
divider: false,
45+
},
46+
],
47+
};
48+
49+
describe('Container Components', () => {
50+
describe('Assertion Tests', () => {
51+
test('GIVEN valid components THEN do not throw', () => {
52+
expect(() =>
53+
new ContainerBuilder().addActionRowComponents(
54+
new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()),
55+
),
56+
).not.toThrowError();
57+
expect(() => new ContainerBuilder().addFileComponents(new FileBuilder())).not.toThrowError();
58+
expect(() => new ContainerBuilder().addMediaGalleryComponents(new MediaGalleryBuilder())).not.toThrowError();
59+
expect(() => new ContainerBuilder().addSectionComponents(new SectionBuilder())).not.toThrowError();
60+
expect(() => new ContainerBuilder().addSeparatorComponents(new SeparatorBuilder())).not.toThrowError();
61+
expect(() => new ContainerBuilder().addTextDisplayComponents(new TextDisplayBuilder())).not.toThrowError();
62+
expect(() => new ContainerBuilder().spliceComponents(0, 0, new SeparatorBuilder())).not.toThrowError();
63+
expect(() => new ContainerBuilder().addSeparatorComponents([new SeparatorBuilder()])).not.toThrowError();
64+
expect(() =>
65+
new ContainerBuilder().spliceComponents(0, 0, [{ type: ComponentType.Separator }]),
66+
).not.toThrowError();
67+
});
68+
69+
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
70+
const containerData: APIContainerComponent = {
71+
type: ComponentType.Container,
72+
components: [
73+
{
74+
type: ComponentType.TextDisplay,
75+
content: 'test',
76+
id: 3,
77+
},
78+
{
79+
type: ComponentType.Separator,
80+
spacing: SeparatorSpacingSize.Large,
81+
divider: true,
82+
id: 4,
83+
},
84+
{
85+
type: ComponentType.File,
86+
file: {
87+
url: 'attachment://file.png',
88+
},
89+
spoiler: false,
90+
},
91+
],
92+
accent_color: 0xff00ff,
93+
spoiler: true,
94+
};
95+
96+
expect(new ContainerBuilder(containerData).toJSON()).toEqual(containerData);
97+
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
98+
});
99+
100+
test('GIVEN valid builder options THEN valid JSON output is given', () => {
101+
const containerWithTextDisplay: APIContainerComponent = {
102+
type: ComponentType.Container,
103+
components: [
104+
{
105+
type: ComponentType.TextDisplay,
106+
content: 'test',
107+
id: 123,
108+
},
109+
],
110+
};
111+
112+
const containerWithSeparatorData: APIContainerComponent = {
113+
type: ComponentType.Container,
114+
components: [
115+
{
116+
type: ComponentType.Separator,
117+
id: 1_234,
118+
spacing: SeparatorSpacingSize.Small,
119+
divider: false,
120+
},
121+
],
122+
accent_color: 0x00ff00,
123+
};
124+
125+
expect(new ContainerBuilder(containerWithTextDisplay).toJSON()).toEqual(containerWithTextDisplay);
126+
expect(new ContainerBuilder(containerWithSeparatorData).toJSON()).toEqual(containerWithSeparatorData);
127+
expect(() => createComponentBuilder({ type: ComponentType.Container, components: [] })).not.toThrowError();
128+
});
129+
130+
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
131+
const textDisplay = new TextDisplayBuilder().setContent('test').setId(123);
132+
const separator = new SeparatorBuilder().setId(1_234).setSpacing(SeparatorSpacingSize.Small).setDivider(false);
133+
134+
expect(new ContainerBuilder().addTextDisplayComponents(textDisplay).toJSON()).toEqual(containerWithTextDisplay);
135+
expect(new ContainerBuilder().addSeparatorComponents(separator).toJSON()).toEqual(
136+
containerWithSeparatorDataNoColor,
137+
);
138+
expect(new ContainerBuilder().addTextDisplayComponents([textDisplay]).toJSON()).toEqual(containerWithTextDisplay);
139+
expect(new ContainerBuilder().addSeparatorComponents([separator]).toJSON()).toEqual(
140+
containerWithSeparatorDataNoColor,
141+
);
142+
});
143+
144+
test('GIVEN valid accent color THEN valid JSON output is given', () => {
145+
expect(
146+
new ContainerBuilder({
147+
components: [
148+
{
149+
type: ComponentType.TextDisplay,
150+
content: 'test',
151+
},
152+
],
153+
})
154+
.setAccentColor([255, 0, 255])
155+
.toJSON(),
156+
).toEqual({
157+
type: ComponentType.Container,
158+
components: [
159+
{
160+
type: ComponentType.TextDisplay,
161+
content: 'test',
162+
},
163+
],
164+
accent_color: 0xff00ff,
165+
});
166+
expect(
167+
new ContainerBuilder({
168+
components: [
169+
{
170+
type: ComponentType.TextDisplay,
171+
content: 'test',
172+
},
173+
],
174+
})
175+
.setAccentColor(0xff00ff)
176+
.toJSON(),
177+
).toEqual({
178+
type: ComponentType.Container,
179+
components: [
180+
{
181+
type: ComponentType.TextDisplay,
182+
content: 'test',
183+
},
184+
],
185+
accent_color: 0xff00ff,
186+
});
187+
expect(
188+
new ContainerBuilder({
189+
components: [
190+
{
191+
type: ComponentType.TextDisplay,
192+
content: 'test',
193+
},
194+
],
195+
})
196+
.setAccentColor([255, 0, 255])
197+
.clearAccentColor()
198+
.toJSON(),
199+
).toEqual({
200+
type: ComponentType.Container,
201+
components: [
202+
{
203+
type: ComponentType.TextDisplay,
204+
content: 'test',
205+
},
206+
],
207+
});
208+
expect(new ContainerBuilder(containerWithSeparatorData).clearAccentColor().toJSON()).toEqual(
209+
containerWithSeparatorDataNoColor,
210+
);
211+
});
212+
213+
test('GIVEN valid method parameters THEN valid JSON is given', () => {
214+
expect(
215+
new ContainerBuilder()
216+
.addTextDisplayComponents(new TextDisplayBuilder().setId(3).clearId().setContent('test'))
217+
.setSpoiler()
218+
.toJSON(),
219+
).toEqual({
220+
type: ComponentType.Container,
221+
components: [
222+
{
223+
type: ComponentType.TextDisplay,
224+
content: 'test',
225+
},
226+
],
227+
spoiler: true,
228+
});
229+
expect(
230+
new ContainerBuilder()
231+
.addTextDisplayComponents({ type: ComponentType.TextDisplay, content: 'test' })
232+
.setSpoiler(false)
233+
.setId(5)
234+
.toJSON(),
235+
).toEqual({
236+
type: ComponentType.Container,
237+
components: [
238+
{
239+
type: ComponentType.TextDisplay,
240+
content: 'test',
241+
},
242+
],
243+
spoiler: false,
244+
id: 5,
245+
});
246+
});
247+
});
248+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { ComponentType } from 'discord-api-types/v10';
2+
import { describe, expect, test } from 'vitest';
3+
import { FileBuilder } from '../../../src/components/v2/File';
4+
5+
const dummy = {
6+
type: ComponentType.File as const,
7+
file: { url: 'attachment://owo.png' },
8+
};
9+
10+
describe('File', () => {
11+
describe('File url', () => {
12+
test('GIVEN a file with a pre-defined url THEN return valid toJSON data', () => {
13+
const file = new FileBuilder({ file: { url: 'attachment://owo.png' } });
14+
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://owo.png' } });
15+
});
16+
17+
test('GIVEN a file using File#setURL THEN return valid toJSON data', () => {
18+
const file = new FileBuilder();
19+
file.setURL('attachment://uwu.png');
20+
21+
expect(file.toJSON()).toEqual({ ...dummy, file: { url: 'attachment://uwu.png' } });
22+
});
23+
24+
test('GIVEN a file with an invalid url THEN throws error', () => {
25+
const file = new FileBuilder();
26+
27+
expect(() => file.setURL('https://google.com')).toThrowError();
28+
});
29+
});
30+
31+
describe('File spoiler', () => {
32+
test('GIVEN a file with a pre-defined spoiler status THEN return valid toJSON data', () => {
33+
const file = new FileBuilder({ ...dummy, spoiler: true });
34+
expect(file.toJSON()).toEqual({ ...dummy, spoiler: true });
35+
});
36+
37+
test('GIVEN a file using File#setSpoiler THEN return valid toJSON data', () => {
38+
const file = new FileBuilder({ ...dummy });
39+
file.setSpoiler(false);
40+
41+
expect(file.toJSON()).toEqual({ ...dummy, spoiler: false });
42+
});
43+
});
44+
});

0 commit comments

Comments
 (0)