feat: change static url to asset url in editor
This commit is contained in:
@@ -29,6 +29,13 @@ exports[`TextEditor snapshots block failed to load, Toast is shown 1`] = `
|
||||
},
|
||||
}
|
||||
}
|
||||
images={
|
||||
Object {
|
||||
"sOmEuiMAge": Object {
|
||||
"staTICUrl": "/assets/sOmEuiMAge",
|
||||
},
|
||||
}
|
||||
}
|
||||
isOpen={false}
|
||||
selection="hooks.selectedImage.selection"
|
||||
setSelection={[MockFunction hooks.selectedImage.setSelection]}
|
||||
@@ -63,6 +70,11 @@ exports[`TextEditor snapshots block failed to load, Toast is shown 1`] = `
|
||||
},
|
||||
},
|
||||
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
|
||||
"images": Object {
|
||||
"sOmEuiMAge": Object {
|
||||
"staTICUrl": "/assets/sOmEuiMAge",
|
||||
},
|
||||
},
|
||||
"initializeEditor": [MockFunction args.intializeEditor],
|
||||
"lmsEndpointUrl": "sOmEvaLue.cOm",
|
||||
"openImgModal": [MockFunction modal.openModal],
|
||||
@@ -106,6 +118,13 @@ exports[`TextEditor snapshots loaded, raw editor 1`] = `
|
||||
},
|
||||
}
|
||||
}
|
||||
images={
|
||||
Object {
|
||||
"sOmEuiMAge": Object {
|
||||
"staTICUrl": "/assets/sOmEuiMAge",
|
||||
},
|
||||
}
|
||||
}
|
||||
isOpen={false}
|
||||
selection="hooks.selectedImage.selection"
|
||||
setSelection={[MockFunction hooks.selectedImage.setSelection]}
|
||||
@@ -173,6 +192,13 @@ exports[`TextEditor snapshots not yet loaded, Spinner appears 1`] = `
|
||||
},
|
||||
}
|
||||
}
|
||||
images={
|
||||
Object {
|
||||
"sOmEuiMAge": Object {
|
||||
"staTICUrl": "/assets/sOmEuiMAge",
|
||||
},
|
||||
}
|
||||
}
|
||||
isOpen={false}
|
||||
selection="hooks.selectedImage.selection"
|
||||
setSelection={[MockFunction hooks.selectedImage.setSelection]}
|
||||
@@ -240,6 +266,13 @@ exports[`TextEditor snapshots renders as expected with default behavior 1`] = `
|
||||
},
|
||||
}
|
||||
}
|
||||
images={
|
||||
Object {
|
||||
"sOmEuiMAge": Object {
|
||||
"staTICUrl": "/assets/sOmEuiMAge",
|
||||
},
|
||||
}
|
||||
}
|
||||
isOpen={false}
|
||||
selection="hooks.selectedImage.selection"
|
||||
setSelection={[MockFunction hooks.selectedImage.setSelection]}
|
||||
@@ -274,6 +307,11 @@ exports[`TextEditor snapshots renders as expected with default behavior 1`] = `
|
||||
},
|
||||
},
|
||||
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
|
||||
"images": Object {
|
||||
"sOmEuiMAge": Object {
|
||||
"staTICUrl": "/assets/sOmEuiMAge",
|
||||
},
|
||||
},
|
||||
"initializeEditor": [MockFunction args.intializeEditor],
|
||||
"lmsEndpointUrl": "sOmEvaLue.cOm",
|
||||
"openImgModal": [MockFunction modal.openModal],
|
||||
|
||||
@@ -52,6 +52,7 @@ export const ImageUploadModal = ({
|
||||
clearSelection,
|
||||
selection,
|
||||
setSelection,
|
||||
images,
|
||||
}) => {
|
||||
if (selection) {
|
||||
return (
|
||||
@@ -78,6 +79,7 @@ export const ImageUploadModal = ({
|
||||
close,
|
||||
setSelection,
|
||||
clearSelection,
|
||||
images,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -101,5 +103,6 @@ ImageUploadModal.propTypes = {
|
||||
altText: PropTypes.bool,
|
||||
}),
|
||||
setSelection: PropTypes.func.isRequired,
|
||||
images: PropTypes.shape({}).isRequired,
|
||||
};
|
||||
export default ImageUploadModal;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { sortFunctions, sortKeys } from './utils';
|
||||
|
||||
export const state = {
|
||||
highlighted: (val) => React.useState(val),
|
||||
images: (val) => React.useState(val),
|
||||
showSelectImageError: (val) => React.useState(val),
|
||||
searchString: (val) => React.useState(val),
|
||||
sortBy: (val) => React.useState(val),
|
||||
@@ -36,9 +35,7 @@ export const displayList = ({ sortBy, searchString, images }) => (
|
||||
imageList: Object.values(images),
|
||||
}).sort(sortFunctions[sortBy in sortKeys ? sortKeys[sortBy] : sortKeys.dateNewest]));
|
||||
|
||||
export const imgListHooks = ({ searchSortProps, setSelection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [images, setImages] = module.state.images({});
|
||||
export const imgListHooks = ({ searchSortProps, setSelection, images }) => {
|
||||
const [highlighted, setHighlighted] = module.state.highlighted(null);
|
||||
const [
|
||||
showSelectImageError,
|
||||
@@ -47,10 +44,6 @@ export const imgListHooks = ({ searchSortProps, setSelection }) => {
|
||||
const [showSizeError, setShowSizeError] = module.state.showSelectImageError(false);
|
||||
const list = module.displayList({ ...searchSortProps, images });
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(thunkActions.app.fetchImages({ setImages }));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
galleryError: {
|
||||
show: showSelectImageError,
|
||||
@@ -126,9 +119,9 @@ export const fileInputHooks = ({ setSelection, clearSelection, imgList }) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const imgHooks = ({ setSelection, clearSelection }) => {
|
||||
export const imgHooks = ({ setSelection, clearSelection, images }) => {
|
||||
const searchSortProps = module.searchAndSortHooks();
|
||||
const imgList = module.imgListHooks({ setSelection, searchSortProps });
|
||||
const imgList = module.imgListHooks({ setSelection, searchSortProps, images });
|
||||
const fileInput = module.fileInputHooks({
|
||||
setSelection,
|
||||
clearSelection,
|
||||
|
||||
@@ -27,25 +27,6 @@ jest.mock('react-redux', () => {
|
||||
jest.mock('../../../../data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
fetchImages: jest.fn(),
|
||||
uploadImage: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const dispatchFn = jest.fn();
|
||||
return {
|
||||
...jest.requireActual('react-redux'),
|
||||
dispatch: dispatchFn,
|
||||
useDispatch: jest.fn(() => dispatchFn),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
fetchImages: jest.fn(),
|
||||
uploadImage: jest.fn(),
|
||||
},
|
||||
},
|
||||
@@ -63,7 +44,6 @@ describe('SelectImageModal hooks', () => {
|
||||
});
|
||||
describe('state hooks', () => {
|
||||
state.testGetter(state.keys.highlighted);
|
||||
state.testGetter(state.keys.images);
|
||||
state.testGetter(state.keys.showSelectImageError);
|
||||
state.testGetter(state.keys.searchString);
|
||||
state.testGetter(state.keys.sortBy);
|
||||
@@ -156,6 +136,12 @@ describe('SelectImageModal hooks', () => {
|
||||
const props = {
|
||||
setSelection: jest.fn(),
|
||||
searchSortProps: { searchString: 'Es', sortBy: sortKeys.dateNewest },
|
||||
images: {
|
||||
sOmEuiMAgeURl: {
|
||||
displayName: 'sOmEuiMAge',
|
||||
staTICUrl: '/assets/sOmEuiMAge',
|
||||
},
|
||||
},
|
||||
};
|
||||
const displayList = (args) => ({ displayList: args });
|
||||
const load = () => {
|
||||
@@ -165,31 +151,17 @@ describe('SelectImageModal hooks', () => {
|
||||
beforeEach(() => {
|
||||
load();
|
||||
});
|
||||
it('returns images value, initialized to an empty object', () => {
|
||||
expect(state.stateVals.images).toEqual(hook.images);
|
||||
expect(state.stateVals.images).toEqual({});
|
||||
});
|
||||
it('dispatches fetchImages thunkAction once, with setImages as onSuccess param', () => {
|
||||
expect(React.useEffect.mock.calls.length).toEqual(1);
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs).toEqual([]);
|
||||
cb();
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
thunkActions.app.fetchImages({ setImages: state.setState.images }),
|
||||
);
|
||||
});
|
||||
describe('selectBtnProps', () => {
|
||||
test('on click, if sets selection to the image with the same id', () => {
|
||||
const highlighted = 'id1';
|
||||
state.mockVal(state.keys.images, { [highlighted]: testValue });
|
||||
const highlighted = 'sOmEuiMAgeURl';
|
||||
const highlightedValue = { displayName: 'sOmEuiMAge', staTICUrl: '/assets/sOmEuiMAge' };
|
||||
state.mockVal(state.keys.highlighted, highlighted);
|
||||
load();
|
||||
expect(props.setSelection).not.toHaveBeenCalled();
|
||||
hook.selectBtnProps.onClick();
|
||||
expect(props.setSelection).toHaveBeenCalledWith(testValue);
|
||||
expect(props.setSelection).toHaveBeenCalledWith(highlightedValue);
|
||||
});
|
||||
test('on click, sets showSelectImageError to true if nothing is highlighted', () => {
|
||||
state.mockVal(state.keys.images, { });
|
||||
state.mockVal(state.keys.highlighted, null);
|
||||
load();
|
||||
hook.selectBtnProps.onClick();
|
||||
@@ -209,7 +181,7 @@ describe('SelectImageModal hooks', () => {
|
||||
test('displayList returns displayListhook called with searchSortProps and images', () => {
|
||||
expect(hook.galleryProps.displayList).toEqual(displayList({
|
||||
...props.searchSortProps,
|
||||
images: hook.images,
|
||||
images: props.images,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -307,6 +279,7 @@ describe('SelectImageModal hooks', () => {
|
||||
};
|
||||
const searchAndSortHooks = { search: 'props' };
|
||||
const fileInputHooks = { file: 'input hooks' };
|
||||
const images = { sOmEuiMAge: { staTICUrl: '/assets/sOmEuiMAge' } };
|
||||
|
||||
const setSelection = jest.fn();
|
||||
const clearSelection = jest.fn();
|
||||
@@ -318,7 +291,7 @@ describe('SelectImageModal hooks', () => {
|
||||
.mockReturnValueOnce(searchAndSortHooks);
|
||||
spies.file = jest.spyOn(hooks, hookKeys.fileInputHooks)
|
||||
.mockReturnValueOnce(fileInputHooks);
|
||||
hook = hooks.imgHooks({ setSelection, clearSelection });
|
||||
hook = hooks.imgHooks({ setSelection, clearSelection, images });
|
||||
});
|
||||
it('forwards fileInputHooks as fileInput, called with uploadImage prop', () => {
|
||||
expect(hook.fileInput).toEqual(fileInputHooks);
|
||||
@@ -327,11 +300,12 @@ describe('SelectImageModal hooks', () => {
|
||||
setSelection, clearSelection, imgList: imgListHooks,
|
||||
});
|
||||
});
|
||||
it('initializes imgListHooks with setSelection and searchAndSortHooks', () => {
|
||||
it('initializes imgListHooks with setSelection,searchAndSortHooks, and images', () => {
|
||||
expect(spies.imgList.mock.calls.length).toEqual(1);
|
||||
expect(spies.imgList).toHaveBeenCalledWith({
|
||||
setSelection,
|
||||
searchSortProps: searchAndSortHooks,
|
||||
images,
|
||||
});
|
||||
});
|
||||
it('forwards searchAndSortHooks as searchSortProps', () => {
|
||||
|
||||
@@ -27,6 +27,7 @@ export const SelectImageModal = ({
|
||||
close,
|
||||
setSelection,
|
||||
clearSelection,
|
||||
images,
|
||||
// injected
|
||||
intl,
|
||||
// redux
|
||||
@@ -39,7 +40,7 @@ export const SelectImageModal = ({
|
||||
galleryProps,
|
||||
searchSortProps,
|
||||
selectBtnProps,
|
||||
} = hooks.imgHooks({ setSelection, clearSelection });
|
||||
} = hooks.imgHooks({ setSelection, clearSelection, images });
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
@@ -96,6 +97,7 @@ SelectImageModal.propTypes = {
|
||||
close: PropTypes.func.isRequired,
|
||||
setSelection: PropTypes.func.isRequired,
|
||||
clearSelection: PropTypes.func.isRequired,
|
||||
images: PropTypes.shape({}).isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
// redux
|
||||
|
||||
@@ -60,6 +60,33 @@ export const setupCustomBehavior = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const replaceStaticwithAsset = (editor, imageUrls) => {
|
||||
const content = editor.getContent();
|
||||
const imageSrcs = content.split('img 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;
|
||||
}
|
||||
});
|
||||
const currentSrc = src.substring(0, src.indexOf('"'));
|
||||
const updatedContent = content.replace(currentSrc, staticFullUrl);
|
||||
editor.setContent(updatedContent);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const checkRelativeUrl = (imageUrls) => (editor) => {
|
||||
editor.on('ExecCommand', (e) => {
|
||||
if (e.command === 'mceFocus') {
|
||||
module.replaceStaticwithAsset(editor, imageUrls);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// imagetools_cors_hosts needs a protocol-sanatized url
|
||||
export const removeProtocolFromUrl = (url) => url.replace(/^https?:\/\//, '');
|
||||
|
||||
@@ -72,6 +99,7 @@ export const editorConfig = ({
|
||||
setEditorRef,
|
||||
setSelection,
|
||||
studioEndpointUrl,
|
||||
images,
|
||||
}) => ({
|
||||
onInit: (evt, editor) => {
|
||||
setEditorRef(editor);
|
||||
@@ -85,6 +113,7 @@ export const editorConfig = ({
|
||||
content_style: tinyMCEStyles,
|
||||
contextmenu: 'link table',
|
||||
document_base_url: lmsEndpointUrl,
|
||||
init_instance_callback: module.checkRelativeUrl(module.fetchImageUrls(images)),
|
||||
imagetools_cors_hosts: [removeProtocolFromUrl(lmsEndpointUrl), removeProtocolFromUrl(studioEndpointUrl)],
|
||||
imagetools_toolbar: pluginConfig.imageToolbar,
|
||||
plugins: pluginConfig.plugins,
|
||||
@@ -108,12 +137,15 @@ export const imgModalToggle = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const sourceCodeModalToggle = () => {
|
||||
export const sourceCodeModalToggle = (editorRef) => {
|
||||
const [isSourceCodeOpen, setIsOpen] = module.state.isSourceCodeModalOpen(false);
|
||||
return {
|
||||
isSourceCodeOpen,
|
||||
openSourceCodeModal: () => setIsOpen(true),
|
||||
closeSourceCodeModal: () => setIsOpen(false),
|
||||
closeSourceCodeModal: () => {
|
||||
setIsOpen(false);
|
||||
editorRef.current.focus();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -145,6 +177,15 @@ export const getContent = ({ editorRef, isRaw }) => () => {
|
||||
return editorRef.current?.getContent();
|
||||
};
|
||||
|
||||
export const fetchImageUrls = (images) => {
|
||||
const imageUrls = [];
|
||||
const imgsArray = Object.values(images);
|
||||
imgsArray.forEach(image => {
|
||||
imageUrls.push(image.staticFullUrl);
|
||||
});
|
||||
return imageUrls;
|
||||
};
|
||||
|
||||
export const selectedImage = (val) => {
|
||||
const [selection, setSelection] = module.state.imageSelection(val);
|
||||
return {
|
||||
|
||||
@@ -86,11 +86,27 @@ describe('TextEditor hooks', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceStaticwithAsset', () => {
|
||||
const editor = { getContent: jest.fn(() => '<img src="/static/soMEImagEURl1.jpeg"/>'), setContent: jest.fn() };
|
||||
const imageUrls = ['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();
|
||||
});
|
||||
|
||||
describe('editorConfig', () => {
|
||||
const props = {
|
||||
blockValue: null,
|
||||
lmsEndpointUrl: 'sOmEuRl.cOm',
|
||||
studioEndpointUrl: 'sOmEoThEruRl.cOm',
|
||||
images: { sOmEuiMAge: { staTICUrl: '/assets/sOmEuiMAge' } },
|
||||
};
|
||||
const evt = 'fakeEvent';
|
||||
const editor = 'myEditor';
|
||||
@@ -159,9 +175,10 @@ describe('TextEditor hooks', () => {
|
||||
});
|
||||
|
||||
describe('sourceCodeModalToggle', () => {
|
||||
const editorRef = { current: { focus: jest.fn() } };
|
||||
const hookKey = state.keys.isSourceCodeModalOpen;
|
||||
beforeEach(() => {
|
||||
hook = module.sourceCodeModalToggle();
|
||||
hook = module.sourceCodeModalToggle(editorRef);
|
||||
});
|
||||
test('isOpen: state value', () => {
|
||||
expect(hook.isSourceCodeOpen).toEqual(state.stateVals[hookKey]);
|
||||
|
||||
@@ -44,14 +44,15 @@ export const TextEditor = ({
|
||||
lmsEndpointUrl,
|
||||
studioEndpointUrl,
|
||||
blockFailed,
|
||||
blockFinished,
|
||||
initializeEditor,
|
||||
images,
|
||||
imagesFinished,
|
||||
// inject
|
||||
intl,
|
||||
}) => {
|
||||
const { editorRef, refReady, setEditorRef } = hooks.prepareEditorRef();
|
||||
const { isImgOpen, openImgModal, closeImgModal } = hooks.imgModalToggle();
|
||||
const { isSourceCodeOpen, openSourceCodeModal, closeSourceCodeModal } = hooks.sourceCodeModalToggle();
|
||||
const { isSourceCodeOpen, openSourceCodeModal, closeSourceCodeModal } = hooks.sourceCodeModalToggle(editorRef);
|
||||
const imageSelection = hooks.selectedImage(null);
|
||||
|
||||
if (!refReady) { return null; }
|
||||
@@ -75,6 +76,7 @@ export const TextEditor = ({
|
||||
initializeEditor,
|
||||
lmsEndpointUrl,
|
||||
studioEndpointUrl,
|
||||
images,
|
||||
setSelection: imageSelection.setSelection,
|
||||
clearSelection: imageSelection.clearSelection,
|
||||
})}
|
||||
@@ -92,6 +94,7 @@ export const TextEditor = ({
|
||||
isOpen={isImgOpen}
|
||||
close={closeImgModal}
|
||||
editorRef={editorRef}
|
||||
images={images}
|
||||
{...imageSelection}
|
||||
/>
|
||||
<SourceCodeModal
|
||||
@@ -104,7 +107,7 @@ export const TextEditor = ({
|
||||
<FormattedMessage {...messages.couldNotLoadTextContext} />
|
||||
</Toast>
|
||||
|
||||
{(!blockFinished)
|
||||
{(!imagesFinished)
|
||||
? (
|
||||
<div className="text-center p-6">
|
||||
<Spinner
|
||||
@@ -124,6 +127,8 @@ TextEditor.defaultProps = {
|
||||
isRaw: null,
|
||||
lmsEndpointUrl: null,
|
||||
studioEndpointUrl: null,
|
||||
images: null,
|
||||
imagesFinished: null,
|
||||
};
|
||||
TextEditor.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
@@ -134,9 +139,10 @@ TextEditor.propTypes = {
|
||||
lmsEndpointUrl: PropTypes.string,
|
||||
studioEndpointUrl: PropTypes.string,
|
||||
blockFailed: PropTypes.bool.isRequired,
|
||||
blockFinished: PropTypes.bool.isRequired,
|
||||
initializeEditor: PropTypes.func.isRequired,
|
||||
isRaw: PropTypes.bool,
|
||||
imagesFinished: PropTypes.bool,
|
||||
images: PropTypes.shape({}),
|
||||
// inject
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
@@ -146,8 +152,9 @@ export const mapStateToProps = (state) => ({
|
||||
lmsEndpointUrl: selectors.app.lmsEndpointUrl(state),
|
||||
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
|
||||
blockFailed: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchBlock }),
|
||||
blockFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchBlock }),
|
||||
isRaw: selectors.app.isRaw(state),
|
||||
imagesFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchImages }),
|
||||
images: selectors.app.images(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -70,6 +70,7 @@ jest.mock('../../data/redux', () => ({
|
||||
lmsEndpointUrl: jest.fn(state => ({ lmsEndpointUrl: state })),
|
||||
studioEndpointUrl: jest.fn(state => ({ studioEndpointUrl: state })),
|
||||
isRaw: jest.fn(state => ({ isRaw: state })),
|
||||
images: jest.fn(state => ({ images: state })),
|
||||
},
|
||||
requests: {
|
||||
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),
|
||||
@@ -86,9 +87,10 @@ describe('TextEditor', () => {
|
||||
lmsEndpointUrl: 'sOmEvaLue.cOm',
|
||||
studioEndpointUrl: 'sOmEoThERvaLue.cOm',
|
||||
blockFailed: false,
|
||||
blockFinished: true,
|
||||
initializeEditor: jest.fn().mockName('args.intializeEditor'),
|
||||
isRaw: false,
|
||||
imagesFinished: true,
|
||||
images: { sOmEuiMAge: { staTICUrl: '/assets/sOmEuiMAge' } },
|
||||
// inject
|
||||
intl: { formatMessage },
|
||||
};
|
||||
@@ -107,7 +109,7 @@ describe('TextEditor', () => {
|
||||
expect(shallow(<TextEditor {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
test('not yet loaded, Spinner appears', () => {
|
||||
expect(shallow(<TextEditor {...props} blockFinished={false} />)).toMatchSnapshot();
|
||||
expect(shallow(<TextEditor {...props} imagesFinished={false} />)).toMatchSnapshot();
|
||||
});
|
||||
test('loaded, raw editor', () => {
|
||||
expect(shallow(<TextEditor {...props} isRaw />)).toMatchSnapshot();
|
||||
@@ -128,15 +130,20 @@ describe('TextEditor', () => {
|
||||
mapStateToProps(testState).lmsEndpointUrl,
|
||||
).toEqual(selectors.app.lmsEndpointUrl(testState));
|
||||
});
|
||||
test('images from app.images', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).images,
|
||||
).toEqual(selectors.app.images(testState));
|
||||
});
|
||||
test('blockFailed from requests.isFailed', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).blockFailed,
|
||||
).toEqual(selectors.requests.isFailed(testState, { requestKey: RequestKeys.fetchBlock }));
|
||||
});
|
||||
test('blockFinished from requests.isFinished', () => {
|
||||
test('imagesFinished from requests.isFinished', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).blockFinished,
|
||||
).toEqual(selectors.requests.isFinished(testState, { requestKey: RequestKeys.fetchBlock }));
|
||||
mapStateToProps(testState).imagesFinished,
|
||||
).toEqual(selectors.requests.isFinished(testState, { requestKey: RequestKeys.fetchImages }));
|
||||
});
|
||||
});
|
||||
describe('mapDispatchToProps', () => {
|
||||
|
||||
@@ -49,6 +49,7 @@ export default StrictDict({
|
||||
menubar: false,
|
||||
min_height: 500,
|
||||
toolbar_sticky: true,
|
||||
relative_urls: false,
|
||||
relative_urls: true,
|
||||
convert_urls: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ const initialState = {
|
||||
blockContent: null,
|
||||
studioView: null,
|
||||
saveResponse: null,
|
||||
|
||||
blockId: null,
|
||||
blockTitle: null,
|
||||
blockType: null,
|
||||
@@ -16,6 +15,7 @@ const initialState = {
|
||||
editorInitialized: false,
|
||||
studioEndpointUrl: null,
|
||||
lmsEndpointUrl: null,
|
||||
images: {},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@@ -45,6 +45,7 @@ const app = createSlice({
|
||||
setBlockTitle: (state, { payload }) => ({ ...state, blockTitle: payload }),
|
||||
setSaveResponse: (state, { payload }) => ({ ...state, saveResponse: payload }),
|
||||
initializeEditor: (state) => ({ ...state, editorInitialized: true }),
|
||||
setImages: (state, { payload }) => ({ ...state, images: payload }),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ describe('app reducer', () => {
|
||||
['setBlockContent', 'blockContent'],
|
||||
['setBlockTitle', 'blockTitle'],
|
||||
['setSaveResponse', 'saveResponse'],
|
||||
['setImages', 'images'],
|
||||
].map(args => setterTest(...args));
|
||||
describe('setBlockValue', () => {
|
||||
it('sets blockValue, as well as setting the blockTitle from data.display_name', () => {
|
||||
|
||||
@@ -22,6 +22,7 @@ export const simpleSelectors = {
|
||||
studioEndpointUrl: mkSimpleSelector(app => app.studioEndpointUrl),
|
||||
unitUrl: mkSimpleSelector(app => app.unitUrl),
|
||||
blockTitle: mkSimpleSelector(app => app.blockTitle),
|
||||
images: mkSimpleSelector(app => app.images),
|
||||
};
|
||||
|
||||
export const returnUrl = createSelector(
|
||||
|
||||
@@ -46,6 +46,7 @@ describe('app selectors unit tests', () => {
|
||||
simpleKeys.unitUrl,
|
||||
simpleKeys.blockTitle,
|
||||
simpleKeys.studioView,
|
||||
simpleKeys.images,
|
||||
].map(testSimpleSelector);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,12 @@ export const fetchUnit = () => (dispatch) => {
|
||||
}));
|
||||
};
|
||||
|
||||
export const fetchImages = () => (dispatch) => {
|
||||
dispatch(requests.fetchImages({
|
||||
onSuccess: (response) => dispatch(actions.app.setImages(response)),
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} studioEndpointUrl
|
||||
* @param {string} blockId
|
||||
@@ -35,6 +41,7 @@ export const initialize = (data) => (dispatch) => {
|
||||
dispatch(module.fetchBlock());
|
||||
dispatch(module.fetchUnit());
|
||||
dispatch(module.fetchStudioView());
|
||||
dispatch(module.fetchImages());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -51,10 +58,6 @@ export const saveBlock = ({ content, returnToUnit }) => (dispatch) => {
|
||||
}));
|
||||
};
|
||||
|
||||
export const fetchImages = ({ setImages }) => (dispatch) => {
|
||||
dispatch(requests.fetchImages({ onSuccess: setImages }));
|
||||
};
|
||||
|
||||
export const uploadImage = ({ file, setSelection }) => (dispatch) => {
|
||||
dispatch(requests.uploadImage({
|
||||
image: file,
|
||||
|
||||
@@ -80,20 +80,28 @@ describe('app thunkActions', () => {
|
||||
});
|
||||
describe('initialize', () => {
|
||||
it('dispatches actions.app.initialize, and then fetches both block and unit', () => {
|
||||
const { fetchBlock, fetchUnit, fetchStudioView } = thunkActions;
|
||||
const {
|
||||
fetchBlock,
|
||||
fetchUnit,
|
||||
fetchStudioView,
|
||||
fetchImages,
|
||||
} = thunkActions;
|
||||
thunkActions.fetchBlock = () => 'fetchBlock';
|
||||
thunkActions.fetchUnit = () => 'fetchUnit';
|
||||
thunkActions.fetchStudioView = () => 'fetchStudioView';
|
||||
thunkActions.fetchImages = () => 'fetchImages';
|
||||
thunkActions.initialize(testValue)(dispatch);
|
||||
expect(dispatch.mock.calls).toEqual([
|
||||
[actions.app.initialize(testValue)],
|
||||
[thunkActions.fetchBlock()],
|
||||
[thunkActions.fetchUnit()],
|
||||
[thunkActions.fetchStudioView()],
|
||||
[thunkActions.fetchImages()],
|
||||
]);
|
||||
thunkActions.fetchBlock = fetchBlock;
|
||||
thunkActions.fetchUnit = fetchUnit;
|
||||
thunkActions.fetchStudioView = fetchStudioView;
|
||||
thunkActions.fetchImages = fetchImages;
|
||||
});
|
||||
});
|
||||
describe('saveBlock', () => {
|
||||
@@ -122,10 +130,11 @@ describe('app thunkActions', () => {
|
||||
});
|
||||
describe('fetchImages', () => {
|
||||
it('dispatches fetchUnit action with setImages for onSuccess param', () => {
|
||||
const setImages = jest.fn();
|
||||
thunkActions.fetchImages({ setImages })(dispatch);
|
||||
[[dispatchedAction]] = dispatch.mock.calls;
|
||||
expect(dispatchedAction.fetchImages).toEqual({ onSuccess: setImages });
|
||||
const response = 'testRESPONSE';
|
||||
thunkActions.fetchImages()(dispatch);
|
||||
const [[dispatchCall]] = dispatch.mock.calls;
|
||||
dispatchCall.fetchImages.onSuccess(response);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.app.setImages(response));
|
||||
});
|
||||
});
|
||||
describe('uploadImage', () => {
|
||||
|
||||
Reference in New Issue
Block a user