Move Markdown filters and pipelines into folders.
This commit is contained in:
parent
f5a630111f
commit
e1e67d383e
|
|
@ -33,7 +33,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.render_result(text, context = {})
|
||||
pipeline_by_name(context[:pipeline]).call(text, context)
|
||||
Pipeline[context[:pipeline]].call(text, context)
|
||||
end
|
||||
|
||||
# Perform post-processing on an HTML String
|
||||
|
|
@ -50,11 +50,9 @@ module Gitlab
|
|||
#
|
||||
# Returns an HTML-safe String
|
||||
def self.post_process(html, context)
|
||||
pipeline = pipeline_by_name(context[:pipeline])
|
||||
context = pipeline.transform_context(context)
|
||||
|
||||
pipeline = pipeline_by_name(:post_process)
|
||||
context = Pipeline[context[:pipeline]].transform_context(context)
|
||||
|
||||
pipeline = Pipeline[:post_process]
|
||||
if context[:xhtml]
|
||||
pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
|
||||
else
|
||||
|
|
@ -66,6 +64,7 @@ module Gitlab
|
|||
|
||||
def self.cacheless_render(text, context = {})
|
||||
result = render_result(text, context)
|
||||
|
||||
output = result[:output]
|
||||
if output.respond_to?(:to_html)
|
||||
output.to_html
|
||||
|
|
@ -76,48 +75,41 @@ module Gitlab
|
|||
|
||||
def self.full_cache_key(cache_key, pipeline_name)
|
||||
return unless cache_key
|
||||
pipeline_name ||= :full
|
||||
["markdown", *cache_key, pipeline_name]
|
||||
end
|
||||
|
||||
def self.pipeline_by_name(pipeline_name)
|
||||
pipeline_name ||= :full
|
||||
const_get("#{pipeline_name.to_s.camelize}Pipeline")
|
||||
["markdown", *cache_key, pipeline_name || :full]
|
||||
end
|
||||
|
||||
# Provide autoload paths for filters to prevent a circular dependency error
|
||||
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
|
||||
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
|
||||
autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter'
|
||||
autoload :EmojiFilter, 'gitlab/markdown/emoji_filter'
|
||||
autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter'
|
||||
autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter'
|
||||
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
|
||||
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
|
||||
autoload :MarkdownFilter, 'gitlab/markdown/markdown_filter'
|
||||
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
|
||||
autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
|
||||
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
|
||||
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
|
||||
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
|
||||
autoload :SyntaxHighlightFilter, 'gitlab/markdown/syntax_highlight_filter'
|
||||
autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
|
||||
autoload :TaskListFilter, 'gitlab/markdown/task_list_filter'
|
||||
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
|
||||
autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
|
||||
autoload :AutolinkFilter, 'gitlab/markdown/filter/autolink_filter'
|
||||
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/filter/commit_range_reference_filter'
|
||||
autoload :CommitReferenceFilter, 'gitlab/markdown/filter/commit_reference_filter'
|
||||
autoload :EmojiFilter, 'gitlab/markdown/filter/emoji_filter'
|
||||
autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/filter/external_issue_reference_filter'
|
||||
autoload :ExternalLinkFilter, 'gitlab/markdown/filter/external_link_filter'
|
||||
autoload :IssueReferenceFilter, 'gitlab/markdown/filter/issue_reference_filter'
|
||||
autoload :LabelReferenceFilter, 'gitlab/markdown/filter/label_reference_filter'
|
||||
autoload :MarkdownFilter, 'gitlab/markdown/filter/markdown_filter'
|
||||
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/filter/merge_request_reference_filter'
|
||||
autoload :RedactorFilter, 'gitlab/markdown/filter/redactor_filter'
|
||||
autoload :ReferenceGathererFilter, 'gitlab/markdown/filter/reference_gatherer_filter'
|
||||
autoload :RelativeLinkFilter, 'gitlab/markdown/filter/relative_link_filter'
|
||||
autoload :SanitizationFilter, 'gitlab/markdown/filter/sanitization_filter'
|
||||
autoload :SnippetReferenceFilter, 'gitlab/markdown/filter/snippet_reference_filter'
|
||||
autoload :SyntaxHighlightFilter, 'gitlab/markdown/filter/syntax_highlight_filter'
|
||||
autoload :TableOfContentsFilter, 'gitlab/markdown/filter/table_of_contents_filter'
|
||||
autoload :TaskListFilter, 'gitlab/markdown/filter/task_list_filter'
|
||||
autoload :UserReferenceFilter, 'gitlab/markdown/filter/user_reference_filter'
|
||||
autoload :UploadLinkFilter, 'gitlab/markdown/filter/upload_link_filter'
|
||||
|
||||
autoload :AsciidocPipeline, 'gitlab/markdown/asciidoc_pipeline'
|
||||
autoload :AtomPipeline, 'gitlab/markdown/atom_pipeline'
|
||||
autoload :CombinedPipeline, 'gitlab/markdown/combined_pipeline'
|
||||
autoload :DescriptionPipeline, 'gitlab/markdown/description_pipeline'
|
||||
autoload :EmailPipeline, 'gitlab/markdown/email_pipeline'
|
||||
autoload :FullPipeline, 'gitlab/markdown/full_pipeline'
|
||||
autoload :GfmPipeline, 'gitlab/markdown/gfm_pipeline'
|
||||
autoload :NotePipeline, 'gitlab/markdown/note_pipeline'
|
||||
autoload :Pipeline, 'gitlab/markdown/pipeline'
|
||||
autoload :PlainMarkdownPipeline, 'gitlab/markdown/plain_markdown_pipeline'
|
||||
autoload :PostProcessPipeline, 'gitlab/markdown/post_process_pipeline'
|
||||
autoload :ReferenceExtractionPipeline, 'gitlab/markdown/reference_extraction_pipeline'
|
||||
autoload :SingleLinePipeline, 'gitlab/markdown/single_line_pipeline'
|
||||
autoload :AsciidocPipeline, 'gitlab/markdown/pipeline/asciidoc_pipeline'
|
||||
autoload :AtomPipeline, 'gitlab/markdown/pipeline/atom_pipeline'
|
||||
autoload :DescriptionPipeline, 'gitlab/markdown/pipeline/description_pipeline'
|
||||
autoload :EmailPipeline, 'gitlab/markdown/pipeline/email_pipeline'
|
||||
autoload :FullPipeline, 'gitlab/markdown/pipeline/full_pipeline'
|
||||
autoload :GfmPipeline, 'gitlab/markdown/pipeline/gfm_pipeline'
|
||||
autoload :NotePipeline, 'gitlab/markdown/pipeline/note_pipeline'
|
||||
autoload :PlainMarkdownPipeline, 'gitlab/markdown/pipeline/plain_markdown_pipeline'
|
||||
autoload :PostProcessPipeline, 'gitlab/markdown/pipeline/post_process_pipeline'
|
||||
autoload :ReferenceExtractionPipeline, 'gitlab/markdown/pipeline/reference_extraction_pipeline'
|
||||
autoload :SingleLinePipeline, 'gitlab/markdown/pipeline/single_line_pipeline'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ require 'gitlab/markdown'
|
|||
module Gitlab
|
||||
module Markdown
|
||||
class Pipeline
|
||||
def self.[](name)
|
||||
name ||= :full
|
||||
Markdown.const_get("#{name.to_s.camelize}Pipeline")
|
||||
end
|
||||
|
||||
def self.filters
|
||||
[]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def self.[](name)
|
||||
Markdown.const_get("#{name.to_s.camelize}ReferenceFilter")
|
||||
end
|
||||
|
||||
def self.user_can_reference?(user, node, context)
|
||||
if node.has_attribute?('data-project')
|
||||
project_id = node.attr('data-project').to_i
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ module Gitlab
|
|||
#
|
||||
# Returns the results Array for the requested filter type
|
||||
def pipeline_result(filter_type)
|
||||
klass = "#{filter_type.to_s.camelize}ReferenceFilter"
|
||||
filter = Gitlab::Markdown.const_get(klass)
|
||||
filter = Gitlab::Markdown::ReferenceFilter[filter_type]
|
||||
|
||||
context = {
|
||||
pipeline: :reference_extraction,
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe AutolinkFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:link) { 'http://about.gitlab.com/' }
|
||||
|
||||
it 'does nothing when :autolink is false' do
|
||||
exp = act = link
|
||||
expect(filter(act, autolink: false).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'does nothing with non-link text' do
|
||||
exp = act = 'This text contains no links to autolink'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
context 'Rinku schemes' do
|
||||
it 'autolinks http' do
|
||||
doc = filter("See #{link}")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks https' do
|
||||
link = 'https://google.com/'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks ftp' do
|
||||
link = 'ftp://ftp.us.debian.org/debian/'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks short URLs' do
|
||||
link = 'http://localhost:3000/'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'accepts link_attr options' do
|
||||
doc = filter("See #{link}", link_attr: { class: 'custom' })
|
||||
|
||||
expect(doc.at_css('a')['class']).to eq 'custom'
|
||||
end
|
||||
|
||||
described_class::IGNORE_PARENTS.each do |elem|
|
||||
it "ignores valid links contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>See #{link}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'other schemes' do
|
||||
let(:link) { 'foo://bar.baz/' }
|
||||
|
||||
it 'autolinks smb' do
|
||||
link = 'smb:///Volumes/shared/foo.pdf'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks irc' do
|
||||
link = 'irc://irc.freenode.net/git'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'does not include trailing punctuation' do
|
||||
doc = filter("See #{link}.")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
|
||||
doc = filter("See #{link}, ok?")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
|
||||
doc = filter("See #{link}...")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
end
|
||||
|
||||
it 'does not include trailing HTML entities' do
|
||||
doc = filter("See <<<#{link}>>>")
|
||||
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
expect(doc.text).to eq "See <<<#{link}>>>"
|
||||
end
|
||||
|
||||
it 'accepts link_attr options' do
|
||||
doc = filter("See #{link}", link_attr: { class: 'custom' })
|
||||
expect(doc.at_css('a')['class']).to eq 'custom'
|
||||
end
|
||||
|
||||
described_class::IGNORE_PARENTS.each do |elem|
|
||||
it "ignores valid links contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>See #{link}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe CommitRangeReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:commit1) { project.commit }
|
||||
let(:commit2) { project.commit("HEAD~2") }
|
||||
|
||||
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
|
||||
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { range.to_reference }
|
||||
let(:reference2) { range2.to_reference }
|
||||
|
||||
it 'links to a valid two-dot reference' do
|
||||
doc = filter("See #{reference2}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
|
||||
end
|
||||
|
||||
it 'links to a valid three-dot reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
|
||||
end
|
||||
|
||||
it 'links to a valid short ID' do
|
||||
reference = "#{commit1.short_id}...#{commit2.id}"
|
||||
reference2 = "#{commit1.id}...#{commit2.short_id}"
|
||||
|
||||
exp = commit1.short_id + '...' + commit2.short_id
|
||||
|
||||
expect(filter("See #{reference}").css('a').first.text).to eq exp
|
||||
expect(filter("See #{reference2}").css('a').first.text).to eq exp
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("See (#{reference}.)")
|
||||
|
||||
exp = Regexp.escape(range.to_s)
|
||||
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs' do
|
||||
exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
|
||||
|
||||
expect(project).to receive(:valid_repo?).and_return(true)
|
||||
expect(project.repository).to receive(:commit).with(commit1.id.reverse)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq range.reference_title
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-commit-range attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-commit-range')
|
||||
expect(link.attr('data-commit-range')).to eq range.to_reference
|
||||
end
|
||||
|
||||
it 'supports an :only_path option' do
|
||||
doc = filter("See #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit_range]).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:project, :public, namespace: namespace) }
|
||||
let(:reference) { range.to_reference(project) }
|
||||
|
||||
before do
|
||||
range.project = project2
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
|
||||
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs on the referenced project' do
|
||||
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
|
||||
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit_range]).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe CommitReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:commit) { project.commit }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { commit.id }
|
||||
|
||||
# Let's test a variety of commit SHA sizes just to be paranoid
|
||||
[6, 8, 12, 18, 20, 32, 40].each do |size|
|
||||
it "links to a valid reference of #{size} characters" do
|
||||
doc = filter("See #{reference[0...size]}")
|
||||
|
||||
expect(doc.css('a').first.text).to eq commit.short_id
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_commit_url(project.namespace, project, reference)
|
||||
end
|
||||
end
|
||||
|
||||
it 'always uses the short ID as the link text' do
|
||||
doc = filter("See #{commit.id}")
|
||||
expect(doc.text).to eq "See #{commit.short_id}"
|
||||
|
||||
doc = filter("See #{commit.id[0...6]}")
|
||||
expect(doc.text).to eq "See #{commit.short_id}"
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("See (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs' do
|
||||
invalid = invalidate_reference(reference)
|
||||
exp = act = "See #{invalid}"
|
||||
|
||||
expect(project).to receive(:valid_repo?).and_return(true)
|
||||
expect(project.repository).to receive(:commit).with(invalid)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq commit.link_title
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.text).to eq "See #{commit.short_id}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-commit attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-commit')
|
||||
expect(link.attr('data-commit')).to eq commit.id
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("See #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit]).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:project, :public, namespace: namespace) }
|
||||
let(:commit) { project2.commit }
|
||||
let(:reference) { commit.to_reference(project) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
|
||||
exp = Regexp.escape(project2.to_reference)
|
||||
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs on the referenced project' do
|
||||
exp = act = "Committed #{invalidate_reference(reference)}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit]).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,35 +1,33 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe CrossProjectReference do
|
||||
include described_class
|
||||
describe Gitlab::Markdown::CrossProjectReference do
|
||||
include described_class
|
||||
|
||||
describe '#project_from_ref' do
|
||||
context 'when no project was referenced' do
|
||||
it 'returns the project from context' do
|
||||
project = double
|
||||
describe '#project_from_ref' do
|
||||
context 'when no project was referenced' do
|
||||
it 'returns the project from context' do
|
||||
project = double
|
||||
|
||||
allow(self).to receive(:context).and_return({ project: project })
|
||||
allow(self).to receive(:context).and_return({ project: project })
|
||||
|
||||
expect(project_from_ref(nil)).to eq project
|
||||
end
|
||||
expect(project_from_ref(nil)).to eq project
|
||||
end
|
||||
end
|
||||
|
||||
context 'when referenced project does not exist' do
|
||||
it 'returns nil' do
|
||||
expect(project_from_ref('invalid/reference')).to be_nil
|
||||
end
|
||||
context 'when referenced project does not exist' do
|
||||
it 'returns nil' do
|
||||
expect(project_from_ref('invalid/reference')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when referenced project exists' do
|
||||
it 'returns the referenced project' do
|
||||
project2 = double('referenced project')
|
||||
context 'when referenced project exists' do
|
||||
it 'returns the referenced project' do
|
||||
project2 = double('referenced project')
|
||||
|
||||
expect(Project).to receive(:find_with_namespace).
|
||||
with('cross/reference').and_return(project2)
|
||||
expect(Project).to receive(:find_with_namespace).
|
||||
with('cross/reference').and_return(project2)
|
||||
|
||||
expect(project_from_ref('cross/reference')).to eq project2
|
||||
end
|
||||
expect(project_from_ref('cross/reference')).to eq project2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe EmojiFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
before do
|
||||
ActionController::Base.asset_host = 'https://foo.com'
|
||||
end
|
||||
|
||||
it 'replaces supported emoji' do
|
||||
doc = filter('<p>:heart:</p>')
|
||||
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
|
||||
end
|
||||
|
||||
it 'ignores unsupported emoji' do
|
||||
exp = act = '<p>:foo:</p>'
|
||||
doc = filter(act)
|
||||
expect(doc.to_html).to match Regexp.escape(exp)
|
||||
end
|
||||
|
||||
it 'correctly encodes the URL' do
|
||||
doc = filter('<p>:+1:</p>')
|
||||
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
|
||||
end
|
||||
|
||||
it 'matches at the start of a string' do
|
||||
doc = filter(':+1:')
|
||||
expect(doc.css('img').size).to eq 1
|
||||
end
|
||||
|
||||
it 'matches at the end of a string' do
|
||||
doc = filter('This gets a :-1:')
|
||||
expect(doc.css('img').size).to eq 1
|
||||
end
|
||||
|
||||
it 'matches with adjacent text' do
|
||||
doc = filter('+1 (:+1:)')
|
||||
expect(doc.css('img').size).to eq 1
|
||||
end
|
||||
|
||||
it 'matches multiple emoji in a row' do
|
||||
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
|
||||
expect(doc.css('img').size).to eq 3
|
||||
end
|
||||
|
||||
it 'has a title attribute' do
|
||||
doc = filter(':-1:')
|
||||
expect(doc.css('img').first.attr('title')).to eq ':-1:'
|
||||
end
|
||||
|
||||
it 'has an alt attribute' do
|
||||
doc = filter(':-1:')
|
||||
expect(doc.css('img').first.attr('alt')).to eq ':-1:'
|
||||
end
|
||||
|
||||
it 'has an align attribute' do
|
||||
doc = filter(':8ball:')
|
||||
expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
|
||||
end
|
||||
|
||||
it 'has an emoji class' do
|
||||
doc = filter(':cat:')
|
||||
expect(doc.css('img').first.attr('class')).to eq 'emoji'
|
||||
end
|
||||
|
||||
it 'has height and width attributes' do
|
||||
doc = filter(':dog:')
|
||||
img = doc.css('img').first
|
||||
|
||||
expect(img.attr('width')).to eq '20'
|
||||
expect(img.attr('height')).to eq '20'
|
||||
end
|
||||
|
||||
it 'keeps whitespace intact' do
|
||||
doc = filter('This deserves a :+1:, big time.')
|
||||
|
||||
expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
|
||||
end
|
||||
|
||||
it 'uses a custom asset_root context' do
|
||||
root = Gitlab.config.gitlab.url + 'gitlab/root'
|
||||
|
||||
doc = filter(':smile:', asset_root: root)
|
||||
expect(doc.css('img').first.attr('src')).to start_with(root)
|
||||
end
|
||||
|
||||
it 'uses a custom asset_host context' do
|
||||
ActionController::Base.asset_host = 'https://cdn.example.com'
|
||||
|
||||
doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
|
||||
expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe ExternalIssueReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
def helper
|
||||
IssuesHelper
|
||||
end
|
||||
|
||||
let(:project) { create(:jira_project) }
|
||||
|
||||
context 'JIRA issue references' do
|
||||
let(:issue) { ExternalIssue.new('JIRA-123', project) }
|
||||
let(:reference) { issue.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
it 'ignores valid references when using default tracker' do
|
||||
expect(project).to receive(:default_issues_tracker?).and_return(true)
|
||||
|
||||
exp = act = "Issue #{reference}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('href'))
|
||||
.to eq helper.url_for_issue(reference, project)
|
||||
end
|
||||
|
||||
it 'links to the external tracker' do
|
||||
doc = filter("Issue #{reference}")
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).to eq "http://jira.example/browse/#{reference}"
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Issue (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
allow(project.external_issue_tracker).to receive(:title).
|
||||
and_return(%{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.text).to eq "Issue #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Issue #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe ExternalLinkFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'ignores elements without an href attribute' do
|
||||
exp = act = %q(<a id="ignored">Ignore Me</a>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'ignores non-HTTP(S) links' do
|
||||
exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'skips internal links' do
|
||||
internal = Gitlab.config.gitlab.url
|
||||
exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds rel="nofollow" to external links' do
|
||||
act = %q(<a href="https://google.com/">Google</a>)
|
||||
doc = filter(act)
|
||||
|
||||
expect(doc.at_css('a')).to have_attribute('rel')
|
||||
expect(doc.at_css('a')['rel']).to eq 'nofollow'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::AutolinkFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:link) { 'http://about.gitlab.com/' }
|
||||
|
||||
it 'does nothing when :autolink is false' do
|
||||
exp = act = link
|
||||
expect(filter(act, autolink: false).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'does nothing with non-link text' do
|
||||
exp = act = 'This text contains no links to autolink'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
context 'Rinku schemes' do
|
||||
it 'autolinks http' do
|
||||
doc = filter("See #{link}")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks https' do
|
||||
link = 'https://google.com/'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks ftp' do
|
||||
link = 'ftp://ftp.us.debian.org/debian/'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks short URLs' do
|
||||
link = 'http://localhost:3000/'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'accepts link_attr options' do
|
||||
doc = filter("See #{link}", link_attr: { class: 'custom' })
|
||||
|
||||
expect(doc.at_css('a')['class']).to eq 'custom'
|
||||
end
|
||||
|
||||
described_class::IGNORE_PARENTS.each do |elem|
|
||||
it "ignores valid links contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>See #{link}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'other schemes' do
|
||||
let(:link) { 'foo://bar.baz/' }
|
||||
|
||||
it 'autolinks smb' do
|
||||
link = 'smb:///Volumes/shared/foo.pdf'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'autolinks irc' do
|
||||
link = 'irc://irc.freenode.net/git'
|
||||
doc = filter("See #{link}")
|
||||
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
end
|
||||
|
||||
it 'does not include trailing punctuation' do
|
||||
doc = filter("See #{link}.")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
|
||||
doc = filter("See #{link}, ok?")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
|
||||
doc = filter("See #{link}...")
|
||||
expect(doc.at_css('a').text).to eq link
|
||||
end
|
||||
|
||||
it 'does not include trailing HTML entities' do
|
||||
doc = filter("See <<<#{link}>>>")
|
||||
|
||||
expect(doc.at_css('a')['href']).to eq link
|
||||
expect(doc.text).to eq "See <<<#{link}>>>"
|
||||
end
|
||||
|
||||
it 'accepts link_attr options' do
|
||||
doc = filter("See #{link}", link_attr: { class: 'custom' })
|
||||
expect(doc.at_css('a')['class']).to eq 'custom'
|
||||
end
|
||||
|
||||
described_class::IGNORE_PARENTS.each do |elem|
|
||||
it "ignores valid links contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>See #{link}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::CommitRangeReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:commit1) { project.commit }
|
||||
let(:commit2) { project.commit("HEAD~2") }
|
||||
|
||||
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
|
||||
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { range.to_reference }
|
||||
let(:reference2) { range2.to_reference }
|
||||
|
||||
it 'links to a valid two-dot reference' do
|
||||
doc = filter("See #{reference2}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
|
||||
end
|
||||
|
||||
it 'links to a valid three-dot reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
|
||||
end
|
||||
|
||||
it 'links to a valid short ID' do
|
||||
reference = "#{commit1.short_id}...#{commit2.id}"
|
||||
reference2 = "#{commit1.id}...#{commit2.short_id}"
|
||||
|
||||
exp = commit1.short_id + '...' + commit2.short_id
|
||||
|
||||
expect(filter("See #{reference}").css('a').first.text).to eq exp
|
||||
expect(filter("See #{reference2}").css('a').first.text).to eq exp
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("See (#{reference}.)")
|
||||
|
||||
exp = Regexp.escape(range.to_s)
|
||||
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs' do
|
||||
exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
|
||||
|
||||
expect(project).to receive(:valid_repo?).and_return(true)
|
||||
expect(project.repository).to receive(:commit).with(commit1.id.reverse)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq range.reference_title
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-commit-range attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-commit-range')
|
||||
expect(link.attr('data-commit-range')).to eq range.to_reference
|
||||
end
|
||||
|
||||
it 'supports an :only_path option' do
|
||||
doc = filter("See #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit_range]).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:project, :public, namespace: namespace) }
|
||||
let(:reference) { range.to_reference(project) }
|
||||
|
||||
before do
|
||||
range.project = project2
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
|
||||
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs on the referenced project' do
|
||||
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
|
||||
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit_range]).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::CommitReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:commit) { project.commit }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { commit.id }
|
||||
|
||||
# Let's test a variety of commit SHA sizes just to be paranoid
|
||||
[6, 8, 12, 18, 20, 32, 40].each do |size|
|
||||
it "links to a valid reference of #{size} characters" do
|
||||
doc = filter("See #{reference[0...size]}")
|
||||
|
||||
expect(doc.css('a').first.text).to eq commit.short_id
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_commit_url(project.namespace, project, reference)
|
||||
end
|
||||
end
|
||||
|
||||
it 'always uses the short ID as the link text' do
|
||||
doc = filter("See #{commit.id}")
|
||||
expect(doc.text).to eq "See #{commit.short_id}"
|
||||
|
||||
doc = filter("See #{commit.id[0...6]}")
|
||||
expect(doc.text).to eq "See #{commit.short_id}"
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("See (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs' do
|
||||
invalid = invalidate_reference(reference)
|
||||
exp = act = "See #{invalid}"
|
||||
|
||||
expect(project).to receive(:valid_repo?).and_return(true)
|
||||
expect(project.repository).to receive(:commit).with(invalid)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq commit.link_title
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.text).to eq "See #{commit.short_id}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("See #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-commit attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-commit')
|
||||
expect(link.attr('data-commit')).to eq commit.id
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("See #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit]).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:project, :public, namespace: namespace) }
|
||||
let(:commit) { project2.commit }
|
||||
let(:reference) { commit.to_reference(project) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
|
||||
exp = Regexp.escape(project2.to_reference)
|
||||
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid commit IDs on the referenced project' do
|
||||
exp = act = "Committed #{invalidate_reference(reference)}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("See #{reference}")
|
||||
expect(result[:references][:commit]).not_to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::EmojiFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
before do
|
||||
@original_asset_host = ActionController::Base.asset_host
|
||||
ActionController::Base.asset_host = 'https://foo.com'
|
||||
end
|
||||
|
||||
after do
|
||||
ActionController::Base.asset_host = @original_asset_host
|
||||
end
|
||||
|
||||
it 'replaces supported emoji' do
|
||||
doc = filter('<p>:heart:</p>')
|
||||
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
|
||||
end
|
||||
|
||||
it 'ignores unsupported emoji' do
|
||||
exp = act = '<p>:foo:</p>'
|
||||
doc = filter(act)
|
||||
expect(doc.to_html).to match Regexp.escape(exp)
|
||||
end
|
||||
|
||||
it 'correctly encodes the URL' do
|
||||
doc = filter('<p>:+1:</p>')
|
||||
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
|
||||
end
|
||||
|
||||
it 'matches at the start of a string' do
|
||||
doc = filter(':+1:')
|
||||
expect(doc.css('img').size).to eq 1
|
||||
end
|
||||
|
||||
it 'matches at the end of a string' do
|
||||
doc = filter('This gets a :-1:')
|
||||
expect(doc.css('img').size).to eq 1
|
||||
end
|
||||
|
||||
it 'matches with adjacent text' do
|
||||
doc = filter('+1 (:+1:)')
|
||||
expect(doc.css('img').size).to eq 1
|
||||
end
|
||||
|
||||
it 'matches multiple emoji in a row' do
|
||||
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
|
||||
expect(doc.css('img').size).to eq 3
|
||||
end
|
||||
|
||||
it 'has a title attribute' do
|
||||
doc = filter(':-1:')
|
||||
expect(doc.css('img').first.attr('title')).to eq ':-1:'
|
||||
end
|
||||
|
||||
it 'has an alt attribute' do
|
||||
doc = filter(':-1:')
|
||||
expect(doc.css('img').first.attr('alt')).to eq ':-1:'
|
||||
end
|
||||
|
||||
it 'has an align attribute' do
|
||||
doc = filter(':8ball:')
|
||||
expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
|
||||
end
|
||||
|
||||
it 'has an emoji class' do
|
||||
doc = filter(':cat:')
|
||||
expect(doc.css('img').first.attr('class')).to eq 'emoji'
|
||||
end
|
||||
|
||||
it 'has height and width attributes' do
|
||||
doc = filter(':dog:')
|
||||
img = doc.css('img').first
|
||||
|
||||
expect(img.attr('width')).to eq '20'
|
||||
expect(img.attr('height')).to eq '20'
|
||||
end
|
||||
|
||||
it 'keeps whitespace intact' do
|
||||
doc = filter('This deserves a :+1:, big time.')
|
||||
|
||||
expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
|
||||
end
|
||||
|
||||
it 'uses a custom asset_root context' do
|
||||
root = Gitlab.config.gitlab.url + 'gitlab/root'
|
||||
|
||||
doc = filter(':smile:', asset_root: root)
|
||||
expect(doc.css('img').first.attr('src')).to start_with(root)
|
||||
end
|
||||
|
||||
it 'uses a custom asset_host context' do
|
||||
ActionController::Base.asset_host = 'https://cdn.example.com'
|
||||
|
||||
doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
|
||||
expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::ExternalIssueReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
def helper
|
||||
IssuesHelper
|
||||
end
|
||||
|
||||
let(:project) { create(:jira_project) }
|
||||
|
||||
context 'JIRA issue references' do
|
||||
let(:issue) { ExternalIssue.new('JIRA-123', project) }
|
||||
let(:reference) { issue.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
it 'ignores valid references when using default tracker' do
|
||||
expect(project).to receive(:default_issues_tracker?).and_return(true)
|
||||
|
||||
exp = act = "Issue #{reference}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('href'))
|
||||
.to eq helper.url_for_issue(reference, project)
|
||||
end
|
||||
|
||||
it 'links to the external tracker' do
|
||||
doc = filter("Issue #{reference}")
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).to eq "http://jira.example/browse/#{reference}"
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Issue (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
allow(project.external_issue_tracker).to receive(:title).
|
||||
and_return(%{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.text).to eq "Issue #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Issue #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::ExternalLinkFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'ignores elements without an href attribute' do
|
||||
exp = act = %q(<a id="ignored">Ignore Me</a>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'ignores non-HTTP(S) links' do
|
||||
exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'skips internal links' do
|
||||
internal = Gitlab.config.gitlab.url
|
||||
exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds rel="nofollow" to external links' do
|
||||
act = %q(<a href="https://google.com/">Google</a>)
|
||||
doc = filter(act)
|
||||
|
||||
expect(doc.at_css('a')).to have_attribute('rel')
|
||||
expect(doc.at_css('a')['rel']).to eq 'nofollow'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::IssueReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
def helper
|
||||
IssuesHelper
|
||||
end
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { issue.to_reference }
|
||||
|
||||
it 'ignores valid references when using non-default tracker' do
|
||||
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
|
||||
|
||||
exp = act = "Issue #{reference}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("Fixed #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq helper.url_for_issue(issue.iid, project)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid issue IDs' do
|
||||
invalid = invalidate_reference(reference)
|
||||
exp = act = "Fixed #{invalid}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
issue.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.text).to eq "Issue #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Issue #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-issue attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-issue')
|
||||
expect(link.attr('data-issue')).to eq issue.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Issue #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Fixed #{reference}")
|
||||
expect(result[:references][:issue]).to eq [issue]
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:empty_project, :public, namespace: namespace) }
|
||||
let(:issue) { create(:issue, project: project2) }
|
||||
let(:reference) { issue.to_reference(project) }
|
||||
|
||||
it 'ignores valid references when cross-reference project uses external tracker' do
|
||||
expect_any_instance_of(Project).to receive(:get_issue).
|
||||
with(issue.iid).and_return(nil)
|
||||
|
||||
exp = act = "Issue #{reference}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq helper.url_for_issue(issue.iid, project2)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid issue IDs on the referenced project' do
|
||||
exp = act = "Fixed #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Fixed #{reference}")
|
||||
expect(result[:references][:issue]).to eq [issue]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
require 'spec_helper'
|
||||
require 'html/pipeline'
|
||||
|
||||
describe Gitlab::Markdown::LabelReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:label) { create(:label, project: project) }
|
||||
let(:reference) { label.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Label #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Label #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Label #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-label attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-label')
|
||||
expect(link.attr('data-label')).to eq label.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Label #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Label #{reference}")
|
||||
expect(result[:references][:label]).to eq [label]
|
||||
end
|
||||
|
||||
describe 'label span element' do
|
||||
it 'includes default classes' do
|
||||
doc = filter("Label #{reference}")
|
||||
expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
|
||||
end
|
||||
|
||||
it 'includes a style attribute' do
|
||||
doc = filter("Label #{reference}")
|
||||
expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'Integer-based references' do
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_issues_url(project.namespace, project, label_name: label.name)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Label (#{reference}.)")
|
||||
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
|
||||
end
|
||||
|
||||
it 'ignores invalid label IDs' do
|
||||
exp = act = "Label #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'String-based single-word references' do
|
||||
let(:label) { create(:label, name: 'gfm', project: project) }
|
||||
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_issues_url(project.namespace, project, label_name: label.name)
|
||||
expect(doc.text).to eq 'See gfm'
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Label (#{reference}.)")
|
||||
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
|
||||
end
|
||||
|
||||
it 'ignores invalid label names' do
|
||||
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'String-based multi-word references in quotes' do
|
||||
let(:label) { create(:label, name: 'gfm references', project: project) }
|
||||
let(:reference) { label.to_reference(:name) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_issues_url(project.namespace, project, label_name: label.name)
|
||||
expect(doc.text).to eq 'See gfm references'
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Label (#{reference}.)")
|
||||
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
|
||||
end
|
||||
|
||||
it 'ignores invalid label names' do
|
||||
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edge cases' do
|
||||
it 'gracefully handles non-references matching the pattern' do
|
||||
exp = act = '(format nil "~0f" 3.0) ; 3.0'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::MergeRequestReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:merge) { create(:merge_request, source_project: project) }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { merge.to_reference }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_merge_request_url(project.namespace, project, merge)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Merge (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid merge IDs' do
|
||||
exp = act = "Merge #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Merge #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
merge.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Merge #{reference}")
|
||||
expect(doc.text).to eq "Merge #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Merge #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Merge #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-merge-request attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-merge-request')
|
||||
expect(link.attr('data-merge-request')).to eq merge.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Merge #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Merge #{reference}")
|
||||
expect(result[:references][:merge_request]).to eq [merge]
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:project, :public, namespace: namespace) }
|
||||
let(:merge) { create(:merge_request, source_project: project2) }
|
||||
let(:reference) { merge.to_reference(project) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_merge_request_url(project2.namespace,
|
||||
project, merge)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Merge (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid merge IDs on the referenced project' do
|
||||
exp = act = "Merge #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Merge #{reference}")
|
||||
expect(result[:references][:merge_request]).to eq [merge]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::RedactorFilter do
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'ignores non-GFM links' do
|
||||
html = %(See <a href="https://google.com/">Google</a>)
|
||||
doc = filter(html, current_user: double)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
def reference_link(data)
|
||||
link_to('text', '', class: 'gfm', data: data)
|
||||
end
|
||||
|
||||
context 'with data-project' do
|
||||
it 'removes unpermitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
|
||||
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 0
|
||||
end
|
||||
|
||||
it 'allows permitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
project.team << [user, :master]
|
||||
|
||||
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'handles invalid Project references' do
|
||||
link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
|
||||
|
||||
expect { filter(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "for user references" do
|
||||
|
||||
context 'with data-group' do
|
||||
it 'removes unpermitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 0
|
||||
end
|
||||
|
||||
it 'allows permitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
group.add_developer(user)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'handles invalid Group references' do
|
||||
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
|
||||
expect { filter(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'with data-user' do
|
||||
it 'allows any User reference' do
|
||||
user = create(:user)
|
||||
|
||||
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
doc = filter(link)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::ReferenceGathererFilter do
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include FilterSpecHelper
|
||||
|
||||
def reference_link(data)
|
||||
link_to('text', '', class: 'gfm', data: data)
|
||||
end
|
||||
|
||||
context "for issue references" do
|
||||
|
||||
context 'with data-project' do
|
||||
it 'removes unpermitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
issue = create(:issue, project: project)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:issue]).to be_empty
|
||||
end
|
||||
|
||||
it 'allows permitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
issue = create(:issue, project: project)
|
||||
project.team << [user, :master]
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:issue]).to eq([issue])
|
||||
end
|
||||
|
||||
it 'handles invalid Project references' do
|
||||
link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
|
||||
|
||||
expect { pipeline_result(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for user references" do
|
||||
|
||||
context 'with data-group' do
|
||||
it 'removes unpermitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:user]).to be_empty
|
||||
end
|
||||
|
||||
it 'allows permitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
group.add_developer(user)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:user]).to eq([user])
|
||||
end
|
||||
|
||||
it 'handles invalid Group references' do
|
||||
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
|
||||
expect { pipeline_result(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'with data-user' do
|
||||
it 'allows any User reference' do
|
||||
user = create(:user)
|
||||
|
||||
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
result = pipeline_result(link)
|
||||
|
||||
expect(result[:references][:user]).to eq([user])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::RelativeLinkFilter do
|
||||
def filter(doc, contexts = {})
|
||||
contexts.reverse_merge!({
|
||||
commit: project.commit,
|
||||
project: project,
|
||||
project_wiki: project_wiki,
|
||||
ref: ref,
|
||||
requested_path: requested_path
|
||||
})
|
||||
|
||||
described_class.call(doc, contexts)
|
||||
end
|
||||
|
||||
def image(path)
|
||||
%(<img src="#{path}" />)
|
||||
end
|
||||
|
||||
def link(path)
|
||||
%(<a href="#{path}">#{path}</a>)
|
||||
end
|
||||
|
||||
let(:project) { create(:project) }
|
||||
let(:project_path) { project.path_with_namespace }
|
||||
let(:ref) { 'markdown' }
|
||||
let(:project_wiki) { nil }
|
||||
let(:requested_path) { '/' }
|
||||
|
||||
shared_examples :preserve_unchanged do
|
||||
it 'does not modify any relative URL in anchor' do
|
||||
doc = filter(link('README.md'))
|
||||
expect(doc.at_css('a')['href']).to eq 'README.md'
|
||||
end
|
||||
|
||||
it 'does not modify any relative URL in image' do
|
||||
doc = filter(image('files/images/logo-black.png'))
|
||||
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples :relative_to_requested do
|
||||
it 'rebuilds URL relative to the requested path' do
|
||||
doc = filter(link('users.md'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a project_wiki' do
|
||||
let(:project_wiki) { double('ProjectWiki') }
|
||||
include_examples :preserve_unchanged
|
||||
end
|
||||
|
||||
context 'without a repository' do
|
||||
let(:project) { create(:empty_project) }
|
||||
include_examples :preserve_unchanged
|
||||
end
|
||||
|
||||
context 'with an empty repository' do
|
||||
let(:project) { create(:project_empty_repo) }
|
||||
include_examples :preserve_unchanged
|
||||
end
|
||||
|
||||
it 'does not raise an exception on invalid URIs' do
|
||||
act = link("://foo")
|
||||
expect { filter(act) }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'with a valid repository' do
|
||||
it 'rebuilds relative URL for a file in the repo' do
|
||||
doc = filter(link('doc/api/README.md'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a file in the repo up one directory' do
|
||||
relative_link = link('../api/README.md')
|
||||
doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
|
||||
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a file in the repo up multiple directories' do
|
||||
relative_link = link('../../../api/README.md')
|
||||
doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md')
|
||||
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a file in the repo with an anchor' do
|
||||
doc = filter(link('README.md#section'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/README.md#section"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a directory in the repo' do
|
||||
doc = filter(link('doc/api/'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/tree/#{ref}/doc/api"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for an image in the repo' do
|
||||
doc = filter(link('files/images/logo-black.png'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
|
||||
end
|
||||
|
||||
it 'does not modify relative URL with an anchor only' do
|
||||
doc = filter(link('#section-1'))
|
||||
expect(doc.at_css('a')['href']).to eq '#section-1'
|
||||
end
|
||||
|
||||
it 'does not modify absolute URL' do
|
||||
doc = filter(link('http://example.com'))
|
||||
expect(doc.at_css('a')['href']).to eq 'http://example.com'
|
||||
end
|
||||
|
||||
it 'supports Unicode filenames' do
|
||||
path = 'files/images/한글.png'
|
||||
escaped = Addressable::URI.escape(path)
|
||||
|
||||
# Stub these methods so the file doesn't actually need to be in the repo
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:file_exists?).and_return(true)
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:image?).with(path).and_return(true)
|
||||
|
||||
doc = filter(image(escaped))
|
||||
expect(doc.at_css('img')['src']).to match '/raw/'
|
||||
end
|
||||
|
||||
context 'when requested path is a file in the repo' do
|
||||
let(:requested_path) { 'doc/api/README.md' }
|
||||
include_examples :relative_to_requested
|
||||
end
|
||||
|
||||
context 'when requested path is a directory in the repo' do
|
||||
let(:requested_path) { 'doc/api' }
|
||||
include_examples :relative_to_requested
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::SanitizationFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
describe 'default whitelist' do
|
||||
it 'sanitizes tags that are not whitelisted' do
|
||||
act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
|
||||
exp = 'no inputs and no blinks'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes tag attributes' do
|
||||
act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
|
||||
exp = %q{<a href="http://example.com/bar.html">Text</a>}
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes javascript in attributes' do
|
||||
act = %q(<a href="javascript:alert('foo')">Text</a>)
|
||||
exp = '<a>Text</a>'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'allows whitelisted HTML tags from the user' do
|
||||
exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes `class` attribute on any element' do
|
||||
act = %q{<strong class="foo">Strong</strong>}
|
||||
expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
|
||||
end
|
||||
|
||||
it 'sanitizes `id` attribute on any element' do
|
||||
act = %q{<em id="foo">Emphasis</em>}
|
||||
expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'custom whitelist' do
|
||||
it 'customizes the whitelist only once' do
|
||||
instance = described_class.new('Foo')
|
||||
3.times { instance.whitelist }
|
||||
|
||||
expect(instance.whitelist[:transformers].size).to eq 5
|
||||
end
|
||||
|
||||
it 'allows syntax highlighting' do
|
||||
exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes `class` attribute from non-highlight spans' do
|
||||
act = %q{<span class="k">def</span>}
|
||||
expect(filter(act).to_html).to eq %q{<span>def</span>}
|
||||
end
|
||||
|
||||
it 'allows `style` attribute on table elements' do
|
||||
html = <<-HTML.strip_heredoc
|
||||
<table>
|
||||
<tr><th style="text-align: center">Head</th></tr>
|
||||
<tr><td style="text-align: right">Body</th></tr>
|
||||
</table>
|
||||
HTML
|
||||
|
||||
doc = filter(html)
|
||||
|
||||
expect(doc.at_css('th')['style']).to eq 'text-align: center'
|
||||
expect(doc.at_css('td')['style']).to eq 'text-align: right'
|
||||
end
|
||||
|
||||
it 'allows `span` elements' do
|
||||
exp = act = %q{<span>Hello</span>}
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'removes `rel` attribute from `a` elements' do
|
||||
act = %q{<a href="#" rel="nofollow">Link</a>}
|
||||
exp = %q{<a href="#">Link</a>}
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
# Adapted from the Sanitize test suite: http://git.io/vczrM
|
||||
protocols = {
|
||||
'protocol-based JS injection: simple, no spaces' => {
|
||||
input: '<a href="javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: simple, spaces before' => {
|
||||
input: '<a href="javascript :alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: simple, spaces after' => {
|
||||
input: '<a href="javascript: alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: simple, spaces before and after' => {
|
||||
input: '<a href="javascript : alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: preceding colon' => {
|
||||
input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: UTF-8 encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: long UTF-8 encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
|
||||
input: '<a href=javascript:alert('XSS')>foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: hex encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: long hex encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: hex encoding without semicolons' => {
|
||||
input: '<a href=javascript:alert('XSS')>foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: null char' => {
|
||||
input: "<a href=java\0script:alert(\"XSS\")>foo</a>",
|
||||
output: '<a href="java"></a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: spaces and entities' => {
|
||||
input: '<a href="  javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a href="">foo</a>'
|
||||
},
|
||||
}
|
||||
|
||||
protocols.each do |name, data|
|
||||
it "handles #{name}" do
|
||||
doc = filter(data[:input])
|
||||
|
||||
expect(doc.to_html).to eq data[:output]
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows non-standard anchor schemes' do
|
||||
exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
|
||||
act = filter(exp)
|
||||
|
||||
expect(act.to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'allows relative links' do
|
||||
exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
|
||||
act = filter(exp)
|
||||
|
||||
expect(act.to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inline_sanitization is true' do
|
||||
it 'uses a stricter whitelist' do
|
||||
doc = filter('<h1>Description</h1>', inline_sanitization: true)
|
||||
expect(doc.to_html.strip).to eq 'Description'
|
||||
end
|
||||
|
||||
%w(pre code img ol ul li).each do |elem|
|
||||
it "removes '#{elem}' elements" do
|
||||
act = "<#{elem}>Description</#{elem}>"
|
||||
expect(filter(act, inline_sanitization: true).to_html.strip).
|
||||
to eq 'Description'
|
||||
end
|
||||
end
|
||||
|
||||
%w(b i strong em a ins del sup sub p).each do |elem|
|
||||
it "still allows '#{elem}' elements" do
|
||||
exp = act = "<#{elem}>Description</#{elem}>"
|
||||
expect(filter(act, inline_sanitization: true).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::SnippetReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:snippet) { create(:project_snippet, project: project) }
|
||||
let(:reference) { snippet.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_snippet_url(project.namespace, project, snippet)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Snippet (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid snippet IDs' do
|
||||
exp = act = "Snippet #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Snippet #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
snippet.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Snippet #{reference}")
|
||||
expect(doc.text).to eq "Snippet #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Snippet #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Snippet #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-snippet attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-snippet')
|
||||
expect(link.attr('data-snippet')).to eq snippet.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Snippet #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Snippet #{reference}")
|
||||
expect(result[:references][:snippet]).to eq [snippet]
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:empty_project, :public, namespace: namespace) }
|
||||
let(:snippet) { create(:project_snippet, project: project2) }
|
||||
let(:reference) { snippet.to_reference(project) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("See (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid snippet IDs on the referenced project' do
|
||||
exp = act = "See #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Snippet #{reference}")
|
||||
expect(result[:references][:snippet]).to eq [snippet]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::SyntaxHighlightFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'highlights valid code blocks' do
|
||||
result = filter('<pre><code>def fun end</code>')
|
||||
expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
|
||||
end
|
||||
|
||||
it 'passes through invalid code blocks' do
|
||||
allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError)
|
||||
|
||||
result = filter('<pre><code>This is a test</code></pre>')
|
||||
expect(result.to_html).to eq('<pre>This is a test</pre>')
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::TableOfContentsFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
def header(level, text)
|
||||
"<h#{level}>#{text}</h#{level}>\n"
|
||||
end
|
||||
|
||||
it 'does nothing when :no_header_anchors is truthy' do
|
||||
exp = act = header(1, 'Header')
|
||||
expect(filter(act, no_header_anchors: 1).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'does nothing with empty headers' do
|
||||
exp = act = header(1, nil)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
1.upto(6) do |i|
|
||||
it "processes h#{i} elements" do
|
||||
html = header(i, "Header #{i}")
|
||||
doc = filter(html)
|
||||
|
||||
expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'anchor tag' do
|
||||
it 'has an `anchor` class' do
|
||||
doc = filter(header(1, 'Header'))
|
||||
expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
|
||||
end
|
||||
|
||||
it 'links to the id' do
|
||||
doc = filter(header(1, 'Header'))
|
||||
expect(doc.css('h1 a').first.attr('href')).to eq '#header'
|
||||
end
|
||||
|
||||
describe 'generated IDs' do
|
||||
it 'translates spaces to dashes' do
|
||||
doc = filter(header(1, 'This header has spaces in it'))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
|
||||
end
|
||||
|
||||
it 'squeezes multiple spaces and dashes' do
|
||||
doc = filter(header(1, 'This---header is poorly-formatted'))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
|
||||
end
|
||||
|
||||
it 'removes punctuation' do
|
||||
doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
|
||||
end
|
||||
|
||||
it 'appends a unique number to duplicates' do
|
||||
doc = filter(header(1, 'One') + header(2, 'One'))
|
||||
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'one'
|
||||
expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
|
||||
end
|
||||
|
||||
it 'supports Unicode' do
|
||||
doc = filter(header(1, '한글'))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq '한글'
|
||||
expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'result' do
|
||||
def result(html)
|
||||
HTML::Pipeline.new([described_class]).call(html)
|
||||
end
|
||||
|
||||
let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) }
|
||||
let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) }
|
||||
|
||||
it 'is contained within a `ul` element' do
|
||||
expect(doc.children.first.name).to eq 'ul'
|
||||
expect(doc.children.first.attr('class')).to eq 'section-nav'
|
||||
end
|
||||
|
||||
it 'contains an `li` element for each header' do
|
||||
expect(doc.css('li').length).to eq 2
|
||||
|
||||
links = doc.css('li a')
|
||||
|
||||
expect(links.first.attr('href')).to eq '#header-1'
|
||||
expect(links.first.text).to eq 'Header 1'
|
||||
expect(links.last.attr('href')).to eq '#header-2'
|
||||
expect(links.last.text).to eq 'Header 2'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::TaskListFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'does not apply `task-list` class to non-task lists' do
|
||||
exp = act = %(<ul><li>Item</li></ul>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::UploadLinkFilter do
|
||||
def filter(doc, contexts = {})
|
||||
contexts.reverse_merge!({
|
||||
project: project
|
||||
})
|
||||
|
||||
described_class.call(doc, contexts)
|
||||
end
|
||||
|
||||
def image(path)
|
||||
%(<img src="#{path}" />)
|
||||
end
|
||||
|
||||
def link(path)
|
||||
%(<a href="#{path}">#{path}</a>)
|
||||
end
|
||||
|
||||
let(:project) { create(:project) }
|
||||
|
||||
shared_examples :preserve_unchanged do
|
||||
it 'does not modify any relative URL in anchor' do
|
||||
doc = filter(link('README.md'))
|
||||
expect(doc.at_css('a')['href']).to eq 'README.md'
|
||||
end
|
||||
|
||||
it 'does not modify any relative URL in image' do
|
||||
doc = filter(image('files/images/logo-black.png'))
|
||||
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not raise an exception on invalid URIs' do
|
||||
act = link("://foo")
|
||||
expect { filter(act) }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'with a valid repository' do
|
||||
it 'rebuilds relative URL for a link' do
|
||||
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for an image' do
|
||||
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
|
||||
end
|
||||
|
||||
it 'does not modify absolute URL' do
|
||||
doc = filter(link('http://example.com'))
|
||||
expect(doc.at_css('a')['href']).to eq 'http://example.com'
|
||||
end
|
||||
|
||||
it 'supports Unicode filenames' do
|
||||
path = '/uploads/한글.png'
|
||||
escaped = Addressable::URI.escape(path)
|
||||
|
||||
# Stub these methods so the file doesn't actually need to be in the repo
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:file_exists?).and_return(true)
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:image?).with(path).and_return(true)
|
||||
|
||||
doc = filter(image(escaped))
|
||||
expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Markdown::UserReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:user) { create(:user) }
|
||||
let(:reference) { user.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
it 'ignores invalid users' do
|
||||
exp = act = "Hey #{invalidate_reference(reference)}"
|
||||
expect(filter(act).to_html).to eq(exp)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'mentioning @all' do
|
||||
let(:reference) { User.reference_prefix + 'all' }
|
||||
|
||||
before do
|
||||
project.team << [project.creator, :developer]
|
||||
end
|
||||
|
||||
it 'supports a special @all mention' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').length).to eq 1
|
||||
expect(doc.css('a').first.attr('href'))
|
||||
.to eq urls.namespace_project_url(project.namespace, project)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Hey #{reference}")
|
||||
expect(result[:references][:user]).to eq [project.creator]
|
||||
end
|
||||
end
|
||||
|
||||
context 'mentioning a user' do
|
||||
it 'links to a User' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
|
||||
end
|
||||
|
||||
it 'links to a User with a period' do
|
||||
user = create(:user, name: 'alphA.Beta')
|
||||
|
||||
doc = filter("Hey #{user.to_reference}")
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'links to a User with an underscore' do
|
||||
user = create(:user, name: 'ping_pong_king')
|
||||
|
||||
doc = filter("Hey #{user.to_reference}")
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'includes a data-user attribute' do
|
||||
doc = filter("Hey #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-user')
|
||||
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Hey #{reference}")
|
||||
expect(result[:references][:user]).to eq [user]
|
||||
end
|
||||
end
|
||||
|
||||
context 'mentioning a group' do
|
||||
let(:group) { create(:group) }
|
||||
let(:reference) { group.to_reference }
|
||||
|
||||
it 'links to the Group' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
|
||||
end
|
||||
|
||||
it 'includes a data-group attribute' do
|
||||
doc = filter("Hey #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-group')
|
||||
expect(link.attr('data-group')).to eq group.id.to_s
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Hey #{reference}")
|
||||
expect(result[:references][:user]).to eq group.users
|
||||
end
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Mention me (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Hey #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.user_path(user)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe IssueReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
def helper
|
||||
IssuesHelper
|
||||
end
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { issue.to_reference }
|
||||
|
||||
it 'ignores valid references when using non-default tracker' do
|
||||
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
|
||||
|
||||
exp = act = "Issue #{reference}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("Fixed #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq helper.url_for_issue(issue.iid, project)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid issue IDs' do
|
||||
invalid = invalidate_reference(reference)
|
||||
exp = act = "Fixed #{invalid}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
issue.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.text).to eq "Issue #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Issue #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Issue #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-issue attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-issue')
|
||||
expect(link.attr('data-issue')).to eq issue.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Issue #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Fixed #{reference}")
|
||||
expect(result[:references][:issue]).to eq [issue]
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:empty_project, :public, namespace: namespace) }
|
||||
let(:issue) { create(:issue, project: project2) }
|
||||
let(:reference) { issue.to_reference(project) }
|
||||
|
||||
it 'ignores valid references when cross-reference project uses external tracker' do
|
||||
expect_any_instance_of(Project).to receive(:get_issue).
|
||||
with(issue.iid).and_return(nil)
|
||||
|
||||
exp = act = "Issue #{reference}"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq helper.url_for_issue(issue.iid, project2)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Fixed (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid issue IDs on the referenced project' do
|
||||
exp = act = "Fixed #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Fixed #{reference}")
|
||||
expect(result[:references][:issue]).to eq [issue]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'html/pipeline'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe LabelReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:label) { create(:label, project: project) }
|
||||
let(:reference) { label.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Label #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Label #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Label #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-label attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-label')
|
||||
expect(link.attr('data-label')).to eq label.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Label #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Label #{reference}")
|
||||
expect(result[:references][:label]).to eq [label]
|
||||
end
|
||||
|
||||
describe 'label span element' do
|
||||
it 'includes default classes' do
|
||||
doc = filter("Label #{reference}")
|
||||
expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
|
||||
end
|
||||
|
||||
it 'includes a style attribute' do
|
||||
doc = filter("Label #{reference}")
|
||||
expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'Integer-based references' do
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_issues_url(project.namespace, project, label_name: label.name)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Label (#{reference}.)")
|
||||
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
|
||||
end
|
||||
|
||||
it 'ignores invalid label IDs' do
|
||||
exp = act = "Label #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'String-based single-word references' do
|
||||
let(:label) { create(:label, name: 'gfm', project: project) }
|
||||
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_issues_url(project.namespace, project, label_name: label.name)
|
||||
expect(doc.text).to eq 'See gfm'
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Label (#{reference}.)")
|
||||
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
|
||||
end
|
||||
|
||||
it 'ignores invalid label names' do
|
||||
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'String-based multi-word references in quotes' do
|
||||
let(:label) { create(:label, name: 'gfm references', project: project) }
|
||||
let(:reference) { label.to_reference(:name) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_issues_url(project.namespace, project, label_name: label.name)
|
||||
expect(doc.text).to eq 'See gfm references'
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Label (#{reference}.)")
|
||||
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
|
||||
end
|
||||
|
||||
it 'ignores invalid label names' do
|
||||
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edge cases' do
|
||||
it 'gracefully handles non-references matching the pattern' do
|
||||
exp = act = '(format nil "~0f" 3.0) ; 3.0'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe MergeRequestReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:merge) { create(:merge_request, source_project: project) }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { merge.to_reference }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_merge_request_url(project.namespace, project, merge)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Merge (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid merge IDs' do
|
||||
exp = act = "Merge #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Merge #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
merge.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Merge #{reference}")
|
||||
expect(doc.text).to eq "Merge #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Merge #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Merge #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-merge-request attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-merge-request')
|
||||
expect(link.attr('data-merge-request')).to eq merge.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Merge #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Merge #{reference}")
|
||||
expect(result[:references][:merge_request]).to eq [merge]
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:project, :public, namespace: namespace) }
|
||||
let(:merge) { create(:merge_request, source_project: project2) }
|
||||
let(:reference) { merge.to_reference(project) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_merge_request_url(project2.namespace,
|
||||
project, merge)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Merge (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid merge IDs on the referenced project' do
|
||||
exp = act = "Merge #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Merge #{reference}")
|
||||
expect(result[:references][:merge_request]).to eq [merge]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe RedactorFilter do
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'ignores non-GFM links' do
|
||||
html = %(See <a href="https://google.com/">Google</a>)
|
||||
doc = filter(html, current_user: double)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
def reference_link(data)
|
||||
link_to('text', '', class: 'gfm', data: data)
|
||||
end
|
||||
|
||||
context 'with data-project' do
|
||||
it 'removes unpermitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
|
||||
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 0
|
||||
end
|
||||
|
||||
it 'allows permitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
project.team << [user, :master]
|
||||
|
||||
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'handles invalid Project references' do
|
||||
link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
|
||||
|
||||
expect { filter(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "for user references" do
|
||||
|
||||
context 'with data-group' do
|
||||
it 'removes unpermitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 0
|
||||
end
|
||||
|
||||
it 'allows permitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
group.add_developer(user)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'handles invalid Group references' do
|
||||
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
|
||||
expect { filter(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'with data-user' do
|
||||
it 'allows any User reference' do
|
||||
user = create(:user)
|
||||
|
||||
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
doc = filter(link)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe ReferenceGathererFilter do
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include FilterSpecHelper
|
||||
|
||||
def reference_link(data)
|
||||
link_to('text', '', class: 'gfm', data: data)
|
||||
end
|
||||
|
||||
context "for issue references" do
|
||||
|
||||
context 'with data-project' do
|
||||
it 'removes unpermitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
issue = create(:issue, project: project)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:issue]).to be_empty
|
||||
end
|
||||
|
||||
it 'allows permitted Project references' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project)
|
||||
issue = create(:issue, project: project)
|
||||
project.team << [user, :master]
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:issue]).to eq([issue])
|
||||
end
|
||||
|
||||
it 'handles invalid Project references' do
|
||||
link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
|
||||
|
||||
expect { pipeline_result(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for user references" do
|
||||
|
||||
context 'with data-group' do
|
||||
it 'removes unpermitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:user]).to be_empty
|
||||
end
|
||||
|
||||
it 'allows permitted Group references' do
|
||||
user = create(:user)
|
||||
group = create(:group)
|
||||
group.add_developer(user)
|
||||
|
||||
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
result = pipeline_result(link, current_user: user)
|
||||
|
||||
expect(result[:references][:user]).to eq([user])
|
||||
end
|
||||
|
||||
it 'handles invalid Group references' do
|
||||
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
|
||||
expect { pipeline_result(link) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'with data-user' do
|
||||
it 'allows any User reference' do
|
||||
user = create(:user)
|
||||
|
||||
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
|
||||
result = pipeline_result(link)
|
||||
|
||||
expect(result[:references][:user]).to eq([user])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe RelativeLinkFilter do
|
||||
def filter(doc, contexts = {})
|
||||
contexts.reverse_merge!({
|
||||
commit: project.commit,
|
||||
project: project,
|
||||
project_wiki: project_wiki,
|
||||
ref: ref,
|
||||
requested_path: requested_path
|
||||
})
|
||||
|
||||
described_class.call(doc, contexts)
|
||||
end
|
||||
|
||||
def image(path)
|
||||
%(<img src="#{path}" />)
|
||||
end
|
||||
|
||||
def link(path)
|
||||
%(<a href="#{path}">#{path}</a>)
|
||||
end
|
||||
|
||||
let(:project) { create(:project) }
|
||||
let(:project_path) { project.path_with_namespace }
|
||||
let(:ref) { 'markdown' }
|
||||
let(:project_wiki) { nil }
|
||||
let(:requested_path) { '/' }
|
||||
|
||||
shared_examples :preserve_unchanged do
|
||||
it 'does not modify any relative URL in anchor' do
|
||||
doc = filter(link('README.md'))
|
||||
expect(doc.at_css('a')['href']).to eq 'README.md'
|
||||
end
|
||||
|
||||
it 'does not modify any relative URL in image' do
|
||||
doc = filter(image('files/images/logo-black.png'))
|
||||
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples :relative_to_requested do
|
||||
it 'rebuilds URL relative to the requested path' do
|
||||
doc = filter(link('users.md'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a project_wiki' do
|
||||
let(:project_wiki) { double('ProjectWiki') }
|
||||
include_examples :preserve_unchanged
|
||||
end
|
||||
|
||||
context 'without a repository' do
|
||||
let(:project) { create(:empty_project) }
|
||||
include_examples :preserve_unchanged
|
||||
end
|
||||
|
||||
context 'with an empty repository' do
|
||||
let(:project) { create(:project_empty_repo) }
|
||||
include_examples :preserve_unchanged
|
||||
end
|
||||
|
||||
it 'does not raise an exception on invalid URIs' do
|
||||
act = link("://foo")
|
||||
expect { filter(act) }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'with a valid repository' do
|
||||
it 'rebuilds relative URL for a file in the repo' do
|
||||
doc = filter(link('doc/api/README.md'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a file in the repo up one directory' do
|
||||
relative_link = link('../api/README.md')
|
||||
doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
|
||||
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a file in the repo up multiple directories' do
|
||||
relative_link = link('../../../api/README.md')
|
||||
doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md')
|
||||
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a file in the repo with an anchor' do
|
||||
doc = filter(link('README.md#section'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/blob/#{ref}/README.md#section"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for a directory in the repo' do
|
||||
doc = filter(link('doc/api/'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/tree/#{ref}/doc/api"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for an image in the repo' do
|
||||
doc = filter(link('files/images/logo-black.png'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
|
||||
end
|
||||
|
||||
it 'does not modify relative URL with an anchor only' do
|
||||
doc = filter(link('#section-1'))
|
||||
expect(doc.at_css('a')['href']).to eq '#section-1'
|
||||
end
|
||||
|
||||
it 'does not modify absolute URL' do
|
||||
doc = filter(link('http://example.com'))
|
||||
expect(doc.at_css('a')['href']).to eq 'http://example.com'
|
||||
end
|
||||
|
||||
it 'supports Unicode filenames' do
|
||||
path = 'files/images/한글.png'
|
||||
escaped = Addressable::URI.escape(path)
|
||||
|
||||
# Stub these methods so the file doesn't actually need to be in the repo
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:file_exists?).and_return(true)
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:image?).with(path).and_return(true)
|
||||
|
||||
doc = filter(image(escaped))
|
||||
expect(doc.at_css('img')['src']).to match '/raw/'
|
||||
end
|
||||
|
||||
context 'when requested path is a file in the repo' do
|
||||
let(:requested_path) { 'doc/api/README.md' }
|
||||
include_examples :relative_to_requested
|
||||
end
|
||||
|
||||
context 'when requested path is a directory in the repo' do
|
||||
let(:requested_path) { 'doc/api' }
|
||||
include_examples :relative_to_requested
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe SanitizationFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
describe 'default whitelist' do
|
||||
it 'sanitizes tags that are not whitelisted' do
|
||||
act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
|
||||
exp = 'no inputs and no blinks'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes tag attributes' do
|
||||
act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
|
||||
exp = %q{<a href="http://example.com/bar.html">Text</a>}
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes javascript in attributes' do
|
||||
act = %q(<a href="javascript:alert('foo')">Text</a>)
|
||||
exp = '<a>Text</a>'
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'allows whitelisted HTML tags from the user' do
|
||||
exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes `class` attribute on any element' do
|
||||
act = %q{<strong class="foo">Strong</strong>}
|
||||
expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
|
||||
end
|
||||
|
||||
it 'sanitizes `id` attribute on any element' do
|
||||
act = %q{<em id="foo">Emphasis</em>}
|
||||
expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'custom whitelist' do
|
||||
it 'customizes the whitelist only once' do
|
||||
instance = described_class.new('Foo')
|
||||
3.times { instance.whitelist }
|
||||
|
||||
expect(instance.whitelist[:transformers].size).to eq 5
|
||||
end
|
||||
|
||||
it 'allows syntax highlighting' do
|
||||
exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'sanitizes `class` attribute from non-highlight spans' do
|
||||
act = %q{<span class="k">def</span>}
|
||||
expect(filter(act).to_html).to eq %q{<span>def</span>}
|
||||
end
|
||||
|
||||
it 'allows `style` attribute on table elements' do
|
||||
html = <<-HTML.strip_heredoc
|
||||
<table>
|
||||
<tr><th style="text-align: center">Head</th></tr>
|
||||
<tr><td style="text-align: right">Body</th></tr>
|
||||
</table>
|
||||
HTML
|
||||
|
||||
doc = filter(html)
|
||||
|
||||
expect(doc.at_css('th')['style']).to eq 'text-align: center'
|
||||
expect(doc.at_css('td')['style']).to eq 'text-align: right'
|
||||
end
|
||||
|
||||
it 'allows `span` elements' do
|
||||
exp = act = %q{<span>Hello</span>}
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'removes `rel` attribute from `a` elements' do
|
||||
act = %q{<a href="#" rel="nofollow">Link</a>}
|
||||
exp = %q{<a href="#">Link</a>}
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
# Adapted from the Sanitize test suite: http://git.io/vczrM
|
||||
protocols = {
|
||||
'protocol-based JS injection: simple, no spaces' => {
|
||||
input: '<a href="javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: simple, spaces before' => {
|
||||
input: '<a href="javascript :alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: simple, spaces after' => {
|
||||
input: '<a href="javascript: alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: simple, spaces before and after' => {
|
||||
input: '<a href="javascript : alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: preceding colon' => {
|
||||
input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: UTF-8 encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: long UTF-8 encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
|
||||
input: '<a href=javascript:alert('XSS')>foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: hex encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: long hex encoding' => {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: hex encoding without semicolons' => {
|
||||
input: '<a href=javascript:alert('XSS')>foo</a>',
|
||||
output: '<a>foo</a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: null char' => {
|
||||
input: "<a href=java\0script:alert(\"XSS\")>foo</a>",
|
||||
output: '<a href="java"></a>'
|
||||
},
|
||||
|
||||
'protocol-based JS injection: spaces and entities' => {
|
||||
input: '<a href="  javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a href="">foo</a>'
|
||||
},
|
||||
}
|
||||
|
||||
protocols.each do |name, data|
|
||||
it "handles #{name}" do
|
||||
doc = filter(data[:input])
|
||||
|
||||
expect(doc.to_html).to eq data[:output]
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows non-standard anchor schemes' do
|
||||
exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
|
||||
act = filter(exp)
|
||||
|
||||
expect(act.to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'allows relative links' do
|
||||
exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
|
||||
act = filter(exp)
|
||||
|
||||
expect(act.to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'when inline_sanitization is true' do
|
||||
it 'uses a stricter whitelist' do
|
||||
doc = filter('<h1>Description</h1>', inline_sanitization: true)
|
||||
expect(doc.to_html.strip).to eq 'Description'
|
||||
end
|
||||
|
||||
%w(pre code img ol ul li).each do |elem|
|
||||
it "removes '#{elem}' elements" do
|
||||
act = "<#{elem}>Description</#{elem}>"
|
||||
expect(filter(act, inline_sanitization: true).to_html.strip).
|
||||
to eq 'Description'
|
||||
end
|
||||
end
|
||||
|
||||
%w(b i strong em a ins del sup sub p).each do |elem|
|
||||
it "still allows '#{elem}' elements" do
|
||||
exp = act = "<#{elem}>Description</#{elem}>"
|
||||
expect(filter(act, inline_sanitization: true).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe SnippetReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:snippet) { create(:project_snippet, project: project) }
|
||||
let(:reference) { snippet.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.
|
||||
namespace_project_snippet_url(project.namespace, project, snippet)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Snippet (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid snippet IDs' do
|
||||
exp = act = "Snippet #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = filter("Snippet #{reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
snippet.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = filter("Snippet #{reference}")
|
||||
expect(doc.text).to eq "Snippet #{reference}"
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Snippet #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = filter("Snippet #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
expect(link.attr('data-project')).to eq project.id.to_s
|
||||
end
|
||||
|
||||
it 'includes a data-snippet attribute' do
|
||||
doc = filter("See #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-snippet')
|
||||
expect(link.attr('data-snippet')).to eq snippet.id.to_s
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Snippet #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Snippet #{reference}")
|
||||
expect(result[:references][:snippet]).to eq [snippet]
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
let(:namespace) { create(:namespace, name: 'cross-reference') }
|
||||
let(:project2) { create(:empty_project, :public, namespace: namespace) }
|
||||
let(:snippet) { create(:project_snippet, project: project2) }
|
||||
let(:reference) { snippet.to_reference(project) }
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = filter("See #{reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href')).
|
||||
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("See (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'ignores invalid snippet IDs on the referenced project' do
|
||||
exp = act = "See #{invalidate_reference(reference)}"
|
||||
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Snippet #{reference}")
|
||||
expect(result[:references][:snippet]).to eq [snippet]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe SyntaxHighlightFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'highlights valid code blocks' do
|
||||
result = filter('<pre><code>def fun end</code>')
|
||||
expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
|
||||
end
|
||||
|
||||
it 'passes through invalid code blocks' do
|
||||
allow_any_instance_of(SyntaxHighlightFilter).to receive(:block_code).and_raise(StandardError)
|
||||
|
||||
result = filter('<pre><code>This is a test</code></pre>')
|
||||
expect(result.to_html).to eq('<pre>This is a test</pre>')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe TableOfContentsFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
def header(level, text)
|
||||
"<h#{level}>#{text}</h#{level}>\n"
|
||||
end
|
||||
|
||||
it 'does nothing when :no_header_anchors is truthy' do
|
||||
exp = act = header(1, 'Header')
|
||||
expect(filter(act, no_header_anchors: 1).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'does nothing with empty headers' do
|
||||
exp = act = header(1, nil)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
1.upto(6) do |i|
|
||||
it "processes h#{i} elements" do
|
||||
html = header(i, "Header #{i}")
|
||||
doc = filter(html)
|
||||
|
||||
expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'anchor tag' do
|
||||
it 'has an `anchor` class' do
|
||||
doc = filter(header(1, 'Header'))
|
||||
expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
|
||||
end
|
||||
|
||||
it 'links to the id' do
|
||||
doc = filter(header(1, 'Header'))
|
||||
expect(doc.css('h1 a').first.attr('href')).to eq '#header'
|
||||
end
|
||||
|
||||
describe 'generated IDs' do
|
||||
it 'translates spaces to dashes' do
|
||||
doc = filter(header(1, 'This header has spaces in it'))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
|
||||
end
|
||||
|
||||
it 'squeezes multiple spaces and dashes' do
|
||||
doc = filter(header(1, 'This---header is poorly-formatted'))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
|
||||
end
|
||||
|
||||
it 'removes punctuation' do
|
||||
doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
|
||||
end
|
||||
|
||||
it 'appends a unique number to duplicates' do
|
||||
doc = filter(header(1, 'One') + header(2, 'One'))
|
||||
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq 'one'
|
||||
expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
|
||||
end
|
||||
|
||||
it 'supports Unicode' do
|
||||
doc = filter(header(1, '한글'))
|
||||
expect(doc.css('h1 a').first.attr('id')).to eq '한글'
|
||||
expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'result' do
|
||||
def result(html)
|
||||
HTML::Pipeline.new([described_class]).call(html)
|
||||
end
|
||||
|
||||
let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) }
|
||||
let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) }
|
||||
|
||||
it 'is contained within a `ul` element' do
|
||||
expect(doc.children.first.name).to eq 'ul'
|
||||
expect(doc.children.first.attr('class')).to eq 'section-nav'
|
||||
end
|
||||
|
||||
it 'contains an `li` element for each header' do
|
||||
expect(doc.css('li').length).to eq 2
|
||||
|
||||
links = doc.css('li a')
|
||||
|
||||
expect(links.first.attr('href')).to eq '#header-1'
|
||||
expect(links.first.text).to eq 'Header 1'
|
||||
expect(links.last.attr('href')).to eq '#header-2'
|
||||
expect(links.last.text).to eq 'Header 2'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe TaskListFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
it 'does not apply `task-list` class to non-task lists' do
|
||||
exp = act = %(<ul><li>Item</li></ul>)
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe UploadLinkFilter do
|
||||
def filter(doc, contexts = {})
|
||||
contexts.reverse_merge!({
|
||||
project: project
|
||||
})
|
||||
|
||||
described_class.call(doc, contexts)
|
||||
end
|
||||
|
||||
def image(path)
|
||||
%(<img src="#{path}" />)
|
||||
end
|
||||
|
||||
def link(path)
|
||||
%(<a href="#{path}">#{path}</a>)
|
||||
end
|
||||
|
||||
let(:project) { create(:project) }
|
||||
|
||||
shared_examples :preserve_unchanged do
|
||||
it 'does not modify any relative URL in anchor' do
|
||||
doc = filter(link('README.md'))
|
||||
expect(doc.at_css('a')['href']).to eq 'README.md'
|
||||
end
|
||||
|
||||
it 'does not modify any relative URL in image' do
|
||||
doc = filter(image('files/images/logo-black.png'))
|
||||
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not raise an exception on invalid URIs' do
|
||||
act = link("://foo")
|
||||
expect { filter(act) }.not_to raise_error
|
||||
end
|
||||
|
||||
context 'with a valid repository' do
|
||||
it 'rebuilds relative URL for a link' do
|
||||
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
|
||||
end
|
||||
|
||||
it 'rebuilds relative URL for an image' do
|
||||
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
|
||||
expect(doc.at_css('a')['href']).
|
||||
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
|
||||
end
|
||||
|
||||
it 'does not modify absolute URL' do
|
||||
doc = filter(link('http://example.com'))
|
||||
expect(doc.at_css('a')['href']).to eq 'http://example.com'
|
||||
end
|
||||
|
||||
it 'supports Unicode filenames' do
|
||||
path = '/uploads/한글.png'
|
||||
escaped = Addressable::URI.escape(path)
|
||||
|
||||
# Stub these methods so the file doesn't actually need to be in the repo
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:file_exists?).and_return(true)
|
||||
allow_any_instance_of(described_class).
|
||||
to receive(:image?).with(path).and_return(true)
|
||||
|
||||
doc = filter(image(escaped))
|
||||
expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Gitlab::Markdown
|
||||
describe UserReferenceFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:user) { create(:user) }
|
||||
let(:reference) { user.to_reference }
|
||||
|
||||
it 'requires project context' do
|
||||
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
|
||||
end
|
||||
|
||||
it 'ignores invalid users' do
|
||||
exp = act = "Hey #{invalidate_reference(reference)}"
|
||||
expect(filter(act).to_html).to eq(exp)
|
||||
end
|
||||
|
||||
%w(pre code a style).each do |elem|
|
||||
it "ignores valid references contained inside '#{elem}' element" do
|
||||
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
|
||||
expect(filter(act).to_html).to eq exp
|
||||
end
|
||||
end
|
||||
|
||||
context 'mentioning @all' do
|
||||
let(:reference) { User.reference_prefix + 'all' }
|
||||
|
||||
before do
|
||||
project.team << [project.creator, :developer]
|
||||
end
|
||||
|
||||
it 'supports a special @all mention' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').length).to eq 1
|
||||
expect(doc.css('a').first.attr('href'))
|
||||
.to eq urls.namespace_project_url(project.namespace, project)
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Hey #{reference}")
|
||||
expect(result[:references][:user]).to eq [project.creator]
|
||||
end
|
||||
end
|
||||
|
||||
context 'mentioning a user' do
|
||||
it 'links to a User' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
|
||||
end
|
||||
|
||||
it 'links to a User with a period' do
|
||||
user = create(:user, name: 'alphA.Beta')
|
||||
|
||||
doc = filter("Hey #{user.to_reference}")
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'links to a User with an underscore' do
|
||||
user = create(:user, name: 'ping_pong_king')
|
||||
|
||||
doc = filter("Hey #{user.to_reference}")
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'includes a data-user attribute' do
|
||||
doc = filter("Hey #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-user')
|
||||
expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Hey #{reference}")
|
||||
expect(result[:references][:user]).to eq [user]
|
||||
end
|
||||
end
|
||||
|
||||
context 'mentioning a group' do
|
||||
let(:group) { create(:group) }
|
||||
let(:reference) { group.to_reference }
|
||||
|
||||
it 'links to the Group' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
|
||||
end
|
||||
|
||||
it 'includes a data-group attribute' do
|
||||
doc = filter("Hey #{reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-group')
|
||||
expect(link.attr('data-group')).to eq group.id.to_s
|
||||
end
|
||||
|
||||
it 'adds to the results hash' do
|
||||
result = reference_pipeline_result("Hey #{reference}")
|
||||
expect(result[:references][:user]).to eq group.users
|
||||
end
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = filter("Mention me (#{reference}.)")
|
||||
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = filter("Hey #{reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
|
||||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = filter("Hey #{reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
expect(link).to eq urls.user_path(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue