code cleanup

master
Tudor Stanciu 2020-05-06 10:06:26 +03:00
parent 5ab7727de5
commit c4e49c0262
20 changed files with 416 additions and 3452 deletions

View File

@ -1,3 +1,5 @@
TO UPDATE
# Starter Kit for [Building Applications in React and Redux](http://www.pluralsight.com/author/cory-house) on Pluralsight
## Get Started

3333
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"name": "react-redux-course",
"name": "reverse-proxy-frontend",
"version": "1.0.0",
"private": true,
"description": "React and Redux Pluralsight course by Cory House",
@ -8,23 +8,12 @@
"start:dev": "webpack-dev-server --config webpack.config.dev.js --port 3000",
"prestart:api": "node tools/createMockDb.js",
"start:api": "node tools/apiServer.js",
"test": "jest --watch",
"test:ci": "jest",
"clean:build": "rimraf ./build && mkdir build",
"prebuild": "run-p clean:build test:ci",
"prebuild": "run-p clean:build",
"build": "webpack --config webpack.config.prod.js",
"postbuild": "run-p start:api serve:build",
"serve:build": "http-server ./build"
},
"jest": {
"setupFiles": [
"./tools/testSetup.js"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/tools/fileMock.js",
"\\.(css|less)$": "<rootDir>/tools/styleMock.js"
}
},
"dependencies": {
"bootstrap": "4.3.1",
"immer": "2.1.3",
@ -45,8 +34,6 @@
"babel-preset-react-app": "7.0.2",
"css-loader": "2.1.1",
"cssnano": "4.1.10",
"enzyme": "3.9.0",
"enzyme-adapter-react-16": "1.11.2",
"eslint": "5.15.2",
"eslint-loader": "2.1.2",
"eslint-plugin-import": "2.16.0",
@ -54,7 +41,6 @@
"fetch-mock": "7.3.1",
"html-webpack-plugin": "3.2.0",
"http-server": "0.9.0",
"jest": "24.5.0",
"json-server": "0.14.2",
"mini-css-extract-plugin": "0.5.0",
"node-fetch": "^2.3.0",
@ -97,8 +83,7 @@
"env": {
"browser": true,
"node": true,
"es6": true,
"jest": true
"es6": true
},
"rules": {
"no-debugger": "off",

6
src/api/systemApi.js Normal file
View File

@ -0,0 +1,6 @@
import { handleResponse, handleError } from "./apiUtils";
const baseUrl = process.env.REVERSE_PROXY_API_URL + "/system";
export function getSystemDateTime() {
return fetch(`${baseUrl}/datetime`).then(handleResponse).catch(handleError);
}

View File

@ -5,7 +5,7 @@ import AboutPage from "./about/AboutPage";
import Header from "./common/Header";
import PageNotFound from "./PageNotFound";
import CoursesPage from "./courses/CoursesPage";
import ManageCoursePage from "./courses/ManageCoursePage"; // eslint-disable-line import/no-named-as-default
import ManageCoursePage from "./courses/ManageCoursePage";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

View File

@ -1,22 +0,0 @@
import React from "react";
import Header from "./Header";
import { mount, shallow } from "enzyme";
import { MemoryRouter } from "react-router-dom";
// Note how with shallow render you search for the React component tag
it("contains 3 NavLinks via shallow", () => {
const numLinks = shallow(<Header />).find("NavLink").length;
expect(numLinks).toEqual(3);
});
// Note how with mount you search for the final rendered HTML since it generates the final DOM.
// We also need to pull in React Router's memoryRouter for testing since the Header expects to have React Router's props passed in.
it("contains 3 anchors via mount", () => {
const numAnchors = mount(
<MemoryRouter>
<Header />
</MemoryRouter>
).find("a").length;
expect(numAnchors).toEqual(3);
});

View File

@ -1,34 +0,0 @@
import React from "react";
import CourseForm from "./CourseForm";
import { shallow } from "enzyme";
function renderCourseForm(args) {
const defaultProps = {
authors: [],
course: {},
saving: false,
errors: {},
onSave: () => {},
onChange: () => {}
};
const props = { ...defaultProps, ...args };
return shallow(<CourseForm {...props} />);
}
it("renders form and header", () => {
const wrapper = renderCourseForm();
// console.log(wrapper.debug());
expect(wrapper.find("form").length).toBe(1);
expect(wrapper.find("h2").text()).toEqual("Add Course");
});
it('labels save buttons as "Save" when not saving', () => {
const wrapper = renderCourseForm();
expect(wrapper.find("button").text()).toBe("Save");
});
it('labels save button as "Saving..." when saving', () => {
const wrapper = renderCourseForm({ saving: true });
expect(wrapper.find("button").text()).toBe("Saving...");
});

View File

@ -1,35 +0,0 @@
import React from "react";
import { cleanup, render } from "react-testing-library";
import CourseForm from "./CourseForm";
afterEach(cleanup);
function renderCourseForm(args) {
let defaultProps = {
authors: [],
course: {},
saving: false,
errors: {},
onSave: () => {},
onChange: () => {}
};
const props = { ...defaultProps, ...args };
return render(<CourseForm {...props} />);
}
it("should render Add Course header", () => {
const { getByText } = renderCourseForm();
getByText("Add Course");
});
it('should label save button as "Save" when not saving', () => {
const { getByText } = renderCourseForm();
getByText("Save");
});
it('should label save button as "Saving..." when saving', () => {
const { getByText, debug } = renderCourseForm({ saving: true });
// debug();
getByText("Saving...");
});

View File

@ -1,32 +0,0 @@
import React from "react";
import CourseForm from "./CourseForm";
import renderer from "react-test-renderer";
import { courses, authors } from "../../../tools/mockData";
it("sets submit button label 'Saving...' when saving is true", () => {
const tree = renderer.create(
<CourseForm
course={courses[0]}
authors={authors}
onSave={jest.fn()}
onChange={jest.fn()}
saving
/>
);
expect(tree).toMatchSnapshot();
});
it("sets submit button label 'Save' when saving is false", () => {
const tree = renderer.create(
<CourseForm
course={courses[0]}
authors={authors}
onSave={jest.fn()}
onChange={jest.fn()}
saving={false}
/>
);
expect(tree).toMatchSnapshot();
});

View File

@ -9,13 +9,7 @@ import { newCourse } from "../../../tools/mockData";
import Spinner from "../common/Spinner";
import { toast } from "react-toastify";
export function ManageCoursePage({
courses,
authors,
actions,
history,
...props
}) {
function ManageCoursePage({ courses, authors, actions, history, ...props }) {
const [course, setCourse] = useState({ ...props.course });
const [errors, setErrors] = useState({});
const [saving, setSaving] = useState(false);

View File

@ -1,34 +0,0 @@
import React from "react";
import { mount } from "enzyme";
import { authors, newCourse, courses } from "../../../tools/mockData";
import { ManageCoursePage } from "./ManageCoursePage";
function render(args) {
const defaultProps = {
authors,
courses,
// Passed from React Router in real app, so just stubbing in for test.
// Could also choose to use MemoryRouter as shown in Header.test.js,
// or even wrap with React Router, depending on whether I
// need to test React Router related behavior.
history: {},
actions: {
saveCourse: jest.fn(),
loadAuthors: jest.fn(),
loadCourses: jest.fn()
},
course: newCourse,
match: {}
};
const props = { ...defaultProps, ...args };
return mount(<ManageCoursePage {...props} />);
}
it("sets error when attempting to save an empty title field", () => {
const wrapper = render();
wrapper.find("form").simulate("submit");
const error = wrapper.find(".alert").first();
expect(error.text()).toBe("Title is required.");
});

View File

@ -1,197 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sets submit button label 'Save' when saving is false 1`] = `
<form
onSubmit={[MockFunction]}
>
<h2>
Edit
Course
</h2>
<div
className="form-group"
>
<label
htmlFor="title"
>
Title
</label>
<div
className="field"
>
<input
className="form-control"
name="title"
onChange={[MockFunction]}
type="text"
value="Securing React Apps with Auth0"
/>
</div>
</div>
<div
className="form-group"
>
<label
htmlFor="authorId"
>
Author
</label>
<div
className="field"
>
<select
className="form-control"
name="authorId"
onChange={[MockFunction]}
value={1}
>
<option
value=""
>
Select Author
</option>
<option
value={1}
>
Cory House
</option>
<option
value={2}
>
Scott Allen
</option>
<option
value={3}
>
Dan Wahlin
</option>
</select>
</div>
</div>
<div
className="form-group"
>
<label
htmlFor="category"
>
Category
</label>
<div
className="field"
>
<input
className="form-control"
name="category"
onChange={[MockFunction]}
type="text"
value="JavaScript"
/>
</div>
</div>
<button
className="btn btn-primary"
disabled={false}
type="submit"
>
Save
</button>
</form>
`;
exports[`sets submit button label 'Saving...' when saving is true 1`] = `
<form
onSubmit={[MockFunction]}
>
<h2>
Edit
Course
</h2>
<div
className="form-group"
>
<label
htmlFor="title"
>
Title
</label>
<div
className="field"
>
<input
className="form-control"
name="title"
onChange={[MockFunction]}
type="text"
value="Securing React Apps with Auth0"
/>
</div>
</div>
<div
className="form-group"
>
<label
htmlFor="authorId"
>
Author
</label>
<div
className="field"
>
<select
className="form-control"
name="authorId"
onChange={[MockFunction]}
value={1}
>
<option
value=""
>
Select Author
</option>
<option
value={1}
>
Cory House
</option>
<option
value={2}
>
Scott Allen
</option>
<option
value={3}
>
Dan Wahlin
</option>
</select>
</div>
</div>
<div
className="form-group"
>
<label
htmlFor="category"
>
Category
</label>
<div
className="field"
>
<input
className="form-control"
name="category"
onChange={[MockFunction]}
type="text"
value="JavaScript"
/>
</div>
</div>
<button
className="btn btn-primary"
disabled={true}
type="submit"
>
Saving...
</button>
</form>
`;

View File

@ -1,3 +0,0 @@
it("should pass", () => {
expect(true).toEqual(true);
});

View File

@ -1,52 +0,0 @@
import * as courseActions from "./courseActions";
import * as types from "./actionTypes";
import { courses } from "../../../tools/mockData";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import configureMockStore from "redux-mock-store";
// Test an async action
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
describe("Async Actions", () => {
afterEach(() => {
fetchMock.restore();
});
describe("Load Courses Thunk", () => {
it("should create BEGIN_API_CALL and LOAD_COURSES_SUCCESS when loading courses", () => {
fetchMock.mock("*", {
body: courses,
headers: { "content-type": "application/json" }
});
const expectedActions = [
{ type: types.BEGIN_API_CALL },
{ type: types.LOAD_COURSES_SUCCESS, courses }
];
const store = mockStore({ courses: [] });
return store.dispatch(courseActions.loadCourses()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
});
describe("createCourseSuccess", () => {
it("should create a CREATE_COURSE_SUCCESS action", () => {
//arrange
const course = courses[0];
const expectedAction = {
type: types.CREATE_COURSE_SUCCESS,
course
};
//act
const action = courseActions.createCourseSuccess(course);
//assert
expect(action).toEqual(expectedAction);
});
});

View File

@ -1,51 +0,0 @@
import courseReducer from "./courseReducer";
import * as actions from "../actions/courseActions";
it("should add course when passed CREATE_COURSE_SUCCESS", () => {
// arrange
const initialState = [
{
title: "A"
},
{
title: "B"
}
];
const newCourse = {
title: "C"
};
const action = actions.createCourseSuccess(newCourse);
// act
const newState = courseReducer(initialState, action);
// assert
expect(newState.length).toEqual(3);
expect(newState[0].title).toEqual("A");
expect(newState[1].title).toEqual("B");
expect(newState[2].title).toEqual("C");
});
it("should update course when passed UPDATE_COURSE_SUCCESS", () => {
// arrange
const initialState = [
{ id: 1, title: "A" },
{ id: 2, title: "B" },
{ id: 3, title: "C" }
];
const course = { id: 2, title: "New Title" };
const action = actions.updateCourseSuccess(course);
// act
const newState = courseReducer(initialState, action);
const updatedCourse = newState.find((a) => a.id == course.id);
const untouchedCourse = newState.find((a) => a.id == 1);
// assert
expect(updatedCourse.title).toEqual("New Title");
expect(untouchedCourse.title).toEqual("A");
expect(newState.length).toEqual(3);
});

View File

@ -1,20 +0,0 @@
import { createStore } from "redux";
import rootReducer from "./reducers";
import initialState from "./reducers/initialState";
import * as courseActions from "./actions/courseActions";
it("Should handle creating courses", function () {
// arrange
const store = createStore(rootReducer, initialState);
const course = {
title: "Clean Code"
};
// act
const action = courseActions.createCourseSuccess(course);
store.dispatch(action);
// assert
const createdCourse = store.getState().courses[0];
expect(createdCourse).toEqual(course);
});

View File

@ -1,2 +0,0 @@
// Mocks file imports for Jest. As suggested by https://jestjs.io/docs/en/webpack
module.exports = "test-file-stub";

View File

@ -1,2 +0,0 @@
// Mocks CSS imports for Jest. As suggested by https://jestjs.io/docs/en/webpack
module.exports = {};

View File

@ -1,3 +0,0 @@
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });

View File

@ -24,7 +24,10 @@ module.exports = {
},
plugins: [
new webpack.DefinePlugin({
"process.env.API_URL": JSON.stringify("http://localhost:3001")
"process.env.API_URL": JSON.stringify("http://localhost:3001"),
"process.env.REVERSE_PROXY_API_URL": JSON.stringify(
"http://localhost:5050"
)
}),
new HtmlWebpackPlugin({
template: "src/index.html",