import { test, expect } from "@playwright/test";
import { UNSAFE_ErrorResponseImpl as ErrorResponseImpl } from "react-router";
import {
createAppFixture,
createFixture,
js,
} from "./helpers/create-fixture.js";
import type { Fixture, AppFixture } from "./helpers/create-fixture.js";
test.describe("ErrorBoundary", () => {
let fixture: Fixture;
let appFixture: AppFixture;
let _consoleError: any;
let errorLogs: any[];
test.beforeAll(async () => {
_consoleError = console.error;
console.error = (v) => errorLogs.push(v);
fixture = await createFixture({
files: {
"app/root.tsx": js`
import { Links, Meta, Outlet, Scripts } from "react-router";
export default function Root() {
return (
);
}
`,
"app/routes/_index.tsx": js`
import { Link, Form } from "react-router";
export default function () {
return Index
}
`,
[`app/routes/loader-throw-error.jsx`]: js`
export async function loader() {
throw Error("BLARGH");
}
export default function () {
return Hello
}
`,
[`app/routes/loader-return-json.jsx`]: js`
export async function loader() {
return { ok: true };
}
export default function () {
return Hello
}
`,
[`app/routes/action-throw-error.jsx`]: js`
export async function action() {
throw Error("YOOOOOOOO WHAT ARE YOU DOING");
}
export default function () {
return Goodbye
;
}
`,
[`app/routes/action-return-json.jsx`]: js`
export async function action() {
return { ok: true };
}
export default function () {
return Hi!
}
`,
},
});
appFixture = await createAppFixture(fixture);
});
test.beforeEach(async () => {
errorLogs = [];
});
test.afterAll(() => {
console.error = _consoleError;
appFixture.close();
});
function assertLoggedErrorInstance(message: string) {
let error = errorLogs[0] as Error;
expect(error).toBeInstanceOf(Error);
expect(error.message).toEqual(message);
}
test("returns a 200 empty response on a data fetch to a path with no loaders", async () => {
let { status, headers, data } =
await fixture.requestSingleFetchData("/_root.data");
expect(status).toBe(200);
expect(headers.has("X-Remix-Response")).toBe(true);
expect(data).toEqual({});
});
test("returns a 405 on a data fetch POST to a path with no action", async () => {
let { status, headers, data } = await fixture.requestSingleFetchData(
"/_root.data?index",
{
method: "POST",
},
);
expect(status).toBe(405);
expect(headers.has("X-Remix-Response")).toBe(true);
expect(data).toEqual({
error: new ErrorResponseImpl(
405,
"Method Not Allowed",
'Error: You made a POST request to "/" but did not provide an `action` for route "routes/_index", so there is no way to handle the request.',
),
});
assertLoggedErrorInstance(
'You made a POST request to "/" but did not provide an `action` for route "routes/_index", so there is no way to handle the request.',
);
});
test("returns a 405 on a data fetch with a bad method", async () => {
try {
await fixture.requestSingleFetchData("/loader-return-json.data", {
method: "TRACE",
});
expect(false).toBe(true);
} catch (e) {
expect((e as Error).message).toMatch(
"'TRACE' HTTP method is unsupported.",
);
}
});
test("returns a 404 on a data fetch to a path with no matches", async () => {
let { status, headers, data } = await fixture.requestSingleFetchData(
"/i/match/nothing.data",
);
expect(status).toBe(404);
expect(headers.has("X-Remix-Response")).toBe(true);
expect(data).toEqual({
root: {
error: new ErrorResponseImpl(
404,
"Not Found",
'Error: No route matches URL "/i/match/nothing"',
),
},
});
assertLoggedErrorInstance('No route matches URL "/i/match/nothing"');
});
});