Skip to content

Commit b162e81

Browse files
authored
Patch patch cause the spec is weird (#10216)
1 parent 73ac17d commit b162e81

File tree

5 files changed

+74
-39
lines changed

5 files changed

+74
-39
lines changed

.changeset/update-web-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Update to latest `@remix-run/[email protected]`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@octokit/plugin-paginate-rest": "^2.17.0",
5151
"@octokit/rest": "^18.12.0",
5252
"@remix-run/changelog-github": "^0.0.5",
53-
"@remix-run/web-fetch": "4.1.3",
53+
"@remix-run/web-fetch": "4.3.3",
5454
"@rollup/plugin-babel": "^5.3.1",
5555
"@rollup/plugin-replace": "^4.0.0",
5656
"@rollup/plugin-typescript": "^8.3.2",

packages/router/__tests__/router-test.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ function setup({
624624
// Otherwise we should only need a loader for the leaf match
625625
let activeLoaderMatches = [match];
626626
// @ts-expect-error
627-
if (opts?.formMethod === "post") {
627+
if (opts?.formMethod != null && opts.formMethod.toLowerCase() !== "get") {
628628
if (currentRouter.state.navigation?.location) {
629629
let matches = matchRoutes(
630630
inFlightRoutes || currentRouter.routes,
@@ -689,7 +689,7 @@ function setup({
689689
invariant(currentRouter, "No currentRouter available");
690690

691691
// @ts-expect-error
692-
if (opts?.formMethod === "post") {
692+
if (opts?.formMethod != null && opts.formMethod.toLowerCase() !== "get") {
693693
activeActionType = "navigation";
694694
activeActionNavigationId = navigationId;
695695
// Assume happy path and mark this navigations loaders as active. Even if
@@ -779,7 +779,7 @@ function setup({
779779
invariant(currentRouter, "No currentRouter available");
780780

781781
// @ts-expect-error
782-
if (opts?.formMethod === "post") {
782+
if (opts?.formMethod != null && opts.formMethod.toLowerCase() !== "get") {
783783
activeActionType = "fetch";
784784
activeActionFetchId = navigationId;
785785
} else {
@@ -867,10 +867,7 @@ function initializeTmTest(init?: {
867867
}
868868

869869
function createRequest(path: string, opts?: RequestInit) {
870-
return new Request(`http://localhost${path}`, {
871-
signal: new AbortController().signal,
872-
...opts,
873-
});
870+
return new Request(`http://localhost${path}`, opts);
874871
}
875872

876873
function createSubmitRequest(path: string, opts?: RequestInit) {
@@ -5899,6 +5896,47 @@ describe("a router", () => {
58995896
expect((await request.formData()).get("query")).toBe("params");
59005897
});
59015898

5899+
// https://fetch.spec.whatwg.org/#concept-method
5900+
it("properly handles method=PATCH weirdness", async () => {
5901+
let t = setup({
5902+
routes: TASK_ROUTES,
5903+
initialEntries: ["/"],
5904+
hydrationData: {
5905+
loaderData: {
5906+
root: "ROOT_DATA",
5907+
},
5908+
},
5909+
});
5910+
5911+
let nav = await t.navigate("/tasks", {
5912+
formMethod: "patch",
5913+
formData: createFormData({ query: "params" }),
5914+
});
5915+
expect(nav.actions.tasks.stub).toHaveBeenCalledWith({
5916+
params: {},
5917+
request: expect.any(Request),
5918+
});
5919+
5920+
// Assert request internals, cannot do a deep comparison above since some
5921+
// internals aren't the same on separate creations
5922+
let request = nav.actions.tasks.stub.mock.calls[0][0].request;
5923+
expect(request.method).toBe("PATCH");
5924+
expect(request.url).toBe("http://localhost/tasks");
5925+
expect(request.headers.get("Content-Type")).toBe(
5926+
"application/x-www-form-urlencoded;charset=UTF-8"
5927+
);
5928+
expect((await request.formData()).get("query")).toBe("params");
5929+
5930+
await nav.actions.tasks.resolve("TASKS ACTION");
5931+
let rootLoaderRequest = nav.loaders.root.stub.mock.calls[0][0].request;
5932+
expect(rootLoaderRequest.method).toBe("GET");
5933+
expect(rootLoaderRequest.url).toBe("http://localhost/tasks");
5934+
5935+
let tasksLoaderRequest = nav.loaders.tasks.stub.mock.calls[0][0].request;
5936+
expect(tasksLoaderRequest.method).toBe("GET");
5937+
expect(tasksLoaderRequest.url).toBe("http://localhost/tasks");
5938+
});
5939+
59025940
it("handles multipart/form-data submissions", async () => {
59035941
let t = setup({
59045942
routes: [
@@ -13437,17 +13475,12 @@ describe("a router", () => {
1343713475
expect(e).toMatchInlineSnapshot(`[Error: query() call aborted]`);
1343813476
});
1343913477

13440-
it("should require a signal on the request", async () => {
13478+
it("should assign signals to requests by default (per the", async () => {
1344113479
let { query } = createStaticHandler(SSR_ROUTES);
1344213480
let request = createRequest("/", { signal: undefined });
13443-
let e;
13444-
try {
13445-
await query(request);
13446-
} catch (_e) {
13447-
e = _e;
13448-
}
13449-
expect(e).toMatchInlineSnapshot(
13450-
`[Error: query()/queryRoute() requests must contain an AbortController signal]`
13481+
let context = await query(request);
13482+
expect((context as StaticHandlerContext).loaderData.index).toBe(
13483+
"INDEX LOADER"
1345113484
);
1345213485
});
1345313486

@@ -14673,18 +14706,11 @@ describe("a router", () => {
1467314706
expect(e).toMatchInlineSnapshot(`[Error: queryRoute() call aborted]`);
1467414707
});
1467514708

14676-
it("should require a signal on the request", async () => {
14709+
it("should assign signals to requests by default (per the spec)", async () => {
1467714710
let { queryRoute } = createStaticHandler(SSR_ROUTES);
1467814711
let request = createRequest("/", { signal: undefined });
14679-
let e;
14680-
try {
14681-
await queryRoute(request, { routeId: "index" });
14682-
} catch (_e) {
14683-
e = _e;
14684-
}
14685-
expect(e).toMatchInlineSnapshot(
14686-
`[Error: query()/queryRoute() requests must contain an AbortController signal]`
14687-
);
14712+
let data = await queryRoute(request, { routeId: "index" });
14713+
expect(data).toBe("INDEX LOADER");
1468814714
});
1468914715

1469014716
it("should support a requestContext passed to loaders and actions", async () => {
@@ -14890,15 +14916,15 @@ describe("a router", () => {
1489014916

1489114917
it("should handle unsupported methods with a 405 Response", async () => {
1489214918
try {
14893-
await queryRoute(createRequest("/", { method: "TRACE" }), {
14919+
await queryRoute(createRequest("/", { method: "CHICKEN" }), {
1489414920
routeId: "root",
1489514921
});
1489614922
expect(false).toBe(true);
1489714923
} catch (data) {
1489814924
expect(isRouteErrorResponse(data)).toBe(true);
1489914925
expect(data.status).toBe(405);
1490014926
expect(data.error).toEqual(
14901-
new Error('Invalid request method "TRACE"')
14927+
new Error('Invalid request method "CHICKEN"')
1490214928
);
1490314929
expect(data.internal).toBe(true);
1490414930
}

packages/router/router.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3492,7 +3492,10 @@ function createClientSideRequest(
34923492

34933493
if (submission && isMutationMethod(submission.formMethod)) {
34943494
let { formMethod, formEncType, formData } = submission;
3495-
init.method = formMethod;
3495+
// Didn't think we needed this but it turns out unlike other methods, patch
3496+
// won't be properly normalized to uppercase and results in a 405 error.
3497+
// See: https://fetch.spec.whatwg.org/#concept-method
3498+
init.method = formMethod.toUpperCase();
34963499
init.body =
34973500
formEncType === "application/x-www-form-urlencoded"
34983501
? convertFormDataToSearchParams(formData)

yarn.lock

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,22 +1997,23 @@
19971997
"@remix-run/web-stream" "^1.0.0"
19981998
web-encoding "1.1.5"
19991999

2000-
"@remix-run/web-fetch@4.1.3":
2001-
version "4.1.3"
2002-
resolved "https://registry.yarnpkg.com/@remix-run/web-fetch/-/web-fetch-4.1.3.tgz#8ad3077c1b5bd9fe2a8813d0ad3c84970a495c04"
2003-
integrity sha512-D3KXAEkzhR248mu7wCHReQrMrIo3Y9pDDa7TrlISnsOEvqkfWkJJF+PQWmOIKpOSHAhDg7TCb2tzvW8lc/MfHw==
2000+
"@remix-run/web-fetch@4.3.3":
2001+
version "4.3.3"
2002+
resolved "https://registry.yarnpkg.com/@remix-run/web-fetch/-/web-fetch-4.3.3.tgz#708371a43f20e645090150dfadb983e950bff12d"
2003+
integrity sha512-DK9vA2tgsadcFPpxW4fvN198tiWpyPhwR0EYOuM4QjpDCz0G619c9RDMdyMy6a7Qb/jwiyx9SOPHWc65QAl+1g==
20042004
dependencies:
20052005
"@remix-run/web-blob" "^3.0.4"
2006-
"@remix-run/web-form-data" "^3.0.2"
2006+
"@remix-run/web-form-data" "^3.0.3"
20072007
"@remix-run/web-stream" "^1.0.3"
20082008
"@web3-storage/multipart-parser" "^1.0.0"
2009+
abort-controller "^3.0.0"
20092010
data-uri-to-buffer "^3.0.1"
20102011
mrmime "^1.0.0"
20112012

2012-
"@remix-run/web-form-data@^3.0.2":
2013-
version "3.0.3"
2014-
resolved "https://registry.yarnpkg.com/@remix-run/web-form-data/-/web-form-data-3.0.3.tgz#f89a7f971aaf1084d2da87affbb7f4e01c32b8ce"
2015-
integrity sha512-wL4veBtVPazSpXfPMzrbmeV3IxuxCfcQYPerQ8BXRO5ahAEVw23tv7xS+yoX0XDO5j+vpRaSbhHJK1H5gF7eYQ==
2013+
"@remix-run/web-form-data@^3.0.3":
2014+
version "3.0.4"
2015+
resolved "https://registry.yarnpkg.com/@remix-run/web-form-data/-/web-form-data-3.0.4.tgz#18c5795edaffbc88c320a311766dc04644125bab"
2016+
integrity sha512-UMF1jg9Vu9CLOf8iHBdY74Mm3PUvMW8G/XZRJE56SxKaOFWGSWlfxfG+/a3boAgHFLTkP7K4H1PxlRugy1iQtw==
20162017
dependencies:
20172018
web-encoding "1.1.5"
20182019

0 commit comments

Comments
 (0)