meocord 4.1.0-beta.0 → 4.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +23 -0
- package/dist/cjs/testing/index.cjs +50 -6
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/mock-interaction.js +51 -7
- package/dist/types/testing/index.d.cts +40 -5
- package/dist/types/testing/index.d.ts +40 -5
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# meocord
|
|
2
2
|
|
|
3
|
+
## 4.1.0-beta.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#120](https://github.com/meocord/meocord/pull/120) [`9d435d9`](https://github.com/meocord/meocord/commit/9d435d9da2bc62eab012ea2b80df2c1e001a926e) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `createMockMessage()` takes an `id`, `content`, `components`, `embeds` and `flags`, so a test can put controls on the message a button sits on, such as to check what `@Defer` locks; components and embeds may be API JSON, builders or discord.js instances.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#118](https://github.com/meocord/meocord/pull/118) [`5c636aa`](https://github.com/meocord/meocord/commit/5c636aa930224aef9b4670a5e16b234a3f57397d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Tools that read the installed version with `require('meocord/package.json')` no longer fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
12
|
+
|
|
13
|
+
- [#121](https://github.com/meocord/meocord/pull/121) [`c6990c2`](https://github.com/meocord/meocord/commit/c6990c250f7694ca5adb9963e89f151b7ed5dc4b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `inGuild()`, `inCachedGuild()` and `inRawGuild()` on a mock answer from its `guildId` and `guild`, so a mock created without a `guildId` is a DM and a guard that requires a guild returns `false`, not `undefined`.
|
|
14
|
+
|
|
3
15
|
## 4.1.0-beta.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1524,6 +1524,8 @@ Creates a smart mock instance of any discord.js class. The full prototype chain
|
|
|
1524
1524
|
|
|
1525
1525
|
**Type guards run real logic** — `isButton()`, `isRepliable()`, `isChatInputCommand()`, etc. are backed by the actual discord.js prototype methods. The right fields (`type`, `componentType`, `commandType`) are set based on the class you pass in, so no manual `.mockReturnValue(true)` setup is needed. All type guard methods are still mock functions and can be overridden per test.
|
|
1526
1526
|
|
|
1527
|
+
**Guild checks read the mock's data** — `inGuild()`, `inCachedGuild()` and `inRawGuild()` answer from the `guildId` and `guild` the mock is given, as discord.js does: a `guildId` means a guild, a `guildId` with a `guild` (such as `createMockGuild()`) a cached one, and a mock created without a `guildId` is a DM, where all three return `false`. A `member` set to `null` or `undefined` makes all three `false` too.
|
|
1528
|
+
|
|
1527
1529
|
**Reply state machine** — for repliable interactions, `replied` and `deferred` start as `false`. Calling `reply()` or `deferReply()` twice throws, just like a real interaction. `followUp()`, `editReply()`, and `deleteReply()` throw if called before any reply. The ephemeral flag is tracked on `interaction.ephemeral`, read from `flags` only — the deprecated `ephemeral: true` reply option is not honoured. All reply methods are still mock functions so call assertions work normally.
|
|
1528
1530
|
|
|
1529
1531
|
Autocomplete interactions are not repliable but get the equivalent for their own single-shot response: `responded` starts as `false`, `respond()` sets it, and a second call throws.
|
|
@@ -1547,6 +1549,10 @@ interaction.isChatInputCommand() // → true
|
|
|
1547
1549
|
interaction.isRepliable() // → true
|
|
1548
1550
|
interaction.isButton() // → false
|
|
1549
1551
|
|
|
1552
|
+
// created without a guildId, the mock is a DM
|
|
1553
|
+
interaction.inGuild() // → false
|
|
1554
|
+
interaction.inCachedGuild() // → false
|
|
1555
|
+
|
|
1550
1556
|
// reply state machine
|
|
1551
1557
|
interaction.replied // → false
|
|
1552
1558
|
await interaction.reply({ content: 'hi' })
|
|
@@ -1691,6 +1697,23 @@ edited.delete // → a mock fn
|
|
|
1691
1697
|
expect(msg.delete).toHaveBeenCalledTimes(1)
|
|
1692
1698
|
```
|
|
1693
1699
|
|
|
1700
|
+
Without arguments the message is empty. Give it an `id`, `content`, `components`, `embeds` and `flags` to test code that reads them, such as a button on a message whose controls `@Defer` locks. Components and embeds may be API JSON, builders or discord.js instances, and `flags` a number, flag names or a `MessageFlagsBitField`:
|
|
1701
|
+
|
|
1702
|
+
```typescript
|
|
1703
|
+
const message = createMockMessage({
|
|
1704
|
+
components: [
|
|
1705
|
+
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
1706
|
+
new ButtonBuilder().setCustomId('card/refresh').setLabel('Refresh').setStyle(ButtonStyle.Primary),
|
|
1707
|
+
),
|
|
1708
|
+
],
|
|
1709
|
+
embeds: [{ title: 'Card' }],
|
|
1710
|
+
flags: MessageFlags.Ephemeral,
|
|
1711
|
+
})
|
|
1712
|
+
const interaction = createMockInteraction(ButtonInteraction, { customId: 'card/refresh', message })
|
|
1713
|
+
```
|
|
1714
|
+
|
|
1715
|
+
API JSON and builders are kept as their JSON behind `toJSON()`, which is what `respond()` and `@Defer` read; discord.js instances are kept as they are.
|
|
1716
|
+
|
|
1694
1717
|
</details>
|
|
1695
1718
|
|
|
1696
1719
|
<details>
|
|
@@ -634,7 +634,9 @@ function findPrototypeMethod(instance, name) {
|
|
|
634
634
|
/**
|
|
635
635
|
* Creates a mock instance of a discord.js class, keeping its prototype so `instanceof` holds.
|
|
636
636
|
*
|
|
637
|
-
* Type guards such as `isButton()` run the real discord.js logic.
|
|
637
|
+
* Type guards such as `isButton()` run the real discord.js logic. `inGuild()`, `inCachedGuild()`
|
|
638
|
+
* and `inRawGuild()` answer from the mock's own `guildId` and `guild`, so a mock created without
|
|
639
|
+
* a `guildId` is a DM. Replies behave like a real
|
|
638
640
|
* interaction: `reply()` or `deferReply()` twice throws, and `followUp()`, `editReply()` and
|
|
639
641
|
* `deleteReply()` throw before a reply. Every method is a mock function you can override.
|
|
640
642
|
*
|
|
@@ -667,6 +669,19 @@ function findPrototypeMethod(instance, name) {
|
|
|
667
669
|
stubs.set(name, createMockFn().mockImplementation(()=>method.call(instance)));
|
|
668
670
|
}
|
|
669
671
|
}
|
|
672
|
+
// Guild checks read the mock's own data, since discord.js resolves `guild` through a client the
|
|
673
|
+
// mock lacks: a guildId is a guild, a guild object with it a cached one, neither a DM. A member
|
|
674
|
+
// not given is the auto-stub, so only one set to null or undefined fails the check.
|
|
675
|
+
const own = (key)=>Object.prototype.hasOwnProperty.call(instance, key) ? instance[key] : undefined;
|
|
676
|
+
const hasMember = ()=>!Object.prototype.hasOwnProperty.call(instance, 'member') || Boolean(instance.member);
|
|
677
|
+
const guildChecks = {
|
|
678
|
+
inGuild: ()=>Boolean(own('guildId') && hasMember()),
|
|
679
|
+
inCachedGuild: ()=>Boolean(own('guildId') && own('guild') && hasMember()),
|
|
680
|
+
inRawGuild: ()=>Boolean(own('guildId') && !own('guild') && hasMember())
|
|
681
|
+
};
|
|
682
|
+
for (const [name, check] of Object.entries(guildChecks)){
|
|
683
|
+
if (findPrototypeMethod(instance, name) !== null) stubs.set(name, createMockFn().mockImplementation(check));
|
|
684
|
+
}
|
|
670
685
|
// Set up reply state machine for repliable interactions
|
|
671
686
|
const isRepliableMethod = findPrototypeMethod(instance, 'isRepliable');
|
|
672
687
|
const repliable = isRepliableMethod !== null && isRepliableMethod.call(instance);
|
|
@@ -900,6 +915,16 @@ function findPrototypeMethod(instance, name) {
|
|
|
900
915
|
guild.bans = stubDeep(Object.create(discord_js.GuildBanManager.prototype));
|
|
901
916
|
return stubDeep(guild);
|
|
902
917
|
}
|
|
918
|
+
/**
|
|
919
|
+
* A component or embed as a message holds it: a discord.js instance as it is; anything else as
|
|
920
|
+
* its API JSON at the time of the call, behind `toJSON()`.
|
|
921
|
+
*/ function asHeld(value) {
|
|
922
|
+
if (value instanceof discord_js.Component || value instanceof discord_js.Embed) return value;
|
|
923
|
+
const json = structuredClone(typeof value.toJSON === 'function' ? value.toJSON() : value);
|
|
924
|
+
return {
|
|
925
|
+
toJSON: ()=>json
|
|
926
|
+
};
|
|
927
|
+
}
|
|
903
928
|
/**
|
|
904
929
|
* Creates a mock {@link Message} that tracks whether it has been deleted.
|
|
905
930
|
*
|
|
@@ -908,13 +933,30 @@ function findPrototypeMethod(instance, name) {
|
|
|
908
933
|
* `msg.author.send` and `msg.guild.members.fetch` are ready to use, and every method is a mock
|
|
909
934
|
* function you can override per test.
|
|
910
935
|
*
|
|
936
|
+
* Without overrides the message is empty: no flags, components, embeds or attachments. Components
|
|
937
|
+
* and embeds given as API JSON or builders keep that JSON behind `toJSON()`, which is what
|
|
938
|
+
* `respond()` and `@Defer` read, such as when `@Defer` locks the controls of the message a button
|
|
939
|
+
* sits on; discord.js instances are kept as they are.
|
|
940
|
+
*
|
|
941
|
+
* @param overrides - The message's id, content, components, embeds and flags.
|
|
942
|
+
* @returns The mock message.
|
|
943
|
+
*
|
|
911
944
|
* @example
|
|
912
945
|
* ```ts
|
|
913
|
-
* const message = createMockMessage(
|
|
946
|
+
* const message = createMockMessage({
|
|
947
|
+
* components: [
|
|
948
|
+
* new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
949
|
+
* new ButtonBuilder().setCustomId('card/refresh').setLabel('Refresh').setStyle(ButtonStyle.Primary),
|
|
950
|
+
* ),
|
|
951
|
+
* ],
|
|
952
|
+
* embeds: [{ title: 'Card' }],
|
|
953
|
+
* })
|
|
954
|
+
* const interaction = createMockInteraction(ButtonInteraction, { customId: 'card/refresh', message })
|
|
955
|
+
*
|
|
914
956
|
* await message.delete()
|
|
915
957
|
* message.deleted // true
|
|
916
958
|
* ```
|
|
917
|
-
*/ function createMockMessage() {
|
|
959
|
+
*/ function createMockMessage(overrides = {}) {
|
|
918
960
|
const instance = Object.create(discord_js.Message.prototype);
|
|
919
961
|
const stubs = new Map();
|
|
920
962
|
instance.deleted = false;
|
|
@@ -942,10 +984,12 @@ function findPrototypeMethod(instance, name) {
|
|
|
942
984
|
// MessageMentions — constructor-assigned, has methods like .has(), .members
|
|
943
985
|
instance.mentions = stubDeep(Object.create(discord_js.MessageMentions.prototype));
|
|
944
986
|
// Data a message always has, real rather than stubbed, so code reading it sees an empty message
|
|
945
|
-
instance.flags = new discord_js.MessageFlagsBitField();
|
|
946
|
-
instance.components = [];
|
|
947
|
-
instance.embeds = [];
|
|
987
|
+
instance.flags = new discord_js.MessageFlagsBitField(overrides.flags);
|
|
988
|
+
instance.components = (overrides.components ?? []).map(asHeld);
|
|
989
|
+
instance.embeds = (overrides.embeds ?? []).map(asHeld);
|
|
948
990
|
instance.attachments = new discord_js.Collection();
|
|
991
|
+
if (overrides.id !== undefined) instance.id = overrides.id;
|
|
992
|
+
if (overrides.content !== undefined) instance.content = overrides.content;
|
|
949
993
|
const alreadyDeleted = ()=>new Error('This message has already been deleted.');
|
|
950
994
|
stubs.set('delete', createMockFn(async ()=>{
|
|
951
995
|
if (instance.deleted) throw alreadyDeleted();
|
package/dist/esm/package.json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { createMockFn } from './mock-fn.js';
|
|
3
|
-
import { InteractionType, ComponentType, ApplicationCommandType, CommandInteractionOptionResolver, GuildMessageManager, ThreadManager, DMMessageManager, MessageManager, ThreadMemberManager, Client, ApplicationCommandManager, UserManager, ChannelManager, GuildManager, ClientUser, Guild, GuildMemberManager, GuildChannelManager, RoleManager, GuildBanManager, AuthorizingIntegrationOwners, Message, User, GuildMember, TextChannel, ThreadChannel, MessageMentions, MessageFlagsBitField, Collection, ApplicationCommandOptionType, Role, BaseChannel, Attachment } from 'discord.js';
|
|
3
|
+
import { InteractionType, ComponentType, ApplicationCommandType, CommandInteractionOptionResolver, GuildMessageManager, ThreadManager, DMMessageManager, MessageManager, ThreadMemberManager, Client, ApplicationCommandManager, UserManager, ChannelManager, GuildManager, ClientUser, Guild, GuildMemberManager, GuildChannelManager, RoleManager, GuildBanManager, AuthorizingIntegrationOwners, Message, User, GuildMember, TextChannel, ThreadChannel, MessageMentions, MessageFlagsBitField, Collection, ApplicationCommandOptionType, Component, Embed, Role, BaseChannel, Attachment } from 'discord.js';
|
|
4
4
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
// stubDeep — Proxy that auto-creates a mock fn on any property access
|
|
@@ -163,7 +163,9 @@ function findPrototypeMethod(instance, name) {
|
|
|
163
163
|
/**
|
|
164
164
|
* Creates a mock instance of a discord.js class, keeping its prototype so `instanceof` holds.
|
|
165
165
|
*
|
|
166
|
-
* Type guards such as `isButton()` run the real discord.js logic.
|
|
166
|
+
* Type guards such as `isButton()` run the real discord.js logic. `inGuild()`, `inCachedGuild()`
|
|
167
|
+
* and `inRawGuild()` answer from the mock's own `guildId` and `guild`, so a mock created without
|
|
168
|
+
* a `guildId` is a DM. Replies behave like a real
|
|
167
169
|
* interaction: `reply()` or `deferReply()` twice throws, and `followUp()`, `editReply()` and
|
|
168
170
|
* `deleteReply()` throw before a reply. Every method is a mock function you can override.
|
|
169
171
|
*
|
|
@@ -196,6 +198,19 @@ function findPrototypeMethod(instance, name) {
|
|
|
196
198
|
stubs.set(name, createMockFn().mockImplementation(()=>method.call(instance)));
|
|
197
199
|
}
|
|
198
200
|
}
|
|
201
|
+
// Guild checks read the mock's own data, since discord.js resolves `guild` through a client the
|
|
202
|
+
// mock lacks: a guildId is a guild, a guild object with it a cached one, neither a DM. A member
|
|
203
|
+
// not given is the auto-stub, so only one set to null or undefined fails the check.
|
|
204
|
+
const own = (key)=>Object.prototype.hasOwnProperty.call(instance, key) ? instance[key] : undefined;
|
|
205
|
+
const hasMember = ()=>!Object.prototype.hasOwnProperty.call(instance, 'member') || Boolean(instance.member);
|
|
206
|
+
const guildChecks = {
|
|
207
|
+
inGuild: ()=>Boolean(own('guildId') && hasMember()),
|
|
208
|
+
inCachedGuild: ()=>Boolean(own('guildId') && own('guild') && hasMember()),
|
|
209
|
+
inRawGuild: ()=>Boolean(own('guildId') && !own('guild') && hasMember())
|
|
210
|
+
};
|
|
211
|
+
for (const [name, check] of Object.entries(guildChecks)){
|
|
212
|
+
if (findPrototypeMethod(instance, name) !== null) stubs.set(name, createMockFn().mockImplementation(check));
|
|
213
|
+
}
|
|
199
214
|
// Set up reply state machine for repliable interactions
|
|
200
215
|
const isRepliableMethod = findPrototypeMethod(instance, 'isRepliable');
|
|
201
216
|
const repliable = isRepliableMethod !== null && isRepliableMethod.call(instance);
|
|
@@ -429,6 +444,16 @@ function findPrototypeMethod(instance, name) {
|
|
|
429
444
|
guild.bans = stubDeep(Object.create(GuildBanManager.prototype));
|
|
430
445
|
return stubDeep(guild);
|
|
431
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* A component or embed as a message holds it: a discord.js instance as it is; anything else as
|
|
449
|
+
* its API JSON at the time of the call, behind `toJSON()`.
|
|
450
|
+
*/ function asHeld(value) {
|
|
451
|
+
if (value instanceof Component || value instanceof Embed) return value;
|
|
452
|
+
const json = structuredClone(typeof value.toJSON === 'function' ? value.toJSON() : value);
|
|
453
|
+
return {
|
|
454
|
+
toJSON: ()=>json
|
|
455
|
+
};
|
|
456
|
+
}
|
|
432
457
|
/**
|
|
433
458
|
* Creates a mock {@link Message} that tracks whether it has been deleted.
|
|
434
459
|
*
|
|
@@ -437,13 +462,30 @@ function findPrototypeMethod(instance, name) {
|
|
|
437
462
|
* `msg.author.send` and `msg.guild.members.fetch` are ready to use, and every method is a mock
|
|
438
463
|
* function you can override per test.
|
|
439
464
|
*
|
|
465
|
+
* Without overrides the message is empty: no flags, components, embeds or attachments. Components
|
|
466
|
+
* and embeds given as API JSON or builders keep that JSON behind `toJSON()`, which is what
|
|
467
|
+
* `respond()` and `@Defer` read, such as when `@Defer` locks the controls of the message a button
|
|
468
|
+
* sits on; discord.js instances are kept as they are.
|
|
469
|
+
*
|
|
470
|
+
* @param overrides - The message's id, content, components, embeds and flags.
|
|
471
|
+
* @returns The mock message.
|
|
472
|
+
*
|
|
440
473
|
* @example
|
|
441
474
|
* ```ts
|
|
442
|
-
* const message = createMockMessage(
|
|
475
|
+
* const message = createMockMessage({
|
|
476
|
+
* components: [
|
|
477
|
+
* new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
478
|
+
* new ButtonBuilder().setCustomId('card/refresh').setLabel('Refresh').setStyle(ButtonStyle.Primary),
|
|
479
|
+
* ),
|
|
480
|
+
* ],
|
|
481
|
+
* embeds: [{ title: 'Card' }],
|
|
482
|
+
* })
|
|
483
|
+
* const interaction = createMockInteraction(ButtonInteraction, { customId: 'card/refresh', message })
|
|
484
|
+
*
|
|
443
485
|
* await message.delete()
|
|
444
486
|
* message.deleted // true
|
|
445
487
|
* ```
|
|
446
|
-
*/ function createMockMessage() {
|
|
488
|
+
*/ function createMockMessage(overrides = {}) {
|
|
447
489
|
const instance = Object.create(Message.prototype);
|
|
448
490
|
const stubs = new Map();
|
|
449
491
|
instance.deleted = false;
|
|
@@ -471,10 +513,12 @@ function findPrototypeMethod(instance, name) {
|
|
|
471
513
|
// MessageMentions — constructor-assigned, has methods like .has(), .members
|
|
472
514
|
instance.mentions = stubDeep(Object.create(MessageMentions.prototype));
|
|
473
515
|
// Data a message always has, real rather than stubbed, so code reading it sees an empty message
|
|
474
|
-
instance.flags = new MessageFlagsBitField();
|
|
475
|
-
instance.components = [];
|
|
476
|
-
instance.embeds = [];
|
|
516
|
+
instance.flags = new MessageFlagsBitField(overrides.flags);
|
|
517
|
+
instance.components = (overrides.components ?? []).map(asHeld);
|
|
518
|
+
instance.embeds = (overrides.embeds ?? []).map(asHeld);
|
|
477
519
|
instance.attachments = new Collection();
|
|
520
|
+
if (overrides.id !== undefined) instance.id = overrides.id;
|
|
521
|
+
if (overrides.content !== undefined) instance.content = overrides.content;
|
|
478
522
|
const alreadyDeleted = ()=>new Error('This message has already been deleted.');
|
|
479
523
|
stubs.set('delete', createMockFn(async ()=>{
|
|
480
524
|
if (instance.deleted) throw alreadyDeleted();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ServiceIdentifier, Container } from 'inversify';
|
|
2
|
-
import { ClientEvents, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User, ModalSubmitFields, DiscordAPIError, Interaction } from 'discord.js';
|
|
2
|
+
import { ClientEvents, APIMessageTopLevelComponent, JSONEncodable, APIEmbed, MessageFlagsResolvable, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User, ModalSubmitFields, DiscordAPIError, Interaction } from 'discord.js';
|
|
3
3
|
import { GuardInterface, InterceptorInterface, ExceptionFilter } from '../interface/index.cjs';
|
|
4
4
|
import { CommandType } from '../enum/index.cjs';
|
|
5
5
|
import { Translator } from '../common/index.cjs';
|
|
@@ -306,7 +306,9 @@ interface InteractionClass<T> {
|
|
|
306
306
|
/**
|
|
307
307
|
* Creates a mock instance of a discord.js class, keeping its prototype so `instanceof` holds.
|
|
308
308
|
*
|
|
309
|
-
* Type guards such as `isButton()` run the real discord.js logic.
|
|
309
|
+
* Type guards such as `isButton()` run the real discord.js logic. `inGuild()`, `inCachedGuild()`
|
|
310
|
+
* and `inRawGuild()` answer from the mock's own `guildId` and `guild`, so a mock created without
|
|
311
|
+
* a `guildId` is a DM. Replies behave like a real
|
|
310
312
|
* interaction: `reply()` or `deferReply()` twice throws, and `followUp()`, `editReply()` and
|
|
311
313
|
* `deleteReply()` throw before a reply. Every method is a mock function you can override.
|
|
312
314
|
*
|
|
@@ -359,6 +361,22 @@ declare function createMockGuild(): DeepMocked<Guild>;
|
|
|
359
361
|
* @param Class - The discord.js channel class to mock.
|
|
360
362
|
*/
|
|
361
363
|
declare function createMockChannel<T extends Channel>(Class: InteractionClass<T>): DeepMocked<T>;
|
|
364
|
+
/**
|
|
365
|
+
* What {@link createMockMessage} builds a message with. Components and embeds may be API JSON,
|
|
366
|
+
* builders or discord.js instances.
|
|
367
|
+
*/
|
|
368
|
+
interface MockMessageOverrides {
|
|
369
|
+
/** The message's id. */
|
|
370
|
+
id?: string;
|
|
371
|
+
/** The message's text. */
|
|
372
|
+
content?: string;
|
|
373
|
+
/** Its top-level components: action rows, or Components V2 such as a container. */
|
|
374
|
+
components?: readonly (APIMessageTopLevelComponent | JSONEncodable<APIMessageTopLevelComponent>)[];
|
|
375
|
+
/** Its embeds. */
|
|
376
|
+
embeds?: readonly (APIEmbed | JSONEncodable<APIEmbed>)[];
|
|
377
|
+
/** Its flags: a number, flag names or a `MessageFlagsBitField`. */
|
|
378
|
+
flags?: MessageFlagsResolvable;
|
|
379
|
+
}
|
|
362
380
|
/**
|
|
363
381
|
* Creates a mock {@link Message} that tracks whether it has been deleted.
|
|
364
382
|
*
|
|
@@ -367,14 +385,31 @@ declare function createMockChannel<T extends Channel>(Class: InteractionClass<T>
|
|
|
367
385
|
* `msg.author.send` and `msg.guild.members.fetch` are ready to use, and every method is a mock
|
|
368
386
|
* function you can override per test.
|
|
369
387
|
*
|
|
388
|
+
* Without overrides the message is empty: no flags, components, embeds or attachments. Components
|
|
389
|
+
* and embeds given as API JSON or builders keep that JSON behind `toJSON()`, which is what
|
|
390
|
+
* `respond()` and `@Defer` read, such as when `@Defer` locks the controls of the message a button
|
|
391
|
+
* sits on; discord.js instances are kept as they are.
|
|
392
|
+
*
|
|
393
|
+
* @param overrides - The message's id, content, components, embeds and flags.
|
|
394
|
+
* @returns The mock message.
|
|
395
|
+
*
|
|
370
396
|
* @example
|
|
371
397
|
* ```ts
|
|
372
|
-
* const message = createMockMessage(
|
|
398
|
+
* const message = createMockMessage({
|
|
399
|
+
* components: [
|
|
400
|
+
* new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
401
|
+
* new ButtonBuilder().setCustomId('card/refresh').setLabel('Refresh').setStyle(ButtonStyle.Primary),
|
|
402
|
+
* ),
|
|
403
|
+
* ],
|
|
404
|
+
* embeds: [{ title: 'Card' }],
|
|
405
|
+
* })
|
|
406
|
+
* const interaction = createMockInteraction(ButtonInteraction, { customId: 'card/refresh', message })
|
|
407
|
+
*
|
|
373
408
|
* await message.delete()
|
|
374
409
|
* message.deleted // true
|
|
375
410
|
* ```
|
|
376
411
|
*/
|
|
377
|
-
declare function createMockMessage(): DeepMocked<Message> & {
|
|
412
|
+
declare function createMockMessage(overrides?: MockMessageOverrides): DeepMocked<Message> & {
|
|
378
413
|
deleted: boolean;
|
|
379
414
|
};
|
|
380
415
|
interface ChatInputOptions {
|
|
@@ -664,4 +699,4 @@ declare function getResponse(interaction: Interaction): ResponseReport;
|
|
|
664
699
|
declare function createDiscordError(code: number, message?: string): DiscordAPIError;
|
|
665
700
|
|
|
666
701
|
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createDiscordError, createExecutionContext, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, createModalFields, expectCompleteCatalog, findRouteConflicts, getResponse, inspectHandler, isMockFunction, resolveRoute };
|
|
667
|
-
export type { ChatInputOptions, ClassProvider, ComponentCommandType, DeepMocked, EmitResult, ExecutionContextOptions, HandlerInspection, HandlerName, InspectHandlerOptions, InspectedCooldown, InspectedFilter, InspectedGuard, InspectedInterceptor, InvocationResult, Mock, MockInstance, MockProps, MockResult, MockState, MockedFunction, Provider, ResolvedRoute, ResponseReport, RouteConflict, TestingModuleOptions, ValueProvider };
|
|
702
|
+
export type { ChatInputOptions, ClassProvider, ComponentCommandType, DeepMocked, EmitResult, ExecutionContextOptions, HandlerInspection, HandlerName, InspectHandlerOptions, InspectedCooldown, InspectedFilter, InspectedGuard, InspectedInterceptor, InvocationResult, Mock, MockInstance, MockMessageOverrides, MockProps, MockResult, MockState, MockedFunction, Provider, ResolvedRoute, ResponseReport, RouteConflict, TestingModuleOptions, ValueProvider };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ServiceIdentifier, Container } from 'inversify';
|
|
2
|
-
import { ClientEvents, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User, ModalSubmitFields, DiscordAPIError, Interaction } from 'discord.js';
|
|
2
|
+
import { ClientEvents, APIMessageTopLevelComponent, JSONEncodable, APIEmbed, MessageFlagsResolvable, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User, ModalSubmitFields, DiscordAPIError, Interaction } from 'discord.js';
|
|
3
3
|
import { GuardInterface, InterceptorInterface, ExceptionFilter } from '../interface/index.js';
|
|
4
4
|
import { CommandType } from '../enum/index.js';
|
|
5
5
|
import { Translator } from '../common/index.js';
|
|
@@ -306,7 +306,9 @@ interface InteractionClass<T> {
|
|
|
306
306
|
/**
|
|
307
307
|
* Creates a mock instance of a discord.js class, keeping its prototype so `instanceof` holds.
|
|
308
308
|
*
|
|
309
|
-
* Type guards such as `isButton()` run the real discord.js logic.
|
|
309
|
+
* Type guards such as `isButton()` run the real discord.js logic. `inGuild()`, `inCachedGuild()`
|
|
310
|
+
* and `inRawGuild()` answer from the mock's own `guildId` and `guild`, so a mock created without
|
|
311
|
+
* a `guildId` is a DM. Replies behave like a real
|
|
310
312
|
* interaction: `reply()` or `deferReply()` twice throws, and `followUp()`, `editReply()` and
|
|
311
313
|
* `deleteReply()` throw before a reply. Every method is a mock function you can override.
|
|
312
314
|
*
|
|
@@ -359,6 +361,22 @@ declare function createMockGuild(): DeepMocked<Guild>;
|
|
|
359
361
|
* @param Class - The discord.js channel class to mock.
|
|
360
362
|
*/
|
|
361
363
|
declare function createMockChannel<T extends Channel>(Class: InteractionClass<T>): DeepMocked<T>;
|
|
364
|
+
/**
|
|
365
|
+
* What {@link createMockMessage} builds a message with. Components and embeds may be API JSON,
|
|
366
|
+
* builders or discord.js instances.
|
|
367
|
+
*/
|
|
368
|
+
interface MockMessageOverrides {
|
|
369
|
+
/** The message's id. */
|
|
370
|
+
id?: string;
|
|
371
|
+
/** The message's text. */
|
|
372
|
+
content?: string;
|
|
373
|
+
/** Its top-level components: action rows, or Components V2 such as a container. */
|
|
374
|
+
components?: readonly (APIMessageTopLevelComponent | JSONEncodable<APIMessageTopLevelComponent>)[];
|
|
375
|
+
/** Its embeds. */
|
|
376
|
+
embeds?: readonly (APIEmbed | JSONEncodable<APIEmbed>)[];
|
|
377
|
+
/** Its flags: a number, flag names or a `MessageFlagsBitField`. */
|
|
378
|
+
flags?: MessageFlagsResolvable;
|
|
379
|
+
}
|
|
362
380
|
/**
|
|
363
381
|
* Creates a mock {@link Message} that tracks whether it has been deleted.
|
|
364
382
|
*
|
|
@@ -367,14 +385,31 @@ declare function createMockChannel<T extends Channel>(Class: InteractionClass<T>
|
|
|
367
385
|
* `msg.author.send` and `msg.guild.members.fetch` are ready to use, and every method is a mock
|
|
368
386
|
* function you can override per test.
|
|
369
387
|
*
|
|
388
|
+
* Without overrides the message is empty: no flags, components, embeds or attachments. Components
|
|
389
|
+
* and embeds given as API JSON or builders keep that JSON behind `toJSON()`, which is what
|
|
390
|
+
* `respond()` and `@Defer` read, such as when `@Defer` locks the controls of the message a button
|
|
391
|
+
* sits on; discord.js instances are kept as they are.
|
|
392
|
+
*
|
|
393
|
+
* @param overrides - The message's id, content, components, embeds and flags.
|
|
394
|
+
* @returns The mock message.
|
|
395
|
+
*
|
|
370
396
|
* @example
|
|
371
397
|
* ```ts
|
|
372
|
-
* const message = createMockMessage(
|
|
398
|
+
* const message = createMockMessage({
|
|
399
|
+
* components: [
|
|
400
|
+
* new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
401
|
+
* new ButtonBuilder().setCustomId('card/refresh').setLabel('Refresh').setStyle(ButtonStyle.Primary),
|
|
402
|
+
* ),
|
|
403
|
+
* ],
|
|
404
|
+
* embeds: [{ title: 'Card' }],
|
|
405
|
+
* })
|
|
406
|
+
* const interaction = createMockInteraction(ButtonInteraction, { customId: 'card/refresh', message })
|
|
407
|
+
*
|
|
373
408
|
* await message.delete()
|
|
374
409
|
* message.deleted // true
|
|
375
410
|
* ```
|
|
376
411
|
*/
|
|
377
|
-
declare function createMockMessage(): DeepMocked<Message> & {
|
|
412
|
+
declare function createMockMessage(overrides?: MockMessageOverrides): DeepMocked<Message> & {
|
|
378
413
|
deleted: boolean;
|
|
379
414
|
};
|
|
380
415
|
interface ChatInputOptions {
|
|
@@ -664,4 +699,4 @@ declare function getResponse(interaction: Interaction): ResponseReport;
|
|
|
664
699
|
declare function createDiscordError(code: number, message?: string): DiscordAPIError;
|
|
665
700
|
|
|
666
701
|
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createDiscordError, createExecutionContext, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, createModalFields, expectCompleteCatalog, findRouteConflicts, getResponse, inspectHandler, isMockFunction, resolveRoute };
|
|
667
|
-
export type { ChatInputOptions, ClassProvider, ComponentCommandType, DeepMocked, EmitResult, ExecutionContextOptions, HandlerInspection, HandlerName, InspectHandlerOptions, InspectedCooldown, InspectedFilter, InspectedGuard, InspectedInterceptor, InvocationResult, Mock, MockInstance, MockProps, MockResult, MockState, MockedFunction, Provider, ResolvedRoute, ResponseReport, RouteConflict, TestingModuleOptions, ValueProvider };
|
|
702
|
+
export type { ChatInputOptions, ClassProvider, ComponentCommandType, DeepMocked, EmitResult, ExecutionContextOptions, HandlerInspection, HandlerName, InspectHandlerOptions, InspectedCooldown, InspectedFilter, InspectedGuard, InspectedInterceptor, InvocationResult, Mock, MockInstance, MockMessageOverrides, MockProps, MockResult, MockState, MockedFunction, Provider, ResolvedRoute, ResponseReport, RouteConflict, TestingModuleOptions, ValueProvider };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meocord",
|
|
3
3
|
"description": "Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.",
|
|
4
|
-
"version": "4.1.0-beta.
|
|
4
|
+
"version": "4.1.0-beta.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.13"
|
|
@@ -106,7 +106,8 @@
|
|
|
106
106
|
"types": "./meocord.eslint.d.cts",
|
|
107
107
|
"default": "./meocord.eslint.cjs"
|
|
108
108
|
}
|
|
109
|
-
}
|
|
109
|
+
},
|
|
110
|
+
"./package.json": "./package.json"
|
|
110
111
|
},
|
|
111
112
|
"files": [
|
|
112
113
|
"dist/**/*",
|