Skip to content

Commit

Permalink
Merge branch 'feature/fetch-jsonp' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
imgarylai committed Feb 6, 2022
2 parents 9da2174 + 4ac5cd3 commit 004bd0c
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 81 deletions.
296 changes: 250 additions & 46 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"react": ">= 16.8.0"
},
"dependencies": {
"jsonp": "^0.2.1",
"fetch-jsonp": "1.2.1",
"query-string": "^7.0.0"
},
"devDependencies": {
Expand All @@ -47,6 +47,7 @@
"@commitlint/prompt": "16.1.0",
"@semantic-release/changelog": "6.0.1",
"@semantic-release/git": "10.0.1",
"@testing-library/react": "12.1.2",
"@testing-library/react-hooks": "7.0.2",
"@types/jest": "27.4.0",
"@types/jsonp": "0.2.1",
Expand Down
24 changes: 24 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type ErrorWithMessage = {
message: string;
};

export const isErrorWithMessage = (error: unknown): error is ErrorWithMessage =>
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof (error as Record<string, unknown>).message === "string";

export const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => {
if (isErrorWithMessage(maybeError)) return maybeError;

try {
return new Error(JSON.parse(JSON.stringify(maybeError)));
} catch {
// fallback in case there's an error stringifying the maybeError
// like with circular references for example.
return new Error(String(maybeError));
}
};

export const getErrorMessage = (error: unknown) =>
toErrorWithMessage(error).message;
33 changes: 19 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import jsonp from "jsonp";
import fetchJsonp from "fetch-jsonp";
import queryString from "query-string";
import { ChangeEvent, useState } from "react";
import { getErrorMessage } from "./errors";

interface Params {
export interface Params {
[key: string]: unknown;
}

Expand Down Expand Up @@ -36,24 +37,27 @@ export const useMailChimpForm: (url: string) => {
const [status, setStatus] = useState(initStatusState);
const [message, setMessage] = useState("");

const handleSubmit = (params: Params): void => {
const handleSubmit = async (params: Params): Promise<void> => {
const query = queryString.stringify(params);
const endpoint = url.replace("/post?", "/post-json?") + "&" + query;
setStatus({ ...status, loading: true });
setMessage("");
jsonp(endpoint, { param: "c" }, (error, data) => {
if (error) {
setStatus({ ...initStatusState, error: true });
setMessage(error.message);
try {
const response: fetchJsonp.Response = await fetchJsonp(endpoint, {
jsonpCallback: "c",
});

if (response.ok) {
setStatus({ ...initStatusState, success: true });
} else {
if (data.result !== "success") {
setStatus({ ...initStatusState, error: true });
} else {
setStatus({ ...initStatusState, success: true });
}
setMessage(data.msg);
setStatus({ ...initStatusState, error: true });
}
});
const data = await response.json();
setMessage(data.msg);
} catch (error) {
setStatus({ ...initStatusState, error: true });
setMessage(getErrorMessage(error));
}
};

const reset: () => void = () => {
Expand All @@ -62,6 +66,7 @@ export const useMailChimpForm: (url: string) => {
};

return {
status: status,
loading: status.loading,
success: status.success,
error: status.error,
Expand Down
6 changes: 6 additions & 0 deletions tests/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const TEST_MAILCHIMP_URL =
"https://example.us20.list-manage.com/subscribe/post?u=example&amp;id=example";

export const TEST_EMAIL = "[email protected]";

export const TEST_MESSAGE = "TESTING Message";
27 changes: 27 additions & 0 deletions tests/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getErrorMessage } from "../src/errors";

describe("Error messages", () => {
test("should get error message from error object", () => {
const error = new Error("Test error");
expect(getErrorMessage(error)).toEqual(error.message);
});

test("should get error message from error json object", () => {
const errorJson = { message: "Test Error" };
expect(getErrorMessage(errorJson)).toEqual(errorJson.message);
});

test("should return error message from error string", () => {
const errorString = "Test Error";
expect(getErrorMessage(errorString)).toEqual(errorString);
});

test("should return error message if error message has cyclic object value", () => {
const jsonMock = jest.spyOn(JSON, "stringify");
jsonMock.mockImplementation(() => {
throw new TypeError("cyclic object value");
});
const errorString = "Test Error";
expect(getErrorMessage(errorString)).toEqual("Test Error");
});
});
20 changes: 0 additions & 20 deletions tests/useMailChimpForm.test.ts

This file was deleted.

34 changes: 34 additions & 0 deletions tests/useMailChimpForm/handleSubmitFail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { act, renderHook } from "@testing-library/react-hooks";
import fetchJsonp from "fetch-jsonp";
import { useMailChimpForm } from "../../src";
import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const";

jest.mock("fetch-jsonp", () =>
jest.fn().mockImplementation(() =>
Promise.resolve({
ok: false,
json: () => Promise.resolve({ msg: "TESTING Message" }),
})
)
);

describe("handleSubmit mailchimp response shows not ok", () => {
beforeEach(() => {
jest.resetModules();
});

test("should receive failure status and message", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useMailChimpForm(TEST_MAILCHIMP_URL)
);
act(() => {
result.current.handleSubmit({ email: TEST_EMAIL });
});
await waitForNextUpdate();
expect(fetchJsonp).toHaveBeenCalled();
expect(result.current.loading).toBe(false);
expect(result.current.success).toBe(false);
expect(result.current.error).toBe(true);
expect(result.current.message).toEqual(TEST_MESSAGE);
});
});
31 changes: 31 additions & 0 deletions tests/useMailChimpForm/handleSubmitFetchJsonpException.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { act, renderHook } from "@testing-library/react-hooks";
import fetchJsonp from "fetch-jsonp";
import { useMailChimpForm } from "../../src";
import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const";

jest.mock("fetch-jsonp", () =>
jest
.fn()
.mockImplementation(() => Promise.reject(new Error("TESTING Message")))
);

describe("handleSubmit fetchJsonp throw errors", () => {
beforeEach(() => {
jest.resetModules();
});

test("should receive failure status and message", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useMailChimpForm(TEST_MAILCHIMP_URL)
);
act(() => {
result.current.handleSubmit({ email: TEST_EMAIL });
});
await waitForNextUpdate();
expect(fetchJsonp).toHaveBeenCalled();
expect(result.current.loading).toBe(false);
expect(result.current.success).toBe(false);
expect(result.current.error).toBe(true);
expect(result.current.message).toEqual(TEST_MESSAGE);
});
});
41 changes: 41 additions & 0 deletions tests/useMailChimpForm/handleSubmitSuccess.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { act, renderHook } from "@testing-library/react-hooks";
import fetchJsonp from "fetch-jsonp";
import { useMailChimpForm } from "../../src";
import { TEST_EMAIL, TEST_MAILCHIMP_URL, TEST_MESSAGE } from "../const";

jest.mock("fetch-jsonp", () =>
jest.fn().mockImplementation(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ msg: "TESTING Message" }),
})
)
);

describe("handleSubmit user subscribes successfully", () => {
beforeEach(() => {
jest.resetModules();
});

test("should receive success status and message", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useMailChimpForm(TEST_MAILCHIMP_URL)
);
act(() => {
result.current.handleSubmit({ email: TEST_EMAIL });
});
await waitForNextUpdate();
expect(fetchJsonp).toHaveBeenCalled();
expect(result.current.loading).toBe(false);
expect(result.current.success).toBe(true);
expect(result.current.error).toBe(false);
expect(result.current.message).toEqual(TEST_MESSAGE);
act(() => {
result.current.reset();
});
expect(result.current.loading).toBe(false);
expect(result.current.success).toBe(false);
expect(result.current.error).toBe(false);
expect(result.current.message).toEqual("");
});
});
28 changes: 28 additions & 0 deletions tests/useMailChimpForm/renderHooks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { act, renderHook } from "@testing-library/react-hooks";
import { useFormFields, useMailChimpForm } from "../../src";
import { TEST_EMAIL, TEST_MAILCHIMP_URL } from "../const";

test("should return initial mailchimp form state", () => {
const { result } = renderHook(() => useFormFields({ email: "" }));
expect(result.current.fields).toEqual({ email: "" });
});

test("should return initial mailchimp form status", () => {
const { result } = renderHook(() => useMailChimpForm(TEST_MAILCHIMP_URL));
expect(result.current.success).toBe(false);
expect(result.current.error).toBe(false);
expect(result.current.loading).toBe(false);
expect(result.current.message).toBe("");
});

test("should return initial mailchimp form state", () => {
const { result } = renderHook(() => useFormFields({ email: "" }));
act(() => {
result.current.handleFieldChange({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
target: { id: "email", value: TEST_EMAIL },
});
});
expect(result.current.fields).toEqual({ email: TEST_EMAIL });
});

0 comments on commit 004bd0c

Please sign in to comment.