Skip to content

Commit c10e842

Browse files
committed
Minor updates + tests
1 parent 67ee043 commit c10e842

File tree

8 files changed

+760
-66
lines changed

8 files changed

+760
-66
lines changed

packages/react-router/__tests__/dom/data-browser-router-test.tsx

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (

packages/react-router/__tests__/router/fetchers-test.ts

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,47 +2415,6 @@ describe("fetchers", () => {
24152415
});
24162416
});
24172417

2418-
it("skips all revalidation when callsite defaultShouldRevalidate is false", async () => {
2419-
let key = "key";
2420-
let actionKey = "actionKey";
2421-
let t = setup({
2422-
routes: TASK_ROUTES,
2423-
initialEntries: ["/"],
2424-
hydrationData: { loaderData: { root: "ROOT", index: "INDEX" } },
2425-
});
2426-
2427-
// preload a fetcher
2428-
let A = await t.fetch("/tasks/1", key);
2429-
await A.loaders.tasksId.resolve("TASKS ID");
2430-
expect(t.fetchers[key]).toMatchObject({
2431-
state: "idle",
2432-
data: "TASKS ID",
2433-
});
2434-
2435-
// submit action with shouldRevalidate=false
2436-
let C = await t.fetch("/tasks", actionKey, {
2437-
formMethod: "post",
2438-
formData: createFormData({}),
2439-
defaultShouldRevalidate: false,
2440-
});
2441-
2442-
expect(t.fetchers[actionKey]).toMatchObject({ state: "submitting" });
2443-
2444-
// resolve action — no loaders should trigger
2445-
await C.actions.tasks.resolve("TASKS ACTION");
2446-
2447-
// verify all fetchers idle
2448-
expect(t.fetchers[key]).toMatchObject({
2449-
state: "idle",
2450-
data: "TASKS ID",
2451-
});
2452-
2453-
expect(t.fetchers[actionKey]).toMatchObject({
2454-
state: "idle",
2455-
data: "TASKS ACTION",
2456-
});
2457-
});
2458-
24592418
it("does not revalidate fetchers initiated from removed routes", async () => {
24602419
let t = setup({
24612420
routes: TASK_ROUTES,

0 commit comments

Comments
 (0)