diff --git a/src/users/data/api.js b/src/users/data/api.js index 35fe488c8..ebe063afa 100644 --- a/src/users/data/api.js +++ b/src/users/data/api.js @@ -304,7 +304,43 @@ export async function postEntitlement({ } } -export async function postEnrollmentChange({ +export async function postEnrollment({ + user, + courseID, + mode, + reason, +}) { + try { + const { data } = await getAuthenticatedHttpClient().post( + AppUrls.getEnrollmentsUrl(user), + { + course_id: courseID, + mode, + reason, + }, + ); + return data; + } catch (error) { + if (error.customAttributes.httpErrorStatus === 400) { + // eslint-disable-next-line no-console + console.log(JSON.parse(error.customAttributes.httpErrorResponseData)); + } + return { + errors: [ + { + code: null, + dismissible: true, + text: + 'There was an error creating the enrollment. Check the JavaScript console for detailed errors.', + type: 'danger', + topic: 'enrollments', + }, + ], + }; + } +} + +export async function patchEnrollment({ user, courseID, newMode, @@ -312,8 +348,8 @@ export async function postEnrollmentChange({ reason, }) { try { - const { data } = await getAuthenticatedHttpClient().post( - AppUrls.getEnrollmentChangeUrl(user), + const { data } = await getAuthenticatedHttpClient().patch( + AppUrls.getEnrollmentsUrl(user), { course_id: courseID, new_mode: newMode, diff --git a/src/users/data/api.test.js b/src/users/data/api.test.js index a0e90464c..7de33f432 100644 --- a/src/users/data/api.test.js +++ b/src/users/data/api.test.js @@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import enrollmentsData from './test/enrollments'; +import { enrollmentsData } from './test/enrollments'; import * as api from './api'; describe('API', () => { @@ -504,7 +504,46 @@ describe('API', () => { }); }); - describe('Post Enrollment Change', () => { + describe('Post Enrollment', () => { + const apiCallData = { + user: testUsername, + courseID: 'course-v1:testX', + mode: 'audit', + reason: 'test enrollment create', + }; + + const requestData = { + course_id: 'course-v1:testX', + mode: 'audit', + reason: 'test enrollment create', + }; + + it('Unsuccessful enrollment create', async () => { + const expectedError = { + code: null, + dismissible: true, + text: + 'There was an error creating the enrollment. Check the JavaScript console for detailed errors.', + type: 'danger', + topic: 'enrollments', + }; + mockAdapter.onPost(enrollmentsApiUrl, requestData).reply(() => throwError(400, '')); + const response = await api.postEnrollment({ ...apiCallData }); + expect(...response.errors).toEqual(expectedError); + }); + + it('Successful enrollment create', async () => { + const expectedSuccessResponse = { + topic: 'enrollments', + message: 'enrollment created', + }; + mockAdapter.onPost(enrollmentsApiUrl, requestData).reply(200, expectedSuccessResponse); + const response = await api.postEnrollment({ ...apiCallData }); + expect(response).toEqual(expectedSuccessResponse); + }); + }); + + describe('Patch Enrollment Change', () => { const apiCallData = { user: testUsername, courseID: 'course-v1:testX', @@ -529,8 +568,8 @@ describe('API', () => { type: 'danger', topic: 'enrollments', }; - mockAdapter.onPost(enrollmentsApiUrl, requestData).reply(() => throwError(400, '')); - const response = await api.postEnrollmentChange({ ...apiCallData }); + mockAdapter.onPatch(enrollmentsApiUrl, requestData).reply(() => throwError(400, '')); + const response = await api.patchEnrollment({ ...apiCallData }); expect(...response.errors).toEqual(expectedError); }); @@ -539,8 +578,8 @@ describe('API', () => { topic: 'enrollments', message: 'enrollment mode changed', }; - mockAdapter.onPost(enrollmentsApiUrl, requestData).reply(200, expectedSuccessResponse); - const response = await api.postEnrollmentChange({ ...apiCallData }); + mockAdapter.onPatch(enrollmentsApiUrl, requestData).reply(200, expectedSuccessResponse); + const response = await api.patchEnrollment({ ...apiCallData }); expect(response).toEqual(expectedSuccessResponse); }); diff --git a/src/users/data/test/enrollmentForm.js b/src/users/data/test/enrollmentForm.js deleted file mode 100644 index c9158fabd..000000000 --- a/src/users/data/test/enrollmentForm.js +++ /dev/null @@ -1,26 +0,0 @@ -const enrollmentFormData = { - user: 'edX', - enrollment: { - courseId: 'course-v1:testX+test123+2030', - courseStart: Date().toLocaleString(), - verifiedUpgradeDeadline: Date().toLocaleString(), - courseEnd: Date().toLocaleString(), - created: Date().toLocaleString(), - courseModes: [ - { - slug: 'verified', - }, - ], - isActive: true, - mode: 'audit', - manualEnrollment: { - reason: 'Test Enrollment', - enrolledBy: 'edX', - timestamp: Date().toLocaleString(), - }, - }, - changeHandler: jest.fn(() => {}), - closeHandler: jest.fn(() => {}), -}; - -export default enrollmentFormData; diff --git a/src/users/data/test/enrollments.js b/src/users/data/test/enrollments.js index 7989de795..4aea37a37 100644 --- a/src/users/data/test/enrollments.js +++ b/src/users/data/test/enrollments.js @@ -1,4 +1,34 @@ -const enrollmentsData = { +export const changeEnrollmentFormData = { + user: 'edX', + enrollment: { + courseId: 'course-v1:testX+test123+2030', + courseStart: Date().toLocaleString(), + verifiedUpgradeDeadline: Date().toLocaleString(), + courseEnd: Date().toLocaleString(), + created: Date().toLocaleString(), + courseModes: [ + { + slug: 'verified', + }, + ], + isActive: true, + mode: 'audit', + manualEnrollment: { + reason: 'Test Enrollment', + enrolledBy: 'edX', + timestamp: Date().toLocaleString(), + }, + }, + changeHandler: jest.fn(() => {}), + closeHandler: jest.fn(() => {}), +}; + +export const createEnrollmentFormData = { + user: 'edX', + closeHandler: jest.fn(() => {}), +}; + +export const enrollmentsData = { data: [{ courseId: 'course-v1:testX+test123+2030', courseStart: Date().toLocaleString(), @@ -42,5 +72,3 @@ const enrollmentsData = { changeHandler: jest.fn(() => {}), expanded: true, }; - -export default enrollmentsData; diff --git a/src/users/data/urls.js b/src/users/data/urls.js index bbedee93d..5d4cf9146 100644 --- a/src/users/data/urls.js +++ b/src/users/data/urls.js @@ -54,10 +54,6 @@ export const getEntitlementUrl = (uuid = null) => { return `${LMS_BASE_URL}/api/entitlements/v1/entitlements/${postfix}`; }; -export const getEnrollmentChangeUrl = user => `${ - LMS_BASE_URL -}/support/enrollment/${user}`; - export const getTogglePasswordStatusUrl = user => `${ LMS_BASE_URL }/support/manage_user/${user}`; diff --git a/src/users/enrollments/ChangeEnrollmentForm.jsx b/src/users/enrollments/ChangeEnrollmentForm.jsx new file mode 100644 index 000000000..252390890 --- /dev/null +++ b/src/users/enrollments/ChangeEnrollmentForm.jsx @@ -0,0 +1,138 @@ +import React, { useCallback, useState, useContext } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, Input, InputSelect, +} from '@edx/paragon'; + +import AlertList from '../../userMessages/AlertList'; +import { patchEnrollment } from '../data/api'; +import UserMessagesContext from '../../userMessages/UserMessagesContext'; +import { reasons } from './constants'; + +const getModes = function getModes(enrollment) { + const modeList = []; + enrollment.courseModes.map(mode => ( + modeList.push(mode.slug) + )); + if (!modeList.some(mode => mode === enrollment.mode)) { + modeList.push(enrollment.mode); + } + return modeList.sort(); +}; + +export default function ChangeEnrollmentForm({ + user, + enrollment, + changeHandler, + closeHandler, + forwardedRef, +}) { + const [mode, setMode] = useState(enrollment.mode); + const [reason, setReason] = useState(''); + const [comments, setComments] = useState(''); + const { add } = useContext(UserMessagesContext); + + const submit = useCallback(() => { + const sendReason = (reason === 'other') ? comments : reason; + patchEnrollment({ + user, + courseID: enrollment.courseId, + oldMode: enrollment.mode, + newMode: mode, + reason: sendReason, + }).then((result) => { + if (result.errors !== undefined) { + result.errors.forEach(error => add(error)); + } else { + changeHandler(); + } + }); + }); + + return ( + + + + Change Enrollment + + Current Enrollment Data + Course Run ID: {enrollment.courseId} + Mode: {enrollment.mode} + Active: {enrollment.isActive.toString()} + + + + All fields are required + setMode(event)} + /> + + + setReason(event)} + /> + + + Explain if other + setComments(event.target.value)} + ref={forwardedRef} + /> + + + + Submit + + + Cancel + + + + + ); +} + +ChangeEnrollmentForm.propTypes = { + enrollment: PropTypes.shape({ + courseId: PropTypes.string.isRequired, + mode: PropTypes.string.isRequired, + isActive: PropTypes.bool.isRequired, + }), + user: PropTypes.string.isRequired, + changeHandler: PropTypes.func.isRequired, + closeHandler: PropTypes.func.isRequired, + forwardedRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }), +}; + +ChangeEnrollmentForm.defaultProps = { + enrollment: { + courseId: '', + mode: '', + isActive: false, + }, + forwardedRef: null, +}; diff --git a/src/users/enrollments/ChangeEnrollmentForm.test.jsx b/src/users/enrollments/ChangeEnrollmentForm.test.jsx new file mode 100644 index 000000000..8ba88bdea --- /dev/null +++ b/src/users/enrollments/ChangeEnrollmentForm.test.jsx @@ -0,0 +1,79 @@ +import { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; + +import { waitForComponentToPaint } from '../../setupTest'; +import ChangeEnrollmentForm from './ChangeEnrollmentForm'; +import { changeEnrollmentFormData } from '../data/test/enrollments'; +import UserMessagesProvider from '../../userMessages/UserMessagesProvider'; +import * as api from '../data/api'; + +const EnrollmentFormWrapper = (props) => ( + + + +); + +describe('Enrollment Change form', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(); + }); + + afterEach(() => { + wrapper.unmount(); + }); + + it('Default form rendering', () => { + const modeSelectionDropdown = wrapper.find('select#mode'); + const modeChangeReasonDropdown = wrapper.find('select#reason'); + const commentsTextarea = wrapper.find('textarea#comments'); + expect(modeSelectionDropdown.find('option')).toHaveLength(2); + expect(modeChangeReasonDropdown.find('option')).toHaveLength(5); + expect(commentsTextarea.text()).toEqual(''); + }); + + describe('Form submission', () => { + it('Successful form submission', async () => { + const apiMock = jest.spyOn(api, 'patchEnrollment').mockImplementationOnce(() => Promise.resolve({})); + expect(apiMock).toHaveBeenCalledTimes(0); + + wrapper.find('select#reason').simulate('change', { target: { value: 'Other' } }); + wrapper.find('select#mode').simulate('change', { target: { value: 'verified' } }); + wrapper.find('textarea#comments').simulate('change', { target: { value: 'test mode change' } }); + wrapper.find('button.btn-primary').simulate('click'); + expect(apiMock).toHaveBeenCalledTimes(1); + + // The mock call count does not update on time for expect call, therefore, waitFor is used to give enough time + // for the call count to update. However, it is possible this might turn out to be flaky. + await waitFor(() => expect(changeEnrollmentFormData.changeHandler).toHaveBeenCalledTimes(1)); + apiMock.mockReset(); + }); + + it('Unsuccessful form submission', async () => { + const apiMock = jest.spyOn(api, 'patchEnrollment').mockImplementationOnce(() => Promise.resolve({ + errors: [ + { + code: null, + dismissible: true, + text: 'Error changing enrollment', + type: 'danger', + topic: 'enrollments', + }, + ], + })); + expect(apiMock).toHaveBeenCalledTimes(0); + + wrapper.find('select#reason').simulate('change', { target: { value: 'Other' } }); + + wrapper.find('select#mode').simulate('change', { target: { value: 'verified' } }); + wrapper.find('textarea#comments').simulate('change', { target: { value: 'test mode change' } }); + wrapper.find('button.btn-primary').simulate('click'); + await waitForComponentToPaint(wrapper); + + expect(apiMock).toHaveBeenCalledTimes(1); + expect(wrapper.find('.alert').text()).toEqual('Error changing enrollment'); + }); + }); +}); diff --git a/src/users/enrollments/CreateEnrollmentForm.jsx b/src/users/enrollments/CreateEnrollmentForm.jsx new file mode 100644 index 000000000..b47955853 --- /dev/null +++ b/src/users/enrollments/CreateEnrollmentForm.jsx @@ -0,0 +1,125 @@ +import React, { useCallback, useState, useContext } from 'react'; +import PropTypes from 'prop-types'; +import { + Button, Input, InputSelect, +} from '@edx/paragon'; + +import AlertList from '../../userMessages/AlertList'; +import { postEnrollment } from '../data/api'; +import UserMessagesContext from '../../userMessages/UserMessagesContext'; +import { reasons, modes } from './constants'; + +export default function CreateEnrollmentForm({ + user, + closeHandler, + forwardedRef, +}) { + const [courseID, setCourseID] = useState(''); + const [mode, setMode] = useState(''); + const [reason, setReason] = useState(''); + const [comments, setComments] = useState(''); + const { add } = useContext(UserMessagesContext); + + const submit = useCallback(() => { + const sendReason = (reason === 'other') ? comments : reason; + postEnrollment({ + user, + courseID, + mode, + reason: sendReason, + }).then((result) => { + if (result.errors !== undefined) { + result.errors.forEach(error => add(error)); + } else { + const successMessage = { + code: null, + dismissible: true, + text: + 'New Enrollment successfully created.', + type: 'success', + topic: 'enrollments', + }; + add(successMessage); + } + }); + }); + + return ( + + + + Create New Enrollment + + + All fields are required + Course Run ID + setCourseID(event.target.value)} + /> + + + All fields are required + setMode(event)} + /> + + + setReason(event)} + /> + + + Explain if other + setComments(event.target.value)} + ref={forwardedRef} + /> + + + + Submit + + + Cancel + + + + + ); +} + +CreateEnrollmentForm.propTypes = { + user: PropTypes.string.isRequired, + closeHandler: PropTypes.func.isRequired, + forwardedRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }), +}; + +CreateEnrollmentForm.defaultProps = { + forwardedRef: null, +}; diff --git a/src/users/enrollments/CreateEnrollmentForm.test.jsx b/src/users/enrollments/CreateEnrollmentForm.test.jsx new file mode 100644 index 000000000..4bb651102 --- /dev/null +++ b/src/users/enrollments/CreateEnrollmentForm.test.jsx @@ -0,0 +1,78 @@ +import { mount } from 'enzyme'; +import React from 'react'; + +import { waitForComponentToPaint } from '../../setupTest'; +import CreateEnrollmentForm from './CreateEnrollmentForm'; +import { createEnrollmentFormData } from '../data/test/enrollments'; +import UserMessagesProvider from '../../userMessages/UserMessagesProvider'; +import * as api from '../data/api'; + +const EnrollmentFormWrapper = (props) => ( + + + +); + +describe('Enrollment Create form', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(); + }); + + afterEach(() => { + wrapper.unmount(); + }); + + it('Default form rendering', () => { + const modeSelectionDropdown = wrapper.find('select#mode'); + const modeChangeReasonDropdown = wrapper.find('select#reason'); + const commentsTextarea = wrapper.find('textarea#comments'); + expect(modeSelectionDropdown.find('option')).toHaveLength(9); + expect(modeChangeReasonDropdown.find('option')).toHaveLength(5); + expect(commentsTextarea.text()).toEqual(''); + }); + + describe('Form submission', () => { + it('Successful form submission', async () => { + const apiMock = jest.spyOn(api, 'postEnrollment').mockImplementationOnce(() => Promise.resolve({})); + expect(apiMock).toHaveBeenCalledTimes(0); + + wrapper.find('input#courseID').simulate('change', { target: { value: 'course-v1:testX+test123+2030' } }); + wrapper.find('select#reason').simulate('change', { target: { value: 'Other' } }); + wrapper.find('select#mode').simulate('change', { target: { value: 'verified' } }); + wrapper.find('textarea#comments').simulate('change', { target: { value: 'test create enrollment' } }); + wrapper.find('button.btn-primary').simulate('click'); + expect(apiMock).toHaveBeenCalledTimes(1); + + await waitForComponentToPaint(wrapper); + expect(wrapper.find('.alert').text()).toEqual('New Enrollment successfully created.'); + apiMock.mockReset(); + }); + + it('Unsuccessful form submission', async () => { + const apiMock = jest.spyOn(api, 'postEnrollment').mockImplementationOnce(() => Promise.resolve({ + errors: [ + { + code: null, + dismissible: true, + text: 'Error creating enrollment', + type: 'danger', + topic: 'enrollments', + }, + ], + })); + expect(apiMock).toHaveBeenCalledTimes(0); + wrapper.find('input#courseID').simulate('change', { target: { value: 'course-v1:testX+test123+2030' } }); + + wrapper.find('select#reason').simulate('change', { target: { value: 'Other' } }); + wrapper.find('select#mode').simulate('change', { target: { value: 'verified' } }); + wrapper.find('textarea#comments').simulate('change', { target: { value: 'test create enrollment' } }); + wrapper.find('button.btn-primary').simulate('click'); + await waitForComponentToPaint(wrapper); + + expect(apiMock).toHaveBeenCalledTimes(1); + expect(wrapper.find('.alert').text()).toEqual('Error creating enrollment'); + }); + }); +}); diff --git a/src/users/enrollments/EnrollmentForm.jsx b/src/users/enrollments/EnrollmentForm.jsx index 5c975d4ae..0c14fb3d1 100644 --- a/src/users/enrollments/EnrollmentForm.jsx +++ b/src/users/enrollments/EnrollmentForm.jsx @@ -1,139 +1,51 @@ -import React, { useCallback, useState, useContext } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import { - Button, Input, InputSelect, -} from '@edx/paragon'; -import classNames from 'classnames'; - -import AlertList from '../../userMessages/AlertList'; -import { postEnrollmentChange } from '../data/api'; -import UserMessagesContext from '../../userMessages/UserMessagesContext'; - -const getModes = function getModes(enrollment) { - const modeList = []; - enrollment.courseModes.map(mode => ( - modeList.push(mode.slug) - )); - if (!modeList.some(mode => mode === enrollment.mode)) { - modeList.push(enrollment.mode); - } - return modeList.sort(); -}; +import { CREATE, CHANGE } from './constants'; +import CreateEnrollmentForm from './CreateEnrollmentForm'; +import ChangeEnrollmentForm from './ChangeEnrollmentForm'; export default function EnrollmentForm({ - user, + formType, enrollment, changeHandler, closeHandler, + user, forwardedRef, }) { - const [mode, setMode] = useState(enrollment.mode); - const [reason, setReason] = useState(''); - const [comments, setComments] = useState(''); - const { add } = useContext(UserMessagesContext); - - const submit = useCallback(() => { - const sendReason = (reason === 'other') ? comments : reason; - postEnrollmentChange({ - user, - courseID: enrollment.courseId, - oldMode: enrollment.mode, - newMode: mode, - reason: sendReason, - }).then((result) => { - if (result.errors !== undefined) { - result.errors.forEach(error => add(error)); - } else { - changeHandler(); - } - }); - }); - - const reasons = [ - { label: '--', value: '' }, - { label: 'Financial Assistance', value: 'Financial Assistance' }, - { label: 'Upset Learner', value: 'Upset Learner' }, - { label: 'Teaching Assistant', value: 'Teaching Assistant' }, - { label: 'Other', value: 'other' }, - ]; - - return ( - - - - Change Enrollment - - Current Enrollment Data - Course Run ID: {enrollment.courseId} - Mode: {enrollment.mode} - Active: {enrollment.isActive.toString()} - - - - All fields are required - setMode(event)} - /> - - - setReason(event)} - /> - - - Explain if other - setComments(event.target.value)} - ref={forwardedRef} - /> - - - - Submit - - - Cancel - - - - - ); + if (formType === CHANGE) { + return ( + {}} + changeHandler={changeHandler} + closeHandler={closeHandler} + forwardedRef={forwardedRef} + /> + ); + } if (formType === CREATE) { + return ( + {}} + closeHandler={closeHandler} + forwardedRef={forwardedRef} + /> + ); + } } EnrollmentForm.propTypes = { + formType: PropTypes.string.isRequired, enrollment: PropTypes.shape({ courseId: PropTypes.string.isRequired, mode: PropTypes.string.isRequired, isActive: PropTypes.bool.isRequired, }), user: PropTypes.string.isRequired, - changeHandler: PropTypes.func.isRequired, + changeHandler: PropTypes.func, closeHandler: PropTypes.func.isRequired, forwardedRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }), }; diff --git a/src/users/enrollments/EnrollmentForm.test.jsx b/src/users/enrollments/EnrollmentForm.test.jsx index 9bf615728..be338faba 100644 --- a/src/users/enrollments/EnrollmentForm.test.jsx +++ b/src/users/enrollments/EnrollmentForm.test.jsx @@ -1,12 +1,10 @@ import { mount } from 'enzyme'; -import { waitFor } from '@testing-library/react'; import React from 'react'; -import { waitForComponentToPaint } from '../../setupTest'; import EnrollmentForm from './EnrollmentForm'; -import enrollmentFormData from '../data/test/enrollmentForm'; +import { createEnrollmentFormData, changeEnrollmentFormData } from '../data/test/enrollments'; import UserMessagesProvider from '../../userMessages/UserMessagesProvider'; -import * as api from '../data/api'; +import { CREATE, CHANGE } from './constants'; const EnrollmentFormWrapper = (props) => ( @@ -14,63 +12,18 @@ const EnrollmentFormWrapper = (props) => ( ); -describe('Enrollment Change form', () => { +describe('Enrollment forms', () => { let wrapper; - beforeEach(() => { - wrapper = mount(); + it('Create Enrollment form render', () => { + wrapper = mount(); + expect(wrapper.find('CreateEnrollmentForm').length).toEqual(1); + expect(wrapper.find('ChangeEnrollmentForm').length).toEqual(0); }); - afterEach(() => { - wrapper.unmount(); - }); - - it('Default form rendering', () => { - const modeSelectionDropdown = wrapper.find('select#mode'); - const modeChangeReasonDropdown = wrapper.find('select#reason'); - const commentsTextarea = wrapper.find('textarea#comments'); - expect(modeSelectionDropdown.find('option')).toHaveLength(2); - expect(modeChangeReasonDropdown.find('option')).toHaveLength(5); - expect(commentsTextarea.text()).toEqual(''); - }); - - describe('Change form submission', () => { - it('Successful form submission', async () => { - const apiMock = jest.spyOn(api, 'postEnrollmentChange').mockImplementationOnce(() => Promise.resolve({})); - expect(apiMock).toHaveBeenCalledTimes(0); - - wrapper.find('select#reason').simulate('change', { target: { value: 'Other' } }); - wrapper.find('select#mode').simulate('change', { target: { value: 'verified' } }); - wrapper.find('textarea#comments').simulate('change', { target: { value: 'test mode change' } }); - wrapper.find('button.btn-primary').simulate('click'); - expect(apiMock).toHaveBeenCalledTimes(1); - - // The mock call count does not update on time for expect call, therefore, waitFor is used to give enough time - // for the call count to update. However, it is possible this might turn out to be flaky. - await waitFor(() => expect(enrollmentFormData.changeHandler).toHaveBeenCalledTimes(1)); - apiMock.mockReset(); - }); - - it('Unsuccessful form submission', async () => { - const apiMock = jest.spyOn(api, 'postEnrollmentChange').mockImplementationOnce(() => Promise.resolve({ - errors: [ - { - code: null, - dismissible: true, - text: 'Error changing enrollment', - type: 'danger', - topic: 'enrollments', - }, - ], - })); - expect(apiMock).toHaveBeenCalledTimes(0); - - wrapper.find('textarea#comments').simulate('change', { target: { value: 'test mode change' } }); - wrapper.find('button.btn-primary').simulate('click'); - await waitForComponentToPaint(wrapper); - - expect(apiMock).toHaveBeenCalledTimes(1); - expect(wrapper.find('.alert').text()).toEqual('Error changing enrollment'); - }); + it('Change Enrollment form render', () => { + wrapper = mount(); + expect(wrapper.find('ChangeEnrollmentForm').length).toEqual(1); + expect(wrapper.find('CreateEnrollmentForm').length).toEqual(0); }); }); diff --git a/src/users/enrollments/Enrollments.jsx b/src/users/enrollments/Enrollments.jsx index e1685a056..1e9d41455 100644 --- a/src/users/enrollments/Enrollments.jsx +++ b/src/users/enrollments/Enrollments.jsx @@ -11,6 +11,7 @@ import { getConfig } from '@edx/frontend-platform'; import PropTypes from 'prop-types'; import EnrollmentForm from './EnrollmentForm'; import EnrollmentExtra from './EnrollmentExtra'; +import { CREATE, CHANGE } from './constants'; import Table from '../../Table'; import { formatDate, sort } from '../../utils'; @@ -87,7 +88,7 @@ export default function Enrollments({ id="enrollment-change" onClick={() => { setEnrollmentToChange(result); - setFormType('CHANGE'); + setFormType(CHANGE); }} > Change @@ -160,11 +161,27 @@ export default function Enrollments({ const tableDataSortable = [...tableData]; return ( + + {!formType && ( + { + setEnrollmentToChange(undefined); + setFormType(CREATE); + }} + > + Create New Enrollment + + )} + {formType != null ? ( {}} changeHandler={changeHandler} diff --git a/src/users/enrollments/Enrollments.test.jsx b/src/users/enrollments/Enrollments.test.jsx index 68421d317..02a4bd0c8 100644 --- a/src/users/enrollments/Enrollments.test.jsx +++ b/src/users/enrollments/Enrollments.test.jsx @@ -2,7 +2,7 @@ import { mount } from 'enzyme'; import React from 'react'; import Enrollments from './Enrollments'; -import enrollmentsData from '../data/test/enrollments'; +import { enrollmentsData } from '../data/test/enrollments'; import UserMessagesProvider from '../../userMessages/UserMessagesProvider'; const EnrollmentPageWrapper = (props) => ( @@ -30,15 +30,23 @@ describe('Course Enrollments Listing', () => { expect(collapsible.text()).toEqual('Enrollments (0)'); }); + it('Enrollment create form is rendered', () => { + const createEnrollmentButton = wrapper.find('button#create-enrollment-button'); + createEnrollmentButton.simulate('click'); + const createEnrollmentForm = wrapper.find('CreateEnrollmentForm'); + createEnrollmentForm.find('button.btn-outline-secondary').simulate('click'); + expect(wrapper.find('CreateEnrollmentForm')).toEqual({}); + }); + it('Enrollment change form is rendered for individual enrollment', () => { const dataTable = wrapper.find('table.table'); dataTable.find('tbody tr').forEach(row => { const courseId = row.find('a').text(); row.find('button#enrollment-change').simulate('click'); - const enrollmentForm = wrapper.find('EnrollmentForm'); - expect(enrollmentForm.html()).toEqual(expect.stringContaining(courseId)); - enrollmentForm.find('button.btn-outline-secondary').simulate('click'); - expect(wrapper.find('EnrollmentForm')).toEqual({}); + const changeEnrollmentForm = wrapper.find('ChangeEnrollmentForm'); + expect(changeEnrollmentForm.html()).toEqual(expect.stringContaining(courseId)); + changeEnrollmentForm.find('button.btn-outline-secondary').simulate('click'); + expect(wrapper.find('changeEnrollmentForm')).toEqual({}); }); }); diff --git a/src/users/enrollments/constants.js b/src/users/enrollments/constants.js new file mode 100644 index 000000000..9dbc89f83 --- /dev/null +++ b/src/users/enrollments/constants.js @@ -0,0 +1,22 @@ +export const CHANGE = 'change'; +export const CREATE = 'create'; + +export const modes = [ + { label: '--', value: '' }, + { label: 'Honor', value: 'honor' }, + { label: 'Professional', value: 'professional' }, + { label: 'Verified', value: 'verified' }, + { label: 'Audit', value: 'audit' }, + { label: 'No ID Professional', value: 'no-id-professional' }, + { label: 'Credit', value: 'credit' }, + { label: 'Masters', value: 'masters' }, + { label: 'Executive Education', value: 'executive-education' }, +]; + +export const reasons = [ + { label: '--', value: '' }, + { label: 'Financial Assistance', value: 'Financial Assistance' }, + { label: 'Upset Learner', value: 'Upset Learner' }, + { label: 'Teaching Assistant', value: 'Teaching Assistant' }, + { label: 'Other', value: 'other' }, +];