feat: refactor tinymce editor to sharedComponents (#255)

This commit is contained in:
Kristin Aoki
2023-02-28 16:37:52 -05:00
committed by GitHub
parent e0c5573c8d
commit c65f60ec10
68 changed files with 1172 additions and 1014 deletions

View File

@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SourceCodeModal renders as expected with default behavior 1`] = `
<BaseModal
close={[MockFunction]}
confirmAction={
<Button
0="S"
1="o"
2="M"
3="e"
4="v"
5="A"
6="l"
7="u"
8="e"
variant="primary"
>
<FormattedMessage
defaultMessage="Save"
description="Label for Save button for the source code editor"
id="authoring.texteditor.sourcecodemodal.next.label"
/>
</Button>
}
footerAction={null}
isOpen={false}
size="xl"
title="Edit Source Code"
>
<div
style={
Object {
"height": "300px",
"padding": "10px 30px",
}
}
>
<injectIntl(ShimmedIntlComponent)
innerRef="moCKrEf"
value="mOckHtMl"
/>
</div>
</BaseModal>
`;

View File

@@ -0,0 +1,27 @@
import { useRef } from 'react';
import * as module from './hooks';
export const getSaveBtnProps = ({ editorRef, ref, close }) => ({
onClick: () => {
if (editorRef && editorRef.current && ref && ref.current) {
const content = ref.current.state.doc.toString();
editorRef.current.setContent(content);
close();
}
},
});
export const prepareSourceCodeModal = ({ editorRef, close }) => {
const ref = useRef();
const saveBtnProps = module.getSaveBtnProps({ editorRef, ref, close });
if (editorRef && editorRef.current && typeof editorRef.current.getContent === 'function') {
const value = editorRef?.current?.getContent();
return { saveBtnProps, value, ref };
}
return { saveBtnProps, value: null, ref };
};
export default {
prepareSourceCodeModal,
};

View File

@@ -0,0 +1,60 @@
import React from 'react';
import * as module from './hooks';
jest.mock('react', () => ({
...jest.requireActual('react'),
useRef: jest.fn(val => ({ current: val })),
useEffect: jest.fn(),
useCallback: (cb, prereqs) => ({ cb, prereqs }),
}));
describe('SourceCodeModal hooks', () => {
const mockContent = 'sOmEMockHtML';
const mockSetContent = jest.fn();
const mockEditorRef = {
current:
{
setContent: mockSetContent,
getContent: jest.fn(() => mockContent),
},
};
const mockClose = jest.fn();
test('getSaveBtnProps', () => {
const mockRef = {
current: {
state: {
doc: mockContent,
},
},
};
const input = {
ref: mockRef,
editorRef: mockEditorRef,
close: mockClose,
};
const resultProps = module.getSaveBtnProps(input);
resultProps.onClick();
expect(mockSetContent).toHaveBeenCalledWith(mockContent);
expect(mockClose).toHaveBeenCalled();
});
test('prepareSourceCodeModal', () => {
const props = {
close: mockClose,
editorRef: mockEditorRef,
};
const mockRef = { current: 'rEf' };
const spyRef = jest.spyOn(React, 'useRef').mockReturnValueOnce(mockRef);
const mockButton = 'mOcKBuTton';
const spyButtons = jest.spyOn(module, 'getSaveBtnProps').mockImplementation(
() => mockButton,
);
const result = module.prepareSourceCodeModal(props);
expect(spyRef).toHaveBeenCalled();
expect(spyButtons).toHaveBeenCalled();
expect(result).toStrictEqual({ saveBtnProps: mockButton, value: mockEditorRef.current.getContent(), ref: mockRef });
});
});

View File

@@ -0,0 +1,59 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
FormattedMessage,
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import messages from './messages';
import hooks from './hooks';
import BaseModal from '../BaseModal';
import CodeEditor from '../CodeEditor';
export const SourceCodeModal = ({
isOpen,
close,
editorRef,
// injected
intl,
}) => {
const { saveBtnProps, value, ref } = hooks.prepareSourceCodeModal({ editorRef, close });
return (
<BaseModal
close={close}
size="xl"
confirmAction={(
<Button {...saveBtnProps} variant="primary">
<FormattedMessage {...messages.saveButtonLabel} />
</Button>
)}
isOpen={isOpen}
title={intl.formatMessage(messages.titleLabel)}
>
<div style={{ padding: '10px 30px', height: '300px' }}>
<CodeEditor
innerRef={ref}
value={value}
/>
</div>
</BaseModal>
);
};
SourceCodeModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
editorRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.any }),
]).isRequired,
// injected
intl: intlShape.isRequired,
};
export default injectIntl(SourceCodeModal);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { shallow } from 'enzyme';
import hooks from './hooks';
import { formatMessage } from '../../../testUtils';
import { SourceCodeModal } from '.';
jest.mock('./hooks', () => ({
prepareSourceCodeModal: jest.fn(() => {
}),
}));
describe('SourceCodeModal', () => {
const mockClose = jest.fn();
const props = {
isOpen: false,
close: mockClose,
editorRef: {
current: jest.fn(),
},
intl: { formatMessage },
};
test('renders as expected with default behavior', () => {
const mocksaveBtnProps = 'SoMevAlue';
const mockvalue = 'mOckHtMl';
const mockref = 'moCKrEf';
hooks.prepareSourceCodeModal.mockReturnValueOnce({
saveBtnProps: mocksaveBtnProps,
value: mockvalue,
ref: mockref,
});
expect(shallow(<SourceCodeModal {...props} />)).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,13 @@
export const messages = {
saveButtonLabel: {
id: 'authoring.texteditor.sourcecodemodal.next.label',
defaultMessage: 'Save',
description: 'Label for Save button for the source code editor',
},
titleLabel: {
id: 'authoring.texteditor.sourcecodemodal.title.label',
defaultMessage: 'Edit Source Code',
description: 'Title for the source code editor',
},
};
export default messages;