Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ab0ac16
test(structures): add tests for Emoji and Entitlement
AsadHumayun Jan 26, 2026
9150192
test: address review feedback
AsadHumayun Jan 29, 2026
0076364
test(structures): add tests for AutoModeration structures
AsadHumayun Jan 29, 2026
796cc30
docs: remove qjuh ://
AsadHumayun Jan 29, 2026
3358806
test: remove duplicate test on Entitlement
AsadHumayun Jan 30, 2026
8cfd831
test(structures): add Webhook tests
AsadHumayun Feb 7, 2026
b490868
test(entitlement): cover end/start dates
AsadHumayun Feb 11, 2026
72e530d
test(structures): add tests for Embed substructures
AsadHumayun Feb 11, 2026
4070470
test(structures): test Poll and substructures
AsadHumayun Feb 11, 2026
d90aef7
fix: add `createdDate` and `createdTimestamp` to SKU struct
AsadHumayun Feb 11, 2026
594fd3e
test(structures): add tests for SKU structure
AsadHumayun Feb 11, 2026
fd322ea
fix(tests): test missed getters on webhook tests
AsadHumayun Feb 11, 2026
3e2d411
test(structures): add test skeletons for message component structures
AsadHumayun Feb 12, 2026
ce7288f
test(structures): add missed getters for testing
AsadHumayun Feb 12, 2026
066c274
chore: use `isFieldSet` on `ButtonComponent` for consistency
AsadHumayun Feb 12, 2026
a6e5c24
fix: export missing class not exported in lib
AsadHumayun Feb 12, 2026
40c8eb9
test(structures): add tests for message components
AsadHumayun Feb 12, 2026
a328ab4
chore: rename for consistency
AsadHumayun Feb 13, 2026
7298cdd
test(structures): add tests for `SoundboardSound` structure
AsadHumayun Feb 16, 2026
0966e7a
fix(structures): add missing `requestToSpeakAt` getter on `VoiceState`
AsadHumayun Feb 18, 2026
a3b18d9
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Feb 20, 2026
b40214a
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Feb 26, 2026
fc71f99
test: add tests for attachment tests
AsadHumayun Feb 26, 2026
84fb36b
test: rm unnecessary test
AsadHumayun Feb 26, 2026
6a6eb07
fix: failing ci
AsadHumayun Feb 26, 2026
ff425f1
chore: tidy up
AsadHumayun Feb 27, 2026
1b19c9c
test: add tests for remaining messages structures
AsadHumayun Feb 27, 2026
2e9c8fc
test(structures): add Stage Instance tests
AsadHumayun Feb 27, 2026
f9f1abd
test(structures): add tests for sticker structures
AsadHumayun Feb 27, 2026
f66b194
test(structures): add tests for Subscription
AsadHumayun Feb 27, 2026
c700ecb
test(structures): add Teams tests
AsadHumayun Feb 27, 2026
e3dc077
test(structures): add tests for User structures
AsadHumayun Feb 28, 2026
ebb37ab
test(structures): add tests for Voice structures
AsadHumayun Feb 28, 2026
130850e
ci: comment out bad test until toJSON is added in #11431
AsadHumayun Feb 28, 2026
fea8e7b
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Mar 3, 2026
822acd1
Revert "ci: comment out bad test until toJSON is added in #11431"
AsadHumayun Mar 3, 2026
c767a89
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Mar 6, 2026
edaa3ea
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Mar 8, 2026
535b534
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Mar 30, 2026
b639eb9
test(structures): standardise date tests
AsadHumayun Apr 4, 2026
1eec4f0
Merge remote-tracking branch 'upstream/main' into feat/add-tests-seve…
AsadHumayun Apr 4, 2026
b56b658
test(structures): remove todo comments
AsadHumayun Apr 4, 2026
6746d7c
test(structures): review comments
AsadHumayun Apr 4, 2026
56c4c26
test: fix failing test on Subscription
AsadHumayun Apr 4, 2026
c825d51
fix(structures): # no longer causes Reaction to show NaN
AsadHumayun Apr 4, 2026
7a0edd2
fix(structures): use protected on `optimizeData` on VoiceState
AsadHumayun Apr 4, 2026
f6eac8b
fix(structures): expose `requestToSpeakTimestamp` correctly
AsadHumayun Apr 7, 2026
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
153 changes: 153 additions & 0 deletions packages/structures/__tests__/automoderation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
type APIAutoModerationAction,
type APIAutoModerationActionMetadata,
type APIAutoModerationRule,
type APIAutoModerationRuleTriggerMetadata,
AutoModerationActionType,
AutoModerationRuleEventType,
AutoModerationRuleTriggerType,
} from 'discord-api-types/v10';
import { describe, expect, test } from 'vitest';
import {
AutoModerationAction,
AutoModerationActionMetadata,
AutoModerationRule,
AutoModerationRuleTriggerMetadata,
} from '../src/automoderation/index.js';
import { kPatch } from '../src/utils/symbols.js';

const ruleTriggerMetadataData: APIAutoModerationRuleTriggerMetadata = {
mention_total_limit: 5,
};

const actionMetadataData: APIAutoModerationActionMetadata = {
channel_id: '1',
custom_message: 'go away.',
};

const actions: APIAutoModerationAction[] = [
{
type: AutoModerationActionType.BlockMessage,
metadata: actionMetadataData,
},
];

const ruleData: APIAutoModerationRule = {
id: '1',
guild_id: '2',
name: 'ruleName',
creator_id: '1',
event_type: AutoModerationRuleEventType.MessageSend,
trigger_metadata: ruleTriggerMetadataData,
trigger_type: AutoModerationRuleTriggerType.MentionSpam,
enabled: true,
actions,
exempt_channels: ['1'],
exempt_roles: [],
};

describe('AutoModerationRule structure', () => {
const instance = new AutoModerationRule(ruleData);
const data = ruleData;

test('correct value for all getters', () => {
expect(instance.id).toBe(data.id);
expect(instance.guildId).toBe(data.guild_id);
expect(instance.name).toBe(data.name);
expect(instance.creatorId).toBe(data.creator_id);
expect(instance.eventType).toBe(data.event_type);
expect(instance.enabled).toBe(data.enabled);
expect(instance.triggerType).toBe(data.trigger_type);
});

test('toJSON() correctly mirrors API data', () => {
expect(instance.toJSON()).toStrictEqual(data);
});

test('Patching the AutoModerationRule works in place', () => {
const patched = instance[kPatch]({
exempt_channels: ['2'],
exempt_roles: ['1', '2', '3'],
});

expect(patched.toJSON()).not.toEqual(data);
expect(instance.toJSON()).toEqual(patched.toJSON());
});

describe('AutoModerationRuleTriggerMetadata sub-structure', () => {
const instance = new AutoModerationRuleTriggerMetadata(ruleTriggerMetadataData);
const data = ruleTriggerMetadataData;

test('getters return correct values', () => {
expect(instance.allowList).toBe(data.allow_list);
expect(instance.keywordFilter).toBe(data.keyword_filter);
expect(instance.mentionRaidProtectionEnabled).toBe(data.mention_raid_protection_enabled);
expect(instance.mentionTotalLimit).toBe(data.mention_total_limit);
expect(instance.presets).toBe(data.presets);
expect(instance.regexPatterns).toBe(data.regex_patterns);
});

test('toJSON() returns expected API data', () => {
expect(instance.toJSON()).toStrictEqual(data);
});

test('patching the structure works in place', () => {
const patched = instance[kPatch]({
mention_total_limit: 10,
});

expect(patched.mentionTotalLimit).toBe(10);

expect(patched.toJSON()).toEqual(instance.toJSON());
});
});

describe('AutoModerationAction structure', () => {
const instance = new AutoModerationAction(actions[0] as APIAutoModerationAction);
const data = actions[0];

test('correct value for all getters', () => {
expect(instance.type).toBe(data!.type);
});

test('toJSON() returns expected API data', () => {
expect(instance.toJSON()).toStrictEqual(data);
});

test('patching the structure works in place', () => {
const patched = instance[kPatch]({
type: AutoModerationActionType.Timeout,
});

expect(instance.type).toBe(AutoModerationActionType.Timeout);

expect(patched.toJSON()).toEqual(instance.toJSON());
});
});

describe('AutoModerationActionMetadata sub-structure', () => {
const instance = new AutoModerationActionMetadata(actionMetadataData);
const data = actionMetadataData;

test('all getters working as expected', () => {
expect(instance.channelId).toBe(data.channel_id);
expect(instance.customMessage).toBe(data.custom_message);
expect(instance.durationSeconds).toBe(data.duration_seconds);
});

test('toJSON() returns expected results', () => {
expect(instance.toJSON()).toStrictEqual(data);
});

test('patching the structure works in place', () => {
const patched = instance[kPatch]({
custom_message: 'noo come back',
duration_seconds: 100,
});

expect(patched.toJSON()).toStrictEqual(instance.toJSON());
expect(patched.customMessage).toBe(instance.customMessage);
expect(patched.durationSeconds).toBe(instance.durationSeconds);
});
});
});
6 changes: 6 additions & 0 deletions packages/structures/__tests__/channels.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type {
APIAnnouncementThreadChannel,
APIDMChannel,
Expand Down Expand Up @@ -68,6 +69,11 @@ describe('text channel', () => {

test('TextChannel has all properties', () => {
const instance = new TextChannel(data);

const createdTimestamp = DiscordSnowflake.timestampFrom(data.id!);
expect(instance.createdTimestamp).toBe(createdTimestamp);
expect(instance.createdAt!.valueOf()).toBe(createdTimestamp);

expect(instance.id).toBe(data.id);
expect(instance.name).toBe(data.name);
expect(instance.position).toBe(data.position);
Expand Down
56 changes: 56 additions & 0 deletions packages/structures/__tests__/emoji.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import type { APIEmoji, APIUser } from 'discord-api-types/v10';
import { describe, expect, test } from 'vitest';
import { Emoji } from '../src/emojis/Emoji.js';
import { kPatch } from '../src/utils/symbols.js';

describe('Emoji structure', () => {
const user: APIUser = {
id: '1',
username: 'username',
discriminator: '0000',
global_name: 'djs.structures.user.global_name',
avatar: 'djs.structures.user.avatar',
};

const data: APIEmoji = {
id: '1',
name: 'name',
roles: ['1', '2', '3'],
user,
require_colons: true,
managed: true,
animated: true,
available: true,
};

const instance = new Emoji(data);

test('Emoji has all properties', () => {
expect(instance.id).toBe(data.id);
expect(instance.name).toBe(data.name);
expect(instance.requireColons).toBe(data.require_colons);
expect(instance.managed).toBe(data.managed);
expect(instance.animated).toBe(data.animated);
expect(instance.available).toBe(data.available);

const createdTimestamp = DiscordSnowflake.timestampFrom(data.id!);
expect(instance.createdTimestamp).toBe(createdTimestamp);
expect(instance.createdAt!.valueOf()).toBe(createdTimestamp);
});

test('toJSON() is accurate', () => {
expect(instance.toJSON()).toStrictEqual(data);
});

test('Patching the Emoji works in place', () => {
const patched = instance[kPatch]({
available: false,
});

expect(patched.available).toBeFalsy();

expect(patched.toJSON()).not.toEqual(data);
expect(patched).toBe(instance);
});
});
65 changes: 65 additions & 0 deletions packages/structures/__tests__/entitlement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { DiscordSnowflake } from '@sapphire/snowflake';
import { type APIEntitlement, EntitlementType } from 'discord-api-types/v10';
import { describe, expect, test } from 'vitest';
import { Entitlement } from '../src/entitlements/Entitlement.js';
import { kPatch } from '../src/utils/symbols.js';

describe('Entitlement structure', () => {
const data: APIEntitlement = {
id: '1',
sku_id: '1',
application_id: '1',
user_id: '1',
type: EntitlementType.Purchase,
deleted: false,
starts_at: '2020-10-10T13:50:17.209000+00:00',
ends_at: '2020-10-10T15:50:17.209000+00:00',
consumed: false,
// note guild_id is missing (to test kPatch)
};

const instance = new Entitlement(data);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other test files also create shared instances outside test blocks that get mutated
rg -n 'const instance = new' packages/structures/__tests__/*.test.ts | head -30

Repository: discordjs/discord.js

Length of output: 2794


🏁 Script executed:

cat -n packages/structures/__tests__/entitlement.test.ts

Repository: discordjs/discord.js

Length of output: 2641


🏁 Script executed:

# Also check webhook.test.ts since it also has line 18 instance creation (appears top-level)
cat -n packages/structures/__tests__/webhook.test.ts | head -50

Repository: discordjs/discord.js

Length of output: 2110


Shared mutable instance may cause test order dependency.

The instance is created once at line 21 (inside the describe block but outside test blocks) and mutated by the patching test (lines 54–57). While tests currently pass because toJSON() runs before patching, if test execution order changes or tests run in parallel, the toJSON() test could fail since instance state is modified. Consider creating a fresh instance within each test using beforeEach:

Proposed fix using beforeEach for test isolation
-const instance = new Entitlement(data);
+let instance: Entitlement;
+
+beforeEach(() => {
+	instance = new Entitlement(data);
+});

This requires importing beforeEach from vitest:

-import { describe, expect, test } from 'vitest';
+import { beforeEach, describe, expect, test } from 'vitest';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const instance = new Entitlement(data);
let instance: Entitlement;
beforeEach(() => {
instance = new Entitlement(data);
});
Suggested change
const instance = new Entitlement(data);
import { beforeEach, describe, expect, test } from 'vitest';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/structures/__tests__/entitlement.test.ts` at line 21, The test
creates a shared Entitlement instance (const instance = new Entitlement(data))
at describe scope and one test mutates it, causing test-order dependency; fix by
moving creation into a per-test lifecycle (use beforeEach to re-instantiate the
Entitlement) so each test gets a fresh instance, and add the beforeEach import
from vitest; update references that currently use the top-level instance to use
the instance provided by beforeEach (e.g., the same variable name scoped to the
describe).


test('Entitlement has all properties', () => {
expect(instance.id).toBe(data.id);
expect(instance.skuId).toBe(data.sku_id);
expect(instance.applicationId).toBe(data.application_id);
expect(instance.userId).toBe(data.user_id);
expect(instance.type).toBe(data.type);
expect(instance.consumed).toBe(data.consumed);
expect(instance.deleted).toBe(data.deleted);
expect(instance.guildId).toBeUndefined();

const createdTimestamp = DiscordSnowflake.timestampFrom(instance.id!);
expect(instance.createdTimestamp).toBe(createdTimestamp);
expect(instance.createdAt!.valueOf()).toBe(createdTimestamp);

const startsTimestamp = Date.parse(data.starts_at!);
expect(instance.startsTimestamp).toBe(startsTimestamp);
expect(instance.startsAt!.valueOf()).toBe(startsTimestamp);

const endsTimestamp = Date.parse(data.ends_at!);
expect(instance.endsTimestamp).toBe(endsTimestamp);
expect(instance.endsAt!.valueOf()).toBe(endsTimestamp);
});

test('toJSON() is accurate', () => {
expect(instance.toJSON()).toStrictEqual(data);
});

test('Patching the Entitlement works in place', () => {
const guildId = '111111';
const consumed = true;

const patched = instance[kPatch]({
guild_id: guildId,
consumed,
});

expect(patched.guildId).toEqual(guildId);
expect(patched.consumed).toEqual(consumed);

expect(patched.toJSON()).not.toEqual(data);
expect(patched).toBe(instance);
});
});
Loading
Loading