Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/curly-chefs-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

[REMOVE] Few additional fixes for returning/throwing data from middleware - attach to #14093
230 changes: 229 additions & 1 deletion integration/middleware-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
Response as PlaywrightResponse,
} from "@playwright/test";
import { test, expect } from "@playwright/test";
import { UNSAFE_ServerMode } from "react-router";
import { UNSAFE_ErrorResponseImpl, UNSAFE_ServerMode } from "react-router";

import {
createAppFixture,
Expand Down Expand Up @@ -2938,6 +2938,234 @@ test.describe("Middleware", () => {
appFixture.close();
});

test("bubbles response up the chain when middleware throws data() before next", async ({
page,
}) => {
let fixture = await createFixture(
{
files: {
"react-router.config.ts": reactRouterConfig({
middleware: true,
}),
"vite.config.ts": js`
import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";

export default defineConfig({
build: { manifest: true, minify: false },
plugins: [reactRouter()],
});
`,
"app/routes/_index.tsx": js`
import { Link } from 'react-router'
export default function Component({ loaderData }) {
return <Link to="/a/b/c">/a/b/c</Link>;
}
`,
"app/routes/a.tsx": js`
import { Outlet } from 'react-router'
export const unstable_middleware = [
async (_, next) => {
let res = await next();
res.headers.set('x-a', 'true');
return res;
}
];
export default function Component() {
return <Outlet/>
}
export function ErrorBoundary({ error }) {
return (
<>
<h1 data-error>A Error Boundary</h1>
<pre>{error.data}</pre>
</>
);
}
`,
"app/routes/a.b.tsx": js`
import { Link, Outlet } from 'react-router'
export const unstable_middleware = [
async (_, next) => {
let res = await next();
res.headers.set('x-b', 'true');
return res;
}
];
export default function Component({ loaderData }) {
return <Outlet/>;
}
`,
"app/routes/a.b.c.tsx": js`
import { data } from "react-router";
export const unstable_middleware = [(_, next) => {
throw data('C ERROR', { status: 418, statusText: "I'm a teapot" })
}];
// Force middleware to run on client side navs
export function loader() {
return null;
}
export default function Component({ loaderData }) {
return <h1>C</h1>
}
`,
},
},
UNSAFE_ServerMode.Development,
);

let appFixture = await createAppFixture(
fixture,
UNSAFE_ServerMode.Development,
);

let res = await fixture.requestDocument("/a/b/c");
expect(res.status).toBe(418);
expect(res.headers.get("x-a")).toBe("true");
expect(res.headers.get("x-b")).toBe("true");
let html = await res.text();
expect(html).toContain("A Error Boundary");
expect(html).toContain("C ERROR");

let data = await fixture.requestSingleFetchData("/a/b/c.data");
expect(data.status).toBe(418);
expect(data.headers.get("x-a")).toBe("true");
expect(data.headers.get("x-b")).toBe("true");
expect((data.data as any)["routes/a.b.c"]).toEqual({
error: new UNSAFE_ErrorResponseImpl(418, "I'm a teapot", "C ERROR"),
});

let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink("/a/b/c");
await page.waitForSelector("[data-error]");
expect(await page.locator("[data-error]").textContent()).toBe(
"A Error Boundary",
);
expect(await page.locator("pre").textContent()).toBe("C ERROR");

appFixture.close();
});

test("bubbles response up the chain when middleware throws data() after next", async ({
page,
}) => {
let fixture = await createFixture(
{
files: {
"react-router.config.ts": reactRouterConfig({
middleware: true,
}),
"vite.config.ts": js`
import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";

export default defineConfig({
build: { manifest: true, minify: false },
plugins: [reactRouter()],
});
`,
"app/routes/_index.tsx": js`
import { Link } from 'react-router'
export default function Component({ loaderData }) {
return <Link to="/a/b/c">/a/b/c</Link>;
}
`,
"app/routes/a.tsx": js`
import { Outlet } from 'react-router'
export const unstable_middleware = [
async (_, next) => {
let res = await next();
res.headers.set('x-a', 'true');
return res;
}
];
export function loader() {
return "A LOADER";
}
export default function Component() {
return <Outlet/>
}
export function ErrorBoundary({ error, loaderData }) {
return (
<>
<h1 data-error>A Error Boundary</h1>
<pre>{error.data}</pre>
<p>{loaderData}</p>
</>
);
}
`,
"app/routes/a.b.tsx": js`
import { Link, Outlet } from 'react-router'
export const unstable_middleware = [
async (_, next) => {
let res = await next();
res.headers.set('x-b', 'true');
return res;
}
];
export default function Component({ loaderData }) {
return <Outlet/>;
}
`,
"app/routes/a.b.c.tsx": js`
import { data } from "react-router";
export const unstable_middleware = [async (_, next) => {
let res = await next();
throw data('C ERROR', { status: 418, statusText: "I'm a teapot" })
}];
// Force middleware to run on client side navs
export function loader() {
return null;
}
export default function Component({ loaderData }) {
return <h1>C</h1>
}
`,
},
},
UNSAFE_ServerMode.Development,
);

let appFixture = await createAppFixture(
fixture,
UNSAFE_ServerMode.Development,
);

let res = await fixture.requestDocument("/a/b/c");
expect(res.status).toBe(418);
expect(res.headers.get("x-a")).toBe("true");
expect(res.headers.get("x-b")).toBe("true");
let html = await res.text();
expect(html).toContain("A Error Boundary");
expect(html).toContain("C ERROR");
expect(html).toContain("A LOADER");

let data = await fixture.requestSingleFetchData("/a/b/c.data");
expect(data.status).toBe(418);
expect(data.headers.get("x-a")).toBe("true");
expect(data.headers.get("x-b")).toBe("true");
expect((data.data as any)["routes/a"]).toEqual({
data: "A LOADER",
});
expect((data.data as any)["routes/a.b.c"]).toEqual({
error: new UNSAFE_ErrorResponseImpl(418, "I'm a teapot", "C ERROR"),
});

let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink("/a/b/c");
await page.waitForSelector("[data-error]");
expect(await page.locator("[data-error]").textContent()).toBe(
"A Error Boundary",
);
expect(await page.locator("pre").textContent()).toBe("C ERROR");
expect(await page.locator("p").textContent()).toBe("A LOADER");

appFixture.close();
});

test("still calls middleware for all matches on granular data requests", async ({
page,
}) => {
Expand Down
Loading
Loading