Commit 0790bb72 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'original-url-tests-markdown' into 'master'

Add support for data-canonical-src in content editor links + add tests

See merge request gitlab-org/gitlab!64194
parents b324db38 3a0d395c
......@@ -43,14 +43,22 @@ export default {
},
mounted() {
this.tiptapEditor.on('selectionUpdate', ({ editor }) => {
const { href } = editor.getAttributes(linkContentType);
const { 'data-canonical-src': canonicalSrc, href } = editor.getAttributes(linkContentType);
this.linkHref = href;
this.linkHref = canonicalSrc || href;
});
},
methods: {
updateLink() {
this.tiptapEditor.chain().focus().unsetLink().setLink({ href: this.linkHref }).run();
this.tiptapEditor
.chain()
.focus()
.unsetLink()
.setLink({
href: this.linkHref,
'data-canonical-src': this.linkHref,
})
.run();
this.$emit('execute', { contentType: linkContentType });
},
......
import { markInputRule } from '@tiptap/core';
import { Link } from '@tiptap/extension-link';
import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown';
export const markdownLinkSyntaxInputRuleRegExp = /(?:^|\s)\[([\w|\s|-]+)\]\((?<href>.+?)\)$/gm;
export const urlSyntaxRegExp = /(?:^|\s)(?<href>(?:https?:\/\/|www\.)[\S]+)(?:\s|\n)$/gim;
const extractHrefFromMatch = (match) => {
......@@ -29,8 +27,37 @@ export const tiptapExtension = Link.extend({
markInputRule(urlSyntaxRegExp, this.type, extractHrefFromMatch),
];
},
addAttributes() {
return {
...this.parent?.(),
href: {
default: null,
parseHTML: (element) => {
return {
href: element.getAttribute('href'),
};
},
},
'data-canonical-src': {
default: null,
parseHTML: (element) => {
return {
href: element.dataset.canonicalSrc,
};
},
},
};
},
}).configure({
openOnClick: false,
});
export const serializer = defaultMarkdownSerializer.marks.link;
export const serializer = {
open() {
return '[';
},
close(state, mark) {
const href = mark.attrs['data-canonical-src'] || mark.attrs.href;
return `](${state.esc(href)}${mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ''})`;
},
};
import { GlDropdown, GlDropdownDivider, GlFormInputGroup, GlButton } from '@gitlab/ui';
import { GlDropdown, GlDropdownDivider, GlButton, GlFormInputGroup } 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';
......@@ -16,9 +16,6 @@ describe('content_editor/components/toolbar_link_button', () => {
propsData: {
tiptapEditor: editor,
},
stubs: {
GlFormInputGroup,
},
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
......@@ -45,9 +42,8 @@ describe('content_editor/components/toolbar_link_button', () => {
});
describe('when there is an active link', () => {
beforeEach(() => {
jest.spyOn(editor, 'isActive');
editor.isActive.mockReturnValueOnce(true);
beforeEach(async () => {
jest.spyOn(editor, 'isActive').mockReturnValueOnce(true);
buildWrapper();
});
......@@ -78,9 +74,35 @@ describe('content_editor/components/toolbar_link_button', () => {
expect(commands.focus).toHaveBeenCalled();
expect(commands.unsetLink).toHaveBeenCalled();
expect(commands.setLink).toHaveBeenCalledWith({ href: 'https://example' });
expect(commands.setLink).toHaveBeenCalledWith({
href: 'https://example',
'data-canonical-src': 'https://example',
});
expect(commands.run).toHaveBeenCalled();
});
describe('on selection update', () => {
it('updates link input box with canonical-src if present', async () => {
jest.spyOn(editor, 'getAttributes').mockReturnValueOnce({
'data-canonical-src': 'uploads/my-file.zip',
href: '/username/my-project/uploads/abcdefgh133535/my-file.zip',
});
await editor.emit('selectionUpdate', { editor });
expect(findLinkURLInput().element.value).toEqual('uploads/my-file.zip');
});
it('updates link input box with link href otherwise', async () => {
jest.spyOn(editor, 'getAttributes').mockReturnValueOnce({
href: 'https://gitlab.com',
});
await editor.emit('selectionUpdate', { editor });
expect(findLinkURLInput().element.value).toEqual('https://gitlab.com');
});
});
});
describe('when there is not an active link', () => {
......@@ -106,7 +128,10 @@ describe('content_editor/components/toolbar_link_button', () => {
await findApplyLinkButton().trigger('click');
expect(commands.focus).toHaveBeenCalled();
expect(commands.setLink).toHaveBeenCalledWith({ href: 'https://example' });
expect(commands.setLink).toHaveBeenCalledWith({
href: 'https://example',
'data-canonical-src': 'https://example',
});
expect(commands.run).toHaveBeenCalled();
});
});
......
import fs from 'fs';
import path from 'path';
import jsYaml from 'js-yaml';
import { toArray } from 'lodash';
import { getJSONFixture } from 'helpers/fixtures';
export const loadMarkdownApiResult = (testName) => {
......@@ -15,5 +14,5 @@ export const loadMarkdownApiExamples = () => {
const apiMarkdownYamlText = fs.readFileSync(apiMarkdownYamlPath);
const apiMarkdownExampleObjects = jsYaml.safeLoad(apiMarkdownYamlText);
return apiMarkdownExampleObjects.map((example) => toArray(example));
return apiMarkdownExampleObjects.map(({ name, context, markdown }) => [name, context, markdown]);
};
......@@ -3,11 +3,15 @@ import { loadMarkdownApiExamples, loadMarkdownApiResult } from './markdown_proce
describe('markdown processing', () => {
// Ensure we generate same markdown that was provided to Markdown API.
it.each(loadMarkdownApiExamples())('correctly handles %s', async (testName, markdown) => {
const { html } = loadMarkdownApiResult(testName);
const contentEditor = createContentEditor({ renderMarkdown: () => html });
await contentEditor.setSerializedContent(markdown);
it.each(loadMarkdownApiExamples())(
'correctly handles %s (context: %s)',
async (name, context, markdown) => {
const testName = context ? `${context}_${name}` : name;
const { html, body } = loadMarkdownApiResult(testName);
const contentEditor = createContentEditor({ renderMarkdown: () => html || body });
await contentEditor.setSerializedContent(markdown);
expect(contentEditor.getSerializedContent()).toBe(markdown);
});
expect(contentEditor.getSerializedContent()).toBe(markdown);
},
);
});
......@@ -4,12 +4,32 @@ require 'spec_helper'
RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
include ApiHelpers
include WikiHelpers
include JavaScriptFixturesHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, :repository, group: group) }
let_it_be(:group_wiki) { create(:group_wiki, user: user) }
let_it_be(:project_wiki) { create(:project_wiki, user: user) }
let(:group_wiki_page) { create(:wiki_page, wiki: group_wiki) }
let(:project_wiki_page) { create(:wiki_page, wiki: project_wiki) }
fixture_subdir = 'api/markdown'
before(:all) do
clean_frontend_fixtures(fixture_subdir)
group.add_owner(user)
project.add_maintainer(user)
end
before do
stub_group_wikis(true)
sign_in(user)
end
markdown_examples = begin
......@@ -19,14 +39,29 @@ RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
end
markdown_examples.each do |markdown_example|
context = markdown_example.fetch(:context, '')
name = markdown_example.fetch(:name)
context "for #{name}" do
context "for #{name}#{!context.empty? ? " (context: #{context})" : ''}" do
let(:markdown) { markdown_example.fetch(:markdown) }
name = "#{context}_#{name}" unless context.empty?
it "#{fixture_subdir}/#{name}.json" do
post api("/markdown"), params: { text: markdown, gfm: true }
api_url = case context
when 'project'
"/#{project.full_path}/preview_markdown"
when 'group'
"/groups/#{group.full_path}/preview_markdown"
when 'project_wiki'
"/#{project.full_path}/-/wikis/#{project_wiki_page.slug}/preview_markdown"
when 'group_wiki'
"/groups/#{group.full_path}/-/wikis/#{group_wiki_page.slug}/preview_markdown"
else
api "/markdown"
end
post api_url, params: { text: markdown, gfm: true }
expect(response).to be_successful
end
end
......
......@@ -14,6 +14,18 @@
markdown: '---'
- name: link
markdown: '[GitLab](https://gitlab.com)'
- name: attachment_link
context: project_wiki
markdown: '[test-file](test-file.zip)'
- name: attachment_link
context: project
markdown: '[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)'
- name: attachment_link
context: group_wiki
markdown: '[test-file](test-file.zip)'
- name: attachment_link
context: group
markdown: '[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)'
- name: code_block
markdown: |-
```javascript
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment