Skip to content

Commit

Permalink
feat: listen to xblock interaction events
Browse files Browse the repository at this point in the history
  • Loading branch information
PKulkoRaccoonGang committed Nov 22, 2024
1 parent bc8d59b commit a18a2e9
Show file tree
Hide file tree
Showing 15 changed files with 639 additions and 182 deletions.
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ export const REGEX_RULES = {
specialCharsRule: /^[a-zA-Z0-9_\-.'*~\s]+$/,
noSpaceRule: /^\S*$/,
};

export const IFRAME_FEATURE_POLICY = (
'microphone *; camera *; midi *; geolocation *; encrypted-media *, clipboard-write *'
);
2 changes: 2 additions & 0 deletions src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,11 @@ const CourseUnit = ({ courseId }) => {
/>
)}
<XBlockContainerIframe
courseId={courseId}
blockId={blockId}
unitXBlockActions={unitXBlockActions}
courseVerticalChildren={courseVerticalChildren.children}
handleConfigureSubmit={handleConfigureSubmit}
/>
<AddComponent
blockId={blockId}
Expand Down
334 changes: 333 additions & 1 deletion src/course-unit/CourseUnit.test.jsx

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions src/course-unit/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,23 @@ export const messageTypes = {
videoFullScreen: 'plugin.videoFullScreen',
refreshXBlock: 'refreshXBlock',
showMoveXBlockModal: 'showMoveXBlockModal',
copyXBlock: 'copyXBlock',
manageXBlockAccess: 'manageXBlockAccess',
deleteXBlock: 'deleteXBlock',
duplicateXBlock: 'duplicateXBlock',
refreshPositions: 'refreshPositions',
newXBlockEditor: 'newXBlockEditor',
currentXBlockId: 'currentXBlockId',
toggleCourseXBlockDropdown: 'toggleCourseXBlockDropdown',
};

export const IFRAME_FEATURE_POLICY = (
'microphone *; camera *; midi *; geolocation *; encrypted-media *, clipboard-write *'
);
export const COMPONENT_TYPES = {
advanced: 'advanced',
discussion: 'discussion',
library: 'library',
html: 'html',
openassessment: 'openassessment',
problem: 'problem',
video: 'video',
dragAndDrop: 'drag-and-drop-v2',
};
18 changes: 0 additions & 18 deletions src/course-unit/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,6 @@ const slice = createSlice({
updateCourseVerticalChildrenLoadingStatus: (state, { payload }) => {
state.loadingStatus.courseVerticalChildrenLoadingStatus = payload.status;
},
deleteXBlock: (state, { payload }) => {
state.courseVerticalChildren.children = state.courseVerticalChildren.children.filter(
(component) => component.id !== payload,
);
},
duplicateXBlock: (state, { payload }) => {
state.courseVerticalChildren = {
...payload.newCourseVerticalChildren,
children: payload.newCourseVerticalChildren.children.map((component) => {
if (component.blockId === payload.newId) {
component.shouldScroll = true;
}
return component;
}),
};
},
fetchStaticFileNoticesSuccess: (state, { payload }) => {
state.staticFileNotices = payload;
},
Expand Down Expand Up @@ -139,8 +123,6 @@ export const {
updateLoadingCourseXblockStatus,
updateCourseVerticalChildren,
updateCourseVerticalChildrenLoadingStatus,
deleteXBlock,
duplicateXBlock,
fetchStaticFileNoticesSuccess,
updateCourseOutlineInfo,
updateCourseOutlineInfoLoadingStatus,
Expand Down
10 changes: 1 addition & 9 deletions src/course-unit/data/thunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ import {
updateCourseVerticalChildren,
updateCourseVerticalChildrenLoadingStatus,
updateQueryPendingStatus,
deleteXBlock,
duplicateXBlock,
fetchStaticFileNoticesSuccess,
updateCourseOutlineInfo,
updateCourseOutlineInfoLoadingStatus,
Expand Down Expand Up @@ -229,7 +227,6 @@ export function deleteUnitItemQuery(itemId, xblockId) {

try {
await deleteUnitItem(xblockId);
dispatch(deleteXBlock(xblockId));
const { userClipboard } = await getCourseSectionVerticalData(itemId);
dispatch(updateClipboardData(userClipboard));
const courseUnit = await getCourseUnitData(itemId);
Expand All @@ -249,12 +246,7 @@ export function duplicateUnitItemQuery(itemId, xblockId) {
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.duplicating));

try {
const { locator } = await duplicateUnitItem(itemId, xblockId);
const newCourseVerticalChildren = await getCourseVerticalChildren(itemId);
dispatch(duplicateXBlock({
newId: locator,
newCourseVerticalChildren,
}));
await duplicateUnitItem(itemId, xblockId);
const courseUnit = await getCourseUnitData(itemId);
dispatch(fetchCourseItemSuccess(courseUnit));
dispatch(hideProcessingNotification());
Expand Down
8 changes: 8 additions & 0 deletions src/course-unit/header-title/HeaderTitle.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
import { getCourseUnitData } from '../data/selectors';
import { updateQueryPendingStatus } from '../data/slice';
import { messageTypes } from '../constants';
import { useIframe } from '../context/hooks';
import messages from './messages';

const HeaderTitle = ({
Expand All @@ -26,9 +28,15 @@ const HeaderTitle = ({
const currentItemData = useSelector(getCourseUnitData);
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
const { selectedPartitionIndex, selectedGroupsLabel } = currentItemData.userPartitionInfo;
const { sendMessageToIframe } = useIframe();

const onConfigureSubmit = (...arg) => {
handleConfigureSubmit(currentItemData.id, ...arg, closeConfigureModal);
// TODO: this artificial delay is a temporary solution
// to ensure the iframe content is properly refreshed.
setTimeout(() => {
sendMessageToIframe(messageTypes.refreshXBlock, null);
}, 1000);
};

const getVisibilityMessage = () => {
Expand Down
58 changes: 35 additions & 23 deletions src/course-unit/header-title/HeaderTitle.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { render } from '@testing-library/react';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
Expand Down Expand Up @@ -60,45 +60,53 @@ describe('<HeaderTitle />', () => {
it('render HeaderTitle component correctly', () => {
const { getByText, getByRole } = renderComponent();

expect(getByText(unitTitle)).toBeInTheDocument();
expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
waitFor(() => {
expect(getByText(unitTitle)).toBeInTheDocument();
expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
});
});

it('render HeaderTitle with open edit form', () => {
const { getByRole } = renderComponent({
isTitleEditFormOpen: true,
});

expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toBeInTheDocument();
expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toHaveValue(unitTitle);
expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
waitFor(() => {
expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toBeInTheDocument();
expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toHaveValue(unitTitle);
expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
});
});

it('calls toggle edit title form by clicking on Edit button', () => {
const { getByRole } = renderComponent();

const editTitleButton = getByRole('button', { name: messages.altButtonEdit.defaultMessage });
userEvent.click(editTitleButton);
expect(handleTitleEdit).toHaveBeenCalledTimes(1);
waitFor(() => {
const editTitleButton = getByRole('button', { name: messages.altButtonEdit.defaultMessage });
userEvent.click(editTitleButton);
expect(handleTitleEdit).toHaveBeenCalledTimes(1);
});
});

it('calls saving title by clicking outside or press Enter key', async () => {
const { getByRole } = renderComponent({
isTitleEditFormOpen: true,
});

const titleField = getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage });
userEvent.type(titleField, ' 1');
expect(titleField).toHaveValue(`${unitTitle} 1`);
userEvent.click(document.body);
expect(handleTitleEditSubmit).toHaveBeenCalledTimes(1);

userEvent.click(titleField);
userEvent.type(titleField, ' 2[Enter]');
expect(titleField).toHaveValue(`${unitTitle} 1 2`);
expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
waitFor(() => {
const titleField = getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage });
userEvent.type(titleField, ' 1');
expect(titleField).toHaveValue(`${unitTitle} 1`);
userEvent.click(document.body);
expect(handleTitleEditSubmit).toHaveBeenCalledTimes(1);

userEvent.click(titleField);
userEvent.type(titleField, ' 2[Enter]');
expect(titleField).toHaveValue(`${unitTitle} 1 2`);
expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
});
});

it('displays a visibility message with the selected groups for the unit', async () => {
Expand All @@ -117,7 +125,9 @@ describe('<HeaderTitle />', () => {
const visibilityMessage = messages.definedVisibilityMessage.defaultMessage
.replace('{selectedGroupsLabel}', 'Visibility group 1');

expect(getByText(visibilityMessage)).toBeInTheDocument();
waitFor(() => {
expect(getByText(visibilityMessage)).toBeInTheDocument();
});
});

it('displays a visibility message with the selected groups for some of xblock', async () => {
Expand All @@ -130,6 +140,8 @@ describe('<HeaderTitle />', () => {
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
const { getByText } = renderComponent();

expect(getByText(messages.commonVisibilityMessage.defaultMessage)).toBeInTheDocument();
waitFor(() => {
expect(getByText(messages.someVisibilityMessage.defaultMessage)).toBeInTheDocument();
});
});
});
9 changes: 8 additions & 1 deletion src/course-unit/sidebar/PublishControls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { useToggle } from '@openedx/paragon';
import { InfoOutline as InfoOutlineIcon } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import useCourseUnitData from './hooks';
import { useIframe } from '../context/hooks';
import { editCourseUnitVisibilityAndData } from '../data/thunk';
import { SidebarBody, SidebarFooter, SidebarHeader } from './components';
import { PUBLISH_TYPES } from '../constants';
import { PUBLISH_TYPES, messageTypes } from '../constants';
import { getCourseUnitData } from '../data/selectors';
import messages from './messages';
import ModalNotification from '../../generic/modal-notification';
Expand All @@ -20,6 +21,7 @@ const PublishControls = ({ blockId }) => {
visibleToStaffOnly,
} = useCourseUnitData(useSelector(getCourseUnitData));
const intl = useIntl();
const { sendMessageToIframe } = useIframe();

const [isDiscardModalOpen, openDiscardModal, closeDiscardModal] = useToggle(false);
const [isVisibleModalOpen, openVisibleModal, closeVisibleModal] = useToggle(false);
Expand All @@ -34,6 +36,11 @@ const PublishControls = ({ blockId }) => {
const handleCourseUnitDiscardChanges = () => {
closeDiscardModal();
dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.discardChanges));
// TODO: this artificial delay is a temporary solution
// to ensure the iframe content is properly refreshed.
setTimeout(() => {
sendMessageToIframe(messageTypes.refreshXBlock, null);
}, 1000);
};

const handleCourseUnitPublish = () => {
Expand Down
Loading

0 comments on commit a18a2e9

Please sign in to comment.