novaapp-sdk 1.4.0 → 1.4.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.
- package/bin/nova.mjs +23 -0
- package/dist/chunk-53M2BTB5.mjs +26 -0
- package/dist/chunk-LMORGVJR.mjs +124 -0
- package/dist/cli/index.d.mts +6 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +871 -0
- package/dist/cli/index.mjs +716 -0
- package/dist/client-DbKa6yJo.d.mts +4244 -0
- package/dist/client-DbKa6yJo.d.ts +4244 -0
- package/dist/devtools/index.d.mts +94 -0
- package/dist/devtools/index.d.ts +94 -0
- package/dist/devtools/index.js +160 -0
- package/dist/devtools/index.mjs +7 -0
- package/dist/index.d.mts +6802 -2834
- package/dist/index.d.ts +6802 -2834
- package/dist/index.js +11200 -1342
- package/dist/index.mjs +11109 -1342
- package/dist/testing/index.d.mts +689 -0
- package/dist/testing/index.d.ts +689 -0
- package/dist/testing/index.js +849 -0
- package/dist/testing/index.mjs +820 -0
- package/package.json +17 -3
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import { al as Message, ae as InteractionType, ac as Interaction, ai as Member, y as BotApplication, c as SendMessageOptions, Z as EditMessageOptions, a7 as FetchMessagesOptions, L as BulkDeleteResult, aL as RespondInteractionOptions, aU as SingleMember, a6 as FetchMembersOptions, O as Channel, aM as Role, at as NovaServer, af as Invite, r as NovaClientEvents, p as NovaClient } from '../client-DbKa6yJo.js';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module testing/types
|
|
6
|
+
* Shared types for the Nova test harness.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A single recorded call to a mock API namespace.
|
|
11
|
+
* Stored in {@link MockClient.calls} and inspected by assertion helpers.
|
|
12
|
+
*/
|
|
13
|
+
interface RecordedCall {
|
|
14
|
+
/** Which API namespace was called, e.g. `'messages'`, `'interactions'`. */
|
|
15
|
+
api: string;
|
|
16
|
+
/** Method name, e.g. `'send'`, `'respond'`, `'ban'`. */
|
|
17
|
+
method: string;
|
|
18
|
+
/** Arguments passed to the method (in order). */
|
|
19
|
+
args: unknown[];
|
|
20
|
+
/** The mock value that was returned. */
|
|
21
|
+
result: unknown;
|
|
22
|
+
/** Wall-clock time of the call. */
|
|
23
|
+
timestamp: Date;
|
|
24
|
+
}
|
|
25
|
+
/** Minimal fields needed to simulate a message event. */
|
|
26
|
+
interface SimulateMessageInput {
|
|
27
|
+
content?: string;
|
|
28
|
+
channelId?: string;
|
|
29
|
+
serverId?: string;
|
|
30
|
+
authorId?: string;
|
|
31
|
+
authorName?: string;
|
|
32
|
+
id?: string;
|
|
33
|
+
/** Extra fields merged into the Message object. */
|
|
34
|
+
extra?: Partial<Message>;
|
|
35
|
+
}
|
|
36
|
+
/** Minimal fields needed to simulate an interaction. */
|
|
37
|
+
interface SimulateInteractionInput {
|
|
38
|
+
type?: InteractionType;
|
|
39
|
+
/** For slash-commands / prefix commands. */
|
|
40
|
+
commandName?: string;
|
|
41
|
+
/** For button / select / modal interactions. */
|
|
42
|
+
customId?: string;
|
|
43
|
+
/** For SELECT_MENU. */
|
|
44
|
+
values?: string[];
|
|
45
|
+
/** For MODAL_SUBMIT. */
|
|
46
|
+
modalData?: Record<string, string>;
|
|
47
|
+
userId?: string;
|
|
48
|
+
channelId?: string;
|
|
49
|
+
serverId?: string;
|
|
50
|
+
/** Slash-command options forwarded as `data`. */
|
|
51
|
+
data?: Record<string, unknown>;
|
|
52
|
+
extra?: Partial<Interaction>;
|
|
53
|
+
}
|
|
54
|
+
/** Minimal fields needed to simulate a slash-command specifically. */
|
|
55
|
+
interface SimulateSlashCommandInput {
|
|
56
|
+
name: string;
|
|
57
|
+
options?: Record<string, unknown>;
|
|
58
|
+
userId?: string;
|
|
59
|
+
channelId?: string;
|
|
60
|
+
serverId?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Minimal fields needed to simulate a button click. */
|
|
63
|
+
interface SimulateButtonInput {
|
|
64
|
+
customId: string;
|
|
65
|
+
userId?: string;
|
|
66
|
+
channelId?: string;
|
|
67
|
+
serverId?: string;
|
|
68
|
+
triggerMsgId?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Minimal fields needed to simulate a select-menu choice. */
|
|
71
|
+
interface SimulateSelectInput {
|
|
72
|
+
customId: string;
|
|
73
|
+
values: string[];
|
|
74
|
+
userId?: string;
|
|
75
|
+
channelId?: string;
|
|
76
|
+
serverId?: string;
|
|
77
|
+
}
|
|
78
|
+
/** Minimal fields needed to simulate a modal submission. */
|
|
79
|
+
interface SimulateModalInput {
|
|
80
|
+
customId: string;
|
|
81
|
+
modalData: Record<string, string>;
|
|
82
|
+
userId?: string;
|
|
83
|
+
channelId?: string;
|
|
84
|
+
serverId?: string;
|
|
85
|
+
}
|
|
86
|
+
/** Minimal fields needed to simulate a member join. */
|
|
87
|
+
interface SimulateMemberInput {
|
|
88
|
+
userId?: string;
|
|
89
|
+
serverId?: string;
|
|
90
|
+
role?: 'OWNER' | 'ADMIN' | 'MEMBER';
|
|
91
|
+
extra?: Partial<Member>;
|
|
92
|
+
}
|
|
93
|
+
/** Minimal fields needed to simulate a voice-state event. */
|
|
94
|
+
interface SimulateVoiceInput {
|
|
95
|
+
userId?: string;
|
|
96
|
+
channelId?: string;
|
|
97
|
+
serverId?: string;
|
|
98
|
+
muted?: boolean;
|
|
99
|
+
deafened?: boolean;
|
|
100
|
+
}
|
|
101
|
+
/** Minimal fields needed to simulate a reaction event. */
|
|
102
|
+
interface SimulateReactionInput {
|
|
103
|
+
messageId: string;
|
|
104
|
+
emoji: string;
|
|
105
|
+
userId?: string;
|
|
106
|
+
channelId?: string;
|
|
107
|
+
serverId?: string;
|
|
108
|
+
}
|
|
109
|
+
/** Options to `harness.assert.sent()`. */
|
|
110
|
+
interface AssertSentOptions {
|
|
111
|
+
/** Expected message content (partial match). */
|
|
112
|
+
content?: string;
|
|
113
|
+
/** Expected channelId. If omitted, any channel matches. */
|
|
114
|
+
channelId?: string;
|
|
115
|
+
/** Assert the embed's title. */
|
|
116
|
+
embedTitle?: string;
|
|
117
|
+
/** Assert any embed field by name. */
|
|
118
|
+
embedField?: string;
|
|
119
|
+
/** Number of times this kind of message should have been sent. @default ≥1 */
|
|
120
|
+
times?: number;
|
|
121
|
+
}
|
|
122
|
+
/** Options to `harness.assert.replied()`. */
|
|
123
|
+
interface AssertRepliedOptions {
|
|
124
|
+
/** Expected content of the reply payload (partial match). */
|
|
125
|
+
content?: string;
|
|
126
|
+
/** Assert the embed title. */
|
|
127
|
+
embedTitle?: string;
|
|
128
|
+
/** Number of times. @default ≥1 */
|
|
129
|
+
times?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @module testing/MockClient
|
|
134
|
+
*
|
|
135
|
+
* A lightweight in-memory substitute for {@link NovaClient} used in unit tests.
|
|
136
|
+
*
|
|
137
|
+
* `MockClient` shares the same EventEmitter interface as `NovaClient`, exposes
|
|
138
|
+
* the same API sub-namespaces, and records every API call for later inspection.
|
|
139
|
+
* It never opens a network connection.
|
|
140
|
+
*
|
|
141
|
+
* Use through {@link TestHarness} — or directly if you need lower-level control:
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* import { MockClient } from 'novaapp-sdk/testing'
|
|
146
|
+
*
|
|
147
|
+
* const client = new MockClient()
|
|
148
|
+
* setupBot(client as unknown as NovaClient)
|
|
149
|
+
*
|
|
150
|
+
* // fire a message
|
|
151
|
+
* client.emit('message', buildMockMessage({ content: '!ping' }))
|
|
152
|
+
*
|
|
153
|
+
* // inspect what the bot sent
|
|
154
|
+
* const [call] = client.findCalls('messages', 'send')
|
|
155
|
+
* console.log(call?.args) // [channelId, { content: 'Pong!' }]
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Drop-in test double for `NovaClient`.
|
|
161
|
+
*
|
|
162
|
+
* Cast to `NovaClient` with `client as unknown as NovaClient` when passing to
|
|
163
|
+
* bot setup functions. The {@link TestHarness} does this automatically.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const client = new MockClient()
|
|
168
|
+
* client.botUser = defaultBotApplication()
|
|
169
|
+
* setupMyBot(client as unknown as NovaClient)
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare class MockClient extends EventEmitter {
|
|
173
|
+
/** All recorded API calls, in chronological order. */
|
|
174
|
+
readonly calls: RecordedCall[];
|
|
175
|
+
/** Simulated bot application info. Pre-set to a default test value. */
|
|
176
|
+
botUser: BotApplication;
|
|
177
|
+
/** Mocked `client.messages` — same method names as the real MessagesAPI. */
|
|
178
|
+
readonly messages: {
|
|
179
|
+
send(channelId: string, options: SendMessageOptions): Promise<Message>;
|
|
180
|
+
edit(messageId: string, options: EditMessageOptions): Promise<Message>;
|
|
181
|
+
delete(messageId: string): Promise<{
|
|
182
|
+
ok: true;
|
|
183
|
+
}>;
|
|
184
|
+
fetch(channelId: string, options?: FetchMessagesOptions): Promise<Message[]>;
|
|
185
|
+
bulkDelete(channelId: string, messageIds: string[]): Promise<BulkDeleteResult>;
|
|
186
|
+
pin(messageId: string): Promise<{
|
|
187
|
+
ok: true;
|
|
188
|
+
}>;
|
|
189
|
+
unpin(messageId: string): Promise<{
|
|
190
|
+
ok: true;
|
|
191
|
+
}>;
|
|
192
|
+
};
|
|
193
|
+
/** Mocked `client.interactions` — records `respond()` calls. */
|
|
194
|
+
readonly interactions: {
|
|
195
|
+
respond(interactionId: string, options: RespondInteractionOptions): Promise<{
|
|
196
|
+
ok: true;
|
|
197
|
+
}>;
|
|
198
|
+
followUp(interactionId: string, options: SendMessageOptions): Promise<Message>;
|
|
199
|
+
edit(interactionId: string, options: SendMessageOptions): Promise<{
|
|
200
|
+
ok: true;
|
|
201
|
+
}>;
|
|
202
|
+
delete(interactionId: string): Promise<{
|
|
203
|
+
ok: true;
|
|
204
|
+
}>;
|
|
205
|
+
};
|
|
206
|
+
/** Mocked `client.members` */
|
|
207
|
+
readonly members: {
|
|
208
|
+
fetch(serverId: string, userId: string): Promise<SingleMember>;
|
|
209
|
+
list(serverId: string, options?: FetchMembersOptions): Promise<Member[]>;
|
|
210
|
+
kick(serverId: string, userId: string, reason?: string): Promise<{
|
|
211
|
+
ok: true;
|
|
212
|
+
}>;
|
|
213
|
+
ban(serverId: string, userId: string, reason?: string): Promise<{
|
|
214
|
+
ok: true;
|
|
215
|
+
}>;
|
|
216
|
+
unban(serverId: string, userId: string): Promise<{
|
|
217
|
+
ok: true;
|
|
218
|
+
}>;
|
|
219
|
+
mute(serverId: string, userId: string, muted: boolean): Promise<{
|
|
220
|
+
ok: true;
|
|
221
|
+
}>;
|
|
222
|
+
timeout(serverId: string, userId: string, duration: number, reason?: string): Promise<{
|
|
223
|
+
ok: true;
|
|
224
|
+
}>;
|
|
225
|
+
};
|
|
226
|
+
/** Mocked `client.channels` */
|
|
227
|
+
readonly channels: {
|
|
228
|
+
fetch(channelId: string): Promise<Channel>;
|
|
229
|
+
list(serverId: string): Promise<Channel[]>;
|
|
230
|
+
};
|
|
231
|
+
/** Mocked `client.roles` */
|
|
232
|
+
readonly roles: {
|
|
233
|
+
list(serverId: string): Promise<Role[]>;
|
|
234
|
+
add(serverId: string, userId: string, roleId: string): Promise<{
|
|
235
|
+
ok: true;
|
|
236
|
+
}>;
|
|
237
|
+
remove(serverId: string, userId: string, roleId: string): Promise<{
|
|
238
|
+
ok: true;
|
|
239
|
+
}>;
|
|
240
|
+
};
|
|
241
|
+
/** Mocked `client.servers` */
|
|
242
|
+
readonly servers: {
|
|
243
|
+
fetch(serverId: string): Promise<NovaServer>;
|
|
244
|
+
leave(serverId: string): Promise<{
|
|
245
|
+
ok: true;
|
|
246
|
+
}>;
|
|
247
|
+
};
|
|
248
|
+
/** Mocked `client.invites` */
|
|
249
|
+
readonly invites: {
|
|
250
|
+
create(serverId: string, options?: Record<string, unknown>): Promise<Invite>;
|
|
251
|
+
delete(code: string): Promise<{
|
|
252
|
+
ok: true;
|
|
253
|
+
}>;
|
|
254
|
+
list(serverId: string): Promise<Invite[]>;
|
|
255
|
+
};
|
|
256
|
+
readonly webhooks: Record<string, unknown>;
|
|
257
|
+
readonly reactions: Record<string, unknown>;
|
|
258
|
+
readonly permissions: Record<string, unknown>;
|
|
259
|
+
readonly auditlog: Record<string, unknown>;
|
|
260
|
+
readonly forum: Record<string, unknown>;
|
|
261
|
+
readonly events: Record<string, unknown>;
|
|
262
|
+
readonly categories: Record<string, unknown>;
|
|
263
|
+
readonly automod: Record<string, unknown>;
|
|
264
|
+
readonly users: Record<string, unknown>;
|
|
265
|
+
/**
|
|
266
|
+
* Recorded WS sends (via `client.wsSend`).
|
|
267
|
+
* Inspected by `harness.assert.wsSent()`.
|
|
268
|
+
*/
|
|
269
|
+
readonly wsSends: Array<{
|
|
270
|
+
channelId: string;
|
|
271
|
+
content: string;
|
|
272
|
+
}>;
|
|
273
|
+
wsSend(channelId: string, content: string): void;
|
|
274
|
+
wsTypingStart(channelId: string): void;
|
|
275
|
+
wsTypingStop(channelId: string): void;
|
|
276
|
+
setStatus(_status: string): void;
|
|
277
|
+
/** Simulated `client.connect()` — no-op in tests. */
|
|
278
|
+
connect(): Promise<this>;
|
|
279
|
+
/** Simulated `client.disconnect()` — emits `disconnect`. */
|
|
280
|
+
disconnect(): void;
|
|
281
|
+
/**
|
|
282
|
+
* Find all recorded calls to a given API namespace + method.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* const sends = client.findCalls('messages', 'send')
|
|
287
|
+
* // sends[0].args → [channelId, options]
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
findCalls(api: string, method: string): RecordedCall[];
|
|
291
|
+
/**
|
|
292
|
+
* Find all `messages.send` calls targeting a specific channel.
|
|
293
|
+
*/
|
|
294
|
+
sentTo(channelId: string): RecordedCall[];
|
|
295
|
+
/**
|
|
296
|
+
* Find all `interactions.respond` calls for a specific interaction ID.
|
|
297
|
+
*/
|
|
298
|
+
repliedTo(interactionId: string): RecordedCall[];
|
|
299
|
+
/** Reset all recorded calls and WS sends. */
|
|
300
|
+
resetCalls(): void;
|
|
301
|
+
on<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
|
|
302
|
+
on(event: string | symbol, listener: (...args: unknown[]) => void): this;
|
|
303
|
+
once<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
|
|
304
|
+
once(event: string | symbol, listener: (...args: unknown[]) => void): this;
|
|
305
|
+
off<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
|
|
306
|
+
off(event: string | symbol, listener: (...args: unknown[]) => void): this;
|
|
307
|
+
emit<K extends keyof NovaClientEvents>(event: K, ...args: Parameters<NovaClientEvents[K]>): boolean;
|
|
308
|
+
emit(event: string | symbol, ...args: unknown[]): boolean;
|
|
309
|
+
}
|
|
310
|
+
/** Convenience cast: use a MockClient anywhere a NovaClient is expected. */
|
|
311
|
+
declare function asMockClient(client: MockClient): NovaClient;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @module testing/EventSimulator
|
|
315
|
+
*
|
|
316
|
+
* Typed helpers for firing every Nova gateway event on a {@link MockClient}.
|
|
317
|
+
*
|
|
318
|
+
* `EventSimulator` is exposed as `harness.simulate` — but you can also use it
|
|
319
|
+
* standalone:
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* import { MockClient, EventSimulator } from 'novaapp-sdk/testing'
|
|
324
|
+
*
|
|
325
|
+
* const client = new MockClient()
|
|
326
|
+
* const sim = new EventSimulator(client)
|
|
327
|
+
*
|
|
328
|
+
* // Fire a message
|
|
329
|
+
* const msg = sim.message({ content: '!ping', channelId: 'ch-1', serverId: 'srv-1' })
|
|
330
|
+
*
|
|
331
|
+
* // Fire a slash command
|
|
332
|
+
* const interaction = sim.slashCommand({ name: 'ban', options: { user: 'user-123' } })
|
|
333
|
+
*
|
|
334
|
+
* // Fire a button click
|
|
335
|
+
* sim.buttonClick({ customId: 'confirm_delete', triggerMsgId: 'msg-456' })
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Fires typed events on a {@link MockClient}, returning the emitted payload
|
|
341
|
+
* so test code can reference it in assertions.
|
|
342
|
+
*
|
|
343
|
+
* Accessed via `harness.simulate`.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* const msg = harness.simulate.message({ content: '/help' })
|
|
348
|
+
* await nextTick() // let async listeners run
|
|
349
|
+
* harness.assert.sent({ channelId: msg.channelId })
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
declare class EventSimulator {
|
|
353
|
+
private readonly client;
|
|
354
|
+
constructor(client: MockClient);
|
|
355
|
+
/** Await all already-queued microtasks (useful after async event handlers). */
|
|
356
|
+
flush(): Promise<void>;
|
|
357
|
+
/**
|
|
358
|
+
* Emit a `message` event (bot received a message in a text channel).
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```ts
|
|
362
|
+
* sim.message({ content: '!ping', channelId: 'ch-1' })
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
message(input?: SimulateMessageInput): Message;
|
|
366
|
+
/**
|
|
367
|
+
* Emit a `messageEdit` event.
|
|
368
|
+
*/
|
|
369
|
+
messageEdit(input?: SimulateMessageInput & {
|
|
370
|
+
newContent?: string;
|
|
371
|
+
}): Message;
|
|
372
|
+
/**
|
|
373
|
+
* Emit a `messageDelete` event.
|
|
374
|
+
*/
|
|
375
|
+
messageDelete(messageId?: string, channelId?: string): {
|
|
376
|
+
messageId: string;
|
|
377
|
+
channelId: string;
|
|
378
|
+
};
|
|
379
|
+
/**
|
|
380
|
+
* Emit an `interaction` event for any interaction type.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts
|
|
384
|
+
* sim.interaction({ type: 'PREFIX_COMMAND', commandName: 'help' })
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
interaction(input?: SimulateInteractionInput): Interaction;
|
|
388
|
+
/**
|
|
389
|
+
* Fire a `SLASH_COMMAND` interaction.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* sim.slashCommand({ name: 'ping' })
|
|
394
|
+
* sim.slashCommand({ name: 'ban', options: { user: 'user-123', reason: 'spam' } })
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
slashCommand(input: SimulateSlashCommandInput): Interaction;
|
|
398
|
+
/**
|
|
399
|
+
* Fire a `BUTTON_CLICK` interaction.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* sim.buttonClick({ customId: 'confirm_delete' })
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
buttonClick(input: SimulateButtonInput): Interaction;
|
|
407
|
+
/**
|
|
408
|
+
* Fire a `SELECT_MENU` interaction.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```ts
|
|
412
|
+
* sim.selectMenu({ customId: 'role_picker', values: ['mod', 'admin'] })
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
selectMenu(input: SimulateSelectInput): Interaction;
|
|
416
|
+
/**
|
|
417
|
+
* Fire a `MODAL_SUBMIT` interaction.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```ts
|
|
421
|
+
* sim.modalSubmit({ customId: 'report_modal', modalData: { reason: 'spam' } })
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
modalSubmit(input: SimulateModalInput): Interaction;
|
|
425
|
+
/**
|
|
426
|
+
* Fire a `PREFIX_COMMAND` interaction.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* sim.prefixCommand({ name: 'kick', userId: 'user-1' })
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
prefixCommand(input: SimulateSlashCommandInput): Interaction;
|
|
434
|
+
/**
|
|
435
|
+
* Emit a `memberJoin` event.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```ts
|
|
439
|
+
* sim.memberJoin({ userId: 'user-123', serverId: 'srv-1' })
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
memberJoin(input?: SimulateMemberInput): Member;
|
|
443
|
+
/**
|
|
444
|
+
* Emit a `memberLeave` event.
|
|
445
|
+
*/
|
|
446
|
+
memberLeave(input?: SimulateMemberInput): Member;
|
|
447
|
+
/**
|
|
448
|
+
* Emit a `memberUpdate` event.
|
|
449
|
+
*/
|
|
450
|
+
memberUpdate(serverId?: string, userId?: string): void;
|
|
451
|
+
/**
|
|
452
|
+
* Emit a `voiceJoin` event.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* sim.voiceJoin({ userId: 'user-1', channelId: 'vc-1', serverId: 'srv-1' })
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
voiceJoin(input?: SimulateVoiceInput): {
|
|
460
|
+
userId: string;
|
|
461
|
+
channelId: string;
|
|
462
|
+
serverId: string;
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* Emit a `voiceLeave` event.
|
|
466
|
+
*/
|
|
467
|
+
voiceLeave(input?: SimulateVoiceInput): {
|
|
468
|
+
userId: string;
|
|
469
|
+
channelId: string;
|
|
470
|
+
serverId: string;
|
|
471
|
+
};
|
|
472
|
+
/**
|
|
473
|
+
* Emit a `reactionAdd` event.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```ts
|
|
477
|
+
* sim.reactionAdd({ messageId: 'msg-1', emoji: '👍' })
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
reactionAdd(input: SimulateReactionInput): void;
|
|
481
|
+
/**
|
|
482
|
+
* Emit a `reactionRemove` event.
|
|
483
|
+
*/
|
|
484
|
+
reactionRemove(input: SimulateReactionInput): void;
|
|
485
|
+
/**
|
|
486
|
+
* Emit a `channelCreate` event.
|
|
487
|
+
*/
|
|
488
|
+
channelCreate(partial: Partial<Channel> & {
|
|
489
|
+
serverId: string;
|
|
490
|
+
}): Channel;
|
|
491
|
+
/**
|
|
492
|
+
* Emit a `channelDelete` event.
|
|
493
|
+
*/
|
|
494
|
+
channelDelete(id: string, serverId: string): void;
|
|
495
|
+
/**
|
|
496
|
+
* Emit a `roleCreate` event.
|
|
497
|
+
*/
|
|
498
|
+
roleCreate(partial: Partial<Role> & {
|
|
499
|
+
serverId: string;
|
|
500
|
+
}): Role;
|
|
501
|
+
/**
|
|
502
|
+
* Emit a `roleDelete` event.
|
|
503
|
+
*/
|
|
504
|
+
roleDelete(id: string, serverId: string): void;
|
|
505
|
+
/**
|
|
506
|
+
* Emit a `memberBanned` event.
|
|
507
|
+
*/
|
|
508
|
+
memberBanned(userId: string, serverId: string, reason?: string): void;
|
|
509
|
+
/**
|
|
510
|
+
* Emit a `memberUnbanned` event.
|
|
511
|
+
*/
|
|
512
|
+
memberUnbanned(userId: string, serverId: string): void;
|
|
513
|
+
/**
|
|
514
|
+
* Emit the `ready` event (simulates successful bot connection).
|
|
515
|
+
*/
|
|
516
|
+
ready(): void;
|
|
517
|
+
/**
|
|
518
|
+
* Emit the `disconnect` event.
|
|
519
|
+
*/
|
|
520
|
+
disconnect(reason?: string): void;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @module testing/TestHarness
|
|
525
|
+
*
|
|
526
|
+
* Orchestration layer for the Nova test harness.
|
|
527
|
+
*
|
|
528
|
+
* `TestHarness` wires together a {@link MockClient}, an {@link EventSimulator},
|
|
529
|
+
* and a set of assertion helpers so you can write concise, readable tests.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```ts
|
|
533
|
+
* import { TestHarness } from 'novaapp-sdk/testing'
|
|
534
|
+
* import { setupPingCommand } from './src/commands/ping.js'
|
|
535
|
+
*
|
|
536
|
+
* test('ping replies', async () => {
|
|
537
|
+
* const harness = new TestHarness()
|
|
538
|
+
* harness.use(setupPingCommand)
|
|
539
|
+
*
|
|
540
|
+
* harness.simulate.slashCommand({ name: 'ping', channelId: 'ch-1' })
|
|
541
|
+
* await harness.flush()
|
|
542
|
+
*
|
|
543
|
+
* harness.assert.replied({ content: 'Pong!' })
|
|
544
|
+
* })
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
|
|
548
|
+
/** Assertion helpers exposed as `harness.assert`. */
|
|
549
|
+
declare class HarnessAssertions {
|
|
550
|
+
private readonly client;
|
|
551
|
+
constructor(client: MockClient);
|
|
552
|
+
/**
|
|
553
|
+
* Assert that `messages.send` was called **at least once** (or exactly `times`
|
|
554
|
+
* times), optionally matching specific call properties.
|
|
555
|
+
*/
|
|
556
|
+
sent(options?: AssertSentOptions): void;
|
|
557
|
+
/**
|
|
558
|
+
* Assert that `interactions.respond` was called, optionally matching content.
|
|
559
|
+
*/
|
|
560
|
+
replied(options?: AssertRepliedOptions): void;
|
|
561
|
+
/**
|
|
562
|
+
* Assert that `client.wsSend` was called (optionally matching channel/content).
|
|
563
|
+
*/
|
|
564
|
+
wsSent(channelId?: string, content?: string): void;
|
|
565
|
+
/**
|
|
566
|
+
* Assert that a specific API method was called exactly `expected` times.
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* harness.assert.callCount('members', 'kick', 1)
|
|
571
|
+
* ```
|
|
572
|
+
*/
|
|
573
|
+
callCount(api: string, method: string, expected: number): void;
|
|
574
|
+
/**
|
|
575
|
+
* Assert that no API calls have been recorded.
|
|
576
|
+
*/
|
|
577
|
+
noCalls(): void;
|
|
578
|
+
/**
|
|
579
|
+
* Assert that no messages were sent (neither via `messages.send` nor `wsSend`).
|
|
580
|
+
*/
|
|
581
|
+
noMessagesSent(): void;
|
|
582
|
+
/**
|
|
583
|
+
* Assert that a specific API method was called at least once.
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* harness.assert.called('members', 'kick')
|
|
588
|
+
* ```
|
|
589
|
+
*/
|
|
590
|
+
called(api: string, method: string): void;
|
|
591
|
+
/**
|
|
592
|
+
* Assert that a specific API method was **never** called.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```ts
|
|
596
|
+
* harness.assert.notCalled('members', 'ban')
|
|
597
|
+
* ```
|
|
598
|
+
*/
|
|
599
|
+
notCalled(api: string, method: string): void;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Central coordinator for Nova bot tests.
|
|
603
|
+
*
|
|
604
|
+
* Provides:
|
|
605
|
+
* - `client` — the in-memory {@link MockClient}
|
|
606
|
+
* - `simulate` — typed event-firing helpers ({@link EventSimulator})
|
|
607
|
+
* - `assert` — expectation helpers ({@link HarnessAssertions})
|
|
608
|
+
* - `use(fn)` — wire your setup functions against the mock client
|
|
609
|
+
* - `reset()` — clear state between tests
|
|
610
|
+
* - `flush()` — drain all pending microtasks
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```ts
|
|
614
|
+
* const harness = new TestHarness()
|
|
615
|
+
* harness.use(client => registerCommands(client))
|
|
616
|
+
*
|
|
617
|
+
* harness.simulate.slashCommand({ name: 'ping' })
|
|
618
|
+
* await harness.flush()
|
|
619
|
+
*
|
|
620
|
+
* harness.assert.replied({ content: 'Pong!' })
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
declare class TestHarness {
|
|
624
|
+
/** The in-memory mock client. */
|
|
625
|
+
readonly client: MockClient;
|
|
626
|
+
/** Typed event simulators. */
|
|
627
|
+
readonly simulate: EventSimulator;
|
|
628
|
+
/** Assertion helpers. */
|
|
629
|
+
readonly assert: HarnessAssertions;
|
|
630
|
+
constructor();
|
|
631
|
+
/**
|
|
632
|
+
* Run a setup function against the mock client (e.g. register commands /
|
|
633
|
+
* event handlers). Supports both sync and async setups.
|
|
634
|
+
*
|
|
635
|
+
* Chainable — call `use` multiple times to compose setup:
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* harness
|
|
640
|
+
* .use(registerCommands)
|
|
641
|
+
* .use(registerEventHandlers)
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
use(fn: (client: NovaClient) => void | Promise<void>): this;
|
|
645
|
+
/**
|
|
646
|
+
* Clear all recorded API calls, wsSend history, and event listeners.
|
|
647
|
+
* Returns `this` so you can chain: `harness.reset().use(...)`.
|
|
648
|
+
*/
|
|
649
|
+
reset(): this;
|
|
650
|
+
/**
|
|
651
|
+
* Awaits all queued microtasks and setImmediate callbacks so that any async
|
|
652
|
+
* event handlers registered with `use()` have fully run.
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* ```ts
|
|
656
|
+
* harness.simulate.slashCommand({ name: 'ban', options: { user: 'u-1' } })
|
|
657
|
+
* await harness.flush()
|
|
658
|
+
* harness.assert.called('members', 'ban')
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
flush(): Promise<void>;
|
|
662
|
+
/**
|
|
663
|
+
* All recorded API calls (shorthand for `harness.client.calls`).
|
|
664
|
+
*/
|
|
665
|
+
get calls(): RecordedCall[];
|
|
666
|
+
/**
|
|
667
|
+
* All `messages.send` calls targeting `channelId` (or all if omitted).
|
|
668
|
+
*/
|
|
669
|
+
getSent(channelId?: string): Array<{
|
|
670
|
+
channelId: string;
|
|
671
|
+
args: unknown[];
|
|
672
|
+
}>;
|
|
673
|
+
/**
|
|
674
|
+
* All `interactions.respond` call payloads.
|
|
675
|
+
*/
|
|
676
|
+
getReplies(): Array<{
|
|
677
|
+
interactionId: string;
|
|
678
|
+
args: unknown[];
|
|
679
|
+
}>;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Thrown by assertion helpers when an expectation fails. Extends `Error`
|
|
683
|
+
* so it's naturally caught by any test runner.
|
|
684
|
+
*/
|
|
685
|
+
declare class HarnessAssertionError extends Error {
|
|
686
|
+
constructor(message: string);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export { type AssertRepliedOptions, type AssertSentOptions, EventSimulator, HarnessAssertionError, MockClient, type RecordedCall, type SimulateButtonInput, type SimulateInteractionInput, type SimulateMemberInput, type SimulateMessageInput, type SimulateModalInput, type SimulateReactionInput, type SimulateSelectInput, type SimulateSlashCommandInput, type SimulateVoiceInput, TestHarness, asMockClient };
|