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.
- package/CHANGELOG.md +264 -0
- package/THIRD_PARTY_NOTICES.md +14 -13
- package/dist/cjs/_shared/{controller.decorator-CRn9kWob.cjs → controller.decorator-XKMlcUZz.cjs} +376 -100
- package/dist/cjs/_shared/{providers-ChvMqnLd.cjs → providers-BZgJyfZS.cjs} +145 -36
- package/dist/cjs/_shared/{sharded-cooldown-store-BdAUTdYs.cjs → sharded-cooldown-store-CsJJyCZd.cjs} +10 -7
- package/dist/cjs/_shared/{startup-roots-CQo8mSP8.cjs → startup-roots-CgfPQrNl.cjs} +219 -63
- package/dist/cjs/_shared/{translate-error-DhDVbT5M.cjs → translate-error-BUAR1WYG.cjs} +10 -5
- package/dist/cjs/common/index.cjs +3 -3
- package/dist/cjs/core/index.cjs +185 -168
- package/dist/cjs/decorator/index.cjs +21 -6
- package/dist/cjs/testing/index.cjs +1594 -286
- package/dist/cli.json +1 -1
- package/dist/esm/bin/generator.js +4 -5
- package/dist/esm/bin/helper/filter-generator.helper.js +4 -5
- package/dist/esm/bin/helper/guard-generator.helper.js +4 -5
- package/dist/esm/bin/helper/interceptor-generator.helper.js +4 -5
- package/dist/esm/bin/helper/observer-generator.helper.js +4 -5
- package/dist/esm/bin/helper/pipe-generator.helper.js +4 -5
- package/dist/esm/bin/helper/service-generator.helper.js +4 -5
- package/dist/esm/bin/meocord.js +84 -25
- package/dist/esm/build/load-config.pre-entry.js +13 -3
- package/dist/esm/build/rsbuild-config.js +16 -12
- package/dist/esm/build/typed-externals.js +36 -0
- package/dist/esm/common/cooldown-store.js +10 -2
- package/dist/esm/common/errors.js +25 -0
- package/dist/esm/common/meocord-text.js +11 -8
- package/dist/esm/common/response/call-order.js +9 -4
- package/dist/esm/common/response/presenter.js +33 -6
- package/dist/esm/common/response/response-state.js +84 -10
- package/dist/esm/common/route.js +11 -4
- package/dist/esm/common/sharded-cooldown-store.js +1 -1
- package/dist/esm/common/translator.js +37 -6
- package/dist/esm/core/command-registration.js +93 -24
- package/dist/esm/core/command-routes.js +2 -2
- package/dist/esm/core/cooldown-runner.js +29 -8
- package/dist/esm/core/fallback.js +20 -11
- package/dist/esm/core/guard-runner.js +16 -3
- package/dist/esm/core/handler-input.js +4 -3
- package/dist/esm/core/lifecycle-hooks.js +134 -47
- package/dist/esm/core/meocord-factory.js +55 -32
- package/dist/esm/core/meocord.app.js +116 -75
- package/dist/esm/core/providers.js +7 -2
- package/dist/esm/core/running-calls.js +43 -0
- package/dist/esm/core/shard-context.js +5 -6
- package/dist/esm/core/shard-manager.js +16 -10
- package/dist/esm/core/startup-roots.js +21 -1
- package/dist/esm/core/theme-resolvers.js +4 -1
- package/dist/esm/core/theme-scope.js +3 -1
- package/dist/esm/core/theme-validation.js +17 -10
- package/dist/esm/decorator/controller.decorator.js +56 -20
- package/dist/esm/decorator/cooldown.decorator.js +17 -0
- package/dist/esm/decorator/guard.decorator.js +3 -2
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +26 -10
- package/dist/esm/testing/cooldown-store-suite.js +67 -5
- package/dist/esm/testing/discord-defaults.js +205 -30
- package/dist/esm/testing/meocord-testing-module.js +134 -28
- package/dist/esm/testing/mock-fn.js +107 -26
- package/dist/esm/testing/mock-interaction.js +1026 -172
- package/dist/esm/testing/snowflake.js +9 -2
- package/dist/esm/testing/strict-mocks.js +37 -17
- package/dist/esm/util/common.util.js +47 -19
- package/dist/esm/util/dev-runner.util.js +8 -8
- package/dist/esm/util/generator-cli.util.js +4 -5
- package/dist/esm/util/interaction.util.js +2 -2
- package/dist/esm/util/meocord-config-loader.util.js +10 -2
- package/dist/esm/util/meocord-config-validation.util.js +3 -5
- package/dist/esm/util/meocord-source-config.util.js +65 -20
- package/dist/esm/util/method-wrapper.util.js +16 -0
- package/dist/esm/util/platform.util.js +10 -10
- package/dist/esm/util/shard-process.util.js +4 -1
- package/dist/esm/util/sharding-mode.util.js +5 -7
- package/dist/esm/util/shared-state.util.js +21 -0
- package/dist/esm/util/tsconfig-paths.util.js +29 -0
- package/dist/esm/util/tsconfig.util.js +10 -10
- package/dist/esm/util/value.util.js +12 -1
- package/dist/types/common/index.d.cts +6 -6
- package/dist/types/common/index.d.ts +6 -6
- package/dist/types/core/index.d.cts +3 -2
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/decorator/index.d.cts +1 -0
- package/dist/types/decorator/index.d.ts +1 -0
- package/dist/types/{index-CdXsFgjE.d.ts → index-DKJclIi-.d.ts} +45 -21
- package/dist/types/{index-nn5p7OoX.d.cts → index-Dlf0ZZtS.d.cts} +45 -21
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-BZgFoNeB.d.cts → route-Bh1U13yy.d.cts} +3 -2
- package/dist/types/{route-BZgFoNeB.d.ts → route-Bh1U13yy.d.ts} +3 -2
- package/dist/types/testing/index.d.cts +175 -53
- package/dist/types/testing/index.d.ts +175 -53
- package/dist/types/{theme-resolvers-UKr3-6Ys.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
- package/dist/types/{theme-resolvers-BcOJVzR9.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
- package/dist/types/{translator-DHMkkCpg.d.cts → translator-Bg7hwxDF.d.cts} +21 -8
- package/dist/types/{translator-BViWKtLJ.d.ts → translator-CeP4bCzp.d.ts} +21 -8
- package/package.json +2 -2
|
@@ -1,10 +1,66 @@
|
|
|
1
|
-
import { Message, Guild, TextChannel, ThreadChannel,
|
|
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:
|
|
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:
|
|
181
|
+
commandId: nextSnowflake,
|
|
115
182
|
commandGuildId: ()=>null
|
|
116
183
|
}),
|
|
117
184
|
defaultsOf(AutocompleteInteraction, {
|
|
118
|
-
commandId:
|
|
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:
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 :
|
|
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:
|
|
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 };
|