feat: Created Course updates page (#581)
This commit is contained in:
64
src/course-updates/course-update/CourseUpdate.jsx
Normal file
64
src/course-updates/course-update/CourseUpdate.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Icon } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Error as ErrorIcon } from '@edx/paragon/icons/es5';
|
||||
|
||||
import { isDateForUpdateValid } from './utils';
|
||||
import messages from './messages';
|
||||
|
||||
const CourseUpdate = ({
|
||||
dateForUpdate,
|
||||
contentForUpdate,
|
||||
onEdit,
|
||||
onDelete,
|
||||
isDisabledButtons,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div className="course-update" data-testid="course-update">
|
||||
<div className="course-update-header">
|
||||
<span className="course-update-header__date small font-weight-bold">{dateForUpdate}</span>
|
||||
{!isDateForUpdateValid(dateForUpdate) && (
|
||||
<div className="course-update-header__error">
|
||||
<Icon src={ErrorIcon} alt={intl.formatMessage(messages.errorMessage)} />
|
||||
<p className="message-error small m-0">{intl.formatMessage(messages.errorMessage)}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="course-update-header__action">
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={onEdit}
|
||||
disabled={isDisabledButtons}
|
||||
data-testid="course-update-edit-button"
|
||||
>
|
||||
{intl.formatMessage(messages.editButton)}
|
||||
</Button>
|
||||
<Button variant="outline-primary" size="sm" onClick={onDelete} disabled={isDisabledButtons}>
|
||||
{intl.formatMessage(messages.deleteButton)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{Boolean(contentForUpdate) && (
|
||||
<div
|
||||
className="small text-gray-800"
|
||||
data-testid="course-update-content"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: contentForUpdate }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CourseUpdate.propTypes = {
|
||||
dateForUpdate: PropTypes.string.isRequired,
|
||||
contentForUpdate: PropTypes.string.isRequired,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
isDisabledButtons: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default CourseUpdate;
|
||||
36
src/course-updates/course-update/CourseUpdate.scss
Normal file
36
src/course-updates/course-update/CourseUpdate.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
.course-update {
|
||||
&:not(:first-child) {
|
||||
padding-top: 1.875rem;
|
||||
margin-top: 1.875rem;
|
||||
border-top: 1px solid $light-400;
|
||||
}
|
||||
|
||||
.course-update-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.125rem;
|
||||
gap: .5rem;
|
||||
|
||||
.course-update-header__date {
|
||||
line-height: 1.875rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.course-update-header__error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
|
||||
svg {
|
||||
color: $warning-300;
|
||||
}
|
||||
}
|
||||
|
||||
.course-update-header__action {
|
||||
display: flex;
|
||||
width: auto;
|
||||
margin-left: auto;
|
||||
gap: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/course-updates/course-update/CourseUpdate.test.jsx
Normal file
72
src/course-updates/course-update/CourseUpdate.test.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import CourseUpdate from './CourseUpdate';
|
||||
import messages from './messages';
|
||||
|
||||
const onEditMock = jest.fn();
|
||||
const onDeleteMock = jest.fn();
|
||||
const dateForUpdateMock = 'May 1, 2023';
|
||||
const contentForUpdateMock = 'Update Content';
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
<IntlProvider locale="en">
|
||||
<CourseUpdate
|
||||
dateForUpdate={dateForUpdateMock}
|
||||
contentForUpdate={contentForUpdateMock}
|
||||
onEdit={onEditMock}
|
||||
onDelete={onDeleteMock}
|
||||
isDisabledButtons={false}
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
describe('<CourseUpdate />', () => {
|
||||
it('render CourseUpdate component correctly', () => {
|
||||
const { getByText, getByRole } = renderComponent();
|
||||
|
||||
expect(getByText(dateForUpdateMock)).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.editButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.deleteButton.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render CourseUpdate component without content correctly', () => {
|
||||
const { getByText, queryByTestId, getByRole } = renderComponent({ contentForUpdate: '' });
|
||||
|
||||
expect(getByText(dateForUpdateMock)).toBeInTheDocument();
|
||||
expect(queryByTestId('course-update-content')).not.toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.editButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.deleteButton.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render error message when dateForUpdate is inValid', () => {
|
||||
const { getByText } = renderComponent({ dateForUpdate: 'Welcome' });
|
||||
|
||||
expect(getByText(messages.errorMessage.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls the onEdit function when the "Edit" button is clicked', () => {
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
const editButton = getByRole('button', { name: messages.editButton.defaultMessage });
|
||||
fireEvent.click(editButton);
|
||||
expect(onEditMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls the onDelete function when the "Delete" button is clicked', () => {
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
const deleteButton = getByRole('button', { name: messages.deleteButton.defaultMessage });
|
||||
fireEvent.click(deleteButton);
|
||||
expect(onDeleteMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('"Edit" and "Delete" buttons is disabled when isDisabledButtons is true', () => {
|
||||
const { getByRole } = renderComponent({ isDisabledButtons: true });
|
||||
|
||||
expect(getByRole('button', { name: messages.editButton.defaultMessage })).toBeDisabled();
|
||||
expect(getByRole('button', { name: messages.deleteButton.defaultMessage })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
18
src/course-updates/course-update/messages.js
Normal file
18
src/course-updates/course-update/messages.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
editButton: {
|
||||
id: 'course-authoring.course-updates.button.edit',
|
||||
defaultMessage: 'Edit',
|
||||
},
|
||||
deleteButton: {
|
||||
id: 'course-authoring.course-updates.button.delete',
|
||||
defaultMessage: 'Delete',
|
||||
},
|
||||
errorMessage: {
|
||||
id: 'course-authoring.course-updates.date-invalid',
|
||||
defaultMessage: 'Action required: Enter a valid date.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
17
src/course-updates/course-update/utils.js
Normal file
17
src/course-updates/course-update/utils.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import moment from 'moment';
|
||||
|
||||
import { COMMA_SEPARATED_DATE_FORMAT } from '../../constants';
|
||||
|
||||
/**
|
||||
* Check is valid date format in course update
|
||||
* @param {string} date - date for update
|
||||
* @returns {boolean} - is valid date format
|
||||
*/
|
||||
const isDateForUpdateValid = (date) => {
|
||||
const parsedDate = moment(date, COMMA_SEPARATED_DATE_FORMAT, true);
|
||||
|
||||
return parsedDate.isValid() && parsedDate.format(COMMA_SEPARATED_DATE_FORMAT) === date;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { isDateForUpdateValid };
|
||||
Reference in New Issue
Block a user