diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-16 15:09:26 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-16 15:09:26 +0300 |
commit | b019dc959ec16b15fe42a680dbd729542ff61537 (patch) | |
tree | cde5c3d0582bd7542e1e195d83ad7a50079f4e49 /spec/frontend/content_editor/components | |
parent | b7b44de429911864686599ef1643baf525bf75ec (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/content_editor/components')
4 files changed, 197 insertions, 17 deletions
diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js index f19bd02443f..646d068e795 100644 --- a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js +++ b/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js @@ -12,6 +12,7 @@ import { stubComponent } from 'helpers/stub_component'; import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block.vue'; import eventHubFactory from '~/helpers/event_hub_factory'; import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; +import Diagram from '~/content_editor/extensions/diagram'; import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader'; import { createTestEditor, emitEditorEvent } from '../../test_utils'; @@ -20,11 +21,13 @@ const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jes describe('content_editor/components/bubble_menus/code_block', () => { let wrapper; let tiptapEditor; + let contentEditor; let bubbleMenu; let eventHub; const buildEditor = () => { - tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight] }); + tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight, Diagram] }); + contentEditor = { renderDiagram: jest.fn() }; eventHub = eventHubFactory(); }; @@ -32,6 +35,7 @@ describe('content_editor/components/bubble_menus/code_block', () => { wrapper = mountExtended(CodeBlockBubbleMenu, { provide: { tiptapEditor, + contentEditor, eventHub, }, stubs: { @@ -85,6 +89,15 @@ describe('content_editor/components/bubble_menus/code_block', () => { expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Javascript'); }); + it('selects diagram sytnax for mermaid', async () => { + tiptapEditor.commands.insertContent('<pre lang="mermaid">test</pre>'); + bubbleMenu = wrapper.findComponent(BubbleMenu); + + await emitEditorEvent({ event: 'transaction', tiptapEditor }); + + expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Diagram (mermaid)'); + }); + it("selects Custom (syntax) if the language doesn't exist in the list", async () => { tiptapEditor.commands.insertContent('<pre lang="nomnoml">test</pre>'); bubbleMenu = wrapper.findComponent(BubbleMenu); @@ -116,6 +129,39 @@ describe('content_editor/components/bubble_menus/code_block', () => { }); }); + describe('preview button', () => { + it('does not appear for a regular code block', async () => { + tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>'); + + expect(wrapper.findByTestId('preview-diagram').exists()).toBe(false); + }); + + it.each` + diagramType | diagramCode + ${'mermaid'} | ${'<pre lang="mermaid">graph TD;\n A-->B;</pre>'} + ${'nomnoml'} | ${'<img data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,WzxmcmFtZT5EZWNvcmF0b3IgcGF0dGVybl0=">'} + `('toggles preview for a $diagramType diagram', async ({ diagramType, diagramCode }) => { + tiptapEditor.commands.insertContent(diagramCode); + + await nextTick(); + await wrapper.findByTestId('preview-diagram').vm.$emit('click'); + + expect(tiptapEditor.getAttributes(Diagram.name)).toEqual({ + isDiagram: true, + language: diagramType, + showPreview: false, + }); + + await wrapper.findByTestId('preview-diagram').vm.$emit('click'); + + expect(tiptapEditor.getAttributes(Diagram.name)).toEqual({ + isDiagram: true, + language: diagramType, + showPreview: true, + }); + }); + }); + describe('when opened and search is changed', () => { beforeEach(async () => { tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>'); diff --git a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js new file mode 100644 index 00000000000..0334a18c9a1 --- /dev/null +++ b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js @@ -0,0 +1,54 @@ +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import ToolbarMoreDropdown from '~/content_editor/components/toolbar_more_dropdown.vue'; +import Diagram from '~/content_editor/extensions/diagram'; +import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; +import { createTestEditor, mockChainedCommands } from '../test_utils'; + +describe('content_editor/components/toolbar_more_dropdown', () => { + let wrapper; + let tiptapEditor; + + const buildEditor = () => { + tiptapEditor = createTestEditor({ + extensions: [Diagram, HorizontalRule], + }); + }; + + const buildWrapper = (propsData = {}) => { + wrapper = mountExtended(ToolbarMoreDropdown, { + provide: { + tiptapEditor, + }, + propsData, + }); + }; + + beforeEach(() => { + buildEditor(); + buildWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe.each` + label | contentType | data + ${'Mermaid diagram'} | ${'diagram'} | ${{ language: 'mermaid' }} + ${'PlantUML diagram'} | ${'diagram'} | ${{ language: 'plantuml' }} + ${'Horizontal rule'} | ${'horizontalRule'} | ${undefined} + `('when option $label is clicked', ({ label, contentType, data }) => { + it(`inserts a ${contentType}`, async () => { + const commands = mockChainedCommands(tiptapEditor, ['setNode', 'focus', 'run']); + + const btn = wrapper.findByRole('menuitem', { name: label }); + await btn.trigger('click'); + + expect(commands.focus).toHaveBeenCalled(); + expect(commands.setNode).toHaveBeenCalledWith(contentType, data); + expect(commands.run).toHaveBeenCalled(); + + expect(wrapper.emitted('execute')).toEqual([[{ contentType }]]); + }); + }); +}); diff --git a/spec/frontend/content_editor/components/top_toolbar_spec.js b/spec/frontend/content_editor/components/top_toolbar_spec.js index ec58877470c..d98a9a52aff 100644 --- a/spec/frontend/content_editor/components/top_toolbar_spec.js +++ b/spec/frontend/content_editor/components/top_toolbar_spec.js @@ -23,20 +23,21 @@ describe('content_editor/components/top_toolbar', () => { }); describe.each` - testId | controlProps - ${'bold'} | ${{ contentType: 'bold', iconName: 'bold', label: 'Bold text', editorCommand: 'toggleBold' }} - ${'italic'} | ${{ contentType: 'italic', iconName: 'italic', label: 'Italic text', editorCommand: 'toggleItalic' }} - ${'strike'} | ${{ contentType: 'strike', iconName: 'strikethrough', label: 'Strikethrough', editorCommand: 'toggleStrike' }} - ${'code'} | ${{ contentType: 'code', iconName: 'code', label: 'Code', editorCommand: 'toggleCode' }} - ${'blockquote'} | ${{ contentType: 'blockquote', iconName: 'quote', label: 'Insert a quote', editorCommand: 'toggleBlockquote' }} - ${'bullet-list'} | ${{ contentType: 'bulletList', iconName: 'list-bulleted', label: 'Add a bullet list', editorCommand: 'toggleBulletList' }} - ${'ordered-list'} | ${{ contentType: 'orderedList', iconName: 'list-numbered', label: 'Add a numbered list', editorCommand: 'toggleOrderedList' }} - ${'details'} | ${{ contentType: 'details', iconName: 'details-block', label: 'Add a collapsible section', editorCommand: 'toggleDetails' }} - ${'horizontal-rule'} | ${{ contentType: 'horizontalRule', iconName: 'dash', label: 'Add a horizontal rule', editorCommand: 'setHorizontalRule' }} - ${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }} - ${'text-styles'} | ${{}} - ${'link'} | ${{}} - ${'image'} | ${{}} + testId | controlProps + ${'bold'} | ${{ contentType: 'bold', iconName: 'bold', label: 'Bold text', editorCommand: 'toggleBold' }} + ${'italic'} | ${{ contentType: 'italic', iconName: 'italic', label: 'Italic text', editorCommand: 'toggleItalic' }} + ${'strike'} | ${{ contentType: 'strike', iconName: 'strikethrough', label: 'Strikethrough', editorCommand: 'toggleStrike' }} + ${'code'} | ${{ contentType: 'code', iconName: 'code', label: 'Code', editorCommand: 'toggleCode' }} + ${'blockquote'} | ${{ contentType: 'blockquote', iconName: 'quote', label: 'Insert a quote', editorCommand: 'toggleBlockquote' }} + ${'bullet-list'} | ${{ contentType: 'bulletList', iconName: 'list-bulleted', label: 'Add a bullet list', editorCommand: 'toggleBulletList' }} + ${'ordered-list'} | ${{ contentType: 'orderedList', iconName: 'list-numbered', label: 'Add a numbered list', editorCommand: 'toggleOrderedList' }} + ${'details'} | ${{ contentType: 'details', iconName: 'details-block', label: 'Add a collapsible section', editorCommand: 'toggleDetails' }} + ${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }} + ${'text-styles'} | ${{}} + ${'link'} | ${{}} + ${'image'} | ${{}} + ${'table'} | ${{}} + ${'more'} | ${{}} `('given a $testId toolbar control', ({ testId, controlProps }) => { beforeEach(() => { buildWrapper(); diff --git a/spec/frontend/content_editor/components/wrappers/code_block_spec.js b/spec/frontend/content_editor/components/wrappers/code_block_spec.js index 2e59cd9714a..17a365e12bb 100644 --- a/spec/frontend/content_editor/components/wrappers/code_block_spec.js +++ b/spec/frontend/content_editor/components/wrappers/code_block_spec.js @@ -1,8 +1,14 @@ import { nextTick } from 'vue'; import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'; -import { shallowMount } from '@vue/test-utils'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; +import eventHubFactory from '~/helpers/event_hub_factory'; +import SandboxedMermaid from '~/behaviors/components/sandboxed_mermaid.vue'; +import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; +import Diagram from '~/content_editor/extensions/diagram'; import CodeBlockWrapper from '~/content_editor/components/wrappers/code_block.vue'; import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader'; +import { emitEditorEvent, createTestEditor } from '../../test_utils'; jest.mock('~/content_editor/services/code_block_language_loader'); @@ -10,21 +16,42 @@ describe('content/components/wrappers/code_block', () => { const language = 'yaml'; let wrapper; let updateAttributesFn; + let tiptapEditor; + let contentEditor; + let eventHub; + + const buildEditor = () => { + tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight, Diagram] }); + contentEditor = { renderDiagram: jest.fn().mockResolvedValue('url/to/some/diagram') }; + eventHub = eventHubFactory(); + }; const createWrapper = async (nodeAttrs = { language }) => { updateAttributesFn = jest.fn(); - wrapper = shallowMount(CodeBlockWrapper, { + wrapper = mountExtended(CodeBlockWrapper, { propsData: { + editor: tiptapEditor, node: { attrs: nodeAttrs, }, updateAttributes: updateAttributesFn, }, + stubs: { + NodeViewContent: stubComponent(NodeViewContent), + NodeViewWrapper: stubComponent(NodeViewWrapper), + }, + provide: { + contentEditor, + tiptapEditor, + eventHub, + }, }); }; beforeEach(() => { + buildEditor(); + codeBlockLanguageLoader.findOrCreateLanguageBySyntax.mockReturnValue({ syntax: language }); }); @@ -68,4 +95,56 @@ describe('content/components/wrappers/code_block', () => { expect(updateAttributesFn).toHaveBeenCalledWith({ language }); }); + + describe('diagrams', () => { + beforeEach(() => { + jest.spyOn(tiptapEditor, 'isActive').mockReturnValue(true); + }); + + it('does not render a preview if showPreview: false', async () => { + createWrapper({ language: 'plantuml', isDiagram: true, showPreview: false }); + + expect(wrapper.find({ ref: 'diagramContainer' }).exists()).toBe(false); + }); + + it('does not update preview when diagram is not active', async () => { + createWrapper({ language: 'plantuml', isDiagram: true, showPreview: true }); + + await emitEditorEvent({ event: 'transaction', tiptapEditor }); + await nextTick(); + + expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram'); + + jest.spyOn(tiptapEditor, 'isActive').mockReturnValue(false); + + const alternateUrl = 'url/to/another/diagram'; + + contentEditor.renderDiagram.mockResolvedValue(alternateUrl); + + await emitEditorEvent({ event: 'transaction', tiptapEditor }); + await nextTick(); + + expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram'); + }); + + it('renders an image with preview for a plantuml/kroki diagram', async () => { + createWrapper({ language: 'plantuml', isDiagram: true, showPreview: true }); + + await emitEditorEvent({ event: 'transaction', tiptapEditor }); + await nextTick(); + + expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram'); + expect(wrapper.find(SandboxedMermaid).exists()).toBe(false); + }); + + it('renders an iframe with preview for a mermaid diagram', async () => { + createWrapper({ language: 'mermaid', isDiagram: true, showPreview: true }); + + await emitEditorEvent({ event: 'transaction', tiptapEditor }); + await nextTick(); + + expect(wrapper.find(SandboxedMermaid).props('source')).toBe(''); + expect(wrapper.find('img').exists()).toBe(false); + }); + }); }); |