@@ -435,6 +435,17 @@ implementations.forEach((implementation) => {
435
435
}
436
436
]
437
437
},
438
+ {
439
+ id: "throw-external-redirect-server-action",
440
+ path: "throw-external-redirect-server-action",
441
+ children: [
442
+ {
443
+ id: "throw-external-redirect-server-action.home",
444
+ index: true,
445
+ lazy: () => import("./routes/throw-external-redirect-server-action/home"),
446
+ }
447
+ ]
448
+ },
438
449
{
439
450
id: "side-effect-redirect-server-action",
440
451
path: "side-effect-redirect-server-action",
@@ -446,6 +457,17 @@ implementations.forEach((implementation) => {
446
457
}
447
458
]
448
459
},
460
+ {
461
+ id: "side-effect-external-redirect-server-action",
462
+ path: "side-effect-external-redirect-server-action",
463
+ children: [
464
+ {
465
+ id: "side-effect-external-redirect-server-action.home",
466
+ index: true,
467
+ lazy: () => import("./routes/side-effect-external-redirect-server-action/home"),
468
+ }
469
+ ]
470
+ },
449
471
{
450
472
id: "server-function-reference",
451
473
path: "server-function-reference",
@@ -986,6 +1008,82 @@ implementations.forEach((implementation) => {
986
1008
);
987
1009
}
988
1010
` ,
1011
+ "src/routes/throw-external-redirect-server-action/home.actions.ts" : js `
1012
+ "use server";
1013
+ import { redirect } from "react-router";
1014
+
1015
+ export async function redirectAction(formData: FormData) {
1016
+ // Throw a redirect to an external URL
1017
+ throw redirect("https://example.com/");
1018
+ }
1019
+ ` ,
1020
+ "src/routes/throw-external-redirect-server-action/home.client.tsx" : js `
1021
+ "use client";
1022
+
1023
+ import { useState } from "react";
1024
+
1025
+ export function Counter() {
1026
+ const [count, setCount] = useState(0);
1027
+ return <button type="button" onClick={() => setCount(c => c + 1)} data-count>Count: {count}</button>;
1028
+ }
1029
+ ` ,
1030
+ "src/routes/throw-external-redirect-server-action/home.tsx" : js `
1031
+ import { redirectAction } from "./home.actions";
1032
+ import { Counter } from "./home.client";
1033
+
1034
+ export default function HomeRoute(props) {
1035
+ return (
1036
+ <div>
1037
+ <form action={redirectAction}>
1038
+ <button type="submit" data-submit>
1039
+ Redirect via Server Function
1040
+ </button>
1041
+ </form>
1042
+ <Counter />
1043
+ </div>
1044
+ );
1045
+ }
1046
+ ` ,
1047
+ "src/routes/side-effect-external-redirect-server-action/home.actions.ts" : js `
1048
+ "use server";
1049
+ import { redirect } from "react-router";
1050
+
1051
+ export async function redirectAction() {
1052
+ // Perform a side-effect redirect to an external URL
1053
+ redirect("https://example.com/", { headers: { "x-test": "test" } });
1054
+ return "redirected";
1055
+ }
1056
+ ` ,
1057
+ "src/routes/side-effect-external-redirect-server-action/home.client.tsx" : js `
1058
+ "use client";
1059
+ import { useState } from "react";
1060
+
1061
+ export function Counter() {
1062
+ const [count, setCount] = useState(0);
1063
+ return <button type="button" onClick={() => setCount(c => c + 1)} data-count>Count: {count}</button>;
1064
+ }
1065
+ ` ,
1066
+ "src/routes/side-effect-external-redirect-server-action/home.tsx" : js `
1067
+ "use client";
1068
+ import {useActionState} from "react";
1069
+ import { redirectAction } from "./home.actions";
1070
+ import { Counter } from "./home.client";
1071
+
1072
+ export default function HomeRoute(props) {
1073
+ const [state, action] = useActionState(redirectAction, null);
1074
+ return (
1075
+ <div>
1076
+ <form action={action}>
1077
+ <button type="submit" data-submit>
1078
+ Redirect via Server Function
1079
+ </button>
1080
+ </form>
1081
+ {state && <div data-testid="state">{state}</div>}
1082
+ <Counter />
1083
+ </div>
1084
+ );
1085
+ }
1086
+ ` ,
989
1087
990
1088
"src/routes/server-function-reference/home.actions.ts" : js `
991
1089
"use server";
@@ -1736,6 +1834,33 @@ implementations.forEach((implementation) => {
1736
1834
validateRSCHtml ( await page . content ( ) ) ;
1737
1835
} ) ;
1738
1836
1837
+ test ( "Supports React Server Functions thrown external redirects" , async ( {
1838
+ page,
1839
+ } ) => {
1840
+ // Test is expected to fail currently — skip running it
1841
+ // test.skip(true, "Known failing test for external redirect behavior");
1842
+
1843
+ await page . goto (
1844
+ `http://localhost:${ port } /throw-external-redirect-server-action/` ,
1845
+ ) ;
1846
+
1847
+ // Verify initial server render
1848
+ await page . waitForSelector ( "[data-count]" ) ;
1849
+ expect ( await page . locator ( "[data-count]" ) . textContent ( ) ) . toBe (
1850
+ "Count: 0" ,
1851
+ ) ;
1852
+ await page . click ( "[data-count]" ) ;
1853
+ expect ( await page . locator ( "[data-count]" ) . textContent ( ) ) . toBe (
1854
+ "Count: 1" ,
1855
+ ) ;
1856
+
1857
+ // Submit the form to trigger server function redirect to external URL
1858
+ await page . click ( "[data-submit]" ) ;
1859
+
1860
+ // We expect the browser to navigate to the external site (example.com)
1861
+ await expect ( page ) . toHaveURL ( `https://example.com/` ) ;
1862
+ } ) ;
1863
+
1739
1864
test ( "Supports React Server Functions side-effect redirects" , async ( {
1740
1865
page,
1741
1866
} ) => {
@@ -1789,6 +1914,46 @@ implementations.forEach((implementation) => {
1789
1914
validateRSCHtml ( await page . content ( ) ) ;
1790
1915
} ) ;
1791
1916
1917
+ test ( "Supports React Server Functions side-effect external redirects" , async ( {
1918
+ page,
1919
+ } ) => {
1920
+ // Test is expected to fail currently — skip running it
1921
+ test . skip ( implementation . name === "parcel" , "Not working in parcel?" ) ;
1922
+
1923
+ await page . goto (
1924
+ `http://localhost:${ port } /side-effect-external-redirect-server-action` ,
1925
+ ) ;
1926
+
1927
+ // Verify initial server render
1928
+ await page . waitForSelector ( "[data-count]" ) ;
1929
+ expect ( await page . locator ( "[data-count]" ) . textContent ( ) ) . toBe (
1930
+ "Count: 0" ,
1931
+ ) ;
1932
+ await page . click ( "[data-count]" ) ;
1933
+ expect ( await page . locator ( "[data-count]" ) . textContent ( ) ) . toBe (
1934
+ "Count: 1" ,
1935
+ ) ;
1936
+
1937
+ const responseHeadersPromise = new Promise < Record < string , string > > (
1938
+ ( resolve ) => {
1939
+ page . addListener ( "response" , ( response ) => {
1940
+ if ( response . request ( ) . method ( ) === "POST" ) {
1941
+ resolve ( response . headers ( ) ) ;
1942
+ }
1943
+ } ) ;
1944
+ } ,
1945
+ ) ;
1946
+
1947
+ // Submit the form to trigger server function redirect to external URL
1948
+ await page . click ( "[data-submit]" ) ;
1949
+
1950
+ // We expect the browser to navigate to the external site (example.com)
1951
+ await expect ( page ) . toHaveURL ( `https://example.com/` ) ;
1952
+
1953
+ // Optionally assert that the server sent the header
1954
+ expect ( ( await responseHeadersPromise ) [ "x-test" ] ) . toBe ( "test" ) ;
1955
+ } ) ;
1956
+
1792
1957
test ( "Supports React Server Function References" , async ( { page } ) => {
1793
1958
await page . goto ( `http://localhost:${ port } /server-function-reference` ) ;
1794
1959
0 commit comments