meocord 4.1.0 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55,17 +55,18 @@ const VISIBLE = new Set([
55
55
  ...state?.history ?? []
56
56
  ];
57
57
  const sent = calls.some((call)=>VISIBLE.has(call.method) && !('error' in call));
58
+ // Each call recorded what its method received, as that method takes it
58
59
  if (state) return {
59
60
  state: state.state,
60
61
  sent,
61
- calls
62
+ calls: calls
62
63
  };
63
64
  const answered = interaction.isRepliable() ? interaction.replied : false;
64
65
  const deferred = interaction.isRepliable() ? interaction.deferred : false;
65
66
  return {
66
67
  state: answered ? 'replied' : deferred ? 'deferred' : 'unanswered',
67
68
  sent: log ? sent : answered,
68
- calls
69
+ calls: calls
69
70
  };
70
71
  }
71
72
  /**
@@ -1,5 +1,5 @@
1
1
  import { ServiceIdentifier, Container } from 'inversify';
2
- import { Client, Interaction, Message, MessageReaction, PartialMessageReaction, User, PartialUser, ClientEvents, Locale, GuildMember, Role, GuildBasedChannel, Guild, APIMessageTopLevelComponent, JSONEncodable, APIEmbed, MessageFlagsResolvable, TextBasedChannel, BaseChannel, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, Attachment, ModalSubmitFields, 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, 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.cjs';
4
4
  import { a8 as Provider, P as ProviderToken } from '../index-DEP0NYrU.cjs';
5
5
  import { CooldownStore, ExecutionContextType, ExecutionContext, CooldownScope, MetadataDecorator, ResponsePhase, ResponseCall } from '../common/index.cjs';
@@ -678,6 +678,17 @@ declare class MeoCordTestingModule {
678
678
  static fromApp(app: new (...args: any[]) => unknown, options?: FromAppOptions): TestingModuleBuilder;
679
679
  }
680
680
 
681
+ type Overloads<T> = T extends {
682
+ (...args: infer A1): infer R1;
683
+ (...args: infer A2): infer R2;
684
+ (...args: infer A3): infer R3;
685
+ (...args: infer A4): infer R4;
686
+ } ? ((...args: A1) => R1) | ((...args: A2) => R2) | ((...args: A3) => R3) | ((...args: A4) => R4) : T;
687
+ type CallArgs<T> = Parameters<Overloads<T> & ((...args: any[]) => any)>;
688
+ type CallResult<T> = ReturnType<Overloads<T> & ((...args: any[]) => any)>;
689
+ type Implementation<T> = {
690
+ fn(...args: CallArgs<T>): CallResult<T>;
691
+ }['fn'];
681
692
  /**
682
693
  * One call's outcome, as a mock function records it: the value it returned, or the error it threw.
683
694
  *
@@ -702,13 +713,13 @@ interface MockResult<T = unknown> {
702
713
  */
703
714
  interface MockState<T extends (...args: any[]) => any = (...args: any[]) => any> {
704
715
  /** Arguments from each call, in order. */
705
- readonly calls: Parameters<T>[];
716
+ readonly calls: CallArgs<T>[];
706
717
  /** Return / throw result from each call, in order. */
707
- readonly results: MockResult<ReturnType<T>>[];
718
+ readonly results: MockResult<CallResult<T>>[];
708
719
  /** The `this` value recorded for each call. */
709
720
  readonly instances: any[];
710
721
  /** Arguments from the most recent call, or undefined if never called. */
711
- readonly lastCall?: Parameters<T>;
722
+ readonly lastCall?: CallArgs<T>;
712
723
  }
713
724
  /**
714
725
  * The mock API of a mock function: what it has recorded, and the methods that change what it does.
@@ -727,21 +738,21 @@ interface MockInstance<T extends (...args: any[]) => any = (...args: any[]) => a
727
738
  /** What the mock has recorded: its calls, their outcomes and their `this`. */
728
739
  readonly mock: MockState<T>;
729
740
  /** Returns `value` from every call, until another implementation is set. */
730
- mockReturnValue(value: ReturnType<T>): MockedFunction<T>;
741
+ mockReturnValue(value: CallResult<T>): MockedFunction<T>;
731
742
  /** Returns `value` from the next call only. */
732
- mockReturnValueOnce(value: ReturnType<T>): MockedFunction<T>;
743
+ mockReturnValueOnce(value: CallResult<T>): MockedFunction<T>;
733
744
  /** Resolves every call to `value`. */
734
- mockResolvedValue(value: Awaited<ReturnType<T>>): MockedFunction<T>;
745
+ mockResolvedValue(value: Awaited<CallResult<T>>): MockedFunction<T>;
735
746
  /** Resolves the next call only to `value`. */
736
- mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockedFunction<T>;
747
+ mockResolvedValueOnce(value: Awaited<CallResult<T>>): MockedFunction<T>;
737
748
  /** Rejects every call with `value`. */
738
749
  mockRejectedValue(value: unknown): MockedFunction<T>;
739
750
  /** Rejects the next call only with `value`. */
740
751
  mockRejectedValueOnce(value: unknown): MockedFunction<T>;
741
752
  /** Runs `fn` for every call. */
742
- mockImplementation(fn: T): MockedFunction<T>;
753
+ mockImplementation(fn: Implementation<T>): MockedFunction<T>;
743
754
  /** Runs `fn` for the next call only; `*Once` implementations run in the order they were set. */
744
- mockImplementationOnce(fn: T): MockedFunction<T>;
755
+ mockImplementationOnce(fn: Implementation<T>): MockedFunction<T>;
745
756
  /** Forgets the recorded calls, keeping the implementation. */
746
757
  mockClear(): MockedFunction<T>;
747
758
  /** Forgets the recorded calls, and puts back the implementation the mock was created with. */
@@ -757,7 +768,8 @@ interface MockInstance<T extends (...args: any[]) => any = (...args: any[]) => a
757
768
  * A mock function with the signature of `T`, and the mock API of {@link MockInstance}.
758
769
  *
759
770
  * It takes the place of jest's `MockedFunction<T>` and Vitest's `MockedFunction<T>`. The mock factories type each
760
- * method of a mock with it, so `interaction.reply.mockResolvedValue(...)` type-checks.
771
+ * method of a mock with it, so `interaction.reply.mockResolvedValue(...)` type-checks. For an overloaded method, such as
772
+ * a manager's `fetch`, the mock API takes what any of its overloads takes or returns.
761
773
  *
762
774
  * @group Testing
763
775
  * @category Mocks
@@ -864,7 +876,8 @@ declare function resetAllMocks(): void;
864
876
  declare function createMockFn<T extends (...args: any[]) => any = (...args: any[]) => any>(impl?: T): MockedFunction<T>;
865
877
 
866
878
  /**
867
- * A mock of `T`: every method a mock function, and every nested object mocked in turn, five levels deep.
879
+ * A mock of `T`: every method a mock function, and every nested object mocked in turn, five levels deep, including a
880
+ * discord.js structure that may be absent, such as a message's `member`, when it is present.
868
881
  *
869
882
  * It is what the mock factories return. It is assignable wherever `T` is expected, and each method takes
870
883
  * `mockResolvedValue` and the rest of {@link MockInstance}.
@@ -878,7 +891,7 @@ declare function createMockFn<T extends (...args: any[]) => any = (...args: any[
878
891
  * @see {@link createMock}
879
892
  */
880
893
  type DeepMocked<T, Depth extends number[] = []> = Depth['length'] extends 5 ? T : {
881
- -readonly [K in keyof T]: T[K] extends (...args: infer A) => infer R ? MockedFunction<(...args: A) => R> : T[K] extends object ? DeepMocked<T[K], [...Depth, 0]> : T[K];
894
+ -readonly [K in keyof T]: T[K] extends (...args: any[]) => any ? MockedFunction<T[K]> : T[K] extends object ? DeepMocked<T[K], [...Depth, 0]> : Depth extends [] ? NonNullable<T[K]> extends Base ? DeepMocked<NonNullable<T[K]>, [...Depth, 0]> | Extract<T[K], null | undefined> : T[K] : T[K];
882
895
  } & T;
883
896
  /**
884
897
  * Property values a mock factory sets as it builds the mock.
@@ -906,6 +919,14 @@ interface InteractionClass<T> {
906
919
  prototype: T;
907
920
  name: string;
908
921
  }
922
+ type WithCache<T, C extends CacheType> = T extends ChatInputCommandInteraction<any> ? ChatInputCommandInteraction<C> : T extends AutocompleteInteraction<any> ? AutocompleteInteraction<C> : T extends MessageContextMenuCommandInteraction<any> ? MessageContextMenuCommandInteraction<C> : T extends UserContextMenuCommandInteraction<any> ? UserContextMenuCommandInteraction<C> : T extends PrimaryEntryPointCommandInteraction<any> ? PrimaryEntryPointCommandInteraction<C> : T extends ContextMenuCommandInteraction<any> ? ContextMenuCommandInteraction<C> : T extends CommandInteraction<any> ? CommandInteraction<C> : T extends ButtonInteraction<any> ? ButtonInteraction<C> : T extends StringSelectMenuInteraction<any> ? StringSelectMenuInteraction<C> : T extends UserSelectMenuInteraction<any> ? UserSelectMenuInteraction<C> : T extends RoleSelectMenuInteraction<any> ? RoleSelectMenuInteraction<C> : T extends MentionableSelectMenuInteraction<any> ? MentionableSelectMenuInteraction<C> : T extends ChannelSelectMenuInteraction<any> ? ChannelSelectMenuInteraction<C> : T extends MessageComponentInteraction<any> ? MessageComponentInteraction<C> : T extends ModalSubmitInteraction<any> ? ModalSubmitInteraction<C> : T extends BaseInteraction<any> ? BaseInteraction<C> : T;
923
+ type OutsideServer = {
924
+ guild: null;
925
+ } | {
926
+ member: null;
927
+ } | {
928
+ channel: DMChannel | PartialGroupDMChannel;
929
+ };
909
930
  /**
910
931
  * Creates a mock instance of a discord.js class, such as an interaction, keeping its prototype so `instanceof` holds.
911
932
  *
@@ -931,6 +952,9 @@ interface InteractionClass<T> {
931
952
  * {@link getResponse} reports every answer the interaction got. Every method is a mock function, and one that returns a
932
953
  * promise in discord.js resolves.
933
954
  *
955
+ * Given `guild: null`, `member: null` or a DM channel, it is typed as an interaction that may come from anywhere, so
956
+ * its `guild` and `member` read as possibly `null`; otherwise as one from a server the bot is in.
957
+ *
934
958
  * @param Class - The discord.js class to mock.
935
959
  * @param props - Values for properties the class declares `readonly`; see {@link MockProps}.
936
960
  *
@@ -952,6 +976,28 @@ interface InteractionClass<T> {
952
976
  * @see {@link https://meocord.dev/docs/4.1/mocks | Mocks}
953
977
  */
954
978
  declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
979
+ /**
980
+ * Creates a mock interaction from outside a server: one given `guild: null`, `member: null` or a DM channel, as from a
981
+ * direct message or a server the bot isn't in.
982
+ *
983
+ * It is typed as discord.js types an interaction that may come from anywhere, so its `guild` and `member` may be
984
+ * `null`. Everything else is as {@link createMockInteraction} builds it from a server.
985
+ *
986
+ * @param Class - The discord.js interaction class to mock.
987
+ * @param props - The interaction's properties, with `guild: null`, `member: null` or a DM `channel`; see {@link MockProps}.
988
+ *
989
+ * @example
990
+ * ```ts
991
+ * import { expect } from 'vitest'
992
+ *
993
+ * const command = createMockInteraction(ChatInputCommandInteraction, { channel: createMockChannel(DMChannel), guild: null })
994
+ * expect(command.inGuild()).toBe(false)
995
+ * ```
996
+ *
997
+ * @group Testing
998
+ * @category Mocks
999
+ */
1000
+ declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props: MockProps<WithCache<T, CacheType>> & OutsideServer): DeepMocked<WithCache<T, CacheType>>;
955
1001
  /**
956
1002
  * Creates a mock of any type, with no class needed, for service doubles and interfaces.
957
1003
  *
@@ -1029,7 +1075,7 @@ declare const createMockUser: (props?: MockProps<User>) => DeepMocked<User>;
1029
1075
  * import { expect } from 'vitest'
1030
1076
  *
1031
1077
  * const client = createMockClient()
1032
- * const botId = client.user!.id
1078
+ * const botId = client.user.id
1033
1079
  * const message = createMockMessage({ content: `<@${botId}> help`, client })
1034
1080
  * expect(message.mentions.users.has(botId)).toBe(true)
1035
1081
  * ```
@@ -1038,7 +1084,7 @@ declare const createMockUser: (props?: MockProps<User>) => DeepMocked<User>;
1038
1084
  * @category Mocks
1039
1085
  * @see {@link createMockMessage}
1040
1086
  */
1041
- declare function createMockClient(): DeepMocked<Client>;
1087
+ declare function createMockClient(): DeepMocked<Client<true>>;
1042
1088
  /**
1043
1089
  * What {@link createMockGuild} puts in the guild's caches, as the gateway would have filled them.
1044
1090
  *
@@ -1070,14 +1116,18 @@ interface MockGuildOverrides {
1070
1116
  *
1071
1117
  * @remarks
1072
1118
  * A manager's `fetch(id)` resolves to its cached item with that id, as discord.js looks there first, or to a new one it
1073
- * caches under that id: a member or channel in this guild, a role with that id, or a ban; `create()` and `edit()`
1074
- * resolve to a mock of its item, and a list fetch to an empty collection. Members, roles and channels given are put in
1119
+ * caches under that id: a member or channel in this guild, a role with that id, or a ban. `members.fetch({ user: ids })`
1120
+ * resolves to a collection of each of those members, found or made the same way; `create()` and `edit()` resolve to a
1121
+ * mock of its item, and a list fetch to an empty collection. Members, roles and channels given are put in
1075
1122
  * their managers' caches, where dispatch looks first when it resolves a message's typed params; a member
1076
1123
  * {@link createMockMember} made without a server is in this one. Its `roles.everyone` is the role given with the
1077
1124
  * guild's id, or else an @everyone role of its own at position 0 with no permissions, in `roles.cache` as Discord has
1078
1125
  * it. The guild is named `'Guild'` and its `preferredLocale` is `'en-US'` unless given, so `t.forGuild(guild)`
1079
1126
  * translates as for a new English server.
1080
1127
  *
1128
+ * The mock makes a member for any id it's asked for, so a test of an id that isn't a member rejects the fetch, as
1129
+ * `guild.members.fetch.mockRejectedValue(createDiscordError(10007))`, or resolves a collection without that member.
1130
+ *
1081
1131
  * @example
1082
1132
  * ```ts
1083
1133
  * const target = createMock<GuildMember>({ id: '111111111111111111' })
@@ -1091,6 +1141,7 @@ interface MockGuildOverrides {
1091
1141
  * @see {@link createMockMessage}
1092
1142
  */
1093
1143
  declare function createMockGuild(overrides?: MockGuildOverrides): DeepMocked<Guild>;
1144
+ type ChannelOf<T> = T extends ThreadChannel ? AnyThreadChannel : T;
1094
1145
  /**
1095
1146
  * Creates a mock channel of the given class, such as `TextChannel`, `ThreadChannel` or `DMChannel`.
1096
1147
  *
@@ -1100,7 +1151,8 @@ declare function createMockGuild(overrides?: MockGuildOverrides): DeepMocked<Gui
1100
1151
  * The managers the class has are ready to stub: `messages`, `threads` on text, announcement, forum and media channels,
1101
1152
  * and `members` on threads, each with a real, empty `cache` and the channel as its `channel`, or `thread`. A subclass gets the managers of the class it extends.
1102
1153
  * Type guards such as `isTextBased()`, `isDMBased()` and `isThread()` run discord.js's own logic, so each answers what
1103
- * the channel is.
1154
+ * the channel is. A thread is typed as discord.js types every thread it gives, a public or a private one, so it fits
1155
+ * an interaction's `channel`, a guild's channels and the `threadCreate` event.
1104
1156
  *
1105
1157
  * @param Class - The discord.js channel class to mock.
1106
1158
  * @param props - Values for the channel's properties, such as its `id`, `name` or `topic`; see {@link MockProps}.
@@ -1118,7 +1170,7 @@ declare function createMockGuild(overrides?: MockGuildOverrides): DeepMocked<Gui
1118
1170
  * @category Mocks
1119
1171
  * @see {@link createMockGuild}
1120
1172
  */
1121
- declare function createMockChannel<T extends BaseChannel>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
1173
+ declare function createMockChannel<T extends BaseChannel>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<ChannelOf<T>>;
1122
1174
  /**
1123
1175
  * What {@link createMockMember} builds a member with.
1124
1176
  *
@@ -1226,7 +1278,8 @@ interface MockMessageOverrides {
1226
1278
  * and `reply()` resolve to a new mock message. Without overrides the message is empty, in a server, from a new
1227
1279
  * person who is its `member`; with `guild: null` it is a direct message. Give `author` to send several messages as
1228
1280
  * one user, such as to reach a per-user cooldown. An author given, and the users, roles and channels the content
1229
- * mentions, are cached as the gateway delivers them.
1281
+ * mentions, are cached as the gateway delivers them. It is typed as discord.js emits a message, never in a group DM,
1282
+ * so it fits `messageCreate` and the other message events.
1230
1283
  *
1231
1284
  * @param overrides - The message's content, components and the rest; see {@link MockMessageOverrides}.
1232
1285
  *
@@ -1249,7 +1302,7 @@ interface MockMessageOverrides {
1249
1302
  * @see {@link MockMessageOverrides}
1250
1303
  * @see {@link createMockGuild}
1251
1304
  */
1252
- declare function createMockMessage(overrides?: MockMessageOverrides): DeepMocked<Message> & {
1305
+ declare function createMockMessage(overrides?: MockMessageOverrides): DeepMocked<Message> & OmitPartialGroupDMChannel<Message> & {
1253
1306
  deleted: boolean;
1254
1307
  };
1255
1308
  /**
@@ -1811,9 +1864,34 @@ interface ResponseReport {
1811
1864
  sent: boolean;
1812
1865
  /**
1813
1866
  * Every answer call the interaction got, through `respond()` or discord.js directly, in order: the payload it sent,
1814
- * without `withResponse`, and the `error` of one Discord refused.
1867
+ * without `withResponse`, and the `error` of one Discord refused. Checking a call's `method` types its `payload` as
1868
+ * that method takes it: the options of a reply, an edit or a deferral, the modal shown, or the message deleted.
1815
1869
  */
1816
- calls: readonly ResponseCall[];
1870
+ calls: readonly ((ResponseCall & {
1871
+ method: 'reply' | 'followUp';
1872
+ payload?: string | MessagePayload | InteractionReplyOptions;
1873
+ }) | (ResponseCall & {
1874
+ method: 'update';
1875
+ payload?: string | MessagePayload | InteractionUpdateOptions;
1876
+ }) | (ResponseCall & {
1877
+ method: 'editReply';
1878
+ payload?: string | MessagePayload | InteractionEditReplyOptions;
1879
+ }) | (ResponseCall & {
1880
+ method: 'message.edit';
1881
+ payload?: string | MessagePayload | MessageEditOptions;
1882
+ }) | (ResponseCall & {
1883
+ method: 'showModal';
1884
+ payload?: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalComponentData | APIModalInteractionResponseCallbackData;
1885
+ }) | (ResponseCall & {
1886
+ method: 'deferReply';
1887
+ payload?: InteractionDeferReplyOptions;
1888
+ }) | (ResponseCall & {
1889
+ method: 'deferUpdate';
1890
+ payload?: InteractionDeferUpdateOptions;
1891
+ }) | (ResponseCall & {
1892
+ method: 'deleteReply';
1893
+ payload?: MessageResolvable | '@original';
1894
+ }))[];
1817
1895
  }
1818
1896
  /**
1819
1897
  * Reports how an interaction was answered: where its answer stands, and every answer call it got, in order.
@@ -1,5 +1,5 @@
1
1
  import { ServiceIdentifier, Container } from 'inversify';
2
- import { Client, Interaction, Message, MessageReaction, PartialMessageReaction, User, PartialUser, ClientEvents, Locale, GuildMember, Role, GuildBasedChannel, Guild, APIMessageTopLevelComponent, JSONEncodable, APIEmbed, MessageFlagsResolvable, TextBasedChannel, BaseChannel, APIAuthorizingIntegrationOwnersMap, CacheType, CommandInteractionOptionResolver, Attachment, ModalSubmitFields, 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, 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
4
  import { a8 as Provider, P as ProviderToken } from '../index-D7n0FBMm.js';
5
5
  import { CooldownStore, ExecutionContextType, ExecutionContext, CooldownScope, MetadataDecorator, ResponsePhase, ResponseCall } from '../common/index.js';
@@ -678,6 +678,17 @@ declare class MeoCordTestingModule {
678
678
  static fromApp(app: new (...args: any[]) => unknown, options?: FromAppOptions): TestingModuleBuilder;
679
679
  }
680
680
 
681
+ type Overloads<T> = T extends {
682
+ (...args: infer A1): infer R1;
683
+ (...args: infer A2): infer R2;
684
+ (...args: infer A3): infer R3;
685
+ (...args: infer A4): infer R4;
686
+ } ? ((...args: A1) => R1) | ((...args: A2) => R2) | ((...args: A3) => R3) | ((...args: A4) => R4) : T;
687
+ type CallArgs<T> = Parameters<Overloads<T> & ((...args: any[]) => any)>;
688
+ type CallResult<T> = ReturnType<Overloads<T> & ((...args: any[]) => any)>;
689
+ type Implementation<T> = {
690
+ fn(...args: CallArgs<T>): CallResult<T>;
691
+ }['fn'];
681
692
  /**
682
693
  * One call's outcome, as a mock function records it: the value it returned, or the error it threw.
683
694
  *
@@ -702,13 +713,13 @@ interface MockResult<T = unknown> {
702
713
  */
703
714
  interface MockState<T extends (...args: any[]) => any = (...args: any[]) => any> {
704
715
  /** Arguments from each call, in order. */
705
- readonly calls: Parameters<T>[];
716
+ readonly calls: CallArgs<T>[];
706
717
  /** Return / throw result from each call, in order. */
707
- readonly results: MockResult<ReturnType<T>>[];
718
+ readonly results: MockResult<CallResult<T>>[];
708
719
  /** The `this` value recorded for each call. */
709
720
  readonly instances: any[];
710
721
  /** Arguments from the most recent call, or undefined if never called. */
711
- readonly lastCall?: Parameters<T>;
722
+ readonly lastCall?: CallArgs<T>;
712
723
  }
713
724
  /**
714
725
  * The mock API of a mock function: what it has recorded, and the methods that change what it does.
@@ -727,21 +738,21 @@ interface MockInstance<T extends (...args: any[]) => any = (...args: any[]) => a
727
738
  /** What the mock has recorded: its calls, their outcomes and their `this`. */
728
739
  readonly mock: MockState<T>;
729
740
  /** Returns `value` from every call, until another implementation is set. */
730
- mockReturnValue(value: ReturnType<T>): MockedFunction<T>;
741
+ mockReturnValue(value: CallResult<T>): MockedFunction<T>;
731
742
  /** Returns `value` from the next call only. */
732
- mockReturnValueOnce(value: ReturnType<T>): MockedFunction<T>;
743
+ mockReturnValueOnce(value: CallResult<T>): MockedFunction<T>;
733
744
  /** Resolves every call to `value`. */
734
- mockResolvedValue(value: Awaited<ReturnType<T>>): MockedFunction<T>;
745
+ mockResolvedValue(value: Awaited<CallResult<T>>): MockedFunction<T>;
735
746
  /** Resolves the next call only to `value`. */
736
- mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockedFunction<T>;
747
+ mockResolvedValueOnce(value: Awaited<CallResult<T>>): MockedFunction<T>;
737
748
  /** Rejects every call with `value`. */
738
749
  mockRejectedValue(value: unknown): MockedFunction<T>;
739
750
  /** Rejects the next call only with `value`. */
740
751
  mockRejectedValueOnce(value: unknown): MockedFunction<T>;
741
752
  /** Runs `fn` for every call. */
742
- mockImplementation(fn: T): MockedFunction<T>;
753
+ mockImplementation(fn: Implementation<T>): MockedFunction<T>;
743
754
  /** Runs `fn` for the next call only; `*Once` implementations run in the order they were set. */
744
- mockImplementationOnce(fn: T): MockedFunction<T>;
755
+ mockImplementationOnce(fn: Implementation<T>): MockedFunction<T>;
745
756
  /** Forgets the recorded calls, keeping the implementation. */
746
757
  mockClear(): MockedFunction<T>;
747
758
  /** Forgets the recorded calls, and puts back the implementation the mock was created with. */
@@ -757,7 +768,8 @@ interface MockInstance<T extends (...args: any[]) => any = (...args: any[]) => a
757
768
  * A mock function with the signature of `T`, and the mock API of {@link MockInstance}.
758
769
  *
759
770
  * It takes the place of jest's `MockedFunction<T>` and Vitest's `MockedFunction<T>`. The mock factories type each
760
- * method of a mock with it, so `interaction.reply.mockResolvedValue(...)` type-checks.
771
+ * method of a mock with it, so `interaction.reply.mockResolvedValue(...)` type-checks. For an overloaded method, such as
772
+ * a manager's `fetch`, the mock API takes what any of its overloads takes or returns.
761
773
  *
762
774
  * @group Testing
763
775
  * @category Mocks
@@ -864,7 +876,8 @@ declare function resetAllMocks(): void;
864
876
  declare function createMockFn<T extends (...args: any[]) => any = (...args: any[]) => any>(impl?: T): MockedFunction<T>;
865
877
 
866
878
  /**
867
- * A mock of `T`: every method a mock function, and every nested object mocked in turn, five levels deep.
879
+ * A mock of `T`: every method a mock function, and every nested object mocked in turn, five levels deep, including a
880
+ * discord.js structure that may be absent, such as a message's `member`, when it is present.
868
881
  *
869
882
  * It is what the mock factories return. It is assignable wherever `T` is expected, and each method takes
870
883
  * `mockResolvedValue` and the rest of {@link MockInstance}.
@@ -878,7 +891,7 @@ declare function createMockFn<T extends (...args: any[]) => any = (...args: any[
878
891
  * @see {@link createMock}
879
892
  */
880
893
  type DeepMocked<T, Depth extends number[] = []> = Depth['length'] extends 5 ? T : {
881
- -readonly [K in keyof T]: T[K] extends (...args: infer A) => infer R ? MockedFunction<(...args: A) => R> : T[K] extends object ? DeepMocked<T[K], [...Depth, 0]> : T[K];
894
+ -readonly [K in keyof T]: T[K] extends (...args: any[]) => any ? MockedFunction<T[K]> : T[K] extends object ? DeepMocked<T[K], [...Depth, 0]> : Depth extends [] ? NonNullable<T[K]> extends Base ? DeepMocked<NonNullable<T[K]>, [...Depth, 0]> | Extract<T[K], null | undefined> : T[K] : T[K];
882
895
  } & T;
883
896
  /**
884
897
  * Property values a mock factory sets as it builds the mock.
@@ -906,6 +919,14 @@ interface InteractionClass<T> {
906
919
  prototype: T;
907
920
  name: string;
908
921
  }
922
+ type WithCache<T, C extends CacheType> = T extends ChatInputCommandInteraction<any> ? ChatInputCommandInteraction<C> : T extends AutocompleteInteraction<any> ? AutocompleteInteraction<C> : T extends MessageContextMenuCommandInteraction<any> ? MessageContextMenuCommandInteraction<C> : T extends UserContextMenuCommandInteraction<any> ? UserContextMenuCommandInteraction<C> : T extends PrimaryEntryPointCommandInteraction<any> ? PrimaryEntryPointCommandInteraction<C> : T extends ContextMenuCommandInteraction<any> ? ContextMenuCommandInteraction<C> : T extends CommandInteraction<any> ? CommandInteraction<C> : T extends ButtonInteraction<any> ? ButtonInteraction<C> : T extends StringSelectMenuInteraction<any> ? StringSelectMenuInteraction<C> : T extends UserSelectMenuInteraction<any> ? UserSelectMenuInteraction<C> : T extends RoleSelectMenuInteraction<any> ? RoleSelectMenuInteraction<C> : T extends MentionableSelectMenuInteraction<any> ? MentionableSelectMenuInteraction<C> : T extends ChannelSelectMenuInteraction<any> ? ChannelSelectMenuInteraction<C> : T extends MessageComponentInteraction<any> ? MessageComponentInteraction<C> : T extends ModalSubmitInteraction<any> ? ModalSubmitInteraction<C> : T extends BaseInteraction<any> ? BaseInteraction<C> : T;
923
+ type OutsideServer = {
924
+ guild: null;
925
+ } | {
926
+ member: null;
927
+ } | {
928
+ channel: DMChannel | PartialGroupDMChannel;
929
+ };
909
930
  /**
910
931
  * Creates a mock instance of a discord.js class, such as an interaction, keeping its prototype so `instanceof` holds.
911
932
  *
@@ -931,6 +952,9 @@ interface InteractionClass<T> {
931
952
  * {@link getResponse} reports every answer the interaction got. Every method is a mock function, and one that returns a
932
953
  * promise in discord.js resolves.
933
954
  *
955
+ * Given `guild: null`, `member: null` or a DM channel, it is typed as an interaction that may come from anywhere, so
956
+ * its `guild` and `member` read as possibly `null`; otherwise as one from a server the bot is in.
957
+ *
934
958
  * @param Class - The discord.js class to mock.
935
959
  * @param props - Values for properties the class declares `readonly`; see {@link MockProps}.
936
960
  *
@@ -952,6 +976,28 @@ interface InteractionClass<T> {
952
976
  * @see {@link https://meocord.dev/docs/4.1/mocks | Mocks}
953
977
  */
954
978
  declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
979
+ /**
980
+ * Creates a mock interaction from outside a server: one given `guild: null`, `member: null` or a DM channel, as from a
981
+ * direct message or a server the bot isn't in.
982
+ *
983
+ * It is typed as discord.js types an interaction that may come from anywhere, so its `guild` and `member` may be
984
+ * `null`. Everything else is as {@link createMockInteraction} builds it from a server.
985
+ *
986
+ * @param Class - The discord.js interaction class to mock.
987
+ * @param props - The interaction's properties, with `guild: null`, `member: null` or a DM `channel`; see {@link MockProps}.
988
+ *
989
+ * @example
990
+ * ```ts
991
+ * import { expect } from 'vitest'
992
+ *
993
+ * const command = createMockInteraction(ChatInputCommandInteraction, { channel: createMockChannel(DMChannel), guild: null })
994
+ * expect(command.inGuild()).toBe(false)
995
+ * ```
996
+ *
997
+ * @group Testing
998
+ * @category Mocks
999
+ */
1000
+ declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props: MockProps<WithCache<T, CacheType>> & OutsideServer): DeepMocked<WithCache<T, CacheType>>;
955
1001
  /**
956
1002
  * Creates a mock of any type, with no class needed, for service doubles and interfaces.
957
1003
  *
@@ -1029,7 +1075,7 @@ declare const createMockUser: (props?: MockProps<User>) => DeepMocked<User>;
1029
1075
  * import { expect } from 'vitest'
1030
1076
  *
1031
1077
  * const client = createMockClient()
1032
- * const botId = client.user!.id
1078
+ * const botId = client.user.id
1033
1079
  * const message = createMockMessage({ content: `<@${botId}> help`, client })
1034
1080
  * expect(message.mentions.users.has(botId)).toBe(true)
1035
1081
  * ```
@@ -1038,7 +1084,7 @@ declare const createMockUser: (props?: MockProps<User>) => DeepMocked<User>;
1038
1084
  * @category Mocks
1039
1085
  * @see {@link createMockMessage}
1040
1086
  */
1041
- declare function createMockClient(): DeepMocked<Client>;
1087
+ declare function createMockClient(): DeepMocked<Client<true>>;
1042
1088
  /**
1043
1089
  * What {@link createMockGuild} puts in the guild's caches, as the gateway would have filled them.
1044
1090
  *
@@ -1070,14 +1116,18 @@ interface MockGuildOverrides {
1070
1116
  *
1071
1117
  * @remarks
1072
1118
  * A manager's `fetch(id)` resolves to its cached item with that id, as discord.js looks there first, or to a new one it
1073
- * caches under that id: a member or channel in this guild, a role with that id, or a ban; `create()` and `edit()`
1074
- * resolve to a mock of its item, and a list fetch to an empty collection. Members, roles and channels given are put in
1119
+ * caches under that id: a member or channel in this guild, a role with that id, or a ban. `members.fetch({ user: ids })`
1120
+ * resolves to a collection of each of those members, found or made the same way; `create()` and `edit()` resolve to a
1121
+ * mock of its item, and a list fetch to an empty collection. Members, roles and channels given are put in
1075
1122
  * their managers' caches, where dispatch looks first when it resolves a message's typed params; a member
1076
1123
  * {@link createMockMember} made without a server is in this one. Its `roles.everyone` is the role given with the
1077
1124
  * guild's id, or else an @everyone role of its own at position 0 with no permissions, in `roles.cache` as Discord has
1078
1125
  * it. The guild is named `'Guild'` and its `preferredLocale` is `'en-US'` unless given, so `t.forGuild(guild)`
1079
1126
  * translates as for a new English server.
1080
1127
  *
1128
+ * The mock makes a member for any id it's asked for, so a test of an id that isn't a member rejects the fetch, as
1129
+ * `guild.members.fetch.mockRejectedValue(createDiscordError(10007))`, or resolves a collection without that member.
1130
+ *
1081
1131
  * @example
1082
1132
  * ```ts
1083
1133
  * const target = createMock<GuildMember>({ id: '111111111111111111' })
@@ -1091,6 +1141,7 @@ interface MockGuildOverrides {
1091
1141
  * @see {@link createMockMessage}
1092
1142
  */
1093
1143
  declare function createMockGuild(overrides?: MockGuildOverrides): DeepMocked<Guild>;
1144
+ type ChannelOf<T> = T extends ThreadChannel ? AnyThreadChannel : T;
1094
1145
  /**
1095
1146
  * Creates a mock channel of the given class, such as `TextChannel`, `ThreadChannel` or `DMChannel`.
1096
1147
  *
@@ -1100,7 +1151,8 @@ declare function createMockGuild(overrides?: MockGuildOverrides): DeepMocked<Gui
1100
1151
  * The managers the class has are ready to stub: `messages`, `threads` on text, announcement, forum and media channels,
1101
1152
  * and `members` on threads, each with a real, empty `cache` and the channel as its `channel`, or `thread`. A subclass gets the managers of the class it extends.
1102
1153
  * Type guards such as `isTextBased()`, `isDMBased()` and `isThread()` run discord.js's own logic, so each answers what
1103
- * the channel is.
1154
+ * the channel is. A thread is typed as discord.js types every thread it gives, a public or a private one, so it fits
1155
+ * an interaction's `channel`, a guild's channels and the `threadCreate` event.
1104
1156
  *
1105
1157
  * @param Class - The discord.js channel class to mock.
1106
1158
  * @param props - Values for the channel's properties, such as its `id`, `name` or `topic`; see {@link MockProps}.
@@ -1118,7 +1170,7 @@ declare function createMockGuild(overrides?: MockGuildOverrides): DeepMocked<Gui
1118
1170
  * @category Mocks
1119
1171
  * @see {@link createMockGuild}
1120
1172
  */
1121
- declare function createMockChannel<T extends BaseChannel>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
1173
+ declare function createMockChannel<T extends BaseChannel>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<ChannelOf<T>>;
1122
1174
  /**
1123
1175
  * What {@link createMockMember} builds a member with.
1124
1176
  *
@@ -1226,7 +1278,8 @@ interface MockMessageOverrides {
1226
1278
  * and `reply()` resolve to a new mock message. Without overrides the message is empty, in a server, from a new
1227
1279
  * person who is its `member`; with `guild: null` it is a direct message. Give `author` to send several messages as
1228
1280
  * one user, such as to reach a per-user cooldown. An author given, and the users, roles and channels the content
1229
- * mentions, are cached as the gateway delivers them.
1281
+ * mentions, are cached as the gateway delivers them. It is typed as discord.js emits a message, never in a group DM,
1282
+ * so it fits `messageCreate` and the other message events.
1230
1283
  *
1231
1284
  * @param overrides - The message's content, components and the rest; see {@link MockMessageOverrides}.
1232
1285
  *
@@ -1249,7 +1302,7 @@ interface MockMessageOverrides {
1249
1302
  * @see {@link MockMessageOverrides}
1250
1303
  * @see {@link createMockGuild}
1251
1304
  */
1252
- declare function createMockMessage(overrides?: MockMessageOverrides): DeepMocked<Message> & {
1305
+ declare function createMockMessage(overrides?: MockMessageOverrides): DeepMocked<Message> & OmitPartialGroupDMChannel<Message> & {
1253
1306
  deleted: boolean;
1254
1307
  };
1255
1308
  /**
@@ -1811,9 +1864,34 @@ interface ResponseReport {
1811
1864
  sent: boolean;
1812
1865
  /**
1813
1866
  * Every answer call the interaction got, through `respond()` or discord.js directly, in order: the payload it sent,
1814
- * without `withResponse`, and the `error` of one Discord refused.
1867
+ * without `withResponse`, and the `error` of one Discord refused. Checking a call's `method` types its `payload` as
1868
+ * that method takes it: the options of a reply, an edit or a deferral, the modal shown, or the message deleted.
1815
1869
  */
1816
- calls: readonly ResponseCall[];
1870
+ calls: readonly ((ResponseCall & {
1871
+ method: 'reply' | 'followUp';
1872
+ payload?: string | MessagePayload | InteractionReplyOptions;
1873
+ }) | (ResponseCall & {
1874
+ method: 'update';
1875
+ payload?: string | MessagePayload | InteractionUpdateOptions;
1876
+ }) | (ResponseCall & {
1877
+ method: 'editReply';
1878
+ payload?: string | MessagePayload | InteractionEditReplyOptions;
1879
+ }) | (ResponseCall & {
1880
+ method: 'message.edit';
1881
+ payload?: string | MessagePayload | MessageEditOptions;
1882
+ }) | (ResponseCall & {
1883
+ method: 'showModal';
1884
+ payload?: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalComponentData | APIModalInteractionResponseCallbackData;
1885
+ }) | (ResponseCall & {
1886
+ method: 'deferReply';
1887
+ payload?: InteractionDeferReplyOptions;
1888
+ }) | (ResponseCall & {
1889
+ method: 'deferUpdate';
1890
+ payload?: InteractionDeferUpdateOptions;
1891
+ }) | (ResponseCall & {
1892
+ method: 'deleteReply';
1893
+ payload?: MessageResolvable | '@original';
1894
+ }))[];
1817
1895
  }
1818
1896
  /**
1819
1897
  * Reports how an interaction was answered: where its answer stands, and every answer call it got, in order.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "meocord",
3
3
  "description": "Decorator-based framework for Discord bots on discord.js, with dependency injection, guards, a CLI and a testing toolkit.",
4
- "version": "4.1.0",
4
+ "version": "4.1.2",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.13"
@@ -14,6 +14,7 @@
14
14
  "prepare": "husky",
15
15
  "test": "vitest run",
16
16
  "verify:generated": "bun scripts/verify-generated.ts",
17
+ "verify:runners": "bun scripts/verify-runners.ts",
17
18
  "cli:scenarios": "bun scripts/cli-scenarios.ts",
18
19
  "test:e2e": "bun scripts/e2e.ts",
19
20
  "test:watch": "vitest",