@@ -365,6 +365,11 @@ function setup({
365365 let redirectNavigationId = ++ guid ;
366366 activeLoaderType = "navigation" ;
367367 activeLoaderNavigationId = redirectNavigationId ;
368+ if ( status === 307 || status === 308 ) {
369+ activeActionType = "navigation" ;
370+ activeActionNavigationId = redirectNavigationId ;
371+ }
372+
368373 let helpers = getNavigationHelpers ( href , redirectNavigationId ) ;
369374
370375 // Since a redirect kicks off and awaits a new navigation we can't shim
@@ -3966,6 +3971,86 @@ describe("a router", () => {
39663971 } ) ;
39673972 } ) ;
39683973
3974+ it ( "returns a 405 error if attempting to submit with method=HEAD" , async ( ) => {
3975+ let t = setup ( {
3976+ routes : TASK_ROUTES ,
3977+ initialEntries : [ "/" ] ,
3978+ hydrationData : {
3979+ loaderData : {
3980+ root : "ROOT DATA" ,
3981+ index : "INDEX DATA" ,
3982+ } ,
3983+ } ,
3984+ } ) ;
3985+
3986+ let formData = new FormData ( ) ;
3987+ formData . append (
3988+ "blob" ,
3989+ new Blob ( [ "<h1>Some html file contents</h1>" ] , {
3990+ type : "text/html" ,
3991+ } )
3992+ ) ;
3993+
3994+ await t . navigate ( "/tasks" , {
3995+ // @ts -expect-error
3996+ formMethod : "head" ,
3997+ formData : formData ,
3998+ } ) ;
3999+ expect ( t . router . state . navigation . state ) . toBe ( "idle" ) ;
4000+ expect ( t . router . state . location ) . toMatchObject ( {
4001+ pathname : "/tasks" ,
4002+ search : "" ,
4003+ } ) ;
4004+ expect ( t . router . state . errors ) . toEqual ( {
4005+ tasks : new ErrorResponse (
4006+ 405 ,
4007+ "Method Not Allowed" ,
4008+ new Error ( 'Invalid request method "HEAD"' ) ,
4009+ true
4010+ ) ,
4011+ } ) ;
4012+ } ) ;
4013+
4014+ it ( "returns a 405 error if attempting to submit with method=OPTIONS" , async ( ) => {
4015+ let t = setup ( {
4016+ routes : TASK_ROUTES ,
4017+ initialEntries : [ "/" ] ,
4018+ hydrationData : {
4019+ loaderData : {
4020+ root : "ROOT DATA" ,
4021+ index : "INDEX DATA" ,
4022+ } ,
4023+ } ,
4024+ } ) ;
4025+
4026+ let formData = new FormData ( ) ;
4027+ formData . append (
4028+ "blob" ,
4029+ new Blob ( [ "<h1>Some html file contents</h1>" ] , {
4030+ type : "text/html" ,
4031+ } )
4032+ ) ;
4033+
4034+ await t . navigate ( "/tasks" , {
4035+ // @ts -expect-error
4036+ formMethod : "options" ,
4037+ formData : formData ,
4038+ } ) ;
4039+ expect ( t . router . state . navigation . state ) . toBe ( "idle" ) ;
4040+ expect ( t . router . state . location ) . toMatchObject ( {
4041+ pathname : "/tasks" ,
4042+ search : "" ,
4043+ } ) ;
4044+ expect ( t . router . state . errors ) . toEqual ( {
4045+ tasks : new ErrorResponse (
4046+ 405 ,
4047+ "Method Not Allowed" ,
4048+ new Error ( 'Invalid request method "OPTIONS"' ) ,
4049+ true
4050+ ) ,
4051+ } ) ;
4052+ } ) ;
4053+
39694054 it ( "runs loaders above the boundary for 400 errors if binary data is attempted to be submitted using formMethod=GET" , async ( ) => {
39704055 let t = setup ( {
39714056 routes : [
@@ -5515,6 +5600,173 @@ describe("a router", () => {
55155600 window . location = oldLocation ;
55165601 }
55175602 } ) ;
5603+
5604+ describe ( "redirect status code handling" , ( ) => {
5605+ it ( "should not treat 300 as a redirect" , async ( ) => {
5606+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5607+
5608+ let A = await t . navigate ( "/parent" ) ;
5609+ await A . loaders . parent . redirectReturn ( "/idk" , 300 ) ;
5610+ expect ( t . router . state ) . toMatchObject ( {
5611+ loaderData : { } ,
5612+ location : {
5613+ pathname : "/parent" ,
5614+ } ,
5615+ navigation : {
5616+ state : "idle" ,
5617+ } ,
5618+ } ) ;
5619+ } ) ;
5620+
5621+ it ( "should not preserve the method on 301 redirects" , async ( ) => {
5622+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5623+
5624+ let A = await t . navigate ( "/parent/child" , {
5625+ formMethod : "post" ,
5626+ formData : createFormData ( { key : "value" } ) ,
5627+ } ) ;
5628+ // Triggers a GET redirect
5629+ let B = await A . actions . child . redirectReturn ( "/parent" , 301 ) ;
5630+ await B . loaders . parent . resolve ( "PARENT" ) ;
5631+ expect ( t . router . state ) . toMatchObject ( {
5632+ loaderData : {
5633+ parent : "PARENT" ,
5634+ } ,
5635+ location : {
5636+ pathname : "/parent" ,
5637+ } ,
5638+ navigation : {
5639+ state : "idle" ,
5640+ } ,
5641+ } ) ;
5642+ } ) ;
5643+
5644+ it ( "should not preserve the method on 302 redirects" , async ( ) => {
5645+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5646+
5647+ let A = await t . navigate ( "/parent/child" , {
5648+ formMethod : "post" ,
5649+ formData : createFormData ( { key : "value" } ) ,
5650+ } ) ;
5651+ // Triggers a GET redirect
5652+ let B = await A . actions . child . redirectReturn ( "/parent" , 302 ) ;
5653+ await B . loaders . parent . resolve ( "PARENT" ) ;
5654+ expect ( t . router . state ) . toMatchObject ( {
5655+ loaderData : {
5656+ parent : "PARENT" ,
5657+ } ,
5658+ location : {
5659+ pathname : "/parent" ,
5660+ } ,
5661+ navigation : {
5662+ state : "idle" ,
5663+ } ,
5664+ } ) ;
5665+ } ) ;
5666+
5667+ it ( "should not preserve the method on 303 redirects" , async ( ) => {
5668+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5669+
5670+ let A = await t . navigate ( "/parent/child" , {
5671+ formMethod : "post" ,
5672+ formData : createFormData ( { key : "value" } ) ,
5673+ } ) ;
5674+ // Triggers a GET redirect
5675+ let B = await A . actions . child . redirectReturn ( "/parent" , 303 ) ;
5676+ await B . loaders . parent . resolve ( "PARENT" ) ;
5677+ expect ( t . router . state ) . toMatchObject ( {
5678+ loaderData : {
5679+ parent : "PARENT" ,
5680+ } ,
5681+ location : {
5682+ pathname : "/parent" ,
5683+ } ,
5684+ navigation : {
5685+ state : "idle" ,
5686+ } ,
5687+ } ) ;
5688+ } ) ;
5689+
5690+ it ( "should not treat 304 as a redirect" , async ( ) => {
5691+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5692+
5693+ let A = await t . navigate ( "/parent" ) ;
5694+ await A . loaders . parent . resolve ( new Response ( null , { status : 304 } ) ) ;
5695+ expect ( t . router . state ) . toMatchObject ( {
5696+ loaderData : { } ,
5697+ location : {
5698+ pathname : "/parent" ,
5699+ } ,
5700+ navigation : {
5701+ state : "idle" ,
5702+ } ,
5703+ } ) ;
5704+ } ) ;
5705+
5706+ it ( "should preserve the method on 307 redirects" , async ( ) => {
5707+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5708+
5709+ let A = await t . navigate ( "/parent/child" , {
5710+ formMethod : "post" ,
5711+ formData : createFormData ( { key : "value" } ) ,
5712+ } ) ;
5713+ // Triggers a POST redirect
5714+ let B = await A . actions . child . redirectReturn ( "/parent" , 307 ) ;
5715+ await B . actions . parent . resolve ( "PARENT ACTION" ) ;
5716+ await B . loaders . parent . resolve ( "PARENT" ) ;
5717+ expect ( t . router . state ) . toMatchObject ( {
5718+ actionData : {
5719+ parent : "PARENT ACTION" ,
5720+ } ,
5721+ loaderData : {
5722+ parent : "PARENT" ,
5723+ } ,
5724+ location : {
5725+ pathname : "/parent" ,
5726+ } ,
5727+ navigation : {
5728+ state : "idle" ,
5729+ } ,
5730+ } ) ;
5731+
5732+ let request = B . actions . parent . stub . mock . calls [ 0 ] [ 0 ] . request ;
5733+ expect ( request . method ) . toBe ( "POST" ) ;
5734+ let fd = await request . formData ( ) ;
5735+ expect ( Array . from ( fd . entries ( ) ) ) . toEqual ( [ [ "key" , "value" ] ] ) ;
5736+ } ) ;
5737+
5738+ it ( "should preserve the method on 308 redirects" , async ( ) => {
5739+ let t = setup ( { routes : REDIRECT_ROUTES } ) ;
5740+
5741+ let A = await t . navigate ( "/parent/child" , {
5742+ formMethod : "post" ,
5743+ formData : createFormData ( { key : "value" } ) ,
5744+ } ) ;
5745+ // Triggers a POST redirect
5746+ let B = await A . actions . child . redirectReturn ( "/parent" , 308 ) ;
5747+ await B . actions . parent . resolve ( "PARENT ACTION" ) ;
5748+ await B . loaders . parent . resolve ( "PARENT" ) ;
5749+ expect ( t . router . state ) . toMatchObject ( {
5750+ actionData : {
5751+ parent : "PARENT ACTION" ,
5752+ } ,
5753+ loaderData : {
5754+ parent : "PARENT" ,
5755+ } ,
5756+ location : {
5757+ pathname : "/parent" ,
5758+ } ,
5759+ navigation : {
5760+ state : "idle" ,
5761+ } ,
5762+ } ) ;
5763+
5764+ let request = B . actions . parent . stub . mock . calls [ 0 ] [ 0 ] . request ;
5765+ expect ( request . method ) . toBe ( "POST" ) ;
5766+ let fd = await request . formData ( ) ;
5767+ expect ( Array . from ( fd . entries ( ) ) ) . toEqual ( [ [ "key" , "value" ] ] ) ;
5768+ } ) ;
5769+ } ) ;
55185770 } ) ;
55195771
55205772 describe ( "scroll restoration" , ( ) => {
@@ -6853,7 +7105,7 @@ describe("a router", () => {
68537105 405 ,
68547106 "Method Not Allowed" ,
68557107 new Error (
6856- 'You made a post request to "/" but did not provide an `action` ' +
7108+ 'You made a POST request to "/" but did not provide an `action` ' +
68577109 'for route "root", so there is no way to handle the request.'
68587110 ) ,
68597111 true
0 commit comments