From 7781bda9bd82997f4a03de4cf911b1156ceb2cde Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 15 Dec 2015 15:51:16 +0100 Subject: Move Markdown/reference logic from Gitlab::Markdown to Banzai --- .../lib/gitlab/markdown/reference_filter_spec.rb | 2 +- spec/lib/banzai/cross_project_reference_spec.rb | 34 ++++ spec/lib/banzai/filter/autolink_filter_spec.rb | 112 +++++++++++ .../filter/commit_range_reference_filter_spec.rb | 182 ++++++++++++++++++ .../banzai/filter/commit_reference_filter_spec.rb | 163 ++++++++++++++++ spec/lib/banzai/filter/emoji_filter_spec.rb | 98 ++++++++++ .../filter/external_issue_reference_filter_spec.rb | 77 ++++++++ .../lib/banzai/filter/external_link_filter_spec.rb | 29 +++ .../banzai/filter/issue_reference_filter_spec.rb | 209 +++++++++++++++++++++ .../banzai/filter/label_reference_filter_spec.rb | 179 ++++++++++++++++++ .../filter/merge_request_reference_filter_spec.rb | 142 ++++++++++++++ spec/lib/banzai/filter/redactor_filter_spec.rb | 89 +++++++++ .../filter/reference_gatherer_filter_spec.rb | 87 +++++++++ .../lib/banzai/filter/relative_link_filter_spec.rb | 147 +++++++++++++++ spec/lib/banzai/filter/sanitization_filter_spec.rb | 197 +++++++++++++++++++ .../banzai/filter/snippet_reference_filter_spec.rb | 146 ++++++++++++++ .../banzai/filter/syntax_highlight_filter_spec.rb | 17 ++ .../banzai/filter/table_of_contents_filter_spec.rb | 97 ++++++++++ spec/lib/banzai/filter/task_list_filter_spec.rb | 10 + spec/lib/banzai/filter/upload_link_filter_spec.rb | 73 +++++++ .../banzai/filter/user_reference_filter_spec.rb | 147 +++++++++++++++ spec/lib/gitlab/asciidoc_spec.rb | 2 +- .../markdown/cross_project_reference_spec.rb | 34 ---- .../gitlab/markdown/filter/autolink_filter_spec.rb | 112 ----------- .../filter/commit_range_reference_filter_spec.rb | 182 ------------------ .../filter/commit_reference_filter_spec.rb | 163 ---------------- .../gitlab/markdown/filter/emoji_filter_spec.rb | 98 ---------- .../filter/external_issue_reference_filter_spec.rb | 77 -------- .../markdown/filter/external_link_filter_spec.rb | 29 --- .../markdown/filter/issue_reference_filter_spec.rb | 209 --------------------- .../markdown/filter/label_reference_filter_spec.rb | 179 ------------------ .../filter/merge_request_reference_filter_spec.rb | 142 -------------- .../gitlab/markdown/filter/redactor_filter_spec.rb | 89 --------- .../filter/reference_gatherer_filter_spec.rb | 87 --------- .../markdown/filter/relative_link_filter_spec.rb | 147 --------------- .../markdown/filter/sanitization_filter_spec.rb | 197 ------------------- .../filter/snippet_reference_filter_spec.rb | 146 -------------- .../filter/syntax_highlight_filter_spec.rb | 17 -- .../filter/table_of_contents_filter_spec.rb | 97 ---------- .../markdown/filter/task_list_filter_spec.rb | 10 - .../markdown/filter/upload_link_filter_spec.rb | 73 ------- .../markdown/filter/user_reference_filter_spec.rb | 147 --------------- spec/support/filter_spec_helper.rb | 36 ++-- 43 files changed, 2255 insertions(+), 2255 deletions(-) create mode 100644 spec/lib/banzai/cross_project_reference_spec.rb create mode 100644 spec/lib/banzai/filter/autolink_filter_spec.rb create mode 100644 spec/lib/banzai/filter/commit_range_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/commit_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/emoji_filter_spec.rb create mode 100644 spec/lib/banzai/filter/external_issue_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/external_link_filter_spec.rb create mode 100644 spec/lib/banzai/filter/issue_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/label_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/merge_request_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/redactor_filter_spec.rb create mode 100644 spec/lib/banzai/filter/reference_gatherer_filter_spec.rb create mode 100644 spec/lib/banzai/filter/relative_link_filter_spec.rb create mode 100644 spec/lib/banzai/filter/sanitization_filter_spec.rb create mode 100644 spec/lib/banzai/filter/snippet_reference_filter_spec.rb create mode 100644 spec/lib/banzai/filter/syntax_highlight_filter_spec.rb create mode 100644 spec/lib/banzai/filter/table_of_contents_filter_spec.rb create mode 100644 spec/lib/banzai/filter/task_list_filter_spec.rb create mode 100644 spec/lib/banzai/filter/upload_link_filter_spec.rb create mode 100644 spec/lib/banzai/filter/user_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/cross_project_reference_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb delete mode 100644 spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb (limited to 'spec') diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb index 34cd9f7e4eb..3855763b200 100644 --- a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb +++ b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Markdown::ReferenceFilter, benchmark: true do +describe Banzai::Filter::ReferenceFilter, benchmark: true do let(:input) do html = <<-EOF

Hello @alice and @bob, how are you doing today?

diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb new file mode 100644 index 00000000000..81b9a513ce3 --- /dev/null +++ b/spec/lib/banzai/cross_project_reference_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Banzai::CrossProjectReference, lib: true 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 + + allow(self).to receive(:context).and_return({ project: project }) + + 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 + end + + 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_from_ref('cross/reference')).to eq project2 + end + end + end +end diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb new file mode 100644 index 00000000000..84c2ddf444e --- /dev/null +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +describe Banzai::Filter::AutolinkFilter, lib: true 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}" + 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}" + expect(filter(act).to_html).to eq exp + end + end + end +end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb new file mode 100644 index 00000000000..c2a8ad36c30 --- /dev/null +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -0,0 +1,182 @@ +require 'spec_helper' + +describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do + include FilterSpecHelper + + let(:project) { create(:project, :public) } + let(:commit1) { project.commit("HEAD~2") } + let(:commit2) { project.commit } + + let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) } + let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", 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}>Commit Range #{range.to_reference}" + expect(reference_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 = reference_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 = reference_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(reference_filter("See #{reference}").css('a').first.text).to eq exp + expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + + exp = Regexp.escape(range.reference_link_text) + expect(doc.to_html).to match(/\(#{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(project.repository).to receive(:commit).with(commit2.id) + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("See #{reference}") + expect(doc.css('a').first.attr('title')).to eq range.reference_title + end + + it 'includes default classes' do + doc = reference_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 = reference_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 = reference_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_s + end + + it 'supports an :only_path option' do + doc = reference_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 = reference_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 = reference_filter("Fixed (#{reference}.)") + + exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") + expect(doc.to_html).to match(/\(#{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(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_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 + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:range) { CommitRange.new("#{commit1.id}...master", project) } + let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } + + before do + range.project = project2 + end + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + + exp = Regexp.escape(range.reference_link_text(project)) + expect(doc.to_html).to match(/\(#{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(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_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 diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb new file mode 100644 index 00000000000..473534ba68a --- /dev/null +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +describe Banzai::Filter::CommitReferenceFilter, lib: true 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}" + expect(reference_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 = reference_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 = reference_filter("See #{commit.id}") + expect(doc.text).to eq "See #{commit.short_id}" + + doc = reference_filter("See #{commit.id[0...6]}") + expect(doc.text).to eq "See #{commit.short_id}" + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(#{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(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_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(%{">whatever#{exp}@#{commit.short_id}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Committed #{invalidate_reference(reference)}" + expect(reference_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 + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } + + it 'links to a valid reference' do + doc = reference_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 = reference_filter("Fixed (#{reference}.)") + + expect(doc.to_html).to match(/\(#{commit.reference_link_text(project)}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + act = "Committed #{invalidate_reference(reference)}" + expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) + 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 diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb new file mode 100644 index 00000000000..cf314058158 --- /dev/null +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe Banzai::Filter::EmojiFilter, lib: true 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('

:heart:

') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png' + end + + it 'ignores unsupported emoji' do + exp = act = '

:foo:

' + doc = filter(act) + expect(doc.to_html).to match Regexp.escape(exp) + end + + it 'correctly encodes the URL' do + doc = filter('

:+1:

') + 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 , 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 diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb new file mode 100644 index 00000000000..953466679e4 --- /dev/null +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true 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}" + 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(/\(#{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(%{">
whateverIgnore Me) + expect(filter(act).to_html).to eq exp + end + + it 'ignores non-HTTP(S) links' do + exp = act = %q(IRC) + expect(filter(act).to_html).to eq exp + end + + it 'skips internal links' do + internal = Gitlab.config.gitlab.url + exp = act = %Q(Login) + expect(filter(act).to_html).to eq exp + end + + it 'adds rel="nofollow" to external links' do + act = %q(Google) + doc = filter(act) + + expect(doc.at_css('a')).to have_attribute('rel') + expect(doc.at_css('a')['rel']).to eq 'nofollow' + end +end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb new file mode 100644 index 00000000000..5a0d3d577a8 --- /dev/null +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' + +describe Banzai::Filter::IssueReferenceFilter, lib: true 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}" + expect(reference_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(reference_filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = reference_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 = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid issue IDs' do + invalid = invalidate_reference(reference) + exp = act = "Fixed #{invalid}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_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, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid issue IDs on the referenced project' do + exp = act = "Fixed #{invalidate_reference(reference)}" + + expect(reference_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 + + context 'cross-project URL 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) { helper.url_for_issue(issue.iid, project2) + "#note_123" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) + 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 in link href' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { %Q{Reference} } + + it 'links to a valid reference' do + doc = reference_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 = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) + 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 URL in link href' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { %Q{Reference} } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + "#note_123" + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end +end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb new file mode 100644 index 00000000000..b46ccc47605 --- /dev/null +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -0,0 +1,179 @@ +require 'spec_helper' +require 'html/pipeline' + +describe Banzai::Filter::LabelReferenceFilter, lib: true 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}" + expect(reference_filter(act).to_html).to eq exp + end + end + + it 'includes default classes' do + doc = reference_filter("Label #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' + end + + it 'includes a data-project attribute' do + doc = reference_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 = reference_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 = reference_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 = reference_filter("Label #{reference}") + expect(doc.css('a span').first.attr('class')).to eq 'label color-label' + end + + it 'includes a style attribute' do + doc = reference_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 = reference_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 = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{label.name}\.\))) + end + + it 'ignores invalid label IDs' do + exp = act = "Label #{invalidate_reference(reference)}" + + expect(reference_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 = reference_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 = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{label.name}\.\))) + end + + it 'ignores invalid label names' do + exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" + + expect(reference_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 = reference_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 = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{label.name}\.\))) + end + + it 'ignores invalid label names' do + exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") + + expect(reference_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(reference_filter(act).to_html).to eq exp + end + end + + describe 'referencing a label in a link href' do + let(:reference) { %Q{Label} } + + it 'links to a valid reference' do + doc = reference_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 = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(Label\.\))) + end + + it 'includes a data-project attribute' do + doc = reference_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 = reference_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 'adds to the results hash' do + result = reference_pipeline_result("Label #{reference}") + expect(result[:references][:label]).to eq [label] + end + end +end diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb new file mode 100644 index 00000000000..352710df307 --- /dev/null +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Banzai::Filter::MergeRequestReferenceFilter, lib: true 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}" + expect(reference_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 = reference_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 = reference_filter("Merge (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid merge IDs' do + exp = act = "Merge #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_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, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid merge IDs on the referenced project' do + exp = act = "Merge #{invalidate_reference(reference)}" + + expect(reference_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 + + context 'cross-project URL reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } + let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Merge (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) + 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 diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb new file mode 100644 index 00000000000..e9bb388e361 --- /dev/null +++ b/spec/lib/banzai/filter/redactor_filter_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe Banzai::Filter::RedactorFilter, lib: true do + include ActionView::Helpers::UrlHelper + include FilterSpecHelper + + it 'ignores non-GFM links' do + html = %(See Google) + 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: 'ReferenceFilter') + 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: 'ReferenceFilter') + 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: 'ReferenceFilter') + + 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: 'UserReferenceFilter') + 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: 'UserReferenceFilter') + 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: 'UserReferenceFilter') + + 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: 'UserReferenceFilter') + doc = filter(link) + + expect(doc.css('a').length).to eq 1 + end + end + end +end diff --git a/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb b/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb new file mode 100644 index 00000000000..c8b1dfdf944 --- /dev/null +++ b/spec/lib/banzai/filter/reference_gatherer_filter_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Banzai::Filter::ReferenceGathererFilter, lib: true 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: 'IssueReferenceFilter') + 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: 'IssueReferenceFilter') + 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: 'IssueReferenceFilter') + + 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: 'UserReferenceFilter') + 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: 'UserReferenceFilter') + 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: 'UserReferenceFilter') + + 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: 'UserReferenceFilter') + result = pipeline_result(link) + + expect(result[:references][:user]).to eq([user]) + end + end + end +end diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb new file mode 100644 index 00000000000..0b3e5ecbc9f --- /dev/null +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -0,0 +1,147 @@ +# encoding: UTF-8 + +require 'spec_helper' + +describe Banzai::Filter::RelativeLinkFilter, lib: true 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) + %() + end + + def link(path) + %(#{path}) + 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 diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb new file mode 100644 index 00000000000..760d60a4190 --- /dev/null +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -0,0 +1,197 @@ +require 'spec_helper' + +describe Banzai::Filter::SanitizationFilter, lib: true do + include FilterSpecHelper + + describe 'default whitelist' do + it 'sanitizes tags that are not whitelisted' do + act = %q{ and no blinks} + exp = 'no inputs and no blinks' + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes tag attributes' do + act = %q{Text} + exp = %q{Text} + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes javascript in attributes' do + act = %q(Text) + exp = 'Text' + expect(filter(act).to_html).to eq exp + end + + it 'allows whitelisted HTML tags from the user' do + exp = act = "
\n
Term
\n
Definition
\n
" + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes `class` attribute on any element' do + act = %q{Strong} + expect(filter(act).to_html).to eq %q{Strong} + end + + it 'sanitizes `id` attribute on any element' do + act = %q{Emphasis} + expect(filter(act).to_html).to eq %q{Emphasis} + 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{
def
} + expect(filter(act).to_html).to eq exp + end + + it 'sanitizes `class` attribute from non-highlight spans' do + act = %q{def} + expect(filter(act).to_html).to eq %q{def} + end + + it 'allows `style` attribute on table elements' do + html = <<-HTML.strip_heredoc + + + +
Head
Body
+ 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{Hello} + expect(filter(act).to_html).to eq exp + end + + it 'removes `rel` attribute from `a` elements' do + act = %q{Link} + exp = %q{Link} + + 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: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: simple, spaces before' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: simple, spaces after' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: simple, spaces before and after' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: preceding colon' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: UTF-8 encoding' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: long UTF-8 encoding' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: hex encoding' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: long hex encoding' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: hex encoding without semicolons' => { + input: 'foo', + output: 'foo' + }, + + 'protocol-based JS injection: null char' => { + input: "foo", + output: '' + }, + + 'protocol-based JS injection: spaces and entities' => { + input: 'foo', + output: 'foo' + }, + } + + 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{IRC} + act = filter(exp) + + expect(act.to_html).to eq exp + end + + it 'allows relative links' do + exp = %q{foo/bar.md} + 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('

Description

', 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" + 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" + expect(filter(act, inline_sanitization: true).to_html).to eq exp + end + end + end +end diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb new file mode 100644 index 00000000000..26466fbb180 --- /dev/null +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' + +describe Banzai::Filter::SnippetReferenceFilter, lib: true 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}" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'internal reference' do + it 'links to a valid reference' do + doc = reference_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 = reference_filter("Snippet (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs' do + exp = act = "Snippet #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_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, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs on the referenced project' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_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 + + context 'cross-project URL 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) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) } + + it 'links to a valid reference' do + doc = reference_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 = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs on the referenced project' do + act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end + end +end diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb new file mode 100644 index 00000000000..407617f3307 --- /dev/null +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Banzai::Filter::SyntaxHighlightFilter, lib: true do + include FilterSpecHelper + + it 'highlights valid code blocks' do + result = filter('
def fun end')
+    expect(result.to_html).to eq("
def fun end
\n") + end + + it 'passes through invalid code blocks' do + allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError) + + result = filter('
This is a test
') + expect(result.to_html).to eq('
This is a test
') + end +end diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb new file mode 100644 index 00000000000..6a5d003e87f --- /dev/null +++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb @@ -0,0 +1,97 @@ +# encoding: UTF-8 + +require 'spec_helper' + +describe Banzai::Filter::TableOfContentsFilter, lib: true do + include FilterSpecHelper + + def header(level, text) + "#{text}\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 diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb new file mode 100644 index 00000000000..f2e3a44478d --- /dev/null +++ b/spec/lib/banzai/filter/task_list_filter_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe Banzai::Filter::TaskListFilter, lib: true do + include FilterSpecHelper + + it 'does not apply `task-list` class to non-task lists' do + exp = act = %(
  • Item
) + expect(filter(act).to_html).to eq exp + end +end diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb new file mode 100644 index 00000000000..3b073a90a95 --- /dev/null +++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +require 'spec_helper' + +describe Banzai::Filter::UploadLinkFilter, lib: true do + def filter(doc, contexts = {}) + contexts.reverse_merge!({ + project: project + }) + + described_class.call(doc, contexts) + end + + def image(path) + %() + end + + def link(path) + %(
#{path}) + 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 diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb new file mode 100644 index 00000000000..3534bf97784 --- /dev/null +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -0,0 +1,147 @@ +require 'spec_helper' + +describe Banzai::Filter::UserReferenceFilter, lib: true 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(reference_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}" + expect(reference_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 = reference_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 = reference_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 = reference_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 = reference_filter("Hey #{user.to_reference}") + expect(doc.css('a').length).to eq 1 + end + + it 'includes a data-user attribute' do + doc = reference_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 = reference_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 = reference_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 = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) + end + + it 'includes default classes' do + doc = reference_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 = reference_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 + + context 'referencing a user in a link href' do + let(:reference) { %Q{User} } + + it 'links to a User' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(/\(User<\/a>\.\)/) + end + + it 'includes a data-user attribute' do + doc = reference_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 +end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 3a860899e18..6beb21c6d2b 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -50,7 +50,7 @@ module Gitlab filtered_html = 'ASCII' allow(Asciidoctor).to receive(:convert).and_return(html) - expect(Gitlab::Markdown).to receive(:render) + expect(Banzai).to receive(:render) .with(html, context.merge(pipeline: :asciidoc)) .and_return(filtered_html) diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb deleted file mode 100644 index f594fe4ccf6..00000000000 --- a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::CrossProjectReference, lib: true 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 - - allow(self).to receive(:context).and_return({ project: project }) - - 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 - end - - 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_from_ref('cross/reference')).to eq project2 - end - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb deleted file mode 100644 index a0844aee559..00000000000 --- a/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::AutolinkFilter, lib: true 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}" - 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}" - expect(filter(act).to_html).to eq exp - end - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb deleted file mode 100644 index 570c9767628..00000000000 --- a/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::CommitRangeReferenceFilter, lib: true do - include FilterSpecHelper - - let(:project) { create(:project, :public) } - let(:commit1) { project.commit("HEAD~2") } - let(:commit2) { project.commit } - - let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) } - let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", 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}>Commit Range #{range.to_reference}" - expect(reference_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 = reference_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 = reference_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(reference_filter("See #{reference}").css('a').first.text).to eq exp - expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp - end - - it 'links with adjacent text' do - doc = reference_filter("See (#{reference}.)") - - exp = Regexp.escape(range.reference_link_text) - expect(doc.to_html).to match(/\(#{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(project.repository).to receive(:commit).with(commit2.id) - expect(reference_filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('title')).to eq range.reference_title - end - - it 'includes default classes' do - doc = reference_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 = reference_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 = reference_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_s - end - - it 'supports an :only_path option' do - doc = reference_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 = reference_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 = reference_filter("Fixed (#{reference}.)") - - exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") - expect(doc.to_html).to match(/\(#{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(reference_filter(act).to_html).to eq exp - - exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" - expect(reference_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 - - context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:range) { CommitRange.new("#{commit1.id}...master", project) } - let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } - - before do - range.project = project2 - end - - it 'links to a valid reference' do - doc = reference_filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq reference - end - - it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") - - exp = Regexp.escape(range.reference_link_text(project)) - expect(doc.to_html).to match(/\(#{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(reference_filter(act).to_html).to eq exp - - exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" - expect(reference_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 diff --git a/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb deleted file mode 100644 index 76e7957bbb9..00000000000 --- a/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb +++ /dev/null @@ -1,163 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::CommitReferenceFilter, lib: true 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}" - expect(reference_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 = reference_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 = reference_filter("See #{commit.id}") - expect(doc.text).to eq "See #{commit.short_id}" - - doc = reference_filter("See #{commit.id[0...6]}") - expect(doc.text).to eq "See #{commit.short_id}" - end - - it 'links with adjacent text' do - doc = reference_filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(#{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(reference_filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = reference_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(%{">whatever#{exp}@#{commit.short_id}<\/a>\.\)/) - end - - it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Committed #{invalidate_reference(reference)}" - expect(reference_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 - - context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:commit) { project2.commit } - let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } - - it 'links to a valid reference' do - doc = reference_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 = reference_filter("Fixed (#{reference}.)") - - expect(doc.to_html).to match(/\(#{commit.reference_link_text(project)}<\/a>\.\)/) - end - - it 'ignores invalid commit IDs on the referenced project' do - act = "Committed #{invalidate_reference(reference)}" - expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) - 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 diff --git a/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb deleted file mode 100644 index ea9b81862cf..00000000000 --- a/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::EmojiFilter, lib: true 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('

:heart:

') - expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png' - end - - it 'ignores unsupported emoji' do - exp = act = '

:foo:

' - doc = filter(act) - expect(doc.to_html).to match Regexp.escape(exp) - end - - it 'correctly encodes the URL' do - doc = filter('

:+1:

') - 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 , 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 diff --git a/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb deleted file mode 100644 index d8420102648..00000000000 --- a/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::ExternalIssueReferenceFilter, lib: true 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}" - 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(/\(#{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(%{">
whateverIgnore Me) - expect(filter(act).to_html).to eq exp - end - - it 'ignores non-HTTP(S) links' do - exp = act = %q(IRC) - expect(filter(act).to_html).to eq exp - end - - it 'skips internal links' do - internal = Gitlab.config.gitlab.url - exp = act = %Q(Login) - expect(filter(act).to_html).to eq exp - end - - it 'adds rel="nofollow" to external links' do - act = %q(Google) - doc = filter(act) - - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to eq 'nofollow' - end -end diff --git a/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb deleted file mode 100644 index 1aa5d44568e..00000000000 --- a/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb +++ /dev/null @@ -1,209 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::IssueReferenceFilter, lib: true 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}" - expect(reference_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(reference_filter(act).to_html).to eq exp - end - - it 'links to a valid reference' do - doc = reference_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 = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid issue IDs' do - invalid = invalidate_reference(reference) - exp = act = "Fixed #{invalid}" - - expect(reference_filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = reference_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, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid issue IDs on the referenced project' do - exp = act = "Fixed #{invalidate_reference(reference)}" - - expect(reference_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 - - context 'cross-project URL 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) { helper.url_for_issue(issue.iid, project2) + "#note_123" } - - it 'links to a valid reference' do - doc = reference_filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq reference - end - - it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) - 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 in link href' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } - let(:issue) { create(:issue, project: project2) } - let(:reference) { %Q{Reference} } - - it 'links to a valid reference' do - doc = reference_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 = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) - 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 URL in link href' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:empty_project, :public, namespace: namespace) } - let(:issue) { create(:issue, project: project2) } - let(:reference) { %Q{Reference} } - - it 'links to a valid reference' do - doc = reference_filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq helper.url_for_issue(issue.iid, project2) + "#note_123" - end - - it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Fixed #{reference}") - expect(result[:references][:issue]).to eq [issue] - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb deleted file mode 100644 index 4fcbb329fe4..00000000000 --- a/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -require 'spec_helper' -require 'html/pipeline' - -describe Gitlab::Markdown::LabelReferenceFilter, lib: true 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}" - expect(reference_filter(act).to_html).to eq exp - end - end - - it 'includes default classes' do - doc = reference_filter("Label #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' - end - - it 'includes a data-project attribute' do - doc = reference_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 = reference_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 = reference_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 = reference_filter("Label #{reference}") - expect(doc.css('a span').first.attr('class')).to eq 'label color-label' - end - - it 'includes a style attribute' do - doc = reference_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 = reference_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 = reference_filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(#{label.name}\.\))) - end - - it 'ignores invalid label IDs' do - exp = act = "Label #{invalidate_reference(reference)}" - - expect(reference_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 = reference_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 = reference_filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(#{label.name}\.\))) - end - - it 'ignores invalid label names' do - exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" - - expect(reference_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 = reference_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 = reference_filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(#{label.name}\.\))) - end - - it 'ignores invalid label names' do - exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") - - expect(reference_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(reference_filter(act).to_html).to eq exp - end - end - - describe 'referencing a label in a link href' do - let(:reference) { %Q{Label} } - - it 'links to a valid reference' do - doc = reference_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 = reference_filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(Label\.\))) - end - - it 'includes a data-project attribute' do - doc = reference_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 = reference_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 'adds to the results hash' do - result = reference_pipeline_result("Label #{reference}") - expect(result[:references][:label]).to eq [label] - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb deleted file mode 100644 index 589550e15c4..00000000000 --- a/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb +++ /dev/null @@ -1,142 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::MergeRequestReferenceFilter, lib: true 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}" - expect(reference_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 = reference_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 = reference_filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid merge IDs' do - exp = act = "Merge #{invalidate_reference(reference)}" - - expect(reference_filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = reference_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, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid merge IDs on the referenced project' do - exp = act = "Merge #{invalidate_reference(reference)}" - - expect(reference_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 - - context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } - let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } - - it 'links to a valid reference' do - doc = reference_filter("See #{reference}") - - expect(doc.css('a').first.attr('href')). - to eq reference - end - - it 'links with adjacent text' do - doc = reference_filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) - 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 diff --git a/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb deleted file mode 100644 index 9e6ee9f0d61..00000000000 --- a/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::RedactorFilter, lib: true do - include ActionView::Helpers::UrlHelper - include FilterSpecHelper - - it 'ignores non-GFM links' do - html = %(See Google) - 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: 'ReferenceFilter') - 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: 'ReferenceFilter') - 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: 'ReferenceFilter') - - 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: 'UserReferenceFilter') - 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: 'UserReferenceFilter') - 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: 'UserReferenceFilter') - - 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: 'UserReferenceFilter') - doc = filter(link) - - expect(doc.css('a').length).to eq 1 - end - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb deleted file mode 100644 index abfb5ad5e49..00000000000 --- a/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::ReferenceGathererFilter, lib: true 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: 'IssueReferenceFilter') - 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: 'IssueReferenceFilter') - 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: 'IssueReferenceFilter') - - 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: 'UserReferenceFilter') - 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: 'UserReferenceFilter') - 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: 'UserReferenceFilter') - - 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: 'UserReferenceFilter') - result = pipeline_result(link) - - expect(result[:references][:user]).to eq([user]) - end - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb deleted file mode 100644 index e0f53e2a533..00000000000 --- a/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb +++ /dev/null @@ -1,147 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -describe Gitlab::Markdown::RelativeLinkFilter, lib: true 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) - %() - end - - def link(path) - %(#{path}) - 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 diff --git a/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb deleted file mode 100644 index a5e5ee0e08a..00000000000 --- a/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb +++ /dev/null @@ -1,197 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::SanitizationFilter, lib: true do - include FilterSpecHelper - - describe 'default whitelist' do - it 'sanitizes tags that are not whitelisted' do - act = %q{ and no blinks} - exp = 'no inputs and no blinks' - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes tag attributes' do - act = %q{Text} - exp = %q{Text} - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes javascript in attributes' do - act = %q(Text) - exp = 'Text' - expect(filter(act).to_html).to eq exp - end - - it 'allows whitelisted HTML tags from the user' do - exp = act = "
\n
Term
\n
Definition
\n
" - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes `class` attribute on any element' do - act = %q{Strong} - expect(filter(act).to_html).to eq %q{Strong} - end - - it 'sanitizes `id` attribute on any element' do - act = %q{Emphasis} - expect(filter(act).to_html).to eq %q{Emphasis} - 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{
def
} - expect(filter(act).to_html).to eq exp - end - - it 'sanitizes `class` attribute from non-highlight spans' do - act = %q{def} - expect(filter(act).to_html).to eq %q{def} - end - - it 'allows `style` attribute on table elements' do - html = <<-HTML.strip_heredoc - - - -
Head
Body
- 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{Hello} - expect(filter(act).to_html).to eq exp - end - - it 'removes `rel` attribute from `a` elements' do - act = %q{Link} - exp = %q{Link} - - 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: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: simple, spaces before' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: simple, spaces after' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: simple, spaces before and after' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: preceding colon' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: UTF-8 encoding' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: long UTF-8 encoding' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: hex encoding' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: long hex encoding' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: hex encoding without semicolons' => { - input: 'foo', - output: 'foo' - }, - - 'protocol-based JS injection: null char' => { - input: "foo", - output: '' - }, - - 'protocol-based JS injection: spaces and entities' => { - input: 'foo', - output: 'foo' - }, - } - - 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{IRC} - act = filter(exp) - - expect(act.to_html).to eq exp - end - - it 'allows relative links' do - exp = %q{foo/bar.md} - 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('

Description

', 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" - 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" - expect(filter(act, inline_sanitization: true).to_html).to eq exp - end - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb deleted file mode 100644 index 51526b58597..00000000000 --- a/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::SnippetReferenceFilter, lib: true 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}" - expect(reference_filter(act).to_html).to eq exp - end - end - - context 'internal reference' do - it 'links to a valid reference' do - doc = reference_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 = reference_filter("Snippet (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid snippet IDs' do - exp = act = "Snippet #{invalidate_reference(reference)}" - - expect(reference_filter(act).to_html).to eq exp - end - - it 'includes a title attribute' do - doc = reference_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, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) - end - - it 'ignores invalid snippet IDs on the referenced project' do - exp = act = "See #{invalidate_reference(reference)}" - - expect(reference_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 - - context 'cross-project URL 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) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) } - - it 'links to a valid reference' do - doc = reference_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 = reference_filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/) - end - - it 'ignores invalid snippet IDs on the referenced project' do - act = "See #{invalidate_reference(reference)}" - - expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) - end - - it 'adds to the results hash' do - result = reference_pipeline_result("Snippet #{reference}") - expect(result[:references][:snippet]).to eq [snippet] - end - end -end diff --git a/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb b/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb deleted file mode 100644 index 8b76048f3e3..00000000000 --- a/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::SyntaxHighlightFilter, lib: true do - include FilterSpecHelper - - it 'highlights valid code blocks' do - result = filter('
def fun end')
-    expect(result.to_html).to eq("
def fun end
\n") - end - - it 'passes through invalid code blocks' do - allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError) - - result = filter('
This is a test
') - expect(result.to_html).to eq('
This is a test
') - end -end diff --git a/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb deleted file mode 100644 index c8c79c41847..00000000000 --- a/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -describe Gitlab::Markdown::TableOfContentsFilter, lib: true do - include FilterSpecHelper - - def header(level, text) - "#{text}\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 diff --git a/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb deleted file mode 100644 index 1b1714ef882..00000000000 --- a/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::TaskListFilter, lib: true do - include FilterSpecHelper - - it 'does not apply `task-list` class to non-task lists' do - exp = act = %(
  • Item
) - expect(filter(act).to_html).to eq exp - end -end diff --git a/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb deleted file mode 100644 index 38a007b5bee..00000000000 --- a/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' - -describe Gitlab::Markdown::UploadLinkFilter, lib: true do - def filter(doc, contexts = {}) - contexts.reverse_merge!({ - project: project - }) - - described_class.call(doc, contexts) - end - - def image(path) - %() - end - - def link(path) - %(
#{path}) - 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 diff --git a/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb deleted file mode 100644 index 277037cf68a..00000000000 --- a/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb +++ /dev/null @@ -1,147 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Markdown::UserReferenceFilter, lib: true 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(reference_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}" - expect(reference_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 = reference_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 = reference_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 = reference_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 = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'includes a data-user attribute' do - doc = reference_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 = reference_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 = reference_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 = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) - end - - it 'includes default classes' do - doc = reference_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 = reference_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 - - context 'referencing a user in a link href' do - let(:reference) { %Q{User} } - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links with adjacent text' do - doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(/\(User<\/a>\.\)/) - end - - it 'includes a data-user attribute' do - doc = reference_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 -end diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb index 91e3bee13c1..d6e03cbef3d 100644 --- a/spec/support/filter_spec_helper.rb +++ b/spec/support/filter_spec_helper.rb @@ -1,4 +1,4 @@ -# Helper methods for Gitlab::Markdown filter specs +# Helper methods for Banzai filter specs # # Must be included into specs manually module FilterSpecHelper @@ -10,49 +10,49 @@ module FilterSpecHelper # if none is provided. # # html - HTML String to pass to the filter's `call` method. - # contexts - Hash context for the filter. (default: {project: project}) + # context - Hash context for the filter. (default: {project: project}) # # Returns a Nokogiri::XML::DocumentFragment - def filter(html, contexts = {}) + def filter(html, context = {}) if defined?(project) - contexts.reverse_merge!(project: project) + context.reverse_merge!(project: project) end - described_class.call(html, contexts) + described_class.call(html, context) end # Run text through HTML::Pipeline with the current filter and return the # result Hash # # body - String text to run through the pipeline - # contexts - Hash context for the filter. (default: {project: project}) + # context - Hash context for the filter. (default: {project: project}) # # Returns the Hash - def pipeline_result(body, contexts = {}) - contexts.reverse_merge!(project: project) if defined?(project) + def pipeline_result(body, context = {}) + context.reverse_merge!(project: project) if defined?(project) - pipeline = HTML::Pipeline.new([described_class], contexts) + pipeline = HTML::Pipeline.new([described_class], context) pipeline.call(body) end - def reference_pipeline(contexts = {}) - contexts.reverse_merge!(project: project) if defined?(project) + def reference_pipeline(context = {}) + context.reverse_merge!(project: project) if defined?(project) filters = [ - Gitlab::Markdown::AutolinkFilter, + Banzai::Filter::AutolinkFilter, described_class, - Gitlab::Markdown::ReferenceGathererFilter + Banzai::Filter::ReferenceGathererFilter ] - HTML::Pipeline.new(filters, contexts) + HTML::Pipeline.new(filters, context) end - def reference_pipeline_result(body, contexts = {}) - reference_pipeline(contexts).call(body) + def reference_pipeline_result(body, context = {}) + reference_pipeline(context).call(body) end - def reference_filter(html, contexts = {}) - reference_pipeline(contexts).to_document(html) + def reference_filter(html, context = {}) + reference_pipeline(context).to_document(html) end # Modify a String reference to make it invalid -- cgit v1.2.1