meocord 1.8.0 → 1.8.2

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.
@@ -361,12 +361,70 @@ function findPrototypeMethod(instance, name) {
361
361
  // Convenience wrappers for common discord.js classes
362
362
  // ---------------------------------------------------------------------------
363
363
  /** Creates a mock {@link User}. All methods are auto-stubbed as `jest.fn()`. */ const createMockUser = ()=>createMockInteraction(discord_js.User);
364
- /** Creates a mock {@link Client}. Managers like `client.users` are nested stubs. */ const createMockClient = ()=>createMockInteraction(discord_js.Client);
365
- /** Creates a mock {@link Guild}. Managers like `guild.members` are nested stubs. */ const createMockGuild = ()=>createMockInteraction(discord_js.Guild);
364
+ /**
365
+ * Creates a mock {@link Client}.
366
+ *
367
+ * Manager methods that are constructor-assigned (not on the prototype) are
368
+ * pre-initialized as `jest.fn()` so they work out of the box without manual
369
+ * setup: `users.fetch`, `channels.fetch`, `guilds.fetch`, and
370
+ * `application.commands.fetch`.
371
+ */ function createMockClient() {
372
+ const instance = Object.create(discord_js.Client.prototype);
373
+ // Manager properties are constructor-assigned — pre-initialize as prototype-based
374
+ // stubs so ALL manager methods (not just fetch) are auto-stubbed as jest.fn().
375
+ const appInstance = Object.create(null);
376
+ appInstance.commands = stubDeep(Object.create(discord_js.ApplicationCommandManager.prototype));
377
+ instance.users = stubDeep(Object.create(discord_js.UserManager.prototype));
378
+ instance.channels = stubDeep(Object.create(discord_js.ChannelManager.prototype));
379
+ instance.guilds = stubDeep(Object.create(discord_js.GuildManager.prototype));
380
+ instance.user = stubDeep(Object.create(discord_js.ClientUser.prototype));
381
+ instance.application = stubDeep(appInstance);
382
+ return stubDeep(instance);
383
+ }
384
+ /**
385
+ * Creates a mock {@link Guild}.
386
+ *
387
+ * Manager properties are constructor-assigned in discord.js. This factory
388
+ * pre-initializes each as a prototype-based stub so all methods are
389
+ * auto-stubbed as `jest.fn()`.
390
+ *
391
+ * Pre-initialized: `members`, `channels`, `roles`, `bans`.
392
+ */ function createMockGuild() {
393
+ const instance = Object.create(discord_js.Guild.prototype);
394
+ instance.members = stubDeep(Object.create(discord_js.GuildMemberManager.prototype));
395
+ instance.channels = stubDeep(Object.create(discord_js.GuildChannelManager.prototype));
396
+ instance.roles = stubDeep(Object.create(discord_js.RoleManager.prototype));
397
+ instance.bans = stubDeep(Object.create(discord_js.GuildBanManager.prototype));
398
+ return stubDeep(instance);
399
+ }
366
400
  /**
367
401
  * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
368
- * All methods are auto-stubbed as `jest.fn()`.
369
- */ const createMockChannel = (Class)=>createMockInteraction(Class);
402
+ *
403
+ * Manager properties that are constructor-assigned in discord.js are
404
+ * pre-initialized as prototype-based stubs so all methods are auto-stubbed
405
+ * as `jest.fn()`:
406
+ * - Guild text channels (`TextChannel`, `NewsChannel`): `messages`, `threads`
407
+ * - `DMChannel`: `messages`
408
+ * - `ThreadChannel`: `messages`, `members`
409
+ */ function createMockChannel(Class) {
410
+ const instance = Object.create(Class.prototype);
411
+ // Guild text channels (TextChannel, NewsChannel) — messages & threads
412
+ // assigned in BaseGuildTextChannel constructor
413
+ if ('messages' in Class.prototype || Class.name === 'TextChannel' || Class.name === 'NewsChannel') {
414
+ instance.messages = stubDeep(Object.create(discord_js.GuildMessageManager.prototype));
415
+ instance.threads = stubDeep(Object.create(discord_js.ThreadManager.prototype));
416
+ }
417
+ // DMChannel — messages assigned in DMChannel constructor
418
+ if (Class.name === 'DMChannel') {
419
+ instance.messages = stubDeep(Object.create(discord_js.DMMessageManager.prototype));
420
+ }
421
+ // ThreadChannel — messages & members assigned in ThreadChannel constructor
422
+ if (Class.name === 'ThreadChannel') {
423
+ instance.messages = stubDeep(Object.create(discord_js.MessageManager.prototype));
424
+ instance.members = stubDeep(Object.create(discord_js.ThreadMemberManager.prototype));
425
+ }
426
+ return stubDeep(instance);
427
+ }
370
428
  /**
371
429
  * Creates a smart mock {@link Message}.
372
430
  *
@@ -374,14 +432,54 @@ function findPrototypeMethod(instance, name) {
374
432
  * `pin()`, and `unpin()` throw if the message has already been deleted.
375
433
  * `edit()` and `reply()` resolve to a new mock `Message` instance.
376
434
  *
435
+ * Constructor-assigned and getter properties are pre-initialized as
436
+ * prototype-based stubs so `msg.author.send`, `msg.member.fetch`,
437
+ * `msg.channel.send`, `msg.guild.members.fetch`, `msg.thread.fetch`,
438
+ * and `msg.mentions.has` all work out of the box.
439
+ *
377
440
  * All methods remain `jest.fn()` — overridable per test.
378
441
  *
379
442
  * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
380
443
  * a `createMockMessage()` by default, matching the official return types.
381
- */ function createMockMessage() {
444
+ */ /**
445
+ * Internal guild stub for use inside createMockMessage.
446
+ * Reuses the same manager stubs as createMockGuild but avoids
447
+ * a circular reference in the module.
448
+ */ function createMockGuildForMessage() {
449
+ const guild = Object.create(discord_js.Guild.prototype);
450
+ guild.members = stubDeep(Object.create(discord_js.GuildMemberManager.prototype));
451
+ guild.channels = stubDeep(Object.create(discord_js.GuildChannelManager.prototype));
452
+ guild.roles = stubDeep(Object.create(discord_js.RoleManager.prototype));
453
+ guild.bans = stubDeep(Object.create(discord_js.GuildBanManager.prototype));
454
+ return stubDeep(guild);
455
+ }
456
+ function createMockMessage() {
382
457
  const instance = Object.create(discord_js.Message.prototype);
383
458
  const stubs = new Map();
384
459
  instance.deleted = false;
460
+ // Constructor-assigned — set as prototype-based stubs
461
+ instance.author = stubDeep(Object.create(discord_js.User.prototype));
462
+ // Getters on the prototype — the proxy sees them as functions and returns
463
+ // jest.fn(), which is wrong. Pre-initialize as own properties to shadow
464
+ // the prototype getters.
465
+ Object.defineProperty(instance, 'member', {
466
+ value: stubDeep(Object.create(discord_js.GuildMember.prototype)),
467
+ writable: true
468
+ });
469
+ Object.defineProperty(instance, 'channel', {
470
+ value: stubDeep(Object.create(discord_js.TextChannel.prototype)),
471
+ writable: true
472
+ });
473
+ Object.defineProperty(instance, 'guild', {
474
+ value: createMockGuildForMessage(),
475
+ writable: true
476
+ });
477
+ Object.defineProperty(instance, 'thread', {
478
+ value: stubDeep(Object.create(discord_js.ThreadChannel.prototype)),
479
+ writable: true
480
+ });
481
+ // MessageMentions — constructor-assigned, has methods like .has(), .members
482
+ instance.mentions = stubDeep(Object.create(discord_js.MessageMentions.prototype));
385
483
  const alreadyDeleted = ()=>new Error('This message has already been deleted.');
386
484
  stubs.set('delete', globals.jest.fn(async ()=>{
387
485
  if (instance.deleted) throw alreadyDeleted();
@@ -1,6 +1,6 @@
1
1
  import 'reflect-metadata';
2
2
  import { jest } from '@jest/globals';
3
- import { InteractionType, ComponentType, ApplicationCommandType, CommandInteractionOptionResolver, Message, User, Client, Guild } 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, Message, User, GuildMember, TextChannel, ThreadChannel, MessageMentions } from 'discord.js';
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
6
  // stubDeep — Proxy that auto-creates jest.fn() on any property access
@@ -250,12 +250,70 @@ function findPrototypeMethod(instance, name) {
250
250
  // Convenience wrappers for common discord.js classes
251
251
  // ---------------------------------------------------------------------------
252
252
  /** Creates a mock {@link User}. All methods are auto-stubbed as `jest.fn()`. */ const createMockUser = ()=>createMockInteraction(User);
253
- /** Creates a mock {@link Client}. Managers like `client.users` are nested stubs. */ const createMockClient = ()=>createMockInteraction(Client);
254
- /** Creates a mock {@link Guild}. Managers like `guild.members` are nested stubs. */ const createMockGuild = ()=>createMockInteraction(Guild);
253
+ /**
254
+ * Creates a mock {@link Client}.
255
+ *
256
+ * Manager methods that are constructor-assigned (not on the prototype) are
257
+ * pre-initialized as `jest.fn()` so they work out of the box without manual
258
+ * setup: `users.fetch`, `channels.fetch`, `guilds.fetch`, and
259
+ * `application.commands.fetch`.
260
+ */ function createMockClient() {
261
+ const instance = Object.create(Client.prototype);
262
+ // Manager properties are constructor-assigned — pre-initialize as prototype-based
263
+ // stubs so ALL manager methods (not just fetch) are auto-stubbed as jest.fn().
264
+ const appInstance = Object.create(null);
265
+ appInstance.commands = stubDeep(Object.create(ApplicationCommandManager.prototype));
266
+ instance.users = stubDeep(Object.create(UserManager.prototype));
267
+ instance.channels = stubDeep(Object.create(ChannelManager.prototype));
268
+ instance.guilds = stubDeep(Object.create(GuildManager.prototype));
269
+ instance.user = stubDeep(Object.create(ClientUser.prototype));
270
+ instance.application = stubDeep(appInstance);
271
+ return stubDeep(instance);
272
+ }
273
+ /**
274
+ * Creates a mock {@link Guild}.
275
+ *
276
+ * Manager properties are constructor-assigned in discord.js. This factory
277
+ * pre-initializes each as a prototype-based stub so all methods are
278
+ * auto-stubbed as `jest.fn()`.
279
+ *
280
+ * Pre-initialized: `members`, `channels`, `roles`, `bans`.
281
+ */ function createMockGuild() {
282
+ const instance = Object.create(Guild.prototype);
283
+ instance.members = stubDeep(Object.create(GuildMemberManager.prototype));
284
+ instance.channels = stubDeep(Object.create(GuildChannelManager.prototype));
285
+ instance.roles = stubDeep(Object.create(RoleManager.prototype));
286
+ instance.bans = stubDeep(Object.create(GuildBanManager.prototype));
287
+ return stubDeep(instance);
288
+ }
255
289
  /**
256
290
  * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
257
- * All methods are auto-stubbed as `jest.fn()`.
258
- */ const createMockChannel = (Class)=>createMockInteraction(Class);
291
+ *
292
+ * Manager properties that are constructor-assigned in discord.js are
293
+ * pre-initialized as prototype-based stubs so all methods are auto-stubbed
294
+ * as `jest.fn()`:
295
+ * - Guild text channels (`TextChannel`, `NewsChannel`): `messages`, `threads`
296
+ * - `DMChannel`: `messages`
297
+ * - `ThreadChannel`: `messages`, `members`
298
+ */ function createMockChannel(Class) {
299
+ const instance = Object.create(Class.prototype);
300
+ // Guild text channels (TextChannel, NewsChannel) — messages & threads
301
+ // assigned in BaseGuildTextChannel constructor
302
+ if ('messages' in Class.prototype || Class.name === 'TextChannel' || Class.name === 'NewsChannel') {
303
+ instance.messages = stubDeep(Object.create(GuildMessageManager.prototype));
304
+ instance.threads = stubDeep(Object.create(ThreadManager.prototype));
305
+ }
306
+ // DMChannel — messages assigned in DMChannel constructor
307
+ if (Class.name === 'DMChannel') {
308
+ instance.messages = stubDeep(Object.create(DMMessageManager.prototype));
309
+ }
310
+ // ThreadChannel — messages & members assigned in ThreadChannel constructor
311
+ if (Class.name === 'ThreadChannel') {
312
+ instance.messages = stubDeep(Object.create(MessageManager.prototype));
313
+ instance.members = stubDeep(Object.create(ThreadMemberManager.prototype));
314
+ }
315
+ return stubDeep(instance);
316
+ }
259
317
  /**
260
318
  * Creates a smart mock {@link Message}.
261
319
  *
@@ -263,14 +321,54 @@ function findPrototypeMethod(instance, name) {
263
321
  * `pin()`, and `unpin()` throw if the message has already been deleted.
264
322
  * `edit()` and `reply()` resolve to a new mock `Message` instance.
265
323
  *
324
+ * Constructor-assigned and getter properties are pre-initialized as
325
+ * prototype-based stubs so `msg.author.send`, `msg.member.fetch`,
326
+ * `msg.channel.send`, `msg.guild.members.fetch`, `msg.thread.fetch`,
327
+ * and `msg.mentions.has` all work out of the box.
328
+ *
266
329
  * All methods remain `jest.fn()` — overridable per test.
267
330
  *
268
331
  * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
269
332
  * a `createMockMessage()` by default, matching the official return types.
270
- */ function createMockMessage() {
333
+ */ /**
334
+ * Internal guild stub for use inside createMockMessage.
335
+ * Reuses the same manager stubs as createMockGuild but avoids
336
+ * a circular reference in the module.
337
+ */ function createMockGuildForMessage() {
338
+ const guild = Object.create(Guild.prototype);
339
+ guild.members = stubDeep(Object.create(GuildMemberManager.prototype));
340
+ guild.channels = stubDeep(Object.create(GuildChannelManager.prototype));
341
+ guild.roles = stubDeep(Object.create(RoleManager.prototype));
342
+ guild.bans = stubDeep(Object.create(GuildBanManager.prototype));
343
+ return stubDeep(guild);
344
+ }
345
+ function createMockMessage() {
271
346
  const instance = Object.create(Message.prototype);
272
347
  const stubs = new Map();
273
348
  instance.deleted = false;
349
+ // Constructor-assigned — set as prototype-based stubs
350
+ instance.author = stubDeep(Object.create(User.prototype));
351
+ // Getters on the prototype — the proxy sees them as functions and returns
352
+ // jest.fn(), which is wrong. Pre-initialize as own properties to shadow
353
+ // the prototype getters.
354
+ Object.defineProperty(instance, 'member', {
355
+ value: stubDeep(Object.create(GuildMember.prototype)),
356
+ writable: true
357
+ });
358
+ Object.defineProperty(instance, 'channel', {
359
+ value: stubDeep(Object.create(TextChannel.prototype)),
360
+ writable: true
361
+ });
362
+ Object.defineProperty(instance, 'guild', {
363
+ value: createMockGuildForMessage(),
364
+ writable: true
365
+ });
366
+ Object.defineProperty(instance, 'thread', {
367
+ value: stubDeep(Object.create(ThreadChannel.prototype)),
368
+ writable: true
369
+ });
370
+ // MessageMentions — constructor-assigned, has methods like .has(), .members
371
+ instance.mentions = stubDeep(Object.create(MessageMentions.prototype));
274
372
  const alreadyDeleted = ()=>new Error('This message has already been deleted.');
275
373
  stubs.set('delete', jest.fn(async ()=>{
276
374
  if (instance.deleted) throw alreadyDeleted();
@@ -117,27 +117,36 @@ interface InteractionClass<T> {
117
117
  declare function createMockInteraction<T extends object>(Class: InteractionClass<T>): DeepMocked<T>;
118
118
  /** Creates a mock {@link User}. All methods are auto-stubbed as `jest.fn()`. */
119
119
  declare const createMockUser: () => DeepMocked<User>;
120
- /** Creates a mock {@link Client}. Managers like `client.users` are nested stubs. */
121
- declare const createMockClient: () => DeepMocked<Client>;
122
- /** Creates a mock {@link Guild}. Managers like `guild.members` are nested stubs. */
123
- declare const createMockGuild: () => DeepMocked<Guild>;
124
120
  /**
125
- * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
126
- * All methods are auto-stubbed as `jest.fn()`.
121
+ * Creates a mock {@link Client}.
122
+ *
123
+ * Manager methods that are constructor-assigned (not on the prototype) are
124
+ * pre-initialized as `jest.fn()` so they work out of the box without manual
125
+ * setup: `users.fetch`, `channels.fetch`, `guilds.fetch`, and
126
+ * `application.commands.fetch`.
127
127
  */
128
- declare const createMockChannel: <T extends Channel>(Class: InteractionClass<T>) => DeepMocked<T>;
128
+ declare function createMockClient(): DeepMocked<Client>;
129
129
  /**
130
- * Creates a smart mock {@link Message}.
130
+ * Creates a mock {@link Guild}.
131
131
  *
132
- * Tracks a `deleted` boolean. `delete()`, `edit()`, `reply()`, `react()`,
133
- * `pin()`, and `unpin()` throw if the message has already been deleted.
134
- * `edit()` and `reply()` resolve to a new mock `Message` instance.
132
+ * Manager properties are constructor-assigned in discord.js. This factory
133
+ * pre-initializes each as a prototype-based stub so all methods are
134
+ * auto-stubbed as `jest.fn()`.
135
135
  *
136
- * All methods remain `jest.fn()` — overridable per test.
136
+ * Pre-initialized: `members`, `channels`, `roles`, `bans`.
137
+ */
138
+ declare function createMockGuild(): DeepMocked<Guild>;
139
+ /**
140
+ * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
137
141
  *
138
- * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
139
- * a `createMockMessage()` by default, matching the official return types.
142
+ * Manager properties that are constructor-assigned in discord.js are
143
+ * pre-initialized as prototype-based stubs so all methods are auto-stubbed
144
+ * as `jest.fn()`:
145
+ * - Guild text channels (`TextChannel`, `NewsChannel`): `messages`, `threads`
146
+ * - `DMChannel`: `messages`
147
+ * - `ThreadChannel`: `messages`, `members`
140
148
  */
149
+ declare function createMockChannel<T extends Channel>(Class: InteractionClass<T>): DeepMocked<T>;
141
150
  declare function createMockMessage(): DeepMocked<Message>;
142
151
  interface ChatInputOptions {
143
152
  subcommandGroup?: string | null;
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": "1.8.0",
4
+ "version": "1.8.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "lint": "eslint --fix . && tsc --noEmit && tsc --noEmit --project tsconfig.test.json",