Skip to content
This repository has been archived by the owner on Nov 11, 2023. It is now read-only.

Commit

Permalink
Add the useMutate
Browse files Browse the repository at this point in the history
  • Loading branch information
fabien0102 committed May 16, 2019
1 parent f24db4f commit 3ef62e7
Show file tree
Hide file tree
Showing 4 changed files with 520 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as Poll, PollProps } from "./Poll";
export { default as Mutate, MutateProps, MutateMethod } from "./Mutate";

export { useGet, UseGetProps } from "./useGet";
export { useMutate, UseMutateProps } from "./useMutate";

export { Get, GetDataError, GetProps, GetMethod };

Expand Down
10 changes: 8 additions & 2 deletions src/useGet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import { cleanup, fireEvent, render, wait, waitForElement } from "react-testing-
import { RestfulProvider, useGet } from "./index";
import { Omit, UseGetProps } from "./useGet";

// NOTES:
// We have react warning due to https://github.com/kentcdodds/react-testing-library/issues/281
describe("useGet hook", () => {
// Mute console.error -> https://github.com/kentcdodds/react-testing-library/issues/281
// tslint:disable:no-console
const originalConsoleError = console.error;
beforeEach(() => {
console.error = jest.fn;
});
afterEach(() => {
console.error = originalConsoleError;
cleanup();
nock.cleanAll();
});

describe("classic usage", () => {
it("should have a loading state on mount", async () => {
nock("https://my-awesome-api.fake")
Expand Down
349 changes: 349 additions & 0 deletions src/useMutate.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
import "isomorphic-fetch";
import nock from "nock";
import React from "react";
import { renderHook } from "react-hooks-testing-library";
import { RestfulProvider, useMutate } from ".";

describe("useMutate", () => {
// Mute console.error -> https://github.com/kentcdodds/react-testing-library/issues/281
// tslint:disable:no-console
const originalConsoleError = console.error;
beforeEach(() => {
console.error = jest.fn;
});
afterEach(() => {
console.error = originalConsoleError;
});

describe("DELETE", () => {
it("should set loading to true after a call", async () => {
nock("https://my-awesome-api.fake")
.delete("/plop")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", ""), { wrapper });
result.current.mutate("plop");

expect(result.current).toMatchObject({
error: null,
loading: true,
});
});

it("should call the correct url with a specific id", async () => {
nock("https://my-awesome-api.fake")
.delete("/plop")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", ""), { wrapper });
const res = await result.current.mutate("plop");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});

it("should call the correct url with a specific id (base in options)", async () => {
nock("https://my-awesome-api.fake")
.delete("/plop")
.reply(200, { id: 1 });

const wrapper = ({ children }) => <RestfulProvider base="https://not-here.fake">{children}</RestfulProvider>;
const { result } = renderHook(() => useMutate("DELETE", "", { base: "https://my-awesome-api.fake" }), {
wrapper,
});
const res = await result.current.mutate("plop");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});

it("should call the correct url with a specific id (base and path in options)", async () => {
nock("https://my-awesome-api.fake/user")
.delete("/plop")
.reply(200, { id: 1 });

const wrapper = ({ children }) => <RestfulProvider base="https://not-here.fake">{children}</RestfulProvider>;
const { result } = renderHook(() => useMutate("DELETE", "user", { base: "https://my-awesome-api.fake" }), {
wrapper,
});
const res = await result.current.mutate("plop");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});

it("should call the correct url without id", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", ""), {
wrapper,
});
const res = await result.current.mutate("");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});

it("should deal with query parameters", async () => {
nock("https://my-awesome-api.fake")
.delete("/")
.query({
myParam: true,
})
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("DELETE", "", { queryParams: { myParam: true } }), {
wrapper,
});
const res = await result.current.mutate("");

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});
});

describe("POST", () => {
it("should set loading to true after a call", async () => {
nock("https://my-awesome-api.fake")
.post("/plop")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", "plop"), { wrapper });
result.current.mutate();

expect(result.current).toMatchObject({
error: null,
loading: true,
});
});

it("should call the correct url", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", ""), { wrapper });
const res = await result.current.mutate();

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});

it("should send the correct body", async () => {
nock("https://my-awesome-api.fake")
.post("/", { foo: "bar" })
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", ""), { wrapper });
const res = await result.current.mutate({ foo: "bar" });

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 1 });
});

it("should return the data and the message on error", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(500, { error: "I can't, I'm just a chicken!" });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", ""), { wrapper });
try {
await result.current.mutate({ foo: "bar" });
expect("this statement").toBe("not executed");
} catch (e) {
expect(result.current).toMatchObject({
error: {
data: { error: "I can't, I'm just a chicken!" },
message: "Failed to fetch: 500 Internal Server Error",
status: 500,
},
loading: false,
});
expect(e).toEqual({
data: { error: "I can't, I'm just a chicken!" },
message: "Failed to fetch: 500 Internal Server Error",
status: 500,
});
}
});

it("should call the provider onError", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(500, { error: "I can't, I'm just a chicken!" });

const onError = jest.fn();
const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake" onError={onError}>
{children}
</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", ""), { wrapper });
await result.current.mutate({ foo: "bar" }).catch(() => {
/* noop */
});
expect(onError).toBeCalledWith(
{
data: { error: "I can't, I'm just a chicken!" },
message: "Failed to fetch: 500 Internal Server Error",
status: 500,
},
expect.any(Function), // retry
expect.any(Object), // response
);
});

it("should be able to retry after error", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(401, { message: "You shall not pass!" });
nock("https://my-awesome-api.fake")
.post("/")
.reply(200, { message: "You shall pass :)" });

const onError = jest.fn();
const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake" onError={onError}>
{children}
</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", ""), { wrapper });

await result.current.mutate().catch(() => {
/* noop */
});

expect(onError).toBeCalledWith(
{
data: { message: "You shall not pass!" },
message: "Failed to fetch: 401 Unauthorized",
status: 401,
},
expect.any(Function), // retry
expect.any(Object), // response
);

const data = await onError.mock.calls[0][1](); // call retry
expect(data).toEqual({ message: "You shall pass :)" });
});

it("should not call the provider onError if localErrorOnly is true", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(500, { error: "I can't, I'm just a chicken!" });

const onError = jest.fn();
const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake" onError={onError}>
{children}
</RestfulProvider>
);
const { result } = renderHook(() => useMutate("POST", "", { localErrorOnly: true }), { wrapper });
await result.current.mutate({ foo: "bar" }).catch(() => {
/* noop */
});
expect(onError).not.toBeCalled();
});

it("should transform the data with the resolve function", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(
() => useMutate<{ id: number }>("POST", "", { resolve: data => ({ id: data.id * 2 }) }),
{ wrapper },
);
const res = await result.current.mutate();

expect(result.current).toMatchObject({
error: null,
loading: false,
});
expect(res).toEqual({ id: 2 });
});

it("should forward the resolve error", async () => {
nock("https://my-awesome-api.fake")
.post("/")
.reply(200, { id: 1 });

const wrapper = ({ children }) => (
<RestfulProvider base="https://my-awesome-api.fake">{children}</RestfulProvider>
);
const { result } = renderHook(
() =>
useMutate<{ id: number }>("POST", "", {
resolve: () => {
throw new Error("I don't like your data!");
},
}),
{ wrapper },
);

try {
await result.current.mutate();
expect("this statement").toBe("not executed");
} catch (e) {
expect(result.current).toMatchObject({
error: {
data: "I don't like your data!",
message: "Failed to resolve: I don't like your data!",
},
loading: false,
});
expect(e.message).toEqual("I don't like your data!");
}
});
});
});
Loading

0 comments on commit 3ef62e7

Please sign in to comment.