@@ -271,64 +271,102 @@ describe("lib/fetch", () => {
271271 expect ( globalThis . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
272272 } ) ;
273273
274- it ( "does NOT follow redirect to different path" , async ( ) => {
274+ it ( "follows redirect to different path on same domain " , async ( ) => {
275275 const redirectRes = createResponse ( {
276276 status : 301 ,
277277 headers : new Headers ( { location : "https://example.com/other-path" } ) ,
278278 } ) ;
279+ const finalRes = createResponse ( { ok : true , status : 200 } ) ;
279280
280- globalThis . fetch = vi . fn (
281- async ( ) => redirectRes ,
282- ) as unknown as typeof fetch ;
281+ const mock = vi
282+ . fn ( )
283+ . mockResolvedValueOnce ( redirectRes )
284+ . mockResolvedValueOnce ( finalRes ) ;
285+ globalThis . fetch = mock as unknown as typeof fetch ;
283286
284287 const out = await fetchWithSelectiveRedirects (
285288 "https://example.com/" ,
286289 { } ,
287290 { timeoutMs : 50 } ,
288291 ) ;
289- expect ( out ) . toBe ( redirectRes ) ;
290- expect ( globalThis . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
292+ expect ( out ) . toBe ( finalRes ) ;
293+ expect ( mock ) . toHaveBeenCalledTimes ( 2 ) ;
291294 } ) ;
292295
293- it ( "does NOT follow redirect with query params" , async ( ) => {
296+ it ( "follows redirect with query params" , async ( ) => {
294297 const redirectRes = createResponse ( {
295298 status : 301 ,
296299 headers : new Headers ( {
297300 location : "https://example.com/?utm_source=test" ,
298301 } ) ,
299302 } ) ;
303+ const finalRes = createResponse ( { ok : true , status : 200 } ) ;
300304
301- globalThis . fetch = vi . fn (
302- async ( ) => redirectRes ,
303- ) as unknown as typeof fetch ;
305+ const mock = vi
306+ . fn ( )
307+ . mockResolvedValueOnce ( redirectRes )
308+ . mockResolvedValueOnce ( finalRes ) ;
309+ globalThis . fetch = mock as unknown as typeof fetch ;
304310
305311 const out = await fetchWithSelectiveRedirects (
306312 "https://example.com/" ,
307313 { } ,
308314 { timeoutMs : 50 } ,
309315 ) ;
310- expect ( out ) . toBe ( redirectRes ) ;
311- expect ( globalThis . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
316+ expect ( out ) . toBe ( finalRes ) ;
317+ expect ( mock ) . toHaveBeenCalledTimes ( 2 ) ;
312318 } ) ;
313319
314- it ( "handles relative redirect URLs " , async ( ) => {
320+ it ( "follows redirect with hash fragment " , async ( ) => {
315321 const redirectRes = createResponse ( {
316322 status : 301 ,
317- headers : new Headers ( { location : "/" } ) ,
323+ headers : new Headers ( {
324+ location : "https://example.com/#section" ,
325+ } ) ,
318326 } ) ;
327+ const finalRes = createResponse ( { ok : true , status : 200 } ) ;
319328
320- globalThis . fetch = vi . fn (
321- async ( ) => redirectRes ,
322- ) as unknown as typeof fetch ;
329+ const mock = vi
330+ . fn ( )
331+ . mockResolvedValueOnce ( redirectRes )
332+ . mockResolvedValueOnce ( finalRes ) ;
333+ globalThis . fetch = mock as unknown as typeof fetch ;
323334
324335 const out = await fetchWithSelectiveRedirects (
325- "https://www. example.com/path " ,
336+ "https://example.com/" ,
326337 { } ,
327338 { timeoutMs : 50 } ,
328339 ) ;
329- // Relative redirect to "/" changes the path (/path -> /), so should NOT be followed
330- expect ( out ) . toBe ( redirectRes ) ;
331- expect ( globalThis . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
340+ expect ( out ) . toBe ( finalRes ) ;
341+ expect ( mock ) . toHaveBeenCalledTimes ( 2 ) ;
342+ } ) ;
343+
344+ it ( "follows relative redirect URLs to different path" , async ( ) => {
345+ const redirectRes = createResponse ( {
346+ status : 301 ,
347+ headers : new Headers ( { location : "/main" } ) ,
348+ } ) ;
349+ const finalRes = createResponse ( { ok : true , status : 200 } ) ;
350+
351+ const mock = vi
352+ . fn ( )
353+ . mockResolvedValueOnce ( redirectRes )
354+ . mockResolvedValueOnce ( finalRes ) ;
355+ globalThis . fetch = mock as unknown as typeof fetch ;
356+
357+ const out = await fetchWithSelectiveRedirects (
358+ "https://www.example.com/" ,
359+ { } ,
360+ { timeoutMs : 50 } ,
361+ ) ;
362+ expect ( out ) . toBe ( finalRes ) ;
363+ expect ( mock ) . toHaveBeenCalledTimes ( 2 ) ;
364+ // Second call should use the absolute URL
365+ expect ( mock ) . toHaveBeenNthCalledWith (
366+ 2 ,
367+ "https://www.example.com/main" ,
368+ expect . objectContaining ( { redirect : "manual" } ) ,
369+ ) ;
332370 } ) ;
333371
334372 it ( "throws on too many redirects" , async ( ) => {
0 commit comments