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.
@@ -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
- overrideProvider(token) {
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
- Reflect.set(target, prop, value, target);
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
  // ---------------------------------------------------------------------------
@@ -1,4 +1,4 @@
1
- var version = "1.2.6";
1
+ var version = "0.0.0-development";
2
2
  var packageJson = {
3
3
  version: version};
4
4
 
@@ -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
- overrideProvider(token) {
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
- Reflect.set(target, prop, value, target);
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 { C as CommandType } from '../controller.enum-QA-IuReF.js';
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 { R as ReactionHandlerAction } from '../controller.enum-QA-IuReF.js';
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
- * -readonly removes readonly modifiers so test setup can write any property.
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": "1.8.4",
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.43",
76
- "chalk": "^5.6.2",
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.1.1",
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.108.3",
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.62.1",
106
- "discord.js": "^14.26.4",
107
- "eslint": "^10.6.0",
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.7.0",
116
+ "globals": "^17.11.0",
114
117
  "husky": "^9.1.7",
115
- "prettier": "^3.9.4",
116
- "rollup": "^4.62.2",
118
+ "prettier": "^3.9.6",
119
+ "rollup": "^4.62.4",
117
120
  "rollup-plugin-copy": "^3.5.0",
118
- "rollup-plugin-dts": "^6.4.1",
119
- "semantic-release": "^25.0.5",
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.62.1",
123
- "@vitest/coverage-istanbul": "^3.2.4",
124
- "unplugin-swc": "^1.5.5",
125
- "vitest": "^3.2.4"
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",