summaryrefslogtreecommitdiff
path: root/spec/frontend/content_editor/components/toolbar_link_button_spec.js
blob: 812e769c891da5fbe4390437e75378d9a6fd9c16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { GlDropdown, GlDropdownDivider, GlFormInputGroup, GlButton } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ToolbarLinkButton from '~/content_editor/components/toolbar_link_button.vue';
import { tiptapExtension as Link } from '~/content_editor/extensions/link';
import { hasSelection } from '~/content_editor/services/utils';
import { createTestEditor, mockChainedCommands } from '../test_utils';

jest.mock('~/content_editor/services/utils');

describe('content_editor/components/toolbar_link_button', () => {
  let wrapper;
  let editor;

  const buildWrapper = () => {
    wrapper = mountExtended(ToolbarLinkButton, {
      propsData: {
        tiptapEditor: editor,
      },
      stubs: {
        GlFormInputGroup,
      },
    });
  };
  const findDropdown = () => wrapper.findComponent(GlDropdown);
  const findDropdownDivider = () => wrapper.findComponent(GlDropdownDivider);
  const findLinkURLInput = () => wrapper.findComponent(GlFormInputGroup).find('input[type="text"]');
  const findApplyLinkButton = () => wrapper.findComponent(GlButton);
  const findRemoveLinkButton = () => wrapper.findByText('Remove link');

  beforeEach(() => {
    editor = createTestEditor({
      extensions: [Link],
    });
  });

  afterEach(() => {
    editor.destroy();
    wrapper.destroy();
  });

  it('renders dropdown component', () => {
    buildWrapper();

    expect(findDropdown().html()).toMatchSnapshot();
  });

  describe('when there is an active link', () => {
    beforeEach(() => {
      jest.spyOn(editor, 'isActive');
      editor.isActive.mockReturnValueOnce(true);
      buildWrapper();
    });

    it('sets dropdown as active when link extension is active', () => {
      expect(findDropdown().props('toggleClass')).toEqual({ active: true });
    });

    it('displays a remove link dropdown option', () => {
      expect(findDropdownDivider().exists()).toBe(true);
      expect(wrapper.findByText('Remove link').exists()).toBe(true);
    });

    it('executes removeLink command when the remove link option is clicked', async () => {
      const commands = mockChainedCommands(editor, ['focus', 'unsetLink', 'run']);

      await findRemoveLinkButton().trigger('click');

      expect(commands.unsetLink).toHaveBeenCalled();
      expect(commands.focus).toHaveBeenCalled();
      expect(commands.run).toHaveBeenCalled();
    });

    it('updates the link with a new link when "Apply" button is clicked', async () => {
      const commands = mockChainedCommands(editor, ['focus', 'unsetLink', 'setLink', 'run']);

      await findLinkURLInput().setValue('https://example');
      await findApplyLinkButton().trigger('click');

      expect(commands.focus).toHaveBeenCalled();
      expect(commands.unsetLink).toHaveBeenCalled();
      expect(commands.setLink).toHaveBeenCalledWith({ href: 'https://example' });
      expect(commands.run).toHaveBeenCalled();
    });
  });

  describe('when there is not an active link', () => {
    beforeEach(() => {
      jest.spyOn(editor, 'isActive');
      editor.isActive.mockReturnValueOnce(false);
      buildWrapper();
    });

    it('does not set dropdown as active', () => {
      expect(findDropdown().props('toggleClass')).toEqual({ active: false });
    });

    it('does not display a remove link dropdown option', () => {
      expect(findDropdownDivider().exists()).toBe(false);
      expect(wrapper.findByText('Remove link').exists()).toBe(false);
    });

    it('sets the link to the value in the URL input when "Apply" button is clicked', async () => {
      const commands = mockChainedCommands(editor, ['focus', 'unsetLink', 'setLink', 'run']);

      await findLinkURLInput().setValue('https://example');
      await findApplyLinkButton().trigger('click');

      expect(commands.focus).toHaveBeenCalled();
      expect(commands.setLink).toHaveBeenCalledWith({ href: 'https://example' });
      expect(commands.run).toHaveBeenCalled();
    });
  });

  describe('when the user displays the dropdown', () => {
    let commands;

    beforeEach(() => {
      commands = mockChainedCommands(editor, ['focus', 'extendMarkRange', 'run']);
    });

    describe('given the user has not selected text', () => {
      beforeEach(() => {
        hasSelection.mockReturnValueOnce(false);
      });

      it('the editor selection is extended to the current mark extent', () => {
        buildWrapper();

        findDropdown().vm.$emit('show');
        expect(commands.extendMarkRange).toHaveBeenCalledWith(Link.name);
        expect(commands.focus).toHaveBeenCalled();
        expect(commands.run).toHaveBeenCalled();
      });
    });

    describe('given the user has selected text', () => {
      beforeEach(() => {
        hasSelection.mockReturnValueOnce(true);
      });

      it('the editor does not modify the current selection', () => {
        buildWrapper();

        findDropdown().vm.$emit('show');
        expect(commands.extendMarkRange).not.toHaveBeenCalled();
        expect(commands.focus).not.toHaveBeenCalled();
        expect(commands.run).not.toHaveBeenCalled();
      });
    });
  });
});