mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: message structures (#10982)
* feat: message structures * fix: docs * chore: components and more * feat: embed and more * feat: more substructures and code review suggestions * chore: tests and date conversions * chore: jsdoc strings * fix: tests * fix: tests * feat: hexColor getters * chore: remove getters for nested data * chore: apply suggestions from code review * fix: burst_colors in toJSON * docs: rephrase SectionBuilder remark * chore: add LabelComponent * fix: add name and size to file component * chore: move resolved interaction data to interactions dir * fix: code review * chore: bump discord-api-types * chore: apply code review suggestions * fix: lockfile * chore: update remark * fix: missing export * chore: code review and tests * build: fix file * fix: typo * fix: missing toJSON * fix: remove redundant patch overrides * chore: missing component suffix * chore: better name * chore: add comment explaining timestamp conversion --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
This commit is contained in:
@@ -38,7 +38,7 @@ import {
|
||||
TextChannel,
|
||||
ThreadMetadata,
|
||||
VoiceChannel,
|
||||
} from '../src/index.js';
|
||||
} from '../src/channels/index.js';
|
||||
import { kData } from '../src/utils/symbols.js';
|
||||
|
||||
describe('text channel', () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { APIExtendedInvite, APIInvite } from 'discord-api-types/v10';
|
||||
import { InviteTargetType, InviteType } from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { Invite } from '../src/index.js';
|
||||
import { Invite } from '../src/invites/Invite.js';
|
||||
import { dateToDiscordISOTimestamp } from '../src/utils/optimization.js';
|
||||
import { kPatch } from '../src/utils/symbols.js';
|
||||
|
||||
describe('Invite', () => {
|
||||
@@ -22,7 +23,7 @@ describe('Invite', () => {
|
||||
|
||||
const dataExtended: Omit<APIExtendedInvite, 'expires_at'> = {
|
||||
...data,
|
||||
created_at: '2020-10-10T13:50:17.209Z',
|
||||
created_at: '2020-10-10T13:50:17.209000+00:00',
|
||||
max_age: 12,
|
||||
max_uses: 34,
|
||||
temporary: false,
|
||||
@@ -54,7 +55,7 @@ describe('Invite', () => {
|
||||
const instance = new Invite(dataExtended);
|
||||
expect(instance.type).toBe(data.type);
|
||||
expect(instance.code).toBe(dataExtended.code);
|
||||
expect(instance.createdAt?.toISOString()).toBe(dataExtended.created_at);
|
||||
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(dataExtended.created_at);
|
||||
expect(instance.createdTimestamp).toBe(Date.parse(dataExtended.created_at));
|
||||
expect(instance.maxAge).toBe(dataExtended.max_age);
|
||||
expect(instance.maxUses).toBe(dataExtended.max_uses);
|
||||
@@ -63,10 +64,10 @@ describe('Invite', () => {
|
||||
expect(instance.targetType).toBe(dataExtended.target_type);
|
||||
expect(instance.temporary).toBe(dataExtended.temporary);
|
||||
expect(instance.uses).toBe(dataExtended.uses);
|
||||
expect(instance.expiresTimestamp).toStrictEqual(Date.parse('2020-10-10T13:50:29.209Z'));
|
||||
expect(instance.expiresAt).toStrictEqual(new Date('2020-10-10T13:50:29.209Z'));
|
||||
expect(instance.expiresTimestamp).toStrictEqual(Date.parse('2020-10-10T13:50:29.209000+00:00'));
|
||||
expect(instance.expiresAt).toStrictEqual(new Date('2020-10-10T13:50:29.209000+00:00'));
|
||||
expect(instance.url).toBe('https://discord.gg/123');
|
||||
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209Z' });
|
||||
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209000+00:00' });
|
||||
});
|
||||
|
||||
test('Invite with omitted properties', () => {
|
||||
@@ -79,8 +80,8 @@ describe('Invite', () => {
|
||||
});
|
||||
|
||||
test('Invite with expiration', () => {
|
||||
const instance = new Invite({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209Z' });
|
||||
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209Z' });
|
||||
const instance = new Invite({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209000+00:00' });
|
||||
expect(instance.toJSON()).toEqual({ ...dataExtended, expires_at: '2020-10-10T13:50:29.209000+00:00' });
|
||||
});
|
||||
|
||||
test('Patching Invite works in place', () => {
|
||||
|
||||
478
packages/structures/__tests__/message.test.ts
Normal file
478
packages/structures/__tests__/message.test.ts
Normal file
@@ -0,0 +1,478 @@
|
||||
import { DiscordSnowflake } from '@sapphire/snowflake';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIButtonComponent,
|
||||
APIChannelSelectComponent,
|
||||
APIContainerComponent,
|
||||
APIFileComponent,
|
||||
APIMediaGalleryComponent,
|
||||
APIMentionableSelectComponent,
|
||||
APIMessage,
|
||||
APIRoleSelectComponent,
|
||||
APISectionComponent,
|
||||
APISeparatorComponent,
|
||||
APIStringSelectComponent,
|
||||
APIUser,
|
||||
APIUserSelectComponent,
|
||||
} from 'discord-api-types/v10';
|
||||
import {
|
||||
MessageReferenceType,
|
||||
MessageType,
|
||||
MessageFlags,
|
||||
ComponentType,
|
||||
ButtonStyle,
|
||||
SeparatorSpacingSize,
|
||||
ChannelType,
|
||||
SelectMenuDefaultValueType,
|
||||
} from 'discord-api-types/v10';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { Attachment } from '../src/messages/Attachment.js';
|
||||
import { Message } from '../src/messages/Message.js';
|
||||
import { ContainerComponent } from '../src/messages/components/ContainerComponent.js';
|
||||
import { Embed } from '../src/messages/embeds/Embed.js';
|
||||
import { User } from '../src/users/User.js';
|
||||
import { dateToDiscordISOTimestamp } from '../src/utils/optimization.js';
|
||||
|
||||
const user: APIUser = {
|
||||
username: 'user',
|
||||
avatar: 'abcd123',
|
||||
global_name: 'User',
|
||||
discriminator: '0',
|
||||
id: '3',
|
||||
};
|
||||
|
||||
describe('message with embeds and attachments', () => {
|
||||
const timestamp = '2025-10-09T17:48:20.192000+00:00';
|
||||
const data: APIMessage = {
|
||||
id: DiscordSnowflake.generate({ timestamp: Date.parse(timestamp) }).toString(),
|
||||
type: MessageType.Default,
|
||||
position: 10,
|
||||
channel_id: '2',
|
||||
author: user,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'file.txt',
|
||||
description: 'describe attachment',
|
||||
id: '0',
|
||||
proxy_url: 'https://media.example.com/attachment/123.txt',
|
||||
size: 5,
|
||||
url: 'https://example.com/attachment/123.txt',
|
||||
},
|
||||
],
|
||||
content: 'something <&5> <&6>',
|
||||
edited_timestamp: '2025-10-09T17:50:20.292000+00:00',
|
||||
embeds: [
|
||||
{
|
||||
author: {
|
||||
name: 'embed author',
|
||||
icon_url: 'https://discord.js.org/static/logo.svg',
|
||||
},
|
||||
color: 255,
|
||||
description: 'describe me',
|
||||
fields: [
|
||||
{
|
||||
name: 'field name',
|
||||
value: 'field value',
|
||||
inline: false,
|
||||
},
|
||||
],
|
||||
footer: {
|
||||
text: 'footer',
|
||||
},
|
||||
image: {
|
||||
url: 'https://discord.js.org/static/logo.svg',
|
||||
},
|
||||
thumbnail: {
|
||||
url: 'https://discord.js.org/static/logo.svg',
|
||||
},
|
||||
title: 'Title',
|
||||
timestamp: '2025-10-19T21:39:40.193000+00:00',
|
||||
},
|
||||
],
|
||||
mention_everyone: false,
|
||||
mention_roles: ['5', '6'],
|
||||
mentions: [user],
|
||||
pinned: false,
|
||||
timestamp,
|
||||
tts: false,
|
||||
flags: MessageFlags.SuppressNotifications,
|
||||
};
|
||||
|
||||
test('Message has all properties', () => {
|
||||
const instance = new Message(data);
|
||||
expect(instance.id).toBe(data.id);
|
||||
expect(instance.channelId).toBe(data.channel_id);
|
||||
expect(instance.position).toBe(data.position);
|
||||
expect(instance.content).toBe(data.content);
|
||||
expect(instance.createdTimestamp).toBe(Date.parse(data.timestamp));
|
||||
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(data.timestamp);
|
||||
expect(instance.flags?.toJSON()).toBe(data.flags);
|
||||
expect(instance.editedTimestamp).toBe(Date.parse(data.edited_timestamp!));
|
||||
expect(dateToDiscordISOTimestamp(instance.editedAt!)).toBe(data.edited_timestamp);
|
||||
expect(instance.nonce).toBe(data.nonce);
|
||||
expect(instance.pinned).toBe(data.pinned);
|
||||
expect(instance.tts).toBe(data.tts);
|
||||
expect(instance.webhookId).toBe(data.webhook_id);
|
||||
expect(instance.type).toBe(MessageType.Default);
|
||||
expect(instance.toJSON()).toEqual(data);
|
||||
});
|
||||
|
||||
test('Attachment sub-structure', () => {
|
||||
const instances = data.attachments?.map((attachment) => new Attachment(attachment));
|
||||
expect(instances?.map((attachment) => attachment.toJSON())).toEqual(data.attachments);
|
||||
expect(instances?.[0]?.description).toBe(data.attachments?.[0]?.description);
|
||||
expect(instances?.[0]?.filename).toBe(data.attachments?.[0]?.filename);
|
||||
expect(instances?.[0]?.id).toBe(data.attachments?.[0]?.id);
|
||||
expect(instances?.[0]?.size).toBe(data.attachments?.[0]?.size);
|
||||
expect(instances?.[0]?.url).toBe(data.attachments?.[0]?.url);
|
||||
expect(instances?.[0]?.proxyURL).toBe(data.attachments?.[0]?.proxy_url);
|
||||
});
|
||||
|
||||
test('Embed sub-structure', () => {
|
||||
const instances = data.embeds?.map((embed) => new Embed(embed));
|
||||
expect(instances?.map((embed) => embed.toJSON())).toEqual(data.embeds);
|
||||
expect(instances?.[0]?.description).toBe(data.embeds?.[0]?.description);
|
||||
expect(instances?.[0]?.color).toBe(data.embeds?.[0]?.color);
|
||||
expect(instances?.[0]?.timestamp).toBe(Date.parse(data.embeds![0]!.timestamp!));
|
||||
expect(instances?.[0]?.title).toBe(data.embeds?.[0]?.title);
|
||||
expect(instances?.[0]?.url).toBe(data.embeds?.[0]?.url);
|
||||
expect(instances?.[0]?.type).toBe(data.embeds?.[0]?.type);
|
||||
});
|
||||
|
||||
test('User sub-structure', () => {
|
||||
const instance = new User(data.author);
|
||||
const instances = data.mentions.map((user) => new User(user));
|
||||
expect(instance.toJSON()).toEqual(data.author);
|
||||
expect(instances.map((user) => user.toJSON())).toEqual(data.mentions);
|
||||
expect(instance.avatar).toBe(data.author.avatar);
|
||||
expect(instance.discriminator).toBe(data.author.discriminator);
|
||||
expect(instance.displayName).toBe(data.author.global_name);
|
||||
expect(instance.globalName).toBe(data.author.global_name);
|
||||
expect(instance.id).toBe(data.author.id);
|
||||
expect(instance.username).toBe(data.author.username);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message with components', () => {
|
||||
const timestamp = '2025-10-10T15:48:20.192000+00:00';
|
||||
const buttonRow: APIActionRowComponent<APIButtonComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
id: 5,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Button,
|
||||
style: ButtonStyle.Danger,
|
||||
custom_id: 'danger',
|
||||
disabled: false,
|
||||
emoji: {
|
||||
animated: false,
|
||||
id: '12345',
|
||||
name: 'emoji',
|
||||
},
|
||||
id: 6,
|
||||
label: 'Danger button',
|
||||
},
|
||||
{
|
||||
type: ComponentType.Button,
|
||||
style: ButtonStyle.Link,
|
||||
url: 'https://discord.js.org/',
|
||||
disabled: false,
|
||||
id: 7,
|
||||
label: 'DJS',
|
||||
},
|
||||
{
|
||||
type: ComponentType.Button,
|
||||
style: ButtonStyle.Premium,
|
||||
sku_id: '9876',
|
||||
disabled: false,
|
||||
id: 8,
|
||||
},
|
||||
],
|
||||
};
|
||||
const file: APIFileComponent = {
|
||||
type: ComponentType.File,
|
||||
file: {
|
||||
url: 'attachment://file.txt',
|
||||
attachment_id: '0',
|
||||
content_type: 'text/plain',
|
||||
flags: 0,
|
||||
},
|
||||
id: 9,
|
||||
spoiler: true,
|
||||
};
|
||||
const mediaGallery: APIMediaGalleryComponent = {
|
||||
type: ComponentType.MediaGallery,
|
||||
items: [
|
||||
{
|
||||
media: {
|
||||
url: 'https://discord.js.org/static/logo.svg',
|
||||
content_type: 'image/svg+xml',
|
||||
height: 50,
|
||||
width: 50,
|
||||
},
|
||||
description: 'Logo',
|
||||
spoiler: false,
|
||||
},
|
||||
],
|
||||
id: 10,
|
||||
};
|
||||
const section: APISectionComponent = {
|
||||
type: ComponentType.Section,
|
||||
accessory: {
|
||||
type: ComponentType.Thumbnail,
|
||||
media: {
|
||||
url: 'https://discord.js.org/static/logo.svg',
|
||||
},
|
||||
description: 'Logo thumbnail',
|
||||
id: 13,
|
||||
spoiler: false,
|
||||
},
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextDisplay,
|
||||
content: 'Text',
|
||||
id: 14,
|
||||
},
|
||||
],
|
||||
id: 12,
|
||||
};
|
||||
const separator: APISeparatorComponent = {
|
||||
type: ComponentType.Separator,
|
||||
divider: true,
|
||||
id: 15,
|
||||
spacing: SeparatorSpacingSize.Large,
|
||||
};
|
||||
const channelRow: APIActionRowComponent<APIChannelSelectComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
id: 16,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.ChannelSelect,
|
||||
custom_id: 'channel',
|
||||
channel_types: [ChannelType.GuildCategory, ChannelType.GuildText],
|
||||
default_values: [
|
||||
{
|
||||
id: '123456789012345678',
|
||||
type: SelectMenuDefaultValueType.Channel,
|
||||
},
|
||||
{
|
||||
id: '123456789012345679',
|
||||
type: SelectMenuDefaultValueType.Channel,
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
id: 17,
|
||||
max_values: 2,
|
||||
min_values: 0,
|
||||
placeholder: '(none)',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const mentionRow: APIActionRowComponent<APIMentionableSelectComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
id: 18,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.MentionableSelect,
|
||||
custom_id: 'mention',
|
||||
default_values: [
|
||||
{
|
||||
id: '123456789012345678',
|
||||
type: SelectMenuDefaultValueType.User,
|
||||
},
|
||||
{
|
||||
id: '123456789012345679',
|
||||
type: SelectMenuDefaultValueType.Role,
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
id: 19,
|
||||
max_values: 2,
|
||||
min_values: 0,
|
||||
placeholder: '(none)',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const roleRow: APIActionRowComponent<APIRoleSelectComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
id: 20,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.RoleSelect,
|
||||
custom_id: 'role',
|
||||
default_values: [
|
||||
{
|
||||
id: '123456789012345678',
|
||||
type: SelectMenuDefaultValueType.Role,
|
||||
},
|
||||
{
|
||||
id: '123456789012345679',
|
||||
type: SelectMenuDefaultValueType.Role,
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
id: 21,
|
||||
max_values: 2,
|
||||
min_values: 0,
|
||||
placeholder: '(none)',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const userRow: APIActionRowComponent<APIUserSelectComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
id: 22,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.UserSelect,
|
||||
custom_id: 'user',
|
||||
default_values: [
|
||||
{
|
||||
id: '123456789012345678',
|
||||
type: SelectMenuDefaultValueType.User,
|
||||
},
|
||||
{
|
||||
id: '123456789012345679',
|
||||
type: SelectMenuDefaultValueType.User,
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
id: 23,
|
||||
max_values: 2,
|
||||
min_values: 0,
|
||||
placeholder: '(none)',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const stringRow: APIActionRowComponent<APIStringSelectComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
id: 24,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.StringSelect,
|
||||
custom_id: 'string',
|
||||
options: [
|
||||
{
|
||||
label: 'one',
|
||||
value: '1',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: 'two',
|
||||
value: '2',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
label: 'three',
|
||||
value: '3',
|
||||
description: 'third',
|
||||
emoji: {
|
||||
id: '3333333333333333333',
|
||||
name: '3',
|
||||
animated: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
disabled: false,
|
||||
id: 25,
|
||||
max_values: 2,
|
||||
min_values: 0,
|
||||
placeholder: '(none)',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
const container: APIContainerComponent = {
|
||||
type: ComponentType.Container,
|
||||
accent_color: 255,
|
||||
id: 4,
|
||||
components: [
|
||||
buttonRow,
|
||||
file,
|
||||
mediaGallery,
|
||||
section,
|
||||
separator,
|
||||
channelRow,
|
||||
mentionRow,
|
||||
roleRow,
|
||||
userRow,
|
||||
stringRow,
|
||||
],
|
||||
spoiler: true,
|
||||
};
|
||||
const data: APIMessage = {
|
||||
id: DiscordSnowflake.generate({ timestamp: Date.parse(timestamp) }).toString(),
|
||||
type: MessageType.Reply,
|
||||
position: 15,
|
||||
channel_id: '2',
|
||||
author: user,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'file.txt',
|
||||
description: 'describe attachment',
|
||||
id: '0',
|
||||
proxy_url: 'https://media.example.com/attachment/123.txt',
|
||||
size: 5,
|
||||
url: 'https://example.com/attachment/123.txt',
|
||||
},
|
||||
],
|
||||
content: '',
|
||||
edited_timestamp: '2025-10-10T15:50:20.292000+00:00',
|
||||
embeds: [],
|
||||
components: [container],
|
||||
message_reference: {
|
||||
channel_id: '505050505050505050',
|
||||
message_id: '606060606060606060',
|
||||
guild_id: '707070707070707070',
|
||||
type: MessageReferenceType.Default,
|
||||
},
|
||||
mention_everyone: false,
|
||||
mention_roles: ['5', '6'],
|
||||
mentions: [user],
|
||||
pinned: false,
|
||||
timestamp,
|
||||
tts: false,
|
||||
flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral,
|
||||
};
|
||||
|
||||
test('Message has all properties', () => {
|
||||
const instance = new Message(data);
|
||||
expect(instance.id).toBe(data.id);
|
||||
expect(instance.channelId).toBe(data.channel_id);
|
||||
expect(instance.position).toBe(data.position);
|
||||
expect(instance.content).toBe(data.content);
|
||||
expect(instance.createdTimestamp).toBe(Date.parse(data.timestamp));
|
||||
expect(dateToDiscordISOTimestamp(instance.createdAt!)).toBe(data.timestamp);
|
||||
expect(instance.flags?.toJSON()).toBe(data.flags);
|
||||
expect(instance.editedTimestamp).toBe(Date.parse(data.edited_timestamp!));
|
||||
expect(dateToDiscordISOTimestamp(instance.editedAt!)).toBe(data.edited_timestamp);
|
||||
expect(instance.nonce).toBe(data.nonce);
|
||||
expect(instance.pinned).toBe(data.pinned);
|
||||
expect(instance.tts).toBe(data.tts);
|
||||
expect(instance.webhookId).toBe(data.webhook_id);
|
||||
expect(instance.type).toBe(MessageType.Reply);
|
||||
expect(instance.toJSON()).toEqual(data);
|
||||
});
|
||||
|
||||
test('Attachment sub-structure', () => {
|
||||
const instances = data.attachments?.map((attachment) => new Attachment(attachment));
|
||||
expect(instances?.map((attachment) => attachment.toJSON())).toEqual(data.attachments);
|
||||
expect(instances?.[0]?.description).toBe(data.attachments?.[0]?.description);
|
||||
expect(instances?.[0]?.filename).toBe(data.attachments?.[0]?.filename);
|
||||
expect(instances?.[0]?.id).toBe(data.attachments?.[0]?.id);
|
||||
expect(instances?.[0]?.size).toBe(data.attachments?.[0]?.size);
|
||||
expect(instances?.[0]?.url).toBe(data.attachments?.[0]?.url);
|
||||
expect(instances?.[0]?.proxyURL).toBe(data.attachments?.[0]?.proxy_url);
|
||||
});
|
||||
|
||||
test('Component sub-structures', () => {
|
||||
const containerInstance = new ContainerComponent(data.components?.[0] as APIContainerComponent);
|
||||
expect(containerInstance.toJSON()).toEqual(container);
|
||||
expect(containerInstance.type).toBe(container.type);
|
||||
expect(containerInstance.id).toBe(container.id);
|
||||
expect(containerInstance.spoiler).toBe(container.spoiler);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { expectNotType, expectType } from 'tsd';
|
||||
import { expectTypeOf } from 'vitest';
|
||||
import type { MixinTypes } from '../../src/MixinTypes.d.ts';
|
||||
import type { kMixinConstruct } from '../../src/utils/symbols.js';
|
||||
@@ -16,22 +15,26 @@ declare const extendsBothOmitBoth: Omit<
|
||||
keyof Base | typeof kMixinConstruct
|
||||
>;
|
||||
|
||||
expectType<MixinTypes<Base, [MixinProperty1]>>(extendsNoOmit);
|
||||
expectType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>(extendsOmitProperty1);
|
||||
expectNotType<MixinTypes<Base, [MixinProperty1]>>(extendsOmitProperty1);
|
||||
expectNotType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>(extendsNoOmit);
|
||||
expectTypeOf(extendsNoOmit).toEqualTypeOf<MixinTypes<Base, [MixinProperty1]>>();
|
||||
expectTypeOf(extendsOmitProperty1).toEqualTypeOf<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>();
|
||||
expectTypeOf(extendsOmitProperty1).not.toEqualTypeOf<MixinTypes<Base, [MixinProperty1]>>();
|
||||
expectTypeOf(extendsNoOmit).not.toEqualTypeOf<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>]>>();
|
||||
|
||||
expectType<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>(extendsBothNoOmit);
|
||||
expectTypeOf(extendsBothNoOmit).toEqualTypeOf<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>();
|
||||
// Since MixinProperty2 doesn't utilize the type of property1 in kData, this works and is ok
|
||||
expectType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>>(extendsBothOmitProperty1);
|
||||
expectNotType<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>(extendsBothOmitProperty1);
|
||||
expectTypeOf(extendsBothOmitProperty1).toEqualTypeOf<
|
||||
MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>
|
||||
>();
|
||||
expectTypeOf(extendsBothOmitProperty1).not.toEqualTypeOf<MixinTypes<Base, [MixinProperty1, MixinProperty2]>>();
|
||||
// Since MixinProperty2 doesn't utilize the type of property1 in kData, this works and is ok
|
||||
expectNotType<MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>>(extendsBothNoOmit);
|
||||
expectTypeOf(extendsBothNoOmit).not.toEqualTypeOf<
|
||||
MixinTypes<Base<'property1'>, [MixinProperty1<'property1'>, MixinProperty2]>
|
||||
>();
|
||||
|
||||
// Earlier mixins in the list must specify all properties because of the way merging works
|
||||
expectType<
|
||||
expectTypeOf(extendsBothOmitBoth).toEqualTypeOf<
|
||||
MixinTypes<Base<'property1' | 'property2'>, [MixinProperty1<'property1' | 'property2'>, MixinProperty2<'property2'>]>
|
||||
>(extendsBothOmitBoth);
|
||||
>();
|
||||
|
||||
expectTypeOf<MixinTypes<Base<'property1'>, [MixinProperty1]>>().toBeNever();
|
||||
// @ts-expect-error Shouldn't be able to assign non identical omits
|
||||
|
||||
@@ -1,79 +1,81 @@
|
||||
import type { ChannelType, GuildChannelType, GuildTextChannelType, ThreadChannelType } from 'discord-api-types/v10';
|
||||
import { expectNever, expectType } from 'tsd';
|
||||
import type { Channel } from '../../src/index.js';
|
||||
import { expectTypeOf } from 'vitest';
|
||||
import type { Channel } from '../../src/channels/Channel.js';
|
||||
|
||||
declare const channel: Channel;
|
||||
|
||||
if (channel.isGuildBased()) {
|
||||
expectType<string>(channel.guildId);
|
||||
expectType<GuildChannelType>(channel.type);
|
||||
expectTypeOf(channel.guildId).toBeString();
|
||||
expectTypeOf(channel.type).toEqualTypeOf<GuildChannelType>();
|
||||
|
||||
if (channel.isDMBased()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isPermissionCapable()) {
|
||||
expectType<Exclude<GuildChannelType, ChannelType.GuildDirectory | ThreadChannelType>>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<
|
||||
Exclude<GuildChannelType, ChannelType.GuildDirectory | ThreadChannelType>
|
||||
>();
|
||||
}
|
||||
|
||||
if (channel.isTextBased()) {
|
||||
expectType<GuildTextChannelType>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<GuildTextChannelType>();
|
||||
}
|
||||
|
||||
if (channel.isWebhookCapable()) {
|
||||
expectType<ChannelType.GuildForum | ChannelType.GuildMedia | Exclude<GuildTextChannelType, ThreadChannelType>>(
|
||||
channel.type,
|
||||
);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<
|
||||
ChannelType.GuildForum | ChannelType.GuildMedia | Exclude<GuildTextChannelType, ThreadChannelType>
|
||||
>();
|
||||
}
|
||||
|
||||
if (channel.isThread()) {
|
||||
expectType<ThreadChannelType>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<ThreadChannelType>();
|
||||
}
|
||||
|
||||
if (channel.isThreadOnly()) {
|
||||
expectType<ChannelType.GuildForum | ChannelType.GuildMedia>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.GuildForum | ChannelType.GuildMedia>();
|
||||
}
|
||||
|
||||
if (channel.isVoiceBased()) {
|
||||
expectType<ChannelType.GuildStageVoice | ChannelType.GuildVoice>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.GuildStageVoice | ChannelType.GuildVoice>();
|
||||
if (!channel.isTextBased()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (!channel.isWebhookCapable()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (channel.isDMBased()) {
|
||||
expectType<ChannelType.DM | ChannelType.GroupDM>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.DM | ChannelType.GroupDM>();
|
||||
|
||||
if (channel.isGuildBased()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isPermissionCapable()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isWebhookCapable()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isVoiceBased()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isThread()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isThreadOnly()) {
|
||||
expectNever(channel);
|
||||
expectTypeOf(channel).toBeNever();
|
||||
}
|
||||
|
||||
if (channel.isTextBased()) {
|
||||
expectType<ChannelType.DM | ChannelType.GroupDM>(channel.type);
|
||||
expectTypeOf(channel.type).toEqualTypeOf<ChannelType.DM | ChannelType.GroupDM>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user