From b4683d40141dd8377a8c889a3a7c3dba51f1038f Mon Sep 17 00:00:00 2001 From: Tejas Kumar Date: Wed, 31 Oct 2018 18:16:08 +0100 Subject: [PATCH] Fix base path propagation to ignore parentPath --- src/Get.test.tsx | 28 +++++++++++++++++++++++++++ src/Get.tsx | 22 +++++++++++++++------ src/Mutate.test.tsx | 31 +++++++++++++++++++++++++++++ src/Mutate.tsx | 44 +++++++++++++++++++++++++++++++----------- src/util/composeUrl.ts | 4 ++-- 5 files changed, 110 insertions(+), 19 deletions(-) diff --git a/src/Get.test.tsx b/src/Get.test.tsx index ed85d4a6..f648f3f8 100644 --- a/src/Get.test.tsx +++ b/src/Get.test.tsx @@ -6,6 +6,7 @@ import React from "react"; import { cleanup, render, wait } from "react-testing-library"; import { Get, RestfulProvider } from "./index"; +import Mutate from "./Mutate"; afterEach(() => { cleanup(); @@ -603,6 +604,33 @@ describe("Get", () => { ); expect(apiCalls).toEqual(1); }); + + it("should rewrite the base and handle path accordingly", async () => { + nock("https://my-other-api.fake") + .get("/") + .reply(200, { id: 1 }); + + nock("https://my-awesome-api.fake/eaegae") + .post("/LOL") + .reply(200, { id: 1 }); + + const children = jest.fn(); + children.mockReturnValue(
); + + render( + + + {() => ( + + {children} + + )} + + , + ); + + await wait(() => expect(children.mock.calls.length).toBe(2)); + }); it("should refetch when base changes", () => { let apiCalls = 0; nock("https://my-awesome-api.fake") diff --git a/src/Get.tsx b/src/Get.tsx index 27ecb08b..5a1b4b09 100644 --- a/src/Get.tsx +++ b/src/Get.tsx @@ -59,6 +59,11 @@ export interface GetProps { * typically composed by parent Gets or the RestfulProvider. */ path: string; + /** + * @private This is an internal implementation detail in restful-react, not meant to be used externally. + * This helps restful-react correctly override `path`s when a new `base` property is provided. + */ + __internal_hasExplicitBase?: boolean; /** * A function that recieves the returned, resolved * data. @@ -209,15 +214,20 @@ class ContextlessGet extends React.Component< }; public fetch = async (requestPath?: string, thisRequestOptions?: RequestInit) => { - const { base, parentPath, path, resolve } = this.props; + const { base, __internal_hasExplicitBase, parentPath, path, resolve } = this.props; if (this.state.error || !this.state.loading) { this.setState(() => ({ error: null, loading: true })); } - const request = new Request( - composeUrl(base!, parentPath!, requestPath || path || ""), - this.getRequestOptions(thisRequestOptions), - ); + const makeRequestPath = () => { + if (__internal_hasExplicitBase) { + return composeUrl(base!, "", path || ""); + } else { + return composeUrl(base!, parentPath!, requestPath || path || ""); + } + }; + + const request = new Request(makeRequestPath(), this.getRequestOptions(thisRequestOptions)); const response = await fetch(request); const { data, responseError } = await processResponse(response); @@ -277,7 +287,7 @@ function Get(props: GetProps) { {contextProps => ( - + )} diff --git a/src/Mutate.test.tsx b/src/Mutate.test.tsx index b42a279c..d82d3887 100644 --- a/src/Mutate.test.tsx +++ b/src/Mutate.test.tsx @@ -4,6 +4,7 @@ import nock from "nock"; import React from "react"; import { cleanup, render, wait } from "react-testing-library"; +import Get from "./Get"; import { Mutate, RestfulProvider } from "./index"; afterEach(() => { @@ -401,6 +402,36 @@ describe("Mutate", () => { expect(children.mock.calls[2][1].loading).toEqual(false); }); + it("should rewrite the base and handle path accordingly", async () => { + nock("https://my-other-api.fake") + .post("/") + .reply(200, { id: 1 }); + + nock("https://my-awesome-api.fake/eaegae") + .get("/LOL") + .reply(200, { id: 1 }); + + const children = jest.fn(); + children.mockReturnValue(
); + + render( + + + {() => ( + + {children} + + )} + + , + ); + + await wait(() => expect(children.mock.calls.length).toBe(2)); + const response = await children.mock.calls[0][0](); + expect(children.mock.calls.length).toBe(4); + expect(response).toEqual({ id: 1 }); + }); + it("should compose base with trailing slash", async () => { nock("https://my-awesome-api.fake/MY_SUBROUTE") .post("/people/relative") diff --git a/src/Mutate.tsx b/src/Mutate.tsx index 527d8929..c5742b10 100644 --- a/src/Mutate.tsx +++ b/src/Mutate.tsx @@ -36,7 +36,12 @@ export interface MutateCommonProps { * The path at which to request data, * typically composed by parents or the RestfulProvider. */ - path: string; + path?: string; + /** + * @private This is an internal implementation detail in restful-react, not meant to be used externally. + * This helps restful-react correctly override `path`s when a new `base` property is provided. + */ + __internal_hasExplicitBase?: boolean; /** * What HTTP verb are we using? */ @@ -117,14 +122,27 @@ class ContextlessMutate extends React.Component< }; public mutate = async (body?: string | {}, mutateRequestOptions?: RequestInit) => { - const { base, parentPath, path, verb, requestOptions: providerRequestOptions } = this.props; + const { + __internal_hasExplicitBase, + base, + parentPath, + path, + verb, + requestOptions: providerRequestOptions, + } = this.props; this.setState(() => ({ error: null, loading: true })); - const requestPath = - verb === "DELETE" && typeof body === "string" - ? composeUrl(base!, parentPath!, composePathWithBody(path, body)) - : composeUrl(base!, parentPath!, path); - const request = new Request(requestPath, { + const makeRequestPath = () => { + if (__internal_hasExplicitBase) { + return composeUrl(base!, "", path || ""); + } else { + return verb === "DELETE" && typeof body === "string" + ? composeUrl(base!, parentPath!, composePathWithBody(path!, body)) + : composeUrl(base!, parentPath!, path!); + } + }; + + const request = new Request(makeRequestPath(), { method: verb, body: typeof body === "object" ? JSON.stringify(body) : body, ...(typeof providerRequestOptions === "function" ? providerRequestOptions() : providerRequestOptions), @@ -136,7 +154,7 @@ class ContextlessMutate extends React.Component< : (providerRequestOptions || {}).headers), ...(mutateRequestOptions ? mutateRequestOptions.headers : {}), }, - }); + } as RequestInit); // Type assertion for version of TypeScript that can't yet discriminate. const response = await fetch(request); const { data, responseError } = await processResponse(response); @@ -163,7 +181,7 @@ class ContextlessMutate extends React.Component< const { children, path, base, parentPath } = this.props; const { error, loading, response } = this.state; - return children(this.mutate, { loading, error }, { response, absolutePath: composeUrl(base!, parentPath!, path) }); + return children(this.mutate, { loading, error }, { response, absolutePath: composeUrl(base!, parentPath!, path!) }); } } @@ -181,8 +199,12 @@ function Mutate(props: MutateProps) { return ( {contextProps => ( - - {...contextProps} {...props} /> + + + {...contextProps} + {...props} + __internal_hasExplicitBase={Boolean(props.base)} + /> )} diff --git a/src/util/composeUrl.ts b/src/util/composeUrl.ts index 8b5f7f5e..76ed69fd 100644 --- a/src/util/composeUrl.ts +++ b/src/util/composeUrl.ts @@ -1,6 +1,6 @@ import url from "url"; -export const composeUrl = (base: string, parentPath: string, path: string): string => { +export const composeUrl = (base: string = "", parentPath: string = "", path: string = ""): string => { const composedPath = composePath(parentPath, path); /* If the base contains a trailing slash, it will be trimmed during composition */ return base!.endsWith("/") ? `${base!.slice(0, -1)}${composedPath}` : `${base}${composedPath}`; @@ -14,7 +14,7 @@ export const composeUrl = (base: string, parentPath: string, path: string): stri * whereas, * parentPath = "/someBasePath" and path = "relative" resolves to "/someBasePath/relative" */ -export const composePath = (parentPath: string, path: string): string => { +export const composePath = (parentPath: string = "", path: string = ""): string => { if (path.startsWith("/") && path.length > 1) { return url.resolve(parentPath, path); } else if (path !== "" && path !== "/") {