Commit 132cc71d authored by Stan Hu's avatar Stan Hu

Merge branch...

Merge branch '338268-refactor-golden-master-spec-approach-for-testing-gitlab-flavored-markdown-support-in-the' into 'master'

Refactor Golden Master spec approach for testing GFM support

See merge request gitlab-org/gitlab!68671
parents c5cb8ce8 2518ceeb
# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation.
---
- name: attachment_link_for_group_wiki
api_context: group_wiki
substitutions:
# NOTE: We don't care about verifying specific attribute values here, that should be the
# responsibility of unit tests. These tests are about the structure of the HTML.
path_attribute_id_substitution:
- regex: '(group|project)(\d+)'
replacement: '\1ID'
markdown: |-
[test-file](test-file.zip)
html: |-
<p data-sourcepos="1:1-1:26" dir="auto"><a href="/groups/group2/-/wikis/test-file.zip" data-canonical-src="test-file.zip">test-file</a></p>
import path from 'path';
import {
createSharedExamples,
loadMarkdownApiExamples,
} from 'jest/content_editor/markdown_processing_spec_helper';
jest.mock('~/emoji');
// See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
describe('EE markdown processing in ContentEditor', () => {
// Ensure we generate same markdown that was provided to Markdown API.
const markdownYamlPath = path.join(
__dirname,
'..',
'..',
'fixtures',
'markdown',
'markdown_golden_master_examples.yml',
);
// eslint-disable-next-line jest/valid-describe
describe.each(loadMarkdownApiExamples(markdownYamlPath))('%s', createSharedExamples);
});
# frozen_string_literal: true
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(:group_wiki) { create(:group_wiki, user: user) }
let(:group_wiki_page) { create(:wiki_page, wiki: group_wiki) }
let(:project_wiki_page) { create(:wiki_page, wiki: project_wiki) }
before(:all) do
group.add_owner(user)
end
before do
stub_group_wikis(true)
sign_in(user)
end
markdown_examples = begin
yaml_file_path = File.expand_path('api_markdown.yml', __dir__)
yaml = File.read(yaml_file_path)
YAML.safe_load(yaml, symbolize_names: true)
end
markdown_examples.each do |markdown_example|
context = markdown_example.fetch(:context, '')
name = markdown_example.fetch(:name)
context "for #{name}#{!context.empty? ? " (context: #{context})" : ''}" do
let(:markdown) { markdown_example.fetch(:markdown) }
name = "#{context}_#{name}" unless context.empty?
it "api/markdown/#{name}.json" do
api_url = case context
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
end
end
# This data file drives the specs in
# ee/spec/frontend/fixtures/api_markdown.rb and
---
- name: attachment_link
context: group_wiki
markdown: '[test-file](test-file.zip)'
# frozen_string_literal: true
require 'spec_helper'
# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
RSpec.describe API::Markdown, 'EE Golden Master' do
include WikiHelpers
let_it_be(:user) { create(:user, username: 'gfm_ee_user') }
let_it_be(:group_wiki) { create(:group_wiki, user: user) }
let_it_be(:group_wiki_page) { create(:wiki_page, wiki: group_wiki) }
before do
stub_group_wikis(true)
sign_in(user)
end
markdown_yml_file_path = File.expand_path('../../fixtures/markdown/markdown_golden_master_examples.yml', __dir__)
include_examples 'API::Markdown Golden Master shared context', markdown_yml_file_path do
extend ::Gitlab::Utils::Override
override :supported_api_contexts
def supported_api_contexts
super + %w(group_wiki)
end
override :get_url_for_api_context
def get_url_for_api_context(api_context)
case api_context
when 'group_wiki'
"/groups/#{group.full_path}/-/wikis/#{group_wiki_page.slug}/preview_markdown"
else
super
end
end
end
end
# Related Specs:
#
# This data file drives the specs in the following specs:
#
# CE Backend: spec/requests/api/markdown_golden_master_spec.rb
# CE Frontend: spec/frontend/content_editor/markdown_processing_spec.js
#
# For EE, these files are used:
# EE Data: ee/spec/fixtures/markdown/markdown_golden_master_examples.yml
# EE Backend: ee/spec/requests/api/markdown_golden_master_spec.rb
# EE Frontend: ee/spec/frontend/content_editor/markdown_processing_spec.js
#
#
# Requirements:
#
# 1. Frontend: We should have test coverage that the Content Editor can properly serialize HTML
# to Markdown for all GFM source elements which it currently supports.
# 2. Frontend: We should have test coverage that the Content Editor can properly render the expected
# HTML for all GFM source elements which it currently supports (not currently implemented in the
# frontend - this will likely be a standalone module outside of the Content Editor).
# 3. Backend: We should ensure that for all GFM elements, the backend always renders the expected
# HTML, for **all** supported GFM source elements.
#
# If any of this this ever changes unexpectedly, tests will start failing, and force the same change
# to be made on the backend and frontend.
#
#
# Overview:
#
# These specs ensure that the bidirectional Markdown <-> HTML conversion logic is implemented
# identically on the backend and frontend, for all supported GitLab-Flavored Markdown examples, by
# running hardcoded examples through the logic and ensuring the results match.
#
# This is an example of the "Golden Master Testing" approach, which is also referred to as
# "Approval Testing" or "Characterization Testing".
#
# The term "Golden Master" originally comes from the recording industry, and refers to process
# of "mastering", or making a final mix from which all other copies will be produced.
#
# See:
# - https://en.wikipedia.org/wiki/Characterization_test
# - https://en.wikipedia.org/wiki/Gold_master_(disambiguation)
#
#
# What we are doing is actually a type Golden Master testing with modifications:
#
# 1. The original markdown examples used to drive the tests are taken from this YAML, and can be
# considered a form of "fixture" in this case.
# 2. The HTML in the YAML is the "Golden Master", but we are going to use it to assert
# against **TWO** different implementations of markdown rendering:
# 1. The frontend, implemented as Jest specs.
# 1. This will assert both HTML -> markdown serialization (what it currently does), as well as...
# 2. Markdown -> HTML rendering (not currently implemented in the frontend - this will likely
# be a standalone module outside of the Content Editor)
# 1. The backend, implemented as requests specs
# 1. This will assert markdown -> HTML conversion by the backend.
#
# Also see the MR for more explanation on the details of this approach:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68671
#
#
# Usage:
#
# * Please keep this file alphabetized.
# * To run focused example(s), set the `FOCUSED_MARKDOWN_EXAMPLES` environment variable to a
# comma-separated list of example names. This works for the frontend and backend specs.
# * Required attributes for every example:
# 1. `name`: Specifies the Name of the example, which will be printed when specs are run.
# 2. `markdown`: Specifies the Markdown for the example, which will be compared with the
# Markdown the code generates from the corresponding specified HTML.
# 3. `html`: Specifies the HTML for the example, which will be compared with the
# HTML the code generated from the corresponding specified Markdown.
# * `api_context` (optional): This is used when a single markdown can be
# rendered differently depending on the API endpoint or area of the app from which it is called or
# used. The valid values for `api_context` are: `project`, `group`, `project_wiki`,
# and (for EE only) `group_wiki`.The `name` attribute must also have a `_for_[API_CONTEXT]` suffix
# which matches the `api_context`, in order to ensure that each example has a unique `name`
# identifier. For example, `attachment_image_for_project`.
# * `pending`: To skip an example that is broken or not yet fully implemented, add
# a `pending: <reason with issue/MR URL>` attribute to the example. See
# the `a_example_of_pending` entry for an example.
# * `pending` with key: You can also mark an example pending on only the frontend or backend. See
# the `a_example_of_pending_with_keys` entry for an example.
# * `substitutions`: For examples which may have variable content in different environments,
# such as portions of the URI, or database record IDs, you can specify
# `substitutions`, which is an array of regex/replacement pairs. The HTML
# value will be normalized with each of these pairs using Ruby `gsub`
# before comparing.
# The substitution values can (and are) also reused in multiple examples
# via YAML anchors.
#
#
# Notes:
#
# * The html values should exactly match what the backend markdown API endpoints return for the
# given markdown example. The HTML is intentionally not indented, formatted, or split across lines.
# This is a bit less readable, but it makes the spec logic simpler and less error prone for edge
# cases.
#
#
# Debugging Failures and Writing New Entries:
#
# * You need to compare what is different between the expected and actual values.
# * In rspec, the diff printed out includes the full text of the HTML. This may be long, so you
# may want to turn line wrapping on or off or copy the diff to separate file(s) for easier comparison.
# * If the difference is just in an attribute value, use the `substitutions` support to normalize
# the HTML before comparing. These specs are only validating the HTML structure, the individual
# markdown elements' unit tests can provide coverage that the exact attribute values are correct.
# * If you are making a new entry, you can create the entry according to the `Usage` section above,
# but leave the `html` value blank. This will cause the spec to fail, and you can fill in the
# `html` value based on the spec failure that is printed out.
---
#- name: an_example_of_pending
# pending: 'This is an example of the pending attribute: http://example.com'
# markdown: ;)
# html: |-
# <blink data-sourcepos="1:1-1:2"/></blink>
#
#- name: an_example_of_pending_with_keys
# pending:
# frontend: 'This is an example of the frontend-only pending attribute: http://example.com'
# backend: 'This is an example of the backend-only pending attribute: http://example.com'
# markdown: ;)
# html: |-
# <blink data-sourcepos="1:1-1:2"/></blink>
- name: attachment_image_for_group
api_context: group
substitutions:
# Note: having the top level `substitutions` data structure be a hash of arrays
# allows us to compose multiple substitutions via YAML anchors (YAML anchors
# pointing to arrays can't be combined)
uri_substitution: &uri_substitution
# NOTE: We don't care about verifying specific attribute values here, that should be the
# responsibility of unit tests. These tests are about the structure of the HTML.
- regex: '(href|data-src)(=")(.*?)(test-file\.(png|zip)")'
replacement: '\1\2URI_PREFIX\4'
markdown: |-
![test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png)
html: |-
<p data-sourcepos="1:1-1:69" dir="auto"><a class="no-attachment-icon gfm" href="/groups/group58/-/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-link="true"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="test-file" class="lazy gfm" data-src="/groups/group58/-/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png"></a></p>
- name: attachment_image_for_project
api_context: project
substitutions:
uri_substitution: *uri_substitution
markdown: |-
![test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png)
html: |-
<p data-sourcepos="1:1-1:69" dir="auto"><a class="no-attachment-icon gfm" href="/group58/project22/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-link="true"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="test-file" class="lazy gfm" data-src="/group58/project22/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png"></a></p>
- name: attachment_image_for_project_wiki
api_context: project_wiki
substitutions:
uri_substitution: *uri_substitution
markdown: |-
![test-file](test-file.png)
html: |-
<p data-sourcepos="1:1-1:27" dir="auto"><a class="no-attachment-icon" href="/group1/project1/-/wikis/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="test-file.png"><img alt="test-file" class="lazy" data-src="/group1/project1/-/wikis/test-file.png" data-canonical-src="test-file.png"></a></p>
- name: attachment_link_for_group
api_context: group
substitutions:
uri_substitution: *uri_substitution
markdown: |-
[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)
html: |-
<p data-sourcepos="1:1-1:68" dir="auto"><a href="/groups/group58/-/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-link="true" class="gfm">test-file</a></p>
- name: attachment_link_for_project
api_context: project
substitutions:
uri_substitution: *uri_substitution
markdown: |-
[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)
html: |-
<p data-sourcepos="1:1-1:68" dir="auto"><a href="/group58/project22/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-canonical-src="/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip" data-link="true" class="gfm">test-file</a></p>
- name: attachment_link_for_project_wiki
api_context: project_wiki
substitutions:
uri_substitution: *uri_substitution
# TODO: The current frontend example doesn't include the path, need to look into why it does after refactoring to the new golden master approach
pending:
frontend: 'The current frontend example doesnt include the path, need to look into why it does after refactoring to the new golden master approach'
markdown: |-
[test-file](test-file.zip)
html: |-
<p data-sourcepos="1:1-1:26" dir="auto"><a href="/group1/project1/-/wikis/test-file.zip" data-canonical-src="test-file.zip">test-file</a></p>
- name: audio
markdown: |-
![Sample Audio](https://gitlab.com/gitlab.mp3)
html: |-
<p data-sourcepos="1:1-1:46" dir="auto"><span class="media-container audio-container"><audio src="https://gitlab.com/gitlab.mp3" controls="true" data-setup="{}" data-title="Sample Audio"></audio><a href="https://gitlab.com/gitlab.mp3" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Audio'">Sample Audio</a></span></p>
- name: audio_and_video_in_lists
markdown: |-
* ![Sample Audio](https://gitlab.com/1.mp3)
* ![Sample Video](https://gitlab.com/2.mp4)
1. ![Sample Video](https://gitlab.com/1.mp4)
2. ![Sample Audio](https://gitlab.com/2.mp3)
* [x] ![Sample Audio](https://gitlab.com/1.mp3)
* [x] ![Sample Audio](https://gitlab.com/2.mp3)
* [x] ![Sample Video](https://gitlab.com/3.mp4)
html: |-
<ul data-sourcepos="1:1-3:0" dir="auto">
<li data-sourcepos="1:1-1:43"><span class="media-container audio-container"><audio src="https://gitlab.com/1.mp3" controls="true" data-setup="{}" data-title="Sample Audio"></audio><a href="https://gitlab.com/1.mp3" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Audio'">Sample Audio</a></span></li>
<li data-sourcepos="2:1-3:0"><span class="media-container video-container"><video src="https://gitlab.com/2.mp4" controls="true" data-setup="{}" data-title="Sample Video" width="400" preload="metadata"></video><a href="https://gitlab.com/2.mp4" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Video'">Sample Video</a></span></li>
</ul>
<ol data-sourcepos="4:1-6:0" dir="auto">
<li data-sourcepos="4:1-4:44"><span class="media-container video-container"><video src="https://gitlab.com/1.mp4" controls="true" data-setup="{}" data-title="Sample Video" width="400" preload="metadata"></video><a href="https://gitlab.com/1.mp4" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Video'">Sample Video</a></span></li>
<li data-sourcepos="5:1-6:0"><span class="media-container audio-container"><audio src="https://gitlab.com/2.mp3" controls="true" data-setup="{}" data-title="Sample Audio"></audio><a href="https://gitlab.com/2.mp3" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Audio'">Sample Audio</a></span></li>
</ol>
<ul data-sourcepos="7:1-9:47" class="task-list" dir="auto">
<li data-sourcepos="7:1-7:47" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> <span class="media-container audio-container"><audio src="https://gitlab.com/1.mp3" controls="true" data-setup="{}" data-title="Sample Audio"></audio><a href="https://gitlab.com/1.mp3" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Audio'">Sample Audio</a></span>
</li>
<li data-sourcepos="8:1-8:47" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> <span class="media-container audio-container"><audio src="https://gitlab.com/2.mp3" controls="true" data-setup="{}" data-title="Sample Audio"></audio><a href="https://gitlab.com/2.mp3" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Audio'">Sample Audio</a></span>
</li>
<li data-sourcepos="9:1-9:47" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> <span class="media-container video-container"><video src="https://gitlab.com/3.mp4" controls="true" data-setup="{}" data-title="Sample Video" width="400" preload="metadata"></video><a href="https://gitlab.com/3.mp4" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Video'">Sample Video</a></span>
</li>
</ul>
- name: blockquote
markdown: |-
> This is a blockquote
>
> This is another one
html: |-
<blockquote data-sourcepos="1:1-3:21" dir="auto">
<p data-sourcepos="1:3-1:22">This is a blockquote</p>
<p data-sourcepos="3:3-3:21">This is another one</p>
</blockquote>
- name: bold
markdown: |-
**bold**
html: |-
<p data-sourcepos="1:1-1:8" dir="auto"><strong>bold</strong></p>
- name: bullet_list_style_1
markdown: |-
* list item 1
* list item 2
* embedded list item 3
html: |-
<ul data-sourcepos="1:1-3:24" dir="auto">
<li data-sourcepos="1:1-1:13">list item 1</li>
<li data-sourcepos="2:1-3:24">list item 2
<ul data-sourcepos="3:3-3:24">
<li data-sourcepos="3:3-3:24">embedded list item 3</li>
</ul>
</li>
</ul>
- name: bullet_list_style_2
markdown: |-
- list item 1
- list item 2
* embedded list item 3
html: |-
<ul data-sourcepos="1:1-3:24" dir="auto">
<li data-sourcepos="1:1-1:13">list item 1</li>
<li data-sourcepos="2:1-3:24">list item 2
<ul data-sourcepos="3:3-3:24">
<li data-sourcepos="3:3-3:24">embedded list item 3</li>
</ul>
</li>
</ul>
- name: bullet_list_style_3
markdown: |-
+ list item 1
+ list item 2
- embedded list item 3
html: |-
<ul data-sourcepos="1:1-3:24" dir="auto">
<li data-sourcepos="1:1-1:13">list item 1</li>
<li data-sourcepos="2:1-3:24">list item 2
<ul data-sourcepos="3:3-3:24">
<li data-sourcepos="3:3-3:24">embedded list item 3</li>
</ul>
</li>
</ul>
- name: code_block
markdown: |-
```javascript
console.log('hello world')
```
html: |-
<pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
- name: color_chips
markdown: |-
- `#F00`
- `#F00A`
- `#FF0000`
- `#FF0000AA`
- `RGB(0,255,0)`
- `RGB(0%,100%,0%)`
- `RGBA(0,255,0,0.3)`
- `HSL(540,70%,50%)`
- `HSLA(540,70%,50%,0.3)`
html: |-
<ul data-sourcepos="1:1-9:25" dir="auto">
<li data-sourcepos="1:1-1:8"><code>#F00<span class="gfm-color_chip"><span style="background-color: #F00;"></span></span></code></li>
<li data-sourcepos="2:1-2:9"><code>#F00A<span class="gfm-color_chip"><span style="background-color: #F00A;"></span></span></code></li>
<li data-sourcepos="3:1-3:11"><code>#FF0000<span class="gfm-color_chip"><span style="background-color: #FF0000;"></span></span></code></li>
<li data-sourcepos="4:1-4:13"><code>#FF0000AA<span class="gfm-color_chip"><span style="background-color: #FF0000AA;"></span></span></code></li>
<li data-sourcepos="5:1-5:16"><code>RGB(0,255,0)<span class="gfm-color_chip"><span style="background-color: RGB(0,255,0);"></span></span></code></li>
<li data-sourcepos="6:1-6:19"><code>RGB(0%,100%,0%)<span class="gfm-color_chip"><span style="background-color: RGB(0%,100%,0%);"></span></span></code></li>
<li data-sourcepos="7:1-7:21"><code>RGBA(0,255,0,0.3)<span class="gfm-color_chip"><span style="background-color: RGBA(0,255,0,0.3);"></span></span></code></li>
<li data-sourcepos="8:1-8:20"><code>HSL(540,70%,50%)<span class="gfm-color_chip"><span style="background-color: HSL(540,70%,50%);"></span></span></code></li>
<li data-sourcepos="9:1-9:25"><code>HSLA(540,70%,50%,0.3)<span class="gfm-color_chip"><span style="background-color: HSLA(540,70%,50%,0.3);"></span></span></code></li>
</ul>
- name: description_list
markdown: |-
<dl>
<dt>Frog</dt>
<dd>Wet green thing</dd>
<dt>Rabbit</dt>
<dd>Warm fluffy thing</dd>
<dt>Punt</dt>
<dd>Kick a ball</dd>
<dd>Take a bet</dd>
<dt>Color</dt>
<dt>Colour</dt>
<dd>
Any hue except _white_ or **black**
</dd>
</dl>
html: |-
<dl>
<dt>Frog</dt>
<dd>Wet green thing</dd>
<dt>Rabbit</dt>
<dd>Warm fluffy thing</dd>
<dt>Punt</dt>
<dd>Kick a ball</dd>
<dd>Take a bet</dd>
<dt>Color</dt>
<dt>Colour</dt>
<dd>
<p data-sourcepos="13:1-13:35">Any hue except <em>white</em> or <strong>black</strong></p>
</dd>
</dl>
- name: details
markdown: |-
<details>
<summary>This is the visible summary of the collapsible section</summary>
1. collapsed markdown
2. more collapsed markdown
</details>
html: |-
<details>
<summary>This is the visible summary of the collapsible section</summary>
<ol data-sourcepos="4:1-6:0">
<li data-sourcepos="4:1-4:21">collapsed markdown</li>
<li data-sourcepos="5:1-6:0">more collapsed markdown</li>
</ol>
</details>
- name: div
markdown: |-
<div>plain text</div>
<div>
just a plain ol' div, not much to _expect_!
</div>
html: |-
<div>plain text</div>
<div>
<p data-sourcepos="4:1-4:43">just a plain ol' div, not much to <em>expect</em>!</p>
</div>
- name: emoji
markdown: |-
:sparkles: :heart: :100:
html: |-
<p data-sourcepos="1:1-1:24" dir="auto"><gl-emoji title="sparkles" data-name="sparkles" data-unicode-version="6.0">✨</gl-emoji> <gl-emoji title="heavy black heart" data-name="heart" data-unicode-version="1.1">❤</gl-emoji> <gl-emoji title="hundred points symbol" data-name="100" data-unicode-version="6.0">💯</gl-emoji></p>
- name: emphasis
markdown: _emphasized text_
html: <p data-sourcepos="1:1-1:17" dir="auto"><em>emphasized text</em></p>
- name: figure
markdown: |-
<figure>
![Elephant at sunset](elephant-sunset.jpg)
<figcaption>An elephant at sunset</figcaption>
</figure>
<figure>
![A crocodile wearing crocs](croc-crocs.jpg)
<figcaption>
A crocodile wearing _crocs_!
</figcaption>
</figure>
html: |-
<figure>
<p data-sourcepos="3:1-3:42"><a class="no-attachment-icon" href="elephant-sunset.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Elephant at sunset" class="lazy" data-src="elephant-sunset.jpg"></a></p>
<figcaption>An elephant at sunset</figcaption>
</figure>
<figure>
<p data-sourcepos="9:1-9:44"><a class="no-attachment-icon" href="croc-crocs.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A crocodile wearing crocs" class="lazy" data-src="croc-crocs.jpg"></a></p>
<figcaption>
<p data-sourcepos="13:1-13:28">A crocodile wearing <em>crocs</em>!</p>
</figcaption>
</figure>
- name: frontmatter_json
markdown: |-
;;;
{
"title": "Page title"
}
;;;
html: |-
<pre data-sourcepos="1:1-5:3" class="code highlight js-syntax-highlight language-json" lang="json" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="json"><span class="p">{</span></span>
<span id="LC2" class="line" lang="json"><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Page title"</span></span>
<span id="LC3" class="line" lang="json"><span class="p">}</span></span></code></pre>
- name: frontmatter_toml
markdown: |-
+++
title = "Page title"
+++
html: <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-toml" lang="toml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="toml"><span class="py">title</span> <span class="p">=</span> <span class="s">"Page title"</span></span></code></pre>
- name: frontmatter_yaml
markdown: |-
---
title: Page title
---
html: <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">Page title</span></span></code></pre>
- name: hard_break
markdown: |-
This is a line after a\
hard break
html: |-
<p data-sourcepos="1:1-2:10" dir="auto">This is a line after a<br>
hard break</p>
- name: headings
markdown: |-
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
html: |-
<h1 data-sourcepos="1:1-1:11" dir="auto">
<a id="user-content-heading-1" class="anchor" href="#heading-1" aria-hidden="true"></a>Heading 1</h1>
<h2 data-sourcepos="3:1-3:12" dir="auto">
<a id="user-content-heading-2" class="anchor" href="#heading-2" aria-hidden="true"></a>Heading 2</h2>
<h3 data-sourcepos="5:1-5:13" dir="auto">
<a id="user-content-heading-3" class="anchor" href="#heading-3" aria-hidden="true"></a>Heading 3</h3>
<h4 data-sourcepos="7:1-7:14" dir="auto">
<a id="user-content-heading-4" class="anchor" href="#heading-4" aria-hidden="true"></a>Heading 4</h4>
<h5 data-sourcepos="9:1-9:15" dir="auto">
<a id="user-content-heading-5" class="anchor" href="#heading-5" aria-hidden="true"></a>Heading 5</h5>
<h6 data-sourcepos="11:1-11:16" dir="auto">
<a id="user-content-heading-6" class="anchor" href="#heading-6" aria-hidden="true"></a>Heading 6</h6>
- name: horizontal_rule
markdown: |-
---
html: |-
<hr data-sourcepos="1:1-1:3">
- name: html_marks
markdown: |-
* Content editor is ~~great~~<ins>amazing</ins>.
* If the changes <abbr title="Looks good to merge">LGTM</abbr>, please <abbr title="Merge when pipeline succeeds">MWPS</abbr>.
* The English song <q>Oh I do like to be beside the seaside</q> looks like this in Hebrew: <span dir="rtl">אה, אני אוהב להיות ליד חוף הים</span>. In the computer's memory, this is stored as <bdo dir="ltr">אה, אני אוהב להיות ליד חוף הים</bdo>.
* <cite>The Scream</cite> by Edvard Munch. Painted in 1893.
* <dfn>HTML</dfn> is the standard markup language for creating web pages.
* Do not forget to buy <mark>milk</mark> today.
* This is a paragraph and <small>smaller text goes here</small>.
* The concert starts at <time datetime="20:00">20:00</time> and you'll be able to enjoy the band for at least <time datetime="PT2H30M">2h 30m</time>.
* Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy text (Windows).
* WWF's goal is to: <q>Build a future where people live in harmony with nature.</q> We hope they succeed.
* The error occured was: <samp>Keyboard not found. Press F1 to continue.</samp>
* The area of a triangle is: 1/2 x <var>b</var> x <var>h</var>, where <var>b</var> is the base, and <var>h</var> is the vertical height.
* <ruby>漢<rt>ㄏㄢˋ</rt></ruby>
* C<sub>7</sub>H<sub>16</sub> + O<sub>2</sub> → CO<sub>2</sub> + H<sub>2</sub>O
* The **Pythagorean theorem** is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>
html: |-
<ul data-sourcepos="1:1-15:130" dir="auto">
<li data-sourcepos="1:1-1:48">Content editor is <del>great</del><ins>amazing</ins>.</li>
<li data-sourcepos="2:1-2:126">If the changes <abbr title="Looks good to merge">LGTM</abbr>, please <abbr title="Merge when pipeline succeeds">MWPS</abbr>.</li>
<li data-sourcepos="3:1-3:288">The English song <q>Oh I do like to be beside the seaside</q> looks like this in Hebrew: <span dir="rtl">אה, אני אוהב להיות ליד חוף הים</span>. In the computer's memory, this is stored as <bdo dir="ltr">אה, אני אוהב להיות ליד חוף הים</bdo>.</li>
<li data-sourcepos="4:1-4:59">
<cite>The Scream</cite> by Edvard Munch. Painted in 1893.</li>
<li data-sourcepos="5:1-5:73">
<dfn>HTML</dfn> is the standard markup language for creating web pages.</li>
<li data-sourcepos="6:1-6:47">Do not forget to buy <mark>milk</mark> today.</li>
<li data-sourcepos="7:1-7:64">This is a paragraph and <small>smaller text goes here</small>.</li>
<li data-sourcepos="8:1-8:149">The concert starts at <time datetime="20:00">20:00</time> and you'll be able to enjoy the band for at least <time datetime="PT2H30M">2h 30m</time>.</li>
<li data-sourcepos="9:1-9:62">Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy text (Windows).</li>
<li data-sourcepos="10:1-10:105">WWF's goal is to: <q>Build a future where people live in harmony with nature.</q> We hope they succeed.</li>
<li data-sourcepos="11:1-11:79">The error occured was: <samp>Keyboard not found. Press F1 to continue.</samp>
</li>
<li data-sourcepos="12:1-12:136">The area of a triangle is: 1/2 x <var>b</var> x <var>h</var>, where <var>b</var> is the base, and <var>h</var> is the vertical height.</li>
<li data-sourcepos="13:1-13:35"><ruby>漢<rt>ㄏㄢˋ</rt></ruby></li>
<li data-sourcepos="14:1-14:81">C<sub>7</sub>H<sub>16</sub> + O<sub>2</sub> → CO<sub>2</sub> + H<sub>2</sub>O</li>
<li data-sourcepos="15:1-15:130">The <strong>Pythagorean theorem</strong> is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>
</li>
</ul>
- name: image
markdown: |-
![alt text](https://gitlab.com/logo.png)
html: |-
<p data-sourcepos="1:1-1:40" dir="auto"><a class="no-attachment-icon" href="https://gitlab.com/logo.png" target="_blank" rel="nofollow noreferrer noopener"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="alt text" class="lazy" data-src="https://gitlab.com/logo.png"></a></p>
- name: inline_code
markdown: |-
`code`
html: |-
<p data-sourcepos="1:1-1:6" dir="auto"><code>code</code></p>
- name: inline_diff
markdown: |-
* {-deleted-}
* {+added+}
html: |-
<ul data-sourcepos="1:1-2:11" dir="auto">
<li data-sourcepos="1:1-1:13"><span class="idiff left right deletion">deleted</span></li>
<li data-sourcepos="2:1-2:11"><span class="idiff left right addition">added</span></li>
</ul>
- name: label
pending:
# TODO: There is an error with the frontend HTML to markdown spec adding a double escape (\\) to the label tilde.
frontend: 'There is an error with the frontend HTML to markdown spec adding a double escape (\\) to the label tilde.'
markdown: |-
~bug
html: |-
<p data-sourcepos="1:1-1:4" dir="auto">~bug</p>
- name: link
markdown: |-
[GitLab](https://gitlab.com)
html: |-
<p data-sourcepos="1:1-1:28" dir="auto"><a href="https://gitlab.com" rel="nofollow noreferrer noopener" target="_blank">GitLab</a></p>
- name: math
markdown: |-
This math is inline $`a^2+b^2=c^2`$.
This is on a separate line:
```math
a^2+b^2=c^2
```
html: |-
<p data-sourcepos="1:1-1:36" dir="auto">This math is inline <code class="code math js-render-math" data-math-style="inline">a^2+b^2=c^2</code>.</p>
<p data-sourcepos="3:1-3:27" dir="auto">This is on a separate line:</p>
<pre data-sourcepos="5:1-7:3" class="code highlight js-syntax-highlight language-math js-render-math" lang="math" v-pre="true" data-math-style="display"><code><span id="LC1" class="line" lang="math">a^2+b^2=c^2</span></code></pre>
- name: ordered_list
markdown: |-
1. list item 1
2. list item 2
3. list item 3
html: |-
<ol data-sourcepos="1:1-3:14" dir="auto">
<li data-sourcepos="1:1-1:14">list item 1</li>
<li data-sourcepos="2:1-2:14">list item 2</li>
<li data-sourcepos="3:1-3:14">list item 3</li>
</ol>
- name: ordered_list_with_start_order
markdown: |-
134. list item 1
135. list item 2
136. list item 3
html: |-
<ol start="134" data-sourcepos="1:1-3:16" dir="auto">
<li data-sourcepos="1:1-1:16">list item 1</li>
<li data-sourcepos="2:1-2:16">list item 2</li>
<li data-sourcepos="3:1-3:16">list item 3</li>
</ol>
- name: ordered_task_list
markdown: |-
1. [x] hello
2. [x] world
3. [ ] example
1. [ ] of nested
1. [x] task list
2. [ ] items
html: |-
<ol data-sourcepos="1:1-6:18" class="task-list" dir="auto">
<li data-sourcepos="1:1-1:12" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> hello</li>
<li data-sourcepos="2:1-2:12" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> world</li>
<li data-sourcepos="3:1-6:18" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> example
<ol data-sourcepos="4:4-6:18" class="task-list">
<li data-sourcepos="4:4-6:18" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> of nested
<ol data-sourcepos="5:7-6:18" class="task-list">
<li data-sourcepos="5:7-5:22" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> task list</li>
<li data-sourcepos="6:7-6:18" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> items</li>
</ol>
</li>
</ol>
</li>
</ol>
- name: ordered_task_list_with_order
markdown: |-
4893. [x] hello
4894. [x] world
4895. [ ] example
html: |-
<ol start="4893" data-sourcepos="1:1-3:17" class="task-list" dir="auto">
<li data-sourcepos="1:1-1:15" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> hello</li>
<li data-sourcepos="2:1-2:15" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> world</li>
<li data-sourcepos="3:1-3:17" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> example</li>
</ol>
- name: reference_for_project_wiki
api_context: project_wiki
substitutions:
# NOTE: We don't care about verifying specific attribute values here, that should be the
# responsibility of unit tests. These tests are about the structure of the HTML.
uri_substitution: *uri_substitution
data_attribute_id_substitution:
- regex: '(data-user|data-project|data-issue|data-iid|data-merge-request|data-milestone)(=")(\d+?)(")'
replacement: '\1\2ID\4'
text_attribute_substitution:
- regex: '(title)(=")(.+?)(")'
replacement: '\1\2TEXT\4'
path_attribute_id_substitution:
- regex: '(group|project)(\d+)'
replacement: '\1ID'
markdown: |-
Hi @gfm_user - thank you for reporting this bug (#1) we hope to fix it in %1.1 as part of !1
html: |-
<p data-sourcepos="1:1-1:92" dir="auto">Hi <a href="/gfm_user" data-user="1" data-reference-type="user" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this bug (<a href="/group1/project1/-/issues/1" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-reference-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue has-tooltip">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-reference-type="milestone" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-mr-title="My title 2" data-reference-type="merge_request" data-container="body" data-placement="top" title="" class="gfm gfm-merge_request">!1</a></p>
- name: strike
markdown: |-
~~del~~
html: |-
<p data-sourcepos="1:1-1:7" dir="auto"><del>del</del></p>
- name: table
markdown: |-
| header | header |
|--------|--------|
| `code` | cell with **bold** |
| ~~strike~~ | cell with _italic_ |
# content after table
html: |-
<table data-sourcepos="1:1-4:35" dir="auto">
<thead>
<tr data-sourcepos="1:1-1:19">
<th data-sourcepos="1:2-1:9">header</th>
<th data-sourcepos="1:11-1:18">header</th>
</tr>
</thead>
<tbody>
<tr data-sourcepos="3:1-3:31">
<td data-sourcepos="3:2-3:9"><code>code</code></td>
<td data-sourcepos="3:11-3:30">cell with <strong>bold</strong>
</td>
</tr>
<tr data-sourcepos="4:1-4:35">
<td data-sourcepos="4:2-4:13"><del>strike</del></td>
<td data-sourcepos="4:15-4:34">cell with <em>italic</em>
</td>
</tr>
</tbody>
</table>
<h1 data-sourcepos="6:1-6:21" dir="auto">
<a id="user-content-content-after-table" class="anchor" href="#content-after-table" aria-hidden="true"></a>content after table</h1>
- name: table_of_contents
markdown: |-
[[_TOC_]]
# Lorem
Well, that's just like... your opinion.. man.
## Ipsum
### Dolar
# Sit amit
### I don't know
html: |-
<ul class="section-nav">
<li>
<a href="#lorem">Lorem</a><ul><li>
<a href="#ipsum">Ipsum</a><ul><li><a href="#dolar">Dolar</a></li></ul>
</li></ul>
</li>
<li>
<a href="#sit-amit">Sit amit</a><ul><li><a href="#i-dont-know">I don't know</a></li></ul>
</li>
</ul>
<h1 data-sourcepos="3:1-3:7" dir="auto">
<a id="user-content-lorem" class="anchor" href="#lorem" aria-hidden="true"></a>Lorem</h1>
<p data-sourcepos="5:1-5:45" dir="auto">Well, that's just like... your opinion.. man.</p>
<h2 data-sourcepos="7:1-7:8" dir="auto">
<a id="user-content-ipsum" class="anchor" href="#ipsum" aria-hidden="true"></a>Ipsum</h2>
<h3 data-sourcepos="9:1-9:9" dir="auto">
<a id="user-content-dolar" class="anchor" href="#dolar" aria-hidden="true"></a>Dolar</h3>
<h1 data-sourcepos="11:1-11:10" dir="auto">
<a id="user-content-sit-amit" class="anchor" href="#sit-amit" aria-hidden="true"></a>Sit amit</h1>
<h3 data-sourcepos="13:1-13:16" dir="auto">
<a id="user-content-i-dont-know" class="anchor" href="#i-dont-know" aria-hidden="true"></a>I don't know</h3>
- name: task_list
markdown: |-
* [x] hello
* [x] world
* [ ] example
* [ ] of nested
* [x] task list
* [ ] items
html: |-
<ul data-sourcepos="1:1-6:15" class="task-list" dir="auto">
<li data-sourcepos="1:1-1:11" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> hello</li>
<li data-sourcepos="2:1-2:11" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> world</li>
<li data-sourcepos="3:1-6:15" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> example
<ul data-sourcepos="4:3-6:15" class="task-list">
<li data-sourcepos="4:3-6:15" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> of nested
<ul data-sourcepos="5:5-6:15" class="task-list">
<li data-sourcepos="5:5-5:19" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" checked disabled> task list</li>
<li data-sourcepos="6:5-6:15" class="task-list-item">
<input type="checkbox" class="task-list-item-checkbox" disabled> items</li>
</ul>
</li>
</ul>
</li>
</ul>
- name: video
markdown: |-
![Sample Video](https://gitlab.com/gitlab.mp4)
html: |-
<p data-sourcepos="1:1-1:46" dir="auto"><span class="media-container video-container"><video src="https://gitlab.com/gitlab.mp4" controls="true" data-setup="{}" data-title="Sample Video" width="400" preload="metadata"></video><a href="https://gitlab.com/gitlab.mp4" target="_blank" rel="nofollow noreferrer noopener" title="Download 'Sample Video'">Sample Video</a></span></p>
- name: word_break
markdown: Fernstraßen<wbr>bau<wbr>privat<wbr>finanzierungs<wbr>gesetz
html: <p data-sourcepos="1:1-1:60" dir="auto">Fernstraßen<wbr>bau<wbr>privat<wbr>finanzierungs<wbr>gesetz</wbr></wbr></wbr></wbr></p>
import fs from 'fs';
import path from 'path';
import jsYaml from 'js-yaml';
// eslint-disable-next-line import/no-deprecated
import { getJSONFixture } from 'helpers/fixtures';
export const loadMarkdownApiResult = (testName) => {
const fixturePathPrefix = `api/markdown/${testName}.json`;
// eslint-disable-next-line import/no-deprecated
const fixture = getJSONFixture(fixturePathPrefix);
return fixture.body || fixture.html;
};
export const loadMarkdownApiExamples = () => {
const apiMarkdownYamlPath = path.join(__dirname, '..', 'fixtures', 'api_markdown.yml');
const apiMarkdownYamlText = fs.readFileSync(apiMarkdownYamlPath);
const apiMarkdownExampleObjects = jsYaml.safeLoad(apiMarkdownYamlText);
return apiMarkdownExampleObjects.map(({ name, context, markdown }) => [name, context, markdown]);
};
export const loadMarkdownApiExample = (testName) => {
return loadMarkdownApiExamples().find(([name, context]) => {
return (context ? `${context}_${name}` : name) === testName;
})[2];
};
import { createContentEditor } from '~/content_editor'; import path from 'path';
import { loadMarkdownApiExamples, loadMarkdownApiResult } from './markdown_processing_examples'; import { createSharedExamples, loadMarkdownApiExamples } from './markdown_processing_spec_helper';
jest.mock('~/emoji'); jest.mock('~/emoji');
describe('markdown processing', () => { // See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
describe('markdown processing in ContentEditor', () => {
// Ensure we generate same markdown that was provided to Markdown API. // Ensure we generate same markdown that was provided to Markdown API.
it.each(loadMarkdownApiExamples())( const markdownYamlPath = path.join(
'correctly handles %s (context: %s)', __dirname,
async (name, context, markdown) => { '..',
const testName = context ? `${context}_${name}` : name; '..',
const contentEditor = createContentEditor({ 'fixtures',
renderMarkdown: () => loadMarkdownApiResult(testName), 'markdown',
}); 'markdown_golden_master_examples.yml',
await contentEditor.setSerializedContent(markdown);
expect(contentEditor.getSerializedContent()).toBe(markdown);
},
); );
// eslint-disable-next-line jest/valid-describe
describe.each(loadMarkdownApiExamples(markdownYamlPath))('%s', createSharedExamples);
}); });
import fs from 'fs';
import jsYaml from 'js-yaml';
import { memoize } from 'lodash';
import { createContentEditor } from '~/content_editor';
import { setTestTimeoutOnce } from 'helpers/timeout';
const getFocusedMarkdownExamples = memoize(
() => process.env.FOCUSED_MARKDOWN_EXAMPLES?.split(',') || [],
);
const includeExample = ({ name }) => {
const focusedMarkdownExamples = getFocusedMarkdownExamples();
if (!focusedMarkdownExamples.length) {
return true;
}
return focusedMarkdownExamples.includes(name);
};
const getPendingReason = (pendingStringOrObject) => {
if (!pendingStringOrObject) {
return null;
}
if (typeof pendingStringOrObject === 'string') {
return pendingStringOrObject;
}
if (pendingStringOrObject.frontend) {
return pendingStringOrObject.frontend;
}
return null;
};
// eslint-disable-next-line jest/no-export
export const loadMarkdownApiExamples = (markdownYamlPath) => {
const apiMarkdownYamlText = fs.readFileSync(markdownYamlPath);
const apiMarkdownExampleObjects = jsYaml.safeLoad(apiMarkdownYamlText);
return apiMarkdownExampleObjects
.filter(includeExample)
.map(({ name, pending, markdown, html }) => [
name,
{ pendingReason: getPendingReason(pending), markdown, html },
]);
};
const testSerializesHtmlToMarkdownForElement = async ({ markdown, html }) => {
const contentEditor = createContentEditor({
// Overwrite renderMarkdown to always return this specific html
renderMarkdown: () => html,
});
await contentEditor.setSerializedContent(markdown);
// This serializes the ContentEditor document, which was based on the HTML, to markdown
const serializedContent = contentEditor.getSerializedContent();
// Assert that the markdown we ended up with after sending it through all the ContentEditor
// plumbing matches the original markdown from the YAML.
expect(serializedContent).toBe(markdown);
};
// eslint-disable-next-line jest/no-export
export const createSharedExamples = (name, { pendingReason, ...example }) => {
const exampleName = 'correctly serializes HTML to markdown';
if (pendingReason) {
it.todo(`${exampleName}: ${pendingReason}`);
} else {
it(exampleName, async () => {
if (name === 'frontmatter_toml') {
setTestTimeoutOnce(2000);
}
await testSerializesHtmlToMarkdownForElement(example);
});
}
};
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::MergeRequests, '(JavaScript fixtures)', type: :request do
include ApiHelpers
include WikiHelpers
include JavaScriptFixturesHelpers
let_it_be(:user) { create(:user, username: 'gitlab') }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, :repository, group: group) }
let_it_be(:label) { create(:label, project: project, title: 'bug') }
let_it_be(:milestone) { create(:milestone, project: project, title: '1.1') }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let_it_be(:project_wiki) { create(:project_wiki, project: project, user: user) }
let(:project_wiki_page) { create(:wiki_page, wiki: project_wiki) }
before(:all) do
group.add_owner(user)
project.add_maintainer(user)
end
before do
sign_in(user)
end
markdown_examples = begin
yaml_file_path = File.expand_path('api_markdown.yml', __dir__)
yaml = File.read(yaml_file_path)
YAML.safe_load(yaml, symbolize_names: true)
end
markdown_examples.each do |markdown_example|
context = markdown_example.fetch(:context, '')
name = markdown_example.fetch(:name)
context "for #{name}#{!context.empty? ? " (context: #{context})" : ''}" do
let(:markdown) { markdown_example.fetch(:markdown) }
name = "#{context}_#{name}" unless context.empty?
it "api/markdown/#{name}.json" do
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"
else
api "/markdown"
end
post api_url, params: { text: markdown, gfm: true }
expect(response).to be_successful
end
end
end
end
# This data file drives the specs in
# spec/frontend/fixtures/api_markdown.rb and
# spec/frontend/content_editor/extensions/markdown_processing_spec.js
---
- name: attachment_image
context: group
markdown: '![test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png)'
- name: attachment_image
context: project
markdown: '![test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.png)'
- name: attachment_image
context: project_wiki
markdown: '![test-file](test-file.png)'
- name: attachment_link
context: group
markdown: '[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)'
- name: attachment_link
context: project
markdown: '[test-file](/uploads/aa45a38ec2cfe97433281b10bbff042c/test-file.zip)'
- name: attachment_link
context: project_wiki
markdown: '[test-file](test-file.zip)'
- name: audio
markdown: '![Sample Audio](https://gitlab.com/gitlab.mp3)'
- name: audio_and_video_in_lists
markdown: |-
* ![Sample Audio](https://gitlab.com/1.mp3)
* ![Sample Video](https://gitlab.com/2.mp4)
1. ![Sample Video](https://gitlab.com/1.mp4)
2. ![Sample Audio](https://gitlab.com/2.mp3)
* [x] ![Sample Audio](https://gitlab.com/1.mp3)
* [x] ![Sample Audio](https://gitlab.com/2.mp3)
* [x] ![Sample Video](https://gitlab.com/3.mp4)
- name: blockquote
markdown: |-
> This is a blockquote
>
> This is another one
- name: bold
markdown: '**bold**'
- name: bullet_list_style_1
markdown: |-
* list item 1
* list item 2
* embedded list item 3
- name: bullet_list_style_2
markdown: |-
- list item 1
- list item 2
* embedded list item 3
- name: bullet_list_style_3
markdown: |-
+ list item 1
+ list item 2
- embedded list item 3
- name: code_block
markdown: |-
```javascript
console.log('hello world')
```
- name: color_chips
markdown: |-
- `#F00`
- `#F00A`
- `#FF0000`
- `#FF0000AA`
- `RGB(0,255,0)`
- `RGB(0%,100%,0%)`
- `RGBA(0,255,0,0.3)`
- `HSL(540,70%,50%)`
- `HSLA(540,70%,50%,0.3)`
- name: description_list
markdown: |-
<dl>
<dt>Frog</dt>
<dd>Wet green thing</dd>
<dt>Rabbit</dt>
<dd>Warm fluffy thing</dd>
<dt>Punt</dt>
<dd>Kick a ball</dd>
<dd>Take a bet</dd>
<dt>Color</dt>
<dt>Colour</dt>
<dd>
Any hue except _white_ or **black**
</dd>
</dl>
- name: details
markdown: |-
<details>
<summary>Apply this patch</summary>
```diff
diff --git a/spec/frontend/fixtures/api_markdown.yml b/spec/frontend/fixtures/api_markdown.yml
index 8433efaf00c..69b12c59d46 100644
--- a/spec/frontend/fixtures/api_markdown.yml
+++ b/spec/frontend/fixtures/api_markdown.yml
@@ -33,6 +33,13 @@
* <ruby>漢<rt>ㄏㄢˋ</rt></ruby>
* C<sub>7</sub>H<sub>16</sub> + O<sub>2</sub> → CO<sub>2</sub> + H<sub>2</sub>O
* The **Pythagorean theorem** is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>.The **Pythagorean theorem** is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>
+- name: details
+ markdown: |-
+ <details>
+ <summary>Apply this patch</summary>
+
+ 🐶 much meta, 🐶 many patch
+ 🐶 such diff, 🐶 very meme
+ 🐶 wow!
+ </details>
- name: link
markdown: '[GitLab](https://gitlab.com)'
- name: attachment_link
```
</details>
- name: div
markdown: |-
<div>plain text</div>
<div>
just a plain ol' div, not much to _expect_!
</div>
- name: emoji
markdown: ':sparkles: :heart: :100:'
- name: emphasis
markdown: '_emphasized text_'
- name: figure
markdown: |-
<figure>
![Elephant at sunset](elephant-sunset.jpg)
<figcaption>An elephant at sunset</figcaption>
</figure>
<figure>
![A crocodile wearing crocs](croc-crocs.jpg)
<figcaption>
A crocodile wearing _crocs_!
</figcaption>
</figure>
- name: frontmatter_json
markdown: |-
;;;
{
"title": "Page title"
}
;;;
- name: frontmatter_toml
markdown: |-
+++
title = "Page title"
+++
- name: frontmatter_yaml
markdown: |-
---
title: Page title
---
- name: hard_break
markdown: |-
This is a line after a\
hard break
- name: headings
markdown: |-
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
- name: horizontal_rule
markdown: '---'
- name: html_marks
markdown: |-
* Content editor is ~~great~~<ins>amazing</ins>.
* If the changes <abbr title="Looks good to merge">LGTM</abbr>, please <abbr title="Merge when pipeline succeeds">MWPS</abbr>.
* The English song <q>Oh I do like to be beside the seaside</q> looks like this in Hebrew: <span dir="rtl">אה, אני אוהב להיות ליד חוף הים</span>. In the computer's memory, this is stored as <bdo dir="ltr">אה, אני אוהב להיות ליד חוף הים</bdo>.
* <cite>The Scream</cite> by Edvard Munch. Painted in 1893.
* <dfn>HTML</dfn> is the standard markup language for creating web pages.
* Do not forget to buy <mark>milk</mark> today.
* This is a paragraph and <small>smaller text goes here</small>.
* The concert starts at <time datetime="20:00">20:00</time> and you'll be able to enjoy the band for at least <time datetime="PT2H30M">2h 30m</time>.
* Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy text (Windows).
* WWF's goal is to: <q>Build a future where people live in harmony with nature.</q> We hope they succeed.
* The error occured was: <samp>Keyboard not found. Press F1 to continue.</samp>
* The area of a triangle is: 1/2 x <var>b</var> x <var>h</var>, where <var>b</var> is the base, and <var>h</var> is the vertical height.
* <ruby>漢<rt>ㄏㄢˋ</rt></ruby>
* C<sub>7</sub>H<sub>16</sub> + O<sub>2</sub> → CO<sub>2</sub> + H<sub>2</sub>O
* The **Pythagorean theorem** is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>
- name: image
markdown: '![alt text](https://gitlab.com/logo.png)'
- name: inline_code
markdown: '`code`'
- name: inline_diff
markdown: |-
* {-deleted-}
* {+added+}
- name: link
markdown: '[GitLab](https://gitlab.com)'
- name: math
markdown: |-
This math is inline $`a^2+b^2=c^2`$.
This is on a separate line:
```math
a^2+b^2=c^2
```
- name: ordered_list
markdown: |-
1. list item 1
2. list item 2
3. list item 3
- name: ordered_list_with_start_order
markdown: |-
134. list item 1
135. list item 2
136. list item 3
- name: ordered_task_list
markdown: |-
1. [x] hello
2. [x] world
3. [ ] example
1. [ ] of nested
1. [x] task list
2. [ ] items
- name: ordered_task_list_with_order
markdown: |-
4893. [x] hello
4894. [x] world
4895. [ ] example
- name: reference
context: project_wiki
markdown: |-
Hi @gitlab - thank you for reporting this ~bug (#1) we hope to fix it in %1.1 as part of !1
- name: strike
markdown: '~~del~~'
- name: table
markdown: |-
| header | header |
|--------|--------|
| `code` | cell with **bold** |
| ~~strike~~ | cell with _italic_ |
# content after table
- name: table_of_contents
markdown: |-
[[_TOC_]]
# Lorem
Well, that's just like... your opinion.. man.
## Ipsum
### Dolar
# Sit amit
### I don't know
- name: task_list
markdown: |-
* [x] hello
* [x] world
* [ ] example
* [ ] of nested
* [x] task list
* [ ] items
- name: thematic_break
markdown: |-
---
- name: video
markdown: '![Sample Video](https://gitlab.com/gitlab.mp4)'
- name: word_break
markdown: Fernstraßen<wbr>bau<wbr>privat<wbr>finanzierungs<wbr>gesetz
# frozen_string_literal: true
require 'spec_helper'
# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
RSpec.describe API::Markdown, 'Golden Master' do
markdown_yml_file_path = File.expand_path('../../fixtures/markdown/markdown_golden_master_examples.yml', __dir__)
include_context 'API::Markdown Golden Master shared context', markdown_yml_file_path
end
...@@ -450,6 +450,13 @@ RSpec.configure do |config| ...@@ -450,6 +450,13 @@ RSpec.configure do |config|
$stdout = StringIO.new $stdout = StringIO.new
end end
# Makes diffs show entire non-truncated values.
config.before(:each, unlimited_max_formatted_output_length: true) do |_example|
config.expect_with :rspec do |c|
c.max_formatted_output_length = nil
end
end
config.after(:each, :silence_stdout) do config.after(:each, :silence_stdout) do
$stdout = STDOUT $stdout = STDOUT
end end
......
# frozen_string_literal: true
require 'spec_helper'
# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
RSpec.shared_context 'API::Markdown Golden Master shared context' do |markdown_yml_file_path|
include ApiHelpers
include WikiHelpers
let_it_be(:user) { create(:user, username: 'gfm_user') }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, :repository, group: group) }
let_it_be(:label) { create(:label, project: project, title: 'bug') }
let_it_be(:milestone) { create(:milestone, project: project, title: '1.1') }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let_it_be(:project_wiki) { create(:project_wiki, project: project, user: user) }
let_it_be(:project_wiki_page) { create(:wiki_page, wiki: project_wiki) }
before(:all) do
group.add_owner(user)
project.add_maintainer(user)
end
before do
sign_in(user)
end
markdown_examples = begin
yaml = File.read(markdown_yml_file_path)
YAML.safe_load(yaml, symbolize_names: true, aliases: true)
end
it "examples must be unique and alphabetized by name", :unlimited_max_formatted_output_length do
names = markdown_examples.map { |example| example[:name] }
expect(names).to eq(names.sort.uniq)
end
if focused_markdown_examples_string = ENV['FOCUSED_MARKDOWN_EXAMPLES']
focused_markdown_examples = focused_markdown_examples_string.split(',').map(&:strip) || []
markdown_examples.reject! {|markdown_example| !focused_markdown_examples.include?(markdown_example.fetch(:name)) }
end
markdown_examples.each do |markdown_example|
name = markdown_example.fetch(:name)
api_context = markdown_example[:api_context]
if api_context && !name.end_with?("_for_#{api_context}")
raise "Name must have suffix of '_for_#{api_context}' to the api_context"
end
context "for #{name}#{api_context ? " (api_context: #{api_context})" : ''}" do
let(:pending_reason) do
pending_value = markdown_example.fetch(:pending, nil)
get_pending_reason(pending_value)
end
let(:example_markdown) { markdown_example.fetch(:markdown) }
let(:example_html) { markdown_example.fetch(:html) }
let(:substitutions) { markdown_example.fetch(:substitutions, {}) }
it "verifies conversion of GFM to HTML", :unlimited_max_formatted_output_length do
pending pending_reason if pending_reason
normalized_example_html = normalize_html(example_html, substitutions)
api_url = get_url_for_api_context(api_context)
post api_url, params: { text: example_markdown, gfm: true }
expect(response).to be_successful
response_body = Gitlab::Json.parse(response.body)
# Some requests have the HTML in the `html` key, others in the `body` key.
response_html = response_body['body'] ? response_body.fetch('body') : response_body.fetch('html')
normalized_response_html = normalize_html(response_html, substitutions)
expect(normalized_response_html).to eq(normalized_example_html)
end
def get_pending_reason(pending_value)
return false unless pending_value
return pending_value if pending_value.is_a?(String)
pending_value[:backend] || false
end
def normalize_html(html, substitutions)
normalized_html = html.dup
# Note: having the top level `substitutions` data structure be a hash of arrays
# allows us to compose multiple substitutions via YAML anchors (YAML anchors
# pointing to arrays can't be combined)
substitutions.each_value do |substitution_entry|
substitution_entry.each do |substitution|
regex = substitution.fetch(:regex)
replacement = substitution.fetch(:replacement)
normalized_html.gsub!(%r{#{regex}}, replacement)
end
end
normalized_html
end
end
end
def supported_api_contexts
%w(project group project_wiki)
end
def get_url_for_api_context(api_context)
case api_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 nil
api "/markdown"
else
raise "Error: 'context' extension was '#{api_context}'. It must be one of: #{supported_api_contexts.join(',')}"
end
end
end
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