feat: Created Course updates page (#581)

This commit is contained in:
vladislavkeblysh
2023-08-31 17:56:45 +03:00
committed by GitHub
parent 181f9c7a5f
commit ffae3bd868
61 changed files with 2241 additions and 8 deletions

View 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;

View 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;
}
}
}

View 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();
});
});

View 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;

View 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 };