Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/content-tags-drawer/ContentTagsDrawer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ describe('<ContentTagsDrawer />', () => {
initializeMocks();
});

afterEach(() => {
jest.clearAllMocks();
});

it('should render page and page title correctly', () => {
renderDrawer(stagedTagsId);
expect(screen.getByText('Manage tags')).toBeInTheDocument();
Expand Down
55 changes: 0 additions & 55 deletions src/editors/Editor.test.jsx

This file was deleted.

34 changes: 22 additions & 12 deletions src/editors/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Note: there is no Editor.test.tsx. This component only works together with
// <EditorPage> 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';
Expand All @@ -7,13 +9,15 @@ import * as hooks from './hooks';

import supportedEditors from './supportedEditors';
import type { EditorComponent } from './EditorComponent';
import { useEditorContext } from './EditorContext';

export interface Props extends EditorComponent {
blockType: string;
blockId: string | null;
learningContextId: string | null;
lmsEndpointUrl: string | null;
studioEndpointUrl: string | null;
fullScreen?: boolean;
}

const Editor: React.FC<Props> = ({
Expand All @@ -36,23 +40,29 @@ const Editor: React.FC<Props> = ({
studioEndpointUrl,
},
});
const { fullScreen } = useEditorContext();

const EditorComponent = supportedEditors[blockType];
return (
<div
className="d-flex flex-column"
>
const innerEditor = (EditorComponent !== undefined)
? <EditorComponent {...{ onClose, returnFunction }} />
: <FormattedMessage {...messages.couldNotFindEditor} />;

if (fullScreen) {
return (
<div
className="pgn__modal-fullscreen h-100"
role="dialog"
aria-label={blockType}
className="d-flex flex-column"
>
{(EditorComponent !== undefined)
? <EditorComponent {...{ onClose, returnFunction }} />
: <FormattedMessage {...messages.couldNotFindEditor} />}
<div
className="pgn__modal-fullscreen h-100"
role="dialog"
aria-label={blockType}
>
{innerEditor}
</div>
</div>
</div>
);
);
}
return innerEditor;
};

export default Editor;
39 changes: 39 additions & 0 deletions src/editors/EditorContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';

/**
* Shared context that's used by all our editors.
*
* Note: we're in the process of moving things from redux into this.
*/
export interface EditorContext {
learningContextId: string;
/**
* When editing components in the libraries part of the Authoring MFE, we show
* the editors in a modal (fullScreen = false). This is the preferred approach
* so that authors can see context behind the modal.
* However, when making edits from the legacy course view, we display the
* editors in a fullscreen view. This approach is deprecated.
*/
fullScreen: boolean;
}

const context = React.createContext<EditorContext | undefined>(undefined);

/** Hook to get the editor context (shared context) */
export function useEditorContext() {
const ctx = React.useContext(context);
if (ctx === undefined) {
/* istanbul ignore next */
throw new Error('This component needs to be wrapped in <EditorContextProvider>');
}
return ctx;
}

export const EditorContextProvider: React.FC<{
children: React.ReactNode,
learningContextId: string;
fullScreen: boolean;
}> = ({ children, ...contextData }) => {
const ctx: EditorContext = React.useMemo(() => ({ ...contextData }), []);
return <context.Provider value={ctx}>{children}</context.Provider>;
};
58 changes: 0 additions & 58 deletions src/editors/EditorPage.jsx

This file was deleted.

32 changes: 0 additions & 32 deletions src/editors/EditorPage.test.jsx

This file was deleted.

98 changes: 98 additions & 0 deletions src/editors/EditorPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { snakeCaseObject } from '@edx/frontend-platform';
import {
render,
screen,
initializeMocks,
} from '../testUtils';
import editorCmsApi from './data/services/cms/api';

import EditorPage from './EditorPage';

// Mock this plugins component:
jest.mock('frontend-components-tinymce-advanced-plugins', () => ({ a11ycheckerCss: '' }));
// Always mock out the "fetch course images" endpoint:
jest.spyOn(editorCmsApi, 'fetchImages').mockImplementation(async () => ( // eslint-disable-next-line
{ data: { assets: [], start: 0, end: 0, page: 0, pageSize: 50, totalCount: 0 } }
));
// Mock out the 'get ancestors' API:
jest.spyOn(editorCmsApi, 'fetchByUnitId').mockImplementation(async () => ({
status: 200,
data: {
ancestors: [{
id: 'block-v1:Org+TS100+24+type@vertical+block@parent',
display_name: 'You-Knit? The Test Unit',
category: 'vertical',
has_children: true,
}],
},
}));

const defaultPropsHtml = {
blockId: 'block-v1:Org+TS100+24+type@html+block@123456html',
blockType: 'html',
courseId: 'course-v1:Org+TS100+24',
lmsEndpointUrl: 'http://lms.test.none/',
studioEndpointUrl: 'http://cms.test.none/',
onClose: jest.fn(),
fullScreen: false,
};
const fieldsHtml = {
displayName: 'Introduction to Testing',
data: '<p>This is a text component which uses <strong>HTML</strong>.</p>',
metadata: { displayName: 'Introduction to Testing' },
};

describe('EditorPage', () => {
beforeEach(() => {
initializeMocks();
});

test('it can display the Text (html) editor in a modal', async () => {
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => (
{ status: 200, data: snakeCaseObject(fieldsHtml) }
));

render(<EditorPage {...defaultPropsHtml} />);

// Then the editor should open
expect(await screen.findByRole('heading', { name: /Introduction to Testing/ })).toBeInTheDocument();

const modalElement = screen.getByRole('dialog');
expect(modalElement.classList).toContain('pgn__modal');
expect(modalElement.classList).toContain('pgn__modal-xl');
expect(modalElement.classList).not.toContain('pgn__modal-fullscreen');
});

test('it can display the Text (html) editor as a full page (when coming from the legacy UI)', async () => {
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => (
{ status: 200, data: snakeCaseObject(fieldsHtml) }
));

render(<EditorPage {...defaultPropsHtml} fullScreen />);

// Then the editor should open
expect(await screen.findByRole('heading', { name: /Introduction to Testing/ })).toBeInTheDocument();

const modalElement = screen.getByRole('dialog');
expect(modalElement.classList).toContain('pgn__modal-fullscreen');
expect(modalElement.classList).not.toContain('pgn__modal');
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:
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => ( // eslint-disable-next-line
{ status: 200, data: { display_name: 'Fake Un-editable Block', category: 'fake', metadata: {}, data: '' } }
));

const defaultPropsFake = {
...defaultPropsHtml,
blockId: 'block-v1:Org+TS100+24+type@fake+block@123456fake',
blockType: 'fake',
};
render(<EditorPage {...defaultPropsFake} />);

expect(await screen.findByText('Error: Could Not find Editor')).toBeInTheDocument();
});
});
Loading