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:
Ben Warzeski
2022-03-24 11:15:32 -04:00
committed by GitHub
parent 284601d6d2
commit 09e9d865c2
30 changed files with 577 additions and 23773 deletions

View File

@@ -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();
});
});

View File

@@ -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;
}

View File

@@ -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);
});
});
});

View File

@@ -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;

View File

@@ -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();
});
});
});

View File

@@ -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>
`;

View File

@@ -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",
}
}
/>
`;