Commit 05f9a6a9 authored by Robert Speicher's avatar Robert Speicher

Update Markdown feature to allow for multiple pipelines

parent 0673ca36
...@@ -17,16 +17,8 @@ require 'erb' ...@@ -17,16 +17,8 @@ require 'erb'
# -> Post-process HTML # -> Post-process HTML
# -> `gfm_with_options` helper # -> `gfm_with_options` helper
# -> HTML::Pipeline # -> HTML::Pipeline
# -> Sanitize # -> SanitizationFilter
# -> RelativeLink # -> Other filters, depending on pipeline
# -> Emoji
# -> Table of Contents
# -> Autolinks
# -> Rinku (http, https, ftp)
# -> Other schemes
# -> ExternalLink
# -> References
# -> TaskList
# -> `html_safe` # -> `html_safe`
# -> Template # -> Template
# #
...@@ -35,6 +27,7 @@ require 'erb' ...@@ -35,6 +27,7 @@ require 'erb'
describe 'GitLab Markdown', feature: true do describe 'GitLab Markdown', feature: true do
include Capybara::Node::Matchers include Capybara::Node::Matchers
include GitlabMarkdownHelper include GitlabMarkdownHelper
include MarkdownMatchers
# Let's only parse this thing once # Let's only parse this thing once
before(:all) do before(:all) do
...@@ -42,50 +35,37 @@ describe 'GitLab Markdown', feature: true do ...@@ -42,50 +35,37 @@ describe 'GitLab Markdown', feature: true do
# `gfm_with_options` depends on a `@project` variable # `gfm_with_options` depends on a `@project` variable
@project = @feat.project @project = @feat.project
@html = markdown(@feat.raw_markdown)
end end
after(:all) do after(:all) do
@feat.teardown @feat.teardown
end end
def doc def doc(html = @html)
@doc ||= Nokogiri::HTML::DocumentFragment.parse(@html) Nokogiri::HTML::DocumentFragment.parse(html)
end
# Given a header ID, goes to that element's parent (the header itself), then
# its next sibling element (the body).
def get_section(id)
doc.at_css("##{id}").parent.next_element
end end
# Sometimes it can be useful to see the parsed output of the Markdown document # Sometimes it can be useful to see the parsed output of the Markdown document
# for debugging. Uncomment this block to write the output to # for debugging. Call this method to write the output to
# tmp/capybara/markdown_spec.html. # `tmp/capybara/<filename>.html`.
# def write_markdown(filename = 'markdown_spec')
# it 'writes to a file' do File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file|
# File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file| file.puts @html
# file.puts @html end
# end end
# end
# Shared behavior that all pipelines should exhibit
shared_examples 'all pipelines' do
describe 'Redcarpet extensions' do describe 'Redcarpet extensions' do
describe 'No Intra Emphasis' do
it 'does not parse emphasis inside of words' do it 'does not parse emphasis inside of words' do
body = get_section('no-intra-emphasis') expect(doc.to_html).not_to match('foo<em>bar</em>baz')
expect(body.to_html).not_to match('foo<em>bar</em>baz')
end
end end
describe 'Tables' do
it 'parses table Markdown' do it 'parses table Markdown' do
body = get_section('tables')
aggregate_failures do aggregate_failures do
expect(body).to have_selector('th:contains("Header")') expect(doc).to have_selector('th:contains("Header")')
expect(body).to have_selector('th:contains("Row")') expect(doc).to have_selector('th:contains("Row")')
expect(body).to have_selector('th:contains("Example")') expect(doc).to have_selector('th:contains("Example")')
end end
end end
...@@ -93,36 +73,23 @@ describe 'GitLab Markdown', feature: true do ...@@ -93,36 +73,23 @@ describe 'GitLab Markdown', feature: true do
expect(doc.at_css('td:contains("Baz")').children.to_html). expect(doc.at_css('td:contains("Baz")').children.to_html).
to eq '<strong>Baz</strong>' to eq '<strong>Baz</strong>'
end end
end
describe 'Fenced Code Blocks' do
it 'parses fenced code blocks' do it 'parses fenced code blocks' do
aggregate_failures do aggregate_failures do
expect(doc).to have_selector('pre.code.highlight.white.c') expect(doc).to have_selector('pre.code.highlight.white.c')
expect(doc).to have_selector('pre.code.highlight.white.python') expect(doc).to have_selector('pre.code.highlight.white.python')
end end
end end
end
describe 'Strikethrough' do
it 'parses strikethroughs' do it 'parses strikethroughs' do
expect(doc).to have_selector(%{del:contains("and this text doesn't")}) expect(doc).to have_selector(%{del:contains("and this text doesn't")})
end end
end
describe 'Superscript' do
it 'parses superscript' do it 'parses superscript' do
body = get_section('superscript') expect(doc).to have_selector('sup', count: 2)
aggregate_failures do
expect(body.to_html).to match('1<sup>st</sup>')
expect(body.to_html).to match('2<sup>nd</sup>')
end
end
end end
end end
describe 'HTML::Pipeline' do
describe 'SanitizationFilter' do describe 'SanitizationFilter' do
it 'permits b elements' do it 'permits b elements' do
expect(doc).to have_selector('b:contains("b tag")') expect(doc).to have_selector('b:contains("b tag")')
...@@ -207,133 +174,56 @@ describe 'GitLab Markdown', feature: true do ...@@ -207,133 +174,56 @@ describe 'GitLab Markdown', feature: true do
end end
end end
describe 'EmojiFilter' do
it 'parses Emoji' do
expect(doc).to have_selector('img.emoji', count: 10)
end
end
describe 'TableOfContentsFilter' do
it 'creates anchors inside header elements' do
aggregate_failures do
expect(doc).to have_selector('h1 a#gitlab-markdown')
expect(doc).to have_selector('h2 a#markdown')
expect(doc).to have_selector('h3 a#autolinkfilter')
end
end
end
describe 'AutolinkFilter' do
def body
get_section('autolinkfilter').next_element
end
# Override Capybara's `have_link` matcher to simplify our use case
def have_link(link)
super(link, href: link)
end
it 'autolinks http://' do
expect(body).to have_link('http://about.gitlab.com/')
end
it 'autolinks https://' do
expect(body).to have_link('https://google.com/')
end
it 'autolinks ftp://' do
expect(body).to have_link('ftp://ftp.us.debian.org/debian/')
end
it 'autolinks smb://' do
expect(body).to have_link('smb://foo/bar/baz')
end
it 'autolinks irc://' do
expect(body).to have_link('irc://irc.freenode.net/git')
end
it 'autolinks short, invalid URLs' do
expect(body).to have_link('http://localhost:3000')
end
%w(code a kbd).each do |elem|
it "ignores links inside '#{elem}' element" do
expect(body).not_to have_selector("#{elem} a")
end
end
end
describe 'ExternalLinkFilter' do describe 'ExternalLinkFilter' do
let(:links) { get_section('externallinkfilter').next_element }
it 'adds nofollow to external link' do it 'adds nofollow to external link' do
expect(links.css('a').first.to_html).to match 'nofollow' link = doc.at_css('a:contains("Google")')
expect(link.attr('rel')).to match 'nofollow'
end end
it 'ignores internal link' do it 'ignores internal link' do
expect(links.css('a').last.to_html).not_to match 'nofollow' link = doc.at_css('a:contains("GitLab Root")')
expect(link.attr('rel')).not_to match 'nofollow'
end end
end end
describe 'ReferenceFilter' do
it 'handles references in headers' do
header = doc.at_css('#reference-filters-eg-1').parent
expect(header.css('a').size).to eq 2
end
it "handles references in Markdown" do
body = get_section('reference-filters-eg-1')
expect(body).to have_selector('em a.gfm-merge_request', count: 1)
end
it 'parses user references' do
body = get_section('userreferencefilter')
expect(body).to have_selector('a.gfm.gfm-project_member', count: 3)
end end
it 'parses issue references' do context 'default pipeline' do
body = get_section('issuereferencefilter') before(:all) do
expect(body).to have_selector('a.gfm.gfm-issue', count: 2) @html = markdown(@feat.raw_markdown)
end end
it 'parses merge request references' do it_behaves_like 'all pipelines'
body = get_section('mergerequestreferencefilter')
expect(body).to have_selector('a.gfm.gfm-merge_request', count: 2)
end
it 'parses snippet references' do it 'includes RelativeLinkFilter' do
body = get_section('snippetreferencefilter') expect(doc).to parse_relative_links
expect(body).to have_selector('a.gfm.gfm-snippet', count: 2)
end end
it 'parses commit range references' do it 'includes EmojiFilter' do
body = get_section('commitrangereferencefilter') expect(doc).to parse_emoji
expect(body).to have_selector('a.gfm.gfm-commit_range', count: 2)
end end
it 'parses commit references' do it 'includes TableOfContentsFilter' do
body = get_section('commitreferencefilter') expect(doc).to create_header_links
expect(body).to have_selector('a.gfm.gfm-commit', count: 2)
end end
it 'parses label references' do it 'includes AutolinkFilter' do
body = get_section('labelreferencefilter') expect(doc).to create_autolinks
expect(body).to have_selector('a.gfm.gfm-label', count: 3)
end end
end
describe 'Task Lists' do
it 'generates task lists' do
body = get_section('task-lists')
it 'includes all reference filters' do
aggregate_failures do aggregate_failures do
expect(body).to have_selector('ul.task-list', count: 2) expect(doc).to reference_users
expect(body).to have_selector('li.task-list-item', count: 7) expect(doc).to reference_issues
expect(body).to have_selector('input[checked]', count: 3) expect(doc).to reference_merge_requests
expect(doc).to reference_snippets
expect(doc).to reference_commit_ranges
expect(doc).to reference_commits
expect(doc).to reference_labels
end end
end end
it 'includes TaskListFilter' do
expect(doc).to parse_task_lists
end end
end end
......
# MarkdownMatchers
#
# Custom matchers for our custom HTML::Pipeline filters. These are used to test
# that specific filters are or are not used by our defined pipelines.
#
# Must be included manually.
module MarkdownMatchers
extend RSpec::Matchers::DSL
include Capybara::Node::Matchers
# RelativeLinkFilter
matcher :parse_relative_links do
set_default_markdown_messages
match do |actual|
link = actual.at_css('a:contains("Relative Link")')
image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md')
expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
end
end
# EmojiFilter
matcher :parse_emoji do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('img.emoji', count: 10)
end
end
# TableOfContentsFilter
matcher :create_header_links do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('h1 a#gitlab-markdown')
expect(actual).to have_selector('h2 a#markdown')
expect(actual).to have_selector('h3 a#autolinkfilter')
end
end
# AutolinkFilter
matcher :create_autolinks do
def have_autolink(link)
have_link(link, href: link)
end
set_default_markdown_messages
match do |actual|
expect(actual).to have_autolink('http://about.gitlab.com/')
expect(actual).to have_autolink('https://google.com/')
expect(actual).to have_autolink('ftp://ftp.us.debian.org/debian/')
expect(actual).to have_autolink('smb://foo/bar/baz')
expect(actual).to have_autolink('irc://irc.freenode.net/git')
expect(actual).to have_autolink('http://localhost:3000')
%w(code a kbd).each do |elem|
expect(body).not_to have_selector("#{elem} a")
end
end
end
# UserReferenceFilter
matcher :reference_users do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
end
end
# IssueReferenceFilter
matcher :reference_issues do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
end
end
# MergeRequestReferenceFilter
matcher :reference_merge_requests do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
expect(actual).to have_selector('em a.gfm-merge_request')
end
end
# SnippetReferenceFilter
matcher :reference_snippets do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
end
end
# CommitRangeReferenceFilter
matcher :reference_commit_ranges do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
end
end
# CommitReferenceFilter
matcher :reference_commits do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
end
end
# LabelReferenceFilter
matcher :reference_labels do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
end
end
# TaskListFilter
matcher :parse_task_lists do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('ul.task-list', count: 2)
expect(actual).to have_selector('li.task-list-item', count: 7)
expect(actual).to have_selector('input[checked]', count: 3)
end
end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
# setting the failure messages for these matchers
module RSpec::Matchers::DSL::Macros
def set_default_markdown_messages
failure_message do
# expected to parse emoji, but didn't
"expected to #{description}, but didn't"
end
failure_message_when_negated do
# expected not to parse task lists, but did
"expected not to #{description}, but did"
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