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/link_bubble_menu_spec.js')
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js305
1 files changed, 305 insertions, 0 deletions
diff --git a/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
new file mode 100644
index 00000000000..9aa9c6483f4
--- /dev/null
+++ b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
@@ -0,0 +1,305 @@
+import { GlLink, GlForm } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+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 } from '../../test_utils';
+
+const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
+
+describe('content_editor/components/bubble_menus/link_bubble_menu', () => {
+ let wrapper;
+ let tiptapEditor;
+ let contentEditor;
+ let eventHub;
+
+ const buildEditor = () => {
+ tiptapEditor = createTestEditor({ extensions: [Link] });
+ contentEditor = { resolveUrl: jest.fn() };
+ eventHub = eventHubFactory();
+ };
+
+ const buildWrapper = () => {
+ wrapper = mountExtended(LinkBubbleMenu, {
+ provide: {
+ tiptapEditor,
+ 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);
+ expect(wrapper.findByTestId('edit-link').exists()).toBe(exist);
+ expect(wrapper.findByTestId('remove-link').exists()).toBe(exist);
+ };
+
+ beforeEach(async () => {
+ buildEditor();
+
+ tiptapEditor
+ .chain()
+ .insertContent(
+ 'Download <a href="/path/to/project/-/wikis/uploads/my_file.pdf" data-canonical-src="uploads/my_file.pdf" title="Click here to download">PDF File</a>',
+ )
+ .setTextSelection(14) // put cursor in the middle of the link
+ .run();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders bubble menu component', async () => {
+ 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 () => {
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().attributes()).toEqual(
+ expect.objectContaining({
+ href: '/path/to/project/-/wikis/uploads/my_file.pdf',
+ 'aria-label': 'uploads/my_file.pdf',
+ title: 'uploads/my_file.pdf',
+ target: '_blank',
+ }),
+ );
+ 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');
+
+ expect(navigator.clipboard.writeText).toHaveBeenCalledWith('uploads/my_file.pdf');
+ });
+ });
+
+ 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>');
+ });
+ });
+
+ describe('for a placeholder link', () => {
+ beforeEach(async () => {
+ tiptapEditor
+ .chain()
+ .clearContent()
+ .insertContent('Dummy link')
+ .selectAll()
+ .setLink({ href: '' })
+ .setTextSelection(4)
+ .run();
+
+ await buildWrapperAndDisplayMenu();
+ });
+
+ it('directly opens the edit form for a placeholder link', async () => {
+ expectLinkButtonsToExist(false);
+
+ expect(wrapper.findComponent(GlForm).exists()).toBe(true);
+ });
+
+ it('removes the link on clicking apply (if no change)', async () => {
+ await wrapper.findComponent(GlForm).vm.$emit('submit', createFakeEvent());
+
+ expect(tiptapEditor.getHTML()).toBe('<p>Dummy link</p>');
+ });
+
+ it('removes the link on clicking cancel', async () => {
+ await wrapper.findByTestId('cancel-link').vm.$emit('click');
+
+ expect(tiptapEditor.getHTML()).toBe('<p>Dummy link</p>');
+ });
+ });
+
+ describe('edit button', () => {
+ let linkHrefInput;
+ let linkTitleInput;
+
+ beforeEach(async () => {
+ await buildWrapperAndDisplayMenu();
+ await wrapper.findByTestId('edit-link').vm.$emit('click');
+
+ linkHrefInput = wrapper.findByTestId('link-href');
+ linkTitleInput = wrapper.findByTestId('link-title');
+ });
+
+ it('hides the link and copy/edit/remove link buttons', async () => {
+ expectLinkButtonsToExist(false);
+ });
+
+ it('shows a form to edit the link', () => {
+ expect(wrapper.findComponent(GlForm).exists()).toBe(true);
+
+ expect(linkHrefInput.element.value).toBe('uploads/my_file.pdf');
+ expect(linkTitleInput.element.value).toBe('Click here to download');
+ });
+
+ it('extends selection to select the entire link', () => {
+ const { from, to } = tiptapEditor.state.selection;
+
+ expect(from).toBe(10);
+ expect(to).toBe(18);
+ });
+
+ describe('after making changes in the form and clicking apply', () => {
+ beforeEach(async () => {
+ linkHrefInput.setValue('https://google.com');
+ linkTitleInput.setValue('Search Google');
+
+ contentEditor.resolveUrl.mockResolvedValue('https://google.com');
+
+ await wrapper.findComponent(GlForm).vm.$emit('submit', createFakeEvent());
+ });
+
+ it('updates prosemirror doc with new link', async () => {
+ expect(tiptapEditor.getHTML()).toBe(
+ '<p>Download <a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com" title="Search Google">PDF File</a></p>',
+ );
+ });
+
+ it('updates the link in the bubble menu', () => {
+ const link = wrapper.findComponent(GlLink);
+ expect(link.attributes()).toEqual(
+ expect.objectContaining({
+ href: 'https://google.com',
+ 'aria-label': 'https://google.com',
+ title: 'https://google.com',
+ target: '_blank',
+ }),
+ );
+ expect(link.text()).toBe('https://google.com');
+ });
+ });
+
+ describe('after making changes in the form and clicking cancel', () => {
+ beforeEach(async () => {
+ linkHrefInput.setValue('https://google.com');
+ linkTitleInput.setValue('Search Google');
+
+ await wrapper.findByTestId('cancel-link').vm.$emit('click');
+ });
+
+ it('hides the form and shows the copy/edit/remove link buttons', () => {
+ expectLinkButtonsToExist();
+ });
+
+ it('resets the form with old values of the link from prosemirror', async () => {
+ // click edit once again to show the form back
+ await wrapper.findByTestId('edit-link').vm.$emit('click');
+
+ linkHrefInput = wrapper.findByTestId('link-href');
+ linkTitleInput = wrapper.findByTestId('link-title');
+
+ expect(linkHrefInput.element.value).toBe('uploads/my_file.pdf');
+ expect(linkTitleInput.element.value).toBe('Click here to download');
+ });
+ });
+ });
+});