meocord 4.1.2 → 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 +362 -0
- package/THIRD_PARTY_NOTICES.md +14 -13
- package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-XKMlcUZz.cjs} +669 -185
- package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-BZgJyfZS.cjs} +218 -75
- package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-CsJJyCZd.cjs} +10 -7
- package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CgfPQrNl.cjs} +374 -132
- package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-BUAR1WYG.cjs} +21 -7
- package/dist/cjs/common/index.cjs +4 -4
- package/dist/cjs/core/index.cjs +365 -310
- package/dist/cjs/decorator/index.cjs +85 -61
- package/dist/cjs/testing/index.cjs +2236 -496
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/README.md.template +3 -1
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
- package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
- 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 +17 -4
- 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/decorator.js +6 -6
- package/dist/esm/common/deprecation.js +2 -2
- package/dist/esm/common/errors.js +27 -2
- package/dist/esm/common/execution-context.js +1 -1
- package/dist/esm/common/meocord-text.js +11 -8
- package/dist/esm/common/metadata.js +1 -1
- 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 +21 -5
- package/dist/esm/common/sharded-cooldown-store.js +1 -1
- package/dist/esm/common/translate-error.js +1 -1
- package/dist/esm/common/translator.js +37 -6
- package/dist/esm/core/command-conflicts.js +34 -34
- package/dist/esm/core/command-registration.js +93 -24
- package/dist/esm/core/command-routes.js +2 -2
- package/dist/esm/core/component-routes.js +74 -31
- package/dist/esm/core/cooldown-runner.js +29 -8
- package/dist/esm/core/dispatcher.js +51 -7
- package/dist/esm/core/fallback.js +20 -11
- package/dist/esm/core/filter-runner.js +2 -2
- package/dist/esm/core/guard-runner.js +16 -3
- package/dist/esm/core/handler-input.js +4 -3
- package/dist/esm/core/handler-pipeline.js +27 -14
- package/dist/esm/core/handler-registry.js +1 -1
- package/dist/esm/core/interceptor-runner.js +2 -2
- package/dist/esm/core/lifecycle-hooks.js +134 -47
- package/dist/esm/core/meocord-factory.js +241 -179
- package/dist/esm/core/meocord.app.js +116 -75
- package/dist/esm/core/message-routes.js +4 -3
- package/dist/esm/core/observer-runner.js +4 -3
- package/dist/esm/core/providers.js +45 -23
- package/dist/esm/core/route-specificity.js +4 -4
- package/dist/esm/core/running-calls.js +43 -0
- package/dist/esm/core/scalar-types.js +15 -3
- package/dist/esm/core/shard-context.js +6 -7
- package/dist/esm/core/shard-manager.js +16 -10
- package/dist/esm/core/startup-roots.js +23 -4
- package/dist/esm/core/theme-resolvers.js +4 -1
- package/dist/esm/core/theme-runtime.js +2 -2
- package/dist/esm/core/theme-scope.js +3 -1
- package/dist/esm/core/theme-validation.js +17 -10
- package/dist/esm/decorator/app.decorator.js +12 -7
- package/dist/esm/decorator/command-builder.decorator.js +4 -4
- package/dist/esm/decorator/controller-class.decorator.js +9 -4
- package/dist/esm/decorator/controller.decorator.js +162 -83
- package/dist/esm/decorator/cooldown.decorator.js +20 -3
- package/dist/esm/decorator/defer.decorator.js +4 -4
- package/dist/esm/decorator/event.decorator.js +5 -5
- package/dist/esm/decorator/filter.decorator.js +7 -7
- package/dist/esm/decorator/guard.decorator.js +11 -10
- package/dist/esm/decorator/inject.decorator.js +4 -4
- package/dist/esm/decorator/interceptor.decorator.js +7 -7
- package/dist/esm/decorator/observer.decorator.js +4 -4
- package/dist/esm/decorator/service.decorator.js +4 -4
- package/dist/esm/decorator/theme.decorator.js +3 -3
- package/dist/esm/decorator/validation.decorator.js +10 -10
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +27 -11
- package/dist/esm/testing/cooldown-store-suite.js +85 -15
- package/dist/esm/testing/discord-defaults.js +249 -21
- package/dist/esm/testing/index.js +4 -2
- package/dist/esm/testing/inspect-handler.js +20 -3
- package/dist/esm/testing/meocord-testing-module.js +352 -205
- package/dist/esm/testing/mock-fn.js +173 -22
- package/dist/esm/testing/mock-interaction.js +1214 -209
- package/dist/esm/testing/response.js +1 -1
- package/dist/esm/testing/routing.js +39 -14
- package/dist/esm/testing/snowflake.js +9 -2
- package/dist/esm/testing/startup-errors.js +26 -0
- package/dist/esm/testing/strict-mocks.js +65 -0
- package/dist/esm/testing/theme.js +1 -1
- package/dist/esm/util/bundle-entry.util.js +2 -1
- 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 +3 -3
- package/dist/esm/util/meocord-config-loader.util.js +10 -2
- package/dist/esm/util/meocord-config-validation.util.js +4 -5
- package/dist/esm/util/meocord-source-config.util.js +65 -20
- package/dist/esm/util/metadata-keys.js +1 -0
- package/dist/esm/util/method-wrapper.util.js +16 -0
- package/dist/esm/util/platform.util.js +10 -10
- package/dist/esm/util/refusal.util.js +148 -8
- 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 +8 -8
- package/dist/types/common/index.d.ts +8 -8
- package/dist/types/core/index.d.cts +10 -7
- package/dist/types/core/index.d.ts +10 -7
- package/dist/types/decorator/index.d.cts +31 -29
- package/dist/types/decorator/index.d.ts +31 -29
- package/dist/types/{index-D7n0FBMm.d.ts → index-DKJclIi-.d.ts} +107 -36
- package/dist/types/{index-DEP0NYrU.d.cts → index-Dlf0ZZtS.d.cts} +107 -36
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-C-aMI7Sl.d.cts → route-Bh1U13yy.d.cts} +8 -4
- package/dist/types/{route-C-aMI7Sl.d.ts → route-Bh1U13yy.d.ts} +8 -4
- package/dist/types/testing/index.d.cts +406 -70
- package/dist/types/testing/index.d.ts +406 -70
- package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
- package/dist/types/{translator-J6tIUfdp.d.ts → translator-Bg7hwxDF.d.cts} +21 -8
- package/dist/types/{translator-B4mc1lzJ.d.cts → translator-CeP4bCzp.d.ts} +21 -8
- package/package.json +4 -3
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ServiceIdentifier, Container } from 'inversify';
|
|
2
|
-
import { Client, Interaction, Message, MessageReaction, PartialMessageReaction, User, PartialUser, ClientEvents, Base, Locale, GuildMember, Role, GuildBasedChannel, Guild, APIMessageTopLevelComponent, JSONEncodable, APIEmbed, MessageFlagsResolvable, TextBasedChannel, BaseChannel, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, ThreadChannel, AnyThreadChannel, ChatInputCommandInteraction, AutocompleteInteraction, MessageContextMenuCommandInteraction, UserContextMenuCommandInteraction, PrimaryEntryPointCommandInteraction, ContextMenuCommandInteraction, CommandInteraction, ButtonInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction, RoleSelectMenuInteraction, MentionableSelectMenuInteraction, ChannelSelectMenuInteraction, MessageComponentInteraction, ModalSubmitInteraction, BaseInteraction, DMChannel, PartialGroupDMChannel, OmitPartialGroupDMChannel, Attachment, ModalSubmitFields, MessagePayload, InteractionReplyOptions, InteractionUpdateOptions, InteractionEditReplyOptions, MessageEditOptions, APIModalInteractionResponseCallbackData, ModalComponentData, InteractionDeferReplyOptions, InteractionDeferUpdateOptions, MessageResolvable, DiscordAPIError } from 'discord.js';
|
|
2
|
+
import { Client, Interaction, Message, MessageReaction, PartialMessageReaction, User, PartialUser, ClientEvents, Base, Locale, GuildMember, Role, GuildBasedChannel, Guild, APIMessageTopLevelComponent, JSONEncodable, APIEmbed, MessageFlagsResolvable, TextBasedChannel, BaseChannel, APIAuthorizingIntegrationOwnersMap, APIInteractionGuildMember, PermissionResolvable, APIUser, CacheType, CommandInteractionOptionResolver, ThreadChannel, AnyThreadChannel, ChatInputCommandInteraction, AutocompleteInteraction, MessageContextMenuCommandInteraction, UserContextMenuCommandInteraction, PrimaryEntryPointCommandInteraction, ContextMenuCommandInteraction, CommandInteraction, ButtonInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction, RoleSelectMenuInteraction, MentionableSelectMenuInteraction, ChannelSelectMenuInteraction, MessageComponentInteraction, ModalSubmitInteraction, BaseInteraction, DMChannel, PartialGroupDMChannel, OmitPartialGroupDMChannel, Attachment, ModalSubmitFields, MessagePayload, InteractionReplyOptions, InteractionUpdateOptions, InteractionEditReplyOptions, MessageEditOptions, APIModalInteractionResponseCallbackData, ModalComponentData, InteractionDeferReplyOptions, InteractionDeferUpdateOptions, MessageResolvable, DiscordAPIError } from 'discord.js';
|
|
3
3
|
import { ReactionHandlerAction, CommandType } from '../enum/index.js';
|
|
4
|
-
import { a8 as Provider, P as ProviderToken } from '../index-
|
|
4
|
+
import { a8 as Provider, P as ProviderToken } from '../index-DKJclIi-.js';
|
|
5
5
|
import { CooldownStore, ExecutionContextType, ExecutionContext, CooldownScope, MetadataDecorator, ResponsePhase, ResponseCall } from '../common/index.js';
|
|
6
6
|
import { DispatchObserver, GuardInterface, InterceptorInterface, ExceptionFilter, ThemeOverride, ThemeResolvers, ThemeResolver, MessageCommandOptions, MessagePrefix, RootTheme, DeepReadonly, MeoCordTheme } from '../interface/index.js';
|
|
7
|
-
export { ao as ClassProvider, F as FactoryProvider, ap as ValueProvider } from '../index-
|
|
7
|
+
export { ao as ClassProvider, F as FactoryProvider, ap as ValueProvider } from '../index-DKJclIi-.js';
|
|
8
8
|
import { ThemeCache } from '../common/index.js';
|
|
9
9
|
import { Translator } from '../common/index.js';
|
|
10
10
|
import '@rsbuild/core';
|
|
@@ -161,7 +161,11 @@ interface DispatchedCall extends InvocationResult {
|
|
|
161
161
|
* @category Module
|
|
162
162
|
*/
|
|
163
163
|
interface EmitResult {
|
|
164
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* How many `@On` and `@Once` handlers ran; a handler a guard denied by returning `false` is not counted. A guard
|
|
166
|
+
* that throws `GuardDeniedError` rejects `emit` instead, as the bot's event fallback, which skips the event, doesn't
|
|
167
|
+
* run in a testing module.
|
|
168
|
+
*/
|
|
165
169
|
ran: number;
|
|
166
170
|
}
|
|
167
171
|
/**
|
|
@@ -194,7 +198,7 @@ interface EmitResult {
|
|
|
194
198
|
* @category Module
|
|
195
199
|
* @see {@link MeoCordTestingModule}
|
|
196
200
|
* @see {@link getResponse}
|
|
197
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
201
|
+
* @see {@link https://meocord.dev/docs/4.2/invoke-and-dispatch | Invoke and dispatch}
|
|
198
202
|
*/
|
|
199
203
|
declare class TestingModule {
|
|
200
204
|
private readonly container;
|
|
@@ -224,6 +228,8 @@ declare class TestingModule {
|
|
|
224
228
|
private resolving?;
|
|
225
229
|
private readying?;
|
|
226
230
|
private closing?;
|
|
231
|
+
/** The `onReady` hooks' pass, which `close()` waits for, less one whose `onReady` called `close()`. */
|
|
232
|
+
private readonly readyPass;
|
|
227
233
|
/**
|
|
228
234
|
* Resolves the module's `useFactory` providers, awaiting those that return a promise, in dependency
|
|
229
235
|
* order. `invoke`, `dispatch` and `emit` call it first; call it yourself before `get` resolves anything that
|
|
@@ -266,12 +272,21 @@ declare class TestingModule {
|
|
|
266
272
|
* It first gives up the theme read outside calls, if `init({ ready: true })` made it this module's;
|
|
267
273
|
* reads outside calls then return MeoCord's defaults until another module or app is ready.
|
|
268
274
|
*
|
|
275
|
+
* A stopped bot runs nothing more. Once `close()` is called, a module still runs what `dispatch`, `invoke` and `emit`
|
|
276
|
+
* are given, against services shut down, or shutting down, and warns once that it is closed, so a `close()` in the
|
|
277
|
+
* wrong place shows.
|
|
278
|
+
*
|
|
269
279
|
* The cooldown store and what it injects shut down last, in the same sequence as the bot's. When any of them has an
|
|
270
280
|
* `onShutdown`, the calls `invoke`, `dispatch` and `emit` have under way finish first, then the store operations
|
|
271
281
|
* they started, so the store's last writes still reach it. The module stops waiting for the whole sequence after
|
|
272
282
|
* its `shutdownTimeout`, 10 seconds unless set, and logs that it did. Give a test whose fake store never answers, or
|
|
273
283
|
* whose `onShutdown` never settles, a short `shutdownTimeout`.
|
|
274
284
|
*
|
|
285
|
+
* A `close()` while `init({ ready: true })` runs the `onReady` hooks waits for them first, within the same
|
|
286
|
+
* `shutdownTimeout`. An `onReady` still running once only the hooks' share of it is left is named in a warning, and
|
|
287
|
+
* its class is shut down all the same, as everything the module constructed is. One that calls `close()` itself isn't
|
|
288
|
+
* waited for.
|
|
289
|
+
*
|
|
275
290
|
* @returns Once every hook has run. Rejects with the error of a hook that failed, or an
|
|
276
291
|
* `AggregateError` naming each when several did.
|
|
277
292
|
*
|
|
@@ -287,6 +302,9 @@ declare class TestingModule {
|
|
|
287
302
|
* ```
|
|
288
303
|
*/
|
|
289
304
|
close(): Promise<void>;
|
|
305
|
+
private warnedClosed;
|
|
306
|
+
/** Warns once, after `close()`, that a call runs against services already shut down. */
|
|
307
|
+
private warnIfClosed;
|
|
290
308
|
private runReady;
|
|
291
309
|
/**
|
|
292
310
|
* The module's {@link ThemeCache}: the instance its classes inject, holding what `themeFor`, or
|
|
@@ -349,7 +367,9 @@ declare class TestingModule {
|
|
|
349
367
|
* @returns Whether the handler ran, and the error a filter handled, if any. Rejects with an error no
|
|
350
368
|
* filter handles, or with the error a filter throws: the built-in fallback, which answers such
|
|
351
369
|
* errors in the bot, does not run here. Rejects before running anything with an interaction or a
|
|
352
|
-
* message the handler's route does not match, naming both.
|
|
370
|
+
* message the handler's route does not match, naming both. Given fewer arguments than the handler
|
|
371
|
+
* declares, with a first that is neither an interaction nor a message, such as a reaction without its
|
|
372
|
+
* `ReactionEvent`, it rejects under `useStrictMocks()`, and otherwise warns once and runs the handler.
|
|
353
373
|
*
|
|
354
374
|
* @example
|
|
355
375
|
* ```ts
|
|
@@ -372,6 +392,13 @@ declare class TestingModule {
|
|
|
372
392
|
* ```
|
|
373
393
|
*/
|
|
374
394
|
invoke<C extends new (...args: any[]) => unknown, M extends HandlerName<C>>(controller: C, methodName: M, ...args: HandlerArgs<C, M>): Promise<InvocationResult>;
|
|
395
|
+
/** The handlers already warned about for being given fewer arguments than they take, so each is named once. */
|
|
396
|
+
private readonly warnedArgumentCounts;
|
|
397
|
+
/**
|
|
398
|
+
* Refuses under `useStrictMocks()`, or warns once otherwise, a call that gives a handler fewer arguments than it
|
|
399
|
+
* declares when `invoke` builds none for it, as for a reaction given without its `ReactionEvent`.
|
|
400
|
+
*/
|
|
401
|
+
private checkArgumentCount;
|
|
375
402
|
/** The calls under way, each removed once it settles: what `close()` lets finish before the cooldown store stops. */
|
|
376
403
|
private readonly calls;
|
|
377
404
|
/** Counts `call` as under way until it settles. */
|
|
@@ -422,15 +449,20 @@ declare class TestingModule {
|
|
|
422
449
|
action?: ReactionHandlerAction;
|
|
423
450
|
}): Promise<DispatchedCall>;
|
|
424
451
|
/**
|
|
425
|
-
* Emits a client event to the module's `@On` and `@Once` handlers, through the
|
|
426
|
-
*
|
|
427
|
-
* module's controllers, class providers and their dependencies all receive it. A
|
|
428
|
-
* handles only the first event, as it would on a client.
|
|
452
|
+
* Emits a client event to the module's `@On` and `@Once` handlers, through the pipeline the app runs
|
|
453
|
+
* them in up to its event fallback: the global guards of the module's `app`, then each handler's own.
|
|
454
|
+
* Handlers on the module's controllers, class providers and their dependencies all receive it. A
|
|
455
|
+
* `@Once` handler handles only the first event, as it would on a client.
|
|
456
|
+
*
|
|
457
|
+
* The bot's event fallback doesn't run here. On a bot, a guard's `GuardDeniedError` is logged and the
|
|
458
|
+
* event skipped, and a handler's `UserError` is logged and, when the event carries a message, answered
|
|
459
|
+
* as a reply to it; here both reject `emit` as any error does, so a test checks them with `rejects`.
|
|
429
460
|
*
|
|
430
461
|
* @param event - The client event, such as `'guildMemberAdd'`.
|
|
431
462
|
* @param args - The event's arguments, typed from discord.js's `ClientEvents`.
|
|
432
|
-
* @returns How many handlers ran. Rejects once every handler has settled if any threw
|
|
433
|
-
*
|
|
463
|
+
* @returns How many handlers ran. Rejects once every handler has settled if any threw, a guard's
|
|
464
|
+
* `GuardDeniedError` and a handler's `UserError` included: with that error when one handler failed,
|
|
465
|
+
* or an `AggregateError` of them when several did.
|
|
434
466
|
*
|
|
435
467
|
* @example
|
|
436
468
|
* ```ts
|
|
@@ -470,7 +502,7 @@ declare class TestingModule {
|
|
|
470
502
|
* @group Testing
|
|
471
503
|
* @category Module
|
|
472
504
|
* @see {@link MeoCordTestingModule}
|
|
473
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
505
|
+
* @see {@link https://meocord.dev/docs/4.2/testing | The testing module}
|
|
474
506
|
*/
|
|
475
507
|
declare class TestingModuleBuilder {
|
|
476
508
|
private readonly options;
|
|
@@ -501,6 +533,10 @@ declare class TestingModuleBuilder {
|
|
|
501
533
|
/**
|
|
502
534
|
* Replaces a guard with a stub wherever it applies, globally or on a controller or handler.
|
|
503
535
|
*
|
|
536
|
+
* It stands in for the guard wherever the guard is bound, a provider of the same token included, and wins over an
|
|
537
|
+
* `overrideProvider` of it. The stub needs a `canActivate`, its own or its prototype's: `compile()` refuses one without
|
|
538
|
+
* it under `useStrictMocks()`, and otherwise warns.
|
|
539
|
+
*
|
|
504
540
|
* @param guard - The guard class to replace.
|
|
505
541
|
* @example
|
|
506
542
|
* ```ts
|
|
@@ -514,6 +550,10 @@ declare class TestingModuleBuilder {
|
|
|
514
550
|
* Replaces an interceptor with a stub wherever it applies, globally or on a controller or handler.
|
|
515
551
|
* The stub's `intercept` receives the context and `next`; call `next.handle()` to run the handler.
|
|
516
552
|
*
|
|
553
|
+
* It stands in for the interceptor wherever it is bound, a provider of the same token included, and wins over an
|
|
554
|
+
* `overrideProvider` of it. A stub without `intercept` is refused by `compile()` under `useStrictMocks()`, and
|
|
555
|
+
* otherwise warned about.
|
|
556
|
+
*
|
|
517
557
|
* @param interceptor - The interceptor class to replace.
|
|
518
558
|
* @example
|
|
519
559
|
* ```ts
|
|
@@ -527,6 +567,10 @@ declare class TestingModuleBuilder {
|
|
|
527
567
|
* Replaces an exception filter with a stub wherever it applies. The filter's `@Catch` still decides
|
|
528
568
|
* which errors reach the stub.
|
|
529
569
|
*
|
|
570
|
+
* It stands in for the filter wherever it is bound, a provider of the same token included, and wins over an
|
|
571
|
+
* `overrideProvider` of it. A stub without `catch` is refused by `compile()` under `useStrictMocks()`, and otherwise
|
|
572
|
+
* warned about.
|
|
573
|
+
*
|
|
530
574
|
* @param filter - The filter class to replace.
|
|
531
575
|
* @example
|
|
532
576
|
* ```ts
|
|
@@ -578,7 +622,12 @@ declare class TestingModuleBuilder {
|
|
|
578
622
|
* its command's builder does not register, a customId pattern given as a command name, a builder that registers
|
|
579
623
|
* another name, and component patterns that can match the same customId are named, as the bot names them.
|
|
580
624
|
*
|
|
625
|
+
* Each check reports every startup error it finds, and the first is thrown, unchanged; when there are several, each
|
|
626
|
+
* is logged first, with the file it comes from. After {@link reportAllStartupErrors}, the errors decorators kept on
|
|
627
|
+
* the classes the module runs are among them.
|
|
628
|
+
*
|
|
581
629
|
* @returns The compiled module.
|
|
630
|
+
* @throws Error for the first startup error the checks find.
|
|
582
631
|
*/
|
|
583
632
|
compile(): TestingModule;
|
|
584
633
|
}
|
|
@@ -609,7 +658,7 @@ declare class TestingModuleBuilder {
|
|
|
609
658
|
* @category Module
|
|
610
659
|
* @see {@link TestingModuleBuilder}
|
|
611
660
|
* @see {@link TestingModule}
|
|
612
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
661
|
+
* @see {@link https://meocord.dev/docs/4.2/testing | The testing module}
|
|
613
662
|
*/
|
|
614
663
|
declare class MeoCordTestingModule {
|
|
615
664
|
/**
|
|
@@ -702,6 +751,22 @@ interface MockResult<T = unknown> {
|
|
|
702
751
|
/** What the call returned, or the error it threw. */
|
|
703
752
|
value: T;
|
|
704
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* One call's outcome once it has settled, as a mock function records it.
|
|
756
|
+
*
|
|
757
|
+
* It holds what a returned promise resolved or rejected with, or what the call returned or threw. Vitest's
|
|
758
|
+
* `toHaveResolved` and the rest read it.
|
|
759
|
+
*
|
|
760
|
+
* @group Testing
|
|
761
|
+
* @category Mocks
|
|
762
|
+
* @see {@link MockState}
|
|
763
|
+
*/
|
|
764
|
+
interface MockSettledResult<T = unknown> {
|
|
765
|
+
/** `'fulfilled'` once the call resolved or returned, `'rejected'` once it rejected or threw, `'incomplete'` before. */
|
|
766
|
+
type: 'fulfilled' | 'rejected' | 'incomplete';
|
|
767
|
+
/** What the call resolved, returned, rejected or threw with; `undefined` while it is incomplete. */
|
|
768
|
+
value: T | undefined;
|
|
769
|
+
}
|
|
705
770
|
/**
|
|
706
771
|
* What a mock function has recorded: each call's arguments, outcome and `this`, in order.
|
|
707
772
|
*
|
|
@@ -720,6 +785,12 @@ interface MockState<T extends (...args: any[]) => any = (...args: any[]) => any>
|
|
|
720
785
|
readonly instances: any[];
|
|
721
786
|
/** Arguments from the most recent call, or undefined if never called. */
|
|
722
787
|
readonly lastCall?: CallArgs<T>;
|
|
788
|
+
/** Each call's outcome once it has settled, in order. */
|
|
789
|
+
readonly settledResults: MockSettledResult<Awaited<CallResult<T>>>[];
|
|
790
|
+
/** When each call came, as a number that grows across meocord's own mocks. */
|
|
791
|
+
readonly invocationCallOrder: number[];
|
|
792
|
+
/** The `this` each call had, in order. */
|
|
793
|
+
readonly contexts: unknown[];
|
|
723
794
|
}
|
|
724
795
|
/**
|
|
725
796
|
* The mock API of a mock function: what it has recorded, and the methods that change what it does.
|
|
@@ -805,12 +876,76 @@ type Mock<T extends (...args: any[]) => any = (...args: any[]) => any> = MockedF
|
|
|
805
876
|
* @see {@link createMockFn}
|
|
806
877
|
*/
|
|
807
878
|
declare function isMockFunction(fn: unknown): fn is MockInstance;
|
|
879
|
+
/**
|
|
880
|
+
* A mock function a test runner makes, as {@link useMockFn} takes it.
|
|
881
|
+
*
|
|
882
|
+
* It is callable, records its calls in `mock.calls`, and has `mockImplementation`, `mockClear` and `mockReset`.
|
|
883
|
+
* Vitest's `vi.fn`, jest's `jest.fn` and bun's `mock` make one.
|
|
884
|
+
*
|
|
885
|
+
* @group Testing
|
|
886
|
+
* @category Mocks
|
|
887
|
+
* @see {@link MockFnFactory}
|
|
888
|
+
*/
|
|
889
|
+
interface RunnerMock {
|
|
890
|
+
(...args: any[]): any;
|
|
891
|
+
/** The calls the mock has recorded, each an array of its arguments. */
|
|
892
|
+
mock: {
|
|
893
|
+
calls: readonly unknown[][];
|
|
894
|
+
};
|
|
895
|
+
/** Sets the implementation the mock runs. */
|
|
896
|
+
mockImplementation(fn: (...args: any[]) => any): unknown;
|
|
897
|
+
/** Forgets the calls the mock has recorded. */
|
|
898
|
+
mockClear(): unknown;
|
|
899
|
+
/** Forgets the calls and what the mock was told to do. */
|
|
900
|
+
mockReset(): unknown;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* A test runner's mock function factory, such as `vi.fn`.
|
|
904
|
+
*
|
|
905
|
+
* Called with the implementation a mock starts with, it returns the runner's mock. See {@link useMockFn}.
|
|
906
|
+
*
|
|
907
|
+
* @group Testing
|
|
908
|
+
* @category Mocks
|
|
909
|
+
*/
|
|
910
|
+
type MockFnFactory = (impl?: (...args: any[]) => any) => RunnerMock;
|
|
911
|
+
/**
|
|
912
|
+
* Makes every mock `meocord/testing` creates with the test runner's own mock function, such as Vitest's `vi.fn`.
|
|
913
|
+
*
|
|
914
|
+
* Call it once in the runner's setup file. The runner then treats meocord's mocks as its own: its `clearMocks` and
|
|
915
|
+
* `mockReset` config and `vi.clearAllMocks()` reach them, its matchers read them, and its whole mock API is there.
|
|
916
|
+
* Without it, mocks are {@link createMockFn}'s own, which jest's and Vitest's `expect` read too.
|
|
917
|
+
*
|
|
918
|
+
* @remarks
|
|
919
|
+
* Each mock starts with the runner's mock holding meocord's behaviour, such as an interaction's replies, as its own
|
|
920
|
+
* implementation. {@link resetAllMocks} puts that behaviour back where the runner's `mockReset` drops it, as jest's and
|
|
921
|
+
* bun's do; a runner's own reset-all, such as jest's `resetMocks`, can't, so under jest and bun reset with
|
|
922
|
+
* {@link resetAllMocks}. node:test's `mock.fn` records calls in a shape of its own, so under node:test leave meocord's
|
|
923
|
+
* own function in place. Calling it again with the same factory does nothing; another, once a mock exists, throws.
|
|
924
|
+
*
|
|
925
|
+
* @param factory - The runner's mock function factory: `vi.fn`, `jest.fn` or bun's `mock`.
|
|
926
|
+
* @throws Error when a mock already exists from another factory, or when `factory` doesn't make a mock with jest's and
|
|
927
|
+
* Vitest's API.
|
|
928
|
+
*
|
|
929
|
+
* @example
|
|
930
|
+
* ```ts
|
|
931
|
+
* // vitest.setup.ts
|
|
932
|
+
* import { vi } from 'vitest'
|
|
933
|
+
*
|
|
934
|
+
* useMockFn(vi.fn)
|
|
935
|
+
* ```
|
|
936
|
+
*
|
|
937
|
+
* @group Testing
|
|
938
|
+
* @category Mocks
|
|
939
|
+
* @see {@link createMockFn}
|
|
940
|
+
*/
|
|
941
|
+
declare function useMockFn(factory: MockFnFactory): void;
|
|
808
942
|
/**
|
|
809
943
|
* Clears the calls every mock from `meocord/testing` has recorded, keeping what each was told to do.
|
|
810
944
|
*
|
|
811
|
-
* Vitest's `clearMocks` and jest's `clearAllMocks()` reach only their own `vi.fn()` and `jest.fn()` mocks
|
|
812
|
-
*
|
|
813
|
-
*
|
|
945
|
+
* Vitest's `clearMocks` and jest's `clearAllMocks()` reach only their own `vi.fn()` and `jest.fn()` mocks, and
|
|
946
|
+
* meocord's too once {@link useMockFn} makes them with the runner's function. This one covers `createMockFn` and
|
|
947
|
+
* everything built on it, such as `createMockInteraction` and `createMockClient`, either way. To also undo what a test
|
|
948
|
+
* set, use {@link resetAllMocks}.
|
|
814
949
|
*
|
|
815
950
|
* @example
|
|
816
951
|
* ```ts
|
|
@@ -850,12 +985,21 @@ declare function resetAllMocks(): void;
|
|
|
850
985
|
* Creates a mock function that jest's and Vitest's `expect` both read.
|
|
851
986
|
*
|
|
852
987
|
* Use it for a function double in code that must not depend on one test runner, such as a shared test helper; in a
|
|
853
|
-
* Vitest test, `vi.fn()` works as well. {@link clearAllMocks} and {@link resetAllMocks} reach it.
|
|
988
|
+
* Vitest test, `vi.fn()` works as well. {@link clearAllMocks} and {@link resetAllMocks} reach it. Once
|
|
989
|
+
* {@link useMockFn} registers a runner's function, it returns that runner's mock.
|
|
854
990
|
*
|
|
855
991
|
* @remarks
|
|
856
992
|
* `mockReturnValue`, `mockResolvedValue`, `mockRejectedValue` and `mockImplementation` share one implementation, so
|
|
857
993
|
* the last one set wins. The `*Once` variants queue, and run first, in the order they were set.
|
|
858
994
|
*
|
|
995
|
+
* It records what Vitest's `toHaveResolved` and `toHaveBeenCalledBefore` read as well. Its call order is counted
|
|
996
|
+
* across meocord's own mocks, apart from the runner's, so compare the order of two of meocord's mocks, or of two of the
|
|
997
|
+
* runner's, not one of each.
|
|
998
|
+
*
|
|
999
|
+
* A promise the implementation returns is handed back as one that settles the same way, not as the same object: test
|
|
1000
|
+
* it with `toEqual`, `toHaveReturnedWith` or by awaiting it, not with `toBe`. A property set on the given promise, such
|
|
1001
|
+
* as a `cancel` method, isn't carried over.
|
|
1002
|
+
*
|
|
859
1003
|
* @param impl - The implementation to run until another is set.
|
|
860
1004
|
*
|
|
861
1005
|
* @example
|
|
@@ -927,6 +1071,11 @@ type OutsideServer = {
|
|
|
927
1071
|
} | {
|
|
928
1072
|
channel: DMChannel | PartialGroupDMChannel;
|
|
929
1073
|
};
|
|
1074
|
+
type NotRaw<T> = T extends {
|
|
1075
|
+
member: unknown;
|
|
1076
|
+
} ? {
|
|
1077
|
+
member?: Exclude<MockProps<T>['member'], APIInteractionGuildMember>;
|
|
1078
|
+
} : unknown;
|
|
930
1079
|
/**
|
|
931
1080
|
* Creates a mock instance of a discord.js class, such as an interaction, keeping its prototype so `instanceof` holds.
|
|
932
1081
|
*
|
|
@@ -938,19 +1087,31 @@ type OutsideServer = {
|
|
|
938
1087
|
* `user` of its own unless given, and its `locale` is `'en-US'`; without a `guildId` it is a DM, and with one its
|
|
939
1088
|
* `member` is its user: for a `user` given, when the test gives the `guild`, the guild's cached member for that user,
|
|
940
1089
|
* so a message and an interaction from one user in that server share it, and `memberPermissions` are that member's
|
|
941
|
-
* permissions (`null` in a DM). A user is a person,
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
* `channel` is a text channel of its server, the
|
|
945
|
-
* `
|
|
946
|
-
* it: a DM channel is no server, and a server's
|
|
947
|
-
*
|
|
948
|
-
* `
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
952
|
-
*
|
|
953
|
-
*
|
|
1090
|
+
* permissions, with its channel's overwrites applied under `useStrictMocks()` (`null` in a DM). A user is a person,
|
|
1091
|
+
* `bot: false`, and a member has the server's @everyone role and the roles {@link createMockMember} gave it; with a
|
|
1092
|
+
* `guildId` but no `guild`, a server the bot isn't in, that @everyone role has the `guildId`. A DM sent to a member
|
|
1093
|
+
* goes through its user's `send()` and the user's one DM channel. Its `channel` is a text channel of its server, the
|
|
1094
|
+
* one its guild caches under `channelId` when it is read, or the user's DM channel. A `channel` given sets what the test leaves out of
|
|
1095
|
+
* `channelId`, `guildId` and `guild`, as discord.js reads them from it: a DM channel is no server, and a server's
|
|
1096
|
+
* channel its server; one in another server than the `guildId` given is refused, naming both. Under `useStrictMocks()`
|
|
1097
|
+
* a `message` given places it in the message's channel and server, a `guild` or a member's guild in that server, and a
|
|
1098
|
+
* `guildId` alone in a server the bot isn't in, with no `guild` or `channel` and a raw member for its user; a
|
|
1099
|
+
* `channel`, `guild` or `guildId` other than the place a test gave the message is refused, and a message whose place
|
|
1100
|
+
* `createMockMessage()` made moves to the one given. In default mode it stays where its own channel and guild fields
|
|
1101
|
+
* place it, and reading where it is warns once where strict mocks would place it otherwise. A select menu has picked
|
|
1102
|
+
* nothing unless given: its `values` are the ids of the `users` and `members`, `roles` or `channels` given, the
|
|
1103
|
+
* collections of what it picks, each empty unless given. Other data Discord always sends reads as Discord sends it,
|
|
1104
|
+
* such as `false` for a flag and `null` for what may be absent; what picks the handler, `commandName` or `customId`, is
|
|
1105
|
+
* the test's to give. Replies follow Discord's order, so a second `reply()` rejects, and {@link getResponse} reports
|
|
1106
|
+
* every answer the interaction got. Every method is a mock function, and one that returns a promise in discord.js
|
|
1107
|
+
* resolves. What the mock computes for itself, such as `memberPermissions` and `appPermissions`, calls none of them and
|
|
1108
|
+
* caches nothing, so a stub a test sets stays for its own code.
|
|
1109
|
+
*
|
|
1110
|
+
* An answer keeps that order whatever it is set to do: one a test gives a value with `mockResolvedValue` replies or
|
|
1111
|
+
* defers as a real one would, and one that rejects changes nothing. `fetchReply()` reads back what `reply()` or
|
|
1112
|
+
* `update()` sent; after `deleteReply()`, or before any answer, fetching, editing or deleting the original response
|
|
1113
|
+
* rejects with 10008 (Unknown Message), while follow-ups stay reachable by their id. Flags are read as discord.js
|
|
1114
|
+
* reads them, and its errors carry its codes, such as `InteractionAlreadyReplied`.
|
|
954
1115
|
*
|
|
955
1116
|
* Given `guild: null`, `member: null` or a DM channel, it is typed as an interaction that may come from anywhere, so
|
|
956
1117
|
* its `guild` and `member` read as possibly `null`; otherwise as one from a server the bot is in.
|
|
@@ -973,9 +1134,37 @@ type OutsideServer = {
|
|
|
973
1134
|
* @category Mocks
|
|
974
1135
|
* @see {@link MockProps}
|
|
975
1136
|
* @see {@link createChatInputOptions}
|
|
976
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
1137
|
+
* @see {@link https://meocord.dev/docs/4.2/mocks | Mocks}
|
|
977
1138
|
*/
|
|
978
|
-
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
|
|
1139
|
+
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props?: MockProps<T> & NotRaw<T>): DeepMocked<T>;
|
|
1140
|
+
/**
|
|
1141
|
+
* Creates a mock interaction from a server the bot isn't in: one given a {@link createMockRawMember | raw member} and
|
|
1142
|
+
* that server's `guildId`, with no `channel`, as discord.js caches none there, as a user-installed command run there
|
|
1143
|
+
* arrives.
|
|
1144
|
+
*
|
|
1145
|
+
* It is typed as discord.js types an interaction from such a server, `'raw'`: its `guild` is `null` and its `member`
|
|
1146
|
+
* the raw member. Everything else is as {@link createMockInteraction} builds it.
|
|
1147
|
+
*
|
|
1148
|
+
* @param Class - The discord.js interaction class to mock.
|
|
1149
|
+
* @param props - The interaction's properties, with the raw `member` and the server's `guildId`; see {@link MockProps}.
|
|
1150
|
+
*
|
|
1151
|
+
* @example
|
|
1152
|
+
* ```ts
|
|
1153
|
+
* import { expect } from 'vitest'
|
|
1154
|
+
*
|
|
1155
|
+
* const command = createMockInteraction(ChatInputCommandInteraction, { guildId: '100000000000000001', member: createMockRawMember() })
|
|
1156
|
+
* expect(command.inRawGuild()).toBe(true)
|
|
1157
|
+
* ```
|
|
1158
|
+
*
|
|
1159
|
+
* @group Testing
|
|
1160
|
+
* @category Mocks
|
|
1161
|
+
*/
|
|
1162
|
+
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props: Omit<MockProps<WithCache<T, 'raw'>>, 'options' | 'channel'> & {
|
|
1163
|
+
options?: object;
|
|
1164
|
+
guildId: string;
|
|
1165
|
+
member: APIInteractionGuildMember;
|
|
1166
|
+
channel?: never;
|
|
1167
|
+
}): DeepMocked<WithCache<T, 'raw'>>;
|
|
979
1168
|
/**
|
|
980
1169
|
* Creates a mock interaction from outside a server: one given `guild: null`, `member: null` or a DM channel, as from a
|
|
981
1170
|
* direct message or a server the bot isn't in.
|
|
@@ -997,7 +1186,7 @@ declare function createMockInteraction<T extends object>(Class: InteractionClass
|
|
|
997
1186
|
* @group Testing
|
|
998
1187
|
* @category Mocks
|
|
999
1188
|
*/
|
|
1000
|
-
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props: MockProps<WithCache<T, CacheType>> & OutsideServer): DeepMocked<WithCache<T, CacheType>>;
|
|
1189
|
+
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props: MockProps<WithCache<T, CacheType>> & OutsideServer & NotRaw<WithCache<T, CacheType>>): DeepMocked<WithCache<T, CacheType>>;
|
|
1001
1190
|
/**
|
|
1002
1191
|
* Creates a mock of any type, with no class needed, for service doubles and interfaces.
|
|
1003
1192
|
*
|
|
@@ -1056,7 +1245,7 @@ declare function createMock<T extends object>(props?: MockProps<T>): DeepMocked<
|
|
|
1056
1245
|
* @category Mocks
|
|
1057
1246
|
* @see {@link createMockInteraction}
|
|
1058
1247
|
*/
|
|
1059
|
-
declare
|
|
1248
|
+
declare function createMockUser(props?: MockProps<User>): DeepMocked<User>;
|
|
1060
1249
|
/**
|
|
1061
1250
|
* Creates a mock {@link Client}, with `users`, `channels`, `guilds` and `application.commands` ready to stub.
|
|
1062
1251
|
*
|
|
@@ -1171,6 +1360,48 @@ type ChannelOf<T> = T extends ThreadChannel ? AnyThreadChannel : T;
|
|
|
1171
1360
|
* @see {@link createMockGuild}
|
|
1172
1361
|
*/
|
|
1173
1362
|
declare function createMockChannel<T extends BaseChannel>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<ChannelOf<T>>;
|
|
1363
|
+
/**
|
|
1364
|
+
* What {@link createMockRawMember} builds a member with: any field Discord sends, with `permissions` as a permission
|
|
1365
|
+
* set and `user` as the fields of the user given.
|
|
1366
|
+
*
|
|
1367
|
+
* @group Testing
|
|
1368
|
+
* @category Mocks
|
|
1369
|
+
*/
|
|
1370
|
+
type MockRawMemberOverrides = Partial<Omit<APIInteractionGuildMember, 'permissions' | 'user'>> & {
|
|
1371
|
+
/** The member's permissions in the channel; unless given, what @everyone has in a server Discord makes today. */
|
|
1372
|
+
permissions?: PermissionResolvable;
|
|
1373
|
+
/** The member's user; a new person, `username` `'user'`, unless given. */
|
|
1374
|
+
user?: Partial<APIUser>;
|
|
1375
|
+
};
|
|
1376
|
+
/**
|
|
1377
|
+
* Creates the member Discord sends with an interaction from a server the bot isn't in, as discord.js keeps it.
|
|
1378
|
+
*
|
|
1379
|
+
* Give it as an interaction's `member`, with the server's `guildId` and no `guild`, to test a user-installed command
|
|
1380
|
+
* run there: the interaction reads `inRawGuild()` true, `guild` and `channel` null, `user` the member's user and
|
|
1381
|
+
* `memberPermissions` the member's, and a user option's member is the plain member Discord resolves too.
|
|
1382
|
+
*
|
|
1383
|
+
* @remarks
|
|
1384
|
+
* It is plain data, not a `GuildMember`: `roles` are role ids, `permissions` is the permission bitfield as a string,
|
|
1385
|
+
* and the rest are as Discord sends them, `null` for what may be absent and `false` for a flag. Code that reads
|
|
1386
|
+
* `member.roles.cache` throws on it, as it does in Discord.
|
|
1387
|
+
*
|
|
1388
|
+
* @param overrides - Fields for the member; see {@link MockRawMemberOverrides}.
|
|
1389
|
+
* @returns The member, as an `APIInteractionGuildMember`.
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
* ```ts
|
|
1393
|
+
* import { expect } from 'vitest'
|
|
1394
|
+
*
|
|
1395
|
+
* const member = createMockRawMember({ roles: ['300000000000000001'], permissions: [PermissionFlagsBits.SendMessages] })
|
|
1396
|
+
* const command = createMockInteraction(ChatInputCommandInteraction, { guildId: '100000000000000001', member })
|
|
1397
|
+
* expect(command.inRawGuild()).toBe(true)
|
|
1398
|
+
* ```
|
|
1399
|
+
*
|
|
1400
|
+
* @group Testing
|
|
1401
|
+
* @category Mocks
|
|
1402
|
+
* @see {@link createMockMember}
|
|
1403
|
+
*/
|
|
1404
|
+
declare function createMockRawMember(overrides?: MockRawMemberOverrides): APIInteractionGuildMember;
|
|
1174
1405
|
/**
|
|
1175
1406
|
* What {@link createMockMember} builds a member with.
|
|
1176
1407
|
*
|
|
@@ -1201,8 +1432,9 @@ interface MockMemberOverrides {
|
|
|
1201
1432
|
* highest, by position, then the lower id. `permissions` are computed as discord.js computes them: every permission for
|
|
1202
1433
|
* the server's owner, and otherwise its roles' permissions combined, @everyone's included, so a member with no roles has
|
|
1203
1434
|
* @everyone's alone. The member is put in its guild's member cache, so an interaction or a message from its user in that
|
|
1204
|
-
* server has it as its `member`, and `memberPermissions` are its permissions
|
|
1205
|
-
* user's `send()` and the user's
|
|
1435
|
+
* server has it as its `member`, and `memberPermissions` are its permissions, with the interaction's channel's
|
|
1436
|
+
* overwrites applied under `useStrictMocks()`. A DM sent to the member goes through its user's `send()` and the user's
|
|
1437
|
+
* one DM channel.
|
|
1206
1438
|
*
|
|
1207
1439
|
* @param overrides - The member's user, server, roles and nickname; see {@link MockMemberOverrides}.
|
|
1208
1440
|
* @returns The mock member.
|
|
@@ -1244,9 +1476,12 @@ interface MockMessageOverrides {
|
|
|
1244
1476
|
* on the message's client, and in a server its `member` is the guild's cached member for that user, or a new one.
|
|
1245
1477
|
*/
|
|
1246
1478
|
author?: User;
|
|
1247
|
-
/**
|
|
1479
|
+
/**
|
|
1480
|
+
* Its top-level components: action rows, or Components V2 such as a container. Builders or API JSON, held as
|
|
1481
|
+
* discord.js's classes, such as `ActionRow` and `ButtonComponent`, built from their JSON at the time of the call.
|
|
1482
|
+
*/
|
|
1248
1483
|
components?: readonly (APIMessageTopLevelComponent | JSONEncodable<APIMessageTopLevelComponent>)[];
|
|
1249
|
-
/** Its embeds. */
|
|
1484
|
+
/** Its embeds, builders or API JSON, held as discord.js's `Embed` built from their JSON at the time of the call. */
|
|
1250
1485
|
embeds?: readonly (APIEmbed | JSONEncodable<APIEmbed>)[];
|
|
1251
1486
|
/** Its flags: a number, flag names or a `MessageFlagsBitField`. */
|
|
1252
1487
|
flags?: MessageFlagsResolvable;
|
|
@@ -1274,12 +1509,14 @@ interface MockMessageOverrides {
|
|
|
1274
1509
|
* `@MessageHandler` does not skip it.
|
|
1275
1510
|
*
|
|
1276
1511
|
* @remarks
|
|
1277
|
-
* `delete()`, `edit()`, `reply()`, `react()`, `pin()` and `unpin()` throw once the message is deleted
|
|
1278
|
-
* and `reply()`
|
|
1279
|
-
*
|
|
1280
|
-
* one
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1512
|
+
* `delete()`, `edit()`, `reply()`, `react()`, `pin()` and `unpin()` throw once the message is deleted. `edit()` changes
|
|
1513
|
+
* this message and resolves to it, and `reply()` resolves to the reply, sent in this message's channel and holding what
|
|
1514
|
+
* was sent, as a channel's `send()` does. Under `useStrictMocks()` a message the bot sends is its `client.user`'s, and
|
|
1515
|
+
* one sent in a DM is in that DM. Without overrides the message is empty, in a server, from a new person who is its
|
|
1516
|
+
* `member`; with `guild: null` it is a direct message. Give `author` to send several messages as one user, such as to
|
|
1517
|
+
* reach a per-user cooldown. An author given, and the users, roles and channels the content mentions, are cached as the
|
|
1518
|
+
* gateway delivers them. It is typed as discord.js emits a message, never in a group DM, so it fits `messageCreate` and
|
|
1519
|
+
* the other message events.
|
|
1283
1520
|
*
|
|
1284
1521
|
* @param overrides - The message's content, components and the rest; see {@link MockMessageOverrides}.
|
|
1285
1522
|
*
|
|
@@ -1344,6 +1581,10 @@ interface ChatInputOptions {
|
|
|
1344
1581
|
* A whole number is an Integer option and a fraction a Number one, so
|
|
1345
1582
|
* `getInteger()` reads only a whole number, and `getNumber()` reads either.
|
|
1346
1583
|
*
|
|
1584
|
+
* The `focused` option reads as Discord sends it, through `getFocused()`, `getFocused(true)` and `data` alike: a
|
|
1585
|
+
* string, `''` when no value is given, and marked `focused` in `data`. A number given for it is its digits under
|
|
1586
|
+
* `useStrictMocks()`; otherwise it stays the number, with a warning when read.
|
|
1587
|
+
*
|
|
1347
1588
|
* @typeParam Cached - `any` by default, to match `createMockInteraction(ChatInputCommandInteraction)`; pass it when the
|
|
1348
1589
|
* interaction under test is cache-pinned.
|
|
1349
1590
|
*
|
|
@@ -1365,6 +1606,76 @@ interface ChatInputOptions {
|
|
|
1365
1606
|
*/
|
|
1366
1607
|
declare function createChatInputOptions<Cached extends CacheType = any>(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver<Cached>>;
|
|
1367
1608
|
|
|
1609
|
+
/**
|
|
1610
|
+
* Has every mock from `meocord/testing` compute the values discord.js computes, where it reads a placeholder otherwise.
|
|
1611
|
+
*
|
|
1612
|
+
* Call it once in the test runner's setup file. A message's `editable`, `deletable`, `pinnable`, `crosspostable`,
|
|
1613
|
+
* `bulkDeletable`, `hasThread` and `partial`, a member's `manageable`, `kickable`, `bannable` and `moderatable`, a
|
|
1614
|
+
* role's `editable`, a channel's and a thread's `viewable`, `manageable`, `deletable`, `joinable` and the rest, a voice
|
|
1615
|
+
* channel's `full`, and `partial` on users, channels and reactions then run discord.js's own getters against the mock,
|
|
1616
|
+
* and `message.thread` is `null` when the channel caches no thread under the message's id. So do a message's
|
|
1617
|
+
* `editedAt`, a member's `presence`, a server's `verified` and `systemChannel` and a channel's `parent`, each `null` or
|
|
1618
|
+
* `false` until the test gives what it is computed from. A reaction's `me` is `false`, a message's
|
|
1619
|
+
* `mentions.repliedUser` and a modal's `message` are `null`, and a reaction has a whole message of its own, so
|
|
1620
|
+
* MeoCord's dispatcher fetches neither. No placeholder warning is logged.
|
|
1621
|
+
*
|
|
1622
|
+
* @remarks
|
|
1623
|
+
* Those getters read what a server holds, so strict mocks also have it as Discord sends it: the bot's member is in the
|
|
1624
|
+
* server's member cache from the start, @everyone has the permissions Discord gives it in a new server, and a channel
|
|
1625
|
+
* or thread made without a server has one of its own, a thread with a text channel as its parent. A message has empty
|
|
1626
|
+
* `content`, a thread is neither archived nor locked, and a reaction's `count` is 1. A user, server or channel made
|
|
1627
|
+
* with a generated id was created when the mock was made, as a message is. An interaction's `memberPermissions` apply
|
|
1628
|
+
* its channel's overwrites, a thread's parent's for a thread, to its member's permissions. An interaction is in its
|
|
1629
|
+
* message's channel and server, a guild's or a member's server, or, given a `guildId` alone, a server the bot isn't in.
|
|
1630
|
+
* A message the bot sends is its `client.user`'s, and one it sends in a DM is in that DM. Give the bot's member a role
|
|
1631
|
+
* to let it do more, such as kick a member its role ranks above. A value the test sets on a mock wins over the computed
|
|
1632
|
+
* one. An interaction's answer that discord.js would refuse, such as a second `reply()` after one a test set with
|
|
1633
|
+
* `mockResolvedValue`, is refused too, where without it the mock runs it and warns. The next major version (5.0)
|
|
1634
|
+
* computes these values, and refuses those answers, without the call.
|
|
1635
|
+
*
|
|
1636
|
+
* `invoke` also refuses a call that gives a handler fewer arguments than it declares where it builds none, such as a
|
|
1637
|
+
* reaction handler given its reaction without its `ReactionEvent`; without strict mocks, it warns once and calls it.
|
|
1638
|
+
*
|
|
1639
|
+
* `compile()` also refuses an `overrideGuard`, `overrideInterceptor` or `overrideFilter` stub without the stage's
|
|
1640
|
+
* method, `canActivate`, `intercept` or `catch`; without strict mocks, it warns and builds the module.
|
|
1641
|
+
*
|
|
1642
|
+
* @throws Error when a mock already exists, which was made without it.
|
|
1643
|
+
*
|
|
1644
|
+
* @example
|
|
1645
|
+
* ```ts
|
|
1646
|
+
* // vitest.setup.ts
|
|
1647
|
+
* useStrictMocks()
|
|
1648
|
+
* ```
|
|
1649
|
+
*
|
|
1650
|
+
* @group Testing
|
|
1651
|
+
* @category Mocks
|
|
1652
|
+
* @see {@link useMockFn}
|
|
1653
|
+
* @see {@link https://meocord.dev/docs/4.2/mocks | Mocks}
|
|
1654
|
+
*/
|
|
1655
|
+
declare function useStrictMocks(): void;
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* Has decorators keep the startup errors they find, as a bot built with `startupErrors: 'all'` does, so a testing
|
|
1659
|
+
* module reports every one at once.
|
|
1660
|
+
*
|
|
1661
|
+
* Call it once in the test runner's setup file, before any test imports a controller: a decorator runs when its class
|
|
1662
|
+
* is defined, as its file is imported. A decorator given what it cannot use then throws nothing as the file loads, and
|
|
1663
|
+
* `MeoCordTestingModule.create(...).compile()` logs every startup error of the classes it runs, each with its handler
|
|
1664
|
+
* and file, then throws the first. Without it, a decorator throws its error as its class is defined.
|
|
1665
|
+
*
|
|
1666
|
+
* @example
|
|
1667
|
+
* ```ts
|
|
1668
|
+
* // vitest.setup.ts
|
|
1669
|
+
* reportAllStartupErrors()
|
|
1670
|
+
* ```
|
|
1671
|
+
*
|
|
1672
|
+
* @group Testing
|
|
1673
|
+
* @category Module
|
|
1674
|
+
* @see {@link MeoCordTestingModule}
|
|
1675
|
+
* @see {@link https://meocord.dev/docs/4.2/configuration | Configuration}
|
|
1676
|
+
*/
|
|
1677
|
+
declare function reportAllStartupErrors(): void;
|
|
1678
|
+
|
|
1368
1679
|
/**
|
|
1369
1680
|
* The command types routed by a `customId` pattern: buttons, select menus and modals.
|
|
1370
1681
|
*
|
|
@@ -1423,9 +1734,16 @@ interface ResolvedRoute {
|
|
|
1423
1734
|
* handler receives `params`.
|
|
1424
1735
|
*/
|
|
1425
1736
|
values?: Record<string, string | number | boolean>;
|
|
1737
|
+
/**
|
|
1738
|
+
* For a component route, the other patterns of its type that match the `customId` too, in the order they rank
|
|
1739
|
+
* behind it: the ones that lost to it. Absent when no other pattern matches. A test reads it; `toEqual` doesn't
|
|
1740
|
+
* compare it, so an assertion on the other fields holds either way.
|
|
1741
|
+
*/
|
|
1742
|
+
alsoMatches?: string[];
|
|
1426
1743
|
}
|
|
1427
1744
|
/**
|
|
1428
|
-
* Two patterns of one component type that can both match a `customId`, as {@link findRouteConflicts}
|
|
1745
|
+
* Two patterns of one component type that rank equally and can both match a `customId`, as {@link findRouteConflicts}
|
|
1746
|
+
* reports them.
|
|
1429
1747
|
*
|
|
1430
1748
|
* @group Testing
|
|
1431
1749
|
* @category Inspection
|
|
@@ -1435,6 +1753,11 @@ interface RouteConflict {
|
|
|
1435
1753
|
type: ComponentCommandType;
|
|
1436
1754
|
/** The two patterns. */
|
|
1437
1755
|
patterns: [string, string];
|
|
1756
|
+
/**
|
|
1757
|
+
* The pattern that runs for the `customId`s both match: the one listed first. A test reads it; `toEqual` doesn't
|
|
1758
|
+
* compare it, so an assertion on `type` and `patterns` alone holds.
|
|
1759
|
+
*/
|
|
1760
|
+
runs: string;
|
|
1438
1761
|
}
|
|
1439
1762
|
/**
|
|
1440
1763
|
* Resolves which handler a component's `customId` or a message's content reaches, as dispatch routes it.
|
|
@@ -1471,33 +1794,36 @@ interface RouteConflict {
|
|
|
1471
1794
|
* @group Testing
|
|
1472
1795
|
* @category Inspection
|
|
1473
1796
|
* @see {@link findRouteConflicts}
|
|
1474
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
1797
|
+
* @see {@link https://meocord.dev/docs/4.2/components | Buttons, selects and modals}
|
|
1475
1798
|
*/
|
|
1476
1799
|
declare function resolveRoute(app: ControllerClass, input: {
|
|
1477
1800
|
type: ComponentCommandType;
|
|
1478
1801
|
customId: string;
|
|
1479
1802
|
} | MessageToResolve): ResolvedRoute | undefined;
|
|
1480
1803
|
/**
|
|
1481
|
-
* Finds component patterns that can match the same `customId`, which MeoCord otherwise only warns
|
|
1804
|
+
* Finds component patterns that rank equally and can match the same `customId`, which MeoCord otherwise only warns
|
|
1805
|
+
* about at startup.
|
|
1482
1806
|
*
|
|
1483
|
-
* Use it in a test to keep the warning from reaching production
|
|
1484
|
-
*
|
|
1807
|
+
* Use it in a test to keep the warning from reaching production. Patterns rank segment by segment, a literal before a
|
|
1808
|
+
* param and then the narrower type, so two collide only with the same literals and equally narrow params at each
|
|
1809
|
+
* position, such as `{action:close|reopen}` and `{step:close|confirm}`, which both take `close`; the one listed
|
|
1810
|
+
* first runs. Patterns are compared within a component type.
|
|
1485
1811
|
*
|
|
1486
1812
|
* @param app - The application class decorated with `@MeoCord`.
|
|
1487
|
-
* @returns Each pair of patterns that can match the same `customId`, with its component type.
|
|
1813
|
+
* @returns Each pair of patterns that rank equally and can match the same `customId`, with its component type.
|
|
1488
1814
|
*
|
|
1489
1815
|
* @example
|
|
1490
1816
|
* ```ts
|
|
1491
1817
|
* import { expect } from 'vitest'
|
|
1492
1818
|
*
|
|
1493
1819
|
* @Controller()
|
|
1494
|
-
* class
|
|
1495
|
-
* @Command('
|
|
1496
|
-
* async
|
|
1497
|
-
* @Command('
|
|
1498
|
-
* async
|
|
1820
|
+
* class TicketController {
|
|
1821
|
+
* @Command('ticket/{action:close|reopen}', CommandType.BUTTON)
|
|
1822
|
+
* async act(interaction: ButtonInteraction) { await respond(interaction).send('Done.') }
|
|
1823
|
+
* @Command('ticket/{step:close|confirm}', CommandType.BUTTON)
|
|
1824
|
+
* async step(interaction: ButtonInteraction) { await respond(interaction).send('Next.') }
|
|
1499
1825
|
* }
|
|
1500
|
-
* @MeoCord({ controllers: [
|
|
1826
|
+
* @MeoCord({ controllers: [TicketController], clientOptions: { intents: [] } })
|
|
1501
1827
|
* class App {}
|
|
1502
1828
|
* expect(findRouteConflicts(App)).toHaveLength(1)
|
|
1503
1829
|
* ```
|
|
@@ -1549,9 +1875,11 @@ declare function createModalFields(values: Record<string, string | string[] | At
|
|
|
1549
1875
|
*
|
|
1550
1876
|
* Use it in a test for a team that wants no fallback in production. It reports the messages a locale lacks, the
|
|
1551
1877
|
* messages the default catalog doesn't have, the plural forms a language needs but a plural lacks, such as `few` for
|
|
1552
|
-
* Russian,
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1878
|
+
* Russian, a translation of another shape than the default's, a text for a plural or a plural for a text, and each
|
|
1879
|
+
* `{param}` a translation uses that the message it translates doesn't take. An empty translation, `''`, counts as
|
|
1880
|
+
* lacking, as the translator reads it as missing. It reads the catalogs' own strings, so it checks the params of a
|
|
1881
|
+
* catalog from a plain variable or a JSON file too, which the compiler can't. An object counts as a plural only when
|
|
1882
|
+
* its every key is a plural category, so a group with an `other` key is a group.
|
|
1555
1883
|
*
|
|
1556
1884
|
* @remarks
|
|
1557
1885
|
* MeoCord's own texts, a catalog's `meocord` group, fall back to English by design, so only a key MeoCord lacks, and
|
|
@@ -1584,7 +1912,7 @@ declare function createModalFields(values: Record<string, string | string[] | At
|
|
|
1584
1912
|
* @group Testing
|
|
1585
1913
|
* @category Inspection
|
|
1586
1914
|
* @see {@link createTranslator}
|
|
1587
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
1915
|
+
* @see {@link https://meocord.dev/docs/4.2/localisation | Localisation}
|
|
1588
1916
|
*/
|
|
1589
1917
|
declare function expectCompleteCatalog(translator: Translator<any>, options?: {
|
|
1590
1918
|
meocord?: boolean;
|
|
@@ -1620,9 +1948,11 @@ interface CooldownStoreSuiteFramework {
|
|
|
1620
1948
|
*
|
|
1621
1949
|
* @remarks
|
|
1622
1950
|
* The cases cover what a shared store most often gets wrong: a sliding window rather than fixed buckets,
|
|
1623
|
-
* `retryAfterMs` from the oldest
|
|
1624
|
-
* calls at the limit where exactly one may pass
|
|
1625
|
-
*
|
|
1951
|
+
* `retryAfterMs` until a use frees up, from the oldest of the newest `uses` calls in the window, calls in the same
|
|
1952
|
+
* millisecond kept apart, concurrent calls at the limit where exactly one may pass, a batch refusal that names the
|
|
1953
|
+
* longest wait, and a release that frees only the call it gave back. They use real time, with windows short enough that the suite takes
|
|
1954
|
+
* a few seconds and margins wide enough that a correct store passes when timers fire late, as on a busy CI runner,
|
|
1955
|
+
* and each case counts under keys of its own. Each window is a whole number of milliseconds, as
|
|
1626
1956
|
* `@Cooldown` gives a store: it rounds its `seconds` to the millisecond.
|
|
1627
1957
|
*
|
|
1628
1958
|
* @param name - What the store is called in the report.
|
|
@@ -1639,7 +1969,7 @@ interface CooldownStoreSuiteFramework {
|
|
|
1639
1969
|
* @group Testing
|
|
1640
1970
|
* @category Inspection
|
|
1641
1971
|
* @see {@link CooldownStore}
|
|
1642
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
1972
|
+
* @see {@link https://meocord.dev/docs/4.2/recipes/cooldown-stores | Cooldown stores}
|
|
1643
1973
|
*/
|
|
1644
1974
|
declare function testCooldownStore(name: string, factory: () => CooldownStore | Promise<CooldownStore>, { describe, it, expect }: CooldownStoreSuiteFramework): void;
|
|
1645
1975
|
|
|
@@ -1751,6 +2081,12 @@ interface HandlerInspection {
|
|
|
1751
2081
|
readonly cooldowns: readonly InspectedCooldown[];
|
|
1752
2082
|
/** The `@MessageHandler` pattern, or `undefined` for a listener and for any other kind of handler. */
|
|
1753
2083
|
readonly pattern: string | undefined;
|
|
2084
|
+
/**
|
|
2085
|
+
* The routes the handler answers because a class its controller extends declares them, such as `slash "ping"`:
|
|
2086
|
+
* those its own class doesn't declare for it. Empty for a handler that declares all it answers. A test reads it;
|
|
2087
|
+
* `toEqual` doesn't compare it, so an assertion on the other fields holds either way.
|
|
2088
|
+
*/
|
|
2089
|
+
readonly inheritedRoutes: readonly string[];
|
|
1754
2090
|
/** The `app`'s observers, in the order they are told about the call; empty without an `app`. */
|
|
1755
2091
|
readonly observers: readonly (new (...args: any[]) => DispatchObserver)[];
|
|
1756
2092
|
/**
|
|
@@ -1847,7 +2183,7 @@ interface InspectHandlerOptions {
|
|
|
1847
2183
|
* @group Testing
|
|
1848
2184
|
* @category Inspection
|
|
1849
2185
|
* @see {@link createExecutionContext}
|
|
1850
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
2186
|
+
* @see {@link https://meocord.dev/docs/4.2/testing-recipes | Testing recipes}
|
|
1851
2187
|
*/
|
|
1852
2188
|
declare function inspectHandler<C extends new (...args: any[]) => unknown>(controller: C, methodName: keyof InstanceType<C> & string, options?: InspectHandlerOptions): HandlerInspection;
|
|
1853
2189
|
|
|
@@ -1925,7 +2261,7 @@ interface ResponseReport {
|
|
|
1925
2261
|
* @group Testing
|
|
1926
2262
|
* @category Inspection
|
|
1927
2263
|
* @see {@link TestingModule}
|
|
1928
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
2264
|
+
* @see {@link https://meocord.dev/docs/4.2/invoke-and-dispatch | Invoke and dispatch}
|
|
1929
2265
|
*/
|
|
1930
2266
|
declare function getResponse(interaction: Interaction): ResponseReport;
|
|
1931
2267
|
/**
|
|
@@ -1982,7 +2318,7 @@ type MockThemeArgs = Partial<RootTheme> extends RootTheme ? [overrides?: RootThe
|
|
|
1982
2318
|
* @group Testing
|
|
1983
2319
|
* @category Mocks
|
|
1984
2320
|
* @see {@link withTheme}
|
|
1985
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
2321
|
+
* @see {@link https://meocord.dev/docs/4.2/theming | Theming}
|
|
1986
2322
|
*/
|
|
1987
2323
|
declare function createMockTheme(...[overrides]: MockThemeArgs): DeepReadonly<MeoCordTheme>;
|
|
1988
2324
|
/**
|
|
@@ -2011,5 +2347,5 @@ declare function createMockTheme(...[overrides]: MockThemeArgs): DeepReadonly<Me
|
|
|
2011
2347
|
*/
|
|
2012
2348
|
declare function withTheme<T>(theme: DeepReadonly<MeoCordTheme> | RootTheme, fn: () => T): T;
|
|
2013
2349
|
|
|
2014
|
-
export { MeoCordTestingModule, Provider, ProviderToken, TestingModule, TestingModuleBuilder, clearAllMocks, createChatInputOptions, createDiscordError, createExecutionContext, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMember, createMockMessage, createMockTheme, createMockUser, createModalFields, expectCompleteCatalog, findRouteConflicts, getResponse, inspectHandler, isMockFunction, resetAllMocks, resolveRoute, testCooldownStore, withTheme };
|
|
2015
|
-
export type { ChatInputOptions, ComponentCommandType, CooldownStoreSuiteFramework, DeepMocked, DispatchedCall, DispatchedHandler, EmitResult, ExecutionContextOptions, FromAppOptions, HandlerInspection, HandlerName, InspectHandlerOptions, InspectedCooldown, InspectedFilter, InspectedGuard, InspectedInterceptor, InvocationResult, MessageToResolve, Mock, MockGuildOverrides, MockInstance, MockMemberOverrides, MockMessageOverrides, MockProps, MockResult, MockState, MockedFunction, ResolvedRoute, ResponseReport, RouteConflict, TestingModuleInitOptions, TestingModuleOptions };
|
|
2350
|
+
export { MeoCordTestingModule, Provider, ProviderToken, TestingModule, TestingModuleBuilder, clearAllMocks, createChatInputOptions, createDiscordError, createExecutionContext, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMember, createMockMessage, createMockRawMember, createMockTheme, createMockUser, createModalFields, expectCompleteCatalog, findRouteConflicts, getResponse, inspectHandler, isMockFunction, reportAllStartupErrors, resetAllMocks, resolveRoute, testCooldownStore, useMockFn, useStrictMocks, withTheme };
|
|
2351
|
+
export type { ChatInputOptions, ComponentCommandType, CooldownStoreSuiteFramework, DeepMocked, DispatchedCall, DispatchedHandler, EmitResult, ExecutionContextOptions, FromAppOptions, HandlerInspection, HandlerName, InspectHandlerOptions, InspectedCooldown, InspectedFilter, InspectedGuard, InspectedInterceptor, InvocationResult, MessageToResolve, Mock, MockFnFactory, MockGuildOverrides, MockInstance, MockMemberOverrides, MockMessageOverrides, MockProps, MockRawMemberOverrides, MockResult, MockSettledResult, MockState, MockedFunction, ResolvedRoute, ResponseReport, RouteConflict, RunnerMock, TestingModuleInitOptions, TestingModuleOptions };
|