diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/rich_content_editor')
5 files changed, 209 insertions, 48 deletions
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js new file mode 100644 index 00000000000..faa32131fab --- /dev/null +++ b/spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js @@ -0,0 +1,77 @@ +import { + generateToolbarItem, + addCustomEventListener, + removeCustomEventListener, + addImage, + getMarkdown, +} from '~/vue_shared/components/rich_content_editor/editor_service'; + +describe('Editor Service', () => { + const mockInstance = { + eventManager: { addEventType: jest.fn(), removeEventHandler: jest.fn(), listen: jest.fn() }, + editor: { exec: jest.fn() }, + invoke: jest.fn(), + }; + const event = 'someCustomEvent'; + const handler = jest.fn(); + + describe('generateToolbarItem', () => { + const config = { + icon: 'bold', + command: 'some-command', + tooltip: 'Some Tooltip', + event: 'some-event', + }; + + const generatedItem = generateToolbarItem(config); + + it('generates the correct command', () => { + expect(generatedItem.options.command).toBe(config.command); + }); + + it('generates the correct event', () => { + expect(generatedItem.options.event).toBe(config.event); + }); + + it('generates a divider when isDivider is set to true', () => { + const isDivider = true; + + expect(generateToolbarItem({ isDivider })).toBe('divider'); + }); + }); + + describe('addCustomEventListener', () => { + it('registers an event type on the instance and adds an event handler', () => { + addCustomEventListener(mockInstance, event, handler); + + expect(mockInstance.eventManager.addEventType).toHaveBeenCalledWith(event); + expect(mockInstance.eventManager.listen).toHaveBeenCalledWith(event, handler); + }); + }); + + describe('removeCustomEventListener', () => { + it('removes an event handler from the instance', () => { + removeCustomEventListener(mockInstance, event, handler); + + expect(mockInstance.eventManager.removeEventHandler).toHaveBeenCalledWith(event, handler); + }); + }); + + describe('addImage', () => { + it('calls the exec method on the instance', () => { + const mockImage = { imageUrl: 'some/url.png', description: 'some description' }; + + addImage(mockInstance, mockImage); + + expect(mockInstance.editor.exec).toHaveBeenCalledWith('AddImage', mockImage); + }); + }); + + describe('getMarkdown', () => { + it('calls the invoke method on the instance', () => { + getMarkdown(mockInstance); + + expect(mockInstance.invoke).toHaveBeenCalledWith('getMarkdown'); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js new file mode 100644 index 00000000000..4889bc8538d --- /dev/null +++ b/spec/frontend/vue_shared/components/rich_content_editor/modals/add_image_modal_spec.js @@ -0,0 +1,41 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; +import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image_modal.vue'; + +describe('Add Image Modal', () => { + let wrapper; + + const findModal = () => wrapper.find(GlModal); + const findUrlInput = () => wrapper.find({ ref: 'urlInput' }); + const findDescriptionInput = () => wrapper.find({ ref: 'descriptionInput' }); + + beforeEach(() => { + wrapper = shallowMount(AddImageModal); + }); + + describe('when content is loaded', () => { + it('renders a modal component', () => { + expect(findModal().exists()).toBe(true); + }); + + it('renders an input to add an image URL', () => { + expect(findUrlInput().exists()).toBe(true); + }); + + it('renders an input to add an image description', () => { + expect(findDescriptionInput().exists()).toBe(true); + }); + }); + + describe('add image', () => { + it('emits an addImage event when a valid URL is specified', () => { + const preventDefault = jest.fn(); + const mockImage = { imageUrl: '/some/valid/url.png', altText: 'some description' }; + wrapper.setData({ ...mockImage }); + + findModal().vm.$emit('ok', { preventDefault }); + expect(preventDefault).not.toHaveBeenCalled(); + expect(wrapper.emitted('addImage')).toEqual([[mockImage]]); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js index 549d89171c6..0db10389df4 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js @@ -1,17 +1,33 @@ import { shallowMount } from '@vue/test-utils'; import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; +import AddImageModal from '~/vue_shared/components/rich_content_editor/modals/add_image_modal.vue'; import { EDITOR_OPTIONS, EDITOR_TYPES, EDITOR_HEIGHT, EDITOR_PREVIEW_STYLE, + CUSTOM_EVENTS, } from '~/vue_shared/components/rich_content_editor/constants'; +import { + addCustomEventListener, + removeCustomEventListener, + addImage, +} from '~/vue_shared/components/rich_content_editor/editor_service'; + +jest.mock('~/vue_shared/components/rich_content_editor/editor_service', () => ({ + ...jest.requireActual('~/vue_shared/components/rich_content_editor/editor_service'), + addCustomEventListener: jest.fn(), + removeCustomEventListener: jest.fn(), + addImage: jest.fn(), +})); + describe('Rich Content Editor', () => { let wrapper; const value = '## Some Markdown'; const findEditor = () => wrapper.find({ ref: 'editor' }); + const findAddImageModal = () => wrapper.find(AddImageModal); beforeEach(() => { wrapper = shallowMount(RichContentEditor, { @@ -56,4 +72,47 @@ describe('Rich Content Editor', () => { expect(wrapper.emitted().input[0][0]).toBe(changedMarkdown); }); }); + + describe('when editor is loaded', () => { + it('adds the CUSTOM_EVENTS.openAddImageModal custom event listener', () => { + const mockEditorApi = { eventManager: { addEventType: jest.fn(), listen: jest.fn() } }; + findEditor().vm.$emit('load', mockEditorApi); + + expect(addCustomEventListener).toHaveBeenCalledWith( + mockEditorApi, + CUSTOM_EVENTS.openAddImageModal, + wrapper.vm.onOpenAddImageModal, + ); + }); + }); + + describe('when editor is destroyed', () => { + it('removes the CUSTOM_EVENTS.openAddImageModal custom event listener', () => { + const mockEditorApi = { eventManager: { removeEventHandler: jest.fn() } }; + + wrapper.vm.editorApi = mockEditorApi; + wrapper.vm.$destroy(); + + expect(removeCustomEventListener).toHaveBeenCalledWith( + mockEditorApi, + CUSTOM_EVENTS.openAddImageModal, + wrapper.vm.onOpenAddImageModal, + ); + }); + }); + + describe('add image modal', () => { + 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', description: 'some description' }; + const mockInstance = { exec: jest.fn() }; + wrapper.vm.$refs.editor = mockInstance; + + findAddImageModal().vm.$emit('addImage', mockImage); + expect(addImage).toHaveBeenCalledWith(mockInstance, mockImage); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js index 8545c43dc1e..2db15a71215 100644 --- a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js +++ b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_item_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { GlIcon } from '@gitlab/ui'; import ToolbarItem from '~/vue_shared/components/rich_content_editor/toolbar_item.vue'; @@ -9,33 +10,45 @@ describe('Toolbar Item', () => { const findButton = () => wrapper.find('button'); const buildWrapper = propsData => { - wrapper = shallowMount(ToolbarItem, { propsData }); + wrapper = shallowMount(ToolbarItem, { + propsData, + directives: { + GlTooltip: createMockDirective(), + }, + }); }; describe.each` - icon - ${'heading'} - ${'bold'} - ${'italic'} - ${'strikethrough'} - ${'quote'} - ${'link'} - ${'doc-code'} - ${'list-bulleted'} - ${'list-numbered'} - ${'list-task'} - ${'list-indent'} - ${'list-outdent'} - ${'dash'} - ${'table'} - ${'code'} - `('toolbar item component', ({ icon }) => { - beforeEach(() => buildWrapper({ icon })); + icon | tooltip + ${'heading'} | ${'Headings'} + ${'bold'} | ${'Add bold text'} + ${'italic'} | ${'Add italic text'} + ${'strikethrough'} | ${'Add strikethrough text'} + ${'quote'} | ${'Insert a quote'} + ${'link'} | ${'Add a link'} + ${'doc-code'} | ${'Insert a code block'} + ${'list-bulleted'} | ${'Add a bullet list'} + ${'list-numbered'} | ${'Add a numbered list'} + ${'list-task'} | ${'Add a task list'} + ${'list-indent'} | ${'Indent'} + ${'list-outdent'} | ${'Outdent'} + ${'dash'} | ${'Add a line'} + ${'table'} | ${'Add a table'} + ${'code'} | ${'Insert an image'} + ${'code'} | ${'Insert inline code'} + `('toolbar item component', ({ icon, tooltip }) => { + beforeEach(() => buildWrapper({ icon, tooltip })); it('renders a toolbar button', () => { expect(findButton().exists()).toBe(true); }); + it('renders the correct tooltip', () => { + const buttonTooltip = getBinding(wrapper.element, 'gl-tooltip'); + expect(buttonTooltip).toBeDefined(); + expect(buttonTooltip.value.title).toBe(tooltip); + }); + it(`renders the ${icon} icon`, () => { expect(findIcon().exists()).toBe(true); expect(findIcon().props().name).toBe(icon); diff --git a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_service_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/toolbar_service_spec.js deleted file mode 100644 index 7605cc6a22c..00000000000 --- a/spec/frontend/vue_shared/components/rich_content_editor/toolbar_service_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import { generateToolbarItem } from '~/vue_shared/components/rich_content_editor/toolbar_service'; - -describe('Toolbar Service', () => { - const config = { - icon: 'bold', - command: 'some-command', - tooltip: 'Some Tooltip', - event: 'some-event', - }; - const generatedItem = generateToolbarItem(config); - - it('generates the correct command', () => { - expect(generatedItem.options.command).toBe(config.command); - }); - - it('generates the correct tooltip', () => { - expect(generatedItem.options.tooltip).toBe(config.tooltip); - }); - - it('generates the correct event', () => { - expect(generatedItem.options.event).toBe(config.event); - }); - - it('generates a divider when isDivider is set to true', () => { - const isDivider = true; - - expect(generateToolbarItem({ isDivider })).toBe('divider'); - }); -}); |