@rimori/playwright-testing 0.2.1 → 0.2.3-next.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/README.md +151 -0
- package/dist/core/MessageChannelSimulator.d.ts +18 -82
- package/dist/core/MessageChannelSimulator.js +63 -104
- package/dist/core/RimoriTestEnvironment.d.ts +163 -25
- package/dist/core/RimoriTestEnvironment.js +383 -136
- package/dist/core/SettingsStateManager.d.ts +41 -0
- package/dist/core/SettingsStateManager.js +74 -0
- package/dist/fixtures/default-user-info.js +1 -0
- package/dist/test/translator.test.js +1 -1
- package/package.json +7 -2
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Page, Request } from '@playwright/test';
|
|
2
2
|
import { UserInfo } from '@rimori/client/dist/controller/SettingsController';
|
|
3
3
|
import { MainPanelAction, Plugin } from '@rimori/client/dist/fromRimori/PluginTypes';
|
|
4
|
+
import { PluginSettings } from './SettingsStateManager';
|
|
5
|
+
import { EventPayload } from '@rimori/client/dist/fromRimori/EventBus';
|
|
4
6
|
interface RimoriTestEnvironmentOptions {
|
|
5
7
|
page: Page;
|
|
6
8
|
pluginId: string;
|
|
9
|
+
pluginUrl: string;
|
|
10
|
+
settings?: PluginSettings;
|
|
7
11
|
queryParams?: Record<string, string>;
|
|
8
12
|
userInfo?: Record<string, unknown>;
|
|
9
13
|
installedPlugins?: Plugin[];
|
|
@@ -26,6 +30,12 @@ interface MockOptions {
|
|
|
26
30
|
* The HTTP method for the route. If not provided, defaults will be used based on the route type.
|
|
27
31
|
*/
|
|
28
32
|
method?: HttpMethod;
|
|
33
|
+
/**
|
|
34
|
+
* If true, the mock is removed after first use. Default: false (persistent).
|
|
35
|
+
* This allows for sequential mock responses where each mock is consumed once.
|
|
36
|
+
* Useful for testing flows where the same route is called multiple times with different responses.
|
|
37
|
+
*/
|
|
38
|
+
once?: boolean;
|
|
29
39
|
}
|
|
30
40
|
export declare class RimoriTestEnvironment {
|
|
31
41
|
private readonly page;
|
|
@@ -34,9 +44,16 @@ export declare class RimoriTestEnvironment {
|
|
|
34
44
|
private backendRoutes;
|
|
35
45
|
private supabaseRoutes;
|
|
36
46
|
private messageChannelSimulator;
|
|
47
|
+
private settingsManager;
|
|
37
48
|
constructor(options: RimoriTestEnvironmentOptions);
|
|
38
49
|
private interceptRoutes;
|
|
39
50
|
setup(): Promise<void>;
|
|
51
|
+
private getRimoriInfo;
|
|
52
|
+
/**
|
|
53
|
+
* Sets up the plugin_settings routes to use the SettingsStateManager.
|
|
54
|
+
* GET returns current state, PATCH updates state, POST creates/updates state.
|
|
55
|
+
*/
|
|
56
|
+
private setupSettingsRoutes;
|
|
40
57
|
/**
|
|
41
58
|
* Formats text as SSE (Server-Sent Events) response.
|
|
42
59
|
* Since Playwright's route.fulfill() requires complete body, we format as SSE without delays.
|
|
@@ -50,6 +67,10 @@ export declare class RimoriTestEnvironment {
|
|
|
50
67
|
* Creates a route key combining HTTP method and normalized URL.
|
|
51
68
|
*/
|
|
52
69
|
private createRouteKey;
|
|
70
|
+
/**
|
|
71
|
+
* Removes a one-time mock from the mocks array after it's been used.
|
|
72
|
+
*/
|
|
73
|
+
private removeOneTimeMock;
|
|
53
74
|
private handleRoute;
|
|
54
75
|
/**
|
|
55
76
|
* Adds a supabase route to the supabase routes object.
|
|
@@ -68,46 +89,108 @@ export declare class RimoriTestEnvironment {
|
|
|
68
89
|
private addBackendRoute;
|
|
69
90
|
readonly plugin: {
|
|
70
91
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
92
|
+
* Manually set the settings state (useful for test setup).
|
|
93
|
+
* This directly modifies the internal settings state.
|
|
94
|
+
* @param settings - The settings to set, or null to clear settings
|
|
95
|
+
*/
|
|
96
|
+
setSettings: (settings: PluginSettings | null) => void;
|
|
97
|
+
/**
|
|
98
|
+
* Get the current settings state (useful for assertions).
|
|
99
|
+
* @returns The current settings or null if no settings exist
|
|
100
|
+
*/
|
|
101
|
+
getSettings: () => PluginSettings | null;
|
|
102
|
+
/**
|
|
103
|
+
* Override the GET handler for plugin_settings (rarely needed).
|
|
104
|
+
* By default, GET returns the current state from SettingsStateManager.
|
|
74
105
|
*/
|
|
75
|
-
|
|
106
|
+
mockGetSettings: (settingsRow: PluginSettings | null, options?: MockOptions) => void;
|
|
76
107
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* Should include: { id, plugin_id, guild_id, settings, is_guild_setting, user_id }.
|
|
80
|
-
* If null, simulates no settings exist (triggers INSERT flow).
|
|
108
|
+
* Override the PATCH handler for plugin_settings (rarely needed).
|
|
109
|
+
* By default, PATCH updates the state in SettingsStateManager.
|
|
81
110
|
*/
|
|
82
|
-
|
|
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;
|
|
111
|
+
mockSetSettings: (response: unknown, options?: MockOptions) => void;
|
|
90
112
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
113
|
+
* Override the POST handler for plugin_settings (rarely needed).
|
|
114
|
+
* By default, POST inserts/updates the state in SettingsStateManager.
|
|
93
115
|
*/
|
|
94
|
-
mockInsertSettings: (response
|
|
116
|
+
mockInsertSettings: (response: unknown, options?: MockOptions) => void;
|
|
95
117
|
mockGetUserInfo: (userInfo: Partial<UserInfo>, options?: MockOptions) => void;
|
|
96
118
|
mockGetPluginInfo: (pluginInfo: Plugin, options?: MockOptions) => void;
|
|
97
119
|
};
|
|
98
120
|
readonly db: {
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Mocks a Supabase table endpoint (from(tableName)).
|
|
123
|
+
* The table name will be prefixed with the plugin ID in the actual URL.
|
|
124
|
+
*
|
|
125
|
+
* Supabase operations map to HTTP methods as follows:
|
|
126
|
+
* - .select() → GET
|
|
127
|
+
* - .insert() → POST
|
|
128
|
+
* - .update() → PATCH
|
|
129
|
+
* - .delete() → DELETE (can return data with .delete().select())
|
|
130
|
+
* - .upsert() → POST
|
|
131
|
+
*
|
|
132
|
+
* @param tableName - The table name (e.g., 'decks')
|
|
133
|
+
* @param value - The response value to return for the request
|
|
134
|
+
* @param options - Mock options including HTTP method (defaults to 'GET' if not specified)
|
|
135
|
+
*/
|
|
136
|
+
mockFrom: (tableName: string, value: unknown, options?: MockOptions) => void;
|
|
101
137
|
};
|
|
102
138
|
readonly event: {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Emit an event into the plugin as if it came from Rimori main or another plugin.
|
|
141
|
+
*
|
|
142
|
+
* Note: This does NOT currently reach worker listeners such as those in
|
|
143
|
+
* `worker/listeners/decks.ts` or `worker/listeners/flascards.ts` – those run in a
|
|
144
|
+
* separate process. This helper is intended for UI‑side events only.
|
|
145
|
+
*/
|
|
146
|
+
mockEmit: (topic: string, data: EventPayload, sender?: string) => Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Registers a one-time auto-responder for request/response style events.
|
|
149
|
+
*
|
|
150
|
+
* When the plugin calls `plugin.event.request(topic, data)`, this registered responder
|
|
151
|
+
* will automatically return the provided response value. The responder is automatically
|
|
152
|
+
* removed after the first request, ensuring it only responds once.
|
|
153
|
+
*
|
|
154
|
+
* Example:
|
|
155
|
+
* ```ts
|
|
156
|
+
* // Register a responder that will return deck summaries when requested
|
|
157
|
+
* env.event.mockRequest('deck.requestOpenToday', [
|
|
158
|
+
* { id: 'deck-1', name: 'My Deck', total_new: 5, total_learning: 2, total_review: 10 }
|
|
159
|
+
* ]);
|
|
160
|
+
*
|
|
161
|
+
* // Now when the plugin calls: plugin.event.request('deck.requestOpenToday', {})
|
|
162
|
+
* // It will receive the deck summaries array above
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @param topic - The event topic to respond to (e.g., 'deck.requestOpenToday')
|
|
166
|
+
* @param response - The response value to return, or a function that receives the event and returns the response
|
|
167
|
+
* @returns A function to manually remove the responder before it's used
|
|
168
|
+
*/
|
|
169
|
+
mockRequest: (topic: string, response: unknown | ((event: unknown) => unknown)) => () => void;
|
|
170
|
+
/**
|
|
171
|
+
* Listen for events emitted by the plugin.
|
|
172
|
+
* @param topic - The event topic to listen for (e.g., 'global.accomplishment.triggerMicro')
|
|
173
|
+
* @param handler - The handler function that receives the event data
|
|
174
|
+
* @returns A function to unsubscribe from the event
|
|
175
|
+
*/
|
|
176
|
+
on: (topic: string, handler: (data: unknown) => void) => (() => void);
|
|
106
177
|
mockOnce: () => void;
|
|
107
178
|
mockRespond: () => void;
|
|
108
179
|
mockEmitAccomplishment: () => void;
|
|
109
180
|
mockOnAccomplishment: () => void;
|
|
110
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Emits a sidebar action event into the plugin as if Rimori main had triggered it.
|
|
183
|
+
* This is useful for testing sidebar-driven flows like flashcard creation from selected text.
|
|
184
|
+
*
|
|
185
|
+
* It sends a message on the 'global.sidebar.triggerAction' topic, which plugins can listen to via:
|
|
186
|
+
* plugin.event.on<{ action: string; text: string }>('global.sidebar.triggerAction', ...)
|
|
187
|
+
*
|
|
188
|
+
* @param payload - The payload forwarded to the plugin, typically including an `action` key and optional `text`.
|
|
189
|
+
*/
|
|
190
|
+
triggerSidebarAction: (payload: {
|
|
191
|
+
action: string;
|
|
192
|
+
text?: string;
|
|
193
|
+
}) => Promise<void>;
|
|
111
194
|
/**
|
|
112
195
|
* Triggers a side panel action event as the parent application would.
|
|
113
196
|
* This simulates how rimori-main's SidebarPluginHandler responds to plugin's 'action.requestSidebar' events.
|
|
@@ -141,6 +224,61 @@ export declare class RimoriTestEnvironment {
|
|
|
141
224
|
mockGetTextFromVoice: (text: string, options?: MockOptions) => void;
|
|
142
225
|
mockGetObject: (value: unknown, options?: MockOptions) => void;
|
|
143
226
|
};
|
|
227
|
+
/**
|
|
228
|
+
* Helpers for tracking browser audio playback in tests.
|
|
229
|
+
*
|
|
230
|
+
* This is useful for components like the AudioPlayer in @rimori/react-client which:
|
|
231
|
+
* 1) Fetch audio data from the backend (mocked via `env.ai.mockGetVoice`)
|
|
232
|
+
* 2) Create `new Audio(url)` and call `.play()`
|
|
233
|
+
*
|
|
234
|
+
* With tracking enabled you can assert how many times audio playback was attempted:
|
|
235
|
+
*
|
|
236
|
+
* ```ts
|
|
237
|
+
* await env.audio.enableTracking();
|
|
238
|
+
* await env.ai.mockGetVoice(Buffer.from('dummy'), { method: 'POST' });
|
|
239
|
+
* await env.setup();
|
|
240
|
+
* // ...navigate and trigger audio...
|
|
241
|
+
* const counts = await env.audio.getPlayCounts();
|
|
242
|
+
* expect(counts.mediaPlayCalls).toBeGreaterThan(0);
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* **Counter Types:**
|
|
246
|
+
* - `mediaPlayCalls`: Tracks calls to `.play()` on any `HTMLMediaElement` instance
|
|
247
|
+
* (including `<audio>`, `<video>` elements, or any element that inherits from `HTMLMediaElement`).
|
|
248
|
+
* This counter increments whenever `HTMLMediaElement.prototype.play()` is invoked.
|
|
249
|
+
* - `audioPlayCalls`: Tracks calls to `.play()` specifically on instances created via the `Audio` constructor
|
|
250
|
+
* (e.g., `new Audio(url).play()`). This is a subset of `mediaPlayCalls` but provides more specific
|
|
251
|
+
* tracking for programmatically created audio elements.
|
|
252
|
+
*
|
|
253
|
+
* **Note**: Since `Audio` instances are also `HTMLMediaElement` instances, calling `.play()` on an
|
|
254
|
+
* `Audio` object will increment **both** counters. For most use cases, checking `mediaPlayCalls`
|
|
255
|
+
* is sufficient as it captures all audio playback attempts.
|
|
256
|
+
*/
|
|
257
|
+
readonly audio: {
|
|
258
|
+
/**
|
|
259
|
+
* Injects tracking hooks for HTMLMediaElement.play and the Audio constructor.
|
|
260
|
+
* Must be called before the plugin code runs (ideally before env.setup()).
|
|
261
|
+
*/
|
|
262
|
+
enableTracking: () => Promise<void>;
|
|
263
|
+
/**
|
|
264
|
+
* Returns current audio play counters from the browser context.
|
|
265
|
+
*
|
|
266
|
+
* @returns An object with two counters:
|
|
267
|
+
* - `mediaPlayCalls`: Total number of `.play()` calls on any `HTMLMediaElement` (includes all audio/video elements)
|
|
268
|
+
* - `audioPlayCalls`: Number of `.play()` calls on instances created via `new Audio()` (subset of `mediaPlayCalls`)
|
|
269
|
+
*
|
|
270
|
+
* **Note**: Since `Audio` extends `HTMLMediaElement`, calling `.play()` on an `Audio` instance increments both counters.
|
|
271
|
+
* For general audio playback tracking, use `mediaPlayCalls` as it captures all playback attempts.
|
|
272
|
+
*/
|
|
273
|
+
getPlayCounts: () => Promise<{
|
|
274
|
+
mediaPlayCalls: number;
|
|
275
|
+
audioPlayCalls: number;
|
|
276
|
+
}>;
|
|
277
|
+
/**
|
|
278
|
+
* Resets the audio play counters to zero.
|
|
279
|
+
*/
|
|
280
|
+
resetPlayCounts: () => Promise<void>;
|
|
281
|
+
};
|
|
144
282
|
readonly runtime: {
|
|
145
283
|
mockFetchBackend: () => void;
|
|
146
284
|
};
|