@@ -837,6 +837,232 @@ test.describe("single-fetch", () => {
837837 expect ( urls ) . toEqual ( [ ] ) ;
838838 } ) ;
839839
840+ test ( "supports call-site revalidation opt-out on submissions (w/o shouldRevalidate)" , async ( {
841+ page,
842+ } ) => {
843+ let fixture = await createFixture ( {
844+ files : {
845+ ...files ,
846+ "app/routes/action.tsx" : js `
847+ import { Form } from 'react-router';
848+
849+ let count = 0;
850+ export function loader() {
851+ return { count: ++count };
852+ }
853+
854+ export function action() {
855+ return { count: ++count };
856+ }
857+
858+ export default function Comp({ loaderData, actionData }) {
859+ return (
860+ <Form method="post" unstable_defaultShouldRevalidate={false}>
861+ <button type="submit" name="name" value="value">Submit</button>
862+ <p id="data">{loaderData.count}</p>
863+ {actionData ? <p id="action-data">{actionData.count}</p> : null}
864+ </Form>
865+ );
866+ }
867+ ` ,
868+ } ,
869+ } ) ;
870+
871+ let urls : string [ ] = [ ] ;
872+ page . on ( "request" , ( req ) => {
873+ if ( req . method ( ) === "GET" && req . url ( ) . includes ( ".data" ) ) {
874+ urls . push ( req . url ( ) ) ;
875+ }
876+ } ) ;
877+
878+ console . error = ( ) => { } ;
879+
880+ let appFixture = await createAppFixture ( fixture ) ;
881+ let app = new PlaywrightFixture ( appFixture , page ) ;
882+ await app . goto ( "/action" ) ;
883+ expect ( await app . getHtml ( "#data" ) ) . toContain ( "1" ) ;
884+ expect ( urls ) . toEqual ( [ ] ) ;
885+
886+ await page . click ( 'button[name="name"][value="value"]' ) ;
887+ await page . waitForSelector ( "#action-data" ) ;
888+ expect ( await app . getHtml ( "#action-data" ) ) . toContain ( "2" ) ;
889+ expect ( await app . getHtml ( "#data" ) ) . toContain ( "1" ) ;
890+ expect ( urls ) . toEqual ( [ ] ) ;
891+ } ) ;
892+
893+ test ( "supports call-site revalidation opt-in on 4xx/5xx action responses (w/o shouldRevalidate)" , async ( {
894+ page,
895+ } ) => {
896+ let fixture = await createFixture ( {
897+ files : {
898+ ...files ,
899+ "app/routes/action.tsx" : js `
900+ import { Form, Link, useNavigation, data } from 'react-router';
901+
902+ export async function action({ request }) {
903+ throw data("Thrown 500", { status: 500 });
904+ }
905+
906+ let count = 0;
907+ export function loader() {
908+ return { count: ++count };
909+ }
910+
911+ export default function Comp({ loaderData }) {
912+ let navigation = useNavigation();
913+ return (
914+ <Form method="post" unstable_defaultShouldRevalidate={true}>
915+ <button type="submit" name="throw" value="5xx">Throw 5xx</button>
916+ <p id="data">{loaderData.count}</p>
917+ {navigation.state === "idle" ? <p id="idle">idle</p> : null}
918+ </Form>
919+ );
920+ }
921+
922+ export function ErrorBoundary() {
923+ return <h1 id="error">Error</h1>
924+ }
925+ ` ,
926+ } ,
927+ } ) ;
928+
929+ let urls : string [ ] = [ ] ;
930+ page . on ( "request" , ( req ) => {
931+ if ( req . method ( ) === "GET" && req . url ( ) . includes ( ".data" ) ) {
932+ urls . push ( req . url ( ) ) ;
933+ }
934+ } ) ;
935+
936+ console . error = ( ) => { } ;
937+
938+ let appFixture = await createAppFixture ( fixture ) ;
939+ let app = new PlaywrightFixture ( appFixture , page ) ;
940+ await app . goto ( "/action" ) ;
941+ expect ( await app . getHtml ( "#data" ) ) . toContain ( "1" ) ;
942+ expect ( urls ) . toEqual ( [ ] ) ;
943+
944+ await page . click ( 'button[name="throw"][value="5xx"]' ) ;
945+ await page . waitForSelector ( "#error" ) ;
946+ expect ( urls ) . toEqual ( [ expect . stringMatching ( / \/ a c t i o n \. d a t a $ / ) ] ) ;
947+ } ) ;
948+
949+ test ( "supports call-site revalidation opt-out on submissions (w/ shouldRevalidate)" , async ( {
950+ page,
951+ } ) => {
952+ let fixture = await createFixture ( {
953+ files : {
954+ ...files ,
955+ "app/routes/action.tsx" : js `
956+ import { Form } from 'react-router';
957+
958+ let count = 0;
959+ export function loader() {
960+ return { count: ++count };
961+ }
962+
963+ export function action() {
964+ return { count: ++count };
965+ }
966+
967+ export function shouldRevalidate({ defaultShouldRevalidate }) {
968+ return defaultShouldRevalidate;
969+ }
970+
971+ export default function Comp({ loaderData, actionData }) {
972+ return (
973+ <Form method="post" unstable_defaultShouldRevalidate={false}>
974+ <button type="submit" name="name" value="value">Submit</button>
975+ <p id="data">{loaderData.count}</p>
976+ {actionData ? <p id="action-data">{actionData.count}</p> : null}
977+ </Form>
978+ );
979+ }
980+ ` ,
981+ } ,
982+ } ) ;
983+
984+ let urls : string [ ] = [ ] ;
985+ page . on ( "request" , ( req ) => {
986+ if ( req . method ( ) === "GET" && req . url ( ) . includes ( ".data" ) ) {
987+ urls . push ( req . url ( ) ) ;
988+ }
989+ } ) ;
990+
991+ console . error = ( ) => { } ;
992+
993+ let appFixture = await createAppFixture ( fixture ) ;
994+ let app = new PlaywrightFixture ( appFixture , page ) ;
995+ await app . goto ( "/action" ) ;
996+ expect ( await app . getHtml ( "#data" ) ) . toContain ( "1" ) ;
997+ expect ( urls ) . toEqual ( [ ] ) ;
998+
999+ await page . click ( 'button[name="name"][value="value"]' ) ;
1000+ await page . waitForSelector ( "#action-data" ) ;
1001+ expect ( await app . getHtml ( "#action-data" ) ) . toContain ( "2" ) ;
1002+ expect ( await app . getHtml ( "#data" ) ) . toContain ( "1" ) ;
1003+ expect ( urls ) . toEqual ( [ ] ) ;
1004+ } ) ;
1005+
1006+ test ( "supports call-site revalidation opt-in on 4xx/5xx action responses (w shouldRevalidate)" , async ( {
1007+ page,
1008+ } ) => {
1009+ let fixture = await createFixture ( {
1010+ files : {
1011+ ...files ,
1012+ "app/routes/action.tsx" : js `
1013+ import { Form, Link, useNavigation, data } from 'react-router';
1014+
1015+ export async function action({ request }) {
1016+ throw data("Thrown 500", { status: 500 });
1017+ }
1018+
1019+ let count = 0;
1020+ export function loader() {
1021+ return { count: ++count };
1022+ }
1023+
1024+ export function shouldRevalidate({ defaultShouldRevalidate }) {
1025+ return defaultShouldRevalidate;
1026+ }
1027+
1028+ export default function Comp({ loaderData }) {
1029+ let navigation = useNavigation();
1030+ return (
1031+ <Form method="post" unstable_defaultShouldRevalidate={true}>
1032+ <button type="submit" name="throw" value="5xx">Throw 5xx</button>
1033+ <p id="data">{loaderData.count}</p>
1034+ {navigation.state === "idle" ? <p id="idle">idle</p> : null}
1035+ </Form>
1036+ );
1037+ }
1038+
1039+ export function ErrorBoundary() {
1040+ return <h1 id="error">Error</h1>
1041+ }
1042+ ` ,
1043+ } ,
1044+ } ) ;
1045+
1046+ let urls : string [ ] = [ ] ;
1047+ page . on ( "request" , ( req ) => {
1048+ if ( req . method ( ) === "GET" && req . url ( ) . includes ( ".data" ) ) {
1049+ urls . push ( req . url ( ) ) ;
1050+ }
1051+ } ) ;
1052+
1053+ console . error = ( ) => { } ;
1054+
1055+ let appFixture = await createAppFixture ( fixture ) ;
1056+ let app = new PlaywrightFixture ( appFixture , page ) ;
1057+ await app . goto ( "/action" ) ;
1058+ expect ( await app . getHtml ( "#data" ) ) . toContain ( "1" ) ;
1059+ expect ( urls ) . toEqual ( [ ] ) ;
1060+
1061+ await page . click ( 'button[name="throw"][value="5xx"]' ) ;
1062+ await page . waitForSelector ( "#error" ) ;
1063+ expect ( urls ) . toEqual ( [ expect . stringMatching ( / \/ a c t i o n \. d a t a $ / ) ] ) ;
1064+ } ) ;
1065+
8401066 test ( "returns headers correctly for singular loader and action calls" , async ( ) => {
8411067 let fixture = await createFixture ( {
8421068 files : {
0 commit comments