Skip to content

Commit

Permalink
Add unit tests for RouterProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Nov 16, 2023
1 parent 008cd9e commit 847902d
Show file tree
Hide file tree
Showing 3 changed files with 373 additions and 0 deletions.
355 changes: 355 additions & 0 deletions packages/react-router-dom/__tests__/data-browser-router-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7420,6 +7420,361 @@ function testDomRouter(
expect(spy).toHaveBeenCalledTimes(2);
});
});

// TODO: Probably want these running against RouterProvider in react-router too?
// Look into extracting the setState stuff and sharing the subscriber,
// layout effect, navigator, render stuff
describe("partial hydration", () => {
it("does not handle partial hydration by default", async () => {
let router = createTestRouter(
[
{
id: "root",
path: "/",
loader: () => "ROOT",
Component() {
let data = useLoaderData() as string;
return (
<div>
<h1>{`Home - ${data}`}</h1>
<Outlet />
</div>
);
},
children: [
{
id: "index",
index: true,
loader: () => "INDEX",
Fallback: () => <p>Should not see me</p>,
Component() {
let data = useLoaderData() as string;
return <h2>{`Index - ${data}`}</h2>;
},
},
],
},
],
{
window: getWindow("/"),
hydrationData: {
loaderData: {
root: "HYDRATED ROOT",
},
},
}
);
let { container } = render(<RouterProvider router={router} />);

expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h1>
Home - HYDRATED ROOT
</h1>
<h2>
Index - undefined
</h2>
</div>
</div>"
`);
});

it("with v7_partialHydration, supports partial hydration w/leaf fallback", async () => {
let dfd = createDeferred();
let router = createTestRouter(
[
{
id: "root",
path: "/",
loader: () => "ROOT",
Component() {
let data = useLoaderData() as string;
return (
<div>
<h1>{`Home - ${data}`}</h1>
<Outlet />
</div>
);
},
children: [
{
id: "index",
index: true,
loader: () => dfd.promise,
Fallback: () => <p>Index Loading...</p>,
Component() {
let data = useLoaderData() as string;
return <h2>{`Index - ${data}`}</h2>;
},
},
],
},
],
{
window: getWindow("/"),
hydrationData: {
loaderData: {
root: "HYDRATED ROOT",
},
},
future: {
v7_partialHydration: true,
},
}
);
let { container } = render(<RouterProvider router={router} />);

expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h1>
Home - HYDRATED ROOT
</h1>
<p>
Index Loading...
</p>
</div>
</div>"
`);

dfd.resolve("INDEX DATA");
await waitFor(() => screen.getByText(/INDEX DATA/));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h1>
Home - HYDRATED ROOT
</h1>
<h2>
Index - INDEX DATA
</h2>
</div>
</div>"
`);
});

it("with v7_partialHydration, supports partial hydration w/root fallback", async () => {
let dfd = createDeferred();
let router = createTestRouter(
[
{
id: "root",
path: "/",
loader: () => "ROOT",
Fallback: () => <p>Root Loading...</p>,
Component() {
let data = useLoaderData() as string;
return (
<div>
<h1>{`Home - ${data}`}</h1>
<Outlet />
</div>
);
},
children: [
{
id: "index",
index: true,
loader: () => dfd.promise,
Component() {
let data = useLoaderData() as string;
return <h2>{`Index - ${data}`}</h2>;
},
},
],
},
],
{
window: getWindow("/"),
hydrationData: {
loaderData: {
root: "HYDRATED ROOT",
},
},
future: {
v7_partialHydration: true,
},
}
);
let { container } = render(<RouterProvider router={router} />);

expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<p>
Root Loading...
</p>
</div>"
`);

dfd.resolve("INDEX DATA");
await waitFor(() => screen.getByText(/INDEX DATA/));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h1>
Home - HYDRATED ROOT
</h1>
<h2>
Index - INDEX DATA
</h2>
</div>
</div>"
`);
});

it("with v7_partialHydration, supports partial hydration w/no fallback", async () => {
let dfd = createDeferred();
let consoleSpy = jest.spyOn(console, "warn");
let router = createTestRouter(
[
{
id: "root",
path: "/",
loader: () => "ROOT",
Component() {
let data = useLoaderData() as string;
return (
<div>
<h1>{`Home - ${data}`}</h1>
<Outlet />
</div>
);
},
children: [
{
id: "index",
index: true,
loader: () => dfd.promise,
Component() {
let data = useLoaderData() as string;
return <h2>{`Index - ${data}`}</h2>;
},
},
],
},
],
{
window: getWindow("/"),
hydrationData: {
loaderData: {
root: "HYDRATED ROOT",
},
},
future: {
v7_partialHydration: true,
},
}
);
let { container } = render(<RouterProvider router={router} />);

expect(getHtml(container)).toMatchInlineSnapshot(`"<div />"`);

expect(consoleSpy).toHaveBeenCalledWith(
"No `Fallback` element provided to render during initial hydration"
);

dfd.resolve("INDEX DATA");
await waitFor(() => screen.getByText(/INDEX DATA/));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h1>
Home - HYDRATED ROOT
</h1>
<h2>
Index - INDEX DATA
</h2>
</div>
</div>"
`);

consoleSpy.mockRestore();
});

it("with v7_partialHydration, deprecates fallbackElement", async () => {
let dfd1 = createDeferred();
let dfd2 = createDeferred();
let consoleSpy = jest.spyOn(console, "warn");
let router = createTestRouter(
[
{
id: "root",
path: "/",
loader: () => dfd1.promise,
Fallback: () => <p>Root Loading...</p>,
Component() {
let data = useLoaderData() as string;
return (
<div>
<h1>{`Home - ${data}`}</h1>
<Outlet />
</div>
);
},
children: [
{
id: "index",
index: true,
loader: () => dfd2.promise,
Component() {
let data = useLoaderData() as string;
return <h2>{`Index - ${data}`}</h2>;
},
},
],
},
],
{
window: getWindow("/"),
hydrationData: {
loaderData: {
root: "HYDRATED ROOT",
},
},
future: {
v7_partialHydration: true,
},
}
);
let { container } = render(
<RouterProvider
router={router}
fallbackElement={<p>fallbackElement...</p>}
/>
);

expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<p>
Root Loading...
</p>
</div>"
`);

expect(consoleSpy).toHaveBeenCalledWith(
"`<RouterProvider fallbackElement>` is deprecated when using `v7_partialHydration`"
);

dfd1.resolve("ROOT DATA");
dfd2.resolve("INDEX DATA");
await waitFor(() => screen.getByText(/INDEX DATA/));
expect(getHtml(container)).toMatchInlineSnapshot(`
"<div>
<div>
<h1>
Home - HYDRATED ROOT
</h1>
<h2>
Index - INDEX DATA
</h2>
</div>
</div>"
`);

consoleSpy.mockRestore();
});
});
});
}

Expand Down
9 changes: 9 additions & 0 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,15 @@ export function RouterProvider({
}
}, [vtContext.isTransitioning, interruption]);

React.useEffect(() => {
warning(
fallbackElement == null || !router.future.v7_partialHydration,
"`<RouterProvider fallbackElement>` is deprecated when using `v7_partialHydration`"
);
// Only log this once on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

let navigator = React.useMemo((): Navigator => {
return {
createHref: router.createHref,
Expand Down
9 changes: 9 additions & 0 deletions packages/react-router/lib/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ export function RouterProvider({
// pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);

React.useEffect(() => {
warning(
fallbackElement == null || !router.future.v7_partialHydration,
"`<RouterProvider fallbackElement>` is deprecated when using `v7_partialHydration`"
);
// Only log this once on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

let navigator = React.useMemo((): Navigator => {
return {
createHref: router.createHref,
Expand Down

0 comments on commit 847902d

Please sign in to comment.