summaryrefslogtreecommitdiff
path: root/spec/frontend/content_editor/components/code_block_bubble_menu_spec.js
blob: 074c311495f8fb876234cf468280d9fc9c5b63a3 (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
import { BubbleMenu } from '@tiptap/vue-2';
import { GlButton, GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import Vue from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CodeBlockBubbleMenu from '~/content_editor/components/code_block_bubble_menu.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
import codeBlockLanguageLoader from '~/content_editor/services/code_block_language_loader';
import { createTestEditor, emitEditorEvent } from '../test_utils';

describe('content_editor/components/code_block_bubble_menu', () => {
  let wrapper;
  let tiptapEditor;
  let bubbleMenu;
  let eventHub;

  const buildEditor = () => {
    tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight] });
    eventHub = eventHubFactory();
  };

  const buildWrapper = () => {
    wrapper = mountExtended(CodeBlockBubbleMenu, {
      provide: {
        tiptapEditor,
        eventHub,
      },
    });
  };

  const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
  const findDropdownItemsData = () =>
    findDropdownItems().wrappers.map((x) => ({
      text: x.text(),
      visible: x.isVisible(),
      checked: x.props('isChecked'),
    }));

  beforeEach(() => {
    buildEditor();
    buildWrapper();
  });

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

  it('renders bubble menu component', async () => {
    tiptapEditor.commands.insertContent('<pre>test</pre>');
    bubbleMenu = wrapper.findComponent(BubbleMenu);

    await emitEditorEvent({ event: 'transaction', tiptapEditor });

    expect(bubbleMenu.props('editor')).toBe(tiptapEditor);
    expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base']);
  });

  it('selects plaintext language by default', async () => {
    tiptapEditor.commands.insertContent('<pre>test</pre>');
    bubbleMenu = wrapper.findComponent(BubbleMenu);

    await emitEditorEvent({ event: 'transaction', tiptapEditor });

    expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Plain text');
  });

  it('selects appropriate language based on the code block', async () => {
    tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>');
    bubbleMenu = wrapper.findComponent(BubbleMenu);

    await emitEditorEvent({ event: 'transaction', tiptapEditor });

    expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Javascript');
  });

  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);

    await emitEditorEvent({ event: 'transaction', tiptapEditor });

    expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Custom (nomnoml)');
  });

  it('delete button deletes the code block', async () => {
    tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>');

    await wrapper.findComponent(GlButton).vm.$emit('click');

    expect(tiptapEditor.getText()).toBe('');
  });

  describe('when opened and search is changed', () => {
    beforeEach(async () => {
      tiptapEditor.commands.insertContent('<pre lang="javascript">var a = 2;</pre>');

      wrapper.findComponent(GlSearchBoxByType).vm.$emit('input', 'js');

      await Vue.nextTick();
    });

    it('shows dropdown items', () => {
      expect(findDropdownItemsData()).toEqual([
        { text: 'Javascript', visible: true, checked: true },
        { text: 'Java', visible: true, checked: false },
        { text: 'Javascript', visible: false, checked: false },
        { text: 'JSON', visible: true, checked: false },
      ]);
    });

    describe('when dropdown item is clicked', () => {
      beforeEach(async () => {
        jest.spyOn(codeBlockLanguageLoader, 'loadLanguages').mockResolvedValue();

        findDropdownItems().at(1).vm.$emit('click');

        await Vue.nextTick();
      });

      it('loads language', () => {
        expect(codeBlockLanguageLoader.loadLanguages).toHaveBeenCalledWith(['java']);
      });

      it('sets code block', () => {
        expect(tiptapEditor.getJSON()).toMatchObject({
          content: [
            {
              type: 'codeBlock',
              attrs: {
                language: 'java',
              },
            },
          ],
        });
      });

      it('updates selected dropdown', () => {
        expect(wrapper.findComponent(GlDropdown).props('text')).toBe('Java');
      });
    });
  });
});