meocord 1.8.4 → 2.0.0-beta.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/dist/cjs/testing/index.cjs +31 -3
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/meocord-testing-module.js +7 -1
- package/dist/esm/testing/mock-interaction.js +24 -2
- package/dist/types/decorator/index.d.ts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +36 -4
- package/package.json +20 -17
|
@@ -22,7 +22,13 @@ function isValueProvider(p) {
|
|
|
22
22
|
* Builder returned by `MeoCordTestingModule.create()`.
|
|
23
23
|
* Call `.compile()` to get the resolved `TestingModule`.
|
|
24
24
|
*/ class TestingModuleBuilder {
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* A double covers the methods under test, not the whole class — and a class
|
|
27
|
+
* with any private member (a service holding a logger, say) can never be
|
|
28
|
+
* satisfied by an object literal at all. `Partial<T>` still rejects a
|
|
29
|
+
* misspelled method name, which is the check that actually earns its keep.
|
|
30
|
+
* Matches `overrideGuard`, which has always taken `Partial<GuardInterface>`.
|
|
31
|
+
*/ overrideProvider(token) {
|
|
26
32
|
return {
|
|
27
33
|
useValue: (value)=>{
|
|
28
34
|
this.overrides.set(token, {
|
|
@@ -355,8 +361,17 @@ function stubDeep(instance, externalStubs) {
|
|
|
355
361
|
stubs.set(key, stub);
|
|
356
362
|
return stub;
|
|
357
363
|
},
|
|
364
|
+
// defineProperty rather than assignment: many discord.js properties are
|
|
365
|
+
// prototype getters with no setter (targetUser, targetMessage, createdAt),
|
|
366
|
+
// and a plain write against one of those is a silent no-op. Defining an own
|
|
367
|
+
// data property shadows the accessor, which is what test setup means.
|
|
358
368
|
set (target, prop, value) {
|
|
359
|
-
|
|
369
|
+
Object.defineProperty(target, prop, {
|
|
370
|
+
value,
|
|
371
|
+
writable: true,
|
|
372
|
+
enumerable: true,
|
|
373
|
+
configurable: true
|
|
374
|
+
});
|
|
360
375
|
return true;
|
|
361
376
|
}
|
|
362
377
|
});
|
|
@@ -481,7 +496,7 @@ function findPrototypeMethod(instance, name) {
|
|
|
481
496
|
* interaction.replied // → true
|
|
482
497
|
* await interaction.reply({}) // throws — already replied
|
|
483
498
|
* ```
|
|
484
|
-
*/ function createMockInteraction(Class) {
|
|
499
|
+
*/ function createMockInteraction(Class, props) {
|
|
485
500
|
const instance = Object.create(Class.prototype);
|
|
486
501
|
const stubs = new Map();
|
|
487
502
|
// Set type fields so all prototype type-guard methods compute the right value
|
|
@@ -551,6 +566,19 @@ function findPrototypeMethod(instance, name) {
|
|
|
551
566
|
}));
|
|
552
567
|
}
|
|
553
568
|
}
|
|
569
|
+
// Applied last so an explicit prop wins over the type fields and the reply
|
|
570
|
+
// state machine. defineProperty rather than assignment for the same reason the
|
|
571
|
+
// Proxy uses it: several of these shadow a getter-only prototype accessor.
|
|
572
|
+
if (props !== undefined) {
|
|
573
|
+
for (const [key, value] of Object.entries(props)){
|
|
574
|
+
Object.defineProperty(instance, key, {
|
|
575
|
+
value,
|
|
576
|
+
writable: true,
|
|
577
|
+
enumerable: true,
|
|
578
|
+
configurable: true
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
554
582
|
return stubDeep(instance, stubs);
|
|
555
583
|
}
|
|
556
584
|
// ---------------------------------------------------------------------------
|
package/dist/esm/package.json.js
CHANGED
|
@@ -19,7 +19,13 @@ function isValueProvider(p) {
|
|
|
19
19
|
* Builder returned by `MeoCordTestingModule.create()`.
|
|
20
20
|
* Call `.compile()` to get the resolved `TestingModule`.
|
|
21
21
|
*/ class TestingModuleBuilder {
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* A double covers the methods under test, not the whole class — and a class
|
|
24
|
+
* with any private member (a service holding a logger, say) can never be
|
|
25
|
+
* satisfied by an object literal at all. `Partial<T>` still rejects a
|
|
26
|
+
* misspelled method name, which is the check that actually earns its keep.
|
|
27
|
+
* Matches `overrideGuard`, which has always taken `Partial<GuardInterface>`.
|
|
28
|
+
*/ overrideProvider(token) {
|
|
23
29
|
return {
|
|
24
30
|
useValue: (value)=>{
|
|
25
31
|
this.overrides.set(token, {
|
|
@@ -48,8 +48,17 @@ function stubDeep(instance, externalStubs) {
|
|
|
48
48
|
stubs.set(key, stub);
|
|
49
49
|
return stub;
|
|
50
50
|
},
|
|
51
|
+
// defineProperty rather than assignment: many discord.js properties are
|
|
52
|
+
// prototype getters with no setter (targetUser, targetMessage, createdAt),
|
|
53
|
+
// and a plain write against one of those is a silent no-op. Defining an own
|
|
54
|
+
// data property shadows the accessor, which is what test setup means.
|
|
51
55
|
set (target, prop, value) {
|
|
52
|
-
|
|
56
|
+
Object.defineProperty(target, prop, {
|
|
57
|
+
value,
|
|
58
|
+
writable: true,
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true
|
|
61
|
+
});
|
|
53
62
|
return true;
|
|
54
63
|
}
|
|
55
64
|
});
|
|
@@ -174,7 +183,7 @@ function findPrototypeMethod(instance, name) {
|
|
|
174
183
|
* interaction.replied // → true
|
|
175
184
|
* await interaction.reply({}) // throws — already replied
|
|
176
185
|
* ```
|
|
177
|
-
*/ function createMockInteraction(Class) {
|
|
186
|
+
*/ function createMockInteraction(Class, props) {
|
|
178
187
|
const instance = Object.create(Class.prototype);
|
|
179
188
|
const stubs = new Map();
|
|
180
189
|
// Set type fields so all prototype type-guard methods compute the right value
|
|
@@ -244,6 +253,19 @@ function findPrototypeMethod(instance, name) {
|
|
|
244
253
|
}));
|
|
245
254
|
}
|
|
246
255
|
}
|
|
256
|
+
// Applied last so an explicit prop wins over the type fields and the reply
|
|
257
|
+
// state machine. defineProperty rather than assignment for the same reason the
|
|
258
|
+
// Proxy uses it: several of these shadow a getter-only prototype accessor.
|
|
259
|
+
if (props !== undefined) {
|
|
260
|
+
for (const [key, value] of Object.entries(props)){
|
|
261
|
+
Object.defineProperty(instance, key, {
|
|
262
|
+
value,
|
|
263
|
+
writable: true,
|
|
264
|
+
enumerable: true,
|
|
265
|
+
configurable: true
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
247
269
|
return stubDeep(instance, stubs);
|
|
248
270
|
}
|
|
249
271
|
// ---------------------------------------------------------------------------
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CommandType } from '../enum/index.js';
|
|
2
2
|
import { SlashCommandBuilder, SlashCommandSubcommandsOnlyBuilder, ContextMenuCommandBuilder, ButtonInteraction, StringSelectMenuInteraction, ChatInputCommandInteraction, UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, OmitPartialGroupDMChannel, Message, MessageReaction, PartialMessageReaction, ClientOptions, ActivityOptions } from 'discord.js';
|
|
3
3
|
import { ReactionHandlerOptions, GuardInterface } from '../interface/index.js';
|
|
4
4
|
import { ServiceIdentifier } from 'inversify';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { User, PartialUser, BaseInteraction, Message, MessageReaction } from 'discord.js';
|
|
2
2
|
import { Configuration } from 'webpack';
|
|
3
|
-
import {
|
|
3
|
+
import { ReactionHandlerAction } from '../enum/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* MeoCord Framework
|
|
@@ -40,8 +40,15 @@ declare class TestingModuleBuilder {
|
|
|
40
40
|
private readonly overrides;
|
|
41
41
|
private readonly guardOverrides;
|
|
42
42
|
constructor(options: TestingModuleOptions);
|
|
43
|
+
/**
|
|
44
|
+
* A double covers the methods under test, not the whole class — and a class
|
|
45
|
+
* with any private member (a service holding a logger, say) can never be
|
|
46
|
+
* satisfied by an object literal at all. `Partial<T>` still rejects a
|
|
47
|
+
* misspelled method name, which is the check that actually earns its keep.
|
|
48
|
+
* Matches `overrideGuard`, which has always taken `Partial<GuardInterface>`.
|
|
49
|
+
*/
|
|
43
50
|
overrideProvider<T>(token: ServiceIdentifier<T>): {
|
|
44
|
-
useValue: (value: T) => TestingModuleBuilder;
|
|
51
|
+
useValue: (value: Partial<T>) => TestingModuleBuilder;
|
|
45
52
|
};
|
|
46
53
|
overrideGuard(guard: new (...args: any[]) => GuardInterface): {
|
|
47
54
|
useValue: (stub: Partial<GuardInterface>) => TestingModuleBuilder;
|
|
@@ -143,10 +150,35 @@ declare function createMockFn<T extends (...args: any[]) => any = (...args: any[
|
|
|
143
150
|
* Recursively transforms all methods of T into MockedFunction and all
|
|
144
151
|
* nested objects into DeepMocked. Depth cap at 5 prevents infinite recursion
|
|
145
152
|
* on circular discord.js types (e.g. Guild ↔ GuildMember).
|
|
146
|
-
*
|
|
153
|
+
*
|
|
154
|
+
* Intersected with `T` so the mock is accepted wherever the real class is
|
|
155
|
+
* expected. A mapped type alone can never be: it iterates `keyof T`, and the
|
|
156
|
+
* private members classes use as brands — discord.js stamps
|
|
157
|
+
* `private readonly _cacheType` on `BaseInteraction` — are not in `keyof T`.
|
|
158
|
+
* Without the intersection every call site has to launder the mock through
|
|
159
|
+
* `as unknown as T`, which defeats the point of typing it at all.
|
|
160
|
+
*
|
|
161
|
+
* The intersection is honest rather than a convenient lie: the object really is
|
|
162
|
+
* `Object.create(Class.prototype)`, so its prototype chain is the real one, and
|
|
163
|
+
* TypeScript `private` is erased at runtime.
|
|
164
|
+
*
|
|
165
|
+
* `readonly` survives from the `T` side, so properties the class declares
|
|
166
|
+
* readonly cannot be assigned after construction. Pass those to the factory as
|
|
167
|
+
* {@link MockProps} instead.
|
|
147
168
|
*/
|
|
148
169
|
type DeepMocked<T, Depth extends number[] = []> = Depth['length'] extends 5 ? T : {
|
|
149
170
|
-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];
|
|
171
|
+
} & T;
|
|
172
|
+
/**
|
|
173
|
+
* Property overrides accepted at construction by the mock factories.
|
|
174
|
+
*
|
|
175
|
+
* Setup belongs here rather than in a post-construction assignment: the returned
|
|
176
|
+
* mock is assignable to the real discord.js class, so anything that class declares
|
|
177
|
+
* `readonly` — `ModalSubmitInteraction#customId`, `#fields`, `MessageComponentInteraction#message`
|
|
178
|
+
* — cannot be written afterwards without a cast.
|
|
179
|
+
*/
|
|
180
|
+
type MockProps<T> = {
|
|
181
|
+
-readonly [K in keyof T]?: T[K] extends (...args: any[]) => any ? (...args: any[]) => any : T[K];
|
|
150
182
|
};
|
|
151
183
|
interface InteractionClass<T> {
|
|
152
184
|
prototype: T;
|
|
@@ -179,7 +211,7 @@ interface InteractionClass<T> {
|
|
|
179
211
|
* await interaction.reply({}) // throws — already replied
|
|
180
212
|
* ```
|
|
181
213
|
*/
|
|
182
|
-
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>): DeepMocked<T>;
|
|
214
|
+
declare function createMockInteraction<T extends object>(Class: InteractionClass<T>, props?: MockProps<T>): DeepMocked<T>;
|
|
183
215
|
/** Creates a mock {@link User}. All methods are auto-stubbed as a mock fn. */
|
|
184
216
|
declare const createMockUser: () => DeepMocked<User>;
|
|
185
217
|
/**
|
|
@@ -243,4 +275,4 @@ interface ChatInputOptions {
|
|
|
243
275
|
declare function createChatInputOptions(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver>;
|
|
244
276
|
|
|
245
277
|
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 };
|
|
278
|
+
export type { ChatInputOptions, ClassProvider, DeepMocked, Mock, MockInstance, MockProps, MockResult, MockState, MockedFunction, Provider, TestingModuleOptions, ValueProvider };
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
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": "
|
|
4
|
+
"version": "2.0.0-beta.1",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
6
9
|
"scripts": {
|
|
7
10
|
"lint": "eslint --fix . && tsc --noEmit && tsc --noEmit --project tsconfig.test.json",
|
|
8
11
|
"build": "rm -rf ./dist && rollup -c",
|
|
@@ -72,13 +75,13 @@
|
|
|
72
75
|
],
|
|
73
76
|
"dependencies": {
|
|
74
77
|
"@clack/prompts": "^1.7.0",
|
|
75
|
-
"@swc/core": "1.15.
|
|
76
|
-
"chalk": "^
|
|
78
|
+
"@swc/core": "1.15.47",
|
|
79
|
+
"chalk": "^6.0.0",
|
|
77
80
|
"cli-table3": "^0.6.5",
|
|
78
81
|
"commander": "^15.0.0",
|
|
79
82
|
"dayjs": "^1.11.21",
|
|
80
83
|
"dotenv": "^17.4.2",
|
|
81
|
-
"inversify": "^8.
|
|
84
|
+
"inversify": "^8.2.3",
|
|
82
85
|
"jiti": "^2.7.0",
|
|
83
86
|
"lodash-es": "^4.18.1",
|
|
84
87
|
"nodemon": "^3.1.14",
|
|
@@ -87,7 +90,7 @@
|
|
|
87
90
|
"swc-loader": "^0.2.7",
|
|
88
91
|
"terser-webpack-plugin": "^5.6.1",
|
|
89
92
|
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
|
90
|
-
"webpack": "^5.
|
|
93
|
+
"webpack": "^5.109.2",
|
|
91
94
|
"webpack-node-externals": "^3.0.0"
|
|
92
95
|
},
|
|
93
96
|
"devDependencies": {
|
|
@@ -102,27 +105,27 @@
|
|
|
102
105
|
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
103
106
|
"@types/lodash-es": "^4.17.12",
|
|
104
107
|
"@types/webpack-node-externals": "^3.0.4",
|
|
105
|
-
"@typescript-eslint/parser": "^8.
|
|
106
|
-
"discord.js": "^14.
|
|
107
|
-
"eslint": "^10.
|
|
108
|
+
"@typescript-eslint/parser": "^8.67.0",
|
|
109
|
+
"discord.js": "^14.27.0",
|
|
110
|
+
"eslint": "^10.8.1",
|
|
108
111
|
"eslint-config-prettier": "^10.1.8",
|
|
109
112
|
"eslint-plugin-headers": "^1.3.4",
|
|
110
113
|
"eslint-plugin-import-x": "^4.17.1",
|
|
111
114
|
"eslint-plugin-prettier": "^5.5.6",
|
|
112
115
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
113
|
-
"globals": "^17.
|
|
116
|
+
"globals": "^17.11.0",
|
|
114
117
|
"husky": "^9.1.7",
|
|
115
|
-
"prettier": "^3.9.
|
|
116
|
-
"rollup": "^4.62.
|
|
118
|
+
"prettier": "^3.9.6",
|
|
119
|
+
"rollup": "^4.62.4",
|
|
117
120
|
"rollup-plugin-copy": "^3.5.0",
|
|
118
|
-
"rollup-plugin-dts": "^6.
|
|
119
|
-
"semantic-release": "^25.0.
|
|
121
|
+
"rollup-plugin-dts": "^6.5.1",
|
|
122
|
+
"semantic-release": "^25.0.9",
|
|
120
123
|
"ts-node": "^10.9.2",
|
|
121
124
|
"typescript": "^6.0.3",
|
|
122
|
-
"typescript-eslint": "^8.
|
|
123
|
-
"@vitest/coverage-istanbul": "^
|
|
124
|
-
"unplugin-swc": "^1.5.
|
|
125
|
-
"vitest": "^
|
|
125
|
+
"typescript-eslint": "^8.67.0",
|
|
126
|
+
"@vitest/coverage-istanbul": "^4.1.10",
|
|
127
|
+
"unplugin-swc": "^1.5.11",
|
|
128
|
+
"vitest": "^4.1.10"
|
|
126
129
|
},
|
|
127
130
|
"peerDependencies": {
|
|
128
131
|
"discord.js": "^14.26.4",
|