meocord 4.2.0 → 4.2.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +264 -0
  2. package/THIRD_PARTY_NOTICES.md +14 -13
  3. package/dist/cjs/_shared/{controller.decorator-CRn9kWob.cjs → controller.decorator-XKMlcUZz.cjs} +376 -100
  4. package/dist/cjs/_shared/{providers-ChvMqnLd.cjs → providers-BZgJyfZS.cjs} +145 -36
  5. package/dist/cjs/_shared/{sharded-cooldown-store-BdAUTdYs.cjs → sharded-cooldown-store-CsJJyCZd.cjs} +10 -7
  6. package/dist/cjs/_shared/{startup-roots-CQo8mSP8.cjs → startup-roots-CgfPQrNl.cjs} +219 -63
  7. package/dist/cjs/_shared/{translate-error-DhDVbT5M.cjs → translate-error-BUAR1WYG.cjs} +10 -5
  8. package/dist/cjs/common/index.cjs +3 -3
  9. package/dist/cjs/core/index.cjs +185 -168
  10. package/dist/cjs/decorator/index.cjs +21 -6
  11. package/dist/cjs/testing/index.cjs +1594 -286
  12. package/dist/cli.json +1 -1
  13. package/dist/esm/bin/generator.js +4 -5
  14. package/dist/esm/bin/helper/filter-generator.helper.js +4 -5
  15. package/dist/esm/bin/helper/guard-generator.helper.js +4 -5
  16. package/dist/esm/bin/helper/interceptor-generator.helper.js +4 -5
  17. package/dist/esm/bin/helper/observer-generator.helper.js +4 -5
  18. package/dist/esm/bin/helper/pipe-generator.helper.js +4 -5
  19. package/dist/esm/bin/helper/service-generator.helper.js +4 -5
  20. package/dist/esm/bin/meocord.js +84 -25
  21. package/dist/esm/build/load-config.pre-entry.js +13 -3
  22. package/dist/esm/build/rsbuild-config.js +16 -12
  23. package/dist/esm/build/typed-externals.js +36 -0
  24. package/dist/esm/common/cooldown-store.js +10 -2
  25. package/dist/esm/common/errors.js +25 -0
  26. package/dist/esm/common/meocord-text.js +11 -8
  27. package/dist/esm/common/response/call-order.js +9 -4
  28. package/dist/esm/common/response/presenter.js +33 -6
  29. package/dist/esm/common/response/response-state.js +84 -10
  30. package/dist/esm/common/route.js +11 -4
  31. package/dist/esm/common/sharded-cooldown-store.js +1 -1
  32. package/dist/esm/common/translator.js +37 -6
  33. package/dist/esm/core/command-registration.js +93 -24
  34. package/dist/esm/core/command-routes.js +2 -2
  35. package/dist/esm/core/cooldown-runner.js +29 -8
  36. package/dist/esm/core/fallback.js +20 -11
  37. package/dist/esm/core/guard-runner.js +16 -3
  38. package/dist/esm/core/handler-input.js +4 -3
  39. package/dist/esm/core/lifecycle-hooks.js +134 -47
  40. package/dist/esm/core/meocord-factory.js +55 -32
  41. package/dist/esm/core/meocord.app.js +116 -75
  42. package/dist/esm/core/providers.js +7 -2
  43. package/dist/esm/core/running-calls.js +43 -0
  44. package/dist/esm/core/shard-context.js +5 -6
  45. package/dist/esm/core/shard-manager.js +16 -10
  46. package/dist/esm/core/startup-roots.js +21 -1
  47. package/dist/esm/core/theme-resolvers.js +4 -1
  48. package/dist/esm/core/theme-scope.js +3 -1
  49. package/dist/esm/core/theme-validation.js +17 -10
  50. package/dist/esm/decorator/controller.decorator.js +56 -20
  51. package/dist/esm/decorator/cooldown.decorator.js +17 -0
  52. package/dist/esm/decorator/guard.decorator.js +3 -2
  53. package/dist/esm/package.json.js +1 -1
  54. package/dist/esm/testing/catalog.js +26 -10
  55. package/dist/esm/testing/cooldown-store-suite.js +67 -5
  56. package/dist/esm/testing/discord-defaults.js +205 -30
  57. package/dist/esm/testing/meocord-testing-module.js +134 -28
  58. package/dist/esm/testing/mock-fn.js +107 -26
  59. package/dist/esm/testing/mock-interaction.js +1026 -172
  60. package/dist/esm/testing/snowflake.js +9 -2
  61. package/dist/esm/testing/strict-mocks.js +37 -17
  62. package/dist/esm/util/common.util.js +47 -19
  63. package/dist/esm/util/dev-runner.util.js +8 -8
  64. package/dist/esm/util/generator-cli.util.js +4 -5
  65. package/dist/esm/util/interaction.util.js +2 -2
  66. package/dist/esm/util/meocord-config-loader.util.js +10 -2
  67. package/dist/esm/util/meocord-config-validation.util.js +3 -5
  68. package/dist/esm/util/meocord-source-config.util.js +65 -20
  69. package/dist/esm/util/method-wrapper.util.js +16 -0
  70. package/dist/esm/util/platform.util.js +10 -10
  71. package/dist/esm/util/shard-process.util.js +4 -1
  72. package/dist/esm/util/sharding-mode.util.js +5 -7
  73. package/dist/esm/util/shared-state.util.js +21 -0
  74. package/dist/esm/util/tsconfig-paths.util.js +29 -0
  75. package/dist/esm/util/tsconfig.util.js +10 -10
  76. package/dist/esm/util/value.util.js +12 -1
  77. package/dist/types/common/index.d.cts +6 -6
  78. package/dist/types/common/index.d.ts +6 -6
  79. package/dist/types/core/index.d.cts +3 -2
  80. package/dist/types/core/index.d.ts +3 -2
  81. package/dist/types/decorator/index.d.cts +1 -0
  82. package/dist/types/decorator/index.d.ts +1 -0
  83. package/dist/types/{index-CdXsFgjE.d.ts → index-DKJclIi-.d.ts} +45 -21
  84. package/dist/types/{index-nn5p7OoX.d.cts → index-Dlf0ZZtS.d.cts} +45 -21
  85. package/dist/types/interface/index.d.cts +1 -1
  86. package/dist/types/interface/index.d.ts +1 -1
  87. package/dist/types/{route-BZgFoNeB.d.cts → route-Bh1U13yy.d.cts} +3 -2
  88. package/dist/types/{route-BZgFoNeB.d.ts → route-Bh1U13yy.d.ts} +3 -2
  89. package/dist/types/testing/index.d.cts +175 -53
  90. package/dist/types/testing/index.d.ts +175 -53
  91. package/dist/types/{theme-resolvers-UKr3-6Ys.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
  92. package/dist/types/{theme-resolvers-BcOJVzR9.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
  93. package/dist/types/{translator-DHMkkCpg.d.cts → translator-Bg7hwxDF.d.cts} +21 -8
  94. package/dist/types/{translator-BViWKtLJ.d.ts → translator-CeP4bCzp.d.ts} +21 -8
  95. package/package.json +2 -2
@@ -1,10 +1,66 @@
1
- import { Message, Guild, TextChannel, ThreadChannel, GuildChannel, BaseGuildVoiceChannel, MessageReaction, GuildPremiumTier, GuildVerificationLevel, GuildNSFWLevel, Locale, ClientUser, User, GuildMember, Role, Collection, MessageType, CommandInteraction, AutocompleteInteraction, PermissionsBitField, BaseInteraction, ChannelType, NewsChannel, VoiceChannel, ForumChannel, MediaChannel, DMChannel, BaseChannel, Client, BaseGuild, SnowflakeUtil } from 'discord.js';
2
- import { createMockChannel, createMockGuild, memberRoles, createMockUser, isRawMember } from './mock-interaction.js';
1
+ import { GuildChannel, MessageReaction, MessageMentions, Message, GuildMember, BaseGuild, Guild, ModalSubmitInteraction, TextChannel, ThreadChannel, BaseGuildVoiceChannel, GuildPremiumTier, GuildVerificationLevel, GuildNSFWLevel, Locale, PresenceManager, ClientUser, UserFlagsBitField, User, GuildMemberFlagsBitField, Role, Collection, MessageType, ReactionManager, ChatInputCommandInteraction, CommandInteraction, AutocompleteInteraction, ReactionUserManager, BaseInteraction, ChannelType, NewsChannel, VoiceChannel, ForumChannel, MediaChannel, DMChannel, BaseChannel, Client, PermissionsBitField, PermissionFlagsBits } from 'discord.js';
2
+ import { createMockChannel, createMockGuild, createMockMessage, ownedManager, memberRoles, createMockUser, createChatInputOptions, readPlaceAs, botMemberOf, heldChannelOf, isRawMember, isUnsetEveryone, NEW_SERVER_EVERYONE_PERMISSIONS } from './mock-interaction.js';
3
+ import { nextSnowflake } from './snowflake.js';
3
4
  import { strictMocks } from './strict-mocks.js';
5
+ import { Logger } from '../common/logger.js';
6
+ import { warnOnce } from '../common/deprecation.js';
4
7
 
8
+ const mockLogger = new Logger('Mocks');
9
+ /**
10
+ * A member's permissions where an interaction was made, as Discord computes them: under strict mocks, every permission
11
+ * for an Administrator or the owner, else the channel's overwrites on top of `member.permissions`, so a test that sets
12
+ * those keeps the base; otherwise `member.permissions`, with a warning once where strict mocks would read otherwise.
13
+ */ function inChannel(interaction, member) {
14
+ const base = member.permissions;
15
+ // Only a server the mock caches has the overwrites to apply
16
+ const { guild } = interaction;
17
+ if (!(guild instanceof Guild)) return base;
18
+ const computed = base.has(PermissionFlagsBits.Administrator) || member.id === guild.ownerId ? new PermissionsBitField(PermissionsBitField.All).freeze() : withOverwrites(base, overwritingChannel(interaction, guild), member);
19
+ if (strictMocks()) return computed;
20
+ if (!computed.equals(base)) {
21
+ const lost = new PermissionsBitField(base).remove(computed).toArray();
22
+ const gained = new PermissionsBitField(computed).remove(base).toArray();
23
+ const change = lost.length > 0 ? [
24
+ `without ${lost.join(', ')}`
25
+ ] : [];
26
+ if (gained.length > 0) change.push(`with ${gained.join(', ')}`);
27
+ warnOnce(mockLogger, `${interaction.constructor.name}.memberPermissions reads the member's own permissions here, without the ` + "channel's overwrites or every permission an Administrator or the owner has; in the next major version (5.0) " + `it reads them as Discord computes them in the channel, ${change.join(' and ')}. Set memberPermissions on the ` + 'mock, or call useStrictMocks() to compute them now.');
28
+ }
29
+ // Unless the test set them, a member's permissions come from its roles, @everyone's included
30
+ if (!Object.prototype.hasOwnProperty.call(member, 'permissions')) warnOnUnsetEveryone(interaction, guild, 'memberPermissions', base);
31
+ return base;
32
+ }
33
+ /**
34
+ * Warns once, as `key`, where `permissions` read a server's @everyone role default mode made with none, and lack what
35
+ * strict mocks give @everyone in a new server.
36
+ */ function warnOnUnsetEveryone(interaction, guild, key, permissions) {
37
+ const everyone = guild.roles.everyone;
38
+ if (!everyone || !isUnsetEveryone(everyone) || permissions.has(NEW_SERVER_EVERYONE_PERMISSIONS)) return;
39
+ warnOnce(mockLogger, `${interaction.constructor.name}.${key} reads the server's @everyone role here with no permissions; in the next major ` + 'version (5.0) it has the permissions Discord gives @everyone in a new server, as strict mocks read it. Set ' + 'guild.roles.everyone.permissions, or call useStrictMocks() to read it so now.');
40
+ }
41
+ /**
42
+ * The channel whose overwrites apply where an interaction was made, read without making or caching one: a thread's
43
+ * parent, or undefined where the channel, or a thread's parent, is one the mock would make, which has none.
44
+ */ function overwritingChannel(interaction, guild) {
45
+ const channel = heldChannelOf(interaction, guild);
46
+ // A thread's parent by the id the test gave it or one already read, so strict mocks' parent isn't made for this read
47
+ const parentId = (thread)=>Object.prototype.hasOwnProperty.call(thread, 'parentId') ? thread.parentId : madeParentIds.get(thread);
48
+ const at = channel instanceof ThreadChannel ? guild.channels.cache.get(parentId(channel) ?? '') : channel;
49
+ return at instanceof GuildChannel ? at : undefined;
50
+ }
51
+ // The parent id strict mocks made for each thread made without one, once read
52
+ const madeParentIds = new WeakMap();
53
+ // Private in discord.js's typings, though they are what its own permissionsFor computes with; called on the prototype
54
+ // so a test's stub on the channel stays for its caller
55
+ const channelPermissions = GuildChannel.prototype;
56
+ /** `base` with `channel`'s overwrites for `member` applied, in discord.js's order: @everyone's, the roles', the member's. */ function withOverwrites(base, channel, member) {
57
+ if (!channel) return base;
58
+ // Given the member and its roles as verified, as discord.js's memberPermissions does, so it resolves nothing again
59
+ const overwrites = channelPermissions.overwritesFor.call(channel, member, true, member.roles.cache);
60
+ return new PermissionsBitField(base).remove(overwrites.everyone?.deny ?? 0n).add(overwrites.everyone?.allow ?? 0n).remove(overwrites.roles.length > 0 ? overwrites.roles.map((role)=>role.deny) : 0n).add(overwrites.roles.length > 0 ? overwrites.roles.map((role)=>role.allow) : 0n).remove(overwrites.member?.deny ?? 0n).add(overwrites.member?.allow ?? 0n).freeze();
61
+ }
5
62
  /** A permission set with none, frozen as discord.js freezes the ones it gives. */ const noPermissions = ()=>new PermissionsBitField().freeze();
6
63
  /** A property discord.js computes from the others: its own getter runs, reading the mock's values. */ const REAL_GETTER = Symbol('real getter');
7
- const snowflake = ()=>SnowflakeUtil.generate().toString();
8
64
  /** A class and its defaults, typed together so a default that stops fitting its property fails to compile. */ const defaultsOf = (Class, defaults)=>[
9
65
  Class.prototype,
10
66
  defaults
@@ -17,7 +73,7 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
17
73
  defaultsOf(Guild, {
18
74
  name: ()=>'Guild',
19
75
  preferredLocale: ()=>Locale.EnglishUS,
20
- ownerId: snowflake,
76
+ ownerId: nextSnowflake,
21
77
  memberCount: ()=>1,
22
78
  available: ()=>true,
23
79
  large: ()=>false,
@@ -30,6 +86,9 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
30
86
  shardId: ()=>0,
31
87
  nsfwLevel: ()=>GuildNSFWLevel.Default,
32
88
  verificationLevel: ()=>GuildVerificationLevel.None,
89
+ presences: (guild)=>ownedManager(PresenceManager.prototype, {
90
+ guild
91
+ }),
33
92
  premiumTier: ()=>GuildPremiumTier.None,
34
93
  createdTimestamp: REAL_GETTER,
35
94
  createdAt: REAL_GETTER
@@ -44,6 +103,7 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
44
103
  discriminator: ()=>'0',
45
104
  avatar: ()=>null,
46
105
  system: ()=>false,
106
+ flags: ()=>new UserFlagsBitField(),
47
107
  tag: REAL_GETTER,
48
108
  displayName: REAL_GETTER,
49
109
  defaultAvatarURL: REAL_GETTER,
@@ -64,12 +124,12 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
64
124
  premiumSinceTimestamp: ()=>null,
65
125
  communicationDisabledUntilTimestamp: ()=>null,
66
126
  pending: ()=>false,
127
+ flags: ()=>new GuildMemberFlagsBitField().freeze(),
67
128
  displayName: REAL_GETTER,
68
129
  roles: (member)=>memberRoles(member, []),
69
130
  permissions: REAL_GETTER
70
131
  }),
71
132
  defaultsOf(Role, {
72
- id: snowflake,
73
133
  name: ()=>'role',
74
134
  // Above @everyone, which a server's role manager makes at position 0
75
135
  position: ()=>1,
@@ -107,37 +167,71 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
107
167
  poll: ()=>null,
108
168
  interactionMetadata: ()=>null,
109
169
  stickers: ()=>new Collection(),
170
+ messageSnapshots: ()=>new Collection(),
171
+ reactions: (message)=>ownedManager(ReactionManager.prototype, {
172
+ message
173
+ }),
110
174
  url: REAL_GETTER
111
175
  }),
112
176
  // A command's name and a component's customId say which handler a call is for, so they stay the test's to give
177
+ defaultsOf(ChatInputCommandInteraction, {
178
+ options: ()=>createChatInputOptions()
179
+ }),
113
180
  defaultsOf(CommandInteraction, {
114
- commandId: snowflake,
181
+ commandId: nextSnowflake,
115
182
  commandGuildId: ()=>null
116
183
  }),
117
184
  defaultsOf(AutocompleteInteraction, {
118
- commandId: snowflake,
119
- commandGuildId: ()=>null
185
+ commandId: nextSnowflake,
186
+ commandGuildId: ()=>null,
187
+ options: ()=>createChatInputOptions()
188
+ }),
189
+ defaultsOf(MessageReaction, {
190
+ users: (reaction)=>ownedManager(ReactionUserManager.prototype, {
191
+ reaction
192
+ })
120
193
  }),
121
194
  defaultsOf(BaseInteraction, {
122
- applicationId: snowflake,
195
+ applicationId: nextSnowflake,
123
196
  token: ()=>'mock-interaction-token',
124
197
  version: ()=>1,
125
198
  context: ()=>null,
199
+ entitlements: ()=>new Collection(),
126
200
  // What Discord sends with an interaction in a server, the member's permissions there; typed for a cached one
127
- memberPermissions: (interaction)=>{
128
- const { guildId } = interaction;
129
- const member = interaction.member;
130
- if (!guildId) return null;
131
- if (member instanceof GuildMember) return member.permissions;
132
- // A raw member's permissions are the string Discord sends, read as discord.js reads them
133
- return isRawMember(member) && !interaction.guild ? new PermissionsBitField(BigInt(member.permissions)).freeze() : null;
134
- },
135
- // The bot's permissions where the interaction was made: its member's in the channel, or none outside a server
136
- appPermissions: (interaction)=>{
137
- const { guild, channel } = interaction;
138
- const me = guild instanceof Guild ? guild.members.me : null;
139
- return (me && channel instanceof GuildChannel ? channel.permissionsFor(me) : null) ?? noPermissions();
140
- }
201
+ // Each reads where the interaction is as its own, and warns by its own name where strict mocks place it otherwise
202
+ memberPermissions: (interaction)=>readPlaceAs(interaction, 'memberPermissions', ()=>{
203
+ const { guildId } = interaction;
204
+ const member = interaction.member;
205
+ if (!guildId) return null;
206
+ if (member instanceof GuildMember) return inChannel(interaction, member);
207
+ // A raw member's permissions are the string Discord sends, read as discord.js reads them
208
+ return isRawMember(member) && !interaction.guild ? new PermissionsBitField(BigInt(member.permissions)).freeze() : null;
209
+ }),
210
+ // The bot's permissions where the interaction was made: its member's in the channel, or in a thread its parent, as
211
+ // discord.js computes them; none outside a server
212
+ appPermissions: (interaction)=>readPlaceAs(interaction, 'appPermissions', ()=>{
213
+ const { guild } = interaction;
214
+ const me = guild instanceof Guild ? botMemberOf(guild) : null;
215
+ if (!(me instanceof GuildMember) || heldChannelOf(interaction, guild) === null) return noPermissions();
216
+ // discord.js's own computation, given its own overwritesFor over a test's stub on the channel; a channel the
217
+ // mock would make has no overwrites
218
+ const at = overwritingChannel(interaction, guild) ?? {
219
+ guild,
220
+ permissionOverwrites: {
221
+ cache: new Collection()
222
+ }
223
+ };
224
+ const channel = Object.create(at, {
225
+ overwritesFor: {
226
+ value: channelPermissions.overwritesFor
227
+ }
228
+ });
229
+ const permissions = channelPermissions.memberPermissions.call(channel, me, true);
230
+ // The bot's member has @everyone's too
231
+ warnOnUnsetEveryone(interaction, guild, 'appPermissions', permissions);
232
+ return permissions;
233
+ }, // A server the bot isn't in has no guild in either mode, so no permissions in both
234
+ (place)=>!place.raw)
141
235
  }),
142
236
  defaultsOf(TextChannel, {
143
237
  type: ()=>ChannelType.GuildText,
@@ -173,7 +267,7 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
173
267
  }),
174
268
  defaultsOf(GuildChannel, {
175
269
  name: ()=>'channel',
176
- guildId: (channel)=>typeof channel.guild?.id === 'string' ? channel.guild.id : snowflake(),
270
+ guildId: (channel)=>typeof channel.guild?.id === 'string' ? channel.guild.id : nextSnowflake(),
177
271
  position: ()=>0,
178
272
  rawPosition: ()=>0,
179
273
  parentId: ()=>null
@@ -197,11 +291,26 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
197
291
  * thread made without one, a thread's parent text channel in it, and data that is otherwise a placeholder.
198
292
  */ const STRICT_DEFAULTS = new Map([
199
293
  defaultsOf(Message, {
200
- content: ()=>''
294
+ content: ()=>'',
295
+ editedTimestamp: ()=>null,
296
+ editedAt: REAL_GETTER
297
+ }),
298
+ defaultsOf(MessageMentions, {
299
+ repliedUser: ()=>null
201
300
  }),
202
301
  defaultsOf(Guild, {
203
302
  rulesChannelId: ()=>null,
204
- publicUpdatesChannelId: ()=>null
303
+ publicUpdatesChannelId: ()=>null,
304
+ systemChannel: REAL_GETTER
305
+ }),
306
+ defaultsOf(BaseGuild, {
307
+ verified: REAL_GETTER
308
+ }),
309
+ defaultsOf(GuildMember, {
310
+ presence: REAL_GETTER
311
+ }),
312
+ defaultsOf(ModalSubmitInteraction, {
313
+ message: ()=>null
205
314
  }),
206
315
  defaultsOf(ThreadChannel, {
207
316
  guild: ()=>createMockGuild(),
@@ -211,25 +320,91 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
211
320
  guild: thread.guild
212
321
  });
213
322
  thread.guild.channels.cache.set(parent.id, parent);
323
+ madeParentIds.set(thread, parent.id);
214
324
  return parent.id;
215
325
  },
216
326
  parent: REAL_GETTER,
217
- ownerId: snowflake,
327
+ ownerId: nextSnowflake,
218
328
  archived: ()=>false,
219
329
  locked: ()=>false
220
330
  }),
221
331
  defaultsOf(GuildChannel, {
222
- guild: ()=>createMockGuild()
332
+ guild: ()=>createMockGuild(),
333
+ parent: REAL_GETTER
223
334
  }),
224
335
  // Full once its members reach a user limit, which a voice channel has none of unless given one
225
336
  defaultsOf(BaseGuildVoiceChannel, {
226
337
  userLimit: ()=>0,
227
338
  full: REAL_GETTER
228
339
  }),
340
+ // A reaction's message, as discord.js always has one, is a whole one
229
341
  defaultsOf(MessageReaction, {
230
- count: ()=>1
342
+ count: ()=>1,
343
+ me: ()=>false,
344
+ message: ()=>createMockMessage()
231
345
  })
232
346
  ]);
347
+ /**
348
+ * What a mock reads as a placeholder unless strict mocks read it as discord.js gives it, by the value a test sets for
349
+ * that, which the warning on reading one names.
350
+ */ const PLACEHOLDER_VALUES = new Map([
351
+ [
352
+ MessageReaction.prototype,
353
+ {
354
+ me: 'false',
355
+ message: 'createMockMessage()'
356
+ }
357
+ ],
358
+ [
359
+ MessageMentions.prototype,
360
+ {
361
+ repliedUser: 'null'
362
+ }
363
+ ],
364
+ [
365
+ Message.prototype,
366
+ {
367
+ editedAt: 'null'
368
+ }
369
+ ],
370
+ [
371
+ GuildMember.prototype,
372
+ {
373
+ presence: 'null'
374
+ }
375
+ ],
376
+ [
377
+ BaseGuild.prototype,
378
+ {
379
+ verified: 'false'
380
+ }
381
+ ],
382
+ [
383
+ Guild.prototype,
384
+ {
385
+ systemChannel: 'null'
386
+ }
387
+ ],
388
+ [
389
+ GuildChannel.prototype,
390
+ {
391
+ parent: 'null'
392
+ }
393
+ ],
394
+ [
395
+ ModalSubmitInteraction.prototype,
396
+ {
397
+ message: 'null'
398
+ }
399
+ ]
400
+ ]);
401
+ /** The value a test sets for `key` on a mock of `target`'s class, when a mock reads it as a placeholder. */ function placeholderValue(target, key) {
402
+ for(let proto = Object.getPrototypeOf(target); proto !== null; proto = Object.getPrototypeOf(proto)){
403
+ const value = PLACEHOLDER_VALUES.get(proto)?.[key];
404
+ if (value !== undefined) return value;
405
+ }
406
+ return undefined;
407
+ }
233
408
  /** The default a mock of `target`'s class has for `key`, read from the nearest class that has one, strict ones first. */ function discordDefault(target, key) {
234
409
  for (const defaults of strictMocks() ? [
235
410
  STRICT_DEFAULTS,
@@ -245,4 +420,4 @@ const snowflake = ()=>SnowflakeUtil.generate().toString();
245
420
  return undefined;
246
421
  }
247
422
 
248
- export { REAL_GETTER, discordDefault };
423
+ export { REAL_GETTER, discordDefault, placeholderValue };