Skip to content

Commit

Permalink
feat: added move modal and tests
Browse files Browse the repository at this point in the history
feat: added alert for successful unit movement

feat: added alert logic

refactor: code refactoring

refactor: some refactoring

refactor: code refactoring

refactor: code refactoring
  • Loading branch information
GlugovGrGlib authored and PKulkoRaccoonGang committed May 3, 2024
1 parent 13c5387 commit 5b2b33a
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const NOTIFICATION_MESSAGES = {
copying: 'Copying',
pasting: 'Pasting',
discardChanges: 'Discarding changes',
undoMoving: 'Undo moving',
publishing: 'Publishing',
hidingFromStudents: 'Hiding from students',
makingVisibleToStudents: 'Making visible to students',
Expand Down
35 changes: 34 additions & 1 deletion src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import {
Container, Layout, Stack, Button,
Container, Layout, Stack, Button, TransitionReplace,
} from '@openedx/paragon';
import { useIntl, injectIntl } from '@edx/frontend-platform/i18n';
import {
Warning as WarningIcon,
ArrowDropDown as ArrowDownIcon,
ArrowDropUp as ArrowUpIcon,
CheckCircle as CheckCircleIcon,
} from '@openedx/paragon/icons';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { DraggableList } from '@edx/frontend-lib-content-components';
Expand Down Expand Up @@ -67,6 +68,10 @@ const CourseUnit = ({ courseId }) => {
isXBlocksExpanded,
isXBlocksRendered,
handleExpandAll,
movedXBlockParams,
handleRollbackMovedXBlock,
handleCloseXBlockMovedAlert,
handleNavigateToTargetUnit,
} = useCourseUnit({ courseId, blockId });

const initialXBlocksData = useMemo(() => courseVerticalChildren.children ?? [], [courseVerticalChildren.children]);
Expand Down Expand Up @@ -111,6 +116,34 @@ const CourseUnit = ({ courseId }) => {
<>
<Container size="xl" className="course-unit px-4">
<section className="course-unit-container mb-4 mt-5">
<TransitionReplace>
{movedXBlockParams.isSuccess ? (
<AlertMessage
key="xblock-moved-alert"
data-testid="xblock-moved-alert"
show={movedXBlockParams.isSuccess}
variant="success"
icon={CheckCircleIcon}
title={movedXBlockParams.isUndo
? intl.formatMessage(messages.alertMoveCancelTitle)
: intl.formatMessage(messages.alertMoveSuccessTitle)}
description={movedXBlockParams.isUndo
? intl.formatMessage(messages.alertMoveCancelDescription, { title: movedXBlockParams.title })
: intl.formatMessage(messages.alertMoveSuccessDescription, { title: movedXBlockParams.title })}
aria-hidden={movedXBlockParams.isSuccess}
dismissible
actions={movedXBlockParams.isUndo ? null : [
<Button onClick={handleRollbackMovedXBlock}>
{intl.formatMessage(messages.undoMoveButton)}
</Button>,
<Button onClick={handleNavigateToTargetUnit}>
{intl.formatMessage(messages.newLocationButton)}
</Button>,
]}
onClose={handleCloseXBlockMovedAlert}
/>
) : null}
</TransitionReplace>
<SubHeader
hideBorder
title={(
Expand Down
170 changes: 164 additions & 6 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform';
import { camelCaseObject, getConfig, initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { cloneDeep, set } from 'lodash';

Expand All @@ -23,20 +23,18 @@ import {
fetchCourseSectionVerticalData,
fetchCourseUnitQuery,
fetchCourseVerticalChildrenData,
rollbackUnitItemQuery,
} from './data/thunk';
import initializeStore from '../store';
import {
clipboardMockResponse,
courseCreateXblockMock,
courseSectionVerticalMock,
courseUnitIndexMock,
courseUnitMock,
courseVerticalChildrenMock,
clipboardMockResponse,
} from './__mocks__';
import {
clipboardUnit,
clipboardXBlock,
} from '../__mocks__';
import { clipboardUnit, clipboardXBlock } from '../__mocks__';
import { executeThunk } from '../utils';
import deleteModalMessages from '../generic/delete-modal/messages';
import pasteComponentMessages from '../generic/clipboard/paste-component/messages';
Expand Down Expand Up @@ -111,6 +109,17 @@ jest.mock('../generic/hooks', () => ({

global.BroadcastChannel = jest.fn(() => clipboardBroadcastChannelMock);

const getIFramePostMessages = (method) => ({
data: {
method,
params: {
targetParentLocator: courseId,
sourceDisplayName: courseVerticalChildrenMock.children[0].name,
sourceLocator: courseVerticalChildrenMock.children[0].block_id,
},
},
});

const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="en">
Expand Down Expand Up @@ -1590,4 +1599,153 @@ describe('<CourseUnit />', () => {
});
});
});

describe('Edit and move modals', () => {
it('should close the edit modal when the close button is clicked', async () => {
const { getByTitle, getAllByTestId } = render(<RootWrapper />);

axiosMock
.onGet(getCourseVerticalChildrenApiUrl(blockId))
.reply(200, courseVerticalChildrenMock);

await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);

const [discussionXBlock] = getAllByTestId('course-xblock');
const xblockEditBtn = within(discussionXBlock)
.getByLabelText(courseXBlockMessages.blockAltButtonEdit.defaultMessage);

userEvent.click(xblockEditBtn);

const iframePostMsg = getIFramePostMessages('close_edit_modal');
const editModalIFrame = getByTitle('xblock-edit-modal-iframe');

expect(editModalIFrame).toHaveAttribute('src', `${getConfig().STUDIO_BASE_URL}/xblock/${courseVerticalChildrenMock.children[0].block_id}/actions/edit`);

await act(async () => window.dispatchEvent(new MessageEvent('message', iframePostMsg)));

expect(editModalIFrame).not.toBeInTheDocument();
});

it('should display success alert and close move modal when move event is triggered', async () => {
const {
getByTitle,
getByRole,
getAllByLabelText,
getByText,
} = render(<RootWrapper />);

const iframePostMsg = getIFramePostMessages('move_xblock');

axiosMock
.onGet(getCourseVerticalChildrenApiUrl(blockId))
.reply(200, courseVerticalChildrenMock);

await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);

const [xblockActionBtn] = getAllByLabelText(courseXBlockMessages.blockActionsDropdownAlt.defaultMessage);
userEvent.click(xblockActionBtn);

const xblockMoveBtn = getByRole('button', { name: courseXBlockMessages.blockLabelButtonMove.defaultMessage });
userEvent.click(xblockMoveBtn);

const moveModalIFrame = getByTitle('xblock-move-modal-iframe');

await act(async () => window.dispatchEvent(new MessageEvent('message', iframePostMsg)));

expect(moveModalIFrame).not.toBeInTheDocument();
expect(getByText(messages.alertMoveSuccessTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(
messages.alertMoveSuccessDescription.defaultMessage
.replace('{title}', courseVerticalChildrenMock.children[0].name),
)).toBeInTheDocument();

await waitFor(() => {
userEvent.click(getByText(/Cancel/i));
expect(moveModalIFrame).not.toBeInTheDocument();
});
});

it('should navigate to new location when new location button is clicked after successful move', async () => {
const {
getByTitle,
getByRole,
getAllByLabelText,
getByText,
} = render(<RootWrapper />);

const iframePostMsg = getIFramePostMessages('move_xblock');

axiosMock
.onGet(getCourseVerticalChildrenApiUrl(blockId))
.reply(200, courseVerticalChildrenMock);

await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);

const [xblockActionBtn] = getAllByLabelText(courseXBlockMessages.blockActionsDropdownAlt.defaultMessage);
userEvent.click(xblockActionBtn);

const xblockMoveBtn = getByRole('button', { name: courseXBlockMessages.blockLabelButtonMove.defaultMessage });
userEvent.click(xblockMoveBtn);

const moveModalIFrame = getByTitle('xblock-move-modal-iframe');

await act(async () => window.dispatchEvent(new MessageEvent('message', iframePostMsg)));

expect(moveModalIFrame).not.toBeInTheDocument();
expect(getByText(messages.alertMoveSuccessTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(
messages.alertMoveSuccessDescription.defaultMessage
.replace('{title}', courseVerticalChildrenMock.children[0].name),
)).toBeInTheDocument();

await waitFor(() => {
userEvent.click(getByText(messages.newLocationButton.defaultMessage));
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseId}/container/${iframePostMsg.data.params.targetParentLocator}`);
});
});

it('should display move cancellation alert when undo move button is clicked', async () => {
const {
getByRole,
getAllByLabelText,
getByText,
} = render(<RootWrapper />);

const iframePostMsg = getIFramePostMessages('move_xblock');

axiosMock
.onGet(getCourseVerticalChildrenApiUrl(blockId))
.reply(200, courseVerticalChildrenMock);

await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);

const [xblockActionBtn] = getAllByLabelText(courseXBlockMessages.blockActionsDropdownAlt.defaultMessage);
userEvent.click(xblockActionBtn);

const xblockMoveBtn = getByRole('button', { name: courseXBlockMessages.blockLabelButtonMove.defaultMessage });
userEvent.click(xblockMoveBtn);

await act(async () => window.dispatchEvent(new MessageEvent('message', iframePostMsg)));

await waitFor(() => userEvent.click(getByText(messages.undoMoveButton.defaultMessage)));

axiosMock
.onPatch(postXBlockBaseApiUrl(), {
parent_locator: blockId,
move_source_locator: courseVerticalChildrenMock.children[0].block_id,
})
.reply(200, {
parent_locator: blockId,
move_source_locator: courseVerticalChildrenMock.children[0].block_id,
});

await executeThunk(rollbackUnitItemQuery(blockId, courseVerticalChildrenMock.children[0].block_id, 'Discussion'), store.dispatch);

expect(getByText(messages.alertMoveCancelTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(
messages.alertMoveCancelDescription.defaultMessage
.replace('{title}', courseVerticalChildrenMock.children[0].name),
)).toBeInTheDocument();
});
});
});
Loading

0 comments on commit 5b2b33a

Please sign in to comment.