Skip to content

Commit

Permalink
feat: Text Editor and V2 Editor Framework (#9)
Browse files Browse the repository at this point in the history
Text Editor and V2 Editor Framework. Documentation to come.
  • Loading branch information
connorhaugh authored Jan 25, 2022
1 parent c93c5e9 commit d8c6b8d
Show file tree
Hide file tree
Showing 21 changed files with 929 additions and 2 deletions.
149 changes: 149 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"@edx/frontend-build": "9.0.4",
"@edx/frontend-platform": "1.14.4",
"@edx/paragon": "16.22.0",
"@testing-library/dom": "^8.11.1",
"@testing-library/react": "12.1.1",
"@testing-library/user-event": "^13.5.0",
"codecov": "3.8.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
Expand All @@ -52,6 +55,7 @@
"redux-saga": "1.1.3"
},
"dependencies": {
"@tinymce/tinymce-react": "^3.13.0",
"babel-polyfill": "6.26.0",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.2"
Expand Down
77 changes: 77 additions & 0 deletions src/editors/EditorFooter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useContext, useEffect } from 'react';
import {
Spinner, ActionRow, Button, ModalDialog, Toast,
} from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import EditorPageContext from './EditorPageContext';
import { ActionStates } from './data/constants';

const navigateAway = (destination) => {
window.location.assign(destination);
};

export default function EditorFooter() {
const {
blockLoading,
setBlockContent,
unitUrlLoading,
unitUrl,
setSaveUnderway,
saveUnderway,
saveResponse,
studioEndpointUrl,
editorRef,
} = useContext(EditorPageContext);

const onSaveClicked = () => {
if (blockLoading === ActionStates.FINISHED && unitUrlLoading === ActionStates.FINISHED && editorRef) {
const content = editorRef.current.getContent();
setBlockContent(content);
setSaveUnderway(ActionStates.IN_PROGRESS);
}
};
const onCancelClicked = () => {
if (unitUrlLoading === ActionStates.FINISHED) {
const destination = `${studioEndpointUrl}/container/${unitUrl.data.ancestors[0].id}`;
navigateAway(destination);
}
};
useEffect(() => {
if (saveUnderway === ActionStates.FINISHED
&& blockLoading === ActionStates.FINISHED
&& unitUrlLoading === ActionStates.FINISHED) {
const destination = `${studioEndpointUrl}/container/${unitUrl.data.ancestors[0].id}`;
navigateAway(destination);
}
}, [saveUnderway]);
return (
<div className="editor-footer mt-auto">
{ saveUnderway === 'complete' && saveResponse.error != null
&& (
<Toast><FormattedMessage
id="authoring.editorfooter.save.error"
defaultMessage="Error: Content save failed. Try again later."
description="Error message displayed when content fails to save."
/>
</Toast>
)}
<ModalDialog.Footer>
<ActionRow>
<ActionRow.Spacer />
<Button aria-label="Discard Changes and Return to Learning Context" variant="tertiary" onClick={onCancelClicked}>Cancel</Button>
<Button aria-label="Save Changes and Return to Learning Context" onClick={onSaveClicked}>
{unitUrlLoading !== ActionStates.FINISHED
? <Spinner animation="border" className="mr-3" />
: (
<FormattedMessage
id="authoring.editorfooter.savebutton.label"
defaultMessage="Add To Course"
description="Label for Save button"
/>
)}
</Button>
</ActionRow>
</ModalDialog.Footer>
</div>
);
}
104 changes: 104 additions & 0 deletions src/editors/EditorFooter.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { mount } from 'enzyme';
import EditorFooter from './EditorFooter';
import EditorPageContext from './EditorPageContext';
import { ActionStates } from './data/constants';
import EditorPageProvider from './EditorPageProvider';
import { saveBlock } from './data/api';

const locationTemp = window.location;
beforeAll(() => {
delete window.location;
window.location = {
assign: jest.fn(),
};
});
afterAll(() => {
window.location = locationTemp;
});

jest.mock('./data/api', () => {
const originalModule = jest.requireActual('./data/api');
// Mock the default export and named export saveBlock
return {
__esModule: true,
...originalModule,
saveBlock: jest.fn(() => {}),
};
});

jest.spyOn(React, 'useRef').mockReturnValue({
current: {
getContent: () => '',
},
});

test('Rendering: loaded', () => {
const context = {
unitUrlLoading: ActionStates.FINISHED,
};
render(
<EditorPageContext.Provider value={context}>
<EditorFooter />
</EditorPageContext.Provider>,
);
expect(screen.getByText('Cancel')).toBeTruthy();
expect(screen.getByText('Add To Course')).toBeTruthy();
});

test('Rendering: loading url', () => {
const context = {
unitUrlLoading: ActionStates.NOT_BEGUN,
};
render(
<EditorPageContext.Provider value={context}>
<EditorFooter />
</EditorPageContext.Provider>,
);
expect(screen.getByText('Cancel')).toBeTruthy();
expect(screen.getAllByRole('button', { 'aria-label': 'Save' })).toBeTruthy();
expect(screen.queryByText('Add To Course')).toBeNull();
});

test('Navigation: Cancel', () => {
const context = {
unitUrlLoading: ActionStates.FINISHED,
unitUrl: {
data: {
ancestors:
[
{ id: 'fakeblockid' },
],
},
},
studioEndpointUrl: 'Testurl',
};
render(
<EditorPageContext.Provider value={context}>
<EditorFooter />
</EditorPageContext.Provider>,
);
expect(screen.getByText('Cancel')).toBeTruthy();
userEvent.click(screen.getByText('Cancel'));
expect(window.location.assign).toHaveBeenCalled();
});

test('Navigation: Save', () => {
const wrapper = mount(
<EditorPageProvider
blockType="html"
courseId="myCourse101"
blockId="redosablocksalot"
studioEndpointUrl="celaicboss.axe"
>
<EditorFooter />
</EditorPageProvider>,
);
const button = wrapper.find({ children: 'Add To Course' });
expect(button).toBeTruthy();
button.simulate('click');
expect(saveBlock).toHaveBeenCalled();
expect(window.location.assign).toHaveBeenCalled();
});
Loading

0 comments on commit d8c6b8d

Please sign in to comment.