@@ -38,12 +38,7 @@ describe('EventSource', () => {
3838 abort : jest . fn ( ) ,
3939 } ;
4040
41- const xhrSpy = jest . spyOn ( window , 'XMLHttpRequest' ) . mockImplementation ( ( ) => mockXhr as XMLHttpRequest ) ;
42- // Preserve static constants that EventSource reads from the constructor reference.
43- // @ts -ignore
44- xhrSpy . LOADING = 3 ;
45- // @ts -ignore
46- xhrSpy . DONE = 4 ;
41+ jest . spyOn ( window , 'XMLHttpRequest' ) . mockImplementation ( ( ) => mockXhr as XMLHttpRequest ) ;
4742
4843 eventSource = new EventSource < EventName > ( uri , { logger } ) ;
4944 eventSource . onclose = jest . fn ( ) ;
@@ -140,232 +135,4 @@ describe('EventSource', () => {
140135
141136 expect ( mockXhr . open ) . toHaveBeenLastCalledWith ( 'GET' , `${ uri } ?basis=initial` , true ) ;
142137 } ) ;
143-
144- test ( 'calls onopen with parsed response headers' , ( ) => {
145- const onopen = jest . fn ( ) ;
146- eventSource . onopen = onopen ;
147-
148- mockXhr . getAllResponseHeaders = jest . fn (
149- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
150- ) ;
151- mockXhr . responseText = '' ;
152-
153- jest . runAllTimers ( ) ;
154-
155- mockXhr . readyState = 4 ;
156- mockXhr . status = 200 ;
157- mockXhr . onreadystatechange ( ) ;
158-
159- expect ( onopen ) . toHaveBeenCalledTimes ( 1 ) ;
160- expect ( onopen ) . toHaveBeenCalledWith (
161- expect . objectContaining ( {
162- type : 'open' ,
163- headers : expect . objectContaining ( {
164- 'x-ld-fd-fallback' : 'true' ,
165- 'x-ld-fd-fallback-ttl' : '60' ,
166- } ) ,
167- } ) ,
168- ) ;
169- } ) ;
170-
171- test ( 'calls onopen with parsed response headers during streaming (onprogress, pre-DONE)' , ( ) => {
172- const onopen = jest . fn ( ) ;
173- eventSource . onopen = onopen ;
174-
175- mockXhr . getAllResponseHeaders = jest . fn (
176- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
177- ) ;
178- mockXhr . responseText = '' ;
179-
180- jest . runAllTimers ( ) ;
181-
182- // Simulate a chunk arriving while the connection is still LOADING (readyState 3).
183- // This is the real runtime path for a live stream: DONE (readyState 4) only
184- // fires once the connection closes, so gating 'open' on DONE would mean the
185- // event, and the fallback headers it carries, never fires during normal streaming.
186- mockXhr . readyState = 3 ;
187- mockXhr . status = 200 ;
188- mockXhr . onprogress ( ) ;
189-
190- expect ( onopen ) . toHaveBeenCalledTimes ( 1 ) ;
191- expect ( onopen ) . toHaveBeenCalledWith (
192- expect . objectContaining ( {
193- type : 'open' ,
194- headers : expect . objectContaining ( {
195- 'x-ld-fd-fallback' : 'true' ,
196- 'x-ld-fd-fallback-ttl' : '60' ,
197- } ) ,
198- } ) ,
199- ) ;
200- } ) ;
201-
202- test ( 'dispatches open exactly once when onprogress precedes an onreadystatechange DONE' , ( ) => {
203- const onopen = jest . fn ( ) ;
204- eventSource . onopen = onopen ;
205-
206- mockXhr . getAllResponseHeaders = jest . fn (
207- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
208- ) ;
209- mockXhr . responseText = '' ;
210-
211- jest . runAllTimers ( ) ;
212-
213- // onprogress observes the connection first (LOADING) and transitions to OPEN.
214- mockXhr . readyState = 3 ;
215- mockXhr . status = 200 ;
216- mockXhr . onprogress ( ) ;
217-
218- // A later onreadystatechange at DONE must not re-dispatch open: the status is
219- // already OPEN (no longer CONNECTING), so its open-dispatch guard is a no-op.
220- mockXhr . readyState = 4 ;
221- mockXhr . status = 200 ;
222- mockXhr . onreadystatechange ( ) ;
223-
224- expect ( onopen ) . toHaveBeenCalledTimes ( 1 ) ;
225- } ) ;
226-
227- test ( 'calls retryAndHandleError with parsed response headers on an error response' , ( ) => {
228- const retryAndHandleError = jest . fn ( ( ) => false ) ;
229-
230- mockXhr . getAllResponseHeaders = jest . fn (
231- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
232- ) ;
233- mockXhr . responseText = 'error body' ;
234-
235- const es = new EventSource < EventName > ( uri , { logger, retryAndHandleError } ) ;
236- es . onerror = jest . fn ( ) ;
237-
238- jest . runAllTimers ( ) ;
239-
240- mockXhr . readyState = 4 ;
241- mockXhr . status = 500 ;
242- mockXhr . onreadystatechange ( ) ;
243-
244- expect ( retryAndHandleError ) . toHaveBeenCalledTimes ( 1 ) ;
245- expect ( retryAndHandleError ) . toHaveBeenCalledWith (
246- expect . objectContaining ( {
247- status : 500 ,
248- message : 'error body' ,
249- headers : expect . objectContaining ( {
250- 'x-ld-fd-fallback' : 'true' ,
251- 'x-ld-fd-fallback-ttl' : '60' ,
252- } ) ,
253- } ) ,
254- ) ;
255- } ) ;
256-
257- test ( 'dispatches error with status and headers from onprogress mid-stream' , ( ) => {
258- const onerror = jest . fn ( ) ;
259-
260- mockXhr . getAllResponseHeaders = jest . fn (
261- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
262- ) ;
263- mockXhr . responseText = 'error body' ;
264-
265- eventSource . onerror = onerror ;
266-
267- jest . runAllTimers ( ) ;
268-
269- // readyState 3 (LOADING), not DONE, so this exercises onprogress's
270- // error branch rather than onreadystatechange's.
271- mockXhr . readyState = 3 ;
272- mockXhr . status = 500 ;
273- mockXhr . onprogress ( ) ;
274-
275- expect ( onerror ) . toHaveBeenCalledWith (
276- expect . objectContaining ( {
277- status : 500 ,
278- headers : expect . objectContaining ( {
279- 'x-ld-fd-fallback' : 'true' ,
280- 'x-ld-fd-fallback-ttl' : '60' ,
281- } ) ,
282- } ) ,
283- ) ;
284- } ) ;
285-
286- test ( 'dispatches error with status and headers from onreadystatechange before retrying' , ( ) => {
287- const onerror = jest . fn ( ) ;
288-
289- mockXhr . getAllResponseHeaders = jest . fn (
290- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
291- ) ;
292- mockXhr . responseText = 'error body' ;
293-
294- eventSource . onerror = onerror ;
295-
296- jest . runAllTimers ( ) ;
297-
298- mockXhr . readyState = 4 ;
299- mockXhr . status = 500 ;
300- mockXhr . onreadystatechange ( ) ;
301-
302- expect ( onerror ) . toHaveBeenCalledWith (
303- expect . objectContaining ( {
304- status : 500 ,
305- headers : expect . objectContaining ( {
306- 'x-ld-fd-fallback' : 'true' ,
307- 'x-ld-fd-fallback-ttl' : '60' ,
308- } ) ,
309- } ) ,
310- ) ;
311- } ) ;
312-
313- test ( 'invokes retryAndHandleError from onprogress when the connection never reaches DONE' , ( ) => {
314- const retryAndHandleError = jest . fn ( ( ) => false ) ;
315-
316- mockXhr . getAllResponseHeaders = jest . fn (
317- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
318- ) ;
319- mockXhr . responseText = 'error body' ;
320-
321- const es = new EventSource < EventName > ( uri , { logger, retryAndHandleError } ) ;
322- es . onerror = jest . fn ( ) ;
323-
324- jest . runAllTimers ( ) ;
325-
326- // readyState never advances to DONE for this attempt, so
327- // onreadystatechange never fires; onprogress must drive the retry
328- // on its own.
329- mockXhr . readyState = 3 ;
330- mockXhr . status = 500 ;
331- mockXhr . onprogress ( ) ;
332-
333- expect ( retryAndHandleError ) . toHaveBeenCalledTimes ( 1 ) ;
334- expect ( retryAndHandleError ) . toHaveBeenCalledWith (
335- expect . objectContaining ( {
336- status : 500 ,
337- message : 'error body' ,
338- headers : expect . objectContaining ( {
339- 'x-ld-fd-fallback' : 'true' ,
340- 'x-ld-fd-fallback-ttl' : '60' ,
341- } ) ,
342- } ) ,
343- ) ;
344- } ) ;
345-
346- test ( 'does not invoke retryAndHandleError twice when onprogress and onreadystatechange observe the same failed attempt' , ( ) => {
347- const retryAndHandleError = jest . fn ( ( ) => false ) ;
348-
349- mockXhr . getAllResponseHeaders = jest . fn (
350- ( ) => 'X-Ld-Fd-Fallback: true\r\nX-Ld-Fd-Fallback-Ttl: 60\r\nContent-Type: text/event-stream' ,
351- ) ;
352- mockXhr . responseText = 'error body' ;
353-
354- const es = new EventSource < EventName > ( uri , { logger, retryAndHandleError } ) ;
355- es . onerror = jest . fn ( ) ;
356-
357- jest . runAllTimers ( ) ;
358-
359- // onprogress observes the error first (LOADING), then the same response
360- // reaches DONE via onreadystatechange. retryAndHandleError must fire
361- // exactly once for this attempt despite both handlers seeing the error.
362- mockXhr . readyState = 3 ;
363- mockXhr . status = 500 ;
364- mockXhr . onprogress ( ) ;
365-
366- mockXhr . readyState = 4 ;
367- mockXhr . onreadystatechange ( ) ;
368-
369- expect ( retryAndHandleError ) . toHaveBeenCalledTimes ( 1 ) ;
370- } ) ;
371138} ) ;
0 commit comments