diff --git a/src/components/App.js b/src/components/App.js index 32dabb5..db7c5de 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -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"; +import ManageCoursePage from "./courses/ManageCoursePage"; // eslint-disable-line import/no-named-as-default import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; diff --git a/src/components/courses/ManageCoursePage.js b/src/components/courses/ManageCoursePage.js index 3141097..ddcc4a8 100644 --- a/src/components/courses/ManageCoursePage.js +++ b/src/components/courses/ManageCoursePage.js @@ -9,7 +9,13 @@ import { newCourse } from "../../../tools/mockData"; import Spinner from "../common/Spinner"; import { toast } from "react-toastify"; -function ManageCoursePage({ courses, authors, actions, history, ...props }) { +export function ManageCoursePage({ + courses, + authors, + actions, + history, + ...props +}) { const [course, setCourse] = useState({ ...props.course }); const [errors, setErrors] = useState({}); const [saving, setSaving] = useState(false); diff --git a/src/components/courses/ManageCoursePage.test.js b/src/components/courses/ManageCoursePage.test.js new file mode 100644 index 0000000..cc35155 --- /dev/null +++ b/src/components/courses/ManageCoursePage.test.js @@ -0,0 +1,34 @@ +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(); +} + +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."); +}); diff --git a/src/redux/actions/courseActions.test.js b/src/redux/actions/courseActions.test.js new file mode 100644 index 0000000..cde1940 --- /dev/null +++ b/src/redux/actions/courseActions.test.js @@ -0,0 +1,52 @@ +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); + }); +}); diff --git a/src/redux/reducers/courseReducer.test.js b/src/redux/reducers/courseReducer.test.js new file mode 100644 index 0000000..0ae4e25 --- /dev/null +++ b/src/redux/reducers/courseReducer.test.js @@ -0,0 +1,51 @@ +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); +}); diff --git a/src/redux/store.test.js b/src/redux/store.test.js new file mode 100644 index 0000000..f93fcb6 --- /dev/null +++ b/src/redux/store.test.js @@ -0,0 +1,20 @@ +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); +});