[ROLES-47] Permissions definitions for Schedule & Details (#854)
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -65,7 +65,7 @@
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"axios-mock-adapter": "1.22.0",
|
||||
"glob": "7.2.3",
|
||||
"husky": "7.0.4",
|
||||
"husky": "^7.0.4",
|
||||
"jest-canvas-mock": "^2.5.2",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
"react-test-renderer": "17.0.2",
|
||||
|
||||
@@ -21,10 +21,13 @@ import scheduleMessages from './schedule-section/messages';
|
||||
import genericMessages from '../generic/help-sidebar/messages';
|
||||
import messages from './messages';
|
||||
import ScheduleAndDetails from '.';
|
||||
import { getUserPermissionsUrl, getUserPermissionsEnabledFlagUrl } from '../generic/data/api';
|
||||
import { fetchUserPermissionsQuery, fetchUserPermissionsEnabledFlag } from '../generic/data/thunks';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
const courseId = '123';
|
||||
const userId = 3;
|
||||
|
||||
// Mock the tinymce lib
|
||||
jest.mock('@tinymce/tinymce-react', () => {
|
||||
@@ -50,6 +53,18 @@ jest.mock('react-textarea-autosize', () => jest.fn((props) => (
|
||||
<textarea {...props} onFocus={() => {}} onBlur={() => {}} />
|
||||
)));
|
||||
|
||||
const permissionsMockStore = async (permissions) => {
|
||||
axiosMock.onGet(getUserPermissionsUrl(courseId, userId)).reply(200, permissions);
|
||||
axiosMock.onGet(getUserPermissionsEnabledFlagUrl).reply(200, { enabled: true });
|
||||
await executeThunk(fetchUserPermissionsQuery(courseId), store.dispatch);
|
||||
await executeThunk(fetchUserPermissionsEnabledFlag(), store.dispatch);
|
||||
};
|
||||
|
||||
const permissionDisabledMockStore = async () => {
|
||||
axiosMock.onGet(getUserPermissionsEnabledFlagUrl).reply(200, { enabled: false });
|
||||
await executeThunk(fetchUserPermissionsEnabledFlag(), store.dispatch);
|
||||
};
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
@@ -62,7 +77,7 @@ describe('<ScheduleAndDetails />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
userId,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
@@ -111,6 +126,30 @@ describe('<ScheduleAndDetails />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should shows the PermissionDeniedAlert when there are not the right user permissions', async () => {
|
||||
const permissionsData = { permissions: ['wrong_permission'] };
|
||||
await permissionsMockStore(permissionsData);
|
||||
|
||||
const { queryByText } = render(<RootWrapper />);
|
||||
await waitFor(() => {
|
||||
const permissionDeniedAlert = queryByText('You are not authorized to view this page. If you feel you should have access, please reach out to your course team admin to be given access.');
|
||||
expect(permissionDeniedAlert).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show the PermissionDeniedAlert when the User Permissions Flag is not enabled', async () => {
|
||||
await permissionDisabledMockStore();
|
||||
|
||||
const { queryByText, getAllByText } = render(<RootWrapper />);
|
||||
await waitFor(() => {
|
||||
const permissionDeniedAlert = queryByText('You are not authorized to view this page. If you feel you should have access, please reach out to your course team admin to be given access.');
|
||||
const scheduleAndDetailElements = getAllByText(messages.headingTitle.defaultMessage);
|
||||
const scheduleAndDetailTitle = scheduleAndDetailElements[0];
|
||||
expect(permissionDeniedAlert).not.toBeInTheDocument();
|
||||
expect(scheduleAndDetailTitle).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide credit section with condition', async () => {
|
||||
const updatedResponse = {
|
||||
...courseSettingsMock,
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = {
|
||||
enrollmentEndEditable: true,
|
||||
isCreditCourse: true,
|
||||
isEntranceExamsEnabled: true,
|
||||
isEditable: true,
|
||||
isPrerequisiteCoursesEnabled: true,
|
||||
languageOptions: [
|
||||
['en', 'English'],
|
||||
|
||||
@@ -18,6 +18,7 @@ describe('<DetailsSection />', () => {
|
||||
language: courseSettingsMock.languageOptions[1][0],
|
||||
languageOptions: courseSettingsMock.languageOptions,
|
||||
onChange: onChangeMock,
|
||||
isEditable: courseSettingsMock.isEditable,
|
||||
};
|
||||
|
||||
it('renders details section successfully', () => {
|
||||
@@ -57,4 +58,10 @@ describe('<DetailsSection />', () => {
|
||||
getByRole('button', { name: messages.dropdownEmpty.defaultMessage }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should disable the dropdown if isEditable is false', () => {
|
||||
const initialProps = { ...props, isEditable: false };
|
||||
const { getByRole } = render(<RootWrapper {...initialProps} />);
|
||||
expect(getByRole('button').disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import SectionSubHeader from '../../generic/section-sub-header';
|
||||
import messages from './messages';
|
||||
|
||||
const DetailsSection = ({
|
||||
language, languageOptions, onChange,
|
||||
language, languageOptions, onChange, isEditable,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const formattedLanguage = () => {
|
||||
@@ -24,7 +24,7 @@ const DetailsSection = ({
|
||||
<Form.Group className="form-group-custom dropdown-language">
|
||||
<Form.Label>{intl.formatMessage(messages.dropdownLabel)}</Form.Label>
|
||||
<Dropdown className="bg-white">
|
||||
<Dropdown.Toggle variant="outline-primary" id="languageDropdown">
|
||||
<Dropdown.Toggle variant="outline-primary" id="languageDropdown" disabled={!isEditable}>
|
||||
{formattedLanguage()}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
@@ -56,6 +56,7 @@ DetailsSection.propTypes = {
|
||||
PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
||||
).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default DetailsSection;
|
||||
|
||||
@@ -43,6 +43,9 @@ import LicenseSection from './license-section';
|
||||
import ScheduleSidebar from './schedule-sidebar';
|
||||
import messages from './messages';
|
||||
import { useLoadValuesPrompt, useSaveValuesPrompt } from './hooks';
|
||||
import { useUserPermissions } from '../generic/hooks';
|
||||
import { getUserPermissionsEnabled } from '../generic/data/selectors';
|
||||
import PermissionDeniedAlert from '../generic/PermissionDeniedAlert';
|
||||
|
||||
const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
const courseSettings = useSelector(getCourseSettings);
|
||||
@@ -53,6 +56,12 @@ const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
|| loadingSettingsStatus === RequestStatus.IN_PROGRESS;
|
||||
|
||||
const course = useModel('courseDetails', courseId);
|
||||
const { checkPermission } = useUserPermissions();
|
||||
const userPermissionsEnabled = useSelector(getUserPermissionsEnabled);
|
||||
const showPermissionDeniedAlert = userPermissionsEnabled && (
|
||||
!checkPermission('manage_course_settings') && !checkPermission('view_course_settings')
|
||||
);
|
||||
const canEdit = (!userPermissionsEnabled) ? true : checkPermission('manage_course_settings');
|
||||
document.title = getPageHeadTitle(course?.name, intl.formatMessage(messages.headingTitle));
|
||||
|
||||
const {
|
||||
@@ -145,6 +154,12 @@ const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (showPermissionDeniedAlert) {
|
||||
return (
|
||||
<PermissionDeniedAlert />
|
||||
);
|
||||
}
|
||||
|
||||
if (loadingDetailsStatus === RequestStatus.DENIED || loadingSettingsStatus === RequestStatus.DENIED) {
|
||||
return (
|
||||
<div className="row justify-content-center m-6">
|
||||
@@ -266,12 +281,14 @@ const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
certificatesDisplayBehavior={certificatesDisplayBehavior}
|
||||
canShowCertificateAvailableDateField={canShowCertificateAvailableDateField}
|
||||
onChange={handleValuesChange}
|
||||
isEditable={canEdit}
|
||||
/>
|
||||
{aboutPageEditable && (
|
||||
<DetailsSection
|
||||
language={language}
|
||||
languageOptions={languageOptions}
|
||||
onChange={handleValuesChange}
|
||||
isEditable={canEdit}
|
||||
/>
|
||||
)}
|
||||
<IntroducingSection
|
||||
@@ -292,6 +309,7 @@ const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
enableExtendedCourseDetails={enableExtendedCourseDetails}
|
||||
videoThumbnailImageAssetPath={videoThumbnailImageAssetPath}
|
||||
onChange={handleValuesChange}
|
||||
isEditable={canEdit}
|
||||
/>
|
||||
{enableExtendedCourseDetails && (
|
||||
<>
|
||||
@@ -319,12 +337,14 @@ const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
isPrerequisiteCoursesEnabled
|
||||
}
|
||||
onChange={handleValuesChange}
|
||||
isEditable={canEdit}
|
||||
/>
|
||||
)}
|
||||
{licensingEnabled && (
|
||||
<LicenseSection
|
||||
license={license}
|
||||
onChange={handleValuesChange}
|
||||
isEditable={canEdit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ const {
|
||||
} = courseDetailsMock;
|
||||
|
||||
const {
|
||||
aboutPageEditable, sidebarHtmlEnabled, shortDescriptionEditable, lmsLinkForAboutPage,
|
||||
aboutPageEditable, sidebarHtmlEnabled, shortDescriptionEditable, lmsLinkForAboutPage, isEditable,
|
||||
} = courseSettingsMock;
|
||||
|
||||
const props = {
|
||||
@@ -63,6 +63,7 @@ const props = {
|
||||
courseImageAssetPath,
|
||||
shortDescriptionEditable,
|
||||
onChange: onChangeMock,
|
||||
isEditable,
|
||||
};
|
||||
|
||||
describe('<IntroducingSection />', () => {
|
||||
@@ -98,4 +99,13 @@ describe('<IntroducingSection />', () => {
|
||||
expect(queryAllByText(messages.courseOverviewLabel.defaultMessage).length).toBe(0);
|
||||
expect(queryAllByText(messages.courseAboutSidebarLabel.defaultMessage).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should hide components if isEditable is false', () => {
|
||||
const initialProps = { ...props, isEditable: false };
|
||||
const { queryAllByText } = render(<RootWrapper {...initialProps} />);
|
||||
expect(queryAllByText(messages.introducingTitle.defaultMessage).length).toBe(0);
|
||||
expect(queryAllByText(messages.introducingDescription.defaultMessage).length).toBe(0);
|
||||
expect(queryAllByText(messages.courseOverviewLabel.defaultMessage).length).toBe(0);
|
||||
expect(queryAllByText(messages.courseAboutSidebarLabel.defaultMessage).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ const IntroducingSection = ({
|
||||
enableExtendedCourseDetails,
|
||||
videoThumbnailImageAssetPath,
|
||||
onChange,
|
||||
isEditable,
|
||||
}) => {
|
||||
const overviewHelpText = (
|
||||
<FormattedMessage
|
||||
@@ -72,7 +73,7 @@ const IntroducingSection = ({
|
||||
|
||||
return (
|
||||
<section className="section-container introducing-section">
|
||||
{aboutPageEditable && (
|
||||
{aboutPageEditable && isEditable && (
|
||||
<SectionSubHeader
|
||||
title={intl.formatMessage(messages.introducingTitle)}
|
||||
description={intl.formatMessage(messages.introducingDescription)}
|
||||
@@ -87,7 +88,7 @@ const IntroducingSection = ({
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
{shortDescriptionEditable && (
|
||||
{shortDescriptionEditable && isEditable && (
|
||||
<Form.Group className="form-group-custom">
|
||||
<Form.Label>
|
||||
{intl.formatMessage(messages.courseShortDescriptionLabel)}
|
||||
@@ -107,7 +108,7 @@ const IntroducingSection = ({
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
)}
|
||||
{aboutPageEditable && (
|
||||
{aboutPageEditable && isEditable && (
|
||||
<>
|
||||
<Form.Group className="form-group-custom">
|
||||
<Form.Label>{intl.formatMessage(messages.courseOverviewLabel)}</Form.Label>
|
||||
@@ -160,7 +161,7 @@ const IntroducingSection = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{aboutPageEditable && (
|
||||
{aboutPageEditable && isEditable && (
|
||||
<IntroductionVideo introVideo={introVideo} onChange={onChange} />
|
||||
)}
|
||||
</section>
|
||||
@@ -200,6 +201,7 @@ IntroducingSection.propTypes = {
|
||||
enableExtendedCourseDetails: PropTypes.bool.isRequired,
|
||||
videoThumbnailImageAssetPath: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(IntroducingSection);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { courseDetailsMock } from '../__mocks__';
|
||||
import { courseDetailsMock, courseSettingsMock } from '../__mocks__';
|
||||
import messages from './messages';
|
||||
import LicenseSection from '.';
|
||||
|
||||
@@ -17,6 +17,7 @@ const RootWrapper = (props) => (
|
||||
const props = {
|
||||
license: courseDetailsMock.license,
|
||||
onChange: onChangeMock,
|
||||
isEditable: courseSettingsMock.isEditable,
|
||||
};
|
||||
|
||||
describe('<LicenseSection />', () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { LICENSE_TYPE } from './constants';
|
||||
import messages from './messages';
|
||||
import { useLicenseDetails } from './hooks';
|
||||
|
||||
const LicenseSection = ({ license, onChange }) => {
|
||||
const LicenseSection = ({ license, onChange, isEditable }) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
licenseURL,
|
||||
@@ -29,6 +29,7 @@ const LicenseSection = ({ license, onChange }) => {
|
||||
<LicenseSelector
|
||||
licenseType={licenseType}
|
||||
onChangeLicenseType={handleChangeLicenseType}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
{licenseType === LICENSE_TYPE.creativeCommons && (
|
||||
<LicenseCommonsOptions
|
||||
@@ -52,6 +53,7 @@ LicenseSection.defaultProps = {
|
||||
LicenseSection.propTypes = {
|
||||
license: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default LicenseSection;
|
||||
|
||||
@@ -18,6 +18,7 @@ const RootWrapper = (props) => (
|
||||
const props = {
|
||||
licenseType: LICENSE_TYPE.allRightsReserved,
|
||||
onChangeLicenseType: onChangeLicenseTypeMock,
|
||||
isEditable: true,
|
||||
};
|
||||
|
||||
describe('<LicenseSelector />', () => {
|
||||
@@ -60,4 +61,13 @@ describe('<LicenseSelector />', () => {
|
||||
expect(buttonFirst).toHaveClass('btn btn-outline-primary');
|
||||
expect(buttonSecond).toHaveClass('btn btn-outline-primary');
|
||||
});
|
||||
|
||||
it('should show disabled buttons if isEditable is false', () => {
|
||||
const initialProps = { ...props, isEditable: false };
|
||||
const { getByRole } = render(<RootWrapper {...initialProps} />);
|
||||
const buttonFirst = getByRole('button', { name: messages.licenseChoice1.defaultMessage });
|
||||
const buttonSecond = getByRole('button', { name: messages.licenseChoice2.defaultMessage });
|
||||
expect(buttonFirst.disabled).toEqual(true);
|
||||
expect(buttonSecond.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { LICENSE_TYPE } from '../constants';
|
||||
import messages from './messages';
|
||||
|
||||
const LicenseSelector = ({ licenseType, onChangeLicenseType }) => {
|
||||
const LicenseSelector = ({ licenseType, onChangeLicenseType, isEditable }) => {
|
||||
const LICENSE_BUTTON_GROUP_LABELS = {
|
||||
[LICENSE_TYPE.allRightsReserved]: {
|
||||
label: <FormattedMessage {...messages.licenseChoice1} />,
|
||||
@@ -37,6 +37,7 @@ const LicenseSelector = ({ licenseType, onChangeLicenseType }) => {
|
||||
<Button
|
||||
variant={isActive ? 'primary' : 'outline-primary'}
|
||||
onClick={() => onChangeLicenseType(type, 'license')}
|
||||
disabled={!isEditable}
|
||||
>
|
||||
{LICENSE_BUTTON_GROUP_LABELS[type].label}
|
||||
</Button>
|
||||
@@ -64,6 +65,7 @@ LicenseSelector.defaultProps = {
|
||||
LicenseSelector.propTypes = {
|
||||
licenseType: PropTypes.oneOf(Object.values(LICENSE_TYPE)),
|
||||
onChangeLicenseType: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default LicenseSelector;
|
||||
|
||||
@@ -36,6 +36,7 @@ const {
|
||||
isEntranceExamsEnabled,
|
||||
possiblePreRequisiteCourses,
|
||||
isPrerequisiteCoursesEnabled,
|
||||
isEditable,
|
||||
} = courseSettingsMock;
|
||||
|
||||
const props = {
|
||||
@@ -49,6 +50,7 @@ const props = {
|
||||
entranceExamMinimumScorePct,
|
||||
isPrerequisiteCoursesEnabled,
|
||||
onChange: onChangeMock,
|
||||
isEditable,
|
||||
};
|
||||
|
||||
describe('<RequirementsSection />', () => {
|
||||
@@ -90,4 +92,10 @@ describe('<RequirementsSection />', () => {
|
||||
expect(queryAllByLabelText(messages.dropdownLabel.defaultMessage).length).toBe(0);
|
||||
expect(queryAllByLabelText(entranceExamMessages.requirementsEntrance.defaultMessage).length).toBe(0);
|
||||
});
|
||||
|
||||
it('should disable the dropdown if isEditable is false', () => {
|
||||
const initialProps = { ...props, isEditable: false };
|
||||
const { queryByTestId } = render(<RootWrapper {...initialProps} />);
|
||||
expect(queryByTestId('prerequisite_dropdown').disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { courseDetailsMock } from '../../__mocks__';
|
||||
import { courseDetailsMock, courseSettingsMock } from '../../__mocks__';
|
||||
import gradeRequirementsMessages from '../grade-requirements/messages';
|
||||
import messages from './messages';
|
||||
import EntranceExam from '.';
|
||||
@@ -30,6 +30,7 @@ const props = {
|
||||
isCheckedString: courseDetailsMock.entranceExamEnabled,
|
||||
entranceExamMinimumScorePct: courseDetailsMock.entranceExamMinimumScorePct,
|
||||
onChange: onChangeMock,
|
||||
isEditable: courseSettingsMock.isEditable,
|
||||
};
|
||||
|
||||
describe('<EntranceExam />', () => {
|
||||
@@ -58,4 +59,11 @@ describe('<EntranceExam />', () => {
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable the checkbox if isEditable is false', () => {
|
||||
const initialProps = { ...props, isEditable: false };
|
||||
const { getAllByRole } = render(<RootWrapper {...initialProps} />);
|
||||
const checkbox = getAllByRole('checkbox')[0];
|
||||
expect(checkbox.disabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ const EntranceExam = ({
|
||||
isCheckedString,
|
||||
entranceExamMinimumScorePct,
|
||||
onChange,
|
||||
isEditable,
|
||||
}) => {
|
||||
const { courseId } = useParams();
|
||||
const showEntranceExam = isCheckedString === 'true';
|
||||
@@ -33,6 +34,7 @@ const EntranceExam = ({
|
||||
<Form.Checkbox
|
||||
checked={showEntranceExam}
|
||||
onChange={toggleEntranceExam}
|
||||
disabled={!isEditable}
|
||||
>
|
||||
<FormattedMessage {...messages.requirementsEntranceCollapseTitle} />
|
||||
</Form.Checkbox>
|
||||
@@ -63,6 +65,7 @@ const EntranceExam = ({
|
||||
errorEffort={errorEffort}
|
||||
entranceExamMinimumScorePct={entranceExamMinimumScorePct}
|
||||
onChange={onChange}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
</Card.Body>
|
||||
</>
|
||||
@@ -83,6 +86,7 @@ EntranceExam.propTypes = {
|
||||
isCheckedString: PropTypes.string,
|
||||
entranceExamMinimumScorePct: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default EntranceExam;
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { courseDetailsMock } from '../../__mocks__';
|
||||
import { courseDetailsMock, courseSettingsMock } from '../../__mocks__';
|
||||
import scheduleMessage from '../../messages';
|
||||
import messages from './messages';
|
||||
import GradeRequirements from '.';
|
||||
@@ -21,6 +21,7 @@ const props = {
|
||||
errorEffort: '',
|
||||
entranceExamMinimumScorePct: courseDetailsMock.entranceExamMinimumScorePct,
|
||||
onChange: onChangeMock,
|
||||
isEditable: courseSettingsMock.isEditable,
|
||||
};
|
||||
|
||||
describe('<GradeRequirements />', () => {
|
||||
@@ -31,6 +32,13 @@ describe('<GradeRequirements />', () => {
|
||||
expect(getByDisplayValue(props.entranceExamMinimumScorePct)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disable the input if isEditable is false', () => {
|
||||
const initialProps = { ...props, isEditable: false };
|
||||
const { getByDisplayValue } = render(<RootWrapper {...initialProps} />);
|
||||
const input = getByDisplayValue(props.entranceExamMinimumScorePct);
|
||||
expect(input.disabled).toEqual(true);
|
||||
});
|
||||
|
||||
it('should call onChange on input change', () => {
|
||||
const { getByDisplayValue } = render(<RootWrapper {...props} />);
|
||||
const input = getByDisplayValue(props.entranceExamMinimumScorePct);
|
||||
|
||||
@@ -10,6 +10,7 @@ const GradeRequirements = ({
|
||||
errorEffort,
|
||||
entranceExamMinimumScorePct,
|
||||
onChange,
|
||||
isEditable,
|
||||
}) => (
|
||||
<Form.Group
|
||||
className={classNames('form-group-custom', {
|
||||
@@ -27,6 +28,7 @@ const GradeRequirements = ({
|
||||
value={entranceExamMinimumScorePct}
|
||||
onChange={(e) => onChange(e.target.value, 'entranceExamMinimumScorePct')}
|
||||
trailingElement="%"
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</Stack>
|
||||
{errorEffort && (
|
||||
@@ -49,6 +51,7 @@ GradeRequirements.propTypes = {
|
||||
errorEffort: PropTypes.string,
|
||||
entranceExamMinimumScorePct: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default GradeRequirements;
|
||||
|
||||
@@ -19,6 +19,7 @@ const RequirementsSection = ({
|
||||
entranceExamMinimumScorePct,
|
||||
isPrerequisiteCoursesEnabled,
|
||||
onChange,
|
||||
isEditable,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const selectedItem = possiblePreRequisiteCourses?.find(
|
||||
@@ -33,7 +34,7 @@ const RequirementsSection = ({
|
||||
>
|
||||
<Form.Label>{intl.formatMessage(messages.dropdownLabel)}</Form.Label>
|
||||
<Dropdown className="bg-white">
|
||||
<Dropdown.Toggle id="prerequisiteDropdown" variant="outline-primary">
|
||||
<Dropdown.Toggle id="prerequisiteDropdown" variant="outline-primary" disabled={!isEditable} data-testid="prerequisite_dropdown">
|
||||
{formattedSelectedItem}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
@@ -74,6 +75,7 @@ const RequirementsSection = ({
|
||||
value={effort || ''}
|
||||
placeholder={TIME_FORMAT.toUpperCase()}
|
||||
onChange={(e) => onChange(e.target.value, 'effort')}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
{intl.formatMessage(messages.timepickerHelpText)}
|
||||
@@ -87,6 +89,7 @@ const RequirementsSection = ({
|
||||
isCheckedString={entranceExamEnabled}
|
||||
entranceExamMinimumScorePct={entranceExamMinimumScorePct}
|
||||
onChange={onChange}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
@@ -125,6 +128,7 @@ RequirementsSection.propTypes = {
|
||||
entranceExamMinimumScorePct: PropTypes.string,
|
||||
isPrerequisiteCoursesEnabled: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default RequirementsSection;
|
||||
|
||||
@@ -125,7 +125,7 @@ const CertificateDisplayRow = ({
|
||||
<Form.Label>
|
||||
{intl.formatMessage(messages.certificateBehaviorLabel)}
|
||||
</Form.Label>
|
||||
<Dropdown claswsName="bg-white">
|
||||
<Dropdown className="bg-white">
|
||||
<Dropdown.Toggle id="certificate-behavior-dropdown" variant="outline-primary">
|
||||
{certificateDisplayValue}
|
||||
</Dropdown.Toggle>
|
||||
|
||||
@@ -20,6 +20,7 @@ const ScheduleSection = ({
|
||||
certificatesDisplayBehavior,
|
||||
canShowCertificateAvailableDateField,
|
||||
onChange,
|
||||
isEditable,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const enrollmentEndHelpText = intl.formatMessage(
|
||||
@@ -42,6 +43,7 @@ const ScheduleSection = ({
|
||||
],
|
||||
rowType: SCHEDULE_ROW_TYPES.datetime,
|
||||
helpText: intl.formatMessage(messages.scheduleCourseStartDateHelpText),
|
||||
readonly: !isEditable,
|
||||
controlName: 'startDate',
|
||||
errorFeedback: errorFields?.startDate,
|
||||
},
|
||||
@@ -53,6 +55,7 @@ const ScheduleSection = ({
|
||||
value: endDate,
|
||||
rowType: SCHEDULE_ROW_TYPES.datetime,
|
||||
helpText: intl.formatMessage(messages.scheduleCourseEndDateHelpText),
|
||||
readonly: !isEditable,
|
||||
controlName: 'endDate',
|
||||
errorFeedback: errorFields?.endDate,
|
||||
},
|
||||
@@ -73,6 +76,7 @@ const ScheduleSection = ({
|
||||
value: enrollmentStart,
|
||||
rowType: SCHEDULE_ROW_TYPES.datetime,
|
||||
helpText: intl.formatMessage(messages.scheduleEnrollmentStartDateHelpText),
|
||||
readonly: !isEditable,
|
||||
controlName: 'enrollmentStart',
|
||||
errorFeedback: errorFields?.enrollmentStart,
|
||||
},
|
||||
@@ -84,7 +88,7 @@ const ScheduleSection = ({
|
||||
value: enrollmentEnd,
|
||||
rowType: SCHEDULE_ROW_TYPES.datetime,
|
||||
helpText: computedEnrollmentEndHelpText,
|
||||
readonly: !enrollmentEndEditable,
|
||||
readonly: !enrollmentEndEditable || !isEditable,
|
||||
controlName: 'enrollmentEnd',
|
||||
errorFeedback: errorFields?.enrollmentEnd,
|
||||
},
|
||||
@@ -165,6 +169,7 @@ ScheduleSection.propTypes = {
|
||||
certificatesDisplayBehavior: PropTypes.string.isRequired,
|
||||
canShowCertificateAvailableDateField: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ScheduleSection;
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('studio-home api calls', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
fit('should get studio courses data', async () => {
|
||||
it('should get studio courses data', async () => {
|
||||
const apiLink = `${getApiBaseUrl()}/api/contentstore/v1/home/courses`;
|
||||
axiosMock.onGet(apiLink).reply(200, generateGetStudioCoursesApiResponse());
|
||||
const result = await getStudioHomeCourses('');
|
||||
|
||||
Reference in New Issue
Block a user