-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Open Editors in a Modal (library components only) [FC-0062] (#1357
) * feat: allow opening editors in modals * refactor: add an EditorContext * test: update tests accordingly * test: make testUtils call clearAllMocks() automatically :)
- Loading branch information
1 parent
83322e2
commit 8c125df
Showing
51 changed files
with
845 additions
and
1,220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
}; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
Oops, something went wrong.