Skip to content

Commit

Permalink
Fix handleDataRequest redirects in single fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Mar 25, 2024
1 parent fbb1a90 commit a6f1bb0
Show file tree
Hide file tree
Showing 2 changed files with 272 additions and 4 deletions.
248 changes: 247 additions & 1 deletion integration/single-fetch-test.ts
Expand Up @@ -90,7 +90,6 @@ test.describe("single-fetch", () => {

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

test.afterEach(() => {
Expand Down Expand Up @@ -138,6 +137,8 @@ test.describe("single-fetch", () => {
});

test("loads proper errors on single fetch loader requests", async () => {
console.error = () => {};

let fixture = await createFixture(
{
config: {
Expand Down Expand Up @@ -584,6 +585,251 @@ test.describe("single-fetch", () => {
expect(res.headers.get("x-c-headers")).toEqual("true");
});

test("processes loader redirects", async ({ page }) => {
let fixture = await createFixture({
config: {
future: {
unstable_singleFetch: true,
},
},
files: {
...files,
"app/routes/data.tsx": js`
import { redirect } from '@remix-run/node';
export function loader() {
return redirect('/target');
}
export default function Component() {
return null
}
`,
"app/routes/target.tsx": js`
export default function Component() {
return <h1 id="target">Target</h1>
}
`,
},
});
let appFixture = await createAppFixture(fixture);
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink("/data");
await page.waitForSelector("#target");
expect(await app.getHtml("#target")).toContain("Target");
});

test("processes action redirects", async ({ page }) => {
let fixture = await createFixture(
{
config: {
future: {
unstable_singleFetch: true,
},
},
files: {
...files,
"app/routes/data.tsx": js`
import { redirect } from '@remix-run/node';
export function action() {
return redirect('/target');
}
export default function Component() {
return null
}
`,
"app/routes/target.tsx": js`
export default function Component() {
return <h1 id="target">Target</h1>
}
`,
},
},
ServerMode.Development
);
let appFixture = await createAppFixture(fixture, ServerMode.Development);
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickSubmitButton("/data");
await page.waitForSelector("#target");
expect(await app.getHtml("#target")).toContain("Target");
});

test("processes redirects from handleDataRequest (after loaders)", async ({
page,
}) => {
let fixture = await createFixture({
config: {
future: {
unstable_singleFetch: true,
},
},
files: {
...files,
"app/entry.server.tsx": js`
import { PassThrough } from "node:stream";
import type { EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToPipeableStream } from "react-dom/server";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
const { pipe } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onShellReady() {
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
},
}
);
});
}
export function handleDataRequest(response, { request }) {
if (request.url.endsWith("/data.data")) {
return new Response(null, {
status: 302,
headers: {
Location: "/target",
},
});
}
return response;
}
`,
"app/routes/data.tsx": js`
import { redirect } from '@remix-run/node';
export function loader() {
return redirect('/target');
}
export default function Component() {
return null
}
`,
"app/routes/target.tsx": js`
export default function Component() {
return <h1 id="target">Target</h1>
}
`,
},
});
let appFixture = await createAppFixture(fixture);
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink("/data");
await page.waitForSelector("#target");
expect(await app.getHtml("#target")).toContain("Target");
});

test("processes redirects from handleDataRequest (after actions)", async ({
page,
}) => {
let fixture = await createFixture({
config: {
future: {
unstable_singleFetch: true,
},
},
files: {
...files,
"app/entry.server.tsx": js`
import { PassThrough } from "node:stream";
import type { EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToPipeableStream } from "react-dom/server";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
const { pipe } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onShellReady() {
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
},
}
);
});
}
export function handleDataRequest(response, { request }) {
if (request.url.endsWith("/data.data")) {
return new Response(null, {
status: 302,
headers: {
Location: "/target",
},
});
}
return response;
}
`,
"app/routes/data.tsx": js`
import { redirect } from '@remix-run/node';
export function action() {
return redirect('/target');
}
export default function Component() {
return null
}
`,
"app/routes/target.tsx": js`
export default function Component() {
return <h1 id="target">Target</h1>
}
`,
},
});
let appFixture = await createAppFixture(fixture);
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickSubmitButton("/data");
await page.waitForSelector("#target");
expect(await app.getHtml("#target")).toContain("Target");
});

test.describe("client loaders", () => {
test("when no routes have client loaders", async ({ page }) => {
let fixture = await createFixture(
Expand Down
28 changes: 25 additions & 3 deletions packages/remix-server-runtime/server.ts
Expand Up @@ -80,6 +80,8 @@ function derive(build: ServerBuild, mode?: string) {
};
}

export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");

export const createRequestHandler: CreateRequestHandlerFunction = (
build,
mode
Expand Down Expand Up @@ -190,7 +192,29 @@ export const createRequestHandler: CreateRequestHandlerFunction = (
});

if (isRedirectResponse(response)) {
response = createRemixRedirectResponse(response, _build.basename);
let result: SingleFetchResult | SingleFetchResults =
getSingleFetchRedirect(response);

if (request.method === "GET") {
result = {
[SingleFetchRedirectSymbol]: result,
};
}
let headers = new Headers(response.headers);
headers.set("Content-Type", "text/x-turbo");

return new Response(
encodeViaTurboStream(
result,
request.signal,
_build.entry.module.streamTimeout,
serverMode
),
{
status: 200,
headers,
}
);
}
}
} else if (
Expand Down Expand Up @@ -300,8 +324,6 @@ async function handleDataRequest(
}
}

export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");

type SingleFetchRedirectResult = {
redirect: string;
status: number;
Expand Down

0 comments on commit a6f1bb0

Please sign in to comment.