diff --git a/src/editors/Editor.test.jsx b/src/editors/Editor.test.jsx new file mode 100644 index 000000000..8fe08893e --- /dev/null +++ b/src/editors/Editor.test.jsx @@ -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()).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(); + 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()).toMatchSnapshot(); + }); + }); + describe('mapDispatchToProps', () => { + test('initialize from thunkActions.app.initialize', () => { + expect(mapDispatchToProps.initialize).toEqual(thunkActions.app.initialize); + }); + }); +}); diff --git a/src/editors/EditorPage.test.jsx b/src/editors/EditorPage.test.jsx index 155ebbcc5..2cf56dcf5 100644 --- a/src/editors/EditorPage.test.jsx +++ b/src/editors/EditorPage.test.jsx @@ -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(); - 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(); - 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()).toMatchSnapshot(); + }); + }); }); diff --git a/src/editors/EditorPageTest.jsx b/src/editors/EditorPageTest.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/editors/__snapshots__/Editor.test.jsx.snap b/src/editors/__snapshots__/Editor.test.jsx.snap new file mode 100644 index 000000000..2cb55231d --- /dev/null +++ b/src/editors/__snapshots__/Editor.test.jsx.snap @@ -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`] = ` +
+
+ + + +
+
+`; + +exports[`Editor snapshots renders "html" editor when ref is ready 1`] = ` +
+
+ + + +
+
+`; + +exports[`Editor snapshots renders no editor when ref isnt ready 1`] = ` +
+
+
+`; diff --git a/src/editors/__snapshots__/EditorPage.test.jsx.snap b/src/editors/__snapshots__/EditorPage.test.jsx.snap new file mode 100644 index 000000000..ad05865d2 --- /dev/null +++ b/src/editors/__snapshots__/EditorPage.test.jsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Editor Page snapshots rendering correctly with expected Input 1`] = ` + + + +`; diff --git a/src/editors/components/EditorFooter/index.jsx b/src/editors/components/EditorFooter/index.jsx index c2b3de3ca..fa918f3df 100644 --- a/src/editors/components/EditorFooter/index.jsx +++ b/src/editors/components/EditorFooter/index.jsx @@ -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 = ({ diff --git a/src/editors/components/EditorFooter/index.test.jsx b/src/editors/components/EditorFooter/index.test.jsx index b5e6cb9b2..98073d3ce 100644 --- a/src/editors/components/EditorFooter/index.test.jsx +++ b/src/editors/components/EditorFooter/index.test.jsx @@ -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 }); diff --git a/src/editors/hooks.js b/src/editors/hooks.js index e9f6fcea9..6e6c9d69c 100644 --- a/src/editors/hooks.js +++ b/src/editors/hooks.js @@ -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(), }); diff --git a/src/editors/hooks.test.jsx b/src/editors/hooks.test.jsx new file mode 100644 index 000000000..a81e7586d --- /dev/null +++ b/src/editors/hooks.test.jsx @@ -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, + }); + }); + }); +});