meocord 1.8.1 → 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.
@@ -399,8 +399,32 @@ function findPrototypeMethod(instance, name) {
399
399
  }
400
400
  /**
401
401
  * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
402
- * All methods are auto-stubbed as `jest.fn()`.
403
- */ 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
+ }
404
428
  /**
405
429
  * Creates a smart mock {@link Message}.
406
430
  *
@@ -408,14 +432,54 @@ function findPrototypeMethod(instance, name) {
408
432
  * `pin()`, and `unpin()` throw if the message has already been deleted.
409
433
  * `edit()` and `reply()` resolve to a new mock `Message` instance.
410
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
+ *
411
440
  * All methods remain `jest.fn()` — overridable per test.
412
441
  *
413
442
  * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
414
443
  * a `createMockMessage()` by default, matching the official return types.
415
- */ 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() {
416
457
  const instance = Object.create(discord_js.Message.prototype);
417
458
  const stubs = new Map();
418
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));
419
483
  const alreadyDeleted = ()=>new Error('This message has already been deleted.');
420
484
  stubs.set('delete', globals.jest.fn(async ()=>{
421
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, Client, ApplicationCommandManager, UserManager, ChannelManager, GuildManager, ClientUser, Guild, GuildMemberManager, GuildChannelManager, RoleManager, GuildBanManager, Message, User } 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
@@ -288,8 +288,32 @@ function findPrototypeMethod(instance, name) {
288
288
  }
289
289
  /**
290
290
  * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
291
- * All methods are auto-stubbed as `jest.fn()`.
292
- */ 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
+ }
293
317
  /**
294
318
  * Creates a smart mock {@link Message}.
295
319
  *
@@ -297,14 +321,54 @@ function findPrototypeMethod(instance, name) {
297
321
  * `pin()`, and `unpin()` throw if the message has already been deleted.
298
322
  * `edit()` and `reply()` resolve to a new mock `Message` instance.
299
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
+ *
300
329
  * All methods remain `jest.fn()` — overridable per test.
301
330
  *
302
331
  * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
303
332
  * a `createMockMessage()` by default, matching the official return types.
304
- */ 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() {
305
346
  const instance = Object.create(Message.prototype);
306
347
  const stubs = new Map();
307
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));
308
372
  const alreadyDeleted = ()=>new Error('This message has already been deleted.');
309
373
  stubs.set('delete', jest.fn(async ()=>{
310
374
  if (instance.deleted) throw alreadyDeleted();
@@ -138,21 +138,15 @@ declare function createMockClient(): DeepMocked<Client>;
138
138
  declare function createMockGuild(): DeepMocked<Guild>;
139
139
  /**
140
140
  * Creates a mock channel of the given class (e.g. `TextChannel`, `DMChannel`).
141
- * All methods are auto-stubbed as `jest.fn()`.
142
- */
143
- declare const createMockChannel: <T extends Channel>(Class: InteractionClass<T>) => DeepMocked<T>;
144
- /**
145
- * Creates a smart mock {@link Message}.
146
- *
147
- * Tracks a `deleted` boolean. `delete()`, `edit()`, `reply()`, `react()`,
148
- * `pin()`, and `unpin()` throw if the message has already been deleted.
149
- * `edit()` and `reply()` resolve to a new mock `Message` instance.
150
- *
151
- * All methods remain `jest.fn()` — overridable per test.
152
141
  *
153
- * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
154
- * 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`
155
148
  */
149
+ declare function createMockChannel<T extends Channel>(Class: InteractionClass<T>): DeepMocked<T>;
156
150
  declare function createMockMessage(): DeepMocked<Message>;
157
151
  interface ChatInputOptions {
158
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.1",
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",