request-iframe 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.CN.md +8 -1
  2. package/README.md +8 -1
  3. package/library/constants/index.d.ts +2 -0
  4. package/library/constants/index.d.ts.map +1 -1
  5. package/library/constants/index.js +3 -1
  6. package/library/constants/messages.d.ts +1 -0
  7. package/library/constants/messages.d.ts.map +1 -1
  8. package/library/constants/messages.js +1 -0
  9. package/library/core/client.d.ts +5 -0
  10. package/library/core/client.d.ts.map +1 -1
  11. package/library/core/client.js +54 -18
  12. package/library/core/response.d.ts.map +1 -1
  13. package/library/core/response.js +51 -32
  14. package/library/core/server.d.ts.map +1 -1
  15. package/library/core/server.js +10 -0
  16. package/library/index.d.ts +1 -1
  17. package/library/index.d.ts.map +1 -1
  18. package/library/index.js +7 -0
  19. package/library/message/channel.d.ts +2 -2
  20. package/library/message/channel.d.ts.map +1 -1
  21. package/library/message/channel.js +5 -1
  22. package/library/message/dispatcher.d.ts +2 -2
  23. package/library/message/dispatcher.d.ts.map +1 -1
  24. package/library/message/dispatcher.js +2 -2
  25. package/library/stream/writable-stream.d.ts.map +1 -1
  26. package/library/stream/writable-stream.js +81 -39
  27. package/library/types/index.d.ts +3 -1
  28. package/library/types/index.d.ts.map +1 -1
  29. package/library/utils/debug.d.ts.map +1 -1
  30. package/library/utils/debug.js +6 -2
  31. package/library/utils/error.d.ts +21 -0
  32. package/library/utils/error.d.ts.map +1 -0
  33. package/library/utils/error.js +34 -0
  34. package/library/utils/index.d.ts +7 -0
  35. package/library/utils/index.d.ts.map +1 -1
  36. package/library/utils/index.js +42 -1
  37. package/package.json +5 -4
  38. package/react/library/index.d.ts.map +1 -1
  39. package/react/library/index.js +4 -1
  40. package/library/__tests__/channel.test.ts +0 -420
  41. package/library/__tests__/coverage-branches.test.ts +0 -356
  42. package/library/__tests__/debug.test.ts +0 -588
  43. package/library/__tests__/dispatcher.test.ts +0 -481
  44. package/library/__tests__/interceptors.test.ts +0 -146
  45. package/library/__tests__/requestIframe.test.ts +0 -5384
  46. package/library/__tests__/server.test.ts +0 -738
  47. package/library/__tests__/stream.test.ts +0 -681
  48. package/library/__tests__/utils.test.ts +0 -433
  49. package/library/api/client.d.js +0 -5
  50. package/library/api/server.d.js +0 -5
  51. package/library/constants/index.d.js +0 -36
  52. package/library/constants/messages.d.js +0 -5
  53. package/library/core/client.d.js +0 -5
  54. package/library/core/message-handler.d.ts +0 -110
  55. package/library/core/message-handler.d.ts.map +0 -1
  56. package/library/core/message-handler.js +0 -320
  57. package/library/core/request-response.d.ts +0 -59
  58. package/library/core/request-response.d.ts.map +0 -1
  59. package/library/core/request-response.js +0 -337
  60. package/library/core/request.d.js +0 -5
  61. package/library/core/response.d.js +0 -5
  62. package/library/core/server-base.d.ts +0 -86
  63. package/library/core/server-base.d.ts.map +0 -1
  64. package/library/core/server-base.js +0 -257
  65. package/library/core/server-client.d.js +0 -5
  66. package/library/core/server-client.d.ts +0 -101
  67. package/library/core/server-client.d.ts.map +0 -1
  68. package/library/core/server-client.js +0 -266
  69. package/library/core/server.d.js +0 -5
  70. package/library/interceptors/index.d.js +0 -5
  71. package/library/message/channel.d.js +0 -5
  72. package/library/message/dispatcher.d.js +0 -5
  73. package/library/message/index.d.js +0 -25
  74. package/library/stream/file-stream.d.js +0 -4
  75. package/library/stream/index.d.js +0 -58
  76. package/library/stream/readable-stream.d.js +0 -5
  77. package/library/stream/types.d.js +0 -5
  78. package/library/stream/writable-stream.d.js +0 -5
  79. package/library/types/index.d.js +0 -5
  80. package/library/utils/cache.d.js +0 -5
  81. package/library/utils/cookie.d.js +0 -5
  82. package/library/utils/debug.d.js +0 -5
  83. package/library/utils/index.d.js +0 -94
  84. package/library/utils/path-match.d.js +0 -5
  85. package/library/utils/protocol.d.js +0 -5
  86. package/react/library/__tests__/index.test.d.ts +0 -2
  87. package/react/library/__tests__/index.test.d.ts.map +0 -1
  88. package/react/library/__tests__/index.test.tsx +0 -792
@@ -1,792 +0,0 @@
1
- import { renderHook, waitFor } from '@testing-library/react';
2
- import { useRef } from 'react';
3
- import { useClient, useServer, useServerHandler, useServerHandlerMap } from '../index';
4
- import { requestIframeClient, clearRequestIframeClientCache } from '../../../library/api/client';
5
- import { requestIframeServer, clearRequestIframeServerCache } from '../../../library/api/server';
6
-
7
- /**
8
- * Create test iframe
9
- */
10
- function createTestIframe(origin: string): HTMLIFrameElement {
11
- const iframe = document.createElement('iframe');
12
- iframe.src = `${origin}/test.html`;
13
- document.body.appendChild(iframe);
14
- return iframe;
15
- }
16
-
17
- /**
18
- * Cleanup test iframe
19
- */
20
- function cleanupIframe(iframe: HTMLIFrameElement): void {
21
- if (iframe.parentNode) {
22
- iframe.parentNode.removeChild(iframe);
23
- }
24
- }
25
-
26
- describe('React Hooks', () => {
27
- beforeEach(() => {
28
- clearRequestIframeClientCache();
29
- clearRequestIframeServerCache();
30
- document.querySelectorAll('iframe').forEach((iframe) => {
31
- if (iframe.parentNode) {
32
- iframe.parentNode.removeChild(iframe);
33
- }
34
- });
35
- });
36
-
37
- afterEach(() => {
38
- clearRequestIframeClientCache();
39
- clearRequestIframeServerCache();
40
- document.querySelectorAll('iframe').forEach((iframe) => {
41
- if (iframe.parentNode) {
42
- iframe.parentNode.removeChild(iframe);
43
- }
44
- });
45
- });
46
-
47
- describe('useClient', () => {
48
- it('should return null when getTarget returns null', () => {
49
- const { result } = renderHook(() => useClient(() => null));
50
- expect(result.current).toBeNull();
51
- });
52
-
53
- it('should create client when getTarget returns valid target', async () => {
54
- const iframe = createTestIframe('https://example.com');
55
- const mockContentWindow = {
56
- postMessage: jest.fn()
57
- };
58
- Object.defineProperty(iframe, 'contentWindow', {
59
- value: mockContentWindow,
60
- writable: true,
61
- configurable: true
62
- });
63
-
64
- const { result } = renderHook(() => useClient(() => iframe));
65
-
66
- await waitFor(() => {
67
- expect(result.current).toBeDefined();
68
- expect(result.current).not.toBeNull();
69
- }, { timeout: 2000 });
70
-
71
- cleanupIframe(iframe);
72
- });
73
-
74
- it('should create client with options', async () => {
75
- const iframe = createTestIframe('https://example.com');
76
- const mockContentWindow = {
77
- postMessage: jest.fn()
78
- };
79
- Object.defineProperty(iframe, 'contentWindow', {
80
- value: mockContentWindow,
81
- writable: true,
82
- configurable: true
83
- });
84
-
85
- const options = { secretKey: 'test-key', timeout: 1000 };
86
- const { result } = renderHook(() => useClient(() => iframe, options));
87
-
88
- await waitFor(() => {
89
- expect(result.current).toBeDefined();
90
- if (result.current) {
91
- expect(result.current.isOpen).toBe(true);
92
- }
93
- }, { timeout: 2000 });
94
-
95
- cleanupIframe(iframe);
96
- });
97
-
98
- it('should destroy client on unmount', async () => {
99
- const iframe = createTestIframe('https://example.com');
100
- const mockContentWindow = {
101
- postMessage: jest.fn()
102
- };
103
- Object.defineProperty(iframe, 'contentWindow', {
104
- value: mockContentWindow,
105
- writable: true,
106
- configurable: true
107
- });
108
-
109
- const { result, unmount } = renderHook(() => useClient(() => iframe));
110
-
111
- await waitFor(() => {
112
- expect(result.current).toBeDefined();
113
- }, { timeout: 2000 });
114
-
115
- const client = result.current;
116
- expect(client).toBeDefined();
117
-
118
- unmount();
119
-
120
- // Client should be destroyed
121
- if (client) {
122
- expect(client.isOpen).toBe(false);
123
- }
124
-
125
- cleanupIframe(iframe);
126
- });
127
-
128
- it('should recreate client when getTarget function changes', async () => {
129
- const iframe1 = createTestIframe('https://example.com');
130
- const mockContentWindow1 = {
131
- postMessage: jest.fn()
132
- };
133
- Object.defineProperty(iframe1, 'contentWindow', {
134
- value: mockContentWindow1,
135
- writable: true,
136
- configurable: true
137
- });
138
-
139
- const { result, rerender } = renderHook(
140
- (props: { getTarget: () => HTMLIFrameElement | Window | null; iframe: HTMLIFrameElement }) =>
141
- useClient(props.getTarget, undefined, [props.iframe]),
142
- { initialProps: { getTarget: () => iframe1, iframe: iframe1 } }
143
- );
144
-
145
- await waitFor(() => {
146
- expect(result.current).toBeDefined();
147
- }, { timeout: 2000 });
148
-
149
- const client1 = result.current;
150
- expect(client1).toBeDefined();
151
-
152
- const iframe2 = createTestIframe('https://example2.com');
153
- const mockContentWindow2 = {
154
- postMessage: jest.fn()
155
- };
156
- Object.defineProperty(iframe2, 'contentWindow', {
157
- value: mockContentWindow2,
158
- writable: true,
159
- configurable: true
160
- });
161
-
162
- rerender({ getTarget: () => iframe2, iframe: iframe2 });
163
-
164
- await waitFor(() => {
165
- // Previous client should be destroyed
166
- if (client1) {
167
- expect(client1.isOpen).toBe(false);
168
- }
169
- // New client should be created
170
- expect(result.current).toBeDefined();
171
- expect(result.current).not.toBe(client1);
172
- }, { timeout: 2000 });
173
-
174
- cleanupIframe(iframe1);
175
- cleanupIframe(iframe2);
176
- });
177
-
178
- it('should handle getTarget returning null after initial mount', async () => {
179
- const iframe = createTestIframe('https://example.com');
180
- const mockContentWindow = {
181
- postMessage: jest.fn()
182
- };
183
- Object.defineProperty(iframe, 'contentWindow', {
184
- value: mockContentWindow,
185
- writable: true,
186
- configurable: true
187
- });
188
-
189
- type Props = { getTarget: () => HTMLIFrameElement | Window | null };
190
- const { result, rerender } = renderHook(
191
- (props: Props) => useClient(props.getTarget),
192
- { initialProps: { getTarget: () => iframe } as Props }
193
- );
194
-
195
- await waitFor(() => {
196
- expect(result.current).toBeDefined();
197
- }, { timeout: 2000 });
198
-
199
- // Change getTarget to return null
200
- rerender({ getTarget: () => null } as Props);
201
-
202
- // Wait for cleanup
203
- await new Promise(resolve => setTimeout(resolve, 100));
204
-
205
- // Note: clientRef.current may still hold the old client until next render
206
- // This is expected behavior with useRef - the component needs to re-render
207
- // to reflect the change. In real usage, this would trigger a re-render.
208
-
209
- cleanupIframe(iframe);
210
- });
211
-
212
- it('should work with function pattern', async () => {
213
- const iframeRef = { current: null as HTMLIFrameElement | null };
214
- const iframe = createTestIframe('https://example.com');
215
- const mockContentWindow = {
216
- postMessage: jest.fn()
217
- };
218
- Object.defineProperty(iframe, 'contentWindow', {
219
- value: mockContentWindow,
220
- writable: true,
221
- configurable: true
222
- });
223
-
224
- type Props = { ver: number };
225
- const { result, rerender } = renderHook(
226
- ({ ver }: Props) => useClient(() => iframeRef.current, undefined, [ver]),
227
- { initialProps: { ver: 0 } as Props }
228
- );
229
-
230
- // Initially null (ref not set)
231
- expect(result.current).toBeNull();
232
-
233
- // Set ref
234
- iframeRef.current = iframe;
235
- rerender({ ver: 1 } as Props);
236
-
237
- await waitFor(() => {
238
- expect(result.current).toBeDefined();
239
- }, { timeout: 2000 });
240
-
241
- cleanupIframe(iframe);
242
- });
243
-
244
- it('should work with ref object directly', async () => {
245
- const iframe = createTestIframe('https://example.com');
246
- const mockContentWindow = {
247
- postMessage: jest.fn()
248
- };
249
- Object.defineProperty(iframe, 'contentWindow', {
250
- value: mockContentWindow,
251
- writable: true,
252
- configurable: true
253
- });
254
-
255
- const { result } = renderHook(() => {
256
- const iframeRef = useRef<HTMLIFrameElement | null>(iframe);
257
- return useClient(iframeRef);
258
- });
259
-
260
- await waitFor(() => {
261
- expect(result.current).toBeDefined();
262
- expect(result.current).not.toBeNull();
263
- }, { timeout: 2000 });
264
-
265
- cleanupIframe(iframe);
266
- });
267
-
268
- it('should recreate client when deps change', async () => {
269
- const iframe = createTestIframe('https://example.com');
270
- const mockContentWindow = {
271
- postMessage: jest.fn()
272
- };
273
- Object.defineProperty(iframe, 'contentWindow', {
274
- value: mockContentWindow,
275
- writable: true,
276
- configurable: true
277
- });
278
-
279
- let userId = 1;
280
- const { result, rerender } = renderHook(() => {
281
- return useClient(() => iframe, { secretKey: `key-${userId}` }, [userId]);
282
- });
283
-
284
- await waitFor(() => {
285
- expect(result.current).toBeDefined();
286
- }, { timeout: 2000 });
287
-
288
- const client1 = result.current;
289
-
290
- // Change dependency
291
- userId = 2;
292
- rerender();
293
-
294
- await waitFor(() => {
295
- // Previous client should be destroyed
296
- if (client1) {
297
- expect(client1.isOpen).toBe(false);
298
- }
299
- // New client should be created
300
- expect(result.current).toBeDefined();
301
- expect(result.current).not.toBe(client1);
302
- }, { timeout: 2000 });
303
-
304
- cleanupIframe(iframe);
305
- });
306
- });
307
-
308
- describe('useServer', () => {
309
- it('should create server instance', async () => {
310
- const { result } = renderHook(() => useServer());
311
-
312
- await waitFor(() => {
313
- expect(result.current).toBeDefined();
314
- expect(result.current).not.toBeNull();
315
- if (result.current) {
316
- expect(result.current.isOpen).toBe(true);
317
- }
318
- }, { timeout: 2000 });
319
- });
320
-
321
- it('should create server with options', async () => {
322
- const options = { secretKey: 'test-key', ackTimeout: 1000 };
323
- const { result } = renderHook(() => useServer(options));
324
-
325
- await waitFor(() => {
326
- expect(result.current).toBeDefined();
327
- expect(result.current).not.toBeNull();
328
- if (result.current) {
329
- expect(result.current.secretKey).toBe('test-key');
330
- }
331
- }, { timeout: 2000 });
332
- });
333
-
334
- it('should destroy server on unmount', async () => {
335
- const { result, unmount } = renderHook(() => useServer());
336
- await waitFor(() => {
337
- expect(result.current).toBeDefined();
338
- expect(result.current).not.toBeNull();
339
- }, { timeout: 2000 });
340
- const server = result.current;
341
-
342
- unmount();
343
-
344
- // Server should be destroyed
345
- if (server) {
346
- expect(server.isOpen).toBe(false);
347
- }
348
- });
349
-
350
- it('should create server only once on mount', async () => {
351
- const { result, rerender } = renderHook(() => useServer());
352
- await waitFor(() => {
353
- expect(result.current).toBeDefined();
354
- expect(result.current).not.toBeNull();
355
- }, { timeout: 2000 });
356
- const server1 = result.current;
357
-
358
- rerender();
359
-
360
- // Should return the same instance when deps is not provided (default empty array)
361
- await waitFor(() => {
362
- expect(result.current).toBeDefined();
363
- }, { timeout: 2000 });
364
- // Note: When deps is not provided, useEffect runs only once, so server should be the same
365
- // But if deps changes, a new server might be created
366
- expect(result.current).toBe(server1);
367
- });
368
-
369
- it('should recreate server when deps change', async () => {
370
- let userId = 1;
371
- const { result, rerender } = renderHook(() => {
372
- return useServer({ secretKey: `key-${userId}` }, [userId]);
373
- });
374
-
375
- const server1 = result.current;
376
- expect(server1).toBeDefined();
377
-
378
- // Change dependency
379
- userId = 2;
380
- rerender();
381
-
382
- await waitFor(() => {
383
- // New server should be created (or same if cached by secretKey)
384
- expect(result.current).toBeDefined();
385
- // Note: If servers are cached by secretKey, it might be the same instance
386
- // So we just verify it's defined
387
- }, { timeout: 2000 });
388
- });
389
- });
390
-
391
- describe('useServerHandler', () => {
392
- it('should register handler when server is available', async () => {
393
- const handler = jest.fn((req, res) => {
394
- res.send({ success: true });
395
- });
396
-
397
- let serverInstance: any = null;
398
- renderHook(() => {
399
- const server = useServer();
400
- serverInstance = server;
401
- useServerHandler(server, 'api/test', handler, []);
402
- });
403
-
404
- // Verify handler is registered by checking server internals
405
- // Since we can't easily test the full message flow, we just verify
406
- // that the hook doesn't throw and the server is created
407
- await waitFor(() => {
408
- expect(serverInstance).toBeDefined();
409
- expect(serverInstance).not.toBeNull();
410
- }, { timeout: 2000 });
411
- expect(handler).not.toHaveBeenCalled(); // Handler not called yet, just registered
412
- });
413
-
414
- it('should not register handler when server is null', () => {
415
- const handler = jest.fn();
416
-
417
- renderHook(() => {
418
- useServerHandler(null, 'api/test', handler, []);
419
- });
420
-
421
- // Should not throw
422
- expect(handler).not.toHaveBeenCalled();
423
- });
424
-
425
- it('should unregister handler on unmount', async () => {
426
- const origin = 'https://example.com';
427
- const iframe = createTestIframe(origin);
428
- const mockContentWindow = {
429
- postMessage: jest.fn()
430
- };
431
- Object.defineProperty(iframe, 'contentWindow', {
432
- value: mockContentWindow,
433
- writable: true,
434
- configurable: true
435
- });
436
-
437
- const handler = jest.fn();
438
-
439
- const { unmount } = renderHook(() => {
440
- const server = useServer();
441
- useServerHandler(server, 'api/test', handler, []);
442
- });
443
-
444
- unmount();
445
-
446
- // Handler should be unregistered
447
- const client = requestIframeClient(iframe);
448
- const server = requestIframeServer();
449
-
450
- // Try to send request - should get METHOD_NOT_FOUND
451
- client.send('api/test', {}).catch(() => {});
452
-
453
- await waitFor(() => {
454
- expect(handler).not.toHaveBeenCalled();
455
- }, { timeout: 1000 });
456
-
457
- client.destroy();
458
- server.destroy();
459
- cleanupIframe(iframe);
460
- });
461
-
462
- it('should re-register handler when dependencies change', () => {
463
- let userId = 1;
464
- const handler = jest.fn((req, res) => {
465
- res.send({ userId });
466
- });
467
-
468
- const { result, rerender } = renderHook(() => {
469
- const server = useServer();
470
- useServerHandler(server, 'api/test', handler, [userId]);
471
- return server;
472
- });
473
-
474
- // Verify server is created
475
- expect(result.current).toBeDefined();
476
-
477
- // Change dependency
478
- userId = 2;
479
- rerender();
480
-
481
- // Verify server is still defined after rerender
482
- expect(result.current).toBeDefined();
483
- });
484
-
485
- it('should use latest handler even when handler function reference changes', () => {
486
- const handler1 = jest.fn((req, res) => {
487
- res.send({ version: 1 });
488
- });
489
-
490
- type HandlerProps = { handler: jest.Mock };
491
- const { rerender } = renderHook(
492
- ({ handler }: HandlerProps) => {
493
- const server = useServer();
494
- useServerHandler(server, 'api/test', handler, []);
495
- return server;
496
- },
497
- {
498
- initialProps: {
499
- handler: handler1
500
- } as HandlerProps
501
- }
502
- );
503
-
504
- // Update handler with new function (different reference)
505
- // The wrapper should use ref to access the latest handler
506
- const handler2 = jest.fn((req, res) => {
507
- res.send({ version: 2 });
508
- });
509
- rerender({ handler: handler2 });
510
-
511
- // Verify handlers are defined (the ref mechanism ensures latest handler is used)
512
- // Note: We can't easily test the actual call without setting up full message flow,
513
- // but the ref mechanism ensures the latest handler is always called
514
- expect(handler1).toBeDefined();
515
- expect(handler2).toBeDefined();
516
- });
517
-
518
- it('should use latest closure values in handler', async () => {
519
- let userId = 1;
520
- const handler1 = jest.fn((req, res) => {
521
- res.send({ userId });
522
- });
523
-
524
- type HandlerClosureProps = { handler: jest.Mock };
525
- const { rerender } = renderHook(
526
- ({ handler }: HandlerClosureProps) => {
527
- const server = useServer();
528
- // Handler uses userId from closure
529
- useServerHandler(server, 'api/user', handler, [userId]);
530
- return server;
531
- },
532
- {
533
- initialProps: { handler: handler1 } as HandlerClosureProps
534
- }
535
- );
536
-
537
- // Wait for server to be ready
538
- await new Promise(resolve => setTimeout(resolve, 100));
539
-
540
- // Update userId and create new handler
541
- userId = 2;
542
- const handler2 = jest.fn((req, res) => {
543
- res.send({ userId });
544
- });
545
- rerender({ handler: handler2 });
546
-
547
- // Wait for update
548
- await new Promise(resolve => setTimeout(resolve, 100));
549
-
550
- // The handler should use the latest handler function via ref
551
- // Note: This test verifies that the handler wrapper correctly accesses
552
- // the latest handler function through the ref mechanism
553
- expect(handler1).toBeDefined();
554
- expect(handler2).toBeDefined();
555
- });
556
- });
557
-
558
- describe('useServerHandlerMap', () => {
559
- it('should register handlers using map when server is available', async () => {
560
- const handlers = {
561
- 'api/user': jest.fn((req, res) => res.send({ user: 'test' })),
562
- 'api/post': jest.fn((req, res) => res.send({ post: 'test' }))
563
- };
564
-
565
- let serverInstance: any = null;
566
- renderHook(() => {
567
- const server = useServer();
568
- serverInstance = server;
569
- useServerHandlerMap(server, handlers, []);
570
- });
571
-
572
- // Verify server is created and handlers are registered
573
- await waitFor(() => {
574
- expect(serverInstance).toBeDefined();
575
- expect(serverInstance).not.toBeNull();
576
- }, { timeout: 2000 });
577
- // Handlers not called yet, just registered
578
- expect(handlers['api/user']).not.toHaveBeenCalled();
579
- expect(handlers['api/post']).not.toHaveBeenCalled();
580
- });
581
-
582
- it('should not register handlers when server is null', () => {
583
- const handlers = {
584
- 'api/user': jest.fn()
585
- };
586
-
587
- renderHook(() => {
588
- useServerHandlerMap(null, handlers, []);
589
- });
590
-
591
- // Should not throw
592
- expect(handlers['api/user']).not.toHaveBeenCalled();
593
- });
594
-
595
- it('should unregister all handlers on unmount', async () => {
596
- const origin = 'https://example.com';
597
- const iframe = createTestIframe(origin);
598
- const mockContentWindow = {
599
- postMessage: jest.fn()
600
- };
601
- Object.defineProperty(iframe, 'contentWindow', {
602
- value: mockContentWindow,
603
- writable: true,
604
- configurable: true
605
- });
606
-
607
- const handlers = {
608
- 'api/user': jest.fn(),
609
- 'api/post': jest.fn()
610
- };
611
-
612
- const { unmount } = renderHook(() => {
613
- const server = useServer();
614
- useServerHandlerMap(server, handlers, [] );
615
- });
616
-
617
- unmount();
618
-
619
- // Handlers should be unregistered
620
- const client = requestIframeClient(iframe);
621
-
622
- // Try to send requests - should get METHOD_NOT_FOUND
623
- client.send('api/user', {}).catch(() => {});
624
- client.send('api/post', {}).catch(() => {});
625
-
626
- await waitFor(() => {
627
- expect(handlers['api/user']).not.toHaveBeenCalled();
628
- expect(handlers['api/post']).not.toHaveBeenCalled();
629
- }, { timeout: 1000 });
630
-
631
- client.destroy();
632
- cleanupIframe(iframe);
633
- });
634
-
635
- it('should re-register handlers when dependencies change', () => {
636
- const handlers = {
637
- 'api/user': jest.fn((req, res) => res.send({}))
638
- };
639
- let userId = 1;
640
-
641
- const { result, rerender } = renderHook(() => {
642
- const server = useServer();
643
- useServerHandlerMap(server, handlers, [userId]);
644
- return server;
645
- });
646
-
647
- // Verify server is created
648
- expect(result.current).toBeDefined();
649
-
650
- // Change dependency
651
- userId = 2;
652
- rerender();
653
-
654
- // Verify server is still defined after rerender
655
- expect(result.current).toBeDefined();
656
- });
657
-
658
- it('should handle empty handlers map', () => {
659
- const handlers = {};
660
-
661
- const { result } = renderHook(() => {
662
- const server = useServer();
663
- useServerHandlerMap(server, handlers, []);
664
- return server;
665
- });
666
-
667
- // Should not throw
668
- expect(result.current).toBeDefined();
669
- });
670
-
671
- it('should use latest handlers even when map object reference changes', () => {
672
- const handler1 = jest.fn((req, res) => {
673
- res.send({ version: 1 });
674
- });
675
-
676
- type MapHandlerProps = { handlers: Record<string, jest.Mock> };
677
- const { rerender } = renderHook(
678
- ({ handlers }: MapHandlerProps) => {
679
- const server = useServer();
680
- useServerHandlerMap(server, handlers, []);
681
- return server;
682
- },
683
- {
684
- initialProps: {
685
- handlers: {
686
- 'api/test': handler1
687
- }
688
- } as MapHandlerProps
689
- }
690
- );
691
-
692
- // Create new map object with same keys but different handler
693
- // The wrapper should use ref to access the latest handlers
694
- const handler2 = jest.fn((req, res) => {
695
- res.send({ version: 2 });
696
- });
697
- rerender({
698
- handlers: {
699
- 'api/test': handler2
700
- }
701
- } as MapHandlerProps);
702
-
703
- // Verify handlers are defined
704
- // Note: When map object reference changes but keys are the same,
705
- // the mapWrapper is not recreated (keysStr doesn't change),
706
- // but the ref mechanism ensures latest handlers are always used
707
- expect(handler1).toBeDefined();
708
- expect(handler2).toBeDefined();
709
- });
710
-
711
- it('should re-register when map keys change', () => {
712
- const handler1 = jest.fn((req, res) => res.send({ path: 'api/user' }));
713
- const handler2 = jest.fn((req, res) => res.send({ path: 'api/post' }));
714
-
715
- type HandlersMapProps = { handlers: Record<string, jest.Mock> };
716
- const { rerender } = renderHook(
717
- ({ handlers }: HandlersMapProps) => {
718
- const server = useServer();
719
- useServerHandlerMap(server, handlers, []);
720
- return server;
721
- },
722
- {
723
- initialProps: {
724
- handlers: {
725
- 'api/user': handler1
726
- }
727
- } as HandlersMapProps
728
- }
729
- );
730
-
731
- // Add new key to map - should trigger re-registration
732
- rerender({
733
- handlers: {
734
- 'api/user': handler1,
735
- 'api/post': handler2
736
- }
737
- } as HandlersMapProps);
738
-
739
- // Verify handlers are defined
740
- // Note: When keys change, the mapWrapper is recreated and handlers are re-registered
741
- expect(handler1).toBeDefined();
742
- expect(handler2).toBeDefined();
743
- });
744
-
745
- it('should use latest closure values in map handlers', async () => {
746
- let userId = 1;
747
- const handler1 = jest.fn((req, res) => {
748
- res.send({ userId });
749
- });
750
-
751
- type MapClosureProps = { handlers: Record<string, jest.Mock> };
752
- const { rerender } = renderHook(
753
- ({ handlers }: MapClosureProps) => {
754
- const server = useServer();
755
- // Handlers use userId from closure
756
- useServerHandlerMap(server, handlers, [userId]);
757
- return server;
758
- },
759
- {
760
- initialProps: {
761
- handlers: {
762
- 'api/user': handler1
763
- }
764
- } as MapClosureProps
765
- }
766
- );
767
-
768
- // Wait for server to be ready
769
- await new Promise(resolve => setTimeout(resolve, 100));
770
-
771
- // Update userId and create new handler
772
- userId = 2;
773
- const handler2 = jest.fn((req, res) => {
774
- res.send({ userId });
775
- });
776
- rerender({
777
- handlers: {
778
- 'api/user': handler2
779
- }
780
- } as MapClosureProps);
781
-
782
- // Wait for update
783
- await new Promise(resolve => setTimeout(resolve, 100));
784
-
785
- // The handler should use the latest handler function via ref
786
- // Note: This test verifies that the handler wrappers correctly access
787
- // the latest handler functions through the ref mechanism
788
- expect(handler1).toBeDefined();
789
- expect(handler2).toBeDefined();
790
- });
791
- });
792
- });