From 524b8911e13264eac2395b6a7ab8c4bdfab68b23 Mon Sep 17 00:00:00 2001 From: Aura Alba Date: Wed, 11 Oct 2023 09:53:28 -0500 Subject: [PATCH] feat: Add instructors component --- .../AddInstructors/_test_/index.test.jsx | 45 ++++++++ .../AddInstructors/_test_/reducer.test.jsx | 53 +++++++++ .../Instructors/AddInstructors/index.jsx | 107 ++++++++++++++++++ .../Instructors/AddInstructors/reducer.jsx | 30 +++++ .../Instructors/InstructorsFilters/index.jsx | 2 +- .../Instructors/InstructorsPage/index.jsx | 3 +- src/features/Instructors/actionTypes.js | 4 + src/features/Instructors/data/api.js | 8 ++ 8 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 src/features/Instructors/AddInstructors/_test_/index.test.jsx create mode 100644 src/features/Instructors/AddInstructors/_test_/reducer.test.jsx create mode 100644 src/features/Instructors/AddInstructors/index.jsx create mode 100644 src/features/Instructors/AddInstructors/reducer.jsx diff --git a/src/features/Instructors/AddInstructors/_test_/index.test.jsx b/src/features/Instructors/AddInstructors/_test_/index.test.jsx new file mode 100644 index 00000000..3cace2a7 --- /dev/null +++ b/src/features/Instructors/AddInstructors/_test_/index.test.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import axios from 'axios'; +import { render, fireEvent, act } from '@testing-library/react'; +import AddInstructors from 'features/Instructors/AddInstructors'; +import '@testing-library/jest-dom/extend-expect'; + +jest.mock('axios'); + +jest.mock('@edx/frontend-platform/logging', () => ({ + logError: jest.fn(), +})); + +const mockResponse = { + data: [ + { + classId: 'CCX1', + className: 'CCX 1', + masterCourseName: 'Master1', + }, + { + classId: 'CCX2', + className: 'CCX 2', + masterCourseName: 'Master2', + }, + ], +}; + +describe('Add instructor component', () => { + test('Render and load modal', async () => { + axios.get.mockResolvedValue(mockResponse); + + const { getByPlaceholderText, getByText } = render( + , + ); + const button = getByText('Add Instructor'); + await act(async () => { + fireEvent.click(button); + }); + + const instructorInfoInput = getByPlaceholderText('Enter Username or Email of the instructor'); + const ccxSelect = getByText('Select Class Name'); + expect(instructorInfoInput).toBeInTheDocument(); + expect(ccxSelect).toBeInTheDocument(); + }); +}); diff --git a/src/features/Instructors/AddInstructors/_test_/reducer.test.jsx b/src/features/Instructors/AddInstructors/_test_/reducer.test.jsx new file mode 100644 index 00000000..c9fda83e --- /dev/null +++ b/src/features/Instructors/AddInstructors/_test_/reducer.test.jsx @@ -0,0 +1,53 @@ +import { + FETCH_CCX_LIST_FAILURE, + FETCH_CCX_LIST_REQUEST, + FETCH_CCX_LIST_SUCCESS, +} from 'features/Instructors/actionTypes'; +import { RequestStatus } from 'features/constants'; +import reducer from 'features/Instructors/InstructorsPage/reducer'; + +describe('Instructor page reducers', () => { + const initialState = { + data: [], + error: null, + }; + + test('should handle FETCH_CCX_LIST_REQUEST', () => { + const state = { + ...initialState, + status: RequestStatus.LOADING, + }; + const action = { + type: FETCH_CCX_LIST_REQUEST, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle FETCH_CCX_LIST_SUCCESS', () => { + const state = { + ...initialState, + status: RequestStatus.SUCCESS, + count: 0, + }; + const action = { + type: FETCH_CCX_LIST_SUCCESS, + payload: { + data: [], + }, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle FETCH_CCX_LIST_FAILURE', () => { + const state = { + ...initialState, + status: RequestStatus.ERROR, + error: '', + }; + const action = { + type: FETCH_CCX_LIST_FAILURE, + payload: '', + }; + expect(reducer(state, action)).toEqual(state); + }); +}); diff --git a/src/features/Instructors/AddInstructors/index.jsx b/src/features/Instructors/AddInstructors/index.jsx new file mode 100644 index 00000000..939ed3e9 --- /dev/null +++ b/src/features/Instructors/AddInstructors/index.jsx @@ -0,0 +1,107 @@ +import React, { useState, useReducer, useEffect } from 'react'; +import { + Button, FormGroup, ModalDialog, useToggle, Form, +} from '@edx/paragon'; +import { getCCXList } from 'features/Instructors/data/api'; +import reducer from 'features/Instructors/AddInstructors/reducer'; +import { logError } from '@edx/frontend-platform/logging'; +import { camelCaseObject } from '@edx/frontend-platform'; + +import { FETCH_CCX_LIST_FAILURE, FETCH_CCX_LIST_REQUEST, FETCH_CCX_LIST_SUCCESS } from 'features/Instructors/actionTypes'; +import { RequestStatus } from 'features/constants'; + +const initialState = { + data: [], + status: RequestStatus.SUCCESS, + error: null, +}; + +const addInstructorState = { + instructorInfo: '', + ccxId: '', + ccxName: '', +}; + +const AddInstructors = () => { + const [state, dispatch] = useReducer(reducer, initialState); + const [isOpen, open, close] = useToggle(false); + const [addInstructorInfo, setAddInstructorInfo] = useState(addInstructorState); + + const fetchData = async () => { + dispatch({ type: FETCH_CCX_LIST_REQUEST }); + + try { + const response = camelCaseObject(await getCCXList()); + dispatch({ type: FETCH_CCX_LIST_SUCCESS, payload: response.data }); + } catch (error) { + dispatch({ type: FETCH_CCX_LIST_FAILURE, payload: error }); + logError(error); + } + }; + + const handleInstructorInput = (e) => { + setAddInstructorInfo({ + ...addInstructorInfo, + instructorInfo: e.target.value.trim(), + }); + }; + + const handleCcxSelect = (e) => { + const select = e.target; + setAddInstructorInfo({ + ...addInstructorInfo, + ccxId: select.value, + ccxName: select.options[select.selectedIndex].text, + }); + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( + <> + + + + + Add Instructor + + + + + + {state.data.map((ccx) => )} + + +
+ +
+
+
+
+ + ); +}; + +export default AddInstructors; diff --git a/src/features/Instructors/AddInstructors/reducer.jsx b/src/features/Instructors/AddInstructors/reducer.jsx new file mode 100644 index 00000000..2e3b604b --- /dev/null +++ b/src/features/Instructors/AddInstructors/reducer.jsx @@ -0,0 +1,30 @@ +import { + FETCH_CCX_LIST_FAILURE, + FETCH_CCX_LIST_REQUEST, + FETCH_CCX_LIST_SUCCESS, +} from 'features/Instructors/actionTypes'; +import { RequestStatus } from 'features/constants'; + +const reducer = (state, action) => { + switch (action.type) { + case FETCH_CCX_LIST_REQUEST: + return { ...state, status: RequestStatus.LOADING }; + case FETCH_CCX_LIST_SUCCESS: { + return { + ...state, + status: RequestStatus.SUCCESS, + data: action.payload, + }; + } + case FETCH_CCX_LIST_FAILURE: + return { + ...state, + status: RequestStatus.ERROR, + error: action.payload, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/features/Instructors/InstructorsFilters/index.jsx b/src/features/Instructors/InstructorsFilters/index.jsx index 8227f0a2..4284f1c5 100644 --- a/src/features/Instructors/InstructorsFilters/index.jsx +++ b/src/features/Instructors/InstructorsFilters/index.jsx @@ -31,7 +31,7 @@ const InstructorsFilters = ({ fetchData, resetPagination }) => { }; return ( - +
{

Instructors

+
-