Skip to content

Commit

Permalink
#4 - Added the ability to add headers in the routes
Browse files Browse the repository at this point in the history
  • Loading branch information
boyney123 committed May 23, 2019
1 parent b3cbd4e commit 8b26c4b
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 141 deletions.
31 changes: 31 additions & 0 deletions client/src/components/HeaderInput/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useState, useEffect } from "react";
import uuid from "uuid/v4";

export default function({ index, data = {}, onBlur = () => {}, onRemove = () => {} } = {}) {
const { id = uuid(), header: initialHeader, value: initialValue } = data;

const [header, setHeader] = useState(initialHeader);
const [value, setValue] = useState(initialValue);

const update = (field, inputValue) => {
field === "header" ? setHeader(inputValue) : setValue(inputValue);
};

useEffect(() => {
if (header && value) onBlur({ id, header, value });
}, [header, value]);

return (
<div className="columns Header" aria-label="header">
<div className="control column">
<input className="input" placeholder="header" value={header} aria-label="header-key" onChange={e => update("header", e.target.value)} />
</div>
<div className="control column">
<input className="input" placeholder="value" value={value} aria-label="header-value" onChange={e => update("value", e.target.value)} />
</div>
<div className="control column is-1 Header__Remove" onClick={() => onRemove(id)}>
<i className="far fa-times-circle" aria-label="remove-header" />
</div>
</div>
);
}
55 changes: 55 additions & 0 deletions client/src/components/HeaderInput/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// __tests__/fetch.test.js
import React from "react";
import { render, fireEvent, cleanup } from "react-testing-library";
import "jest-dom/extend-expect";
import HeaderInput from "./";

afterEach(cleanup);

describe("DoubleInput", () => {
describe("renders", () => {
it("two inputs one for the header and one for the value", () => {
const { getByPlaceholderText } = render(<HeaderInput />);
expect(getByPlaceholderText("header")).toBeVisible();
expect(getByPlaceholderText("value")).toBeVisible();
});

it('two inputs are rendered with the given "header" and "value" data when given to the component', () => {
const { getByPlaceholderText } = render(<HeaderInput data={{ id: 1, header: "Content-Type", value: "application/json" }} />);
expect(getByPlaceholderText("header").value).toEqual("Content-Type");
expect(getByPlaceholderText("value").value).toEqual("application/json");
});
});

describe("props: events", () => {
it('onBlur is called when both "header" and "value" have been entered', () => {
const spy = jest.fn();
const { getByPlaceholderText } = render(<HeaderInput onBlur={spy} />);
fireEvent.change(getByPlaceholderText("header"), { target: { value: "Content-Type" } });
fireEvent.change(getByPlaceholderText("value"), { target: { value: "application/json" } });
expect(spy).toHaveBeenCalled();
});

it('onBlur is not called when "header" value is set but "value" is missing', () => {
const spy = jest.fn();
const { getByPlaceholderText } = render(<HeaderInput onBlur={spy} />);
fireEvent.change(getByPlaceholderText("header"), { target: { value: "Content-Type" } });
expect(spy).not.toHaveBeenCalled();
});

it('onBlur is not called when "value" is set but the "header" value is not', () => {
const spy = jest.fn();
const { getByPlaceholderText } = render(<HeaderInput onBlur={spy} />);
fireEvent.change(getByPlaceholderText("value"), { target: { value: "application/json" } });
expect(spy).not.toHaveBeenCalled();
});

it("onRemove is called with the headers id when the user clicks on the remove icon", () => {
const spy = jest.fn();
const data = { id: 1, header: "Content-Type", value: "application/json" };
const { getByLabelText } = render(<HeaderInput data={data} onRemove={spy} />);
fireEvent.click(getByLabelText("remove-header"));
expect(spy).toHaveBeenCalledWith(1);
});
});
});
68 changes: 29 additions & 39 deletions client/src/components/RouteModal/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from "react";
import JSONInput from "react-json-editor-ajrm";
import HeaderInput from "../HeaderInput";
import { HttpMethods, StatusCodes } from "../../utils/consts";
import { updateRoute as updateRouteRequest, createNewRoute } from "../../utils/routes-api";
import faker from "faker";
Expand All @@ -8,35 +9,9 @@ import uuid from "uuid/v4";
const HTTP_METHOD_LIST = [HttpMethods.GET, HttpMethods.POST, HttpMethods.PUT, HttpMethods.DELETE];
const STATUS_CODES = [StatusCodes.OK, StatusCodes.CREATED, StatusCodes.NO_CONTENT, StatusCodes.BAD_REQUEST, StatusCodes.FORBIDDEN, StatusCodes.INTERNAL_SERVER_ERROR];

const HeaderInput = function({ index, header: initialHeader, value: initialValue, onBlur = () => {}, onChange = () => {}, onRemove = () => {} } = {}) {
const [header, setHeader] = useState(initialHeader);
const [value, setValue] = useState(initialValue);

const update = (field, inputValue) => {
field === "header" ? setHeader(inputValue) : setValue(inputValue);
};

useEffect(() => {
if (header && value) onBlur({ index, header, value });
}, [header, value]);

return (
<div className="columns Header">
<div className="control column">
<input className="input" placeHolder="Key" value={header} aria-label="header-key" onChange={e => update("header", e.target.value)} />
</div>
<div className="control column">
<input className="input" placeHolder="Value" value={value} aria-label="header-value" onChange={e => update("value", e.target.value)} />
</div>
<div className="control column is-1 Header__Remove" onClick={onRemove}>
<i class="far fa-times-circle" />
</div>
</div>
);
};

/**
* add header adds some input fields
*
* Clicking X removes the field from the array....
*/

Expand All @@ -55,16 +30,30 @@ const Modal = function(props) {

const modalTitle = isNewRoute ? "Add Route" : "Edit Route";

const setHeader = header => {
const { index } = header;
const newHeaders = headers.concat([]);
newHeaders[index] = header;
console.log("header", headers);
updateHeaders(newHeaders);
const setHeader = updatedHeader => {
const { id } = updatedHeader;

const updatedHeaders = headers.map((header, index) => {
if (id !== header.id) return header;
return {
...header,
...updatedHeader
};
});

updateHeaders(updatedHeaders);
};

const addNewHeader = () => updateHeaders(headers.concat([{ id: uuid(), header: "", value: "" }]));
const removeHeader = route => {
const filteredHeaders = headers.filter(({ id } = {}) => id !== route);
updateHeaders(filteredHeaders);
};

const saveChanges = async () => {
try {
const cleanedHeaders = headers.filter(({ header, value }) => header !== "" && value !== "");

const data = {
...editedRoute,
route,
Expand All @@ -73,7 +62,7 @@ const Modal = function(props) {
delay,
payload,
disabled,
headers
headers: cleanedHeaders
};

isNewRoute ? await createNewRoute(data) : await updateRouteRequest(data);
Expand All @@ -82,8 +71,6 @@ const Modal = function(props) {
}
};

console.log("headers", headers);

return (
<>
<div className="modal is-active" data-testid="route-modal">
Expand Down Expand Up @@ -162,11 +149,14 @@ const Modal = function(props) {
</div>
<hr />
<div className="field mt10">
<label className="label">Response Headers (optional)</label>
<label className="label" aria-label="no-headers-message">
Response Headers (optional)
</label>
{headers.length === 0 && <i>No headers added.</i>}
{headers.map((header, index) => {
return <HeaderInput index={index} {...header} onBlur={setHeader} />;
return <HeaderInput data={header} onBlur={setHeader} onRemove={removeHeader} />;
})}
<button aria-label="route-save" className="button is-primary is-small is-pulled-right" onClick={saveChanges}>
<button aria-label="add-header" className="button is-primary is-small is-pulled-right" onClick={addNewHeader}>
Add Header
</button>
</div>
Expand Down
54 changes: 53 additions & 1 deletion client/src/components/RouteModal/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const buildRoute = () => {
delay: "0",
disabled: false,
payload: { test: true },
statusCode: "200"
statusCode: "200",
headers: [{ id: 1, header: "Content-Type", value: "application/json" }, { id: 2, header: "x-api-key", value: "test" }]
};
};

Expand Down Expand Up @@ -97,6 +98,20 @@ describe("Route Modal", () => {
expect(getByLabelText("route-randomly-generate-data")).toBeVisible();
});

it("with a list of headers when the route has headers", () => {
const route = buildRoute();
const { getAllByLabelText } = render(<RouteModal route={route} />);
const headers = getAllByLabelText("header");
expect(headers).toHaveLength(2);
});

it('a message saying "No headers added" when no headers are in the route', () => {
const route = buildRoute();
delete route["headers"];
const { getByLabelText } = render(<RouteModal route={route} />);
expect(getByLabelText("no-headers-message")).toBeVisible();
});

it("with a checkbox to disable the route", () => {
const route = buildRoute();
const { getByLabelText } = render(<RouteModal route={route} />);
Expand All @@ -111,6 +126,43 @@ describe("Route Modal", () => {
});
});

describe("headers", () => {
it("when clicking `Add Header` two new inputs are shown", () => {
const route = buildRoute();
delete route["headers"];
const { getByLabelText } = render(<RouteModal route={route} />);
fireEvent.click(getByLabelText("add-header"));

expect(getByLabelText("header")).toBeVisible();
expect(getByLabelText("header-key").value).toBe("");
expect(getByLabelText("header-value").value).toBe("");
});

it("when clicking the remove button on the header the header is removed", () => {
const route = buildRoute();
delete route["headers"];
route["headers"] = [{ id: "1", header: "x-api-key", value: "test" }];

const { getByLabelText, queryByLabelText } = render(<RouteModal route={route} />);
fireEvent.click(getByLabelText("remove-header"));

expect(queryByLabelText("header")).toBeNull();
});

it("when clicking `Save Route` with empty headers on the screen they are removed before sending the data", () => {
const route = buildRoute();
route.headers.push({ id: 99, header: "", value: "" });

const { getByLabelText } = render(<RouteModal route={route} />);
fireEvent.click(getByLabelText("route-save"));

const expectedResult = Object.assign({}, route);
expectedResult.routes = expectedResult.headers.pop();

expect(utils.updateRoute).toHaveBeenCalledWith(route);
});
});

describe("modal", () => {
it("when entering a value into the route input field the value is updated", () => {
const { getByLabelText } = render(<RouteModal route={buildRoute()} />);
Expand Down
2 changes: 2 additions & 0 deletions client/src/config/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
},
"headers": [
{
"id": "1",
"header": "Accept",
"value": "application/json"
},
{
"id": "2",
"header": "x-api-key",
"value": "abcdef12345"
}
Expand Down
Loading

0 comments on commit 8b26c4b

Please sign in to comment.