From e5ea0a096cf4d55284affa6171124962709edb2c Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Wed, 31 Aug 2022 12:03:07 -0400 Subject: [PATCH] feat: change image absolute urls to relative urls --- .../containers/EditorContainer/hooks.js | 36 ++++++++++++++++++- .../containers/EditorContainer/hooks.test.jsx | 21 +++++++++-- .../components/ImageUploadModal.jsx | 2 +- .../components/ImageUploadModal.test.jsx | 4 +-- src/editors/containers/TextEditor/hooks.js | 16 +++++---- .../containers/TextEditor/hooks.test.jsx | 22 +++++++----- src/editors/data/redux/app/selectors.test.js | 1 + 7 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/editors/containers/EditorContainer/hooks.js b/src/editors/containers/EditorContainer/hooks.js index 9bf835ac6..32f3a8346 100644 --- a/src/editors/containers/EditorContainer/hooks.js +++ b/src/editors/containers/EditorContainer/hooks.js @@ -11,11 +11,45 @@ export const { saveBlock, } = appHooks; +export const setAssetToStaticUrl = (images, getContent) => { + /* For assets to remain usable across course instances, we convert their url to be course-agnostic. + * For example, /assets/course//filename gets converted to /static/filename. This is + * important for rerunning courses and importing/exporting course as the /static/ part of the url + * allows the asset to be mapped to the new course run. + */ + let content = getContent(); + const imageUrls = []; + const imgsArray = Object.values(images); + imgsArray.forEach(image => { + imageUrls.push({ portableUrl: image.portableUrl, displayName: image.displayName }); + }); + const imageSrcs = content.split('src="'); + imageSrcs.forEach(src => { + if (src.startsWith('/asset') && imageUrls.length > 0) { + const nameFromEditorSrc = src.substring(src.lastIndexOf('@') + 1, src.indexOf('"')); + const nameFromStudioSrc = nameFromEditorSrc.substring(nameFromEditorSrc.indexOf('/') + 1); + let portableUrl; + imageUrls.forEach((url) => { + if (url.displayName === nameFromEditorSrc || url.displayName === nameFromStudioSrc) { + portableUrl = url.portableUrl; + } + }); + if (portableUrl) { + const currentSrc = src.substring(0, src.indexOf('"')); + const updatedContent = content.replace(currentSrc, portableUrl); + content = updatedContent; + } + } + }); + return content; +}; + export const handleSaveClicked = ({ getContent, dispatch }) => { const destination = useSelector(selectors.app.returnUrl); const analytics = useSelector(selectors.app.analytics); + const images = useSelector(selectors.app.images); return () => saveBlock({ - content: getContent(), + content: setAssetToStaticUrl(images, getContent), destination, analytics, dispatch, diff --git a/src/editors/containers/EditorContainer/hooks.test.jsx b/src/editors/containers/EditorContainer/hooks.test.jsx index 3f48607cd..3929d4bb2 100644 --- a/src/editors/containers/EditorContainer/hooks.test.jsx +++ b/src/editors/containers/EditorContainer/hooks.test.jsx @@ -11,6 +11,7 @@ jest.mock('../../data/redux', () => ({ selectors: { app: { isInitialized: (state) => ({ isInitialized: state }), + images: (state) => ({ images: state }), }, requests: { isFailed: (...args) => ({ requestFailed: args }), @@ -25,6 +26,20 @@ jest.mock('../../hooks', () => ({ const dispatch = jest.fn(); describe('EditorContainer hooks', () => { + describe('non-state hooks', () => { + describe('replaceStaticwithAsset', () => { + it('returns content with updated img links', () => { + const getContent = jest.fn(() => ' '); + const images = [ + { portableUrl: '/static/soMEImagEURl', displayName: 'soMEImagEURl' }, + { portableUrl: '/static/soMEImagEURl1', displayName: 'soMEImagEURl1' }, + ]; + const content = hooks.setAssetToStaticUrl(images, getContent); + expect(getContent).toHaveBeenCalled(); + expect(content).toEqual(' '); + }); + }); + }); describe('forwarded hooks', () => { it('forwards navigateCallback from app hooks', () => { expect(hooks.navigateCallback).toEqual(appHooks.navigateCallback); @@ -41,17 +56,19 @@ describe('EditorContainer hooks', () => { jest.clearAllMocks(); }); describe('handleSaveClicked', () => { - it('returns callback to saveBlock with dispatch and content from getContent', () => { + it('returns callback to saveBlock with dispatch and content from setAssetToStaticUrl', () => { const getContent = () => 'myTestContentValue'; + const setAssetToStaticUrl = () => 'myTestContentValue'; const output = hooks.handleSaveClicked({ getContent, + images: { portableUrl: '/static/sOmEuiMAge.jpeg', displayName: 'sOmEuiMAge' }, destination: 'testDEsTURL', analytics: 'soMEanALytics', dispatch, }); output(); expect(appHooks.saveBlock).toHaveBeenCalledWith({ - content: getContent(), + content: setAssetToStaticUrl(reactRedux.useSelector(selectors.app.images), getContent), destination: reactRedux.useSelector(selectors.app.returnUrl), analytics: reactRedux.useSelector(selectors.app.analytics), dispatch, diff --git a/src/editors/containers/TextEditor/components/ImageUploadModal.jsx b/src/editors/containers/TextEditor/components/ImageUploadModal.jsx index 9b5eea5a5..ad86f9bae 100644 --- a/src/editors/containers/TextEditor/components/ImageUploadModal.jsx +++ b/src/editors/containers/TextEditor/components/ImageUploadModal.jsx @@ -11,7 +11,7 @@ export const propsString = (props) => ( ); export const imgProps = ({ settings, selection }) => ({ - src: selection.externalUrl, + src: selection.url, alt: settings.isDecorative ? '' : settings.altText, width: settings.dimensions.width, height: settings.dimensions.height, diff --git a/src/editors/containers/TextEditor/components/ImageUploadModal.test.jsx b/src/editors/containers/TextEditor/components/ImageUploadModal.test.jsx index 4b89e7e59..4a0f5b0bc 100644 --- a/src/editors/containers/TextEditor/components/ImageUploadModal.test.jsx +++ b/src/editors/containers/TextEditor/components/ImageUploadModal.test.jsx @@ -25,9 +25,9 @@ const settings = { describe('ImageUploadModal', () => { describe('hooks', () => { describe('imgTag', () => { - const selection = { externalUrl: 'sOmEuRl.cOm' }; + const selection = { url: 'sOmEuRl.cOm' }; const expected = { - src: selection.externalUrl, + src: selection.url, alt: settings.altText, width: settings.dimensions.width, height: settings.dimensions.height, diff --git a/src/editors/containers/TextEditor/hooks.js b/src/editors/containers/TextEditor/hooks.js index 9cd9cc585..c322f2ff8 100644 --- a/src/editors/containers/TextEditor/hooks.js +++ b/src/editors/containers/TextEditor/hooks.js @@ -62,19 +62,21 @@ export const setupCustomBehavior = ({ export const replaceStaticwithAsset = (editor, imageUrls) => { const content = editor.getContent(); - const imageSrcs = content.split('img src="'); + const imageSrcs = content.split('src="'); imageSrcs.forEach(src => { if (src.startsWith('/static/') && imageUrls.length > 0) { const imgName = src.substring(8, src.indexOf('"')); let staticFullUrl; imageUrls.forEach((url) => { - if (url.includes(imgName)) { - staticFullUrl = url; + if (imgName === url.displayName) { + staticFullUrl = url.staticFullUrl; } }); - const currentSrc = src.substring(0, src.indexOf('"')); - const updatedContent = content.replace(currentSrc, staticFullUrl); - editor.setContent(updatedContent); + if (staticFullUrl) { + const currentSrc = src.substring(0, src.indexOf('"')); + const updatedContent = content.replace(currentSrc, staticFullUrl); + editor.setContent(updatedContent); + } } }); }; @@ -182,7 +184,7 @@ export const fetchImageUrls = (images) => { const imageUrls = []; const imgsArray = Object.values(images); imgsArray.forEach(image => { - imageUrls.push(image.staticFullUrl); + imageUrls.push({ staticFullUrl: image.staticFullUrl, displayName: image.displayName }); }); return imageUrls; }; diff --git a/src/editors/containers/TextEditor/hooks.test.jsx b/src/editors/containers/TextEditor/hooks.test.jsx index 15e276b78..269ebca14 100644 --- a/src/editors/containers/TextEditor/hooks.test.jsx +++ b/src/editors/containers/TextEditor/hooks.test.jsx @@ -87,18 +87,22 @@ describe('TextEditor hooks', () => { }); describe('replaceStaticwithAsset', () => { - const editor = { getContent: jest.fn(() => ''), setContent: jest.fn() }; - const imageUrls = ['soMEImagEURl1.jpeg']; - module.replaceStaticwithAsset(editor, imageUrls); - expect(editor.getContent).toHaveBeenCalled(); - expect(editor.setContent).toHaveBeenCalled(); + test('it calls getContent and setContent', () => { + const editor = { getContent: jest.fn(() => ''), setContent: jest.fn() }; + const imageUrls = [{ staticFullUrl: '/assets/soMEImagEURl1.jpeg', displayName: 'soMEImagEURl1.jpeg' }]; + module.replaceStaticwithAsset(editor, imageUrls); + expect(editor.getContent).toHaveBeenCalled(); + expect(editor.setContent).toHaveBeenCalled(); + }); }); describe('checkRelativeUrl', () => { - const editor = { on: jest.fn() }; - const imageUrls = ['soMEImagEURl1']; - module.checkRelativeUrl(imageUrls)(editor); - expect(editor.on).toHaveBeenCalled(); + test('it calls editor.on', () => { + const editor = { on: jest.fn() }; + const imageUrls = ['soMEImagEURl1']; + module.checkRelativeUrl(imageUrls)(editor); + expect(editor.on).toHaveBeenCalled(); + }); }); describe('editorConfig', () => { diff --git a/src/editors/data/redux/app/selectors.test.js b/src/editors/data/redux/app/selectors.test.js index 1b42fd26f..e620ed9e0 100644 --- a/src/editors/data/redux/app/selectors.test.js +++ b/src/editors/data/redux/app/selectors.test.js @@ -42,6 +42,7 @@ describe('app selectors unit tests', () => { simpleKeys.learningContextId, simpleKeys.editorInitialized, simpleKeys.saveResponse, + simpleKeys.lmsEndpointUrl, simpleKeys.studioEndpointUrl, simpleKeys.unitUrl, simpleKeys.blockTitle,