feat: handle unsaved changes in text & problem editors (#1444)

The text & problem xblock editors will display a confirmation box before
cancelling only if user has changed something else it will directly go
back.
This commit is contained in:
Navin Karkera
2024-11-04 23:11:00 +05:30
committed by GitHub
parent 949e4ac94c
commit df8a65dc4e
23 changed files with 353 additions and 76 deletions

View File

@@ -7,7 +7,7 @@ import {
} from '@openedx/paragon';
import { EditOutline as EditIcon, MoreVert as MoveVertIcon } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom';
import { getCanEdit, getCourseId } from 'CourseAuthoring/course-unit/data/selectors';
import DeleteModal from '../../generic/delete-modal/DeleteModal';
@@ -19,6 +19,7 @@ import { copyToClipboard } from '../../generic/data/thunks';
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
import XBlockMessages from './xblock-messages/XBlockMessages';
import messages from './messages';
import { createCorrectInternalRoute } from '../../utils';
const CourseXBlock = ({
id, title, type, unitXBlockActions, shouldScroll, userPartitionInfo,
@@ -28,7 +29,6 @@ const CourseXBlock = ({
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
const dispatch = useDispatch();
const navigate = useNavigate();
const canEdit = useSelector(getCanEdit);
const courseId = useSelector(getCourseId);
const intl = useIntl();
@@ -58,7 +58,11 @@ const CourseXBlock = ({
case COMPONENT_TYPES.html:
case COMPONENT_TYPES.problem:
case COMPONENT_TYPES.video:
navigate(`/course/${courseId}/editor/${type}/${id}`);
// Not using useNavigate from react router to use browser navigation
// which allows us to block back button if unsaved changes in editor are present.
window.location.assign(
createCorrectInternalRoute(`/course/${courseId}/editor/${type}/${id}`),
);
break;
default:
}

View File

@@ -29,7 +29,6 @@ const blockId = '567890';
const handleDeleteMock = jest.fn();
const handleDuplicateMock = jest.fn();
const handleConfigureSubmitMock = jest.fn();
const mockedUsedNavigate = jest.fn();
const {
name,
block_id: id,
@@ -42,11 +41,6 @@ const unitXBlockActionsMock = {
handleDuplicate: handleDuplicateMock,
};
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate,
}));
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
@@ -78,6 +72,16 @@ useSelector.mockImplementation((selector) => {
});
describe('<CourseXBlock />', () => {
const locationTemp = window.location;
beforeAll(() => {
delete window.location;
window.location = {
assign: jest.fn(),
};
});
afterAll(() => {
window.location = locationTemp;
});
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
@@ -168,8 +172,8 @@ describe('<CourseXBlock />', () => {
expect(editButton).toBeInTheDocument();
userEvent.click(editButton);
expect(mockedUsedNavigate).toHaveBeenCalled();
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseId}/editor/html/${id}`);
expect(window.location.assign).toHaveBeenCalled();
expect(window.location.assign).toHaveBeenCalledWith(`/course/${courseId}/editor/html/${id}`);
});
it('navigates to editor page on edit Video xblock', () => {
@@ -182,8 +186,8 @@ describe('<CourseXBlock />', () => {
expect(editButton).toBeInTheDocument();
userEvent.click(editButton);
expect(mockedUsedNavigate).toHaveBeenCalled();
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseId}/editor/video/${id}`);
expect(window.location.assign).toHaveBeenCalled();
expect(window.location.assign).toHaveBeenCalledWith(`/course/${courseId}/editor/video/${id}`);
});
it('navigates to editor page on edit Problem xblock', () => {
@@ -196,8 +200,8 @@ describe('<CourseXBlock />', () => {
expect(editButton).toBeInTheDocument();
userEvent.click(editButton);
expect(mockedUsedNavigate).toHaveBeenCalled();
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseId}/editor/problem/${id}`);
expect(window.location.assign).toHaveBeenCalled();
expect(window.location.assign).toHaveBeenCalledWith(`/course/${courseId}/editor/problem/${id}`);
expect(handleDeleteMock).toHaveBeenCalledWith(id);
});
});