meocord 4.2.0 → 4.2.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +322 -0
  2. package/THIRD_PARTY_NOTICES.md +14 -13
  3. package/dist/cjs/_shared/{controller.decorator-CRn9kWob.cjs → controller.decorator-U2NBWcoU.cjs} +416 -108
  4. package/dist/cjs/_shared/{providers-ChvMqnLd.cjs → providers-B4MOFBrW.cjs} +202 -50
  5. package/dist/cjs/_shared/{sharded-cooldown-store-BdAUTdYs.cjs → sharded-cooldown-store-HTUb3Ii_.cjs} +10 -7
  6. package/dist/cjs/_shared/{startup-roots-CQo8mSP8.cjs → startup-roots-lA5xkBFq.cjs} +228 -71
  7. package/dist/cjs/_shared/{translate-error-DhDVbT5M.cjs → translate-error-CbBHUu5P.cjs} +10 -5
  8. package/dist/cjs/common/index.cjs +3 -3
  9. package/dist/cjs/core/index.cjs +188 -169
  10. package/dist/cjs/decorator/index.cjs +52 -7
  11. package/dist/cjs/testing/index.cjs +1854 -311
  12. package/dist/cli.json +1 -1
  13. package/dist/esm/bin/generator.js +4 -5
  14. package/dist/esm/bin/helper/filter-generator.helper.js +4 -5
  15. package/dist/esm/bin/helper/guard-generator.helper.js +4 -5
  16. package/dist/esm/bin/helper/interceptor-generator.helper.js +4 -5
  17. package/dist/esm/bin/helper/observer-generator.helper.js +4 -5
  18. package/dist/esm/bin/helper/pipe-generator.helper.js +4 -5
  19. package/dist/esm/bin/helper/service-generator.helper.js +4 -5
  20. package/dist/esm/bin/meocord.js +84 -25
  21. package/dist/esm/build/load-config.pre-entry.js +13 -3
  22. package/dist/esm/build/rsbuild-config.js +16 -12
  23. package/dist/esm/build/typed-externals.js +36 -0
  24. package/dist/esm/common/cooldown-store.js +10 -2
  25. package/dist/esm/common/errors.js +25 -0
  26. package/dist/esm/common/meocord-text.js +11 -8
  27. package/dist/esm/common/response/call-order.js +9 -4
  28. package/dist/esm/common/response/presenter.js +33 -6
  29. package/dist/esm/common/response/response-state.js +89 -15
  30. package/dist/esm/common/route.js +11 -4
  31. package/dist/esm/common/sharded-cooldown-store.js +1 -1
  32. package/dist/esm/common/translator.js +52 -11
  33. package/dist/esm/core/command-registration.js +93 -24
  34. package/dist/esm/core/command-routes.js +2 -2
  35. package/dist/esm/core/cooldown-runner.js +29 -8
  36. package/dist/esm/core/dispatcher.js +9 -8
  37. package/dist/esm/core/fallback.js +20 -11
  38. package/dist/esm/core/filter-runner.js +2 -2
  39. package/dist/esm/core/guard-runner.js +16 -3
  40. package/dist/esm/core/handler-input.js +4 -3
  41. package/dist/esm/core/handler-pipeline.js +10 -4
  42. package/dist/esm/core/lifecycle-hooks.js +134 -47
  43. package/dist/esm/core/meocord-factory.js +59 -34
  44. package/dist/esm/core/meocord.app.js +116 -75
  45. package/dist/esm/core/message-help.js +32 -6
  46. package/dist/esm/core/observer-runner.js +2 -1
  47. package/dist/esm/core/providers.js +19 -4
  48. package/dist/esm/core/running-calls.js +43 -0
  49. package/dist/esm/core/shard-context.js +5 -6
  50. package/dist/esm/core/shard-manager.js +16 -10
  51. package/dist/esm/core/stage-scope.js +2 -1
  52. package/dist/esm/core/startup-roots.js +21 -1
  53. package/dist/esm/core/theme-resolvers.js +4 -1
  54. package/dist/esm/core/theme-scope.js +7 -2
  55. package/dist/esm/core/theme-validation.js +17 -10
  56. package/dist/esm/decorator/app.decorator.js +31 -0
  57. package/dist/esm/decorator/controller.decorator.js +56 -20
  58. package/dist/esm/decorator/cooldown.decorator.js +17 -0
  59. package/dist/esm/decorator/filter.decorator.js +2 -2
  60. package/dist/esm/decorator/guard.decorator.js +3 -2
  61. package/dist/esm/package.json.js +1 -1
  62. package/dist/esm/testing/catalog.js +20 -13
  63. package/dist/esm/testing/cooldown-store-suite.js +67 -5
  64. package/dist/esm/testing/discord-defaults.js +225 -30
  65. package/dist/esm/testing/meocord-testing-module.js +158 -44
  66. package/dist/esm/testing/mock-fn.js +107 -26
  67. package/dist/esm/testing/mock-interaction.js +1250 -182
  68. package/dist/esm/testing/snowflake.js +9 -2
  69. package/dist/esm/testing/strict-mocks.js +40 -17
  70. package/dist/esm/util/common.util.js +47 -19
  71. package/dist/esm/util/dev-runner.util.js +8 -8
  72. package/dist/esm/util/generator-cli.util.js +4 -5
  73. package/dist/esm/util/interaction.util.js +2 -2
  74. package/dist/esm/util/meocord-config-loader.util.js +10 -2
  75. package/dist/esm/util/meocord-config-validation.util.js +3 -5
  76. package/dist/esm/util/meocord-source-config.util.js +65 -20
  77. package/dist/esm/util/method-wrapper.util.js +16 -0
  78. package/dist/esm/util/platform.util.js +10 -10
  79. package/dist/esm/util/shard-process.util.js +4 -1
  80. package/dist/esm/util/sharding-mode.util.js +5 -7
  81. package/dist/esm/util/shared-state.util.js +21 -0
  82. package/dist/esm/util/tsconfig-paths.util.js +29 -0
  83. package/dist/esm/util/tsconfig.util.js +10 -10
  84. package/dist/esm/util/value.util.js +30 -2
  85. package/dist/types/common/index.d.cts +10 -7
  86. package/dist/types/common/index.d.ts +10 -7
  87. package/dist/types/core/index.d.cts +3 -2
  88. package/dist/types/core/index.d.ts +3 -2
  89. package/dist/types/decorator/index.d.cts +1 -0
  90. package/dist/types/decorator/index.d.ts +1 -0
  91. package/dist/types/{index-CdXsFgjE.d.ts → index-DKJclIi-.d.ts} +45 -21
  92. package/dist/types/{index-nn5p7OoX.d.cts → index-Dlf0ZZtS.d.cts} +45 -21
  93. package/dist/types/interface/index.d.cts +1 -1
  94. package/dist/types/interface/index.d.ts +1 -1
  95. package/dist/types/{route-BZgFoNeB.d.cts → route-Bh1U13yy.d.cts} +3 -2
  96. package/dist/types/{route-BZgFoNeB.d.ts → route-Bh1U13yy.d.ts} +3 -2
  97. package/dist/types/testing/index.d.cts +198 -62
  98. package/dist/types/testing/index.d.ts +198 -62
  99. package/dist/types/{theme-resolvers-UKr3-6Ys.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
  100. package/dist/types/{theme-resolvers-BcOJVzR9.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
  101. package/dist/types/{translator-DHMkkCpg.d.cts → translator-Bg7hwxDF.d.cts} +21 -8
  102. package/dist/types/{translator-BViWKtLJ.d.ts → translator-CeP4bCzp.d.ts} +21 -8
  103. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
+ import { shared } from '../util/shared-state.util.js';
2
+
1
3
  // Framework-agnostic mock function, so `meocord/testing` depends on neither jest nor vitest.
2
4
  // Both detect it through `_isMockFunction` and read `fn.mock.calls` in their assertions.
3
- // Each call signature of `T`, up to four, as a union: `Parameters` and `ReturnType` read only an overloaded function's
4
- // last signature, such as the list form of a manager's `fetch`
5
5
  /**
6
6
  * Tells whether a value is a mock function: one from `meocord/testing`, `jest.fn()` or `vi.fn()`.
7
7
  *
@@ -24,13 +24,20 @@
24
24
  */ function isMockFunction(fn) {
25
25
  return typeof fn === 'function' && '_isMockFunction' in fn && fn._isMockFunction === true;
26
26
  }
27
- // Every mock createMockFn made, held weakly so the mocks of a finished test can still be collected
28
- const created = new Set();
29
- const collected = new FinalizationRegistry((ref)=>created.delete(ref));
30
- // The runner's factory useMockFn registered, and for each of its mocks how to give back the implementation it was
31
- // created with, through the runner's own setter, which a mock's behaviour may wrap
32
- let runnerFactory;
33
- const restoreImpl = new WeakMap();
27
+ /**
28
+ * What `useMockFn` set and the mocks made, kept for both builds of this meocord version, so a setup file in one module
29
+ * format sets them for specs in the other:
30
+ * - `invocationOrder`: when each call of meocord's own mocks came, across all of them, as Vitest numbers its own;
31
+ * - `created`: every mock createMockFn made, held weakly so the mocks of a finished test can still be collected;
32
+ * - `runnerFactory` and `restoreImpl`: the runner's factory useMockFn registered, and for each of its mocks how to
33
+ * give back the implementation it was created with, through the runner's own setter, which a mock's behaviour may wrap.
34
+ */ const mocks = shared('mocks', ()=>({
35
+ invocationOrder: 0,
36
+ created: new Set(),
37
+ runnerFactory: undefined,
38
+ restoreImpl: new WeakMap()
39
+ }));
40
+ const collected = new FinalizationRegistry((ref)=>mocks.created.delete(ref));
34
41
  /**
35
42
  * Makes every mock `meocord/testing` creates with the test runner's own mock function, such as Vitest's `vi.fn`.
36
43
  *
@@ -61,26 +68,36 @@ const restoreImpl = new WeakMap();
61
68
  * @category Mocks
62
69
  * @see {@link createMockFn}
63
70
  */ function useMockFn(factory) {
64
- if (factory === runnerFactory) return;
71
+ if (factory === mocks.runnerFactory) return;
65
72
  if ([
66
- ...created
73
+ ...mocks.created
67
74
  ].some((ref)=>ref.deref() !== undefined)) {
68
75
  throw new Error("useMockFn() goes before any mock is made: a mock made before it keeps meocord's own function, and the two kinds " + "would mix. Call it once, in the test runner's setup file.");
69
76
  }
70
- const probe = factory();
77
+ const refusal = (cause)=>new Error("useMockFn() takes a function that makes mocks with jest's and Vitest's API, such as vi.fn, jest.fn or bun's " + "mock. node:test's mock.fn records calls in its own shape, so under node:test leave meocord's own mock function " + 'in place.', cause === undefined ? undefined : {
78
+ cause
79
+ });
80
+ let probe;
81
+ try {
82
+ probe = factory();
83
+ } catch (error) {
84
+ // node:test's mock.fn, given on its own, throws for want of its tracker
85
+ throw refusal(error);
86
+ }
71
87
  const api = [
72
88
  'mockImplementation',
73
89
  'mockClear',
74
90
  'mockReset'
75
91
  ];
76
92
  if (!isMockFunction(probe) || !Array.isArray(probe.mock?.calls) || api.some((name)=>typeof probe[name] !== 'function')) {
77
- throw new Error("useMockFn() takes a function that makes mocks with jest's and Vitest's API, such as vi.fn, jest.fn or bun's " + "mock. node:test's mock.fn records calls in its own shape, so under node:test leave meocord's own mock function " + 'in place.');
93
+ throw refusal();
78
94
  }
79
- runnerFactory = factory;
95
+ mocks.runnerFactory = factory;
80
96
  }
97
+ /** Whether mocks are made with a runner's function, which {@link useMockFn} registered. */ const usesRunnerMockFn = ()=>mocks.runnerFactory !== undefined;
81
98
  /** Tracks a mock for clearAllMocks and resetAllMocks. */ function track(mockFn) {
82
99
  const ref = new WeakRef(mockFn);
83
- created.add(ref);
100
+ mocks.created.add(ref);
84
101
  collected.register(mockFn, ref);
85
102
  return mockFn;
86
103
  }
@@ -103,7 +120,7 @@ const restoreImpl = new WeakMap();
103
120
  * @category Mocks
104
121
  * @see {@link resetAllMocks}
105
122
  */ function clearAllMocks() {
106
- for (const ref of created)ref.deref()?.mockClear();
123
+ for (const ref of mocks.created)ref.deref()?.mockClear();
107
124
  }
108
125
  /**
109
126
  * Clears every mock from `meocord/testing`, and puts each back to the implementation it was created with.
@@ -126,12 +143,12 @@ const restoreImpl = new WeakMap();
126
143
  * @category Mocks
127
144
  * @see {@link clearAllMocks}
128
145
  */ function resetAllMocks() {
129
- for (const ref of created){
146
+ for (const ref of mocks.created){
130
147
  const mockFn = ref.deref();
131
148
  if (!mockFn) continue;
132
149
  mockFn.mockReset();
133
150
  // A runner whose reset drops the implementation the mock was created with, as jest's and bun's do, gets it back
134
- restoreImpl.get(mockFn)?.();
151
+ mocks.restoreImpl.get(mockFn)?.();
135
152
  }
136
153
  }
137
154
  /**
@@ -145,6 +162,14 @@ const restoreImpl = new WeakMap();
145
162
  * `mockReturnValue`, `mockResolvedValue`, `mockRejectedValue` and `mockImplementation` share one implementation, so
146
163
  * the last one set wins. The `*Once` variants queue, and run first, in the order they were set.
147
164
  *
165
+ * It records what Vitest's `toHaveResolved` and `toHaveBeenCalledBefore` read as well. Its call order is counted
166
+ * across meocord's own mocks, apart from the runner's, so compare the order of two of meocord's mocks, or of two of the
167
+ * runner's, not one of each.
168
+ *
169
+ * A promise the implementation returns is handed back as one that settles the same way, not as the same object: test
170
+ * it with `toEqual`, `toHaveReturnedWith` or by awaiting it, not with `toBe`. A property set on the given promise, such
171
+ * as a `cancel` method, isn't carried over.
172
+ *
148
173
  * @param impl - The implementation to run until another is set.
149
174
  *
150
175
  * @example
@@ -162,11 +187,12 @@ const restoreImpl = new WeakMap();
162
187
  * @category Mocks
163
188
  * @see {@link MockInstance}
164
189
  */ function createMockFn(impl) {
190
+ const { runnerFactory } = mocks;
165
191
  if (runnerFactory) {
166
192
  const runnerMock = runnerFactory(impl);
167
193
  if (impl) {
168
194
  const setImpl = runnerMock.mockImplementation.bind(runnerMock);
169
- restoreImpl.set(runnerMock, ()=>setImpl(impl));
195
+ mocks.restoreImpl.set(runnerMock, ()=>setImpl(impl));
170
196
  }
171
197
  return track(runnerMock);
172
198
  }
@@ -179,9 +205,20 @@ const restoreImpl = new WeakMap();
179
205
  const calls = [];
180
206
  const results = [];
181
207
  const instances = [];
208
+ const settledResults = [];
209
+ const invocationCallOrder = [];
210
+ const contexts = [];
182
211
  const mockFn = function(...args) {
183
212
  calls.push(args);
184
213
  instances.push(this);
214
+ contexts.push(this);
215
+ invocationCallOrder.push(++mocks.invocationOrder);
216
+ // Updated in place as the call settles, so a promise's outcome shows once it resolves or rejects
217
+ const settled = {
218
+ type: 'incomplete',
219
+ value: undefined
220
+ };
221
+ settledResults.push(settled);
185
222
  let type = 'return';
186
223
  let value;
187
224
  try {
@@ -192,10 +229,36 @@ const restoreImpl = new WeakMap();
192
229
  } else {
193
230
  value = undefined;
194
231
  }
232
+ if (value instanceof Promise) {
233
+ // Returned in its place, so a rejection nobody awaits stays unhandled in the test, as it is on a bot: a handler
234
+ // on the implementation's own promise would mark it handled
235
+ value = value.then((resolved)=>{
236
+ Object.assign(settled, {
237
+ type: 'fulfilled',
238
+ value: resolved
239
+ });
240
+ return resolved;
241
+ }, (rejected)=>{
242
+ Object.assign(settled, {
243
+ type: 'rejected',
244
+ value: rejected
245
+ });
246
+ throw rejected;
247
+ });
248
+ } else {
249
+ Object.assign(settled, {
250
+ type: 'fulfilled',
251
+ value
252
+ });
253
+ }
195
254
  return value;
196
255
  } catch (err) {
197
256
  type = 'throw';
198
257
  value = err;
258
+ Object.assign(settled, {
259
+ type: 'rejected',
260
+ value: err
261
+ });
199
262
  throw err;
200
263
  } finally{
201
264
  results.push({
@@ -223,6 +286,18 @@ const restoreImpl = new WeakMap();
223
286
  return calls.length > 0 ? calls[calls.length - 1] : undefined;
224
287
  }
225
288
  };
289
+ // Out of the mock's keys, so a test comparing `mock` as a whole sees the shape it always had
290
+ Object.defineProperties(mock, {
291
+ settledResults: {
292
+ get: ()=>settledResults
293
+ },
294
+ invocationCallOrder: {
295
+ get: ()=>invocationCallOrder
296
+ },
297
+ contexts: {
298
+ get: ()=>contexts
299
+ }
300
+ });
226
301
  Object.defineProperty(mockFn, 'mock', {
227
302
  value: mock,
228
303
  enumerable: false
@@ -259,16 +334,22 @@ const restoreImpl = new WeakMap();
259
334
  onceQueue.push(fn);
260
335
  return mockFn;
261
336
  };
337
+ const forgetCalls = ()=>{
338
+ for (const record of [
339
+ calls,
340
+ results,
341
+ instances,
342
+ settledResults,
343
+ invocationCallOrder,
344
+ contexts
345
+ ])record.length = 0;
346
+ };
262
347
  mockFn.mockClear = ()=>{
263
- calls.length = 0;
264
- results.length = 0;
265
- instances.length = 0;
348
+ forgetCalls();
266
349
  return mockFn;
267
350
  };
268
351
  mockFn.mockReset = ()=>{
269
- calls.length = 0;
270
- results.length = 0;
271
- instances.length = 0;
352
+ forgetCalls();
272
353
  onceQueue = [];
273
354
  currentImpl = impl;
274
355
  return mockFn;
@@ -285,4 +366,4 @@ const restoreImpl = new WeakMap();
285
366
  return track(mockFn);
286
367
  }
287
368
 
288
- export { clearAllMocks, createMockFn, isMockFunction, resetAllMocks, useMockFn };
369
+ export { clearAllMocks, createMockFn, isMockFunction, resetAllMocks, useMockFn, usesRunnerMockFn };