@pyreon/query 0.11.5 → 0.11.7
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 +58 -56
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +7 -7
- package/package.json +16 -16
- package/src/index.ts +21 -33
- package/src/query-client.ts +5 -5
- package/src/tests/query-additional.test.tsx +59 -59
- package/src/tests/query.test.tsx +243 -243
- package/src/tests/sse.test.tsx +131 -131
- package/src/tests/subscription.test.tsx +97 -97
- package/src/use-infinite-query.ts +7 -7
- package/src/use-is-fetching.ts +5 -5
- package/src/use-mutation.ts +8 -8
- package/src/use-queries.ts +6 -6
- package/src/use-query-error-reset-boundary.ts +6 -6
- package/src/use-query.ts +8 -8
- package/src/use-sse.ts +19 -19
- package/src/use-subscription.ts +18 -18
- package/src/use-suspense-query.ts +12 -12
package/src/tests/sse.test.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { signal } from
|
|
2
|
-
import { mount } from
|
|
3
|
-
import { QueryClient } from
|
|
4
|
-
import { QueryClientProvider, type UseSSEResult, useSSE } from
|
|
1
|
+
import { signal } from '@pyreon/reactivity'
|
|
2
|
+
import { mount } from '@pyreon/runtime-dom'
|
|
3
|
+
import { QueryClient } from '@tanstack/query-core'
|
|
4
|
+
import { QueryClientProvider, type UseSSEResult, useSSE } from '../index'
|
|
5
5
|
|
|
6
6
|
// ─── Mock EventSource ───────────────────────────────────────────────────────
|
|
7
7
|
|
|
@@ -66,11 +66,11 @@ class MockEventSourceClass {
|
|
|
66
66
|
|
|
67
67
|
_simulateOpen() {
|
|
68
68
|
this.readyState = MockEventSourceClass.OPEN
|
|
69
|
-
this.onopen?.({ type:
|
|
69
|
+
this.onopen?.({ type: 'open' })
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
_simulateMessage(data: string, lastEventId =
|
|
73
|
-
this.onmessage?.({ type:
|
|
72
|
+
_simulateMessage(data: string, lastEventId = '') {
|
|
73
|
+
this.onmessage?.({ type: 'message', data, lastEventId } as unknown as MessageEvent)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
_simulateNamedEvent(name: string, data: string) {
|
|
@@ -84,7 +84,7 @@ class MockEventSourceClass {
|
|
|
84
84
|
if (closed) {
|
|
85
85
|
this.readyState = MockEventSourceClass.CLOSED
|
|
86
86
|
}
|
|
87
|
-
this.onerror?.({ type:
|
|
87
|
+
this.onerror?.({ type: 'error' })
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -106,7 +106,7 @@ function makeClient() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function withProvider(client: QueryClient, component: () => void): () => void {
|
|
109
|
-
const el = document.createElement(
|
|
109
|
+
const el = document.createElement('div')
|
|
110
110
|
document.body.appendChild(el)
|
|
111
111
|
const unmount = mount(
|
|
112
112
|
<QueryClientProvider client={client}>
|
|
@@ -129,66 +129,66 @@ function lastMockES(): MockEventSource {
|
|
|
129
129
|
|
|
130
130
|
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
131
131
|
|
|
132
|
-
describe(
|
|
132
|
+
describe('useSSE', () => {
|
|
133
133
|
beforeEach(() => {
|
|
134
134
|
mockInstances = []
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
-
it(
|
|
137
|
+
it('connects to the EventSource URL', () => {
|
|
138
138
|
const client = makeClient()
|
|
139
139
|
let sse: UseSSEResult<string> | null = null
|
|
140
140
|
|
|
141
141
|
const unmount = withProvider(client, () => {
|
|
142
|
-
sse = useSSE({ url:
|
|
142
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
143
143
|
})
|
|
144
144
|
|
|
145
145
|
expect(mockInstances).toHaveLength(1)
|
|
146
|
-
expect(lastMockES().url).toBe(
|
|
147
|
-
expect(sse!.status()).toBe(
|
|
146
|
+
expect(lastMockES().url).toBe('http://example.com/events')
|
|
147
|
+
expect(sse!.status()).toBe('connecting')
|
|
148
148
|
|
|
149
149
|
unmount()
|
|
150
150
|
})
|
|
151
151
|
|
|
152
|
-
it(
|
|
152
|
+
it('status transitions to connected on open', () => {
|
|
153
153
|
const client = makeClient()
|
|
154
154
|
let sse: UseSSEResult<string> | null = null
|
|
155
155
|
|
|
156
156
|
const unmount = withProvider(client, () => {
|
|
157
|
-
sse = useSSE({ url:
|
|
157
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
158
158
|
})
|
|
159
159
|
|
|
160
160
|
lastMockES()._simulateOpen()
|
|
161
|
-
expect(sse!.status()).toBe(
|
|
161
|
+
expect(sse!.status()).toBe('connected')
|
|
162
162
|
|
|
163
163
|
unmount()
|
|
164
164
|
})
|
|
165
165
|
|
|
166
|
-
it(
|
|
166
|
+
it('updates data signal on message', () => {
|
|
167
167
|
const client = makeClient()
|
|
168
168
|
let sse: UseSSEResult<string> | null = null
|
|
169
169
|
|
|
170
170
|
const unmount = withProvider(client, () => {
|
|
171
|
-
sse = useSSE({ url:
|
|
171
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
172
172
|
})
|
|
173
173
|
|
|
174
174
|
lastMockES()._simulateOpen()
|
|
175
|
-
lastMockES()._simulateMessage(
|
|
175
|
+
lastMockES()._simulateMessage('hello')
|
|
176
176
|
|
|
177
|
-
expect(sse!.data()).toBe(
|
|
177
|
+
expect(sse!.data()).toBe('hello')
|
|
178
178
|
|
|
179
|
-
lastMockES()._simulateMessage(
|
|
180
|
-
expect(sse!.data()).toBe(
|
|
179
|
+
lastMockES()._simulateMessage('world')
|
|
180
|
+
expect(sse!.data()).toBe('world')
|
|
181
181
|
|
|
182
182
|
unmount()
|
|
183
183
|
})
|
|
184
184
|
|
|
185
|
-
it(
|
|
185
|
+
it('calls onMessage with parsed data and queryClient', () => {
|
|
186
186
|
const client = makeClient()
|
|
187
187
|
const messages: string[] = []
|
|
188
188
|
|
|
189
189
|
const unmount = withProvider(client, () => {
|
|
190
190
|
useSSE({
|
|
191
|
-
url:
|
|
191
|
+
url: 'http://example.com/events',
|
|
192
192
|
onMessage: (data, qc) => {
|
|
193
193
|
messages.push(data)
|
|
194
194
|
expect(qc).toBe(client)
|
|
@@ -197,20 +197,20 @@ describe("useSSE", () => {
|
|
|
197
197
|
})
|
|
198
198
|
|
|
199
199
|
lastMockES()._simulateOpen()
|
|
200
|
-
lastMockES()._simulateMessage(
|
|
201
|
-
lastMockES()._simulateMessage(
|
|
200
|
+
lastMockES()._simulateMessage('hello')
|
|
201
|
+
lastMockES()._simulateMessage('world')
|
|
202
202
|
|
|
203
|
-
expect(messages).toEqual([
|
|
203
|
+
expect(messages).toEqual(['hello', 'world'])
|
|
204
204
|
unmount()
|
|
205
205
|
})
|
|
206
206
|
|
|
207
|
-
it(
|
|
207
|
+
it('parses messages with parse option', () => {
|
|
208
208
|
const client = makeClient()
|
|
209
209
|
let sse: UseSSEResult<{ value: number }> | null = null
|
|
210
210
|
|
|
211
211
|
const unmount = withProvider(client, () => {
|
|
212
212
|
sse = useSSE({
|
|
213
|
-
url:
|
|
213
|
+
url: 'http://example.com/events',
|
|
214
214
|
parse: JSON.parse,
|
|
215
215
|
})
|
|
216
216
|
})
|
|
@@ -223,60 +223,60 @@ describe("useSSE", () => {
|
|
|
223
223
|
unmount()
|
|
224
224
|
})
|
|
225
225
|
|
|
226
|
-
it(
|
|
226
|
+
it('invalidates queries on message', () => {
|
|
227
227
|
const client = makeClient()
|
|
228
|
-
const invalidateSpy = vi.spyOn(client,
|
|
228
|
+
const invalidateSpy = vi.spyOn(client, 'invalidateQueries')
|
|
229
229
|
|
|
230
230
|
const unmount = withProvider(client, () => {
|
|
231
231
|
useSSE({
|
|
232
|
-
url:
|
|
232
|
+
url: 'http://example.com/events',
|
|
233
233
|
onMessage: (_data, qc) => {
|
|
234
|
-
qc.invalidateQueries({ queryKey: [
|
|
234
|
+
qc.invalidateQueries({ queryKey: ['orders'] })
|
|
235
235
|
},
|
|
236
236
|
})
|
|
237
237
|
})
|
|
238
238
|
|
|
239
239
|
lastMockES()._simulateOpen()
|
|
240
|
-
lastMockES()._simulateMessage(
|
|
240
|
+
lastMockES()._simulateMessage('order-updated')
|
|
241
241
|
|
|
242
|
-
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: [
|
|
242
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['orders'] })
|
|
243
243
|
unmount()
|
|
244
244
|
})
|
|
245
245
|
|
|
246
|
-
it(
|
|
246
|
+
it('listens to named events', () => {
|
|
247
247
|
const client = makeClient()
|
|
248
248
|
let sse: UseSSEResult<string> | null = null
|
|
249
249
|
|
|
250
250
|
const unmount = withProvider(client, () => {
|
|
251
251
|
sse = useSSE({
|
|
252
|
-
url:
|
|
253
|
-
events:
|
|
252
|
+
url: 'http://example.com/events',
|
|
253
|
+
events: 'order-update',
|
|
254
254
|
})
|
|
255
255
|
})
|
|
256
256
|
|
|
257
257
|
lastMockES()._simulateOpen()
|
|
258
258
|
|
|
259
259
|
// Named event listener should be attached
|
|
260
|
-
expect(lastMockES().addEventListener).toHaveBeenCalledWith(
|
|
260
|
+
expect(lastMockES().addEventListener).toHaveBeenCalledWith('order-update', expect.any(Function))
|
|
261
261
|
|
|
262
262
|
// Generic onmessage should NOT be set
|
|
263
263
|
expect(lastMockES().onmessage).toBeNull()
|
|
264
264
|
|
|
265
265
|
// Simulate named event
|
|
266
|
-
lastMockES()._simulateNamedEvent(
|
|
267
|
-
expect(sse!.data()).toBe(
|
|
266
|
+
lastMockES()._simulateNamedEvent('order-update', 'data1')
|
|
267
|
+
expect(sse!.data()).toBe('data1')
|
|
268
268
|
|
|
269
269
|
unmount()
|
|
270
270
|
})
|
|
271
271
|
|
|
272
|
-
it(
|
|
272
|
+
it('listens to multiple named events', () => {
|
|
273
273
|
const client = makeClient()
|
|
274
274
|
let sse: UseSSEResult<string> | null = null
|
|
275
275
|
|
|
276
276
|
const unmount = withProvider(client, () => {
|
|
277
277
|
sse = useSSE({
|
|
278
|
-
url:
|
|
279
|
-
events: [
|
|
278
|
+
url: 'http://example.com/events',
|
|
279
|
+
events: ['order-update', 'user-update'],
|
|
280
280
|
})
|
|
281
281
|
})
|
|
282
282
|
|
|
@@ -284,57 +284,57 @@ describe("useSSE", () => {
|
|
|
284
284
|
|
|
285
285
|
expect(lastMockES().addEventListener).toHaveBeenCalledTimes(2)
|
|
286
286
|
|
|
287
|
-
lastMockES()._simulateNamedEvent(
|
|
288
|
-
expect(sse!.data()).toBe(
|
|
287
|
+
lastMockES()._simulateNamedEvent('order-update', 'order1')
|
|
288
|
+
expect(sse!.data()).toBe('order1')
|
|
289
289
|
|
|
290
|
-
lastMockES()._simulateNamedEvent(
|
|
291
|
-
expect(sse!.data()).toBe(
|
|
290
|
+
lastMockES()._simulateNamedEvent('user-update', 'user1')
|
|
291
|
+
expect(sse!.data()).toBe('user1')
|
|
292
292
|
|
|
293
293
|
unmount()
|
|
294
294
|
})
|
|
295
295
|
|
|
296
|
-
it(
|
|
296
|
+
it('close() disconnects and sets status', () => {
|
|
297
297
|
const client = makeClient()
|
|
298
298
|
let sse: UseSSEResult<string> | null = null
|
|
299
299
|
|
|
300
300
|
const unmount = withProvider(client, () => {
|
|
301
|
-
sse = useSSE({ url:
|
|
301
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
302
302
|
})
|
|
303
303
|
|
|
304
304
|
lastMockES()._simulateOpen()
|
|
305
305
|
sse!.close()
|
|
306
306
|
|
|
307
|
-
expect(sse!.status()).toBe(
|
|
307
|
+
expect(sse!.status()).toBe('disconnected')
|
|
308
308
|
expect(lastMockES().close).toHaveBeenCalled()
|
|
309
309
|
unmount()
|
|
310
310
|
})
|
|
311
311
|
|
|
312
|
-
it(
|
|
312
|
+
it('status transitions to error on error', () => {
|
|
313
313
|
const client = makeClient()
|
|
314
314
|
let sse: UseSSEResult<string> | null = null
|
|
315
315
|
const errors: Event[] = []
|
|
316
316
|
|
|
317
317
|
const unmount = withProvider(client, () => {
|
|
318
318
|
sse = useSSE({
|
|
319
|
-
url:
|
|
319
|
+
url: 'http://example.com/events',
|
|
320
320
|
reconnect: false,
|
|
321
321
|
onError: (e) => errors.push(e),
|
|
322
322
|
})
|
|
323
323
|
})
|
|
324
324
|
|
|
325
325
|
lastMockES()._simulateError()
|
|
326
|
-
expect(sse!.status()).toBe(
|
|
326
|
+
expect(sse!.status()).toBe('error')
|
|
327
327
|
expect(sse!.error()).not.toBeNull()
|
|
328
328
|
expect(errors).toHaveLength(1)
|
|
329
329
|
unmount()
|
|
330
330
|
})
|
|
331
331
|
|
|
332
|
-
it(
|
|
332
|
+
it('auto-reconnects when EventSource closes', async () => {
|
|
333
333
|
const client = makeClient()
|
|
334
334
|
|
|
335
335
|
const unmount = withProvider(client, () => {
|
|
336
336
|
useSSE({
|
|
337
|
-
url:
|
|
337
|
+
url: 'http://example.com/events',
|
|
338
338
|
reconnect: true,
|
|
339
339
|
reconnectDelay: 50,
|
|
340
340
|
})
|
|
@@ -353,12 +353,12 @@ describe("useSSE", () => {
|
|
|
353
353
|
unmount()
|
|
354
354
|
})
|
|
355
355
|
|
|
356
|
-
it(
|
|
356
|
+
it('does not reconnect when reconnect is false', async () => {
|
|
357
357
|
const client = makeClient()
|
|
358
358
|
|
|
359
359
|
const unmount = withProvider(client, () => {
|
|
360
360
|
useSSE({
|
|
361
|
-
url:
|
|
361
|
+
url: 'http://example.com/events',
|
|
362
362
|
reconnect: false,
|
|
363
363
|
})
|
|
364
364
|
})
|
|
@@ -371,13 +371,13 @@ describe("useSSE", () => {
|
|
|
371
371
|
unmount()
|
|
372
372
|
})
|
|
373
373
|
|
|
374
|
-
it(
|
|
374
|
+
it('does not reconnect after intentional close()', async () => {
|
|
375
375
|
const client = makeClient()
|
|
376
376
|
let sse: UseSSEResult<string> | null = null
|
|
377
377
|
|
|
378
378
|
const unmount = withProvider(client, () => {
|
|
379
379
|
sse = useSSE({
|
|
380
|
-
url:
|
|
380
|
+
url: 'http://example.com/events',
|
|
381
381
|
reconnect: true,
|
|
382
382
|
reconnectDelay: 50,
|
|
383
383
|
})
|
|
@@ -391,12 +391,12 @@ describe("useSSE", () => {
|
|
|
391
391
|
unmount()
|
|
392
392
|
})
|
|
393
393
|
|
|
394
|
-
it(
|
|
394
|
+
it('respects maxReconnectAttempts', async () => {
|
|
395
395
|
const client = makeClient()
|
|
396
396
|
|
|
397
397
|
const unmount = withProvider(client, () => {
|
|
398
398
|
useSSE({
|
|
399
|
-
url:
|
|
399
|
+
url: 'http://example.com/events',
|
|
400
400
|
reconnect: true,
|
|
401
401
|
reconnectDelay: 10,
|
|
402
402
|
maxReconnectAttempts: 2,
|
|
@@ -424,13 +424,13 @@ describe("useSSE", () => {
|
|
|
424
424
|
unmount()
|
|
425
425
|
})
|
|
426
426
|
|
|
427
|
-
it(
|
|
427
|
+
it('reconnect() resets attempts and reconnects', () => {
|
|
428
428
|
const client = makeClient()
|
|
429
429
|
let sse: UseSSEResult<string> | null = null
|
|
430
430
|
|
|
431
431
|
const unmount = withProvider(client, () => {
|
|
432
432
|
sse = useSSE({
|
|
433
|
-
url:
|
|
433
|
+
url: 'http://example.com/events',
|
|
434
434
|
reconnect: false,
|
|
435
435
|
})
|
|
436
436
|
})
|
|
@@ -442,34 +442,34 @@ describe("useSSE", () => {
|
|
|
442
442
|
|
|
443
443
|
sse!.reconnect()
|
|
444
444
|
expect(mockInstances).toHaveLength(2)
|
|
445
|
-
expect(sse!.status()).toBe(
|
|
445
|
+
expect(sse!.status()).toBe('connecting')
|
|
446
446
|
|
|
447
447
|
unmount()
|
|
448
448
|
})
|
|
449
449
|
|
|
450
|
-
it(
|
|
450
|
+
it('enabled: false prevents connection', () => {
|
|
451
451
|
const client = makeClient()
|
|
452
452
|
let sse: UseSSEResult<string> | null = null
|
|
453
453
|
|
|
454
454
|
const unmount = withProvider(client, () => {
|
|
455
455
|
sse = useSSE({
|
|
456
|
-
url:
|
|
456
|
+
url: 'http://example.com/events',
|
|
457
457
|
enabled: false,
|
|
458
458
|
})
|
|
459
459
|
})
|
|
460
460
|
|
|
461
461
|
expect(mockInstances).toHaveLength(0)
|
|
462
|
-
expect(sse!.status()).toBe(
|
|
462
|
+
expect(sse!.status()).toBe('disconnected')
|
|
463
463
|
unmount()
|
|
464
464
|
})
|
|
465
465
|
|
|
466
|
-
it(
|
|
466
|
+
it('reactive enabled signal controls connection', () => {
|
|
467
467
|
const client = makeClient()
|
|
468
468
|
const enabled = signal(false)
|
|
469
469
|
|
|
470
470
|
const unmount = withProvider(client, () => {
|
|
471
471
|
useSSE({
|
|
472
|
-
url:
|
|
472
|
+
url: 'http://example.com/events',
|
|
473
473
|
enabled: () => enabled(),
|
|
474
474
|
})
|
|
475
475
|
})
|
|
@@ -483,31 +483,31 @@ describe("useSSE", () => {
|
|
|
483
483
|
unmount()
|
|
484
484
|
})
|
|
485
485
|
|
|
486
|
-
it(
|
|
486
|
+
it('reactive URL reconnects when URL changes', () => {
|
|
487
487
|
const client = makeClient()
|
|
488
|
-
const url = signal(
|
|
488
|
+
const url = signal('http://example.com/events1')
|
|
489
489
|
|
|
490
490
|
const unmount = withProvider(client, () => {
|
|
491
491
|
useSSE({ url: () => url() })
|
|
492
492
|
})
|
|
493
493
|
|
|
494
494
|
expect(mockInstances).toHaveLength(1)
|
|
495
|
-
expect(lastMockES().url).toBe(
|
|
495
|
+
expect(lastMockES().url).toBe('http://example.com/events1')
|
|
496
496
|
|
|
497
|
-
url.set(
|
|
497
|
+
url.set('http://example.com/events2')
|
|
498
498
|
|
|
499
499
|
expect(mockInstances).toHaveLength(2)
|
|
500
|
-
expect(lastMockES().url).toBe(
|
|
500
|
+
expect(lastMockES().url).toBe('http://example.com/events2')
|
|
501
501
|
|
|
502
502
|
unmount()
|
|
503
503
|
})
|
|
504
504
|
|
|
505
|
-
it(
|
|
505
|
+
it('passes withCredentials to EventSource', () => {
|
|
506
506
|
const client = makeClient()
|
|
507
507
|
|
|
508
508
|
const unmount = withProvider(client, () => {
|
|
509
509
|
useSSE({
|
|
510
|
-
url:
|
|
510
|
+
url: 'http://example.com/events',
|
|
511
511
|
withCredentials: true,
|
|
512
512
|
})
|
|
513
513
|
})
|
|
@@ -516,22 +516,22 @@ describe("useSSE", () => {
|
|
|
516
516
|
unmount()
|
|
517
517
|
})
|
|
518
518
|
|
|
519
|
-
it(
|
|
519
|
+
it('withCredentials defaults to false', () => {
|
|
520
520
|
const client = makeClient()
|
|
521
521
|
|
|
522
522
|
const unmount = withProvider(client, () => {
|
|
523
|
-
useSSE({ url:
|
|
523
|
+
useSSE({ url: 'http://example.com/events' })
|
|
524
524
|
})
|
|
525
525
|
|
|
526
526
|
expect(lastMockES().withCredentials).toBe(false)
|
|
527
527
|
unmount()
|
|
528
528
|
})
|
|
529
529
|
|
|
530
|
-
it(
|
|
530
|
+
it('cleans up on unmount', () => {
|
|
531
531
|
const client = makeClient()
|
|
532
532
|
|
|
533
533
|
const unmount = withProvider(client, () => {
|
|
534
|
-
useSSE({ url:
|
|
534
|
+
useSSE({ url: 'http://example.com/events' })
|
|
535
535
|
})
|
|
536
536
|
|
|
537
537
|
lastMockES()._simulateOpen()
|
|
@@ -541,15 +541,15 @@ describe("useSSE", () => {
|
|
|
541
541
|
expect(es.close).toHaveBeenCalled()
|
|
542
542
|
})
|
|
543
543
|
|
|
544
|
-
it(
|
|
544
|
+
it('message handler that throws does not crash the subscription', () => {
|
|
545
545
|
const client = makeClient()
|
|
546
546
|
let sse: UseSSEResult<string> | null = null
|
|
547
547
|
|
|
548
548
|
const unmount = withProvider(client, () => {
|
|
549
549
|
sse = useSSE({
|
|
550
|
-
url:
|
|
550
|
+
url: 'http://example.com/events',
|
|
551
551
|
onMessage: () => {
|
|
552
|
-
throw new Error(
|
|
552
|
+
throw new Error('handler boom')
|
|
553
553
|
},
|
|
554
554
|
})
|
|
555
555
|
})
|
|
@@ -557,20 +557,20 @@ describe("useSSE", () => {
|
|
|
557
557
|
lastMockES()._simulateOpen()
|
|
558
558
|
|
|
559
559
|
// Should not throw — the error is caught internally
|
|
560
|
-
expect(() => lastMockES()._simulateMessage(
|
|
560
|
+
expect(() => lastMockES()._simulateMessage('test')).not.toThrow()
|
|
561
561
|
|
|
562
562
|
// Subscription should still be connected
|
|
563
|
-
expect(sse!.status()).toBe(
|
|
563
|
+
expect(sse!.status()).toBe('connected')
|
|
564
564
|
|
|
565
565
|
unmount()
|
|
566
566
|
})
|
|
567
567
|
|
|
568
|
-
it(
|
|
568
|
+
it('no reconnect attempts after unmount', async () => {
|
|
569
569
|
const client = makeClient()
|
|
570
570
|
|
|
571
571
|
const unmount = withProvider(client, () => {
|
|
572
572
|
useSSE({
|
|
573
|
-
url:
|
|
573
|
+
url: 'http://example.com/events',
|
|
574
574
|
reconnect: true,
|
|
575
575
|
reconnectDelay: 20,
|
|
576
576
|
})
|
|
@@ -584,25 +584,25 @@ describe("useSSE", () => {
|
|
|
584
584
|
expect(mockInstances).toHaveLength(1)
|
|
585
585
|
})
|
|
586
586
|
|
|
587
|
-
it(
|
|
587
|
+
it('data() starts as null', () => {
|
|
588
588
|
const client = makeClient()
|
|
589
589
|
let sse: UseSSEResult<string> | null = null
|
|
590
590
|
|
|
591
591
|
const unmount = withProvider(client, () => {
|
|
592
|
-
sse = useSSE({ url:
|
|
592
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
593
593
|
})
|
|
594
594
|
|
|
595
595
|
expect(sse!.data()).toBeNull()
|
|
596
596
|
unmount()
|
|
597
597
|
})
|
|
598
598
|
|
|
599
|
-
it(
|
|
599
|
+
it('error() starts as null and is set on error', () => {
|
|
600
600
|
const client = makeClient()
|
|
601
601
|
let sse: UseSSEResult<string> | null = null
|
|
602
602
|
|
|
603
603
|
const unmount = withProvider(client, () => {
|
|
604
604
|
sse = useSSE({
|
|
605
|
-
url:
|
|
605
|
+
url: 'http://example.com/events',
|
|
606
606
|
reconnect: false,
|
|
607
607
|
})
|
|
608
608
|
})
|
|
@@ -615,13 +615,13 @@ describe("useSSE", () => {
|
|
|
615
615
|
unmount()
|
|
616
616
|
})
|
|
617
617
|
|
|
618
|
-
it(
|
|
618
|
+
it('error is cleared on successful open', () => {
|
|
619
619
|
const client = makeClient()
|
|
620
620
|
let sse: UseSSEResult<string> | null = null
|
|
621
621
|
|
|
622
622
|
const unmount = withProvider(client, () => {
|
|
623
623
|
sse = useSSE({
|
|
624
|
-
url:
|
|
624
|
+
url: 'http://example.com/events',
|
|
625
625
|
reconnect: false,
|
|
626
626
|
})
|
|
627
627
|
})
|
|
@@ -634,90 +634,90 @@ describe("useSSE", () => {
|
|
|
634
634
|
lastMockES()._simulateOpen()
|
|
635
635
|
|
|
636
636
|
expect(sse!.error()).toBeNull()
|
|
637
|
-
expect(sse!.status()).toBe(
|
|
637
|
+
expect(sse!.status()).toBe('connected')
|
|
638
638
|
|
|
639
639
|
unmount()
|
|
640
640
|
})
|
|
641
641
|
|
|
642
642
|
// ── lastEventId ──────────────────────────────────────────────────────────
|
|
643
643
|
|
|
644
|
-
it(
|
|
644
|
+
it('lastEventId starts as empty string', () => {
|
|
645
645
|
const client = makeClient()
|
|
646
646
|
let sse: UseSSEResult<string> | null = null
|
|
647
647
|
|
|
648
648
|
const unmount = withProvider(client, () => {
|
|
649
|
-
sse = useSSE({ url:
|
|
649
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
650
650
|
})
|
|
651
651
|
|
|
652
|
-
expect(sse!.lastEventId()).toBe(
|
|
652
|
+
expect(sse!.lastEventId()).toBe('')
|
|
653
653
|
unmount()
|
|
654
654
|
})
|
|
655
655
|
|
|
656
|
-
it(
|
|
656
|
+
it('lastEventId is updated from message events', () => {
|
|
657
657
|
const client = makeClient()
|
|
658
658
|
let sse: UseSSEResult<string> | null = null
|
|
659
659
|
|
|
660
660
|
const unmount = withProvider(client, () => {
|
|
661
|
-
sse = useSSE({ url:
|
|
661
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
662
662
|
})
|
|
663
663
|
|
|
664
664
|
lastMockES()._simulateOpen()
|
|
665
|
-
lastMockES()._simulateMessage(
|
|
665
|
+
lastMockES()._simulateMessage('hello', 'evt-1')
|
|
666
666
|
|
|
667
|
-
expect(sse!.lastEventId()).toBe(
|
|
667
|
+
expect(sse!.lastEventId()).toBe('evt-1')
|
|
668
668
|
|
|
669
|
-
lastMockES()._simulateMessage(
|
|
670
|
-
expect(sse!.lastEventId()).toBe(
|
|
669
|
+
lastMockES()._simulateMessage('world', 'evt-2')
|
|
670
|
+
expect(sse!.lastEventId()).toBe('evt-2')
|
|
671
671
|
|
|
672
672
|
unmount()
|
|
673
673
|
})
|
|
674
674
|
|
|
675
|
-
it(
|
|
675
|
+
it('lastEventId is not updated when lastEventId is empty', () => {
|
|
676
676
|
const client = makeClient()
|
|
677
677
|
let sse: UseSSEResult<string> | null = null
|
|
678
678
|
|
|
679
679
|
const unmount = withProvider(client, () => {
|
|
680
|
-
sse = useSSE({ url:
|
|
680
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
681
681
|
})
|
|
682
682
|
|
|
683
683
|
lastMockES()._simulateOpen()
|
|
684
|
-
lastMockES()._simulateMessage(
|
|
685
|
-
expect(sse!.lastEventId()).toBe(
|
|
684
|
+
lastMockES()._simulateMessage('hello', 'evt-1')
|
|
685
|
+
expect(sse!.lastEventId()).toBe('evt-1')
|
|
686
686
|
|
|
687
687
|
// Message with empty lastEventId should not overwrite
|
|
688
|
-
lastMockES()._simulateMessage(
|
|
689
|
-
expect(sse!.lastEventId()).toBe(
|
|
688
|
+
lastMockES()._simulateMessage('world', '')
|
|
689
|
+
expect(sse!.lastEventId()).toBe('evt-1')
|
|
690
690
|
|
|
691
691
|
unmount()
|
|
692
692
|
})
|
|
693
693
|
|
|
694
694
|
// ── onOpen callback ──────────────────────────────────────────────────────
|
|
695
695
|
|
|
696
|
-
it(
|
|
696
|
+
it('calls onOpen when connection opens', () => {
|
|
697
697
|
const client = makeClient()
|
|
698
698
|
const openEvents: Event[] = []
|
|
699
699
|
|
|
700
700
|
const unmount = withProvider(client, () => {
|
|
701
701
|
useSSE({
|
|
702
|
-
url:
|
|
702
|
+
url: 'http://example.com/events',
|
|
703
703
|
onOpen: (event) => openEvents.push(event),
|
|
704
704
|
})
|
|
705
705
|
})
|
|
706
706
|
|
|
707
707
|
lastMockES()._simulateOpen()
|
|
708
708
|
expect(openEvents).toHaveLength(1)
|
|
709
|
-
expect(openEvents[0]!.type).toBe(
|
|
709
|
+
expect(openEvents[0]!.type).toBe('open')
|
|
710
710
|
|
|
711
711
|
unmount()
|
|
712
712
|
})
|
|
713
713
|
|
|
714
|
-
it(
|
|
714
|
+
it('onOpen is called on each reconnection open', async () => {
|
|
715
715
|
const client = makeClient()
|
|
716
716
|
const openEvents: Event[] = []
|
|
717
717
|
|
|
718
718
|
const unmount = withProvider(client, () => {
|
|
719
719
|
useSSE({
|
|
720
|
-
url:
|
|
720
|
+
url: 'http://example.com/events',
|
|
721
721
|
reconnect: true,
|
|
722
722
|
reconnectDelay: 10,
|
|
723
723
|
onOpen: (event) => openEvents.push(event),
|
|
@@ -739,24 +739,24 @@ describe("useSSE", () => {
|
|
|
739
739
|
|
|
740
740
|
// ── readyState ───────────────────────────────────────────────────────────
|
|
741
741
|
|
|
742
|
-
it(
|
|
742
|
+
it('readyState starts as CLOSED (2)', () => {
|
|
743
743
|
const client = makeClient()
|
|
744
744
|
let sse: UseSSEResult<string> | null = null
|
|
745
745
|
|
|
746
746
|
const unmount = withProvider(client, () => {
|
|
747
|
-
sse = useSSE({ url:
|
|
747
|
+
sse = useSSE({ url: 'http://example.com/events', enabled: false })
|
|
748
748
|
})
|
|
749
749
|
|
|
750
750
|
expect(sse!.readyState()).toBe(2)
|
|
751
751
|
unmount()
|
|
752
752
|
})
|
|
753
753
|
|
|
754
|
-
it(
|
|
754
|
+
it('readyState transitions to CONNECTING (0) then OPEN (1)', () => {
|
|
755
755
|
const client = makeClient()
|
|
756
756
|
let sse: UseSSEResult<string> | null = null
|
|
757
757
|
|
|
758
758
|
const unmount = withProvider(client, () => {
|
|
759
|
-
sse = useSSE({ url:
|
|
759
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
760
760
|
})
|
|
761
761
|
|
|
762
762
|
// After connect() but before open, readyState should be CONNECTING
|
|
@@ -768,12 +768,12 @@ describe("useSSE", () => {
|
|
|
768
768
|
unmount()
|
|
769
769
|
})
|
|
770
770
|
|
|
771
|
-
it(
|
|
771
|
+
it('readyState becomes CLOSED (2) after close()', () => {
|
|
772
772
|
const client = makeClient()
|
|
773
773
|
let sse: UseSSEResult<string> | null = null
|
|
774
774
|
|
|
775
775
|
const unmount = withProvider(client, () => {
|
|
776
|
-
sse = useSSE({ url:
|
|
776
|
+
sse = useSSE({ url: 'http://example.com/events' })
|
|
777
777
|
})
|
|
778
778
|
|
|
779
779
|
lastMockES()._simulateOpen()
|
|
@@ -785,13 +785,13 @@ describe("useSSE", () => {
|
|
|
785
785
|
unmount()
|
|
786
786
|
})
|
|
787
787
|
|
|
788
|
-
it(
|
|
788
|
+
it('readyState reflects CLOSED on terminal error', () => {
|
|
789
789
|
const client = makeClient()
|
|
790
790
|
let sse: UseSSEResult<string> | null = null
|
|
791
791
|
|
|
792
792
|
const unmount = withProvider(client, () => {
|
|
793
793
|
sse = useSSE({
|
|
794
|
-
url:
|
|
794
|
+
url: 'http://example.com/events',
|
|
795
795
|
reconnect: false,
|
|
796
796
|
})
|
|
797
797
|
})
|
|
@@ -803,12 +803,12 @@ describe("useSSE", () => {
|
|
|
803
803
|
unmount()
|
|
804
804
|
})
|
|
805
805
|
|
|
806
|
-
it(
|
|
806
|
+
it('resets reconnect count on successful connection', async () => {
|
|
807
807
|
const client = makeClient()
|
|
808
808
|
|
|
809
809
|
const unmount = withProvider(client, () => {
|
|
810
810
|
useSSE({
|
|
811
|
-
url:
|
|
811
|
+
url: 'http://example.com/events',
|
|
812
812
|
reconnect: true,
|
|
813
813
|
reconnectDelay: 10,
|
|
814
814
|
maxReconnectAttempts: 2,
|
|
@@ -834,14 +834,14 @@ describe("useSSE", () => {
|
|
|
834
834
|
unmount()
|
|
835
835
|
})
|
|
836
836
|
|
|
837
|
-
it(
|
|
837
|
+
it('removes named event listeners on close', () => {
|
|
838
838
|
const client = makeClient()
|
|
839
839
|
let sse: UseSSEResult<string> | null = null
|
|
840
840
|
|
|
841
841
|
const unmount = withProvider(client, () => {
|
|
842
842
|
sse = useSSE({
|
|
843
|
-
url:
|
|
844
|
-
events: [
|
|
843
|
+
url: 'http://example.com/events',
|
|
844
|
+
events: ['order-update', 'user-update'],
|
|
845
845
|
})
|
|
846
846
|
})
|
|
847
847
|
|
|
@@ -849,8 +849,8 @@ describe("useSSE", () => {
|
|
|
849
849
|
lastMockES()._simulateOpen()
|
|
850
850
|
sse!.close()
|
|
851
851
|
|
|
852
|
-
expect(es.removeEventListener).toHaveBeenCalledWith(
|
|
853
|
-
expect(es.removeEventListener).toHaveBeenCalledWith(
|
|
852
|
+
expect(es.removeEventListener).toHaveBeenCalledWith('order-update', expect.any(Function))
|
|
853
|
+
expect(es.removeEventListener).toHaveBeenCalledWith('user-update', expect.any(Function))
|
|
854
854
|
|
|
855
855
|
unmount()
|
|
856
856
|
})
|