diff --git a/src/editors/AdvancedEditor.test.tsx b/src/editors/AdvancedEditor.test.tsx new file mode 100644 index 0000000000..32ab90945e --- /dev/null +++ b/src/editors/AdvancedEditor.test.tsx @@ -0,0 +1,62 @@ +import { getConfig } from '@edx/frontend-platform'; + +import { + render, + initializeMocks, +} from '../testUtils'; +import AdvancedEditor from './AdvancedEditor'; + +jest.mock('./containers/EditorContainer', () => ({ + EditorModalWrapper: jest.fn(() => (
Advanced Editor Iframe
)), +})); + +describe('AdvancedEditor', () => { + beforeEach(() => { + initializeMocks(); + }); + + it('should call onClose when receiving "cancel-clicked" message', () => { + const onCloseMock = jest.fn(); + + render(); + + const messageEvent = new MessageEvent('message', { + data: 'cancel-clicked', + origin: getConfig().STUDIO_BASE_URL, + }); + + window.dispatchEvent(messageEvent); + + expect(onCloseMock).toHaveBeenCalled(); + }); + + it('should call onClose when receiving "save-clicked" message', () => { + const onCloseMock = jest.fn(); + + render(); + + const messageEvent = new MessageEvent('message', { + data: 'save-clicked', + origin: getConfig().STUDIO_BASE_URL, + }); + + window.dispatchEvent(messageEvent); + + expect(onCloseMock).toHaveBeenCalled(); + }); + + it('should not call onClose if the message is from an invalid origin', () => { + const onCloseMock = jest.fn(); + + render(); + + const messageEvent = new MessageEvent('message', { + data: 'cancel-clicked', + origin: 'https://invalid-origin.com', + }); + + window.dispatchEvent(messageEvent); + + expect(onCloseMock).not.toHaveBeenCalled(); + }); +}); diff --git a/src/editors/AdvancedEditor.tsx b/src/editors/AdvancedEditor.tsx new file mode 100644 index 0000000000..a044e6ad9d --- /dev/null +++ b/src/editors/AdvancedEditor.tsx @@ -0,0 +1,41 @@ +import { useEffect } from 'react'; +import { getConfig } from '@edx/frontend-platform'; + +import { LibraryBlock } from '../library-authoring/LibraryBlock'; +import { EditorModalWrapper } from './containers/EditorContainer'; + +interface AdvancedEditorProps { + usageKey: string, + onClose: Function | null, +} + +const AdvancedEditor = ({ usageKey, onClose }: AdvancedEditorProps) => { + useEffect(() => { + const handleIframeMessage = (event) => { + if (event.origin !== getConfig().STUDIO_BASE_URL) { + return; + } + + if (onClose && (event.data === 'cancel-clicked' || event.data === 'save-clicked')) { + onClose(); + } + }; + + window.addEventListener('message', handleIframeMessage); + + return () => { + window.removeEventListener('message', handleIframeMessage); + }; + }, []); + + return ( + void}> + + + ); +}; + +export default AdvancedEditor; diff --git a/src/editors/Editor.tsx b/src/editors/Editor.tsx index 8e52448242..33f24c9f39 100644 --- a/src/editors/Editor.tsx +++ b/src/editors/Editor.tsx @@ -2,14 +2,13 @@ // as its parent, so they are tested together in EditorPage.test.tsx import React from 'react'; import { useDispatch } from 'react-redux'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import messages from './messages'; import * as hooks from './hooks'; import supportedEditors from './supportedEditors'; import type { EditorComponent } from './EditorComponent'; import { useEditorContext } from './EditorContext'; +import AdvancedEditor from './AdvancedEditor'; export interface Props extends EditorComponent { blockType: string; @@ -43,9 +42,17 @@ const Editor: React.FC = ({ const { fullScreen } = useEditorContext(); const EditorComponent = supportedEditors[blockType]; - const innerEditor = (EditorComponent !== undefined) - ? - : ; + + if (EditorComponent === undefined && blockId) { + return ( + + ); + } + + const innerEditor = ; if (fullScreen) { return ( diff --git a/src/editors/EditorPage.test.tsx b/src/editors/EditorPage.test.tsx index 4ae9817d3b..66d7ffac95 100644 --- a/src/editors/EditorPage.test.tsx +++ b/src/editors/EditorPage.test.tsx @@ -26,6 +26,9 @@ jest.spyOn(editorCmsApi, 'fetchByUnitId').mockImplementation(async () => ({ }], }, })); +jest.mock('../library-authoring/LibraryBlock', () => ({ + LibraryBlock: jest.fn(() => (
Advanced Editor Iframe
)), +})); const defaultPropsHtml = { blockId: 'block-v1:Org+TS100+24+type@html+block@123456html', @@ -79,9 +82,7 @@ describe('EditorPage', () => { expect(modalElement.classList).not.toContain('pgn__modal-xl'); }); - test('it shows an error message if there is no corresponding editor', async () => { - // We can edit 'html', 'problem', and 'video' blocks. - // But if we try to edit some other type, say 'fake', we should get an error: + test('it shows the Advanced Editor if there is no corresponding editor', async () => { jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => ( // eslint-disable-next-line { status: 200, data: { display_name: 'Fake Un-editable Block', category: 'fake', metadata: {}, data: '' } } )); @@ -93,6 +94,6 @@ describe('EditorPage', () => { }; render(); - expect(await screen.findByText('Error: Could Not find Editor')).toBeInTheDocument(); + expect(await screen.findByText('Advanced Editor Iframe')).toBeInTheDocument(); }); }); diff --git a/src/editors/messages.ts b/src/editors/messages.ts index 4b1134a270..1ac257514e 100644 --- a/src/editors/messages.ts +++ b/src/editors/messages.ts @@ -2,11 +2,6 @@ import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ - couldNotFindEditor: { - id: 'authoring.editorpage.selecteditor.error', - defaultMessage: 'Error: Could Not find Editor', - description: 'Error Message Dispayed When An unsopported Editor is desired in V2', - }, dropVideoFileHere: { defaultMessage: 'Drag and drop video here or click to upload', id: 'VideoUploadEditor.dropVideoFileHere', diff --git a/src/library-authoring/LibraryBlock/LibraryBlock.tsx b/src/library-authoring/LibraryBlock/LibraryBlock.tsx index 4397090edd..e635f1f8ba 100644 --- a/src/library-authoring/LibraryBlock/LibraryBlock.tsx +++ b/src/library-authoring/LibraryBlock/LibraryBlock.tsx @@ -10,6 +10,7 @@ interface LibraryBlockProps { onBlockNotification?: (event: { eventType: string; [key: string]: any }) => void; usageKey: string; version?: VersionSpec; + view?: string; } /** * React component that displays an XBlock in a sandboxed IFrame. @@ -20,7 +21,12 @@ interface LibraryBlockProps { * cannot access things like the user's cookies, nor can it make GET/POST * requests as the user. However, it is allowed to call any XBlock handlers. */ -export const LibraryBlock = ({ onBlockNotification, usageKey, version }: LibraryBlockProps) => { +export const LibraryBlock = ({ + onBlockNotification, + usageKey, + version, + view, +}: LibraryBlockProps) => { const iframeRef = useRef(null); const [iFrameHeight, setIFrameHeight] = useState(50); const studioBaseUrl = getConfig().STUDIO_BASE_URL; @@ -71,6 +77,8 @@ export const LibraryBlock = ({ onBlockNotification, usageKey, version }: Library const queryStr = version ? `?version=${version}` : ''; + const xblockView = view ?? 'student_view'; + return (
> = () => {