import { Editor, mockEditorApi } from '@toast-ui/vue-editor'; import { shallowMount } from '@vue/test-utils'; import { EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE, CUSTOM_EVENTS, } from '~/static_site_editor/rich_content_editor/constants'; import AddImageModal from '~/static_site_editor/rich_content_editor/modals/add_image/add_image_modal.vue'; import InsertVideoModal from '~/static_site_editor/rich_content_editor/modals/insert_video_modal.vue'; import RichContentEditor from '~/static_site_editor/rich_content_editor/rich_content_editor.vue'; import { addCustomEventListener, removeCustomEventListener, addImage, insertVideo, registerHTMLToMarkdownRenderer, getEditorOptions, getMarkdown, } from '~/static_site_editor/rich_content_editor/services/editor_service'; jest.mock('~/static_site_editor/rich_content_editor/services/editor_service', () => ({ addCustomEventListener: jest.fn(), removeCustomEventListener: jest.fn(), addImage: jest.fn(), insertVideo: jest.fn(), registerHTMLToMarkdownRenderer: jest.fn(), getEditorOptions: jest.fn(), getMarkdown: jest.fn(), })); describe('Rich Content Editor', () => { let wrapper; const content = '## Some Markdown'; const imageRoot = 'path/to/root/'; const findEditor = () => wrapper.find({ ref: 'editor' }); const findAddImageModal = () => wrapper.find(AddImageModal); const findInsertVideoModal = () => wrapper.find(InsertVideoModal); const buildWrapper = async () => { wrapper = shallowMount(RichContentEditor, { propsData: { content, imageRoot }, stubs: { ToastEditor: Editor, }, }); }; afterEach(() => { wrapper.destroy(); wrapper = null; }); describe('when content is loaded', () => { const editorOptions = {}; beforeEach(() => { getEditorOptions.mockReturnValueOnce(editorOptions); buildWrapper(); }); it('renders an editor', () => { expect(findEditor().exists()).toBe(true); }); it('renders the correct content', () => { expect(findEditor().props().initialValue).toBe(content); }); it('provides options generated by the getEditorOptions service', () => { expect(findEditor().props().options).toBe(editorOptions); }); it('has the correct preview style', () => { expect(findEditor().props().previewStyle).toBe(EDITOR_PREVIEW_STYLE); }); it('has the correct initial edit type', () => { expect(findEditor().props().initialEditType).toBe(EDITOR_TYPES.wysiwyg); }); it('has the correct height', () => { expect(findEditor().props().height).toBe(EDITOR_HEIGHT); }); }); describe('when content is changed', () => { beforeEach(() => { buildWrapper(); }); it('emits an input event with the changed content', () => { const changedMarkdown = '## Changed Markdown'; getMarkdown.mockReturnValueOnce(changedMarkdown); findEditor().vm.$emit('change'); expect(wrapper.emitted().input[0][0]).toBe(changedMarkdown); }); }); describe('when content is reset', () => { beforeEach(() => { buildWrapper(); }); it('should reset the content via setMarkdown', () => { const newContent = 'Just the body content excluding the front matter for example'; const mockInstance = { invoke: jest.fn() }; wrapper.vm.$refs.editor = mockInstance; wrapper.vm.resetInitialValue(newContent); expect(mockInstance.invoke).toHaveBeenCalledWith('setMarkdown', newContent); }); }); describe('when editor is loaded', () => { const formattedMarkdown = 'formatted markdown'; beforeEach(() => { mockEditorApi.getMarkdown.mockReturnValueOnce(formattedMarkdown); buildWrapper(); }); afterEach(() => { mockEditorApi.getMarkdown.mockReset(); }); it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => { expect(addCustomEventListener).toHaveBeenCalledWith( wrapper.vm.editorApi, CUSTOM_EVENTS.openAddImageModal, wrapper.vm.onOpenAddImageModal, ); }); it('adds the CUSTOM_EVENTS.openInsertVideoModal custom event listener', () => { expect(addCustomEventListener).toHaveBeenCalledWith( wrapper.vm.editorApi, CUSTOM_EVENTS.openInsertVideoModal, wrapper.vm.onOpenInsertVideoModal, ); }); it('registers HTML to markdown renderer', () => { expect(registerHTMLToMarkdownRenderer).toHaveBeenCalledWith(wrapper.vm.editorApi); }); it('emits load event with the markdown formatted by Toast UI', () => { mockEditorApi.getMarkdown.mockReturnValueOnce(formattedMarkdown); expect(mockEditorApi.getMarkdown).toHaveBeenCalled(); expect(wrapper.emitted('load')[0]).toEqual([{ formattedMarkdown }]); }); }); describe('when editor is destroyed', () => { beforeEach(() => { buildWrapper(); }); it('removes the CUSTOM_EVENTS.openAddImageModal custom event listener', () => { wrapper.vm.$destroy(); expect(removeCustomEventListener).toHaveBeenCalledWith( wrapper.vm.editorApi, CUSTOM_EVENTS.openAddImageModal, wrapper.vm.onOpenAddImageModal, ); }); it('removes the CUSTOM_EVENTS.openInsertVideoModal custom event listener', () => { wrapper.vm.$destroy(); expect(removeCustomEventListener).toHaveBeenCalledWith( wrapper.vm.editorApi, CUSTOM_EVENTS.openInsertVideoModal, wrapper.vm.onOpenInsertVideoModal, ); }); }); describe('add image modal', () => { beforeEach(() => { buildWrapper(); }); it('renders an addImageModal component', () => { expect(findAddImageModal().exists()).toBe(true); }); it('calls the onAddImage method when the addImage event is emitted', () => { const mockImage = { imageUrl: 'some/url.png', altText: 'some description' }; const mockInstance = { exec: jest.fn() }; wrapper.vm.$refs.editor = mockInstance; findAddImageModal().vm.$emit('addImage', mockImage); expect(addImage).toHaveBeenCalledWith(mockInstance, mockImage, undefined); }); }); describe('insert video modal', () => { beforeEach(() => { buildWrapper(); }); it('renders an insertVideoModal component', () => { expect(findInsertVideoModal().exists()).toBe(true); }); it('calls the onInsertVideo method when the insertVideo event is emitted', () => { const mockUrl = 'https://www.youtube.com/embed/someId'; const mockInstance = { exec: jest.fn() }; wrapper.vm.$refs.editor = mockInstance; findInsertVideoModal().vm.$emit('insertVideo', mockUrl); expect(insertVideo).toHaveBeenCalledWith(mockInstance, mockUrl); }); }); });