Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/content_editor/components/bubble_menus')
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js126
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/code_block_spec.js)8
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/formatting_spec.js)11
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/link_spec.js)136
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js (renamed from spec/frontend/content_editor/components/bubble_menus/media_spec.js)13
5 files changed, 252 insertions, 42 deletions
diff --git a/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
new file mode 100644
index 00000000000..0700cf5d529
--- /dev/null
+++ b/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
@@ -0,0 +1,126 @@
+import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import { createTestEditor } from '../../test_utils';
+
+jest.mock('@tiptap/extension-bubble-menu');
+
+describe('content_editor/components/bubble_menus/bubble_menu', () => {
+ let wrapper;
+ let tiptapEditor;
+ const pluginKey = 'key';
+ const shouldShow = jest.fn();
+ const tippyOptions = { placement: 'bottom' };
+ const pluginInitializationResult = {};
+
+ const buildEditor = () => {
+ tiptapEditor = createTestEditor();
+ };
+
+ const createWrapper = (propsData = {}) => {
+ wrapper = shallowMountExtended(BubbleMenu, {
+ provide: {
+ tiptapEditor,
+ },
+ propsData: {
+ pluginKey,
+ shouldShow,
+ tippyOptions,
+ ...propsData,
+ },
+ slots: {
+ default: '<div>menu content</div>',
+ },
+ });
+ };
+
+ const setupMocks = () => {
+ BubbleMenuPlugin.mockReturnValueOnce(pluginInitializationResult);
+ jest.spyOn(tiptapEditor, 'registerPlugin').mockImplementationOnce(() => true);
+ };
+
+ const invokeTippyEvent = (eventName, eventArgs) => {
+ const pluginConfig = BubbleMenuPlugin.mock.calls[0][0];
+
+ pluginConfig.tippyOptions[eventName](eventArgs);
+ };
+
+ beforeEach(() => {
+ buildEditor();
+ setupMocks();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('initializes BubbleMenuPlugin', async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ expect(BubbleMenuPlugin).toHaveBeenCalledWith({
+ pluginKey,
+ editor: tiptapEditor,
+ shouldShow,
+ element: wrapper.vm.$el,
+ tippyOptions: expect.objectContaining({
+ onHidden: expect.any(Function),
+ onShow: expect.any(Function),
+ ...tippyOptions,
+ }),
+ });
+
+ expect(tiptapEditor.registerPlugin).toHaveBeenCalledWith(pluginInitializationResult);
+ });
+
+ it('does not render default slot by default', async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ expect(wrapper.text()).not.toContain('menu content');
+ });
+
+ describe('when onShow event handler is invoked', () => {
+ const onShowArgs = {};
+
+ beforeEach(async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ invokeTippyEvent('onShow', onShowArgs);
+ });
+
+ it('displays the menu content', () => {
+ expect(wrapper.text()).toContain('menu content');
+ });
+
+ it('emits show event', () => {
+ expect(wrapper.emitted('show')).toEqual([[onShowArgs]]);
+ });
+ });
+
+ describe('when onHidden event handler is invoked', () => {
+ const onHiddenArgs = {};
+
+ beforeEach(async () => {
+ createWrapper({});
+
+ await nextTick();
+
+ invokeTippyEvent('onShow', onHiddenArgs);
+ invokeTippyEvent('onHidden', onHiddenArgs);
+ });
+
+ it('displays the menu content', () => {
+ expect(wrapper.text()).not.toContain('menu content');
+ });
+
+ it('emits show event', () => {
+ expect(wrapper.emitted('hidden')).toEqual([[onHiddenArgs]]);
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
index 154035a46ed..378b11f4ae9 100644
--- a/spec/frontend/content_editor/components/bubble_menus/code_block_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
@@ -1,4 +1,3 @@
-import { BubbleMenu } from '@tiptap/vue-2';
import {
GlDropdown,
GlDropdownForm,
@@ -9,8 +8,9 @@ import {
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
-import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block.vue';
+import CodeBlockBubbleMenu from '~/content_editor/components/bubble_menus/code_block_bubble_menu.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
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';
@@ -18,7 +18,7 @@ import { createTestEditor, emitEditorEvent } from '../../test_utils';
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
-describe('content_editor/components/bubble_menus/code_block', () => {
+describe('content_editor/components/bubble_menus/code_block_bubble_menu', () => {
let wrapper;
let tiptapEditor;
let contentEditor;
@@ -40,6 +40,7 @@ describe('content_editor/components/bubble_menus/code_block', () => {
},
stubs: {
GlDropdownItem: stubComponent(GlDropdownItem),
+ BubbleMenu: stubComponent(BubbleMenu),
},
});
};
@@ -73,7 +74,6 @@ describe('content_editor/components/bubble_menus/code_block', () => {
await emitEditorEvent({ event: 'transaction', tiptapEditor });
- expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
diff --git a/spec/frontend/content_editor/components/bubble_menus/formatting_spec.js b/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
index 1e2f58d9e40..cce17176129 100644
--- a/spec/frontend/content_editor/components/bubble_menus/formatting_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
@@ -1,7 +1,8 @@
-import { BubbleMenu } from '@tiptap/vue-2';
import { mockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import FormattingBubbleMenu from '~/content_editor/components/bubble_menus/formatting.vue';
+import FormattingBubbleMenu from '~/content_editor/components/bubble_menus/formatting_bubble_menu.vue';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import { stubComponent } from 'helpers/stub_component';
import {
BUBBLE_MENU_TRACKING_ACTION,
@@ -9,7 +10,7 @@ import {
} from '~/content_editor/constants';
import { createTestEditor } from '../../test_utils';
-describe('content_editor/components/bubble_menus/formatting', () => {
+describe('content_editor/components/bubble_menus/formatting_bubble_menu', () => {
let wrapper;
let trackingSpy;
let tiptapEditor;
@@ -25,6 +26,9 @@ describe('content_editor/components/bubble_menus/formatting', () => {
provide: {
tiptapEditor,
},
+ stubs: {
+ BubbleMenu: stubComponent(BubbleMenu),
+ },
});
};
@@ -41,7 +45,6 @@ describe('content_editor/components/bubble_menus/formatting', () => {
buildWrapper();
const bubbleMenu = wrapper.findComponent(BubbleMenu);
- expect(bubbleMenu.props().editor).toBe(tiptapEditor);
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
diff --git a/spec/frontend/content_editor/components/bubble_menus/link_spec.js b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
index 93204deb68c..9aa9c6483f4 100644
--- a/spec/frontend/content_editor/components/bubble_menus/link_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
@@ -1,18 +1,20 @@
import { GlLink, GlForm } from '@gitlab/ui';
-import { BubbleMenu } from '@tiptap/vue-2';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import LinkBubbleMenu from '~/content_editor/components/bubble_menus/link.vue';
+import LinkBubbleMenu from '~/content_editor/components/bubble_menus/link_bubble_menu.vue';
+import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import { stubComponent } from 'helpers/stub_component';
import Link from '~/content_editor/extensions/link';
-import { createTestEditor, emitEditorEvent } from '../../test_utils';
+import { createTestEditor } from '../../test_utils';
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
-describe('content_editor/components/bubble_menus/link', () => {
+describe('content_editor/components/bubble_menus/link_bubble_menu', () => {
let wrapper;
let tiptapEditor;
let contentEditor;
- let bubbleMenu;
let eventHub;
const buildEditor = () => {
@@ -28,9 +30,28 @@ describe('content_editor/components/bubble_menus/link', () => {
contentEditor,
eventHub,
},
+ stubs: {
+ BubbleMenu: stubComponent(BubbleMenu),
+ },
});
};
+ const showMenu = () => {
+ wrapper.findComponent(BubbleMenu).vm.$emit('show');
+ return nextTick();
+ };
+
+ const buildWrapperAndDisplayMenu = () => {
+ buildWrapper();
+
+ return showMenu();
+ };
+
+ const findBubbleMenu = () => wrapper.findComponent(BubbleMenu);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findEditorStateObserver = () => wrapper.findComponent(EditorStateObserver);
+ const findEditLinkButton = () => wrapper.findByTestId('edit-link');
+
const expectLinkButtonsToExist = (exist = true) => {
expect(wrapper.findComponent(GlLink).exists()).toBe(exist);
expect(wrapper.findByTestId('copy-link-url').exists()).toBe(exist);
@@ -40,7 +61,6 @@ describe('content_editor/components/bubble_menus/link', () => {
beforeEach(async () => {
buildEditor();
- buildWrapper();
tiptapEditor
.chain()
@@ -49,10 +69,6 @@ describe('content_editor/components/bubble_menus/link', () => {
)
.setTextSelection(14) // put cursor in the middle of the link
.run();
-
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
-
- bubbleMenu = wrapper.findComponent(BubbleMenu);
});
afterEach(() => {
@@ -60,13 +76,15 @@ describe('content_editor/components/bubble_menus/link', () => {
});
it('renders bubble menu component', async () => {
- expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
- expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
+ await buildWrapperAndDisplayMenu();
+
+ expect(findBubbleMenu().classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
it('shows a clickable link to the URL in the link node', async () => {
- const link = wrapper.findComponent(GlLink);
- expect(link.attributes()).toEqual(
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
expect.objectContaining({
href: '/path/to/project/-/wikis/uploads/my_file.pdf',
'aria-label': 'uploads/my_file.pdf',
@@ -74,11 +92,82 @@ describe('content_editor/components/bubble_menus/link', () => {
target: '_blank',
}),
);
- expect(link.text()).toBe('uploads/my_file.pdf');
+ expect(findLink().text()).toBe('uploads/my_file.pdf');
+ });
+
+ it('updates the bubble menu state when @selectionUpdate event is triggered', async () => {
+ const linkUrl = 'https://gitlab.com';
+
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '/path/to/project/-/wikis/uploads/my_file.pdf',
+ }),
+ );
+
+ tiptapEditor
+ .chain()
+ .setContent(
+ `Link to <a href="${linkUrl}" data-canonical-src="${linkUrl}" title="Click here to download">GitLab</a>`,
+ )
+ .setTextSelection(11)
+ .run();
+
+ findEditorStateObserver().vm.$emit('selectionUpdate');
+
+ await nextTick();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: linkUrl,
+ }),
+ );
+ });
+
+ describe('when the selection changes within the same link', () => {
+ it('does not update the bubble menu state', async () => {
+ await buildWrapperAndDisplayMenu();
+
+ await findEditLinkButton().trigger('click');
+
+ expect(wrapper.findComponent(GlForm).exists()).toBe(true);
+
+ tiptapEditor.commands.setTextSelection(13);
+
+ findEditorStateObserver().vm.$emit('selectionUpdate');
+
+ await nextTick();
+
+ expect(wrapper.findComponent(GlForm).exists()).toBe(true);
+ });
+ });
+
+ it('cleans bubble menu state when hidden event is triggered', async () => {
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '/path/to/project/-/wikis/uploads/my_file.pdf',
+ }),
+ );
+
+ findBubbleMenu().vm.$emit('hidden');
+
+ await nextTick();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '#',
+ }),
+ );
+ expect(findLink().text()).toEqual('');
});
describe('copy button', () => {
it('copies the canonical link to clipboard', async () => {
+ await buildWrapperAndDisplayMenu();
+
jest.spyOn(navigator.clipboard, 'writeText');
await wrapper.findByTestId('copy-link-url').vm.$emit('click');
@@ -89,6 +178,7 @@ describe('content_editor/components/bubble_menus/link', () => {
describe('remove link button', () => {
it('removes the link', async () => {
+ await buildWrapperAndDisplayMenu();
await wrapper.findByTestId('remove-link').vm.$emit('click');
expect(tiptapEditor.getHTML()).toBe('<p>Download PDF File</p>');
@@ -106,7 +196,7 @@ describe('content_editor/components/bubble_menus/link', () => {
.setTextSelection(4)
.run();
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ await buildWrapperAndDisplayMenu();
});
it('directly opens the edit form for a placeholder link', async () => {
@@ -133,6 +223,7 @@ describe('content_editor/components/bubble_menus/link', () => {
let linkTitleInput;
beforeEach(async () => {
+ await buildWrapperAndDisplayMenu();
await wrapper.findByTestId('edit-link').vm.$emit('click');
linkHrefInput = wrapper.findByTestId('link-href');
@@ -157,19 +248,6 @@ describe('content_editor/components/bubble_menus/link', () => {
expect(to).toBe(18);
});
- it('shows the copy/edit/remove link buttons again if selection changes to another non-link and then back again to a link', async () => {
- expectLinkButtonsToExist(false);
-
- tiptapEditor.commands.setTextSelection(3);
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
-
- tiptapEditor.commands.setTextSelection(14);
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
-
- expectLinkButtonsToExist(true);
- expect(wrapper.findComponent(GlForm).exists()).toBe(false);
- });
-
describe('after making changes in the form and clicking apply', () => {
beforeEach(async () => {
linkHrefInput.setValue('https://google.com');
diff --git a/spec/frontend/content_editor/components/bubble_menus/media_spec.js b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
index fada4f06743..13c6495ac41 100644
--- a/spec/frontend/content_editor/components/bubble_menus/media_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
@@ -1,7 +1,8 @@
import { GlLink, GlForm } from '@gitlab/ui';
-import { BubbleMenu } from '@tiptap/vue-2';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media.vue';
+import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
+import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media_bubble_menu.vue';
+import { stubComponent } from 'helpers/stub_component';
import eventHubFactory from '~/helpers/event_hub_factory';
import Image from '~/content_editor/extensions/image';
import Audio from '~/content_editor/extensions/audio';
@@ -33,7 +34,7 @@ describe.each`
${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${'test-file.mp3'} | ${TIPTAP_AUDIO_HTML}
${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${'test-file.mp4'} | ${TIPTAP_VIDEO_HTML}
`(
- 'content_editor/components/bubble_menus/media ($mediaType)',
+ 'content_editor/components/bubble_menus/media_bubble_menu ($mediaType)',
({ mediaType, mediaHTML, filePath, mediaOutputHTML }) => {
let wrapper;
let tiptapEditor;
@@ -54,11 +55,14 @@ describe.each`
contentEditor,
eventHub,
},
+ stubs: {
+ BubbleMenu: stubComponent(BubbleMenu),
+ },
});
};
const selectFile = async (file) => {
- const input = wrapper.find({ ref: 'fileSelector' });
+ const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });
@@ -94,7 +98,6 @@ describe.each`
});
it('renders bubble menu component', async () => {
- expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});