meocord 1.8.4-beta.1 → 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.
package/README.md
CHANGED
|
@@ -337,7 +337,21 @@ async ban(interaction: ChatInputCommandInteraction) { ... }
|
|
|
337
337
|
|
|
338
338
|
## Testing
|
|
339
339
|
|
|
340
|
-
MeoCord ships a `meocord/testing` entry point with utilities for testing controllers in isolation — no real Discord connection required.
|
|
340
|
+
MeoCord ships a `meocord/testing` entry point with utilities for testing controllers in isolation — no real Discord connection required. The framework repo runs tests with [Vitest](https://vitest.dev/); the mocks themselves are **framework-agnostic** and work with Vitest or Jest assertions (see below).
|
|
341
|
+
|
|
342
|
+
### Running tests
|
|
343
|
+
|
|
344
|
+
From the MeoCord repo root:
|
|
345
|
+
|
|
346
|
+
```shell
|
|
347
|
+
bun run test # run once
|
|
348
|
+
bun run test:watch # watch mode
|
|
349
|
+
bun run test:coverage # coverage report
|
|
350
|
+
bun run test:typecheck # tsc -p tsconfig.test.json
|
|
351
|
+
bun run lint # eslint --fix + tsc
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
In your own bot project, add Vitest (or keep Jest) plus a `vitest.config.ts` with SWC if you use decorator metadata — see this repo's config for reference. Generated apps include `*.spec.ts` stubs but not a test runner config yet.
|
|
341
355
|
|
|
342
356
|
### `MeoCordTestingModule`
|
|
343
357
|
|
|
@@ -364,7 +378,7 @@ Creates a smart mock instance of any discord.js class. The full prototype chain
|
|
|
364
378
|
|
|
365
379
|
**Reply state machine** — for repliable interactions, `replied` and `deferred` start as `false`. Calling `reply()` or `deferReply()` twice throws, just like a real interaction. `followUp()`, `editReply()`, and `deleteReply()` throw if called before any reply. The ephemeral flag is tracked on `interaction.ephemeral`. All reply methods are still mock functions so call assertions work normally.
|
|
366
380
|
|
|
367
|
-
> **Framework-agnostic** — the mocks returned here are plain mock functions that stamp `_isMockFunction` and expose `.mock.calls`, the exact contract both `jest` and `vitest` check. Use them with either framework's `expect(...).toHaveBeenCalledWith(...)` / `toHaveBeenCalledTimes(...)` — no jest or vitest import is required to produce them.
|
|
381
|
+
> **Framework-agnostic** — the mocks returned here are plain mock functions that stamp `_isMockFunction` and expose `.mock.calls`, the exact contract both `jest` and `vitest` check. Use them with either framework's `expect(...).toHaveBeenCalledWith(...)` / `toHaveBeenCalledTimes(...)` — no jest or vitest import is required to produce them. For typed stubs in your own code, import `MockedFunction`, `createMockFn`, and `DeepMocked` from `meocord/testing`.
|
|
368
382
|
|
|
369
383
|
```typescript
|
|
370
384
|
import { createMockInteraction } from 'meocord/testing'
|
|
@@ -429,8 +443,13 @@ All methods are mock functions — override any per test with `.mockReturnValue(
|
|
|
429
443
|
Convenience wrappers for common discord.js classes. All methods are auto-stubbed as mock functions. Nested managers (`client.users`, `guild.members`, etc.) are independent nested stubs.
|
|
430
444
|
|
|
431
445
|
```typescript
|
|
432
|
-
import {
|
|
433
|
-
|
|
446
|
+
import {
|
|
447
|
+
createMockFn,
|
|
448
|
+
createMockUser,
|
|
449
|
+
createMockClient,
|
|
450
|
+
createMockGuild,
|
|
451
|
+
createMockChannel,
|
|
452
|
+
} from 'meocord/testing'
|
|
434
453
|
import { TextChannel } from 'discord.js'
|
|
435
454
|
|
|
436
455
|
const user = createMockUser()
|
|
@@ -438,8 +457,8 @@ const client = createMockClient()
|
|
|
438
457
|
const guild = createMockGuild()
|
|
439
458
|
const channel = createMockChannel(TextChannel)
|
|
440
459
|
|
|
441
|
-
// override nested manager methods per test
|
|
442
|
-
;(client.users as any).fetch =
|
|
460
|
+
// override nested manager methods per test (createMockFn works with vitest and jest matchers)
|
|
461
|
+
;(client.users as any).fetch = createMockFn(() => Promise.resolve(user))
|
|
443
462
|
await (client.users as any).fetch('user-123')
|
|
444
463
|
expect((client.users as any).fetch).toHaveBeenCalledWith('user-123')
|
|
445
464
|
```
|
|
@@ -486,8 +505,13 @@ const module = MeoCordTestingModule.create({
|
|
|
486
505
|
### Full example
|
|
487
506
|
|
|
488
507
|
```typescript
|
|
489
|
-
import {
|
|
490
|
-
|
|
508
|
+
import {
|
|
509
|
+
MeoCordTestingModule,
|
|
510
|
+
createMockFn,
|
|
511
|
+
createMockInteraction,
|
|
512
|
+
createChatInputOptions,
|
|
513
|
+
type MockedFunction,
|
|
514
|
+
} from 'meocord/testing'
|
|
491
515
|
import { ChatInputCommandInteraction } from 'discord.js'
|
|
492
516
|
import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
|
|
493
517
|
import { GreetingService } from '@src/services/greeting.service.js'
|
|
@@ -498,7 +522,7 @@ describe('GreetingSlashController', () => {
|
|
|
498
522
|
let greetingService: { buildGreeting: MockedFunction<GreetingService['buildGreeting']> }
|
|
499
523
|
|
|
500
524
|
beforeEach(() => {
|
|
501
|
-
greetingService = { buildGreeting:
|
|
525
|
+
greetingService = { buildGreeting: createMockFn() }
|
|
502
526
|
|
|
503
527
|
const module = MeoCordTestingModule.create({
|
|
504
528
|
controllers: [GreetingSlashController],
|
|
@@ -566,9 +590,10 @@ npx meocord start --prod
|
|
|
566
590
|
1. Fork the repository
|
|
567
591
|
2. Create a feature branch: `git checkout -b feat/your-feature`
|
|
568
592
|
3. Commit with conventional commits: `git commit -m "feat: add X"`
|
|
569
|
-
4.
|
|
593
|
+
4. Run `bun run lint` and `bun run test` before pushing
|
|
594
|
+
5. Push and open a pull request against `main`
|
|
570
595
|
|
|
571
|
-
Include a description of what changed and why, and add tests for any new behaviour.
|
|
596
|
+
Include a description of what changed and why, and add tests for any new behaviour. Use `fix:` / `feat:` / `docs:` prefixes so [semantic-release](https://semantic-release.gitbook.io/) can version correctly — `test:` commits do not trigger a release.
|
|
572
597
|
|
|
573
598
|
---
|
|
574
599
|
|
|
@@ -97,10 +97,12 @@ function isValueProvider(p) {
|
|
|
97
97
|
*
|
|
98
98
|
* @example
|
|
99
99
|
* ```typescript
|
|
100
|
+
* import { MeoCordTestingModule, createMockFn } from 'meocord/testing'
|
|
101
|
+
*
|
|
100
102
|
* const module = MeoCordTestingModule.create({
|
|
101
103
|
* controllers: [PingController],
|
|
102
104
|
* providers: [
|
|
103
|
-
* { provide: PingService, useValue: { handlePing:
|
|
105
|
+
* { provide: PingService, useValue: { handlePing: createMockFn().mockResolvedValue('pong') } },
|
|
104
106
|
* ],
|
|
105
107
|
* }).compile()
|
|
106
108
|
*
|
|
@@ -126,6 +128,12 @@ function isValueProvider(p) {
|
|
|
126
128
|
// `fn.mock.calls` — so a mock produced here is recognized by assertions in
|
|
127
129
|
// either framework.
|
|
128
130
|
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Type guard that recognises mocks produced by `createMockFn` as well as
|
|
133
|
+
* native `jest.fn()` / `vi.fn()` mocks — all stamp `_isMockFunction = true`.
|
|
134
|
+
*/ function isMockFunction(fn) {
|
|
135
|
+
return typeof fn === 'function' && '_isMockFunction' in fn && fn._isMockFunction === true;
|
|
136
|
+
}
|
|
129
137
|
/**
|
|
130
138
|
* Creates a framework-agnostic mock function. Callable; records calls on
|
|
131
139
|
* `.mock.calls`; supports the mock-return/resolved/rejected/implementation
|
|
@@ -752,7 +760,9 @@ exports.TestingModuleBuilder = TestingModuleBuilder;
|
|
|
752
760
|
exports.createChatInputOptions = createChatInputOptions;
|
|
753
761
|
exports.createMockChannel = createMockChannel;
|
|
754
762
|
exports.createMockClient = createMockClient;
|
|
763
|
+
exports.createMockFn = createMockFn;
|
|
755
764
|
exports.createMockGuild = createMockGuild;
|
|
756
765
|
exports.createMockInteraction = createMockInteraction;
|
|
757
766
|
exports.createMockMessage = createMockMessage;
|
|
758
767
|
exports.createMockUser = createMockUser;
|
|
768
|
+
exports.isMockFunction = isMockFunction;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder } from './meocord-testing-module.js';
|
|
2
2
|
export { createChatInputOptions, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser } from './mock-interaction.js';
|
|
3
|
+
export { createMockFn, isMockFunction } from './mock-fn.js';
|
|
@@ -94,10 +94,12 @@ function isValueProvider(p) {
|
|
|
94
94
|
*
|
|
95
95
|
* @example
|
|
96
96
|
* ```typescript
|
|
97
|
+
* import { MeoCordTestingModule, createMockFn } from 'meocord/testing'
|
|
98
|
+
*
|
|
97
99
|
* const module = MeoCordTestingModule.create({
|
|
98
100
|
* controllers: [PingController],
|
|
99
101
|
* providers: [
|
|
100
|
-
* { provide: PingService, useValue: { handlePing:
|
|
102
|
+
* { provide: PingService, useValue: { handlePing: createMockFn().mockResolvedValue('pong') } },
|
|
101
103
|
* ],
|
|
102
104
|
* }).compile()
|
|
103
105
|
*
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
// `fn.mock.calls` — so a mock produced here is recognized by assertions in
|
|
13
13
|
// either framework.
|
|
14
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
|
+
}
|
|
15
21
|
/**
|
|
16
22
|
* Creates a framework-agnostic mock function. Callable; records calls on
|
|
17
23
|
* `.mock.calls`; supports the mock-return/resolved/rejected/implementation
|
|
@@ -187,4 +193,4 @@
|
|
|
187
193
|
return mockFn;
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
export { createMockFn };
|
|
196
|
+
export { createMockFn, isMockFunction };
|
|
@@ -53,10 +53,12 @@ declare class TestingModuleBuilder {
|
|
|
53
53
|
*
|
|
54
54
|
* @example
|
|
55
55
|
* ```typescript
|
|
56
|
+
* import { MeoCordTestingModule, createMockFn } from 'meocord/testing'
|
|
57
|
+
*
|
|
56
58
|
* const module = MeoCordTestingModule.create({
|
|
57
59
|
* controllers: [PingController],
|
|
58
60
|
* providers: [
|
|
59
|
-
* { provide: PingService, useValue: { handlePing:
|
|
61
|
+
* { provide: PingService, useValue: { handlePing: createMockFn().mockResolvedValue('pong') } },
|
|
60
62
|
* ],
|
|
61
63
|
* }).compile()
|
|
62
64
|
*
|
|
@@ -110,6 +112,26 @@ interface MockInstance<T extends (...args: any[]) => any = (...args: any[]) => a
|
|
|
110
112
|
* Drop-in replacement for `jest.MockedFunction<T>` / vitest `MockedFunction<T>`.
|
|
111
113
|
*/
|
|
112
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>;
|
|
113
135
|
|
|
114
136
|
/**
|
|
115
137
|
* MeoCord Framework
|
|
@@ -220,5 +242,5 @@ interface ChatInputOptions {
|
|
|
220
242
|
*/
|
|
221
243
|
declare function createChatInputOptions(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver>;
|
|
222
244
|
|
|
223
|
-
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser };
|
|
224
|
-
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 };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meocord",
|
|
3
3
|
"description": "Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.",
|
|
4
|
-
"version": "1.8.4
|
|
4
|
+
"version": "1.8.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint --fix . && tsc --noEmit && tsc --noEmit --project tsconfig.test.json",
|