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
|
import { nextTick } from 'vue';
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
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');
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 = 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 });
});
it('renders a node-view-wrapper as a pre element', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewWrapper).props().as).toBe('pre');
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative');
});
it('adds content-editor-code-block class to the pre element', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('content-editor-code-block');
});
it('renders a node-view-content as a code element', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewContent).props().as).toBe('code');
});
it('renders label indicating that code block is frontmatter', () => {
createWrapper({ isFrontmatter: true, language });
const label = wrapper.find('[data-testid="frontmatter-label"]');
expect(label.text()).toEqual('frontmatter:yaml');
expect(label.classes()).toEqual(['gl-absolute', 'gl-top-0', 'gl-right-3']);
});
it('loads code block’s syntax highlight language', async () => {
createWrapper();
expect(codeBlockLanguageLoader.loadLanguage).toHaveBeenCalledWith(language);
await nextTick();
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.findComponent({ 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.findComponent(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.findComponent(SandboxedMermaid).props('source')).toBe('');
expect(wrapper.find('img').exists()).toBe(false);
});
});
});
|