meocord 1.8.3 → 1.8.4

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.
@@ -0,0 +1,196 @@
1
+ /**
2
+ * MeoCord Framework
3
+ * Copyright (c) 2025 Ukasyah Rahmatullah Zada
4
+ * SPDX-License-Identifier: MIT
5
+ */ // ---------------------------------------------------------------------------
6
+ // Framework-agnostic mock function
7
+ //
8
+ // A minimal mock-fn implementation used internally by the testing utilities so
9
+ // that `meocord/testing` does not depend on jest OR vitest. Both jest and
10
+ // vitest detect a mock via the `_isMockFunction` flag, and both
11
+ // `expect(fn).toHaveBeenCalledWith(...)` / `toHaveBeenCalledTimes(...)` read
12
+ // `fn.mock.calls` — so a mock produced here is recognized by assertions in
13
+ // either framework.
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Type guard that recognises mocks produced by `createMockFn` as well as
17
+ * native `jest.fn()` / `vi.fn()` mocks — all stamp `_isMockFunction = true`.
18
+ */ function isMockFunction(fn) {
19
+ return typeof fn === 'function' && '_isMockFunction' in fn && fn._isMockFunction === true;
20
+ }
21
+ /**
22
+ * Creates a framework-agnostic mock function. Callable; records calls on
23
+ * `.mock.calls`; supports the mock-return/resolved/rejected/implementation
24
+ * API used by the testing utilities and by user assertions.
25
+ *
26
+ * @example
27
+ * const fn = createMockFn<(x: number) => number>((x) => x + 1)
28
+ * fn(2) // → 3
29
+ * fn.mockReturnValue(99)
30
+ * fn(2) // → 99
31
+ * fn.mock.calls // → [[2], [2]]
32
+ */ function createMockFn(impl) {
33
+ let currentImpl = impl;
34
+ let returnOnce = [];
35
+ let returnValue = {
36
+ set: false,
37
+ value: undefined
38
+ };
39
+ let resolvedOnce = [];
40
+ let resolvedValue = {
41
+ set: false,
42
+ value: undefined
43
+ };
44
+ let rejectedOnce = [];
45
+ let rejectedValue = {
46
+ set: false,
47
+ value: undefined
48
+ };
49
+ const implOnce = [];
50
+ let name = 'vi.fn';
51
+ const calls = [];
52
+ const results = [];
53
+ const instances = [];
54
+ const mockFn = function(...args) {
55
+ calls.push(args);
56
+ instances.push(this);
57
+ let type = 'return';
58
+ let value;
59
+ try {
60
+ if (implOnce.length > 0) {
61
+ value = implOnce.shift().apply(this, args);
62
+ } else if (returnOnce.length > 0) {
63
+ value = returnOnce.shift();
64
+ } else if (returnValue.set) {
65
+ value = returnValue.value;
66
+ } else if (resolvedOnce.length > 0) {
67
+ value = Promise.resolve(resolvedOnce.shift());
68
+ } else if (resolvedValue.set) {
69
+ value = Promise.resolve(resolvedValue.value);
70
+ } else if (rejectedOnce.length > 0) {
71
+ value = Promise.reject(rejectedOnce.shift());
72
+ } else if (rejectedValue.set) {
73
+ value = Promise.reject(rejectedValue.value);
74
+ } else if (currentImpl !== undefined) {
75
+ value = currentImpl.apply(this, args);
76
+ } else {
77
+ value = undefined;
78
+ }
79
+ return value;
80
+ } catch (err) {
81
+ type = 'throw';
82
+ value = err;
83
+ throw err;
84
+ } finally{
85
+ results.push({
86
+ type,
87
+ value
88
+ });
89
+ }
90
+ };
91
+ // Stamp the marker both jest and vitest check via isMockFunction.
92
+ Object.defineProperty(mockFn, '_isMockFunction', {
93
+ value: true,
94
+ enumerable: false
95
+ });
96
+ const mock = {
97
+ get calls () {
98
+ return calls;
99
+ },
100
+ get results () {
101
+ return results;
102
+ },
103
+ get instances () {
104
+ return instances;
105
+ },
106
+ get lastCall () {
107
+ return calls.length > 0 ? calls[calls.length - 1] : undefined;
108
+ }
109
+ };
110
+ Object.defineProperty(mockFn, 'mock', {
111
+ value: mock,
112
+ enumerable: false
113
+ });
114
+ mockFn.mockReturnValue = (v)=>{
115
+ returnValue = {
116
+ set: true,
117
+ value: v
118
+ };
119
+ return mockFn;
120
+ };
121
+ mockFn.mockReturnValueOnce = (v)=>{
122
+ returnOnce.push(v);
123
+ return mockFn;
124
+ };
125
+ mockFn.mockResolvedValue = (v)=>{
126
+ resolvedValue = {
127
+ set: true,
128
+ value: v
129
+ };
130
+ return mockFn;
131
+ };
132
+ mockFn.mockResolvedValueOnce = (v)=>{
133
+ resolvedOnce.push(v);
134
+ return mockFn;
135
+ };
136
+ mockFn.mockRejectedValue = (v)=>{
137
+ rejectedValue = {
138
+ set: true,
139
+ value: v
140
+ };
141
+ return mockFn;
142
+ };
143
+ mockFn.mockRejectedValueOnce = (v)=>{
144
+ rejectedOnce.push(v);
145
+ return mockFn;
146
+ };
147
+ mockFn.mockImplementation = (fn)=>{
148
+ currentImpl = fn;
149
+ return mockFn;
150
+ };
151
+ mockFn.mockImplementationOnce = (fn)=>{
152
+ implOnce.push(fn);
153
+ return mockFn;
154
+ };
155
+ mockFn.mockClear = ()=>{
156
+ calls.length = 0;
157
+ results.length = 0;
158
+ instances.length = 0;
159
+ return mockFn;
160
+ };
161
+ mockFn.mockReset = ()=>{
162
+ calls.length = 0;
163
+ results.length = 0;
164
+ instances.length = 0;
165
+ returnOnce = [];
166
+ returnValue = {
167
+ set: false,
168
+ value: undefined
169
+ };
170
+ resolvedOnce = [];
171
+ resolvedValue = {
172
+ set: false,
173
+ value: undefined
174
+ };
175
+ rejectedOnce = [];
176
+ rejectedValue = {
177
+ set: false,
178
+ value: undefined
179
+ };
180
+ implOnce.length = 0;
181
+ currentImpl = impl;
182
+ return mockFn;
183
+ };
184
+ mockFn.mockRestore = ()=>{
185
+ mockFn.mockReset();
186
+ return mockFn;
187
+ };
188
+ mockFn.getMockName = ()=>name;
189
+ mockFn.mockName = (n)=>{
190
+ name = n;
191
+ return mockFn;
192
+ };
193
+ return mockFn;
194
+ }
195
+
196
+ export { createMockFn, isMockFunction };
@@ -1,9 +1,9 @@
1
1
  import 'reflect-metadata';
2
- import { jest } from '@jest/globals';
2
+ import { createMockFn } from './mock-fn.js';
3
3
  import { InteractionType, ComponentType, ApplicationCommandType, CommandInteractionOptionResolver, GuildMessageManager, ThreadManager, DMMessageManager, MessageManager, ThreadMemberManager, Client, ApplicationCommandManager, UserManager, ChannelManager, GuildManager, ClientUser, Guild, GuildMemberManager, GuildChannelManager, RoleManager, GuildBanManager, Message, User, GuildMember, TextChannel, ThreadChannel, MessageMentions } from 'discord.js';
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
- // stubDeep — Proxy that auto-creates jest.fn() on any property access
6
+ // stubDeep — Proxy that auto-creates a mock fn on any property access
7
7
  // ---------------------------------------------------------------------------
8
8
  const SKIP = new Set([
9
9
  'constructor',
@@ -21,7 +21,7 @@ function stubDeep(instance, externalStubs) {
21
21
  return Reflect.get(target, prop, target);
22
22
  }
23
23
  const key = prop;
24
- // 'then' must be undefined — prevents jest treating the mock as a Promise
24
+ // 'then' must be undefined — prevents frameworks treating the mock as a Promise
25
25
  if (key === 'then') return undefined;
26
26
  // Own property writes take precedence (e.g. interaction.guildId = 'abc')
27
27
  if (Object.prototype.hasOwnProperty.call(target, key)) {
@@ -44,7 +44,7 @@ function stubDeep(instance, externalStubs) {
44
44
  }
45
45
  proto = Object.getPrototypeOf(proto);
46
46
  }
47
- const stub = typeof protoValue === 'function' ? jest.fn() : stubDeep({});
47
+ const stub = typeof protoValue === 'function' ? createMockFn() : stubDeep({});
48
48
  stubs.set(key, stub);
49
49
  return stub;
50
50
  },
@@ -114,7 +114,7 @@ const CLASS_TYPE_FIELDS = {
114
114
  }
115
115
  };
116
116
  // All known pure type-guard methods on BaseInteraction and its subclasses.
117
- // These are wired as jest.fn() wrapping the real prototype logic so they return
117
+ // These are wired as a mock fn wrapping the real prototype logic so they return
118
118
  // correct values by default and can still be overridden per test.
119
119
  const TYPE_GUARD_METHODS = [
120
120
  'isCommand',
@@ -154,14 +154,14 @@ function findPrototypeMethod(instance, name) {
154
154
  *
155
155
  * **Type guards** (`isButton()`, `isRepliable()`, etc.) run the real discord.js
156
156
  * prototype logic — no manual `.mockReturnValue(true)` setup needed. They are
157
- * still `jest.fn()` so you can override them per test.
157
+ * still mock functions so you can override them per test.
158
158
  *
159
159
  * **Reply state machine** — `replied` and `deferred` start as `false`. Calling
160
160
  * `reply()` or `deferReply()` twice throws, just like a real interaction would.
161
161
  * `followUp()`, `editReply()`, and `deleteReply()` throw if called before any
162
- * reply. These are still `jest.fn()` so call assertions work normally.
162
+ * reply. These are still mock functions so call assertions work normally.
163
163
  *
164
- * All other methods are auto-stubbed as `jest.fn()` via Proxy.
164
+ * All other methods are auto-stubbed as a mock fn via Proxy.
165
165
  *
166
166
  * @example
167
167
  * ```ts
@@ -184,12 +184,12 @@ function findPrototypeMethod(instance, name) {
184
184
  instance[key] = value;
185
185
  }
186
186
  }
187
- // Wire each type guard as jest.fn() calling the real prototype implementation.
187
+ // Wire each type guard as a mock fn calling the real prototype implementation.
188
188
  // Correct by default; overridable per test via .mockReturnValue().
189
189
  for (const name of TYPE_GUARD_METHODS){
190
190
  const method = findPrototypeMethod(instance, name);
191
191
  if (method !== null) {
192
- stubs.set(name, jest.fn().mockImplementation(()=>method.call(instance)));
192
+ stubs.set(name, createMockFn().mockImplementation(()=>method.call(instance)));
193
193
  }
194
194
  }
195
195
  // Set up reply state machine for repliable interactions
@@ -209,36 +209,36 @@ function findPrototypeMethod(instance, name) {
209
209
  if (typeof flags === 'bigint') return (flags & 64n) !== 0n;
210
210
  return false;
211
211
  };
212
- stubs.set('reply', jest.fn(async (...args)=>{
212
+ stubs.set('reply', createMockFn(async (...args)=>{
213
213
  if (instance.deferred || instance.replied) throw alreadyReplied();
214
214
  instance.replied = true;
215
215
  if (hasEphemeralFlag(args[0])) instance.ephemeral = true;
216
216
  }));
217
- stubs.set('deferReply', jest.fn(async (...args)=>{
217
+ stubs.set('deferReply', createMockFn(async (...args)=>{
218
218
  if (instance.deferred || instance.replied) throw alreadyReplied();
219
219
  instance.deferred = true;
220
220
  if (hasEphemeralFlag(args[0])) instance.ephemeral = true;
221
221
  }));
222
- stubs.set('followUp', jest.fn(async ()=>{
222
+ stubs.set('followUp', createMockFn(async ()=>{
223
223
  if (!instance.deferred && !instance.replied) throw notYetReplied('followUp');
224
224
  instance.replied = true;
225
225
  return createMockMessage();
226
226
  }));
227
- stubs.set('editReply', jest.fn(async ()=>{
227
+ stubs.set('editReply', createMockFn(async ()=>{
228
228
  if (!instance.deferred && !instance.replied) throw notYetReplied('editReply');
229
229
  instance.replied = true;
230
230
  return createMockMessage();
231
231
  }));
232
- stubs.set('deleteReply', jest.fn(async ()=>{
232
+ stubs.set('deleteReply', createMockFn(async ()=>{
233
233
  if (!instance.deferred && !instance.replied) throw notYetReplied('deleteReply');
234
234
  }));
235
235
  // deferUpdate / update — MessageComponentInteraction only (type === MessageComponent)
236
236
  if (instance.type === InteractionType.MessageComponent) {
237
- stubs.set('update', jest.fn(async ()=>{
237
+ stubs.set('update', createMockFn(async ()=>{
238
238
  if (instance.deferred || instance.replied) throw alreadyReplied();
239
239
  instance.replied = true;
240
240
  }));
241
- stubs.set('deferUpdate', jest.fn(async ()=>{
241
+ stubs.set('deferUpdate', createMockFn(async ()=>{
242
242
  if (instance.deferred || instance.replied) throw alreadyReplied();
243
243
  instance.deferred = true;
244
244
  }));
@@ -249,18 +249,18 @@ function findPrototypeMethod(instance, name) {
249
249
  // ---------------------------------------------------------------------------
250
250
  // Convenience wrappers for common discord.js classes
251
251
  // ---------------------------------------------------------------------------
252
- /** Creates a mock {@link User}. All methods are auto-stubbed as `jest.fn()`. */ const createMockUser = ()=>createMockInteraction(User);
252
+ /** Creates a mock {@link User}. All methods are auto-stubbed as a mock fn. */ const createMockUser = ()=>createMockInteraction(User);
253
253
  /**
254
254
  * Creates a mock {@link Client}.
255
255
  *
256
256
  * Manager methods that are constructor-assigned (not on the prototype) are
257
- * pre-initialized as `jest.fn()` so they work out of the box without manual
257
+ * pre-initialized as a mock fn so they work out of the box without manual
258
258
  * setup: `users.fetch`, `channels.fetch`, `guilds.fetch`, and
259
259
  * `application.commands.fetch`.
260
260
  */ function createMockClient() {
261
261
  const instance = Object.create(Client.prototype);
262
262
  // Manager properties are constructor-assigned — pre-initialize as prototype-based
263
- // stubs so ALL manager methods (not just fetch) are auto-stubbed as jest.fn().
263
+ // stubs so ALL manager methods (not just fetch) are auto-stubbed as a mock fn.
264
264
  const appInstance = Object.create(null);
265
265
  appInstance.commands = stubDeep(Object.create(ApplicationCommandManager.prototype));
266
266
  instance.users = stubDeep(Object.create(UserManager.prototype));
@@ -275,7 +275,7 @@ function findPrototypeMethod(instance, name) {
275
275
  *
276
276
  * Manager properties are constructor-assigned in discord.js. This factory
277
277
  * pre-initializes each as a prototype-based stub so all methods are
278
- * auto-stubbed as `jest.fn()`.
278
+ * auto-stubbed as a mock fn.
279
279
  *
280
280
  * Pre-initialized: `members`, `channels`, `roles`, `bans`.
281
281
  */ function createMockGuild() {
@@ -291,7 +291,7 @@ function findPrototypeMethod(instance, name) {
291
291
  *
292
292
  * Manager properties that are constructor-assigned in discord.js are
293
293
  * pre-initialized as prototype-based stubs so all methods are auto-stubbed
294
- * as `jest.fn()`:
294
+ * as a mock fn:
295
295
  * - Guild text channels (`TextChannel`, `NewsChannel`): `messages`, `threads`
296
296
  * - `DMChannel`: `messages`
297
297
  * - `ThreadChannel`: `messages`, `members`
@@ -326,7 +326,7 @@ function findPrototypeMethod(instance, name) {
326
326
  * `msg.channel.send`, `msg.guild.members.fetch`, `msg.thread.fetch`,
327
327
  * and `msg.mentions.has` all work out of the box.
328
328
  *
329
- * All methods remain `jest.fn()` — overridable per test.
329
+ * All methods remain mock functions — overridable per test.
330
330
  *
331
331
  * Note: `createMockInteraction`'s `followUp()` and `editReply()` stubs return
332
332
  * a `createMockMessage()` by default, matching the official return types.
@@ -349,7 +349,7 @@ function createMockMessage() {
349
349
  // Constructor-assigned — set as prototype-based stubs
350
350
  instance.author = stubDeep(Object.create(User.prototype));
351
351
  // Getters on the prototype — the proxy sees them as functions and returns
352
- // jest.fn(), which is wrong. Pre-initialize as own properties to shadow
352
+ // a mock fn, which is wrong. Pre-initialize as own properties to shadow
353
353
  // the prototype getters.
354
354
  Object.defineProperty(instance, 'member', {
355
355
  value: stubDeep(Object.create(GuildMember.prototype)),
@@ -370,25 +370,25 @@ function createMockMessage() {
370
370
  // MessageMentions — constructor-assigned, has methods like .has(), .members
371
371
  instance.mentions = stubDeep(Object.create(MessageMentions.prototype));
372
372
  const alreadyDeleted = ()=>new Error('This message has already been deleted.');
373
- stubs.set('delete', jest.fn(async ()=>{
373
+ stubs.set('delete', createMockFn(async ()=>{
374
374
  if (instance.deleted) throw alreadyDeleted();
375
375
  instance.deleted = true;
376
376
  }));
377
- stubs.set('edit', jest.fn(async ()=>{
377
+ stubs.set('edit', createMockFn(async ()=>{
378
378
  if (instance.deleted) throw alreadyDeleted();
379
379
  return createMockMessage();
380
380
  }));
381
- stubs.set('reply', jest.fn(async ()=>{
381
+ stubs.set('reply', createMockFn(async ()=>{
382
382
  if (instance.deleted) throw alreadyDeleted();
383
383
  return createMockMessage();
384
384
  }));
385
- stubs.set('react', jest.fn(async ()=>{
385
+ stubs.set('react', createMockFn(async ()=>{
386
386
  if (instance.deleted) throw alreadyDeleted();
387
387
  }));
388
- stubs.set('pin', jest.fn(async ()=>{
388
+ stubs.set('pin', createMockFn(async ()=>{
389
389
  if (instance.deleted) throw alreadyDeleted();
390
390
  }));
391
- stubs.set('unpin', jest.fn(async ()=>{
391
+ stubs.set('unpin', createMockFn(async ()=>{
392
392
  if (instance.deleted) throw alreadyDeleted();
393
393
  }));
394
394
  return stubDeep(instance, stubs);
@@ -398,7 +398,7 @@ function createMockMessage() {
398
398
  * `CommandInteractionOptionResolver` works: declare what options the command
399
399
  * was invoked with, and the resolver finds them by name.
400
400
  *
401
- * All explicit methods are `jest.fn()` — override per test with `.mockReturnValue()`.
401
+ * All explicit methods are mock functions — override per test with `.mockReturnValue()`.
402
402
  * Methods not listed (e.g. `getAttachment`) are auto-stubbed by the Proxy.
403
403
  *
404
404
  * @example
@@ -430,20 +430,20 @@ function createMockMessage() {
430
430
  }
431
431
  const isObjectOption = (v)=>typeof v === 'object' && v !== null && 'id' in v;
432
432
  // Use a real prototype instance so unlisted methods (e.g. getAttachment)
433
- // are found on the prototype chain and auto-stubbed as jest.fn()
433
+ // are found on the prototype chain and auto-stubbed as a mock fn
434
434
  const base = Object.create(CommandInteractionOptionResolver.prototype);
435
- base.getSubcommandGroup = jest.fn((required)=>resolveSubEntry(subcommandGroup, 'subcommand group', required));
436
- base.getSubcommand = jest.fn((required)=>resolveSubEntry(subcommand, 'subcommand', required));
437
- base.getString = jest.fn((name, required)=>resolveOrThrow(name, typeof values[name] === 'string' ? values[name] : null, required));
438
- base.getNumber = jest.fn((name, required)=>resolveOrThrow(name, typeof values[name] === 'number' ? values[name] : null, required));
439
- base.getInteger = jest.fn((name, required)=>resolveOrThrow(name, typeof values[name] === 'number' ? values[name] : null, required));
440
- base.getBoolean = jest.fn((name, required)=>resolveOrThrow(name, typeof values[name] === 'boolean' ? values[name] : null, required));
435
+ base.getSubcommandGroup = createMockFn((required)=>resolveSubEntry(subcommandGroup, 'subcommand group', required));
436
+ base.getSubcommand = createMockFn((required)=>resolveSubEntry(subcommand, 'subcommand', required));
437
+ base.getString = createMockFn((name, required)=>resolveOrThrow(name, typeof values[name] === 'string' ? values[name] : null, required));
438
+ base.getNumber = createMockFn((name, required)=>resolveOrThrow(name, typeof values[name] === 'number' ? values[name] : null, required));
439
+ base.getInteger = createMockFn((name, required)=>resolveOrThrow(name, typeof values[name] === 'number' ? values[name] : null, required));
440
+ base.getBoolean = createMockFn((name, required)=>resolveOrThrow(name, typeof values[name] === 'boolean' ? values[name] : null, required));
441
441
  const getObjectOption = (name, required)=>resolveOrThrow(name, isObjectOption(values[name]) ? values[name] : null, required);
442
- base.getUser = jest.fn(getObjectOption);
443
- base.getRole = jest.fn(getObjectOption);
444
- base.getChannel = jest.fn(getObjectOption);
445
- base.getMember = jest.fn(getObjectOption);
446
- base.getMentionable = jest.fn(getObjectOption);
442
+ base.getUser = createMockFn(getObjectOption);
443
+ base.getRole = createMockFn(getObjectOption);
444
+ base.getChannel = createMockFn(getObjectOption);
445
+ base.getMember = createMockFn(getObjectOption);
446
+ base.getMentionable = createMockFn(getObjectOption);
447
447
  return stubDeep(base);
448
448
  }
449
449
 
@@ -1,6 +1,5 @@
1
1
  import { ServiceIdentifier, Container } from 'inversify';
2
2
  import { GuardInterface } from '../interface/index.js';
3
- import { jest } from '@jest/globals';
4
3
  import { CommandInteractionOptionResolver, Channel, Client, Guild, Message, User } from 'discord.js';
5
4
  import 'webpack';
6
5
  import '../controller.enum-QA-IuReF.js';
@@ -54,10 +53,12 @@ declare class TestingModuleBuilder {
54
53
  *
55
54
  * @example
56
55
  * ```typescript
56
+ * import { MeoCordTestingModule, createMockFn } from 'meocord/testing'
57
+ *
57
58
  * const module = MeoCordTestingModule.create({
58
59
  * controllers: [PingController],
59
60
  * providers: [
60
- * { provide: PingService, useValue: { handlePing: jest.fn().mockResolvedValue('pong') } },
61
+ * { provide: PingService, useValue: { handlePing: createMockFn().mockResolvedValue('pong') } },
61
62
  * ],
62
63
  * }).compile()
63
64
  *
@@ -68,6 +69,70 @@ declare class MeoCordTestingModule {
68
69
  static create(options: TestingModuleOptions): TestingModuleBuilder;
69
70
  }
70
71
 
72
+ /**
73
+ * MeoCord Framework
74
+ * Copyright (c) 2025 Ukasyah Rahmatullah Zada
75
+ * SPDX-License-Identifier: MIT
76
+ */
77
+ interface MockResult<T = unknown> {
78
+ type: 'return' | 'throw';
79
+ value: T;
80
+ }
81
+ interface MockState<T extends (...args: any[]) => any = (...args: any[]) => any> {
82
+ /** Arguments from each call, in order. */
83
+ readonly calls: Parameters<T>[];
84
+ /** Return / throw result from each call, in order. */
85
+ readonly results: MockResult<ReturnType<T>>[];
86
+ /** The `this` value recorded for each call. */
87
+ readonly instances: any[];
88
+ /** Arguments from the most recent call, or undefined if never called. */
89
+ readonly lastCall?: Parameters<T>;
90
+ }
91
+ interface MockInstance<T extends (...args: any[]) => any = (...args: any[]) => any> {
92
+ /** Marker both jest and vitest check via `isMockFunction`. Do not remove. */
93
+ readonly _isMockFunction: true;
94
+ readonly mock: MockState<T>;
95
+ mockReturnValue(value: ReturnType<T>): MockedFunction<T>;
96
+ mockReturnValueOnce(value: ReturnType<T>): MockedFunction<T>;
97
+ mockResolvedValue(value: Awaited<ReturnType<T>>): MockedFunction<T>;
98
+ mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockedFunction<T>;
99
+ mockRejectedValue(value: unknown): MockedFunction<T>;
100
+ mockRejectedValueOnce(value: unknown): MockedFunction<T>;
101
+ mockImplementation(fn: T): MockedFunction<T>;
102
+ mockImplementationOnce(fn: T): MockedFunction<T>;
103
+ mockClear(): MockedFunction<T>;
104
+ mockReset(): MockedFunction<T>;
105
+ mockRestore(): MockedFunction<T>;
106
+ getMockName(): string;
107
+ mockName(name: string): MockedFunction<T>;
108
+ }
109
+ /**
110
+ * A callable that mirrors the signature of `T` and exposes the mock API
111
+ * (`mockReturnValue`, `mockResolvedValue`, `mockImplementation`, `.mock`, …).
112
+ * Drop-in replacement for `jest.MockedFunction<T>` / vitest `MockedFunction<T>`.
113
+ */
114
+ type MockedFunction<T extends (...args: any[]) => any> = ((...args: Parameters<T>) => ReturnType<T>) & MockInstance<T>;
115
+ /** Alias kept for parity with `jest.Mock` / vitest `Mock`. */
116
+ type Mock<T extends (...args: any[]) => any = (...args: any[]) => any> = MockedFunction<T>;
117
+ /**
118
+ * Type guard that recognises mocks produced by `createMockFn` as well as
119
+ * native `jest.fn()` / `vi.fn()` mocks — all stamp `_isMockFunction = true`.
120
+ */
121
+ declare function isMockFunction(fn: unknown): fn is MockInstance;
122
+ /**
123
+ * Creates a framework-agnostic mock function. Callable; records calls on
124
+ * `.mock.calls`; supports the mock-return/resolved/rejected/implementation
125
+ * API used by the testing utilities and by user assertions.
126
+ *
127
+ * @example
128
+ * const fn = createMockFn<(x: number) => number>((x) => x + 1)
129
+ * fn(2) // → 3
130
+ * fn.mockReturnValue(99)
131
+ * fn(2) // → 99
132
+ * fn.mock.calls // → [[2], [2]]
133
+ */
134
+ declare function createMockFn<T extends (...args: any[]) => any = (...args: any[]) => any>(impl?: T): MockedFunction<T>;
135
+
71
136
  /**
72
137
  * MeoCord Framework
73
138
  * Copyright (c) 2025 Ukasyah Rahmatullah Zada
@@ -75,13 +140,13 @@ declare class MeoCordTestingModule {
75
140
  */
76
141
 
77
142
  /**
78
- * Recursively transforms all methods of T into jest.MockedFunction and all
143
+ * Recursively transforms all methods of T into MockedFunction and all
79
144
  * nested objects into DeepMocked. Depth cap at 5 prevents infinite recursion
80
145
  * on circular discord.js types (e.g. Guild ↔ GuildMember).
81
146
  * -readonly removes readonly modifiers so test setup can write any property.
82
147
  */
83
148
  type DeepMocked<T, Depth extends number[] = []> = Depth['length'] extends 5 ? T : {
84
- -readonly [K in keyof T]: T[K] extends (...args: infer A) => infer R ? jest.MockedFunction<(...args: A) => R> : T[K] extends object ? DeepMocked<T[K], [...Depth, 0]> : T[K];
149
+ -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];
85
150
  };
86
151
  interface InteractionClass<T> {
87
152
  prototype: T;
@@ -93,14 +158,14 @@ interface InteractionClass<T> {
93
158
  *
94
159
  * **Type guards** (`isButton()`, `isRepliable()`, etc.) run the real discord.js
95
160
  * prototype logic — no manual `.mockReturnValue(true)` setup needed. They are
96
- * still `jest.fn()` so you can override them per test.
161
+ * still mock functions so you can override them per test.
97
162
  *
98
163
  * **Reply state machine** — `replied` and `deferred` start as `false`. Calling
99
164
  * `reply()` or `deferReply()` twice throws, just like a real interaction would.
100
165
  * `followUp()`, `editReply()`, and `deleteReply()` throw if called before any
101
- * reply. These are still `jest.fn()` so call assertions work normally.
166
+ * reply. These are still mock functions so call assertions work normally.
102
167
  *
103
- * All other methods are auto-stubbed as `jest.fn()` via Proxy.
168
+ * All other methods are auto-stubbed as a mock fn via Proxy.
104
169
  *
105
170
  * @example
106
171
  * ```ts
@@ -115,13 +180,13 @@ interface InteractionClass<T> {
115
180
  * ```
116
181
  */
117
182
  declare function createMockInteraction<T extends object>(Class: InteractionClass<T>): DeepMocked<T>;
118
- /** Creates a mock {@link User}. All methods are auto-stubbed as `jest.fn()`. */
183
+ /** Creates a mock {@link User}. All methods are auto-stubbed as a mock fn. */
119
184
  declare const createMockUser: () => DeepMocked<User>;
120
185
  /**
121
186
  * Creates a mock {@link Client}.
122
187
  *
123
188
  * Manager methods that are constructor-assigned (not on the prototype) are
124
- * pre-initialized as `jest.fn()` so they work out of the box without manual
189
+ * pre-initialized as a mock fn so they work out of the box without manual
125
190
  * setup: `users.fetch`, `channels.fetch`, `guilds.fetch`, and
126
191
  * `application.commands.fetch`.
127
192
  */
@@ -131,7 +196,7 @@ declare function createMockClient(): DeepMocked<Client>;
131
196
  *
132
197
  * Manager properties are constructor-assigned in discord.js. This factory
133
198
  * pre-initializes each as a prototype-based stub so all methods are
134
- * auto-stubbed as `jest.fn()`.
199
+ * auto-stubbed as a mock fn.
135
200
  *
136
201
  * Pre-initialized: `members`, `channels`, `roles`, `bans`.
137
202
  */
@@ -141,7 +206,7 @@ declare function createMockGuild(): DeepMocked<Guild>;
141
206
  *
142
207
  * Manager properties that are constructor-assigned in discord.js are
143
208
  * pre-initialized as prototype-based stubs so all methods are auto-stubbed
144
- * as `jest.fn()`:
209
+ * as a mock fn:
145
210
  * - Guild text channels (`TextChannel`, `NewsChannel`): `messages`, `threads`
146
211
  * - `DMChannel`: `messages`
147
212
  * - `ThreadChannel`: `messages`, `members`
@@ -160,7 +225,7 @@ interface ChatInputOptions {
160
225
  * `CommandInteractionOptionResolver` works: declare what options the command
161
226
  * was invoked with, and the resolver finds them by name.
162
227
  *
163
- * All explicit methods are `jest.fn()` — override per test with `.mockReturnValue()`.
228
+ * All explicit methods are mock functions — override per test with `.mockReturnValue()`.
164
229
  * Methods not listed (e.g. `getAttachment`) are auto-stubbed by the Proxy.
165
230
  *
166
231
  * @example
@@ -177,5 +242,5 @@ interface ChatInputOptions {
177
242
  */
178
243
  declare function createChatInputOptions(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver>;
179
244
 
180
- export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser };
181
- export type { ChatInputOptions, ClassProvider, DeepMocked, Provider, TestingModuleOptions, ValueProvider };
245
+ export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, isMockFunction };
246
+ export type { ChatInputOptions, ClassProvider, DeepMocked, Mock, MockInstance, MockResult, MockState, MockedFunction, Provider, TestingModuleOptions, ValueProvider };
@@ -83,7 +83,7 @@ const specConfig = {
83
83
  }
84
84
 
85
85
  module.exports = [
86
- { ignores: ['docs/*', 'build/*', 'lib/*', 'dist/*', 'meocord.config.ts', 'jest.config.ts'] },
86
+ { ignores: ['docs/*', 'build/*', 'lib/*', 'dist/*', 'meocord.config.ts', 'vitest.config.ts'] },
87
87
  ...recommendedTypeScriptConfigs,
88
88
  specConfig,
89
89
  eslintConfigPrettier,
@@ -83,7 +83,7 @@ const specConfig = {
83
83
  }
84
84
 
85
85
  export default [
86
- { ignores: ['docs/*', 'build/*', 'lib/*', 'dist/*', 'meocord.config.ts', 'jest.config.ts'] },
86
+ { ignores: ['docs/*', 'build/*', 'lib/*', 'dist/*', 'meocord.config.ts', 'vitest.config.ts'] },
87
87
  ...recommendedTypeScriptConfigs,
88
88
  specConfig,
89
89
  eslintConfigPrettier,