Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RRR Rendering #4900

Merged
merged 8 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/thirty-eels-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@remix-run/react": minor
"@remix-run/server-runtime": minor
---

Update Remix to use React Router 6.4+ data APIs
253 changes: 249 additions & 4 deletions integration/error-boundary-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect } from "@playwright/test";
import { ServerMode } from "@remix-run/server-runtime/mode";

import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
import type { Fixture, AppFixture } from "./helpers/create-fixture";
Expand Down Expand Up @@ -305,7 +306,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickSubmitButton(HAS_BOUNDARY_ACTION);
await page.waitForSelector("#own-boundary");
await page.waitForSelector(`text=${OWN_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(OWN_BOUNDARY_TEXT);
});

Expand All @@ -315,7 +316,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto(HAS_BOUNDARY_ACTION);
await app.clickSubmitButton(HAS_BOUNDARY_ACTION);
await page.waitForSelector("#own-boundary");
await page.waitForSelector(`text=${OWN_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(OWN_BOUNDARY_TEXT);
});

Expand All @@ -332,6 +333,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickSubmitButton(NO_BOUNDARY_ACTION);
await page.waitForSelector(`text=${ROOT_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(ROOT_BOUNDARY_TEXT);
});

Expand All @@ -341,6 +343,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto(NO_BOUNDARY_ACTION);
await app.clickSubmitButton(NO_BOUNDARY_ACTION);
await page.waitForSelector(`text=${ROOT_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(ROOT_BOUNDARY_TEXT);
});

Expand All @@ -354,6 +357,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink(HAS_BOUNDARY_LOADER);
await page.waitForSelector(`text=${OWN_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(OWN_BOUNDARY_TEXT);
});

Expand All @@ -369,6 +373,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink(NO_BOUNDARY_LOADER);
await page.waitForSelector(`text=${ROOT_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(ROOT_BOUNDARY_TEXT);
});

Expand All @@ -384,6 +389,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink(NO_BOUNDARY_RENDER);
await page.waitForSelector(`text=${ROOT_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(ROOT_BOUNDARY_TEXT);
});

Expand All @@ -397,6 +403,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink(HAS_BOUNDARY_RENDER);
await page.waitForSelector(`text=${OWN_BOUNDARY_TEXT}`);
expect(await app.getHtml("main")).toMatch(OWN_BOUNDARY_TEXT);
});

Expand Down Expand Up @@ -429,7 +436,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/fetcher-no-boundary");
await app.clickSubmitButton(NO_BOUNDARY_NO_LOADER_OR_ACTION);
await page.waitForSelector("#root-boundary");
await page.waitForSelector(`text=${ROOT_BOUNDARY_TEXT}`);
});

test("renders root boundary in document POST without action requests", async () => {
Expand All @@ -446,7 +453,7 @@ test.describe("ErrorBoundary", () => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickSubmitButton(NO_BOUNDARY_NO_LOADER_OR_ACTION);
await page.waitForSelector("#root-boundary");
await page.waitForSelector(`text=${ROOT_BOUNDARY_TEXT}`);
});

test("renders own boundary in document POST without action requests", async () => {
Expand Down Expand Up @@ -630,3 +637,241 @@ test.describe("ErrorBoundary", () => {
});
});
});

test.describe("loaderData in ErrorBoundary", () => {
let fixture: Fixture;
let appFixture: AppFixture;
let consoleErrors: string[];
let oldConsoleError: () => void;

test.beforeAll(async () => {
fixture = await createFixture({
files: {
"app/root.jsx": js`
import { Links, Meta, Outlet, Scripts } from "@remix-run/react";

export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<main>
<Outlet />
</main>
<Scripts />
</body>
</html>
);
}
`,

"app/routes/parent.jsx": js`
import { Outlet, useLoaderData, useMatches } from "@remix-run/react";

export function loader() {
return "PARENT";
}

export default function () {
return (
<div>
<p id="parent-data">{useLoaderData()}</p>
<Outlet />
</div>
)
}

export function ErrorBoundary({ error }) {
return (
<>
<p id="parent-data">{useLoaderData()}</p>
<p id="parent-matches-data">
{useMatches().find(m => m.id === 'routes/parent').data}
</p>
<p id="parent-error">{error.message}</p>
</>
);
}
`,

"app/routes/parent/child-with-boundary.jsx": js`
import { Form, useLoaderData } from "@remix-run/react";

export function loader() {
return "CHILD";
}

export function action() {
throw new Error("Broken!");
}

export default function () {
return (
<>
<p id="child-data">{useLoaderData()}</p>
<Form method="post">
<button type="submit" name="key" value="value">
Submit
</button>
</Form>
</>
)
}

export function ErrorBoundary({ error }) {
return (
<>
<p id="child-data">{useLoaderData()}</p>
<p id="child-error">{error.message}</p>
</>
);
}
`,

"app/routes/parent/child-without-boundary.jsx": js`
import { Form, useLoaderData } from "@remix-run/react";

export function loader() {
return "CHILD";
}

export function action() {
throw new Error("Broken!");
}

export default function () {
return (
<>
<p id="child-data">{useLoaderData()}</p>
<Form method="post">
<button type="submit" name="key" value="value">
Submit
</button>
</Form>
</>
)
}
`,
},
});

appFixture = await createAppFixture(fixture, ServerMode.Development);
});

test.afterAll(() => {
appFixture.close();
});

test.beforeEach(({ page }) => {
oldConsoleError = console.error;
console.error = () => {};
consoleErrors = [];
// Listen for all console events and handle errors
page.on("console", (msg) => {
if (msg.type() === "error") {
consoleErrors.push(msg.text());
}
});
});

test.afterEach(() => {
console.error = oldConsoleError;
});

test.describe("without JavaScript", () => {
test.use({ javaScriptEnabled: false });
runBoundaryTests();
});

test.describe("with JavaScript", () => {
test.use({ javaScriptEnabled: true });
runBoundaryTests();
});

function runBoundaryTests() {
test("Prevents useLoaderData in self ErrorBoundary", async ({
page,
javaScriptEnabled,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/parent/child-with-boundary");

expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data">PARENT</p>'
);
expect(await app.getHtml("#child-data")).toEqual(
'<p id="child-data">CHILD</p>'
);
expect(consoleErrors).toEqual([]);

await app.clickSubmitButton("/parent/child-with-boundary");
await page.waitForSelector("#child-error");

expect(await app.getHtml("#child-error")).toEqual(
'<p id="child-error">Broken!</p>'
);
expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data">PARENT</p>'
);
expect(await app.getHtml("#child-data")).toEqual(
'<p id="child-data"></p>'
);

// Only look for this message. Chromium browsers will also log the
// network error but firefox does not
// "Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
let msg =
"You cannot `useLoaderData` in an errorElement (routeId: routes/parent/child-with-boundary)";
if (javaScriptEnabled) {
expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]);
} else {
// We don't get the useLoaderData message in the client when JS is disabled
expect(consoleErrors.filter((m) => m === msg)).toEqual([]);
}
});

test("Prevents useLoaderData in bubbled ErrorBoundary", async ({
page,
javaScriptEnabled,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/parent/child-without-boundary");

expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data">PARENT</p>'
);
expect(await app.getHtml("#child-data")).toEqual(
'<p id="child-data">CHILD</p>'
);
expect(consoleErrors).toEqual([]);

await app.clickSubmitButton("/parent/child-without-boundary");
await page.waitForSelector("#parent-error");

expect(await app.getHtml("#parent-error")).toEqual(
'<p id="parent-error">Broken!</p>'
);
expect(await app.getHtml("#parent-matches-data")).toEqual(
'<p id="parent-matches-data"></p>'
);
expect(await app.getHtml("#parent-data")).toEqual(
'<p id="parent-data"></p>'
);

// Only look for this message. Chromium browsers will also log the
// network error but firefox does not
// "Failed to load resource: the server responded with a status of 500 (Internal Server Error)",
let msg =
"You cannot `useLoaderData` in an errorElement (routeId: routes/parent)";
if (javaScriptEnabled) {
expect(consoleErrors.filter((m) => m === msg)).toEqual([msg]);
} else {
// We don't get the useLoaderData message in the client when JS is disabled
expect(consoleErrors.filter((m) => m === msg)).toEqual([]);
}
});
}
});
Loading