Skip to content

Commit

Permalink
Fix base path propagation to ignore parentPath
Browse files Browse the repository at this point in the history
  • Loading branch information
Tejas Kumar authored and fabien0102 committed Nov 1, 2018
1 parent 4cba6ad commit b4683d4
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 19 deletions.
28 changes: 28 additions & 0 deletions src/Get.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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(<div />);

render(
<RestfulProvider base="https://my-awesome-api.fake/eaegae">
<Mutate verb="POST" path="/LOL">
{() => (
<Get base="https://my-other-api.fake" path="">
{children}
</Get>
)}
</Mutate>
</RestfulProvider>,
);

await wait(() => expect(children.mock.calls.length).toBe(2));
});
it("should refetch when base changes", () => {
let apiCalls = 0;
nock("https://my-awesome-api.fake")
Expand Down
22 changes: 16 additions & 6 deletions src/Get.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export interface GetProps<TData, TError> {
* 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.
Expand Down Expand Up @@ -209,15 +214,20 @@ class ContextlessGet<TData, TError> 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);

Expand Down Expand Up @@ -277,7 +287,7 @@ function Get<TData = any, TError = any>(props: GetProps<TData, TError>) {
<RestfulReactConsumer>
{contextProps => (
<RestfulReactProvider {...contextProps} parentPath={composePath(contextProps.parentPath, props.path)}>
<ContextlessGet {...contextProps} {...props} />
<ContextlessGet {...contextProps} {...props} __internal_hasExplicitBase={Boolean(props.base)} />
</RestfulReactProvider>
)}
</RestfulReactConsumer>
Expand Down
31 changes: 31 additions & 0 deletions src/Mutate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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(<div />);

render(
<RestfulProvider base="https://my-awesome-api.fake/eaegae">
<Get path="/LOL">
{() => (
<Mutate verb="POST" base="https://my-other-api.fake" path="">
{children}
</Mutate>
)}
</Get>
</RestfulProvider>,
);

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")
Expand Down
44 changes: 33 additions & 11 deletions src/Mutate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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?
*/
Expand Down Expand Up @@ -117,14 +122,27 @@ class ContextlessMutate<TData, TError> 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),
Expand All @@ -136,7 +154,7 @@ class ContextlessMutate<TData, TError> 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);
Expand All @@ -163,7 +181,7 @@ class ContextlessMutate<TData, TError> 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!) });
}
}

Expand All @@ -181,8 +199,12 @@ function Mutate<TError = any, TData = any>(props: MutateProps<TData, TError>) {
return (
<RestfulReactConsumer>
{contextProps => (
<RestfulReactProvider {...contextProps} parentPath={composePath(contextProps.parentPath, props.path)}>
<ContextlessMutate<TData, TError> {...contextProps} {...props} />
<RestfulReactProvider {...contextProps} parentPath={composePath(contextProps.parentPath, props.path!)}>
<ContextlessMutate<TData, TError>
{...contextProps}
{...props}
__internal_hasExplicitBase={Boolean(props.base)}
/>
</RestfulReactProvider>
)}
</RestfulReactConsumer>
Expand Down
4 changes: 2 additions & 2 deletions src/util/composeUrl.ts
Original file line number Diff line number Diff line change
@@ -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}`;
Expand All @@ -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 !== "/") {
Expand Down

0 comments on commit b4683d4

Please sign in to comment.