Chore: Test coverage hunt (#36)
* chore: add brand mocking in gallery view * feat: dev gallery app * chore: link mock block ids to real block type api * feat: image settings page features * chore: more tests * chore: keystore util and more testing * chore: more tests * chore: re-install lint plugin... * chore: lint fixes * chore: moar tests * chore: remove brand from module.config and link gallery to edx.org brand
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { BaseModal } from './BaseModal';
|
||||
|
||||
describe('BaseModal ImageUploadModal template component', () => {
|
||||
test('snapshot', () => {
|
||||
const props = {
|
||||
isOpen: true,
|
||||
close: jest.fn().mockName('props.close'),
|
||||
title: 'props.title node',
|
||||
children: 'props.children node',
|
||||
confirmAction: 'props.confirmAction node',
|
||||
};
|
||||
expect(shallow(<BaseModal {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export const dimensionLockHooks = () => {
|
||||
|
||||
const initializeLock = ({ width, height }) => {
|
||||
// find minimum viable increment
|
||||
let gcd = findGcd(width, height);
|
||||
let gcd = module.findGcd(width, height);
|
||||
if ([width, height].some(v => !Number.isInteger(v / gcd))) {
|
||||
gcd = 1;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,11 @@ describe('ImageSettingsModal hooks', () => {
|
||||
hook.initializeLock(multiDims);
|
||||
expect(state.setState.lockDims).toHaveBeenCalledWith(reducedDims);
|
||||
});
|
||||
it('returns the values themselves if they have no gcd', () => {
|
||||
jest.spyOn(hooks, hookKeys.findGcd).mockReturnValueOnce(2);
|
||||
hook.initializeLock(simpleDims);
|
||||
expect(state.setState.lockDims).toHaveBeenCalledWith(simpleDims);
|
||||
});
|
||||
});
|
||||
test('lock sets isLocked to true', () => {
|
||||
hook = hooks.dimensionLockHooks({ dimensions: simpleDims });
|
||||
@@ -280,4 +285,12 @@ describe('ImageSettingsModal hooks', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('isSaveDisabled', () => {
|
||||
it('returns true iff is not decorative and altText value is empty', () => {
|
||||
hook = hooks.isSaveDisabled;
|
||||
expect(hook({ isDecorative: false, value: '' })).toEqual(true);
|
||||
expect(hook({ isDecorative: false, value: 'test' })).toEqual(false);
|
||||
expect(hook({ isDecorative: true, value: '' })).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,44 +1,67 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import tinyMCEKeys from '../../../data/constants/tinyMCE';
|
||||
import ImageSettingsModal from './ImageSettingsModal';
|
||||
import SelectImageModal from './SelectImageModal';
|
||||
import * as module from './ImageUploadModal';
|
||||
|
||||
export const propsString = (props) => Object.keys(props)
|
||||
.map(key => `${key}="${props[key]}"`)
|
||||
.join(' ');
|
||||
|
||||
export const imgProps = ({ settings, selection }) => ({
|
||||
src: selection.externalUrl,
|
||||
alt: settings.isDecorative ? '' : settings.altText,
|
||||
width: settings.dimensions.width,
|
||||
height: settings.dimensions.height,
|
||||
});
|
||||
|
||||
export const hooks = {
|
||||
createSaveCallback: ({
|
||||
close, editorRef, setSelection, selection,
|
||||
}) => (settings) => {
|
||||
editorRef.current.execCommand('mceInsertContent', false, module.hooks.getImgTag({ settings, selection }));
|
||||
editorRef.current.execCommand(
|
||||
tinyMCEKeys.commands.insertContent,
|
||||
false,
|
||||
module.hooks.imgTag({ settings, selection }),
|
||||
);
|
||||
setSelection(null);
|
||||
close();
|
||||
},
|
||||
getImgTag: ({ settings, selection }) => `<img src="${selection.externalUrl}" alt="${settings.isDecorative ? '' : settings.altText}" width="${settings.dimensions.width}" height="${settings.dimensions.height}">`,
|
||||
onClose: ({ clearSelection, close }) => {
|
||||
clearSelection();
|
||||
close();
|
||||
},
|
||||
imgTag: ({ settings, selection }) => {
|
||||
const props = module.imgProps({ settings, selection });
|
||||
return `<img ${propsString(props)} />`;
|
||||
},
|
||||
};
|
||||
|
||||
const ImageUploadModal = ({
|
||||
export const ImageUploadModal = ({
|
||||
// eslint-disable-next-line
|
||||
editorRef,
|
||||
isOpen,
|
||||
close,
|
||||
clearSelection,
|
||||
selection,
|
||||
setSelection,
|
||||
}) => {
|
||||
const saveToEditor = module.hooks.createSaveCallback({
|
||||
close, editorRef, setSelection, selection,
|
||||
});
|
||||
const closeAndReset = () => {
|
||||
setSelection(null);
|
||||
close();
|
||||
};
|
||||
if (selection) {
|
||||
return (
|
||||
<ImageSettingsModal
|
||||
{...{
|
||||
isOpen,
|
||||
close: closeAndReset,
|
||||
close: module.hooks.onClose({ clearSelection, close }),
|
||||
selection,
|
||||
saveToEditor,
|
||||
returnToSelection: () => setSelection(null),
|
||||
saveToEditor: module.hooks.createSaveCallback({
|
||||
close,
|
||||
editorRef,
|
||||
selection,
|
||||
setSelection,
|
||||
}),
|
||||
returnToSelection: clearSelection,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -51,17 +74,18 @@ ImageUploadModal.defaultProps = {
|
||||
selection: null,
|
||||
};
|
||||
ImageUploadModal.propTypes = {
|
||||
clearSelection: PropTypes.func.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
editorRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.any }),
|
||||
]),
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
selection: PropTypes.shape({
|
||||
url: PropTypes.string,
|
||||
externalUrl: PropTypes.string,
|
||||
altText: PropTypes.bool,
|
||||
}),
|
||||
setSelection: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
editorRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.any }),
|
||||
]),
|
||||
};
|
||||
export default ImageUploadModal;
|
||||
|
||||
@@ -1,68 +1,122 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { keyStore } from '../../../utils';
|
||||
import tinyMCEKeys from '../../../data/constants/tinyMCE';
|
||||
|
||||
import * as module from './ImageUploadModal';
|
||||
|
||||
describe('ImageUploadModal hooks', () => {
|
||||
describe('getImgTag', () => {
|
||||
const mockSelection = { externalUrl: 'sOmEuRl.cOm' };
|
||||
let output;
|
||||
test('It returns a html string which matches an image tag', () => {
|
||||
const mockSettings = {
|
||||
altText: 'aLt tExt',
|
||||
isDecorative: false,
|
||||
dimensions: {
|
||||
width: 2022,
|
||||
height: 1619,
|
||||
},
|
||||
jest.mock('./ImageSettingsModal', () => 'ImageSettingsModal');
|
||||
jest.mock('./SelectImageModal', () => 'SelectImageModal');
|
||||
|
||||
const { ImageUploadModal } = module;
|
||||
|
||||
const hookKeys = keyStore(module.hooks);
|
||||
|
||||
const settings = {
|
||||
altText: 'aLt tExt',
|
||||
isDecorative: false,
|
||||
dimensions: {
|
||||
width: 2022,
|
||||
height: 1619,
|
||||
},
|
||||
};
|
||||
|
||||
describe('ImageUploadModal', () => {
|
||||
describe('hooks', () => {
|
||||
describe('imgTag', () => {
|
||||
const selection = { externalUrl: 'sOmEuRl.cOm' };
|
||||
const expected = {
|
||||
src: selection.externalUrl,
|
||||
alt: settings.altText,
|
||||
width: settings.dimensions.width,
|
||||
height: settings.dimensions.height,
|
||||
};
|
||||
output = module.hooks.getImgTag({ selection: mockSelection, settings: mockSettings });
|
||||
expect(output).toEqual(`<img src="${mockSelection.externalUrl}" alt="${mockSettings.altText}" width="${mockSettings.dimensions.width}" height="${mockSettings.dimensions.height}">`);
|
||||
const testImgTag = (args) => {
|
||||
const output = module.hooks.imgTag({
|
||||
settings: args.settings,
|
||||
selection,
|
||||
});
|
||||
expect(output).toEqual(`<img ${module.propsString(args.expected)} />`);
|
||||
};
|
||||
test('It returns a html string which matches an image tag', () => {
|
||||
testImgTag({ settings, expected });
|
||||
});
|
||||
test('If isDecorative is true, alt text is an empty string', () => {
|
||||
testImgTag({
|
||||
settings: { ...settings, isDecorative: true },
|
||||
expected: { ...expected, alt: '' },
|
||||
});
|
||||
});
|
||||
});
|
||||
test('If Is decorative is true, alt text is an empty string', () => {
|
||||
const mockSettings = {
|
||||
isDecorative: true,
|
||||
altText: 'aLt tExt',
|
||||
dimensions: {
|
||||
width: 2022,
|
||||
height: 1619,
|
||||
},
|
||||
};
|
||||
output = module.hooks.getImgTag({ selection: mockSelection, settings: mockSettings });
|
||||
expect(output).toEqual(`<img src="${mockSelection.externalUrl}" alt="" width="${mockSettings.dimensions.width}" height="${mockSettings.dimensions.height}">`);
|
||||
describe('createSaveCallback', () => {
|
||||
const close = jest.fn();
|
||||
const execCommandMock = jest.fn();
|
||||
const editorRef = { current: { some: 'dATa', execCommand: execCommandMock } };
|
||||
const setSelection = jest.fn();
|
||||
const selection = jest.fn();
|
||||
let output;
|
||||
beforeEach(() => {
|
||||
output = module.hooks.createSaveCallback({
|
||||
close, editorRef, setSelection, selection,
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
test('It creates a callback, that when called, inserts to the editor, sets the selection to be null, and calls close', () => {
|
||||
jest.spyOn(module.hooks, hookKeys.imgTag)
|
||||
.mockImplementationOnce((props) => ({ selection, settings: props.settings }));
|
||||
expect(execCommandMock).not.toBeCalled();
|
||||
expect(setSelection).not.toBeCalled();
|
||||
expect(close).not.toBeCalled();
|
||||
output(settings);
|
||||
expect(execCommandMock).toBeCalledWith(
|
||||
tinyMCEKeys.commands.insertContent,
|
||||
false,
|
||||
{ selection, settings },
|
||||
);
|
||||
expect(setSelection).toBeCalledWith(null);
|
||||
expect(close).toBeCalled();
|
||||
});
|
||||
});
|
||||
describe('onClose', () => {
|
||||
it('takes and calls clearSelection and close callbacks', () => {
|
||||
const clearSelection = jest.fn();
|
||||
const close = jest.fn();
|
||||
module.hooks.onClose({ clearSelection, close });
|
||||
expect(clearSelection).toHaveBeenCalled();
|
||||
expect(close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSaveCallback', () => {
|
||||
const close = jest.fn();
|
||||
const execCommandMock = jest.fn();
|
||||
const editorRef = { current: { some: 'dATa', execCommand: execCommandMock } };
|
||||
const setSelection = jest.fn();
|
||||
const selection = jest.fn();
|
||||
const mockSettings = {
|
||||
altText: 'aLt tExt',
|
||||
isDecorative: false,
|
||||
dimensions: {
|
||||
width: 2022,
|
||||
height: 1619,
|
||||
},
|
||||
};
|
||||
let output;
|
||||
beforeEach(() => {
|
||||
output = module.hooks.createSaveCallback({
|
||||
close, editorRef, setSelection, selection,
|
||||
});
|
||||
describe('component', () => {
|
||||
let props;
|
||||
let hooks;
|
||||
beforeAll(() => {
|
||||
hooks = module.hooks;
|
||||
props = {
|
||||
editorRef: { current: null },
|
||||
isOpen: false,
|
||||
close: jest.fn().mockName('props.close'),
|
||||
clearSelection: jest.fn().mockName('props.clearSelection'),
|
||||
selection: { some: 'images' },
|
||||
setSelection: jest.fn().mockName('props.setSelection'),
|
||||
};
|
||||
module.hooks = {
|
||||
createSaveCallback: jest.fn().mockName('hooks.createSaveCallback'),
|
||||
onClose: jest.fn().mockName('hooks.onClose'),
|
||||
};
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
afterAll(() => {
|
||||
module.hooks = hooks;
|
||||
});
|
||||
test('It creates a callback, that when called, inserts to the editor, sets the selection to be null, and calls close', () => {
|
||||
jest.spyOn(module.hooks, 'getImgTag').mockImplementationOnce(({ settings }) => ({ selection, settings }));
|
||||
expect(execCommandMock).not.toBeCalled();
|
||||
expect(setSelection).not.toBeCalled();
|
||||
expect(close).not.toBeCalled();
|
||||
output(mockSettings);
|
||||
expect(execCommandMock).toBeCalledWith('mceInsertContent', false, { selection, settings: mockSettings });
|
||||
expect(setSelection).toBeCalledWith(null);
|
||||
expect(close).toBeCalled();
|
||||
test('snapshot: with selection content (ImageSettingsUpload)', () => {
|
||||
expect(shallow(<ImageUploadModal {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: no selection (Select Image Modal)', () => {
|
||||
expect(shallow(<ImageUploadModal {...props} selection={null} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BaseModal ImageUploadModal template component snapshot 1`] = `
|
||||
<ModalDialog
|
||||
hasCloseButton={true}
|
||||
isFullscreenOnMobile={true}
|
||||
isOpen={true}
|
||||
onClose={[MockFunction props.close]}
|
||||
size="lg"
|
||||
title="My dialog"
|
||||
variant="default"
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
props.title node
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
props.children node
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton
|
||||
onClick={[MockFunction props.close]}
|
||||
variant="tertiary"
|
||||
>
|
||||
Cancel
|
||||
</ModalDialog.CloseButton>
|
||||
props.confirmAction node
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
`;
|
||||
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ImageUploadModal component snapshot: no selection (Select Image Modal) 1`] = `
|
||||
<SelectImageModal
|
||||
close={[MockFunction props.close]}
|
||||
isOpen={false}
|
||||
setSelection={[MockFunction props.setSelection]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`ImageUploadModal component snapshot: with selection content (ImageSettingsUpload) 1`] = `
|
||||
<ImageSettingsModal
|
||||
isOpen={false}
|
||||
returnToSelection={[MockFunction props.clearSelection]}
|
||||
selection={
|
||||
Object {
|
||||
"some": "images",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
Reference in New Issue
Block a user