test: update editor-level tests (#26)

To complete https://openedx.atlassian.net/browse/TNL-9601
This commit is contained in:
connorhaugh
2022-03-07 15:03:13 -05:00
committed by GitHub
parent ac0d261e89
commit c4cd0c44ce
9 changed files with 285 additions and 44 deletions

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { Editor, mapDispatchToProps, supportedEditors } from './Editor';
import { thunkActions } from './data/redux';
import * as hooks from './hooks';
import { blockTypes } from './data/constants/app';
jest.mock('./hooks', () => ({
initializeApp: jest.fn(),
prepareEditorRef: jest.fn().mockName('prepareEditorRef'),
}));
jest.mock('./containers/TextEditor/TextEditor', () => 'TextEditor');
jest.mock('./containers/VideoEditor/VideoEditor', () => 'VideoEditor');
jest.mock('./containers/ProblemEditor/ProblemEditor', () => 'ProblemEditor');
jest.mock('./components/EditorFooter', () => 'EditorFooter');
jest.mock('./components/EditorHeader', () => 'EditorHeader');
const props = {
courseId: 'course-v1:edX+DemoX+Demo_Course',
blockId: 'block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4',
studioEndpointUrl: 'fakeurl.com',
initialize: jest.fn(),
};
describe('Editor', () => {
describe('snapshots', () => {
test('renders no editor when ref isnt ready', () => {
hooks.prepareEditorRef.mockImplementationOnce(
() => ({ editorRef: null, refReady: false, setEditorRef: jest.fn() }),
);
expect(shallow(<Editor blockType="AblOck" {...props} />)).toMatchSnapshot();
});
test.each(Object.values(blockTypes))('renders %p editor when ref is ready', (blockType) => {
hooks.prepareEditorRef.mockImplementationOnce(
() => ({ editorRef: { current: 'ref' }, refReady: true, setEditorRef: jest.fn().mockName('setEditorRef') }),
);
const wrapper = shallow(<Editor blockType={blockType} {...props} />);
if(blockType == 'html'){ // snap just one editor to make viewing easier
expect(wrapper).toMatchSnapshot();
};
expect(wrapper.children().children().at(1).is(supportedEditors[blockType])).toBe(true);
});
test('presents error message if no relevant editor found and ref ready', () => {
hooks.prepareEditorRef.mockImplementationOnce(
() => ({ editorRef: { current: 'ref' }, refReady: true, setEditorRef: jest.fn().mockName('setEditorRef') }),
);
expect(shallow(<Editor
{...props}
blockType="fAkEBlock"
/>)).toMatchSnapshot();
});
});
describe('mapDispatchToProps', () => {
test('initialize from thunkActions.app.initialize', () => {
expect(mapDispatchToProps.initialize).toEqual(thunkActions.app.initialize);
});
});
});

View File

@@ -1,39 +1,22 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { shallow } from 'enzyme';
import EditorPage from './EditorPage';
test('rendering correctly with expected Input', () => {
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const blockType = 'html';
const blockId = 'block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4';
const studioEndpointUrl = 'fakeurl.com';
render(<EditorPage
courseId={courseId}
blockType={blockType}
blockId={blockId}
studioEndpointUrl={studioEndpointUrl}
/>);
expect(screen.getByText('Text')).toBeTruthy();
expect(screen.getByText('Cancel')).toBeTruthy();
expect(screen.getAllByLabelText('Close')).toBeTruthy();
expect(screen.getByText('Add To Course')).toBeTruthy();
expect(screen.getByText('Error: Could Not Load Text Content')).toBeTruthy();
});
const props = {
courseId: 'course-v1:edX+DemoX+Demo_Course',
blockType: 'html',
blockId: 'block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4',
studioEndpointUrl: 'fakeurl.com',
};
jest.mock('react-redux', () => ({
Provider: 'Provider',
}));
jest.mock('./Editor', () => 'Editor');
test('rendering correctly with expected Error', () => {
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const blockType = 'Smelly Garbage Xblock';
const blockId = 'BadGarbadioU@Garbagefha4521ea ';
const studioEndpointUrl = 'fakeurl.com';
render(<EditorPage
courseId={courseId}
blockType={blockType}
blockId={blockId}
studioEndpointUrl={studioEndpointUrl}
/>);
expect(screen.getByText(blockType)).toBeTruthy();
expect(screen.getByText('Cancel')).toBeTruthy();
expect(screen.getAllByLabelText('Close')).toBeTruthy();
expect(screen.getByText('Add To Course')).toBeTruthy();
expect(screen.getByText('Error: Could Not find Editor')).toBeTruthy();
describe('Editor Page', () => {
describe('snapshots', () => {
test('rendering correctly with expected Input', () => {
expect(shallow(<EditorPage {...props} />)).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Editor snapshots presents error message if no relevant editor found and ref ready 1`] = `
<div
className="d-flex flex-column vh-100"
>
<div
aria-label="fAkEBlock"
className="pgn__modal-fullscreen"
role="dialog"
>
<EditorHeader
editorRef={
Object {
"current": "ref",
}
}
/>
<FormattedMessage
defaultMessage="Error: Could Not find Editor"
description="Error Message Dispayed When An unsopported Editor is desired in V2"
id="authoring.editorpage.selecteditor.error"
/>
<EditorFooter
editorRef={
Object {
"current": "ref",
}
}
/>
</div>
</div>
`;
exports[`Editor snapshots renders "html" editor when ref is ready 1`] = `
<div
className="d-flex flex-column vh-100"
>
<div
aria-label="html"
className="pgn__modal-fullscreen"
role="dialog"
>
<EditorHeader
editorRef={
Object {
"current": "ref",
}
}
/>
<TextEditor
setEditorRef={[MockFunction setEditorRef]}
/>
<EditorFooter
editorRef={
Object {
"current": "ref",
}
}
/>
</div>
</div>
`;
exports[`Editor snapshots renders no editor when ref isnt ready 1`] = `
<div
className="d-flex flex-column vh-100"
>
<div
aria-label="AblOck"
className="pgn__modal-fullscreen"
role="dialog"
/>
</div>
`;

View File

@@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Editor Page snapshots rendering correctly with expected Input 1`] = `
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
}
>
<Editor
blockId="block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4"
blockType="html"
courseId="course-v1:edX+DemoX+Demo_Course"
studioEndpointUrl="fakeurl.com"
/>
</Provider>
`;

View File

@@ -10,7 +10,7 @@ import {
Toast,
} from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { nullMethod, saveTextBlock, navigateCallback } from '../../hooks';
import { nullMethod, saveBlock, navigateCallback } from '../../hooks';
import { RequestKeys } from '../../data/constants/requests';
import { selectors, thunkActions } from '../../data/redux';
@@ -18,7 +18,7 @@ import { selectors, thunkActions } from '../../data/redux';
import messages from '../messages';
import * as module from '.';
export const handleSaveClicked = (props) => () => saveTextBlock(props);
export const handleSaveClicked = (props) => () => saveBlock(props);
export const handleCancelClicked = ({ returnUrl }) => navigateCallback(returnUrl);
export const EditorFooter = ({

View File

@@ -3,7 +3,7 @@ import { shallow } from 'enzyme';
import * as module from './index';
import { selectors, thunkActions } from '../../data/redux';
import { RequestKeys } from '../../data/constants/requests';
import { saveTextBlock, navigateCallback } from '../../hooks';
import { saveBlock, navigateCallback } from '../../hooks';
jest.mock('../../data/redux', () => ({
thunkActions: {
@@ -30,7 +30,7 @@ jest.mock('.', () => ({
}));
jest.mock('../../hooks', () => ({
saveTextBlock: jest.fn(),
saveBlock: jest.fn(),
navigateCallback: jest.fn(),
nullMethod: jest.fn().mockName('nullMethod'),
}));
@@ -45,10 +45,10 @@ describe('EditorFooter', () => {
};
describe('behavior', () => {
const realmodule = jest.requireActual('./index');
test('handleSaveClicked calls saveTextBlock', () => {
test('handleSaveClicked calls saveBlock', () => {
const createdCallback = realmodule.handleSaveClicked(props);
createdCallback();
expect(saveTextBlock).toHaveBeenCalled();
expect(saveBlock).toHaveBeenCalled();
});
test('handleCancelClicked calls navigateCallback', () => {
realmodule.handleCancelClicked({ returnUrl: props.returnUrl });

View File

@@ -1,6 +1,7 @@
import {
useRef, useEffect, useCallback, useState,
} from 'react';
import * as module from './hooks';
export const initializeApp = ({ initialize, data }) => useEffect(() => initialize(data), []);
@@ -18,14 +19,14 @@ export const navigateTo = (destination) => {
window.location.assign(destination);
};
export const navigateCallback = (destination) => () => navigateTo(destination);
export const navigateCallback = (destination) => () => module.navigateTo(destination);
export const saveTextBlock = ({
export const saveBlock = ({
editorRef,
returnUrl,
saveBlock,
saveFunction,
}) => {
saveBlock({
saveFunction({
returnToUnit: module.navigateCallback(returnUrl),
content: editorRef.current.getContent(),
});

101
src/editors/hooks.test.jsx Normal file
View File

@@ -0,0 +1,101 @@
import { useEffect, updateState } from 'react';
import * as module from './hooks';
jest.mock('react', () => {
const updateStateMock = jest.fn();
return {
updateState: updateStateMock,
useState: jest.fn(val => ([{ state: val }, (newVal) => updateStateMock({ val, newVal })])),
useRef: jest.fn(val => ({ current: val })),
useEffect: jest.fn(),
useCallback: (cb, prereqs) => ({ cb, prereqs }),
};
});
describe('hooks', () => {
const locationTemp = window.location;
beforeAll(() => {
delete window.location;
window.location = {
assign: jest.fn(),
};
});
afterAll(() => {
window.location = locationTemp;
});
describe('initializeApp', () => {
test('calls provided function with provided data as args when useEffect is called', () => {
const mockIntialize = jest.fn(val => (val));
const fakedata = { some: 'data' };
module.initializeApp({ initialize: mockIntialize, data: fakedata });
expect(mockIntialize).not.toHaveBeenCalledWith(fakedata);
const [cb, prereqs] = useEffect.mock.calls[0];
expect(prereqs).toStrictEqual([]);
cb();
expect(mockIntialize).toHaveBeenCalledWith(fakedata);
});
});
describe('prepareEditorRef', () => {
let output;
beforeEach(() => {
output = module.prepareEditorRef();
});
afterEach(() => {
jest.clearAllMocks();
});
test('sets refReady to false by default, ref is null', () => {
expect(output.refReady.state).toBe(false);
expect(output.editorRef.current).toBe(null);
});
test('when useEffect triggers, refReady is set to true', () => {
expect(updateState).not.toHaveBeenCalled();
const [cb, prereqs] = useEffect.mock.calls[0];
expect(prereqs).toStrictEqual([]);
cb();
expect(updateState).toHaveBeenCalledWith({ newVal: true, val: false });
});
test('calling setEditorRef sets the ref value', () => {
const fakeEditor = { editor: 'faKe Editor' };
expect(output.editorRef.current).not.toBe(fakeEditor);
output.setEditorRef.cb(fakeEditor);
expect(output.editorRef.current).toBe(fakeEditor);
});
});
describe('navigateTo', () => {
const destination = 'HoME';
beforeEach(() => {
module.navigateTo(destination);
});
test('it calls window assign', () => {
expect(window.location.assign).toHaveBeenCalled();
});
});
describe('navigateCallback', () => {
let output;
const destination = 'hOmE';
beforeEach(() => {
output = module.navigateCallback(destination);
});
test('it calls navigateTo with output destination', () => {
const spy = jest.spyOn(module, 'navigateTo');
output();
expect(spy).toHaveBeenCalledWith(destination);
});
});
describe('saveBlock', () => {
test('saveBlock calls the save function provided with created nav callback and content', () => {
const mockNavCallback = (returnUrl) => ({ navigateCallback: returnUrl });
jest.spyOn(module, 'navigateCallback').mockImplementationOnce(mockNavCallback);
const content = { some: 'content' };
const args = {
editorRef: { current: { getContent: () => content } },
returnUrl: 'rEtUrNUrl',
saveFunction: jest.fn(),
};
module.saveBlock(args);
expect(args.saveFunction).toHaveBeenCalledWith({
returnToUnit: mockNavCallback(args.returnUrl),
content,
});
});
});
});