@@ -2527,6 +2527,305 @@ function testDomRouter(
25272527 ` ) ;
25282528 } ) ;
25292529
2530+ describe ( "call-site revalidation opt-out" , ( ) => {
2531+ it ( "accepts defaultShouldRevalidate on <Link> navigations" , async ( ) => {
2532+ let loaderDefer = createDeferred ( ) ;
2533+
2534+ let router = createTestRouter (
2535+ createRoutesFromElements (
2536+ < Route
2537+ path = "/"
2538+ loader = { ( ) => loaderDefer . promise }
2539+ element = { < Home /> }
2540+ /> ,
2541+ ) ,
2542+ {
2543+ window : getWindow ( "/" ) ,
2544+ hydrationData : { loaderData : { "0" : null } } ,
2545+ } ,
2546+ ) ;
2547+ let { container } = render ( < RouterProvider router = { router } /> ) ;
2548+
2549+ function Home ( ) {
2550+ let data = useLoaderData ( ) as string ;
2551+ let location = useLocation ( ) ;
2552+ let navigation = useNavigation ( ) ;
2553+ return (
2554+ < div >
2555+ < Link to = "/?foo=bar" defaultShouldRevalidate = { false } >
2556+ Change Search Params
2557+ </ Link >
2558+ < div id = "output" >
2559+ < p > { location . pathname + location . search } </ p >
2560+ < p > { navigation . state } </ p >
2561+ < p > { data } </ p >
2562+ </ div >
2563+ </ div >
2564+ ) ;
2565+ }
2566+
2567+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2568+ . toMatchInlineSnapshot ( `
2569+ "<div
2570+ id="output"
2571+ >
2572+ <p>
2573+ /
2574+ </p>
2575+ <p>
2576+ idle
2577+ </p>
2578+ <p />
2579+ </div>"
2580+ ` ) ;
2581+
2582+ fireEvent . click ( screen . getByText ( "Change Search Params" ) ) ;
2583+ await waitFor ( ( ) => screen . getByText ( "idle" ) ) ;
2584+ loaderDefer . resolve ( "SHOULD NOT SEE ME" ) ;
2585+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2586+ . toMatchInlineSnapshot ( `
2587+ "<div
2588+ id="output"
2589+ >
2590+ <p>
2591+ /?foo=bar
2592+ </p>
2593+ <p>
2594+ idle
2595+ </p>
2596+ <p />
2597+ </div>"
2598+ ` ) ;
2599+ } ) ;
2600+
2601+ it ( "accepts defaultShouldRevalidate on setSearchParams navigations" , async ( ) => {
2602+ let loaderDefer = createDeferred ( ) ;
2603+
2604+ let router = createTestRouter (
2605+ createRoutesFromElements (
2606+ < Route
2607+ path = "/"
2608+ loader = { ( ) => loaderDefer . promise }
2609+ element = { < Home /> }
2610+ /> ,
2611+ ) ,
2612+ {
2613+ window : getWindow ( "/" ) ,
2614+ hydrationData : { loaderData : { "0" : null } } ,
2615+ } ,
2616+ ) ;
2617+ let { container } = render ( < RouterProvider router = { router } /> ) ;
2618+
2619+ function Home ( ) {
2620+ let data = useLoaderData ( ) as string ;
2621+ let location = useLocation ( ) ;
2622+ let navigation = useNavigation ( ) ;
2623+ let [ , setSearchParams ] = useSearchParams ( ) ;
2624+ return (
2625+ < div >
2626+ < button
2627+ onClick = { ( ) =>
2628+ setSearchParams ( new URLSearchParams ( [ [ "foo" , "bar" ] ] ) , {
2629+ defaultShouldRevalidate : false ,
2630+ } )
2631+ }
2632+ >
2633+ Change Search Params
2634+ </ button >
2635+ < div id = "output" >
2636+ < p > { location . pathname + location . search } </ p >
2637+ < p > { navigation . state } </ p >
2638+ < p > { data } </ p >
2639+ </ div >
2640+ </ div >
2641+ ) ;
2642+ }
2643+
2644+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2645+ . toMatchInlineSnapshot ( `
2646+ "<div
2647+ id="output"
2648+ >
2649+ <p>
2650+ /
2651+ </p>
2652+ <p>
2653+ idle
2654+ </p>
2655+ <p />
2656+ </div>"
2657+ ` ) ;
2658+
2659+ fireEvent . click ( screen . getByText ( "Change Search Params" ) ) ;
2660+ await waitFor ( ( ) => screen . getByText ( "idle" ) ) ;
2661+ loaderDefer . resolve ( "SHOULD NOT SEE ME" ) ;
2662+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2663+ . toMatchInlineSnapshot ( `
2664+ "<div
2665+ id="output"
2666+ >
2667+ <p>
2668+ /?foo=bar
2669+ </p>
2670+ <p>
2671+ idle
2672+ </p>
2673+ <p />
2674+ </div>"
2675+ ` ) ;
2676+ } ) ;
2677+
2678+ it ( "accepts defaultShouldRevalidate on <Form method=post> navigations" , async ( ) => {
2679+ let loaderDefer = createDeferred ( ) ;
2680+ let actionDefer = createDeferred ( ) ;
2681+
2682+ let router = createTestRouter (
2683+ createRoutesFromElements (
2684+ < Route
2685+ path = "/"
2686+ action = { ( ) => actionDefer . promise }
2687+ loader = { ( ) => loaderDefer . promise }
2688+ element = { < Home /> }
2689+ /> ,
2690+ ) ,
2691+ {
2692+ window : getWindow ( "/" ) ,
2693+ hydrationData : { loaderData : { "0" : null } } ,
2694+ } ,
2695+ ) ;
2696+ let { container } = render ( < RouterProvider router = { router } /> ) ;
2697+
2698+ function Home ( ) {
2699+ let data = useLoaderData ( ) as string ;
2700+ let actionData = useActionData ( ) as string | undefined ;
2701+ let navigation = useNavigation ( ) ;
2702+ return (
2703+ < div >
2704+ < Form method = "post" defaultShouldRevalidate = { false } >
2705+ < input name = "test" value = "value" />
2706+ < button type = "submit" > Submit Form</ button >
2707+ </ Form >
2708+ < div id = "output" >
2709+ < p > { navigation . state } </ p >
2710+ < p > { data } </ p >
2711+ < p > { actionData } </ p >
2712+ </ div >
2713+ </ div >
2714+ ) ;
2715+ }
2716+
2717+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2718+ . toMatchInlineSnapshot ( `
2719+ "<div
2720+ id="output"
2721+ >
2722+ <p>
2723+ idle
2724+ </p>
2725+ <p />
2726+ <p />
2727+ </div>"
2728+ ` ) ;
2729+
2730+ fireEvent . click ( screen . getByText ( "Submit Form" ) ) ;
2731+ await waitFor ( ( ) => screen . getByText ( "submitting" ) ) ;
2732+ actionDefer . resolve ( "Action Data" ) ;
2733+ await waitFor ( ( ) => screen . getByText ( "idle" ) ) ;
2734+ loaderDefer . resolve ( "SHOULD NOT SEE ME" ) ;
2735+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2736+ . toMatchInlineSnapshot ( `
2737+ "<div
2738+ id="output"
2739+ >
2740+ <p>
2741+ idle
2742+ </p>
2743+ <p />
2744+ <p>
2745+ Action Data
2746+ </p>
2747+ </div>"
2748+ ` ) ;
2749+ } ) ;
2750+
2751+ it ( "accepts defaultShouldRevalidate on fetcher.submit" , async ( ) => {
2752+ let loaderDefer = createDeferred ( ) ;
2753+ let actionDefer = createDeferred ( ) ;
2754+
2755+ let router = createTestRouter (
2756+ createRoutesFromElements (
2757+ < Route
2758+ path = "/"
2759+ action = { ( ) => actionDefer . promise }
2760+ loader = { ( ) => loaderDefer . promise }
2761+ element = { < Home /> }
2762+ /> ,
2763+ ) ,
2764+ {
2765+ window : getWindow ( "/" ) ,
2766+ hydrationData : { loaderData : { "0" : null } } ,
2767+ } ,
2768+ ) ;
2769+ let { container } = render ( < RouterProvider router = { router } /> ) ;
2770+
2771+ function Home ( ) {
2772+ let data = useLoaderData ( ) as string ;
2773+ let fetcher = useFetcher < string > ( ) ;
2774+ return (
2775+ < div >
2776+ < button
2777+ onClick = { ( ) =>
2778+ fetcher . submit (
2779+ { } ,
2780+ {
2781+ method : "post" ,
2782+ action : "/" ,
2783+ defaultShouldRevalidate : false ,
2784+ } ,
2785+ )
2786+ }
2787+ >
2788+ Submit Fetcher
2789+ </ button >
2790+ < div id = "output" >
2791+ < p > { `${ fetcher . state } :${ fetcher . data } ` } </ p >
2792+ < p > { data } </ p >
2793+ </ div >
2794+ </ div >
2795+ ) ;
2796+ }
2797+
2798+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2799+ . toMatchInlineSnapshot ( `
2800+ "<div
2801+ id="output"
2802+ >
2803+ <p>
2804+ idle:undefined
2805+ </p>
2806+ <p />
2807+ </div>"
2808+ ` ) ;
2809+
2810+ fireEvent . click ( screen . getByText ( "Submit Fetcher" ) ) ;
2811+ await waitFor ( ( ) => screen . getByText ( "submitting:undefined" ) ) ;
2812+ actionDefer . resolve ( "Action Data" ) ;
2813+ await waitFor ( ( ) => screen . getByText ( "idle:Action Data" ) ) ;
2814+ loaderDefer . resolve ( "SHOULD NOT SEE ME" ) ;
2815+ expect ( getHtml ( container . querySelector ( "#output" ) ! ) )
2816+ . toMatchInlineSnapshot ( `
2817+ "<div
2818+ id="output"
2819+ >
2820+ <p>
2821+ idle:Action Data
2822+ </p>
2823+ <p />
2824+ </div>"
2825+ ` ) ;
2826+ } ) ;
2827+ } ) ;
2828+
25302829 describe ( "<Form action>" , ( ) => {
25312830 function NoActionComponent ( ) {
25322831 return (
0 commit comments