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/services')
-rw-r--r--spec/frontend/content_editor/services/asset_resolver_spec.js23
-rw-r--r--spec/frontend/content_editor/services/code_block_language_loader_spec.js36
-rw-r--r--spec/frontend/content_editor/services/content_editor_spec.js15
-rw-r--r--spec/frontend/content_editor/services/create_content_editor_spec.js48
-rw-r--r--spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js (renamed from spec/frontend/content_editor/services/markdown_deserializer_spec.js)16
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js44
-rw-r--r--spec/frontend/content_editor/services/markdown_sourcemap_spec.js2
7 files changed, 117 insertions, 67 deletions
diff --git a/spec/frontend/content_editor/services/asset_resolver_spec.js b/spec/frontend/content_editor/services/asset_resolver_spec.js
new file mode 100644
index 00000000000..f4e7d9bf881
--- /dev/null
+++ b/spec/frontend/content_editor/services/asset_resolver_spec.js
@@ -0,0 +1,23 @@
+import createAssetResolver from '~/content_editor/services/asset_resolver';
+
+describe('content_editor/services/asset_resolver', () => {
+ let renderMarkdown;
+ let assetResolver;
+
+ beforeEach(() => {
+ renderMarkdown = jest.fn();
+ assetResolver = createAssetResolver({ renderMarkdown });
+ });
+
+ describe('resolveUrl', () => {
+ it('resolves a canonical url to an absolute url', async () => {
+ renderMarkdown.mockResolvedValue(
+ '<p><a href="/group1/project1/-/wikis/test-file.png" data-canonical-src="test-file.png">link</a></p>',
+ );
+
+ expect(await assetResolver.resolveUrl('test-file.png')).toBe(
+ '/group1/project1/-/wikis/test-file.png',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/services/code_block_language_loader_spec.js b/spec/frontend/content_editor/services/code_block_language_loader_spec.js
index 905c1685b94..943de327762 100644
--- a/spec/frontend/content_editor/services/code_block_language_loader_spec.js
+++ b/spec/frontend/content_editor/services/code_block_language_loader_spec.js
@@ -53,47 +53,25 @@ describe('content_editor/services/code_block_language_loader', () => {
});
});
- describe('loadLanguages', () => {
+ describe('loadLanguage', () => {
it('loads highlight.js language packages identified by a list of languages', async () => {
- const languages = ['javascript', 'ruby'];
+ const language = 'javascript';
- await languageLoader.loadLanguages(languages);
+ await languageLoader.loadLanguage(language);
- languages.forEach((language) => {
- expect(lowlight.registerLanguage).toHaveBeenCalledWith(language, expect.any(Function));
- });
+ expect(lowlight.registerLanguage).toHaveBeenCalledWith(language, expect.any(Function));
});
describe('when language is already registered', () => {
it('does not load the language again', async () => {
- const languages = ['javascript'];
-
- await languageLoader.loadLanguages(languages);
- await languageLoader.loadLanguages(languages);
+ await languageLoader.loadLanguage('javascript');
+ await languageLoader.loadLanguage('javascript');
expect(lowlight.registerLanguage).toHaveBeenCalledTimes(1);
});
});
});
- describe('loadLanguagesFromDOM', () => {
- it('loads highlight.js language packages identified by pre tags in a DOM fragment', async () => {
- const parser = new DOMParser();
- const { body } = parser.parseFromString(
- `
- <pre lang="javascript"></pre>
- <pre lang="ruby"></pre>
- `,
- 'text/html',
- );
-
- await languageLoader.loadLanguagesFromDOM(body);
-
- expect(lowlight.registerLanguage).toHaveBeenCalledWith('javascript', expect.any(Function));
- expect(lowlight.registerLanguage).toHaveBeenCalledWith('ruby', expect.any(Function));
- });
- });
-
describe('loadLanguageFromInputRule', () => {
it('loads highlight.js language packages identified from the input rule', async () => {
const match = new RegExp(backtickInputRegex).exec('```js ');
@@ -112,7 +90,7 @@ describe('content_editor/services/code_block_language_loader', () => {
expect(languageLoader.isLanguageLoaded(language)).toBe(false);
- await languageLoader.loadLanguages([language]);
+ await languageLoader.loadLanguage(language);
expect(languageLoader.isLanguageLoaded(language)).toBe(true);
});
diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js
index 5b7a27b501d..a3553e612ca 100644
--- a/spec/frontend/content_editor/services/content_editor_spec.js
+++ b/spec/frontend/content_editor/services/content_editor_spec.js
@@ -11,7 +11,6 @@ describe('content_editor/services/content_editor', () => {
let contentEditor;
let serializer;
let deserializer;
- let languageLoader;
let eventHub;
let doc;
let p;
@@ -26,16 +25,14 @@ describe('content_editor/services/content_editor', () => {
tiptapEditor,
}));
- serializer = { deserialize: jest.fn() };
+ serializer = { serialize: jest.fn() };
deserializer = { deserialize: jest.fn() };
- languageLoader = { loadLanguagesFromDOM: jest.fn() };
eventHub = eventHubFactory();
contentEditor = new ContentEditor({
tiptapEditor,
serializer,
deserializer,
eventHub,
- languageLoader,
});
});
@@ -51,12 +48,12 @@ describe('content_editor/services/content_editor', () => {
describe('when setSerializedContent succeeds', () => {
let document;
- const dom = {};
+ const languages = ['javascript'];
const testMarkdown = '**bold text**';
beforeEach(() => {
document = doc(p('document'));
- deserializer.deserialize.mockResolvedValueOnce({ document, dom });
+ deserializer.deserialize.mockResolvedValueOnce({ document, languages });
});
it('emits loadingContent and loadingSuccess event in the eventHub', () => {
@@ -77,12 +74,6 @@ describe('content_editor/services/content_editor', () => {
expect(contentEditor.tiptapEditor.state.doc.toJSON()).toEqual(document.toJSON());
});
-
- it('passes deserialized DOM document to language loader', async () => {
- await contentEditor.setSerializedContent(testMarkdown);
-
- expect(languageLoader.loadLanguagesFromDOM).toHaveBeenCalledWith(dom);
- });
});
describe('when setSerializedContent fails', () => {
diff --git a/spec/frontend/content_editor/services/create_content_editor_spec.js b/spec/frontend/content_editor/services/create_content_editor_spec.js
index 6b2f28b3306..e1a30819ac8 100644
--- a/spec/frontend/content_editor/services/create_content_editor_spec.js
+++ b/spec/frontend/content_editor/services/create_content_editor_spec.js
@@ -1,8 +1,12 @@
import { PROVIDE_SERIALIZER_OR_RENDERER_ERROR } from '~/content_editor/constants';
import { createContentEditor } from '~/content_editor/services/create_content_editor';
+import createGlApiMarkdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer';
+import createRemarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestContentEditorExtension } from '../test_utils';
jest.mock('~/emoji');
+jest.mock('~/content_editor/services/remark_markdown_deserializer');
+jest.mock('~/content_editor/services/gl_api_markdown_deserializer');
describe('content_editor/services/create_content_editor', () => {
let renderMarkdown;
@@ -11,9 +15,36 @@ describe('content_editor/services/create_content_editor', () => {
beforeEach(() => {
renderMarkdown = jest.fn();
+ window.gon = {
+ features: {
+ preserveUnchangedMarkdown: false,
+ },
+ };
editor = createContentEditor({ renderMarkdown, uploadsPath });
});
+ describe('when preserveUnchangedMarkdown feature is on', () => {
+ beforeEach(() => {
+ window.gon.features.preserveUnchangedMarkdown = true;
+ });
+
+ it('provides a remark markdown deserializer to the content editor class', () => {
+ createContentEditor({ renderMarkdown, uploadsPath });
+ expect(createRemarkMarkdownDeserializer).toHaveBeenCalled();
+ });
+ });
+
+ describe('when preserveUnchangedMarkdown feature is off', () => {
+ beforeEach(() => {
+ window.gon.features.preserveUnchangedMarkdown = false;
+ });
+
+ it('provides a gl api markdown deserializer to the content editor class', () => {
+ createContentEditor({ renderMarkdown, uploadsPath });
+ expect(createGlApiMarkdownDeserializer).toHaveBeenCalledWith({ render: renderMarkdown });
+ });
+ });
+
it('sets gl-outline-0! class selector to the tiptapEditor instance', () => {
expect(editor.tiptapEditor.options.editorProps).toMatchObject({
attributes: {
@@ -22,30 +53,19 @@ describe('content_editor/services/create_content_editor', () => {
});
});
- it('provides the renderMarkdown function to the markdown serializer', async () => {
- const serializedContent = '**bold text**';
-
- renderMarkdown.mockReturnValueOnce('<p><b>bold text</b></p>');
-
- await editor.setSerializedContent(serializedContent);
-
- expect(renderMarkdown).toHaveBeenCalledWith(serializedContent);
- });
-
it('allows providing external content editor extensions', async () => {
const labelReference = 'this is a ~group::editor';
const { tiptapExtension, serializer } = createTestContentEditorExtension();
- renderMarkdown.mockReturnValueOnce(
- '<p>this is a <span data-reference="label" data-label-name="group::editor">group::editor</span></p>',
- );
editor = createContentEditor({
renderMarkdown,
extensions: [tiptapExtension],
serializerConfig: { nodes: { [tiptapExtension.name]: serializer } },
});
- await editor.setSerializedContent(labelReference);
+ editor.tiptapEditor.commands.setContent(
+ '<p>this is a <span data-reference="label" data-label-name="group::editor">group::editor</span></p>',
+ );
expect(editor.getSerializedContent()).toBe(labelReference);
});
diff --git a/spec/frontend/content_editor/services/markdown_deserializer_spec.js b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js
index bea43a0effc..5458a42532f 100644
--- a/spec/frontend/content_editor/services/markdown_deserializer_spec.js
+++ b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js
@@ -1,8 +1,8 @@
-import createMarkdownDeserializer from '~/content_editor/services/markdown_deserializer';
+import createMarkdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer';
import Bold from '~/content_editor/extensions/bold';
import { createTestEditor, createDocBuilder } from '../test_utils';
-describe('content_editor/services/markdown_deserializer', () => {
+describe('content_editor/services/gl_api_markdown_deserializer', () => {
let renderMarkdown;
let doc;
let p;
@@ -32,7 +32,9 @@ describe('content_editor/services/markdown_deserializer', () => {
beforeEach(async () => {
const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
- renderMarkdown.mockResolvedValueOnce(`<p><strong>${text}</strong></p>`);
+ renderMarkdown.mockResolvedValueOnce(
+ `<p><strong>${text}</strong></p><pre lang="javascript"></pre>`,
+ );
result = await deserializer.deserialize({
content: 'content',
@@ -40,13 +42,9 @@ describe('content_editor/services/markdown_deserializer', () => {
});
});
it('transforms HTML returned by render function to a ProseMirror document', async () => {
- const expectedDoc = doc(p(bold(text)));
+ const document = doc(p(bold(text)));
- expect(result.document.toJSON()).toEqual(expectedDoc.toJSON());
- });
-
- it('returns parsed HTML as a DOM object', () => {
- expect(result.dom.innerHTML).toEqual(`<p><strong>${text}</strong></p><!--content-->`);
+ expect(result.document.toJSON()).toEqual(document.toJSON());
});
});
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 2b76dc6c984..25b7483f234 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -24,6 +24,7 @@ import Link from '~/content_editor/extensions/link';
import ListItem from '~/content_editor/extensions/list_item';
import OrderedList from '~/content_editor/extensions/ordered_list';
import Paragraph from '~/content_editor/extensions/paragraph';
+import Sourcemap from '~/content_editor/extensions/sourcemap';
import Strike from '~/content_editor/extensions/strike';
import Table from '~/content_editor/extensions/table';
import TableCell from '~/content_editor/extensions/table_cell';
@@ -32,6 +33,7 @@ import TableRow from '~/content_editor/extensions/table_row';
import TaskItem from '~/content_editor/extensions/task_item';
import TaskList from '~/content_editor/extensions/task_list';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
+import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestEditor, createDocBuilder } from '../test_utils';
jest.mock('~/emoji');
@@ -63,6 +65,7 @@ const tiptapEditor = createTestEditor({
Link,
ListItem,
OrderedList,
+ Sourcemap,
Strike,
Table,
TableCell,
@@ -151,8 +154,7 @@ const {
const serialize = (...content) =>
markdownSerializer({}).serialize({
- schema: tiptapEditor.schema,
- content: doc(...content).toJSON(),
+ doc: doc(...content),
});
describe('markdownSerializer', () => {
@@ -1159,4 +1161,42 @@ Oranges are orange [^1]
`.trim(),
);
});
+
+ it.each`
+ mark | content | modifiedContent
+ ${'bold'} | ${'**bold**'} | ${'**bold modified**'}
+ ${'bold'} | ${'__bold__'} | ${'__bold modified__'}
+ ${'bold'} | ${'<strong>bold</strong>'} | ${'<strong>bold modified</strong>'}
+ ${'bold'} | ${'<b>bold</b>'} | ${'<b>bold modified</b>'}
+ ${'italic'} | ${'_italic_'} | ${'_italic modified_'}
+ ${'italic'} | ${'*italic*'} | ${'*italic modified*'}
+ ${'italic'} | ${'<em>italic</em>'} | ${'<em>italic modified</em>'}
+ ${'italic'} | ${'<i>italic</i>'} | ${'<i>italic modified</i>'}
+ ${'link'} | ${'[gitlab](https://gitlab.com)'} | ${'[gitlab modified](https://gitlab.com)'}
+ ${'link'} | ${'<a href="https://gitlab.com">link</a>'} | ${'<a href="https://gitlab.com">link modified</a>'}
+ ${'code'} | ${'`code`'} | ${'`code modified`'}
+ ${'code'} | ${'<code>code</code>'} | ${'<code>code modified</code>'}
+ `(
+ 'preserves original $mark syntax when sourceMarkdown is available',
+ async ({ content, modifiedContent }) => {
+ const { document } = await remarkMarkdownDeserializer().deserialize({
+ schema: tiptapEditor.schema,
+ content,
+ });
+
+ tiptapEditor
+ .chain()
+ .setContent(document.toJSON())
+ // changing the document ensures that block preservation doesn’t yield false positives
+ .insertContent(' modified')
+ .run();
+
+ const serialized = markdownSerializer({}).serialize({
+ pristineDoc: document,
+ doc: tiptapEditor.state.doc,
+ });
+
+ expect(serialized).toEqual(modifiedContent);
+ },
+ );
});
diff --git a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js
index abd9588daff..8a304c73163 100644
--- a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js
+++ b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js
@@ -2,7 +2,7 @@ import { Extension } from '@tiptap/core';
import BulletList from '~/content_editor/extensions/bullet_list';
import ListItem from '~/content_editor/extensions/list_item';
import Paragraph from '~/content_editor/extensions/paragraph';
-import markdownDeserializer from '~/content_editor/services/markdown_deserializer';
+import markdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer';
import { getMarkdownSource, getFullSource } from '~/content_editor/services/markdown_sourcemap';
import { createTestEditor, createDocBuilder } from '../test_utils';