@rpgjs/testing 4.3.0 → 5.0.0-alpha.25

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.
@@ -0,0 +1,239 @@
1
+ import { Provider } from '@signe/di';
2
+ import { RpgClientEngine, RpgClient } from '@rpgjs/client';
3
+ import { RpgServer, RpgPlayer } from '@rpgjs/server';
4
+ /**
5
+ * Provides a default map loader for testing environments
6
+ *
7
+ * This function returns a `provideLoadMap` provider that creates mock maps
8
+ * with default dimensions (1024x768) and a minimal component. It's automatically
9
+ * used by `testing()` if no custom `provideLoadMap` is provided in `clientConfig.providers`.
10
+ *
11
+ * @returns A provider function that can be used with `provideLoadMap`
12
+ * @example
13
+ * ```ts
14
+ * // Used automatically by testing()
15
+ * const fixture = await testing([myModule])
16
+ *
17
+ * // Or use directly in clientConfig
18
+ * const fixture = await testing([myModule], {
19
+ * providers: [provideTestingLoadMap()]
20
+ * })
21
+ * ```
22
+ */
23
+ export declare function provideTestingLoadMap(): {
24
+ provide: string;
25
+ useFactory: (context: import('@signe/di').Context) => void;
26
+ }[];
27
+ /**
28
+ * Testing utility function to set up server and client instances for unit testing
29
+ *
30
+ * This function creates a test environment with both server and client instances,
31
+ * allowing you to test player interactions, server hooks, and game mechanics.
32
+ *
33
+ * @param modules - Array of modules that can be either:
34
+ * - Direct module objects: { server: RpgServer, client: RpgClient }
35
+ * - Providers returned by createModule(): Provider[] with meta.server/client and useValue
36
+ * @param clientConfig - Optional client configuration
37
+ * @param serverConfig - Optional server configuration
38
+ * @returns Testing fixture with createClient method
39
+ * @example
40
+ * ```ts
41
+ * // Using direct modules
42
+ * const fixture = await testing([{
43
+ * server: serverModule,
44
+ * client: clientModule
45
+ * }])
46
+ *
47
+ * // Using createModule
48
+ * const myModule = createModule('MyModule', [{
49
+ * server: serverModule,
50
+ * client: clientModule
51
+ * }])
52
+ * const fixture = await testing(myModule)
53
+ * ```
54
+ */
55
+ export declare function testing(modules?: ({
56
+ server?: RpgServer;
57
+ client?: RpgClient;
58
+ } | Provider)[], clientConfig?: any, serverConfig?: any): Promise<{
59
+ createClient(): Promise<{
60
+ readonly server: any;
61
+ socket: any;
62
+ client: RpgClientEngine<any>;
63
+ playerId: string;
64
+ readonly player: RpgPlayer;
65
+ /**
66
+ * Wait for player to be on a specific map
67
+ *
68
+ * This utility function polls the player's current map until it matches
69
+ * the expected map ID, or throws an error if the timeout is exceeded.
70
+ *
71
+ * @param expectedMapId - The expected map ID (without 'map-' prefix, e.g. 'map1')
72
+ * @param timeout - Maximum time to wait in milliseconds (default: 5000)
73
+ * @returns Promise that resolves when player is on the expected map
74
+ * @throws Error if timeout is exceeded
75
+ * @example
76
+ * ```ts
77
+ * const client = await fixture.createClient()
78
+ * await client.waitForMapChange('map1')
79
+ * ```
80
+ */
81
+ waitForMapChange(expectedMapId: string, timeout?: number): Promise<RpgPlayer>;
82
+ /**
83
+ * Manually trigger a game tick for processing inputs and physics
84
+ *
85
+ * This method is a convenience wrapper around the exported nextTick() function.
86
+ *
87
+ * @param timestamp - Optional timestamp to use for the tick (default: Date.now())
88
+ * @returns Promise that resolves when the tick is complete
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const client = await fixture.createClient()
93
+ *
94
+ * // Manually advance the game by one tick
95
+ * await client.nextTick()
96
+ * ```
97
+ */
98
+ nextTick(timestamp?: number): Promise<void>;
99
+ }>;
100
+ /**
101
+ * Clear all server, client instances and reset the DOM
102
+ *
103
+ * This method should be called in afterEach to clean up test state.
104
+ * It destroys all created client instances, clears the server, and resets the DOM.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const fixture = await testing([myModule])
109
+ *
110
+ * afterEach(() => {
111
+ * fixture.clear()
112
+ * })
113
+ * ```
114
+ */
115
+ clear(): void;
116
+ }>;
117
+ /**
118
+ * Clear all caches and reset test state
119
+ *
120
+ * This function should be called after the end of each test to clean up
121
+ * all server and client instances, clear caches, and reset the DOM.
122
+ *
123
+ * ## Design
124
+ *
125
+ * Cleans up all created fixtures, client engines, server instances, and resets
126
+ * the DOM to a clean state. This ensures no state leaks between tests.
127
+ *
128
+ * @returns void
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * import { clear } from '@rpgjs/testing'
133
+ *
134
+ * afterEach(() => {
135
+ * clear()
136
+ * })
137
+ * ```
138
+ */
139
+ export declare function clear(): void;
140
+ /**
141
+ * Manually trigger a game tick for processing inputs and physics
142
+ *
143
+ * This function allows you to manually advance the game by one tick.
144
+ * It performs the following operations:
145
+ * 1. On server: processes pending inputs and advances physics
146
+ * 2. Server sends data to client
147
+ * 3. Client retrieves data and performs inputs (move, etc.) and server reconciliation
148
+ * 4. A tick is performed on the client
149
+ * 5. A tick is performed on VueJS (if Vue is used)
150
+ *
151
+ * @param client - The RpgClientEngine instance
152
+ * @param timestamp - Optional timestamp to use for the tick (default: Date.now())
153
+ * @returns Promise that resolves when the tick is complete
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * import { nextTick } from '@rpgjs/testing'
158
+ *
159
+ * const client = await fixture.createClient()
160
+ *
161
+ * // Manually advance the game by one tick
162
+ * await nextTick(client.client, Date.now())
163
+ * ```
164
+ */
165
+ export declare function nextTick(client: RpgClientEngine, timestamp?: number): Promise<void>;
166
+ /**
167
+ * Wait for synchronization to complete on the client
168
+ *
169
+ * This function waits for the client to receive and process synchronization data
170
+ * from the server. It monitors the `playersReceived$` and `eventsReceived$` observables
171
+ * in the RpgClientEngine to determine when synchronization is complete.
172
+ *
173
+ * ## Design
174
+ *
175
+ * - Uses `combineLatest` to wait for both `playersReceived$` and `eventsReceived$` to be `true`
176
+ * - Filters to only proceed when both are `true`
177
+ * - Includes a timeout to prevent waiting indefinitely
178
+ * - Resets the observables to `false` before waiting to ensure we catch the next sync
179
+ *
180
+ * @param client - The RpgClientEngine instance
181
+ * @param timeout - Maximum time to wait in milliseconds (default: 1000ms)
182
+ * @returns Promise that resolves when synchronization is complete
183
+ * @throws Error if timeout is exceeded
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * import { waitForSync } from '@rpgjs/testing'
188
+ *
189
+ * const client = await fixture.createClient()
190
+ *
191
+ * // Wait for sync to complete
192
+ * await waitForSync(client.client)
193
+ *
194
+ * // Now you can safely test client-side state
195
+ * expect(client.client.sceneMap.players()).toBeDefined()
196
+ * ```
197
+ */
198
+ export declare function waitForSync(client: RpgClientEngine, timeout?: number): Promise<void>;
199
+ /**
200
+ * Wait for complete synchronization cycle (server sync + client receive)
201
+ *
202
+ * This function performs a complete synchronization cycle:
203
+ * 1. Triggers a game tick using `nextTick()` which calls `syncChanges()` on all players
204
+ * 2. Waits for the client to receive and process the synchronization data
205
+ *
206
+ * This is useful when you need to ensure that server-side changes are fully
207
+ * synchronized to the client before testing client-side state.
208
+ *
209
+ * ## Design
210
+ *
211
+ * - Calls `nextTick()` to trigger server-side sync
212
+ * - Waits for client to receive sync data using `waitForSync()`
213
+ * - Ensures complete synchronization cycle is finished
214
+ *
215
+ * @param player - The RpgPlayer instance (optional, will sync all players if not provided)
216
+ * @param client - The RpgClientEngine instance
217
+ * @param timeout - Maximum time to wait in milliseconds (default: 1000ms)
218
+ * @returns Promise that resolves when synchronization is complete
219
+ * @throws Error if timeout is exceeded
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * import { waitForSyncComplete } from '@rpgjs/testing'
224
+ *
225
+ * const client = await fixture.createClient()
226
+ * const player = client.player
227
+ *
228
+ * // Make a server-side change
229
+ * player.addItem('potion', 5)
230
+ *
231
+ * // Wait for sync to complete
232
+ * await waitForSyncComplete(player, client.client)
233
+ *
234
+ * // Now you can safely test client-side state
235
+ * const clientPlayer = client.client.sceneMap.players()[player.id]
236
+ * expect(clientPlayer.items()).toBeDefined()
237
+ * ```
238
+ */
239
+ export declare function waitForSyncComplete(player: RpgPlayer | null, client: RpgClientEngine, timeout?: number): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,315 @@
1
+ import { mergeConfig } from './node_modules/.pnpm/@signe_di@2.6.0/node_modules/@signe/di/dist/index.js';
2
+ import { provideLoadMap, LoadMapToken, startGame, provideRpg, provideClientGlobalConfig, provideClientModules, inject, WebSocketToken, RpgClientEngine, clearInject } from '@rpgjs/client';
3
+ import { createServer, provideServerModules, clearInject as clearInject$1 } from '@rpgjs/server';
4
+ import { h, Container } from 'canvasengine';
5
+ import { combineLatest, filter, take, firstValueFrom } from 'rxjs';
6
+
7
+ function provideTestingLoadMap() {
8
+ return provideLoadMap((id) => {
9
+ return {
10
+ id,
11
+ data: {
12
+ width: 1024,
13
+ height: 768,
14
+ hitboxes: [],
15
+ params: {}
16
+ },
17
+ component: h(Container),
18
+ width: 1024,
19
+ height: 768
20
+ };
21
+ });
22
+ }
23
+ function normalizeModules(modules) {
24
+ if (!modules || modules.length === 0) {
25
+ return { serverModules: [], clientModules: [] };
26
+ }
27
+ const isProviderArray = modules.some(
28
+ (item) => item && typeof item === "object" && "meta" in item && "useValue" in item
29
+ );
30
+ const serverModules = [];
31
+ const clientModules = [];
32
+ if (!isProviderArray) {
33
+ modules.forEach((module) => {
34
+ if (module && typeof module === "object") {
35
+ if (module.server) {
36
+ serverModules.push(module.server);
37
+ }
38
+ if (module.client) {
39
+ clientModules.push(module.client);
40
+ }
41
+ }
42
+ });
43
+ return { serverModules, clientModules };
44
+ }
45
+ const seenUseValues = /* @__PURE__ */ new Set();
46
+ modules.forEach((provider) => {
47
+ if (!provider || typeof provider !== "object" || !("meta" in provider) || !("useValue" in provider)) {
48
+ return;
49
+ }
50
+ const { useValue } = provider;
51
+ if (seenUseValues.has(useValue)) {
52
+ return;
53
+ }
54
+ if (useValue && typeof useValue === "object" && ("server" in useValue || "client" in useValue)) {
55
+ seenUseValues.add(useValue);
56
+ if (useValue.server) {
57
+ serverModules.push(useValue.server);
58
+ }
59
+ if (useValue.client) {
60
+ clientModules.push(useValue.client);
61
+ }
62
+ }
63
+ });
64
+ return { serverModules, clientModules };
65
+ }
66
+ const globalFixtures = [];
67
+ async function testing(modules = [], clientConfig = {}, serverConfig = {}) {
68
+ const { serverModules, clientModules } = normalizeModules(modules);
69
+ const serverClass = createServer({
70
+ ...serverConfig,
71
+ providers: [
72
+ provideServerModules(serverModules),
73
+ ...serverConfig.providers || []
74
+ ]
75
+ });
76
+ return {
77
+ async createClient() {
78
+ const hasLoadMap = clientConfig.providers?.some((provider) => {
79
+ if (Array.isArray(provider)) {
80
+ return provider.some((p) => p?.provide === LoadMapToken);
81
+ }
82
+ return provider?.provide === LoadMapToken;
83
+ }) || false;
84
+ const context = await startGame(
85
+ mergeConfig({
86
+ ...clientConfig,
87
+ providers: [
88
+ provideClientGlobalConfig({}),
89
+ ...hasLoadMap ? [] : [provideTestingLoadMap()],
90
+ // Add only if not already provided
91
+ provideClientModules(clientModules),
92
+ ...clientConfig.providers || []
93
+ ]
94
+ }, {
95
+ providers: [provideRpg(serverClass)]
96
+ })
97
+ );
98
+ const websocket = inject(WebSocketToken);
99
+ const clientEngine = inject(RpgClientEngine);
100
+ const playerId = clientEngine.playerId;
101
+ if (!playerId) {
102
+ throw new Error("Player ID is not available");
103
+ }
104
+ const playerIdString = playerId;
105
+ const server = websocket.getServer();
106
+ setTimeout(() => {
107
+ try {
108
+ const serverMap = server?.subRoom;
109
+ if (serverMap && typeof serverMap.setAutoTick === "function") {
110
+ serverMap.setAutoTick(false);
111
+ }
112
+ } catch (error) {
113
+ }
114
+ }, 0);
115
+ const clientInstance = {
116
+ context,
117
+ clientEngine,
118
+ websocket,
119
+ server
120
+ };
121
+ globalFixtures.push(clientInstance);
122
+ return {
123
+ get server() {
124
+ return websocket.getServer();
125
+ },
126
+ socket: websocket.getSocket(),
127
+ client: clientEngine,
128
+ playerId: playerIdString,
129
+ get player() {
130
+ return this.server.subRoom.players()[playerIdString];
131
+ },
132
+ /**
133
+ * Wait for player to be on a specific map
134
+ *
135
+ * This utility function polls the player's current map until it matches
136
+ * the expected map ID, or throws an error if the timeout is exceeded.
137
+ *
138
+ * @param expectedMapId - The expected map ID (without 'map-' prefix, e.g. 'map1')
139
+ * @param timeout - Maximum time to wait in milliseconds (default: 5000)
140
+ * @returns Promise that resolves when player is on the expected map
141
+ * @throws Error if timeout is exceeded
142
+ * @example
143
+ * ```ts
144
+ * const client = await fixture.createClient()
145
+ * await client.waitForMapChange('map1')
146
+ * ```
147
+ */
148
+ async waitForMapChange(expectedMapId, timeout = 5e3) {
149
+ const startTime = Date.now();
150
+ while (Date.now() - startTime < timeout) {
151
+ const currentMap2 = this.player.getCurrentMap();
152
+ if (currentMap2?.id === expectedMapId) {
153
+ return this.player;
154
+ }
155
+ await new Promise((resolve) => setTimeout(resolve, 50));
156
+ }
157
+ const currentMap = this.player.getCurrentMap();
158
+ throw new Error(
159
+ `Timeout: Player did not reach map ${expectedMapId} within ${timeout}ms. Current map: ${currentMap?.id || "null"}`
160
+ );
161
+ },
162
+ /**
163
+ * Manually trigger a game tick for processing inputs and physics
164
+ *
165
+ * This method is a convenience wrapper around the exported nextTick() function.
166
+ *
167
+ * @param timestamp - Optional timestamp to use for the tick (default: Date.now())
168
+ * @returns Promise that resolves when the tick is complete
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * const client = await fixture.createClient()
173
+ *
174
+ * // Manually advance the game by one tick
175
+ * await client.nextTick()
176
+ * ```
177
+ */
178
+ async nextTick(timestamp) {
179
+ return nextTick(this.client);
180
+ }
181
+ };
182
+ },
183
+ /**
184
+ * Clear all server, client instances and reset the DOM
185
+ *
186
+ * This method should be called in afterEach to clean up test state.
187
+ * It destroys all created client instances, clears the server, and resets the DOM.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const fixture = await testing([myModule])
192
+ *
193
+ * afterEach(() => {
194
+ * fixture.clear()
195
+ * })
196
+ * ```
197
+ */
198
+ clear() {
199
+ clear();
200
+ }
201
+ };
202
+ }
203
+ function clear() {
204
+ for (const client of globalFixtures) {
205
+ try {
206
+ if (client.clientEngine && typeof client.clientEngine.clear === "function") {
207
+ client.clientEngine.clear();
208
+ }
209
+ const serverMap = client.server?.subRoom;
210
+ if (serverMap && typeof serverMap.clear === "function") {
211
+ serverMap.clear();
212
+ }
213
+ } catch (error) {
214
+ console.warn("Error during cleanup:", error);
215
+ }
216
+ }
217
+ globalFixtures.length = 0;
218
+ try {
219
+ clearInject();
220
+ } catch (error) {
221
+ console.warn("Error clearing client inject:", error);
222
+ }
223
+ try {
224
+ clearInject$1();
225
+ } catch (error) {
226
+ console.warn("Error clearing server inject:", error);
227
+ }
228
+ if (typeof window !== "undefined" && window.document) {
229
+ window.document.body.innerHTML = `<div id="rpg"></div>`;
230
+ }
231
+ }
232
+ async function nextTick(client, timestamp) {
233
+ if (!client) {
234
+ throw new Error("nextTick: client parameter is required");
235
+ }
236
+ const delta = 16;
237
+ const websocket = client.webSocket;
238
+ if (!websocket) {
239
+ throw new Error("nextTick: websocket not found in client");
240
+ }
241
+ const server = websocket.getServer();
242
+ if (!server) {
243
+ throw new Error("nextTick: server not found");
244
+ }
245
+ const serverMap = server.subRoom;
246
+ if (!serverMap) {
247
+ return;
248
+ }
249
+ for (const player of serverMap.getPlayers()) {
250
+ if (player.pendingInputs && player.pendingInputs.length > 0) {
251
+ await serverMap.processInput(player.id);
252
+ }
253
+ }
254
+ if (typeof serverMap.runFixedTicks === "function") {
255
+ serverMap.runFixedTicks(delta);
256
+ }
257
+ for (const player of serverMap.getPlayers()) {
258
+ if (player && typeof player.syncChanges === "function") {
259
+ player.syncChanges();
260
+ }
261
+ }
262
+ await new Promise((resolve) => setTimeout(resolve, 0));
263
+ const sceneMap = client.sceneMap;
264
+ if (sceneMap && typeof sceneMap.stepPredictionTick === "function") {
265
+ sceneMap.stepPredictionTick();
266
+ }
267
+ }
268
+ async function waitForSync(client, timeout = 1e3) {
269
+ if (!client) {
270
+ throw new Error("waitForSync: client parameter is required");
271
+ }
272
+ const playersReceived$ = client.playersReceived$;
273
+ const eventsReceived$ = client.eventsReceived$;
274
+ if (!playersReceived$ || !eventsReceived$) {
275
+ throw new Error("waitForSync: playersReceived$ or eventsReceived$ not found in client");
276
+ }
277
+ const playersAlreadyTrue = playersReceived$.getValue ? playersReceived$.getValue() === true : false;
278
+ const eventsAlreadyTrue = eventsReceived$.getValue ? eventsReceived$.getValue() === true : false;
279
+ if (playersAlreadyTrue && eventsAlreadyTrue) {
280
+ return;
281
+ }
282
+ playersReceived$.next(false);
283
+ eventsReceived$.next(false);
284
+ const syncComplete$ = combineLatest([
285
+ playersReceived$.pipe(filter((received) => received === true)),
286
+ eventsReceived$.pipe(filter((received) => received === true))
287
+ ]).pipe(
288
+ take(1)
289
+ );
290
+ const timeoutPromise = new Promise((_, reject) => {
291
+ setTimeout(() => {
292
+ reject(new Error(`waitForSync: Timeout after ${timeout}ms. Synchronization did not complete.`));
293
+ }, timeout);
294
+ });
295
+ await Promise.race([
296
+ firstValueFrom(syncComplete$),
297
+ timeoutPromise
298
+ ]);
299
+ }
300
+ async function waitForSyncComplete(player, client, timeout = 1e3) {
301
+ if (!client) {
302
+ throw new Error("waitForSyncComplete: client parameter is required");
303
+ }
304
+ const playersReceived$ = client.playersReceived$;
305
+ const eventsReceived$ = client.eventsReceived$;
306
+ if (playersReceived$ && eventsReceived$) {
307
+ playersReceived$.next(false);
308
+ eventsReceived$.next(false);
309
+ }
310
+ await nextTick(client);
311
+ await waitForSync(client, timeout);
312
+ }
313
+
314
+ export { clear, nextTick, provideTestingLoadMap, testing, waitForSync, waitForSyncComplete };
315
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { mergeConfig, Provider } from \"@signe/di\";\nimport { provideRpg, startGame, provideClientModules, provideLoadMap, provideClientGlobalConfig, inject, WebSocketToken, AbstractWebsocket, RpgClientEngine, RpgClient, LoadMapToken } from \"@rpgjs/client\";\nimport { createServer, provideServerModules, RpgServer, RpgPlayer } from \"@rpgjs/server\";\nimport { h, Container } from \"canvasengine\";\nimport { clearInject as clearClientInject } from \"@rpgjs/client\";\nimport { clearInject as clearServerInject } from \"@rpgjs/server\";\nimport { combineLatest, filter, take, race, timer, firstValueFrom } from \"rxjs\";\n\n/**\n * Provides a default map loader for testing environments\n * \n * This function returns a `provideLoadMap` provider that creates mock maps\n * with default dimensions (1024x768) and a minimal component. It's automatically\n * used by `testing()` if no custom `provideLoadMap` is provided in `clientConfig.providers`.\n * \n * @returns A provider function that can be used with `provideLoadMap`\n * @example\n * ```ts\n * // Used automatically by testing()\n * const fixture = await testing([myModule])\n * \n * // Or use directly in clientConfig\n * const fixture = await testing([myModule], {\n * providers: [provideTestingLoadMap()]\n * })\n * ```\n */\nexport function provideTestingLoadMap() {\n return provideLoadMap((id: string) => {\n return {\n id,\n data: {\n width: 1024,\n height: 768,\n hitboxes: [],\n params: {}\n },\n component: h(Container),\n width: 1024,\n height: 768,\n }\n })\n}\n\n/**\n * Normalizes modules input to extract server/client modules from createModule providers or direct module objects\n * \n * @param modules - Array of modules that can be either:\n * - Direct module objects: { server: RpgServer, client: RpgClient }\n * - Providers returned by createModule(): Provider[] with meta.server/client and useValue\n * @returns Object with separate arrays for server and client modules\n * @example\n * ```ts\n * // Direct modules\n * normalizeModules([{ server: serverModule, client: clientModule }])\n * \n * // createModule providers\n * const providers = createModule('MyModule', [{ server: serverModule, client: clientModule }])\n * normalizeModules(providers)\n * ```\n */\nfunction normalizeModules(modules: any[]): { serverModules: RpgServer[], clientModules: RpgClient[] } {\n if (!modules || modules.length === 0) {\n return { serverModules: [], clientModules: [] }\n }\n\n // Check if first item is a provider (has meta and useValue properties)\n const isProviderArray = modules.some((item: any) => \n item && typeof item === 'object' && 'meta' in item && 'useValue' in item\n )\n\n const serverModules: RpgServer[] = []\n const clientModules: RpgClient[] = []\n\n if (!isProviderArray) {\n // Direct module objects, extract server and client separately\n modules.forEach((module: any) => {\n if (module && typeof module === 'object') {\n if (module.server) {\n serverModules.push(module.server)\n }\n if (module.client) {\n clientModules.push(module.client)\n }\n }\n })\n return { serverModules, clientModules }\n }\n\n // Extract modules from createModule providers\n // createModule returns providers where useValue contains the original { server, client } object\n // We need to group providers by their useValue to reconstruct the original modules\n const seenUseValues = new Set<any>()\n\n modules.forEach((provider: any) => {\n if (!provider || typeof provider !== 'object' || !('meta' in provider) || !('useValue' in provider)) {\n return\n }\n\n const { useValue } = provider\n \n // Skip if we've already processed this useValue (same module, different provider for server/client)\n if (seenUseValues.has(useValue)) {\n return\n }\n\n // Check if useValue has server or client properties (it's a module object)\n if (useValue && typeof useValue === 'object' && ('server' in useValue || 'client' in useValue)) {\n seenUseValues.add(useValue)\n if (useValue.server) {\n serverModules.push(useValue.server)\n }\n if (useValue.client) {\n clientModules.push(useValue.client)\n }\n }\n })\n\n return { serverModules, clientModules }\n}\n\n// Global storage for all created fixtures and clients (for clear() function)\nconst globalFixtures: Array<{\n context: any;\n clientEngine: RpgClientEngine;\n websocket: AbstractWebsocket;\n server?: any;\n}> = []\n\n/**\n * Testing utility function to set up server and client instances for unit testing\n * \n * This function creates a test environment with both server and client instances,\n * allowing you to test player interactions, server hooks, and game mechanics.\n * \n * @param modules - Array of modules that can be either:\n * - Direct module objects: { server: RpgServer, client: RpgClient }\n * - Providers returned by createModule(): Provider[] with meta.server/client and useValue\n * @param clientConfig - Optional client configuration\n * @param serverConfig - Optional server configuration\n * @returns Testing fixture with createClient method\n * @example\n * ```ts\n * // Using direct modules\n * const fixture = await testing([{\n * server: serverModule,\n * client: clientModule\n * }])\n * \n * // Using createModule\n * const myModule = createModule('MyModule', [{\n * server: serverModule,\n * client: clientModule\n * }])\n * const fixture = await testing(myModule)\n * ```\n */\nexport async function testing(\n modules: ({ server?: RpgServer, client?: RpgClient } | Provider)[] = [], \n clientConfig: any = {}, \n serverConfig: any = {}\n) {\n // Normalize modules to extract server/client from providers if needed\n const { serverModules, clientModules } = normalizeModules(modules as any[])\n \n const serverClass = createServer({\n ...serverConfig,\n providers: [\n provideServerModules(serverModules),\n ...(serverConfig.providers || [])\n ]\n })\n \n // Store created instances for cleanup\n const createdClients: Array<{\n context: any;\n clientEngine: RpgClientEngine;\n websocket: AbstractWebsocket;\n server?: any;\n }> = []\n \n return {\n async createClient() {\n // Check if LoadMapToken is already provided in clientConfig.providers\n // (provideLoadMap returns an array with LoadMapToken)\n const hasLoadMap = clientConfig.providers?.some((provider: any) => {\n if (Array.isArray(provider)) {\n return provider.some((p: any) => p?.provide === LoadMapToken)\n }\n return provider?.provide === LoadMapToken\n }) || false\n \n const context = await startGame(\n mergeConfig({\n ...clientConfig,\n providers: [\n provideClientGlobalConfig({}),\n ...(hasLoadMap ? [] : [provideTestingLoadMap()]), // Add only if not already provided\n provideClientModules(clientModules),\n ...(clientConfig.providers || [])\n ]\n }, {\n providers: [provideRpg(serverClass)],\n })\n )\n const websocket = inject<AbstractWebsocket>(WebSocketToken) as any\n const clientEngine = inject<RpgClientEngine>(RpgClientEngine)\n const playerId = clientEngine.playerId\n if (!playerId) {\n throw new Error('Player ID is not available')\n }\n const playerIdString: string = playerId\n \n // Get server instance\n const server = websocket.getServer()\n \n // Disable auto tick for unit tests (manual control via nextTick)\n // The map will be available after the player connects, so we disable it asynchronously\n setTimeout(() => {\n try {\n const serverMap = server?.subRoom as any\n if (serverMap && typeof serverMap.setAutoTick === 'function') {\n serverMap.setAutoTick(false)\n }\n } catch (error) {\n // Ignore errors if map is not ready yet\n }\n }, 0)\n \n // Store instance for cleanup (both locally and globally)\n const clientInstance = {\n context,\n clientEngine,\n websocket,\n server\n }\n createdClients.push(clientInstance)\n globalFixtures.push(clientInstance)\n \n return {\n get server() {\n return websocket.getServer()\n },\n socket: websocket.getSocket(),\n client: clientEngine,\n playerId: playerIdString,\n get player(): RpgPlayer {\n return this.server.subRoom.players()[playerIdString] as RpgPlayer\n },\n /**\n * Wait for player to be on a specific map\n * \n * This utility function polls the player's current map until it matches\n * the expected map ID, or throws an error if the timeout is exceeded.\n * \n * @param expectedMapId - The expected map ID (without 'map-' prefix, e.g. 'map1')\n * @param timeout - Maximum time to wait in milliseconds (default: 5000)\n * @returns Promise that resolves when player is on the expected map\n * @throws Error if timeout is exceeded\n * @example\n * ```ts\n * const client = await fixture.createClient()\n * await client.waitForMapChange('map1')\n * ```\n */\n async waitForMapChange(expectedMapId: string, timeout = 5000): Promise<RpgPlayer> {\n const startTime = Date.now()\n\n while (Date.now() - startTime < timeout) {\n const currentMap = this.player.getCurrentMap()\n if (currentMap?.id === expectedMapId) {\n return this.player\n }\n await new Promise(resolve => setTimeout(resolve, 50)) // Wait 50ms before next check\n }\n \n const currentMap = this.player.getCurrentMap()\n throw new Error(\n `Timeout: Player did not reach map ${expectedMapId} within ${timeout}ms. ` +\n `Current map: ${currentMap?.id || 'null'}`\n )\n },\n /**\n * Manually trigger a game tick for processing inputs and physics\n * \n * This method is a convenience wrapper around the exported nextTick() function.\n * \n * @param timestamp - Optional timestamp to use for the tick (default: Date.now())\n * @returns Promise that resolves when the tick is complete\n * \n * @example\n * ```ts\n * const client = await fixture.createClient()\n * \n * // Manually advance the game by one tick\n * await client.nextTick()\n * ```\n */\n async nextTick(timestamp?: number): Promise<void> {\n return nextTick(this.client, timestamp)\n }\n }\n },\n /**\n * Clear all server, client instances and reset the DOM\n * \n * This method should be called in afterEach to clean up test state.\n * It destroys all created client instances, clears the server, and resets the DOM.\n * \n * @example\n * ```ts\n * const fixture = await testing([myModule])\n * \n * afterEach(() => {\n * fixture.clear()\n * })\n * ```\n */\n clear() {\n // Use the global clear function\n clear()\n }\n }\n}\n\n/**\n * Clear all caches and reset test state\n * \n * This function should be called after the end of each test to clean up\n * all server and client instances, clear caches, and reset the DOM.\n * \n * ## Design\n * \n * Cleans up all created fixtures, client engines, server instances, and resets\n * the DOM to a clean state. This ensures no state leaks between tests.\n * \n * @returns void\n * \n * @example\n * ```ts\n * import { clear } from '@rpgjs/testing'\n * \n * afterEach(() => {\n * clear()\n * })\n * ```\n */\nexport function clear(): void {\n // Clean up all created client and server instances from all fixtures\n for (const client of globalFixtures) {\n try {\n // Clear client engine\n if (client.clientEngine && typeof (client.clientEngine as any).clear === 'function') {\n (client.clientEngine as any).clear()\n }\n \n // Clear server map (subRoom)\n const serverMap = client.server?.subRoom as any\n if (serverMap && typeof serverMap.clear === 'function') {\n serverMap.clear()\n }\n } catch (error) {\n // Silently ignore cleanup errors\n console.warn('Error during cleanup:', error)\n }\n }\n \n // Clear the global fixtures array\n globalFixtures.length = 0\n \n // Clear client context injection\n try {\n clearClientInject()\n } catch (error) {\n console.warn('Error clearing client inject:', error)\n }\n \n // Clear server context injection\n try {\n clearServerInject()\n } catch (error) {\n console.warn('Error clearing server inject:', error)\n }\n \n // Reset DOM\n if (typeof window !== 'undefined' && window.document) {\n window.document.body.innerHTML = `<div id=\"rpg\"></div>`\n }\n}\n\n/**\n * Manually trigger a game tick for processing inputs and physics\n * \n * This function allows you to manually advance the game by one tick.\n * It performs the following operations:\n * 1. On server: processes pending inputs and advances physics\n * 2. Server sends data to client\n * 3. Client retrieves data and performs inputs (move, etc.) and server reconciliation\n * 4. A tick is performed on the client\n * 5. A tick is performed on VueJS (if Vue is used)\n * \n * @param client - The RpgClientEngine instance\n * @param timestamp - Optional timestamp to use for the tick (default: Date.now())\n * @returns Promise that resolves when the tick is complete\n * \n * @example\n * ```ts\n * import { nextTick } from '@rpgjs/testing'\n * \n * const client = await fixture.createClient()\n * \n * // Manually advance the game by one tick\n * await nextTick(client.client, Date.now())\n * ```\n */\nexport async function nextTick(client: RpgClientEngine, timestamp?: number): Promise<void> {\n if (!client) {\n throw new Error('nextTick: client parameter is required')\n }\n\n const tickTimestamp = timestamp ?? Date.now()\n const delta = 16 // 16ms for 60fps\n\n // Get server instance from client context\n const websocket = (client as any).webSocket\n if (!websocket) {\n throw new Error('nextTick: websocket not found in client')\n }\n\n const server = websocket.getServer()\n if (!server) {\n throw new Error('nextTick: server not found')\n }\n\n // Get server map (subRoom)\n const serverMap = server.subRoom as any\n if (!serverMap) {\n return\n }\n\n // 1. On server: Process inputs for all players\n for (const player of serverMap.getPlayers()) {\n if (player.pendingInputs && player.pendingInputs.length > 0) {\n await serverMap.processInput(player.id)\n }\n }\n\n // 2. Run physics tick on server map\n if (typeof serverMap.runFixedTicks === 'function') {\n serverMap.runFixedTicks(delta)\n }\n\n // 3. Server sends data to client - trigger sync for all players\n // The sync is triggered by calling syncChanges() on each player\n for (const player of serverMap.getPlayers()) {\n if (player && typeof (player as any).syncChanges === 'function') {\n (player as any).syncChanges()\n }\n }\n\n // 4. Client retrieves data and performs reconciliation\n // The sync data will be received by the client through the websocket\n // We need to wait a bit for the sync data to be processed\n await new Promise(resolve => setTimeout(resolve, 0))\n\n // 5. Run physics tick on client map (performs client-side prediction)\n const sceneMap = (client as any).sceneMap\n if (sceneMap && typeof sceneMap.stepPredictionTick === 'function') {\n sceneMap.stepPredictionTick()\n }\n\n // 6. Trigger VueJS tick if Vue is used (handled by CanvasEngine internally)\n // CanvasEngine handles this automatically through its tick system\n}\n\n/**\n * Wait for synchronization to complete on the client\n * \n * This function waits for the client to receive and process synchronization data\n * from the server. It monitors the `playersReceived$` and `eventsReceived$` observables\n * in the RpgClientEngine to determine when synchronization is complete.\n * \n * ## Design\n * \n * - Uses `combineLatest` to wait for both `playersReceived$` and `eventsReceived$` to be `true`\n * - Filters to only proceed when both are `true`\n * - Includes a timeout to prevent waiting indefinitely\n * - Resets the observables to `false` before waiting to ensure we catch the next sync\n * \n * @param client - The RpgClientEngine instance\n * @param timeout - Maximum time to wait in milliseconds (default: 1000ms)\n * @returns Promise that resolves when synchronization is complete\n * @throws Error if timeout is exceeded\n * \n * @example\n * ```ts\n * import { waitForSync } from '@rpgjs/testing'\n * \n * const client = await fixture.createClient()\n * \n * // Wait for sync to complete\n * await waitForSync(client.client)\n * \n * // Now you can safely test client-side state\n * expect(client.client.sceneMap.players()).toBeDefined()\n * ```\n */\nexport async function waitForSync(client: RpgClientEngine, timeout: number = 1000): Promise<void> {\n if (!client) {\n throw new Error('waitForSync: client parameter is required')\n }\n\n // Access private observables via type assertion\n const playersReceived$ = (client as any).playersReceived$ as any\n const eventsReceived$ = (client as any).eventsReceived$ as any\n\n if (!playersReceived$ || !eventsReceived$) {\n throw new Error('waitForSync: playersReceived$ or eventsReceived$ not found in client')\n }\n\n // Check if observables are already true - if so, sync has already arrived, don't reset\n const playersAlreadyTrue = playersReceived$.getValue ? playersReceived$.getValue() === true : false;\n const eventsAlreadyTrue = eventsReceived$.getValue ? eventsReceived$.getValue() === true : false;\n\n // If both observables are already true, sync has already completed - return immediately\n if (playersAlreadyTrue && eventsAlreadyTrue) {\n return;\n }\n\n // Reset observables to false to ensure we catch the next sync\n // Note: This is only needed when waitForSync is called standalone.\n // When called from waitForSyncComplete, observables are already reset before nextTick\n playersReceived$.next(false)\n eventsReceived$.next(false)\n\n // Wait for both observables to be true\n const syncComplete$ = combineLatest([\n playersReceived$.pipe(filter(received => received === true)),\n eventsReceived$.pipe(filter(received => received === true))\n ]).pipe(\n take(1)\n )\n\n // Create a timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(`waitForSync: Timeout after ${timeout}ms. Synchronization did not complete.`))\n }, timeout)\n })\n\n // Race between sync completion and timeout\n await Promise.race([\n firstValueFrom(syncComplete$),\n timeoutPromise\n ])\n}\n\n/**\n * Wait for complete synchronization cycle (server sync + client receive)\n * \n * This function performs a complete synchronization cycle:\n * 1. Triggers a game tick using `nextTick()` which calls `syncChanges()` on all players\n * 2. Waits for the client to receive and process the synchronization data\n * \n * This is useful when you need to ensure that server-side changes are fully\n * synchronized to the client before testing client-side state.\n * \n * ## Design\n * \n * - Calls `nextTick()` to trigger server-side sync\n * - Waits for client to receive sync data using `waitForSync()`\n * - Ensures complete synchronization cycle is finished\n * \n * @param player - The RpgPlayer instance (optional, will sync all players if not provided)\n * @param client - The RpgClientEngine instance\n * @param timeout - Maximum time to wait in milliseconds (default: 1000ms)\n * @returns Promise that resolves when synchronization is complete\n * @throws Error if timeout is exceeded\n * \n * @example\n * ```ts\n * import { waitForSyncComplete } from '@rpgjs/testing'\n * \n * const client = await fixture.createClient()\n * const player = client.player\n * \n * // Make a server-side change\n * player.addItem('potion', 5)\n * \n * // Wait for sync to complete\n * await waitForSyncComplete(player, client.client)\n * \n * // Now you can safely test client-side state\n * const clientPlayer = client.client.sceneMap.players()[player.id]\n * expect(clientPlayer.items()).toBeDefined()\n * ```\n */\nexport async function waitForSyncComplete(player: RpgPlayer | null, client: RpgClientEngine, timeout: number = 1000): Promise<void> {\n if (!client) {\n throw new Error('waitForSyncComplete: client parameter is required')\n }\n\n // Reset observables BEFORE calling nextTick to ensure we catch the sync that will be sent\n // This prevents race condition where sync arrives before we start waiting\n const playersReceived$ = (client as any).playersReceived$ as any\n const eventsReceived$ = (client as any).eventsReceived$ as any\n if (playersReceived$ && eventsReceived$) {\n playersReceived$.next(false)\n eventsReceived$.next(false)\n }\n\n // Trigger sync by calling nextTick (which calls syncChanges on all players)\n await nextTick(client)\n\n // Wait for client to receive and process the sync\n await waitForSync(client, timeout)\n}"],"names":["currentMap","clearClientInject","clearServerInject"],"mappings":"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"}