@rimori/playwright-testing 0.2.0
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 +498 -0
- package/dist/_old/wait-helpers.d.ts +0 -0
- package/dist/_old/wait-helpers.js +28 -0
- package/dist/core/MessageChannelSimulator.d.ts +132 -0
- package/dist/core/MessageChannelSimulator.js +341 -0
- package/dist/core/RimoriTestEnvironment.d.ts +168 -0
- package/dist/core/RimoriTestEnvironment.js +446 -0
- package/dist/fixtures/default-user-info.d.ts +3 -0
- package/dist/fixtures/default-user-info.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/test/translator.test.d.ts +1 -0
- package/dist/test/translator.test.js +134 -0
- package/package.json +38 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageChannelSimulator = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const default_user_info_1 = require("../fixtures/default-user-info");
|
|
6
|
+
class MessageChannelSimulator {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a simulator that mimics the Rimori host for plugin tests.
|
|
9
|
+
* @param param
|
|
10
|
+
* @param param.page - Playwright page hosting the plugin iframe.
|
|
11
|
+
* @param param.pluginId - Target plugin identifier.
|
|
12
|
+
* @param param.queryParams - Query parameters forwarded to the plugin init.
|
|
13
|
+
*/
|
|
14
|
+
constructor({ page, pluginId, queryParams, rimoriInfo }) {
|
|
15
|
+
this.listeners = new Map();
|
|
16
|
+
this.autoResponders = new Map();
|
|
17
|
+
this.pendingOutbound = [];
|
|
18
|
+
this.currentRimoriInfo = null;
|
|
19
|
+
this.isReady = false;
|
|
20
|
+
this.instanceId = (0, node_crypto_1.randomUUID)();
|
|
21
|
+
this.page = page;
|
|
22
|
+
this.pluginId = pluginId;
|
|
23
|
+
this.queryParams = queryParams ?? {};
|
|
24
|
+
this.baseUserInfo = this.cloneUserInfo(default_user_info_1.DEFAULT_USER_INFO);
|
|
25
|
+
this.currentUserInfo = this.cloneUserInfo(default_user_info_1.DEFAULT_USER_INFO);
|
|
26
|
+
this.providedInfo = rimoriInfo ? this.cloneRimoriInfo(rimoriInfo) : undefined;
|
|
27
|
+
this.registerAutoResponders();
|
|
28
|
+
}
|
|
29
|
+
get defaultUserInfo() {
|
|
30
|
+
return this.cloneUserInfo(this.baseUserInfo);
|
|
31
|
+
}
|
|
32
|
+
get userInfo() {
|
|
33
|
+
return this.cloneUserInfo(this.currentUserInfo);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Injects the handshake shims so the plugin talks to this simulator.
|
|
37
|
+
*/
|
|
38
|
+
async initialize() {
|
|
39
|
+
await this.page.exposeBinding('__rimoriSimulator_onHello', async () => {
|
|
40
|
+
await this.setupMessageChannel();
|
|
41
|
+
}, { handle: false });
|
|
42
|
+
await this.page.exposeBinding('__rimoriSimulator_onPortMessage', async (_source, payload) => {
|
|
43
|
+
await this.handlePortMessage(payload);
|
|
44
|
+
}, { handle: false });
|
|
45
|
+
await this.page.addInitScript(({ pluginId }) => {
|
|
46
|
+
// Create a fake parent window object to simulate iframe environment
|
|
47
|
+
// This ensures window !== window.parent (so standalone mode is NOT triggered)
|
|
48
|
+
const fakeParent = {
|
|
49
|
+
postMessage: (message, targetOriginOrOptions, transfer) => {
|
|
50
|
+
const payload = (message ?? {});
|
|
51
|
+
// Intercept rimori:hello messages
|
|
52
|
+
if (payload.type === 'rimori:hello' && payload.pluginId === pluginId) {
|
|
53
|
+
// @ts-expect-error binding injected at runtime
|
|
54
|
+
window.__rimoriSimulator_onHello();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Intercept rimori:acknowledged messages (plugin finished initialization)
|
|
58
|
+
if (payload.type === 'rimori:acknowledged' && payload.pluginId === pluginId) {
|
|
59
|
+
// Plugin has acknowledged init completion - no action needed, just intercept
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// For all other messages, allow normal postMessage behavior
|
|
63
|
+
// This handles cases where the plugin might send other messages
|
|
64
|
+
// Handle both string targetOrigin and WindowPostMessageOptions object
|
|
65
|
+
if (typeof targetOriginOrOptions === 'object' && targetOriginOrOptions !== null) {
|
|
66
|
+
window.postMessage(message, targetOriginOrOptions);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
window.postMessage(message, targetOriginOrOptions ?? '*', transfer);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
// Add other Window properties that might be accessed
|
|
73
|
+
location: window.location,
|
|
74
|
+
top: window.top,
|
|
75
|
+
frames: window.frames,
|
|
76
|
+
length: window.length,
|
|
77
|
+
closed: false,
|
|
78
|
+
opener: null,
|
|
79
|
+
frameElement: null,
|
|
80
|
+
self: window.self,
|
|
81
|
+
window: window,
|
|
82
|
+
document: window.document,
|
|
83
|
+
navigator: window.navigator,
|
|
84
|
+
history: window.history,
|
|
85
|
+
screen: window.screen,
|
|
86
|
+
outerHeight: window.outerHeight,
|
|
87
|
+
outerWidth: window.outerWidth,
|
|
88
|
+
innerHeight: window.innerHeight,
|
|
89
|
+
innerWidth: window.innerWidth,
|
|
90
|
+
scrollX: window.scrollX,
|
|
91
|
+
scrollY: window.scrollY,
|
|
92
|
+
pageXOffset: window.pageXOffset,
|
|
93
|
+
pageYOffset: window.pageYOffset,
|
|
94
|
+
// Make it NOT equal to window
|
|
95
|
+
toString: () => '[object Window] (parent)',
|
|
96
|
+
};
|
|
97
|
+
// Override window.parent to return our fake parent
|
|
98
|
+
// This makes window !== window.parent (iframe mode, not standalone)
|
|
99
|
+
Object.defineProperty(window, 'parent', {
|
|
100
|
+
get: () => fakeParent,
|
|
101
|
+
configurable: true, // Allow reconfiguration if needed
|
|
102
|
+
enumerable: true,
|
|
103
|
+
});
|
|
104
|
+
}, {
|
|
105
|
+
pluginId: this.pluginId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Sends an event into the plugin as though the Rimori parent emitted it.
|
|
110
|
+
*/
|
|
111
|
+
async emit(topic, data, sender = 'global') {
|
|
112
|
+
const message = {
|
|
113
|
+
event: {
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
sender,
|
|
116
|
+
topic,
|
|
117
|
+
data,
|
|
118
|
+
debug: false,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
if (!this.isReady) {
|
|
122
|
+
this.pendingOutbound.push(message);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await this.sendToPlugin(message);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Registers a handler for events emitted from the plugin.
|
|
129
|
+
*/
|
|
130
|
+
on(topic, handler) {
|
|
131
|
+
const handlers = this.listeners.get(topic) ?? new Set();
|
|
132
|
+
handlers.add(handler);
|
|
133
|
+
this.listeners.set(topic, handlers);
|
|
134
|
+
return () => {
|
|
135
|
+
const existing = this.listeners.get(topic);
|
|
136
|
+
if (!existing) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
existing.delete(handler);
|
|
140
|
+
if (existing.size === 0) {
|
|
141
|
+
this.listeners.delete(topic);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Overrides the default profile returned by the auto responders.
|
|
147
|
+
*/
|
|
148
|
+
setUserInfo(overrides) {
|
|
149
|
+
this.currentUserInfo = this.mergeUserInfo(this.currentUserInfo, overrides);
|
|
150
|
+
if (this.currentRimoriInfo) {
|
|
151
|
+
this.currentRimoriInfo.profile = this.cloneUserInfo(this.currentUserInfo);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
getRimoriInfo() {
|
|
155
|
+
return this.currentRimoriInfo ? this.cloneRimoriInfo(this.currentRimoriInfo) : null;
|
|
156
|
+
}
|
|
157
|
+
async setupMessageChannel() {
|
|
158
|
+
if (this.isReady) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const rimoriInfo = this.buildRimoriInfo();
|
|
162
|
+
this.currentRimoriInfo = rimoriInfo;
|
|
163
|
+
const serialized = this.serializeRimoriInfo(rimoriInfo);
|
|
164
|
+
await this.page.evaluate(({ pluginId, queryParams, instanceId, rimoriInfo: info, }) => {
|
|
165
|
+
const channel = new MessageChannel();
|
|
166
|
+
channel.port1.onmessage = (event) => {
|
|
167
|
+
// @ts-expect-error binding injected via exposeBinding
|
|
168
|
+
window.__rimoriSimulator_onPortMessage(event.data);
|
|
169
|
+
};
|
|
170
|
+
window.__rimoriSimulator_sendToPlugin = (payload) => {
|
|
171
|
+
channel.port1.postMessage(payload);
|
|
172
|
+
};
|
|
173
|
+
const initEvent = new MessageEvent('message', {
|
|
174
|
+
data: {
|
|
175
|
+
type: 'rimori:init',
|
|
176
|
+
pluginId,
|
|
177
|
+
instanceId,
|
|
178
|
+
queryParams,
|
|
179
|
+
rimoriInfo: {
|
|
180
|
+
...info,
|
|
181
|
+
expiration: new Date(info.expiration),
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
ports: [channel.port2],
|
|
185
|
+
});
|
|
186
|
+
window.dispatchEvent(initEvent);
|
|
187
|
+
}, {
|
|
188
|
+
pluginId: this.pluginId,
|
|
189
|
+
queryParams: this.queryParams,
|
|
190
|
+
instanceId: this.instanceId,
|
|
191
|
+
rimoriInfo: serialized,
|
|
192
|
+
});
|
|
193
|
+
this.isReady = true;
|
|
194
|
+
await this.flushPending();
|
|
195
|
+
}
|
|
196
|
+
async sendToPlugin(message) {
|
|
197
|
+
await this.page.evaluate((payload) => {
|
|
198
|
+
const bridge = window.__rimoriSimulator_sendToPlugin;
|
|
199
|
+
if (!bridge) {
|
|
200
|
+
throw new Error('Simulator bridge unavailable');
|
|
201
|
+
}
|
|
202
|
+
bridge(payload);
|
|
203
|
+
}, message);
|
|
204
|
+
}
|
|
205
|
+
async flushPending() {
|
|
206
|
+
if (!this.pendingOutbound.length) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (const message of this.pendingOutbound.splice(0)) {
|
|
210
|
+
await this.sendToPlugin(message);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async handlePortMessage(payload) {
|
|
214
|
+
if (!payload) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if ('event' in payload && payload.event) {
|
|
218
|
+
console.log('[MessageChannelSimulator] handlePortMessage - received event:', payload.event.topic, 'from:', payload.event.sender);
|
|
219
|
+
await this.dispatchEvent(payload.event);
|
|
220
|
+
await this.maybeRespond(payload.event);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async dispatchEvent(event) {
|
|
225
|
+
console.log('[MessageChannelSimulator] dispatchEvent - topic:', event.topic, 'sender:', event.sender, 'listeners:', this.listeners.has(event.topic) ? this.listeners.get(event.topic)?.size : 0);
|
|
226
|
+
const handlers = this.listeners.get(event.topic);
|
|
227
|
+
if (!handlers?.size) {
|
|
228
|
+
console.log('[MessageChannelSimulator] No handlers found for topic:', event.topic);
|
|
229
|
+
console.log('[MessageChannelSimulator] Available topics:', Array.from(this.listeners.keys()));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
console.log('[MessageChannelSimulator] Calling', handlers.size, 'handler(s) for topic:', event.topic);
|
|
233
|
+
for (const handler of handlers) {
|
|
234
|
+
await handler(event);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async maybeRespond(event) {
|
|
238
|
+
if (!event.eventId) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const responder = this.autoResponders.get(event.topic);
|
|
242
|
+
if (!responder) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const data = await responder(event);
|
|
246
|
+
await this.sendToPlugin({
|
|
247
|
+
type: 'response',
|
|
248
|
+
eventId: event.eventId,
|
|
249
|
+
response: {
|
|
250
|
+
topic: event.topic,
|
|
251
|
+
data,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
buildRimoriInfo() {
|
|
256
|
+
if (this.providedInfo) {
|
|
257
|
+
const clone = this.cloneRimoriInfo(this.providedInfo);
|
|
258
|
+
clone.profile = this.cloneUserInfo(this.currentUserInfo);
|
|
259
|
+
clone.pluginId = this.pluginId;
|
|
260
|
+
clone.tablePrefix = clone.tablePrefix || `${this.pluginId}_`;
|
|
261
|
+
return clone;
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
url: 'http://localhost:3500',
|
|
265
|
+
key: 'rimori-sdk-key',
|
|
266
|
+
backendUrl: 'http://localhost:3501',
|
|
267
|
+
token: 'rimori-token',
|
|
268
|
+
expiration: new Date(Date.now() + 60 * 60 * 1000),
|
|
269
|
+
tablePrefix: `${this.pluginId}_`,
|
|
270
|
+
pluginId: this.pluginId,
|
|
271
|
+
guild: {
|
|
272
|
+
id: 'guild-test',
|
|
273
|
+
longTermGoalOverride: '',
|
|
274
|
+
allowUserPluginSettings: true,
|
|
275
|
+
},
|
|
276
|
+
installedPlugins: [
|
|
277
|
+
{
|
|
278
|
+
id: this.pluginId,
|
|
279
|
+
title: 'Test Plugin',
|
|
280
|
+
description: 'Playwright testing plugin',
|
|
281
|
+
logo: '',
|
|
282
|
+
url: 'https://plugins.rimori.localhost',
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
profile: this.cloneUserInfo(this.currentUserInfo),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
serializeRimoriInfo(info) {
|
|
289
|
+
return {
|
|
290
|
+
...info,
|
|
291
|
+
expiration: info.expiration.toISOString(),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
cloneUserInfo(input) {
|
|
295
|
+
return JSON.parse(JSON.stringify(input));
|
|
296
|
+
}
|
|
297
|
+
mergeUserInfo(current, overrides) {
|
|
298
|
+
const clone = this.cloneUserInfo(current);
|
|
299
|
+
if (overrides.mother_tongue) {
|
|
300
|
+
clone.mother_tongue = {
|
|
301
|
+
...clone.mother_tongue,
|
|
302
|
+
...overrides.mother_tongue,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (overrides.target_language) {
|
|
306
|
+
clone.target_language = {
|
|
307
|
+
...clone.target_language,
|
|
308
|
+
...overrides.target_language,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (overrides.study_buddy) {
|
|
312
|
+
clone.study_buddy = {
|
|
313
|
+
...clone.study_buddy,
|
|
314
|
+
...overrides.study_buddy,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const { mother_tongue, target_language, study_buddy, ...rest } = overrides;
|
|
318
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
319
|
+
if (value === undefined) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
clone[key] = value;
|
|
323
|
+
}
|
|
324
|
+
return clone;
|
|
325
|
+
}
|
|
326
|
+
registerAutoResponders() {
|
|
327
|
+
this.autoResponders.set('global.supabase.requestAccess', () => this.buildRimoriInfo());
|
|
328
|
+
this.autoResponders.set('global.profile.requestUserInfo', () => this.cloneUserInfo(this.currentUserInfo));
|
|
329
|
+
this.autoResponders.set('global.profile.getUserInfo', () => this.cloneUserInfo(this.currentUserInfo));
|
|
330
|
+
}
|
|
331
|
+
cloneRimoriInfo(info) {
|
|
332
|
+
return {
|
|
333
|
+
...info,
|
|
334
|
+
expiration: new Date(info.expiration),
|
|
335
|
+
guild: { ...info.guild },
|
|
336
|
+
installedPlugins: info.installedPlugins.map((plugin) => ({ ...plugin })),
|
|
337
|
+
profile: this.cloneUserInfo(info.profile),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
exports.MessageChannelSimulator = MessageChannelSimulator;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Page, Request } from '@playwright/test';
|
|
2
|
+
import { UserInfo } from '@rimori/client/dist/controller/SettingsController';
|
|
3
|
+
import { MainPanelAction, Plugin } from '@rimori/client/dist/fromRimori/PluginTypes';
|
|
4
|
+
interface RimoriTestEnvironmentOptions {
|
|
5
|
+
page: Page;
|
|
6
|
+
pluginId: string;
|
|
7
|
+
queryParams?: Record<string, string>;
|
|
8
|
+
userInfo?: Record<string, unknown>;
|
|
9
|
+
installedPlugins?: Plugin[];
|
|
10
|
+
guildOverrides?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
13
|
+
interface MockOptions {
|
|
14
|
+
error?: 'aborted' | 'accessdenied' | 'addressunreachable' | 'blockedbyclient' | 'blockedbyresponse' | 'connectionaborted' | 'connectionclosed' | 'connectionfailed' | 'connectionrefused' | 'connectionreset' | 'internetdisconnected' | 'namenotresolved' | 'timedout';
|
|
15
|
+
/**
|
|
16
|
+
* The delay in milliseconds before the response is returned.
|
|
17
|
+
*/
|
|
18
|
+
delay?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Optional matcher function to determine if this mock should be used for the request.
|
|
21
|
+
* If provided, the mock will only be used if the matcher returns true.
|
|
22
|
+
* If multiple mocks match, the first one in the array will be used.
|
|
23
|
+
*/
|
|
24
|
+
matcher?: (request: Request) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The HTTP method for the route. If not provided, defaults will be used based on the route type.
|
|
27
|
+
*/
|
|
28
|
+
method?: HttpMethod;
|
|
29
|
+
}
|
|
30
|
+
export declare class RimoriTestEnvironment {
|
|
31
|
+
private readonly page;
|
|
32
|
+
private readonly pluginId;
|
|
33
|
+
private rimoriInfo;
|
|
34
|
+
private backendRoutes;
|
|
35
|
+
private supabaseRoutes;
|
|
36
|
+
private messageChannelSimulator;
|
|
37
|
+
constructor(options: RimoriTestEnvironmentOptions);
|
|
38
|
+
private interceptRoutes;
|
|
39
|
+
setup(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Formats text as SSE (Server-Sent Events) response.
|
|
42
|
+
* Since Playwright's route.fulfill() requires complete body, we format as SSE without delays.
|
|
43
|
+
*/
|
|
44
|
+
private formatAsSSE;
|
|
45
|
+
/**
|
|
46
|
+
* Normalizes a URL by removing query parameters and fragments for consistent matching.
|
|
47
|
+
*/
|
|
48
|
+
private normalizeUrl;
|
|
49
|
+
/**
|
|
50
|
+
* Creates a route key combining HTTP method and normalized URL.
|
|
51
|
+
*/
|
|
52
|
+
private createRouteKey;
|
|
53
|
+
private handleRoute;
|
|
54
|
+
/**
|
|
55
|
+
* Adds a supabase route to the supabase routes object.
|
|
56
|
+
* @param path - The path of the route.
|
|
57
|
+
* @param values - The values to return in the response.
|
|
58
|
+
* @param options - The options for the route. Method defaults to 'GET' if not specified.
|
|
59
|
+
*/
|
|
60
|
+
private addSupabaseRoute;
|
|
61
|
+
/**
|
|
62
|
+
* Adds a backend route to the backend routes object.
|
|
63
|
+
* @param path - The path of the route.
|
|
64
|
+
* @param values - The values to return in the response.
|
|
65
|
+
* @param options - The options for the route. Method defaults to 'POST' if not specified.
|
|
66
|
+
* @param isStreaming - Optional flag to mark this as a streaming response.
|
|
67
|
+
*/
|
|
68
|
+
private addBackendRoute;
|
|
69
|
+
readonly plugin: {
|
|
70
|
+
/**
|
|
71
|
+
* Mocks PATCH request for updating plugin_settings.
|
|
72
|
+
* @param response - The response for PATCH. Defaults to empty array (no rows updated).
|
|
73
|
+
* Should return array with updated row(s) like [{ id: '...' }] if update succeeds.
|
|
74
|
+
*/
|
|
75
|
+
mockSetSettings: (response?: unknown, options?: MockOptions) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Mocks GET request for fetching plugin_settings.
|
|
78
|
+
* @param settingsRow - The full row object from plugin_settings table, or null if not found.
|
|
79
|
+
* Should include: { id, plugin_id, guild_id, settings, is_guild_setting, user_id }.
|
|
80
|
+
* If null, simulates no settings exist (triggers INSERT flow).
|
|
81
|
+
*/
|
|
82
|
+
mockGetSettings: (settingsRow: {
|
|
83
|
+
id?: string;
|
|
84
|
+
plugin_id?: string;
|
|
85
|
+
guild_id?: string;
|
|
86
|
+
settings?: Record<string, unknown>;
|
|
87
|
+
is_guild_setting?: boolean;
|
|
88
|
+
user_id?: string | null;
|
|
89
|
+
} | null, options?: MockOptions) => void;
|
|
90
|
+
/**
|
|
91
|
+
* Mocks POST request for inserting plugin_settings.
|
|
92
|
+
* @param response - The response for POST. Defaults to success response with inserted row.
|
|
93
|
+
*/
|
|
94
|
+
mockInsertSettings: (response?: unknown, options?: MockOptions) => void;
|
|
95
|
+
mockGetUserInfo: (userInfo: Partial<UserInfo>, options?: MockOptions) => void;
|
|
96
|
+
mockGetPluginInfo: (pluginInfo: Plugin, options?: MockOptions) => void;
|
|
97
|
+
};
|
|
98
|
+
readonly db: {
|
|
99
|
+
mockFrom: () => void;
|
|
100
|
+
mockTable: () => void;
|
|
101
|
+
};
|
|
102
|
+
readonly event: {
|
|
103
|
+
mockEmit: () => void;
|
|
104
|
+
mockRequest: () => void;
|
|
105
|
+
mockOn: () => void;
|
|
106
|
+
mockOnce: () => void;
|
|
107
|
+
mockRespond: () => void;
|
|
108
|
+
mockEmitAccomplishment: () => void;
|
|
109
|
+
mockOnAccomplishment: () => void;
|
|
110
|
+
mockEmitSidebarAction: () => void;
|
|
111
|
+
/**
|
|
112
|
+
* Triggers a side panel action event as the parent application would.
|
|
113
|
+
* This simulates how rimori-main's SidebarPluginHandler responds to plugin's 'action.requestSidebar' events.
|
|
114
|
+
* @param payload - The action payload containing plugin_id, action_key, and action parameters
|
|
115
|
+
*/
|
|
116
|
+
triggerOnSidePanelAction: (payload: MainPanelAction) => Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Triggers a main panel action event as the parent application would.
|
|
119
|
+
* This simulates how rimori-main's MainPluginHandler uses EventBus.respond to respond
|
|
120
|
+
* to plugin's 'action.requestMain' events. When the plugin calls onMainPanelAction(),
|
|
121
|
+
* it emits '{pluginId}.action.requestMain' and listens for the response.
|
|
122
|
+
* This method sets up a responder that automatically responds when the plugin emits this event.
|
|
123
|
+
* @param payload - The main panel action payload containing plugin_id, action_key, and action parameters
|
|
124
|
+
*/
|
|
125
|
+
triggerOnMainPanelAction: (payload: MainPanelAction) => Promise<void>;
|
|
126
|
+
};
|
|
127
|
+
readonly ai: {
|
|
128
|
+
mockGetText: (values: unknown, options?: MockOptions) => void;
|
|
129
|
+
/**
|
|
130
|
+
* Mocks a streaming text response from the LLM endpoint.
|
|
131
|
+
* The text will be formatted as SSE (Server-Sent Events) to simulate streaming.
|
|
132
|
+
*
|
|
133
|
+
* **Note**: Due to Playwright's route.fulfill() requiring a complete response body,
|
|
134
|
+
* all SSE chunks are sent at once (no delays). The client will still parse it as SSE correctly.
|
|
135
|
+
*
|
|
136
|
+
* @param text - The text to stream. Will be formatted as SSE chunks.
|
|
137
|
+
* @param options - Optional mock options.
|
|
138
|
+
*/
|
|
139
|
+
mockGetSteamedText: (text: string, options?: MockOptions) => void;
|
|
140
|
+
mockGetVoice: (values: Buffer, options?: MockOptions) => void;
|
|
141
|
+
mockGetTextFromVoice: (text: string, options?: MockOptions) => void;
|
|
142
|
+
mockGetObject: (value: unknown, options?: MockOptions) => void;
|
|
143
|
+
};
|
|
144
|
+
readonly runtime: {
|
|
145
|
+
mockFetchBackend: () => void;
|
|
146
|
+
};
|
|
147
|
+
readonly community: {
|
|
148
|
+
sharedContent: {
|
|
149
|
+
mockGet: () => void;
|
|
150
|
+
mockGetList: () => void;
|
|
151
|
+
mockGetNew: () => void;
|
|
152
|
+
mockCreate: () => void;
|
|
153
|
+
mockUpdate: () => void;
|
|
154
|
+
mockComplete: () => void;
|
|
155
|
+
mockUpdateState: () => void;
|
|
156
|
+
mockRemove: () => void;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
readonly exercise: {
|
|
160
|
+
mockView: () => void;
|
|
161
|
+
mockAdd: () => void;
|
|
162
|
+
mockDelete: () => void;
|
|
163
|
+
};
|
|
164
|
+
readonly navigation: {
|
|
165
|
+
mockToDashboard: () => void;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export {};
|