fix: text editor opening blank with no images (#492)
* fix: pasting and images only insert at beginning * fix: add image click not showing gallery * chore: increase code coverage * fix: empty string when no srcs need updates * fix: assest to static in raw editor
This commit is contained in:
@@ -16,10 +16,12 @@ export const ExplanationWidget = ({
|
||||
intl,
|
||||
}) => {
|
||||
const { editorRef, refReady, setEditorRef } = prepareEditorRef();
|
||||
const solutionContent = replaceStaticWithAsset({
|
||||
initialContent: settings?.solutionExplanation || '',
|
||||
const initialContent = settings?.solutionExplanation || '';
|
||||
const newContent = replaceStaticWithAsset({
|
||||
initialContent,
|
||||
learningContextId,
|
||||
});
|
||||
const solutionContent = newContent || initialContent;
|
||||
if (!refReady) { return null; }
|
||||
return (
|
||||
<div className="tinyMceWidget mt-4 text-primary-500">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { formatMessage } from '../../../../../../testUtils';
|
||||
@@ -24,11 +23,11 @@ jest.mock('../../../../../data/redux', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../sharedComponents/TinyMceWidget/hooks', () => ({
|
||||
...jest.requireActual('../../../../../sharedComponents/TinyMceWidget/hooks'),
|
||||
prepareEditorRef: jest.fn(() => ({
|
||||
refReady: true,
|
||||
setEditorRef: jest.fn().mockName('prepareEditorRef.setEditorRef'),
|
||||
})),
|
||||
replaceStaticWithAsset: jest.fn(() => 'This is my solution'),
|
||||
}));
|
||||
|
||||
describe('SolutionWidget', () => {
|
||||
|
||||
@@ -16,10 +16,12 @@ export const QuestionWidget = ({
|
||||
intl,
|
||||
}) => {
|
||||
const { editorRef, refReady, setEditorRef } = prepareEditorRef();
|
||||
const questionContent = replaceStaticWithAsset({
|
||||
initialContent: question,
|
||||
const initialContent = question;
|
||||
const newContent = replaceStaticWithAsset({
|
||||
initialContent,
|
||||
learningContextId,
|
||||
});
|
||||
const questionContent = newContent || initialContent;
|
||||
if (!refReady) { return null; }
|
||||
return (
|
||||
<div className="tinyMceWidget">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { formatMessage } from '../../../../../../testUtils';
|
||||
@@ -29,11 +28,11 @@ jest.mock('../../../../../data/redux', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../sharedComponents/TinyMceWidget/hooks', () => ({
|
||||
...jest.requireActual('../../../../../sharedComponents/TinyMceWidget/hooks'),
|
||||
prepareEditorRef: jest.fn(() => ({
|
||||
refReady: true,
|
||||
setEditorRef: jest.fn().mockName('prepareEditorRef.setEditorRef'),
|
||||
})),
|
||||
replaceStaticWithAsset: jest.fn(() => 'This is my question'),
|
||||
}));
|
||||
|
||||
describe('QuestionWidget', () => {
|
||||
|
||||
@@ -191,3 +191,52 @@ exports[`TextEditor snapshots renders as expected with default behavior 1`] = `
|
||||
</div>
|
||||
</EditorContainer>
|
||||
`;
|
||||
|
||||
exports[`TextEditor snapshots renders static images with relative paths 1`] = `
|
||||
<EditorContainer
|
||||
getContent={
|
||||
{
|
||||
"getContent": {
|
||||
"editorRef": {
|
||||
"current": {
|
||||
"value": "something",
|
||||
},
|
||||
},
|
||||
"showRawEditor": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
onClose={[MockFunction props.onClose]}
|
||||
returnFunction={null}
|
||||
>
|
||||
<div
|
||||
className="editor-body h-75 overflow-auto"
|
||||
>
|
||||
<Toast
|
||||
onClose={[MockFunction hooks.nullMethod]}
|
||||
show={false}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Error: Could Not Load Text Content"
|
||||
description="Error Message Dispayed When HTML content fails to Load"
|
||||
id="authoring.texteditor.load.error"
|
||||
/>
|
||||
</Toast>
|
||||
<[object Object]
|
||||
editorContentHtml="eDiTablE Text with <img src="/asset+org+run+type@asset+block@img.jpg" />"
|
||||
editorRef={
|
||||
{
|
||||
"current": {
|
||||
"value": "something",
|
||||
},
|
||||
}
|
||||
}
|
||||
editorType="text"
|
||||
height="100%"
|
||||
initializeEditor={[MockFunction args.intializeEditor]}
|
||||
minHeight={500}
|
||||
setEditorRef={[MockFunction hooks.prepareEditorRef.setEditorRef]}
|
||||
/>
|
||||
</div>
|
||||
</EditorContainer>
|
||||
`;
|
||||
|
||||
@@ -32,10 +32,12 @@ export const TextEditor = ({
|
||||
intl,
|
||||
}) => {
|
||||
const { editorRef, refReady, setEditorRef } = prepareEditorRef();
|
||||
const editorContent = blockValue ? replaceStaticWithAsset({
|
||||
initialContent: blockValue.data.data,
|
||||
const initialContent = blockValue ? blockValue.data.data : '';
|
||||
const newContent = replaceStaticWithAsset({
|
||||
initialContent,
|
||||
learningContextId,
|
||||
}) : '';
|
||||
});
|
||||
const editorContent = newContent || initialContent;
|
||||
|
||||
if (!refReady) { return null; }
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ jest.mock('./hooks', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('../../sharedComponents/TinyMceWidget/hooks', () => ({
|
||||
...jest.requireActual('../../sharedComponents/TinyMceWidget/hooks'),
|
||||
prepareEditorRef: jest.fn(() => ({
|
||||
editorRef: { current: { value: 'something' } },
|
||||
refReady: true,
|
||||
setEditorRef: jest.fn().mockName('hooks.prepareEditorRef.setEditorRef'),
|
||||
})),
|
||||
replaceStaticWithAsset: jest.fn(() => 'eDiTablE Text'),
|
||||
}));
|
||||
|
||||
jest.mock('react', () => {
|
||||
@@ -88,6 +88,13 @@ describe('TextEditor', () => {
|
||||
test('renders as expected with default behavior', () => {
|
||||
expect(shallow(<TextEditor {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('renders static images with relative paths', () => {
|
||||
const updatedProps = {
|
||||
...props,
|
||||
blockValue: { data: { data: 'eDiTablE Text with <img src="/static/img.jpg" />' } },
|
||||
};
|
||||
expect(shallow(<TextEditor {...updatedProps} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('not yet loaded, Spinner appears', () => {
|
||||
expect(shallow(<TextEditor {...props} blockFinished={false} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RawEditor renders as expected with content equal to null 1`] = `
|
||||
<div>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
You are using the raw
|
||||
html
|
||||
editor.
|
||||
</Alert>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`RawEditor renders as expected with default behavior 1`] = `
|
||||
<div>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
You are using the raw
|
||||
html
|
||||
editor.
|
||||
</Alert>
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
innerRef={
|
||||
{
|
||||
"current": {
|
||||
"value": "Ref Value",
|
||||
},
|
||||
}
|
||||
}
|
||||
lang="html"
|
||||
value="eDiTablE Text"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`RawEditor renders as expected with lang equal to xml 1`] = `
|
||||
<div>
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
innerRef={
|
||||
{
|
||||
"current": {
|
||||
"value": "Ref Value",
|
||||
},
|
||||
}
|
||||
}
|
||||
lang="xml"
|
||||
value="eDiTablE Text"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Alert } from '@openedx/paragon';
|
||||
|
||||
import CodeEditor from '../CodeEditor';
|
||||
import { setAssetToStaticUrl } from '../TinyMceWidget/hooks';
|
||||
|
||||
function getValue(content) {
|
||||
if (!content) { return null; }
|
||||
@@ -15,7 +16,8 @@ export const RawEditor = ({
|
||||
content,
|
||||
lang,
|
||||
}) => {
|
||||
const value = getValue(content);
|
||||
const value = getValue(content) || '';
|
||||
const staticUpdate = setAssetToStaticUrl({ editorValue: value });
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -27,8 +29,9 @@ export const RawEditor = ({
|
||||
{ value ? (
|
||||
<CodeEditor
|
||||
innerRef={editorRef}
|
||||
value={value}
|
||||
value={staticUpdate}
|
||||
lang={lang}
|
||||
data-testid="code-editor"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
import { RawEditor } from '.';
|
||||
|
||||
jest.unmock('@openedx/paragon');
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
<IntlProvider locale="en">
|
||||
<RawEditor {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
describe('RawEditor', () => {
|
||||
const defaultProps = {
|
||||
editorRef: {
|
||||
@@ -10,7 +19,7 @@ describe('RawEditor', () => {
|
||||
value: 'Ref Value',
|
||||
},
|
||||
},
|
||||
content: { data: { data: 'eDiTablE Text' } },
|
||||
content: { data: { data: 'eDiTablE Text HtmL' } },
|
||||
lang: 'html',
|
||||
};
|
||||
const xmlProps = {
|
||||
@@ -19,7 +28,7 @@ describe('RawEditor', () => {
|
||||
value: 'Ref Value',
|
||||
},
|
||||
},
|
||||
content: { data: { data: 'eDiTablE Text' } },
|
||||
content: { data: { data: 'eDiTablE Text XMl' } },
|
||||
lang: 'xml',
|
||||
};
|
||||
const noContentProps = {
|
||||
@@ -33,13 +42,39 @@ describe('RawEditor', () => {
|
||||
width: { width: '80%' },
|
||||
};
|
||||
|
||||
test('renders as expected with default behavior', () => {
|
||||
expect(shallow(<RawEditor {...defaultProps} />).snapshot).toMatchSnapshot();
|
||||
it('renders as expected with default behavior', () => {
|
||||
renderComponent(defaultProps);
|
||||
expect(screen.getByRole('alert')).toBeVisible();
|
||||
|
||||
expect(screen.getByText('eDiTablE Text HtmL')).toBeVisible();
|
||||
});
|
||||
test('renders as expected with lang equal to xml', () => {
|
||||
expect(shallow(<RawEditor {...xmlProps} />).snapshot).toMatchSnapshot();
|
||||
|
||||
it('updates the assets to static srcs', () => {
|
||||
const updatedProps = {
|
||||
...defaultProps,
|
||||
content: 'pick <img src="/asset-v1:org+run+term+type@asset+block@img.jpeg" /> or <img src="/assets/courseware/v1/hash/asset-v1:org+run+term+type@asset+block/img2.jpeg" />',
|
||||
};
|
||||
renderComponent(updatedProps);
|
||||
expect(screen.getByText('"/static/img.jpeg"')).toBeVisible();
|
||||
|
||||
expect(screen.getByText('"/static/img2.jpeg"')).toBeVisible();
|
||||
|
||||
expect(screen.queryByText('"/asset-v1:org+run+term+type@asset+block@img.jpeg"')).toBeNull();
|
||||
|
||||
expect(screen.queryByText('"/assets/courseware/v1/hash/asset-v1:org+run+term+type@asset+block/img2.jpeg"')).toBeNull();
|
||||
});
|
||||
test('renders as expected with content equal to null', () => {
|
||||
expect(shallow(<RawEditor {...noContentProps} />).snapshot).toMatchSnapshot();
|
||||
|
||||
it('renders as expected with lang equal to xml', () => {
|
||||
renderComponent(xmlProps);
|
||||
expect(screen.queryByRole('alert')).toBeNull();
|
||||
|
||||
expect(screen.getByText('eDiTablE Text XMl')).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders as expected with content equal to null', () => {
|
||||
renderComponent(noContentProps);
|
||||
expect(screen.getByRole('alert')).toBeVisible();
|
||||
|
||||
expect(screen.queryByTestId('code-editor')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,15 +403,20 @@ export const setAssetToStaticUrl = ({ editorValue, lmsEndpointUrl }) => {
|
||||
let content = editorValue.replace(regExLmsEndpointUrl, '');
|
||||
|
||||
const assetSrcs = typeof content === 'string' ? content.split(/(src="|src="|href="|href=")/g) : [];
|
||||
assetSrcs.forEach(src => {
|
||||
if (src.startsWith('/asset')) {
|
||||
assetSrcs.filter(src => src.startsWith('/asset')).forEach(src => {
|
||||
let nameFromEditorSrc;
|
||||
if (src.match(/\/assets\/.+\/asset-v1:\S+[+]\S+[@]\S+[+]\S+\//)?.length >= 1) {
|
||||
const assetBlockName = src.substring(0, src.search(/("|")/));
|
||||
const dividedSrc = assetBlockName.split(/\/assets\/.+\/asset-v1:\S+[+]\S+[@]\S+[+]\S+\//);
|
||||
[, nameFromEditorSrc] = dividedSrc;
|
||||
} else {
|
||||
const assetBlockName = src.substring(src.indexOf('@') + 1, src.search(/("|")/));
|
||||
const nameFromEditorSrc = assetBlockName.substring(assetBlockName.indexOf('@') + 1);
|
||||
const portableUrl = getStaticUrl({ displayName: nameFromEditorSrc });
|
||||
const currentSrc = src.substring(0, src.search(/("|")/));
|
||||
const updatedContent = content.replace(currentSrc, portableUrl);
|
||||
content = updatedContent;
|
||||
nameFromEditorSrc = assetBlockName.substring(assetBlockName.indexOf('@') + 1);
|
||||
}
|
||||
const portableUrl = getStaticUrl({ displayName: nameFromEditorSrc });
|
||||
const currentSrc = src.substring(0, src.search(/("|")/));
|
||||
const updatedContent = content.replace(currentSrc, portableUrl);
|
||||
content = updatedContent;
|
||||
});
|
||||
return content;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user