Skip to content
This repository has been archived by the owner on Jan 8, 2022. It is now read-only.

feat: Add Components #63

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
072f557
feat: add message components
suneettipirneni Dec 13, 2021
46456ed
Merge branch 'main' of https://github.com/discordjs/builders into fea…
suneettipirneni Dec 13, 2021
2434201
types: redo some typings
suneettipirneni Dec 13, 2021
962e2cd
types: finalized button typings
suneettipirneni Dec 14, 2021
46a7055
chore: add button validations
suneettipirneni Dec 14, 2021
3c12a8b
feat: add select menu component;
suneettipirneni Dec 14, 2021
907da2f
chore: move to folders
suneettipirneni Dec 14, 2021
71d964e
feat: add action row component
suneettipirneni Dec 14, 2021
e69bd13
chore: begin writing unit tests
suneettipirneni Dec 14, 2021
9f065fb
test: add button test
suneettipirneni Dec 14, 2021
f228988
test: add select menu tests
suneettipirneni Dec 14, 2021
8371214
test: add action row tests
suneettipirneni Dec 15, 2021
62f9ea2
refactor: change BaseComponent -> Component
suneettipirneni Dec 15, 2021
c798e65
refactor: change filename
suneettipirneni Dec 15, 2021
89ca227
feat: add component factory
suneettipirneni Dec 15, 2021
3524482
Merge branch 'main' of https://github.com/discordjs/builders into fea…
suneettipirneni Dec 27, 2021
25012b5
refactor: remove generics and improve serialization
suneettipirneni Dec 28, 2021
4375006
fix: not validating properly
suneettipirneni Dec 28, 2021
50c0ad8
chore: update dapi-types
suneettipirneni Dec 28, 2021
9575593
fix: version dapi-types
suneettipirneni Dec 28, 2021
37cce6b
Merge branch 'main' of https://github.com/discordjs/builders into fea…
suneettipirneni Dec 28, 2021
fde9d7a
types: make toPartialJSON protected
suneettipirneni Dec 28, 2021
13f016e
chore: make requested changes
suneettipirneni Dec 29, 2021
d0b22b0
test: fix tests
suneettipirneni Dec 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 46 additions & 0 deletions __tests__/components/actionRow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { APIActionRowComponent, ButtonStyle, ComponentType } from 'discord-api-types/v9';
import { ActionRow, createComponent, LinkButtonComponent } from '../../src';

describe('Action Row Components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid components THEN do not throw', () => {
expect(() => new ActionRow().addComponents(new LinkButtonComponent())).not.toThrowError();
expect(() => new ActionRow().setComponents([new LinkButtonComponent()])).not.toThrowError();
});

test('GIVEN valid JSON input THEN valid JSON output is given', () => {
const actionRowData: APIActionRowComponent = {
type: ComponentType.ActionRow,
components: [
{
type: ComponentType.Button,
label: 'button',
style: ButtonStyle.Primary,
custom_id: 'test',
},
{
type: ComponentType.Button,
label: 'link',
style: ButtonStyle.Link,
url: 'https://google.com',
},
{
type: ComponentType.SelectMenu,
placeholder: 'test',
custom_id: 'test',
options: [
{
label: 'option',
value: 'option',
},
],
},
],
};

expect(new ActionRow(actionRowData).toJSON()).toEqual(actionRowData);
expect(new ActionRow().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
expect(() => createComponent({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
});
});
});
118 changes: 118 additions & 0 deletions __tests__/components/button.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
APIButtonComponentWithCustomId,
APIButtonComponentWithURL,
ButtonStyle,
ComponentType,
} from 'discord-api-types/v9';
import { buttonLabelValidator, InteractionButtonComponent, LinkButtonComponent, styleValidator } from '../../src/index';

const interactionButtonComponent = () => new InteractionButtonComponent();
const linkButtonComponent = () => new LinkButtonComponent();

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(() => styleValidator.parse(3)).not.toThrowError();
expect(() => styleValidator.parse(ButtonStyle.Secondary)).not.toThrowError();
});

test('GIVEN invalid style then validator does not throw', () => {
expect(() => styleValidator.parse(7)).toThrowError();

// Note: this throws because link styles are always set for you.
expect(() => styleValidator.parse(ButtonStyle.Link)).toThrowError();
});

test('GIVEN valid fields THEN builder does not throw', () => {
expect(() =>
interactionButtonComponent().setCustomId('custom').setStyle(ButtonStyle.Primary).setLabel('test'),
).not.toThrowError();

expect(() => {
const button = interactionButtonComponent()
.setCustomId('custom')
.setStyle(ButtonStyle.Primary)
.setDisabled(true)
.setEmoji({ name: 'test' });

button.toJSON();
}).not.toThrowError();

expect(() => linkButtonComponent().setURL('https://google.com')).not.toThrowError();
});

test('GIVEN invalid fields THEN build does throw', () => {
expect(() => {
interactionButtonComponent().setCustomId(longStr);
}).toThrowError();

expect(() => {
const button = interactionButtonComponent()
.setCustomId('custom')
.setStyle(ButtonStyle.Primary)
.setDisabled(true)
.setLabel('test')
// @ts-expect-error
.setEmoji({ name: 'test' });

button.toJSON();
}).toThrowError();

expect(() => interactionButtonComponent().setStyle(24));
expect(() => interactionButtonComponent().setLabel(longStr));
// @ts-ignore
expect(() => interactionButtonComponent().setDisabled(0));
// @ts-ignore
expect(() => interactionButtonComponent().setEmoji('foo'));

expect(() => linkButtonComponent().setURL('foobar')).toThrowError();
});

test('GiVEN valid input THEN valid JSON outputs are given', () => {
const interactionData: APIButtonComponentWithCustomId = {
type: ComponentType.Button,
custom_id: 'test',
label: 'test',
style: ButtonStyle.Primary,
disabled: true,
};

expect(new InteractionButtonComponent(interactionData).toJSON()).toEqual(interactionData);

expect(
interactionButtonComponent()
.setCustomId(interactionData.custom_id)
.setLabel(interactionData.label)
.setStyle(interactionData.style)
.setDisabled(interactionData.disabled)
.toJSON(),
).toEqual(interactionData);

const linkData: APIButtonComponentWithURL = {
type: ComponentType.Button,
label: 'test',
style: ButtonStyle.Link,
disabled: true,
url: 'https://google.com',
};

expect(new LinkButtonComponent(linkData).toJSON()).toEqual(linkData);

expect(linkButtonComponent().setLabel(linkData.label).setDisabled(true).setURL(linkData.url));
});
});
});
72 changes: 72 additions & 0 deletions __tests__/components/selectMenu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { APISelectMenuComponent, APISelectMenuOption, ComponentType } from 'discord-api-types/v9';
import { SelectMenuComponent, SelectMenuOption } from '../../src/index';

const selectMenu = () => new SelectMenuComponent();
const selectMenuOption = () => new SelectMenuOption();

const longStr =
'looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong';

describe('Button Components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid inputs THEN Select Menu does not throw', () => {
expect(() => selectMenu().setCustomId('foo')).not.toThrowError();
expect(() => selectMenu().setMaxValues(10)).not.toThrowError();
expect(() => selectMenu().setMinValues(3)).not.toThrowError();
expect(() => selectMenu().setDisabled(true)).not.toThrowError();
expect(() => selectMenu().setPlaceholder('description')).not.toThrowError();

const option = selectMenuOption()
.setLabel('test')
.setValue('test')
.setDefault(true)
.setEmoji({ name: 'test' })
.setDescription('description');
expect(() => selectMenu().addOptions(option)).not.toThrowError();
expect(() => selectMenu().setOptions([option])).not.toThrowError();
});

test('GIVEN invalid inputs THEN Select Menu does throw', () => {
expect(() => selectMenu().setCustomId(longStr)).toThrowError();
expect(() => selectMenu().setMaxValues(30)).toThrowError();
expect(() => selectMenu().setMinValues(-20)).toThrowError();
// @ts-expect-error
expect(() => selectMenu().setDisabled(0)).toThrowError();
expect(() => selectMenu().setPlaceholder(longStr)).toThrowError();

expect(() => {
selectMenuOption()
.setLabel(longStr)
.setValue(longStr)
// @ts-expect-error
.setDefault(-1)
// @ts-expect-error
.setEmoji({ name: 1 })
.setDescription(longStr);
}).toThrowError();
});

test('GIVEN valid JSON input THEN valid JSON history is correct', () => {
const selectMenuOptionData: APISelectMenuOption = {
label: 'test',
value: 'test',
emoji: { name: 'test' },
default: true,
description: 'test',
};

const selectMenuData: APISelectMenuComponent = {
type: ComponentType.SelectMenu,
custom_id: 'test',
max_values: 10,
min_values: 3,
disabled: true,
options: [selectMenuOptionData],
placeholder: 'test',
};

expect(new SelectMenuComponent(selectMenuData).toJSON()).toEqual(selectMenuData);
expect(new SelectMenuOption(selectMenuOptionData).toJSON()).toEqual(selectMenuOptionData);
});
});
});