@@ -365,6 +365,11 @@ function setup({
365
365
let redirectNavigationId = ++ guid ;
366
366
activeLoaderType = "navigation" ;
367
367
activeLoaderNavigationId = redirectNavigationId ;
368
+ if ( status === 307 || status === 308 ) {
369
+ activeActionType = "navigation" ;
370
+ activeActionNavigationId = redirectNavigationId ;
371
+ }
372
+
368
373
let helpers = getNavigationHelpers ( href , redirectNavigationId ) ;
369
374
370
375
// Since a redirect kicks off and awaits a new navigation we can't shim
@@ -3966,6 +3971,86 @@ describe("a router", () => {
3966
3971
} ) ;
3967
3972
} ) ;
3968
3973
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
+
3969
4054
it ( "runs loaders above the boundary for 400 errors if binary data is attempted to be submitted using formMethod=GET" , async ( ) => {
3970
4055
let t = setup ( {
3971
4056
routes : [
@@ -5515,6 +5600,173 @@ describe("a router", () => {
5515
5600
window . location = oldLocation ;
5516
5601
}
5517
5602
} ) ;
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
+ } ) ;
5518
5770
} ) ;
5519
5771
5520
5772
describe ( "scroll restoration" , ( ) => {
@@ -6853,7 +7105,7 @@ describe("a router", () => {
6853
7105
405 ,
6854
7106
"Method Not Allowed" ,
6855
7107
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` ' +
6857
7109
'for route "root", so there is no way to handle the request.'
6858
7110
) ,
6859
7111
true
0 commit comments