summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2015-12-10 14:34:12 +0000
committerDouwe Maan <douwe@gitlab.com>2015-12-10 14:34:12 +0000
commit4e5897f51ef97d7c3ff6c57f81521f552979a3da (patch)
treeef0655df13cef6c267ea3e547d497f92498142ed
parent3bfedbd25a933d59defbb0875fb80758919df4a6 (diff)
parent10387f6b8a9071688d7db9a12c910ca02660ca87 (diff)
downloadgitlab-ce-4e5897f51ef97d7c3ff6c57f81521f552979a3da.tar.gz
Merge branch 'tmp-reference-pipeline-and-caching' into 'master'
[Second try] Implement different Markdown rendering pipelines and cache Markdown !1602 already got merged in bcd89a58e736685bcce662fe0acf793ee925b536, but it would appear the merge commit disappeared because of #3816 (or some other reason). cc @rspeicher See merge request !2051
-rw-r--r--app/helpers/gitlab_markdown_helper.rb60
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/concerns/mentionable.rb34
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb1
-rw-r--r--app/views/dashboard/milestones/show.html.haml2
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/groups/milestones/show.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml4
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/commits/show.atom.builder2
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/issues/_closed_by_box.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/repositories/_feed.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--config/environments/test.rb2
-rw-r--r--config/initializers/session_store.rb4
-rw-r--r--lib/gitlab/asciidoc.rb27
-rw-r--r--lib/gitlab/markdown.rb212
-rw-r--r--lib/gitlab/markdown/combined_pipeline.rb27
-rw-r--r--lib/gitlab/markdown/filter/autolink_filter.rb (renamed from lib/gitlab/markdown/autolink_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/commit_range_reference_filter.rb (renamed from lib/gitlab/markdown/commit_range_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/commit_reference_filter.rb (renamed from lib/gitlab/markdown/commit_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/emoji_filter.rb (renamed from lib/gitlab/markdown/emoji_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/external_issue_reference_filter.rb (renamed from lib/gitlab/markdown/external_issue_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/external_link_filter.rb (renamed from lib/gitlab/markdown/external_link_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/issue_reference_filter.rb (renamed from lib/gitlab/markdown/issue_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/label_reference_filter.rb (renamed from lib/gitlab/markdown/label_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/markdown_filter.rb39
-rw-r--r--lib/gitlab/markdown/filter/merge_request_reference_filter.rb (renamed from lib/gitlab/markdown/merge_request_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/redactor_filter.rb (renamed from lib/gitlab/markdown/redactor_filter.rb)2
-rw-r--r--lib/gitlab/markdown/filter/reference_gatherer_filter.rb (renamed from lib/gitlab/markdown/reference_gatherer_filter.rb)2
-rw-r--r--lib/gitlab/markdown/filter/relative_link_filter.rb (renamed from lib/gitlab/markdown/relative_link_filter.rb)5
-rw-r--r--lib/gitlab/markdown/filter/sanitization_filter.rb (renamed from lib/gitlab/markdown/sanitization_filter.rb)6
-rw-r--r--lib/gitlab/markdown/filter/snippet_reference_filter.rb (renamed from lib/gitlab/markdown/snippet_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/syntax_highlight_filter.rb (renamed from lib/gitlab/markdown/syntax_highlight_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/table_of_contents_filter.rb (renamed from lib/gitlab/markdown/table_of_contents_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/task_list_filter.rb (renamed from lib/gitlab/markdown/task_list_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/upload_link_filter.rb (renamed from lib/gitlab/markdown/upload_link_filter.rb)0
-rw-r--r--lib/gitlab/markdown/filter/user_reference_filter.rb (renamed from lib/gitlab/markdown/user_reference_filter.rb)0
-rw-r--r--lib/gitlab/markdown/pipeline.rb34
-rw-r--r--lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb13
-rw-r--r--lib/gitlab/markdown/pipeline/atom_pipeline.rb14
-rw-r--r--lib/gitlab/markdown/pipeline/description_pipeline.rb14
-rw-r--r--lib/gitlab/markdown/pipeline/email_pipeline.rb13
-rw-r--r--lib/gitlab/markdown/pipeline/full_pipeline.rb9
-rw-r--r--lib/gitlab/markdown/pipeline/gfm_pipeline.rb41
-rw-r--r--lib/gitlab/markdown/pipeline/note_pipeline.rb14
-rw-r--r--lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb13
-rw-r--r--lib/gitlab/markdown/pipeline/post_process_pipeline.rb20
-rw-r--r--lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb13
-rw-r--r--lib/gitlab/markdown/pipeline/single_line_pipeline.rb9
-rw-r--r--lib/gitlab/markdown/reference_filter.rb10
-rw-r--r--lib/gitlab/reference_extractor.rb49
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb6
-rw-r--r--spec/lib/gitlab/markdown/autolink_filter_spec.rb114
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb184
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb165
-rw-r--r--spec/lib/gitlab/markdown/cross_project_reference_spec.rb40
-rw-r--r--spec/lib/gitlab/markdown/emoji_filter_spec.rb95
-rw-r--r--spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb79
-rw-r--r--spec/lib/gitlab/markdown/external_link_filter_spec.rb31
-rw-r--r--spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb112
-rw-r--r--spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb182
-rw-r--r--spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb163
-rw-r--r--spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb98
-rw-r--r--spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb77
-rw-r--r--spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb29
-rw-r--r--spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb209
-rw-r--r--spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb179
-rw-r--r--spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb142
-rw-r--r--spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb89
-rw-r--r--spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb87
-rw-r--r--spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb147
-rw-r--r--spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb197
-rw-r--r--spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb146
-rw-r--r--spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb17
-rw-r--r--spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb97
-rw-r--r--spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb10
-rw-r--r--spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb73
-rw-r--r--spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb147
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb211
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb181
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb144
-rw-r--r--spec/lib/gitlab/markdown/redactor_filter_spec.rb91
-rw-r--r--spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb89
-rw-r--r--spec/lib/gitlab/markdown/relative_link_filter_spec.rb149
-rw-r--r--spec/lib/gitlab/markdown/sanitization_filter_spec.rb199
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb148
-rw-r--r--spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb19
-rw-r--r--spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb99
-rw-r--r--spec/lib/gitlab/markdown/task_list_filter_spec.rb12
-rw-r--r--spec/lib/gitlab/markdown/upload_link_filter_spec.rb75
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb149
100 files changed, 2662 insertions, 2551 deletions
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 98c6d9d5d2e..5004e02ea0b 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -20,7 +20,7 @@ module GitlabMarkdownHelper
end
user = current_user if defined?(current_user)
- gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
+ gfm_body = Gitlab::Markdown.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
@@ -46,23 +46,35 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
- process_markdown(text, context)
- end
+ return "" unless text.present?
+
+ context[:project] ||= @project
- # TODO (rspeicher): Remove all usages of this helper and just call `markdown`
- # with a custom pipeline depending on the content being rendered
- def gfm(text, options = {})
- process_markdown(text, options, :gfm)
+ html = Gitlab::Markdown.render(text, context)
+
+ context.merge!(
+ current_user: (current_user if defined?(current_user)),
+
+ # RelativeLinkFilter
+ requested_path: @path,
+ project_wiki: @project_wiki,
+ ref: @ref
+ )
+
+ Gitlab::Markdown.post_process(html, context)
end
def asciidoc(text)
- Gitlab::Asciidoc.render(text, {
- commit: @commit,
- project: @project,
- project_wiki: @project_wiki,
+ Gitlab::Asciidoc.render(text,
+ project: @project,
+ current_user: (current_user if defined?(current_user)),
+
+ # RelativeLinkFilter
+ project_wiki: @project_wiki,
requested_path: @path,
- ref: @ref
- })
+ ref: @ref,
+ commit: @commit
+ )
end
# Return the first line of +text+, up to +max_chars+, after parsing the line
@@ -178,26 +190,4 @@ module GitlabMarkdownHelper
''
end
end
-
- def process_markdown(text, options, method = :markdown)
- return "" unless text.present?
-
- options.reverse_merge!(
- path: @path,
- pipeline: :default,
- project: @project,
- project_wiki: @project_wiki,
- ref: @ref
- )
-
- user = current_user if defined?(current_user)
-
- html = if method == :gfm
- Gitlab::Markdown.gfm(text, options)
- else
- Gitlab::Markdown.render(text, options)
- end
-
- Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
- end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index fa88a408fa3..0ba7b584d91 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -7,7 +7,7 @@ class Commit
include Referable
include StaticModel
- attr_mentionable :safe_message
+ attr_mentionable :safe_message, pipeline: :single_line
participant :author, :committer, :notes
attr_accessor :project
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index badeadfa418..f56fd3e02d4 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -50,7 +50,8 @@ module Issuable
allow_nil: true,
prefix: true
- attr_mentionable :title, :description
+ attr_mentionable :title, pipeline: :single_line
+ attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations
strip_attributes :title
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 634a8d0f274..d2ea9ab7313 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -10,8 +10,9 @@ module Mentionable
module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
- def attr_mentionable(*attrs)
- mentionable_attrs.concat(attrs.map(&:to_s))
+ def attr_mentionable(attr, options = {})
+ attr = attr.to_s
+ mentionable_attrs << [attr, options]
end
# Accessor for attributes marked mentionable.
@@ -37,19 +38,24 @@ module Mentionable
"#{friendly_name} #{to_reference(from_project)}"
end
- # Construct a String that contains possible GFM references.
- def mentionable_text
- self.class.mentionable_attrs.map { |attr| send(attr) }.compact.join("\n\n")
- end
-
# The GFM reference to this Mentionable, which shouldn't be included in its #references.
def local_reference
self
end
- def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
+ def all_references(current_user = self.author, text = nil, load_lazy_references: true)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
- ext.analyze(text)
+
+ if text
+ ext.analyze(text)
+ else
+ self.class.mentionable_attrs.each do |attr, options|
+ text = send(attr)
+ options[:cache_key] = [self, attr] if options.delete(:cache)
+ ext.analyze(text, options)
+ end
+ end
+
ext
end
@@ -58,9 +64,7 @@ module Mentionable
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
- return [] if text.blank?
-
+ def referenced_mentionables(current_user = self.author, text = nil, load_lazy_references: true)
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
refs = (refs.issues + refs.merge_requests + refs.commits)
@@ -70,8 +74,8 @@ module Mentionable
refs.reject { |ref| ref == local_reference }
end
- # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
- def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
+ # Create a cross-reference Note for each GFM reference to another Mentionable found in the +mentionable_attrs+.
+ def create_cross_references!(author = self.author, without = [], text = nil)
refs = referenced_mentionables(author, text)
# We're using this method instead of Array diffing because that requires
@@ -111,7 +115,7 @@ module Mentionable
def detect_mentionable_changes
source = (changes.present? ? changes : previous_changes).dup
- mentionable = self.class.mentionable_attrs
+ mentionable = self.class.mentionable_attrs.map { |attr, options| attr }
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
diff --git a/app/models/note.rb b/app/models/note.rb
index 98c29ddc4cd..de9392adbf4 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -29,7 +29,7 @@ class Note < ActiveRecord::Base
default_value_for :system, false
- attr_mentionable :note
+ attr_mentionable :note, cache: true, pipeline: :note
participant :author
belongs_to :project
diff --git a/app/models/project.rb b/app/models/project.rb
index cb965ce1b9e..e78868af1cc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -662,6 +662,7 @@ class Project < ActiveRecord::Base
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
reset_events_cache
+ @repository = nil
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 3536bbeaf4b..44b7efe5232 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -12,7 +12,7 @@
.gray-content-block.middle-block
%h2.issue-title
- = gfm escape_once(@milestone.title)
+ = markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index ad63841ccf3..4ba8b84fd92 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -2,4 +2,4 @@
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
&middot;
- = gfm event_commit_title(commit[:message]), project: project
+ = markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 3c1d8815013..350e216fcc6 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -18,7 +18,7 @@
.gray-content-block.middle-block
%h2.issue-title
- = gfm escape_once(@milestone.title)
+ = markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index bb37e4a7049..132cdc35c94 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -52,10 +52,10 @@
.commit-box.gray-content-block.middle-block
%h3.commit-title
- = gfm escape_once(@commit.title)
+ = markdown escape_once(@commit.title), pipeline: :single_line
- if @commit.description.present?
%pre.commit-description
- = preserve(gfm(escape_once(@commit.description)))
+ = preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
:javascript
$(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 2e489a0a4d5..0d64486164e 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -32,7 +32,7 @@
- if commit.description?
.commit-row-description.js-toggle-content
%pre
- = preserve(gfm(escape_once(commit.description)))
+ = preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info
= commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 268b9b815ee..7ffa7317196 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.name commit.author_name
xml.email commit.author_email
end
- xml.summary gfm(commit.description)
+ xml.summary markdown(commit.description, pipeline: :single_line)
end
end
end
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b28ada909b7..d865d299135 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -24,10 +24,10 @@
.col-sm-10
= f.text_area :description, class: "form-control", rows: 3, maxlength: 250
- - if @project.repository.exists? && @project.repository.branch_names.any?
+ - unless @project.empty_repo?
.form-group
= f.label :default_branch, "Default Branch", class: 'control-label'
- .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
+ .col-sm-10= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
= render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
index 917d5181689..3c491c1a8b8 100644
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -1,3 +1,3 @@
.issue-closed-by-widget
= icon('check')
- This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
+ This issue will be closed automatically when merge request #{markdown(merge_requests_sentence(@closed_by_merge_requests), pipeline: :gfm)} is accepted.
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index e2de17cee6d..a78d20cc07e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -38,13 +38,13 @@
.gray-content-block.middle-block
%h2.issue-title
- = gfm escape_once(@issue.title)
+ = markdown escape_once(@issue.title), pipeline: :single_line
%div
- if @issue.description.present?
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@issue.description)
+ = markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field
= @issue.description
- if @closed_by_merge_requests.present?
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index b4f62a75890..9bfe202589e 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,12 +1,12 @@
.gray-content-block.middle-block
%h2.issue-title
- = gfm escape_once(@merge_request.title)
+ = markdown escape_once(@merge_request.title), pipeline: :single_line
%div
- if @merge_request.description.present?
.description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
- = markdown(@merge_request.description)
+ = markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field
= @merge_request.description
diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml
index e0013fb769a..55dbae598d3 100644
--- a/app/views/projects/merge_requests/widget/_open.html.haml
+++ b/app/views/projects/merge_requests/widget/_open.html.haml
@@ -26,4 +26,4 @@
%i.fa.fa-check
Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
= succeed '.' do
- != gfm(issues_sentence(@closes_issues))
+ != markdown issues_sentence(@closes_issues), pipeline: :gfm
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index c3bda794c65..7ecee440337 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -32,7 +32,7 @@
.gray-content-block.middle-block
%h2.issue-title
- = gfm escape_once(@milestone.title)
+ = markdown escape_once(@milestone.title), pipeline: :single_line
%div
- if @milestone.description.present?
.description
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index dd0abc8c746..922535e5c4a 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -38,7 +38,7 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
- = markdown(note.note, {no_header_anchors: true})
+ = markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index f3526ad0747..6ca919f7f80 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -12,7 +12,7 @@
= link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
- = gfm escape_once(truncate(commit.title, length: 40))
+ = markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
%td
%span.pull-right.cgray
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index eb0fd21c2d4..669e6119fb6 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -22,4 +22,4 @@
.gray-content-block.middle-block
%h2.issue-title
- = gfm escape_once(@snippet.title)
+ = markdown escape_once(@snippet.title), pipeline: :single_line
diff --git a/config/environments/test.rb b/config/environments/test.rb
index f96ac6f9753..d6842affa6c 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -7,6 +7,8 @@ Rails.application.configure do
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = false
+ config.cache_store = :null_store
+
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_files = true
config.static_cache_control = "public, max-age=3600"
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index d5208b8c93e..0fc725842ba 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -10,7 +10,9 @@ rescue
Settings.gitlab['session_expire_delay'] ||= 10080
end
-unless Rails.env.test?
+if Rails.env.test?
+ Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
+else
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: Rails.application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index bf33e5b1b1e..330d3342dd1 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -1,14 +1,10 @@
require 'asciidoctor'
-require 'html/pipeline'
module Gitlab
# Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
# the resulting HTML through HTML pipeline filters.
module Asciidoc
- # Provide autoload paths for filters to prevent a circular dependency error
- autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
-
DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
'env-gitlab', 'source-highlighter=html-pipeline'
@@ -24,13 +20,11 @@ module Gitlab
# :requested_path
# :ref
# asciidoc_opts - a Hash of options to pass to the Asciidoctor converter
- # html_opts - a Hash of options for HTML output:
- # :xhtml - output XHTML instead of HTML
#
- def self.render(input, context, asciidoc_opts = {}, html_opts = {})
- asciidoc_opts = asciidoc_opts.reverse_merge(
+ def self.render(input, context, asciidoc_opts = {})
+ asciidoc_opts.reverse_merge!(
safe: :secure,
- backend: html_opts[:xhtml] ? :xhtml5 : :html5,
+ backend: :html5,
attributes: []
)
asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS)
@@ -38,23 +32,10 @@ module Gitlab
html = ::Asciidoctor.convert(input, asciidoc_opts)
if context[:project]
- result = HTML::Pipeline.new(filters).call(html, context)
-
- save_opts = html_opts[:xhtml] ?
- Nokogiri::XML::Node::SaveOptions::AS_XHTML : 0
-
- html = result[:output].to_html(save_with: save_opts)
+ html = Gitlab::Markdown.render(html, context.merge(pipeline: :asciidoc))
end
html.html_safe
end
-
- private
-
- def self.filters
- [
- Gitlab::Markdown::RelativeLinkFilter
- ]
- end
end
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 886a09f52af..f4e2cefca51 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -19,24 +19,21 @@ module Gitlab
# context - Hash of context options passed to our HTML Pipeline
#
# Returns an HTML-safe String
- def self.render(markdown, context = {})
- html = renderer.render(markdown)
- html = gfm(html, context)
-
- html.html_safe
+ def self.render(text, context = {})
+ cache_key = context.delete(:cache_key)
+ cache_key = full_cache_key(cache_key, context[:pipeline])
+
+ if cache_key
+ Rails.cache.fetch(cache_key) do
+ cacheless_render(text, context)
+ end
+ else
+ cacheless_render(text, context)
+ end
end
- # Convert a Markdown String into HTML without going through the HTML
- # Pipeline.
- #
- # Note that because the pipeline is skipped, SanitizationFilter is as well.
- # Do not output the result of this method to the user.
- #
- # markdown - Markdown String
- #
- # Returns a String
- def self.render_without_gfm(markdown)
- renderer.render(markdown)
+ def self.render_result(text, context = {})
+ Pipeline[context[:pipeline]].call(text, context)
end
# Perform post-processing on an HTML String
@@ -46,156 +43,73 @@ module Gitlab
# permission to make (`RedactorFilter`).
#
# html - String to process
- # options - Hash of options to customize output
+ # context - Hash of options to customize output
# :pipeline - Symbol pipeline type
# :project - Project
# :user - User object
#
# Returns an HTML-safe String
- def self.post_process(html, options)
- context = {
- project: options[:project],
- current_user: options[:user]
- }
- doc = post_processor.to_document(html, context)
+ def self.post_process(html, context)
+ context = Pipeline[context[:pipeline]].transform_context(context)
- if options[:pipeline] == :atom
- doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
+ pipeline = Pipeline[:post_process]
+ if context[:xhtml]
+ pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
else
- doc.to_html
+ pipeline.to_html(html, context)
end.html_safe
end
- # Provide autoload paths for filters to prevent a circular dependency error
- autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
- autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
- autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter'
- autoload :EmojiFilter, 'gitlab/markdown/emoji_filter'
- autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter'
- autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter'
- autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
- autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
- autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
- autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
- autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
- autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
- autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
- autoload :SyntaxHighlightFilter, 'gitlab/markdown/syntax_highlight_filter'
- autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
- autoload :TaskListFilter, 'gitlab/markdown/task_list_filter'
- autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
- autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
-
- # Public: Parse the provided HTML with GitLab-Flavored Markdown
- #
- # html - HTML String
- # options - A Hash of options used to customize output (default: {})
- # :no_header_anchors - Disable header anchors in TableOfContentsFilter
- # :path - Current path String
- # :pipeline - Symbol pipeline type
- # :project - Current Project object
- # :project_wiki - Current ProjectWiki object
- # :ref - Current ref String
- #
- # Returns an HTML-safe String
- def self.gfm(html, options = {})
- return '' unless html.present?
-
- @pipeline ||= HTML::Pipeline.new(filters)
-
- context = {
- # SanitizationFilter
- pipeline: options[:pipeline],
-
- # EmojiFilter
- asset_host: Gitlab::Application.config.asset_host,
- asset_root: Gitlab.config.gitlab.base_url,
-
- # ReferenceFilter
- only_path: only_path_pipeline?(options[:pipeline]),
- project: options[:project],
-
- # RelativeLinkFilter
- project_wiki: options[:project_wiki],
- ref: options[:ref],
- requested_path: options[:path],
-
- # TableOfContentsFilter
- no_header_anchors: options[:no_header_anchors]
- }
-
- @pipeline.to_html(html, context).html_safe
- end
-
private
- # Check if a pipeline enables the `only_path` context option
- #
- # Returns Boolean
- def self.only_path_pipeline?(pipeline)
- case pipeline
- when :atom, :email
- false
- else
- true
- end
- end
-
- def self.redcarpet_options
- # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
- @redcarpet_options ||= {
- fenced_code_blocks: true,
- footnotes: true,
- lax_spacing: true,
- no_intra_emphasis: true,
- space_after_headers: true,
- strikethrough: true,
- superscript: true,
- tables: true
- }.freeze
- end
+ def self.cacheless_render(text, context = {})
+ result = render_result(text, context)
- def self.renderer
- @markdown ||= begin
- renderer = Redcarpet::Render::HTML.new
- Redcarpet::Markdown.new(renderer, redcarpet_options)
+ output = result[:output]
+ if output.respond_to?(:to_html)
+ output.to_html
+ else
+ output.to_s
end
end
- def self.post_processor
- @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
+ def self.full_cache_key(cache_key, pipeline_name)
+ return unless cache_key
+ ["markdown", *cache_key, pipeline_name || :full]
end
- # Filters used in our pipeline
- #
- # SanitizationFilter should come first so that all generated reference HTML
- # goes through untouched.
- #
- # See https://github.com/jch/html-pipeline#filters for more filters.
- def self.filters
- [
- Gitlab::Markdown::SyntaxHighlightFilter,
- Gitlab::Markdown::SanitizationFilter,
-
- Gitlab::Markdown::UploadLinkFilter,
- Gitlab::Markdown::EmojiFilter,
- Gitlab::Markdown::TableOfContentsFilter,
- Gitlab::Markdown::AutolinkFilter,
- Gitlab::Markdown::ExternalLinkFilter,
-
- Gitlab::Markdown::UserReferenceFilter,
- Gitlab::Markdown::IssueReferenceFilter,
- Gitlab::Markdown::ExternalIssueReferenceFilter,
- Gitlab::Markdown::MergeRequestReferenceFilter,
- Gitlab::Markdown::SnippetReferenceFilter,
- Gitlab::Markdown::CommitRangeReferenceFilter,
- Gitlab::Markdown::CommitReferenceFilter,
- Gitlab::Markdown::LabelReferenceFilter,
-
- Gitlab::Markdown::RelativeLinkFilter,
-
- Gitlab::Markdown::TaskListFilter
- ]
- end
+ # Provide autoload paths for filters to prevent a circular dependency error
+ autoload :AutolinkFilter, 'gitlab/markdown/filter/autolink_filter'
+ autoload :CommitRangeReferenceFilter, 'gitlab/markdown/filter/commit_range_reference_filter'
+ autoload :CommitReferenceFilter, 'gitlab/markdown/filter/commit_reference_filter'
+ autoload :EmojiFilter, 'gitlab/markdown/filter/emoji_filter'
+ autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/filter/external_issue_reference_filter'
+ autoload :ExternalLinkFilter, 'gitlab/markdown/filter/external_link_filter'
+ autoload :IssueReferenceFilter, 'gitlab/markdown/filter/issue_reference_filter'
+ autoload :LabelReferenceFilter, 'gitlab/markdown/filter/label_reference_filter'
+ autoload :MarkdownFilter, 'gitlab/markdown/filter/markdown_filter'
+ autoload :MergeRequestReferenceFilter, 'gitlab/markdown/filter/merge_request_reference_filter'
+ autoload :RedactorFilter, 'gitlab/markdown/filter/redactor_filter'
+ autoload :ReferenceGathererFilter, 'gitlab/markdown/filter/reference_gatherer_filter'
+ autoload :RelativeLinkFilter, 'gitlab/markdown/filter/relative_link_filter'
+ autoload :SanitizationFilter, 'gitlab/markdown/filter/sanitization_filter'
+ autoload :SnippetReferenceFilter, 'gitlab/markdown/filter/snippet_reference_filter'
+ autoload :SyntaxHighlightFilter, 'gitlab/markdown/filter/syntax_highlight_filter'
+ autoload :TableOfContentsFilter, 'gitlab/markdown/filter/table_of_contents_filter'
+ autoload :TaskListFilter, 'gitlab/markdown/filter/task_list_filter'
+ autoload :UserReferenceFilter, 'gitlab/markdown/filter/user_reference_filter'
+ autoload :UploadLinkFilter, 'gitlab/markdown/filter/upload_link_filter'
+
+ autoload :AsciidocPipeline, 'gitlab/markdown/pipeline/asciidoc_pipeline'
+ autoload :AtomPipeline, 'gitlab/markdown/pipeline/atom_pipeline'
+ autoload :DescriptionPipeline, 'gitlab/markdown/pipeline/description_pipeline'
+ autoload :EmailPipeline, 'gitlab/markdown/pipeline/email_pipeline'
+ autoload :FullPipeline, 'gitlab/markdown/pipeline/full_pipeline'
+ autoload :GfmPipeline, 'gitlab/markdown/pipeline/gfm_pipeline'
+ autoload :NotePipeline, 'gitlab/markdown/pipeline/note_pipeline'
+ autoload :PlainMarkdownPipeline, 'gitlab/markdown/pipeline/plain_markdown_pipeline'
+ autoload :PostProcessPipeline, 'gitlab/markdown/pipeline/post_process_pipeline'
+ autoload :ReferenceExtractionPipeline, 'gitlab/markdown/pipeline/reference_extraction_pipeline'
+ autoload :SingleLinePipeline, 'gitlab/markdown/pipeline/single_line_pipeline'
end
end
diff --git a/lib/gitlab/markdown/combined_pipeline.rb b/lib/gitlab/markdown/combined_pipeline.rb
new file mode 100644
index 00000000000..6b08a5e9f72
--- /dev/null
+++ b/lib/gitlab/markdown/combined_pipeline.rb
@@ -0,0 +1,27 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ module CombinedPipeline
+ def self.new(*pipelines)
+ Class.new(Pipeline) do
+ const_set :PIPELINES, pipelines
+
+ def self.pipelines
+ self::PIPELINES
+ end
+
+ def self.filters
+ pipelines.flat_map(&:filters)
+ end
+
+ def self.transform_context(context)
+ pipelines.reduce(context) do |context, pipeline|
+ pipeline.transform_context(context)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/autolink_filter.rb b/lib/gitlab/markdown/filter/autolink_filter.rb
index c37c3bc55bf..c37c3bc55bf 100644
--- a/lib/gitlab/markdown/autolink_filter.rb
+++ b/lib/gitlab/markdown/filter/autolink_filter.rb
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/filter/commit_range_reference_filter.rb
index 36b3258ef76..36b3258ef76 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/commit_range_reference_filter.rb
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/filter/commit_reference_filter.rb
index e3066a89b04..e3066a89b04 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/commit_reference_filter.rb
diff --git a/lib/gitlab/markdown/emoji_filter.rb b/lib/gitlab/markdown/filter/emoji_filter.rb
index da10e4d3760..da10e4d3760 100644
--- a/lib/gitlab/markdown/emoji_filter.rb
+++ b/lib/gitlab/markdown/filter/emoji_filter.rb
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/filter/external_issue_reference_filter.rb
index 14bdf5521fc..14bdf5521fc 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/external_issue_reference_filter.rb
diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/filter/external_link_filter.rb
index e09dfcb83c8..e09dfcb83c8 100644
--- a/lib/gitlab/markdown/external_link_filter.rb
+++ b/lib/gitlab/markdown/filter/external_link_filter.rb
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/filter/issue_reference_filter.rb
index 1ed69e2f431..1ed69e2f431 100644
--- a/lib/gitlab/markdown/issue_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/issue_reference_filter.rb
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/filter/label_reference_filter.rb
index a2026eecaeb..a2026eecaeb 100644
--- a/lib/gitlab/markdown/label_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/label_reference_filter.rb
diff --git a/lib/gitlab/markdown/filter/markdown_filter.rb b/lib/gitlab/markdown/filter/markdown_filter.rb
new file mode 100644
index 00000000000..921e2a0794e
--- /dev/null
+++ b/lib/gitlab/markdown/filter/markdown_filter.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module Markdown
+ class MarkdownFilter < HTML::Pipeline::TextFilter
+ def initialize(text, context = nil, result = nil)
+ super text, context, result
+ @text = @text.gsub "\r", ''
+ end
+
+ def call
+ html = self.class.renderer.render(@text)
+ html.rstrip!
+ html
+ end
+
+ private
+
+ def self.redcarpet_options
+ # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
+ @redcarpet_options ||= {
+ fenced_code_blocks: true,
+ footnotes: true,
+ lax_spacing: true,
+ no_intra_emphasis: true,
+ space_after_headers: true,
+ strikethrough: true,
+ superscript: true,
+ tables: true
+ }.freeze
+ end
+
+ def self.renderer
+ @renderer ||= begin
+ renderer = Redcarpet::Render::HTML.new
+ Redcarpet::Markdown.new(renderer, redcarpet_options)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/filter/merge_request_reference_filter.rb
index 2eb77c46da7..2eb77c46da7 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/merge_request_reference_filter.rb
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/filter/redactor_filter.rb
index bea714a01e7..33ef7ce18b5 100644
--- a/lib/gitlab/markdown/redactor_filter.rb
+++ b/lib/gitlab/markdown/filter/redactor_filter.rb
@@ -27,7 +27,7 @@ module Gitlab
def user_can_reference?(node)
if node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
- reference_filter = reference_type.constantize
+ reference_filter = Gitlab::Markdown.const_get(reference_type)
reference_filter.user_can_reference?(current_user, node, context)
else
diff --git a/lib/gitlab/markdown/reference_gatherer_filter.rb b/lib/gitlab/markdown/filter/reference_gatherer_filter.rb
index 00f983675e6..62f241b4967 100644
--- a/lib/gitlab/markdown/reference_gatherer_filter.rb
+++ b/lib/gitlab/markdown/filter/reference_gatherer_filter.rb
@@ -31,7 +31,7 @@ module Gitlab
return unless node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
- reference_filter = reference_type.constantize
+ reference_filter = Gitlab::Markdown.const_get(reference_type)
return if context[:reference_filter] && reference_filter != context[:reference_filter]
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/filter/relative_link_filter.rb
index 692c51fd324..81f60120fcd 100644
--- a/lib/gitlab/markdown/relative_link_filter.rb
+++ b/lib/gitlab/markdown/filter/relative_link_filter.rb
@@ -16,10 +16,7 @@ module Gitlab
def call
return doc unless linkable_files?
- doc.search('a').each do |el|
- klass = el.attr('class')
- next if klass && klass.include?('gfm')
-
+ doc.search('a:not(.gfm)').each do |el|
process_link_attr el.attribute('href')
end
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/filter/sanitization_filter.rb
index ffb9dc33b64..cf153f30622 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/filter/sanitization_filter.rb
@@ -11,7 +11,7 @@ module Gitlab
def whitelist
# Descriptions are more heavily sanitized, allowing only a few elements.
# See http://git.io/vkuAN
- if pipeline == :description
+ if context[:inline_sanitization]
whitelist = LIMITED
whitelist[:elements] -= %w(pre code img ol ul li)
else
@@ -25,10 +25,6 @@ module Gitlab
private
- def pipeline
- context[:pipeline] || :default
- end
-
def customized?(transformers)
transformers.last.source_location[0] == __FILE__
end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/filter/snippet_reference_filter.rb
index f7bd07c2a34..f7bd07c2a34 100644
--- a/lib/gitlab/markdown/snippet_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/snippet_reference_filter.rb
diff --git a/lib/gitlab/markdown/syntax_highlight_filter.rb b/lib/gitlab/markdown/filter/syntax_highlight_filter.rb
index 8597e02f0de..8597e02f0de 100644
--- a/lib/gitlab/markdown/syntax_highlight_filter.rb
+++ b/lib/gitlab/markdown/filter/syntax_highlight_filter.rb
diff --git a/lib/gitlab/markdown/table_of_contents_filter.rb b/lib/gitlab/markdown/filter/table_of_contents_filter.rb
index bbb3bf7fc8b..bbb3bf7fc8b 100644
--- a/lib/gitlab/markdown/table_of_contents_filter.rb
+++ b/lib/gitlab/markdown/filter/table_of_contents_filter.rb
diff --git a/lib/gitlab/markdown/task_list_filter.rb b/lib/gitlab/markdown/filter/task_list_filter.rb
index 2f133ae8500..2f133ae8500 100644
--- a/lib/gitlab/markdown/task_list_filter.rb
+++ b/lib/gitlab/markdown/filter/task_list_filter.rb
diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/gitlab/markdown/filter/upload_link_filter.rb
index fbada73ab86..fbada73ab86 100644
--- a/lib/gitlab/markdown/upload_link_filter.rb
+++ b/lib/gitlab/markdown/filter/upload_link_filter.rb
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/filter/user_reference_filter.rb
index 0a20d9c0347..0a20d9c0347 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/filter/user_reference_filter.rb
diff --git a/lib/gitlab/markdown/pipeline.rb b/lib/gitlab/markdown/pipeline.rb
new file mode 100644
index 00000000000..d683756f95a
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline.rb
@@ -0,0 +1,34 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class Pipeline
+ def self.[](name)
+ name ||= :full
+ Markdown.const_get("#{name.to_s.camelize}Pipeline")
+ end
+
+ def self.filters
+ []
+ end
+
+ def self.transform_context(context)
+ context
+ end
+
+ def self.html_pipeline
+ @html_pipeline ||= HTML::Pipeline.new(filters)
+ end
+
+ class << self
+ %i(call to_document to_html).each do |meth|
+ define_method(meth) do |text, context|
+ context = transform_context(context)
+
+ html_pipeline.send(meth, text, context)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb b/lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb
new file mode 100644
index 00000000000..6829b4acb95
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb
@@ -0,0 +1,13 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class AsciidocPipeline < Pipeline
+ def self.filters
+ [
+ Gitlab::Markdown::RelativeLinkFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/atom_pipeline.rb b/lib/gitlab/markdown/pipeline/atom_pipeline.rb
new file mode 100644
index 00000000000..e151f8f5e5a
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/atom_pipeline.rb
@@ -0,0 +1,14 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class AtomPipeline < FullPipeline
+ def self.transform_context(context)
+ super(context).merge(
+ only_path: false,
+ xhtml: true
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/description_pipeline.rb b/lib/gitlab/markdown/pipeline/description_pipeline.rb
new file mode 100644
index 00000000000..76f6948af8f
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/description_pipeline.rb
@@ -0,0 +1,14 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class DescriptionPipeline < FullPipeline
+ def self.transform_context(context)
+ super(context).merge(
+ # SanitizationFilter
+ inline_sanitization: true
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/email_pipeline.rb b/lib/gitlab/markdown/pipeline/email_pipeline.rb
new file mode 100644
index 00000000000..b88cb790270
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/email_pipeline.rb
@@ -0,0 +1,13 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class EmailPipeline < FullPipeline
+ def self.transform_context(context)
+ super(context).merge(
+ only_path: false
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/full_pipeline.rb b/lib/gitlab/markdown/pipeline/full_pipeline.rb
new file mode 100644
index 00000000000..b3b7a3c27c0
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/full_pipeline.rb
@@ -0,0 +1,9 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline)
+
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/gfm_pipeline.rb b/lib/gitlab/markdown/pipeline/gfm_pipeline.rb
new file mode 100644
index 00000000000..ca90bd75d77
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/gfm_pipeline.rb
@@ -0,0 +1,41 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class GfmPipeline < Pipeline
+ def self.filters
+ @filters ||= [
+ Gitlab::Markdown::SyntaxHighlightFilter,
+ Gitlab::Markdown::SanitizationFilter,
+
+ Gitlab::Markdown::UploadLinkFilter,
+ Gitlab::Markdown::EmojiFilter,
+ Gitlab::Markdown::TableOfContentsFilter,
+ Gitlab::Markdown::AutolinkFilter,
+ Gitlab::Markdown::ExternalLinkFilter,
+
+ Gitlab::Markdown::UserReferenceFilter,
+ Gitlab::Markdown::IssueReferenceFilter,
+ Gitlab::Markdown::ExternalIssueReferenceFilter,
+ Gitlab::Markdown::MergeRequestReferenceFilter,
+ Gitlab::Markdown::SnippetReferenceFilter,
+ Gitlab::Markdown::CommitRangeReferenceFilter,
+ Gitlab::Markdown::CommitReferenceFilter,
+ Gitlab::Markdown::LabelReferenceFilter,
+
+ Gitlab::Markdown::TaskListFilter
+ ]
+ end
+
+ def self.transform_context(context)
+ context.merge(
+ only_path: true,
+
+ # EmojiFilter
+ asset_host: Gitlab::Application.config.asset_host,
+ asset_root: Gitlab.config.gitlab.base_url
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/note_pipeline.rb b/lib/gitlab/markdown/pipeline/note_pipeline.rb
new file mode 100644
index 00000000000..a8bf5f42d8e
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/note_pipeline.rb
@@ -0,0 +1,14 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class NotePipeline < FullPipeline
+ def self.transform_context(context)
+ super(context).merge(
+ # TableOfContentsFilter
+ no_header_anchors: true
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb b/lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb
new file mode 100644
index 00000000000..0abb93f8a03
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb
@@ -0,0 +1,13 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class PlainMarkdownPipeline < Pipeline
+ def self.filters
+ [
+ Gitlab::Markdown::MarkdownFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/post_process_pipeline.rb b/lib/gitlab/markdown/pipeline/post_process_pipeline.rb
new file mode 100644
index 00000000000..60cc32f490e
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/post_process_pipeline.rb
@@ -0,0 +1,20 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class PostProcessPipeline < Pipeline
+ def self.filters
+ [
+ Gitlab::Markdown::RelativeLinkFilter,
+ Gitlab::Markdown::RedactorFilter
+ ]
+ end
+
+ def self.transform_context(context)
+ context.merge(
+ post_process: true
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb b/lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb
new file mode 100644
index 00000000000..a89ab462bac
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb
@@ -0,0 +1,13 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class ReferenceExtractionPipeline < Pipeline
+ def self.filters
+ [
+ Gitlab::Markdown::ReferenceGathererFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/pipeline/single_line_pipeline.rb b/lib/gitlab/markdown/pipeline/single_line_pipeline.rb
new file mode 100644
index 00000000000..2f24927b879
--- /dev/null
+++ b/lib/gitlab/markdown/pipeline/single_line_pipeline.rb
@@ -0,0 +1,9 @@
+require 'gitlab/markdown'
+
+module Gitlab
+ module Markdown
+ class SingleLinePipeline < GfmPipeline
+
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index b6d93e05ec7..3b83b8bd8f8 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -29,6 +29,10 @@ module Gitlab
end
end
+ def self.[](name)
+ Markdown.const_get("#{name.to_s.camelize}ReferenceFilter")
+ end
+
def self.user_can_reference?(user, node, context)
if node.has_attribute?('data-project')
project_id = node.attr('data-project').to_i
@@ -53,14 +57,14 @@ module Gitlab
# Examples:
#
# data_attribute(project: 1, issue: 2)
- # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
+ # # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
#
# data_attribute(project: 3, merge_request: 4)
- # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
+ # # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
#
# Returns a String
def data_attribute(attributes = {})
- attributes[:reference_filter] = self.class.name
+ attributes[:reference_filter] = self.class.name.demodulize
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 3c3478a1271..e83167fa7d7 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -9,30 +9,23 @@ module Gitlab
@project = project
@current_user = current_user
@load_lazy_references = load_lazy_references
+
+ @texts = []
+ @references = {}
end
- def analyze(text)
- references.clear
- @text = Gitlab::Markdown.render_without_gfm(text)
+ def analyze(text, options = {})
+ @texts << Gitlab::Markdown.render(text, options.merge(project: project))
end
%i(user label issue merge_request snippet commit commit_range).each do |type|
define_method("#{type}s") do
- references[type]
+ @references[type] ||= pipeline_result(type)
end
end
private
- def references
- @references ||= Hash.new do |references, type|
- type = type.to_sym
- next references[type] if references.has_key?(type)
-
- references[type] = pipeline_result(type)
- end
- end
-
# Instantiate and call HTML::Pipeline with a single reference filter type,
# returning the result
#
@@ -40,36 +33,24 @@ module Gitlab
#
# Returns the results Array for the requested filter type
def pipeline_result(filter_type)
- return [] if @text.blank?
-
- klass = "#{filter_type.to_s.camelize}ReferenceFilter"
- filter = Gitlab::Markdown.const_get(klass)
+ filter = Gitlab::Markdown::ReferenceFilter[filter_type]
context = {
- project: project,
- current_user: current_user,
+ pipeline: :reference_extraction,
- # We don't actually care about the links generated
- only_path: true,
- ignore_blockquotes: true,
+ project: project,
+ current_user: current_user,
# ReferenceGathererFilter
load_lazy_references: false,
reference_filter: filter
}
- # We need to autolink first to finds links to referables, and to prevent
- # numeric anchors to be parsed as issue references.
- filters = [
- Gitlab::Markdown::AutolinkFilter,
- filter,
- Gitlab::Markdown::ReferenceGathererFilter
- ]
-
- pipeline = HTML::Pipeline.new(filters, context)
- result = pipeline.call(@text)
-
- values = result[:references][filter_type].uniq
+ values = @texts.flat_map do |html|
+ text_context = context.dup
+ result = Gitlab::Markdown.render_result(html, text_context)
+ result[:references][filter_type]
+ end.uniq
if @load_lazy_references
values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 7d985fb6f1e..3a860899e18 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -50,9 +50,9 @@ module Gitlab
filtered_html = '<b>ASCII</b>'
allow(Asciidoctor).to receive(:convert).and_return(html)
- expect_any_instance_of(HTML::Pipeline).to receive(:call)
- .with(html, context)
- .and_return(output: Nokogiri::HTML.fragment(filtered_html))
+ expect(Gitlab::Markdown).to receive(:render)
+ .with(html, context.merge(pipeline: :asciidoc))
+ .and_return(filtered_html)
expect( render('foo', context) ).to eql filtered_html
end
diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
deleted file mode 100644
index 7cfec554a45..00000000000
--- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- expect(filter(act).to_html).to eq exp
- end
- end
- end
-
- context 'other schemes' do
- let(:link) { 'foo://bar.baz/' }
-
- it 'autolinks smb' do
- link = 'smb:///Volumes/shared/foo.pdf'
- doc = filter("See #{link}")
-
- expect(doc.at_css('a').text).to eq link
- expect(doc.at_css('a')['href']).to eq link
- end
-
- it 'autolinks irc' do
- link = 'irc://irc.freenode.net/git'
- doc = filter("See #{link}")
-
- expect(doc.at_css('a').text).to eq link
- expect(doc.at_css('a')['href']).to eq link
- end
-
- it 'does not include trailing punctuation' do
- doc = filter("See #{link}.")
- expect(doc.at_css('a').text).to eq link
-
- doc = filter("See #{link}, ok?")
- expect(doc.at_css('a').text).to eq link
-
- doc = filter("See #{link}...")
- expect(doc.at_css('a').text).to eq link
- end
-
- it 'does not include trailing HTML entities' do
- doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
-
- expect(doc.at_css('a')['href']).to eq link
- expect(doc.text).to eq "See <<<#{link}>>>"
- end
-
- it 'accepts link_attr options' do
- doc = filter("See #{link}", link_attr: { class: 'custom' })
- expect(doc.at_css('a')['class']).to eq 'custom'
- end
-
- described_class::IGNORE_PARENTS.each do |elem|
- it "ignores valid links contained inside '#{elem}' element" do
- exp = act = "<#{elem}>See #{link}</#{elem}>"
- expect(filter(act).to_html).to eq exp
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
deleted file mode 100644
index 6aecba5a676..00000000000
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ /dev/null
@@ -1,184 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(/\(<a.+>#{exp}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs' do
- exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
-
- expect(project).to receive(:valid_repo?).and_return(true)
- expect(project.repository).to receive(:commit).with(commit1.id.reverse)
- expect(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(/\(<a.+>#{exp}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
- expect(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(/\(<a.+>#{exp}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
- expect(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
-end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
deleted file mode 100644
index 9ac76b3228b..00000000000
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs' do
- invalid = invalidate_reference(reference)
- exp = act = "See #{invalid}"
-
- expect(project).to receive(:valid_repo?).and_return(true)
- expect(project.repository).to receive(:commit).with(invalid)
- expect(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(%{"></a>whatever<a title="})
-
- doc = reference_filter("See #{reference}")
- expect(doc.text).to eq "See #{commit.short_id}"
- end
-
- it 'includes default classes' do
- doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
- 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 attribute' do
- doc = reference_filter("See #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-commit')
- expect(link.attr('data-commit')).to eq commit.id
- end
-
- it 'supports an :only_path context' do
- doc = 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_commit_url(project.namespace, project, reference, only_path: true)
- end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("See #{reference}")
- expect(result[:references][:commit]).not_to be_empty
- end
- end
-
- context 'cross-project reference' do
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:commit) { project2.commit }
- let(:reference) { commit.to_reference(project) }
-
- it 'links to a valid reference' do
- doc = 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}.)")
-
- exp = Regexp.escape(project2.to_reference)
- expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Committed #{invalidate_reference(reference)}"
- expect(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(/\(<a.+>#{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(/<a.+>#{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
-end
diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
index e7774897780..f594fe4ccf6 100644
--- a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
+++ b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
@@ -1,35 +1,33 @@
require 'spec_helper'
-module Gitlab::Markdown
- describe CrossProjectReference, lib: true do
- include described_class
+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
+ describe '#project_from_ref' do
+ context 'when no project was referenced' do
+ it 'returns the project from context' do
+ project = double
- allow(self).to receive(:context).and_return({ project: project })
+ allow(self).to receive(:context).and_return({ project: project })
- expect(project_from_ref(nil)).to eq project
- end
+ expect(project_from_ref(nil)).to eq project
end
+ end
- context 'when referenced project does not exist' do
- it 'returns nil' do
- expect(project_from_ref('invalid/reference')).to be_nil
- end
+ context 'when referenced project does not exist' do
+ it 'returns nil' do
+ expect(project_from_ref('invalid/reference')).to be_nil
end
+ end
- context 'when referenced project exists' do
- it 'returns the referenced project' do
- project2 = double('referenced project')
+ context 'when referenced project exists' do
+ it 'returns the referenced project' do
+ project2 = double('referenced project')
- expect(Project).to receive(:find_with_namespace).
- with('cross/reference').and_return(project2)
+ expect(Project).to receive(:find_with_namespace).
+ with('cross/reference').and_return(project2)
- expect(project_from_ref('cross/reference')).to eq project2
- end
+ expect(project_from_ref('cross/reference')).to eq project2
end
end
end
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
deleted file mode 100644
index fb98aac3340..00000000000
--- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe EmojiFilter, lib: true do
- include FilterSpecHelper
-
- before do
- ActionController::Base.asset_host = 'https://foo.com'
- end
-
- it 'replaces supported emoji' do
- doc = filter('<p>:heart:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
- end
-
- it 'ignores unsupported emoji' do
- exp = act = '<p>:foo:</p>'
- doc = filter(act)
- expect(doc.to_html).to match Regexp.escape(exp)
- end
-
- it 'correctly encodes the URL' do
- doc = filter('<p>:+1:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
- end
-
- it 'matches at the start of a string' do
- doc = filter(':+1:')
- expect(doc.css('img').size).to eq 1
- end
-
- it 'matches at the end of a string' do
- doc = filter('This gets a :-1:')
- expect(doc.css('img').size).to eq 1
- end
-
- it 'matches with adjacent text' do
- doc = filter('+1 (:+1:)')
- expect(doc.css('img').size).to eq 1
- end
-
- it 'matches multiple emoji in a row' do
- doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
- expect(doc.css('img').size).to eq 3
- end
-
- it 'has a title attribute' do
- doc = filter(':-1:')
- expect(doc.css('img').first.attr('title')).to eq ':-1:'
- end
-
- it 'has an alt attribute' do
- doc = filter(':-1:')
- expect(doc.css('img').first.attr('alt')).to eq ':-1:'
- end
-
- it 'has an align attribute' do
- doc = filter(':8ball:')
- expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
- end
-
- it 'has an emoji class' do
- doc = filter(':cat:')
- expect(doc.css('img').first.attr('class')).to eq 'emoji'
- end
-
- it 'has height and width attributes' do
- doc = filter(':dog:')
- img = doc.css('img').first
-
- expect(img.attr('width')).to eq '20'
- expect(img.attr('height')).to eq '20'
- end
-
- it 'keeps whitespace intact' do
- doc = filter('This deserves a :+1:, big time.')
-
- expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
- end
-
- it 'uses a custom asset_root context' do
- root = Gitlab.config.gitlab.url + 'gitlab/root'
-
- doc = filter(':smile:', asset_root: root)
- expect(doc.css('img').first.attr('src')).to start_with(root)
- end
-
- it 'uses a custom asset_host context' do
- ActionController::Base.asset_host = 'https://cdn.example.com'
-
- doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
- expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
deleted file mode 100644
index cd92007e047..00000000000
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- expect(filter(act).to_html).to eq exp
- end
- end
-
- it 'ignores valid references when using default tracker' do
- expect(project).to receive(:default_issues_tracker?).and_return(true)
-
- exp = act = "Issue #{reference}"
- expect(filter(act).to_html).to eq exp
- end
-
- it 'links to a valid reference' do
- doc = filter("Issue #{reference}")
- expect(doc.css('a').first.attr('href'))
- .to eq helper.url_for_issue(reference, project)
- end
-
- it 'links to the external tracker' do
- doc = filter("Issue #{reference}")
- link = doc.css('a').first.attr('href')
-
- expect(link).to eq "http://jira.example/browse/#{reference}"
- end
-
- it 'links with adjacent text' do
- doc = filter("Issue (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
- end
-
- it 'includes a title attribute' do
- doc = filter("Issue #{reference}")
- expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
- end
-
- it 'escapes the title attribute' do
- allow(project.external_issue_tracker).to receive(:title).
- and_return(%{"></a>whatever<a title="})
-
- doc = filter("Issue #{reference}")
- expect(doc.text).to eq "Issue #{reference}"
- end
-
- it 'includes default classes' do
- doc = filter("Issue #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
- end
-
- it 'supports an :only_path context' do
- doc = filter("Issue #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
-
- expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
deleted file mode 100644
index 493c19a287d..00000000000
--- a/spec/lib/gitlab/markdown/external_link_filter_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe ExternalLinkFilter, lib: true do
- include FilterSpecHelper
-
- it 'ignores elements without an href attribute' do
- exp = act = %q(<a id="ignored">Ignore Me</a>)
- expect(filter(act).to_html).to eq exp
- end
-
- it 'ignores non-HTTP(S) links' do
- exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
- expect(filter(act).to_html).to eq exp
- end
-
- it 'skips internal links' do
- internal = Gitlab.config.gitlab.url
- exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
- expect(filter(act).to_html).to eq exp
- end
-
- it 'adds rel="nofollow" to external links' do
- act = %q(<a href="https://google.com/">Google</a>)
- doc = filter(act)
-
- expect(doc.at_css('a')).to have_attribute('rel')
- expect(doc.at_css('a')['rel']).to eq 'nofollow'
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb
new file mode 100644
index 00000000000..a0844aee559
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/autolink_filter_spec.rb
@@ -0,0 +1,112 @@
+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}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+
+ context 'other schemes' do
+ let(:link) { 'foo://bar.baz/' }
+
+ it 'autolinks smb' do
+ link = 'smb:///Volumes/shared/foo.pdf'
+ doc = filter("See #{link}")
+
+ expect(doc.at_css('a').text).to eq link
+ expect(doc.at_css('a')['href']).to eq link
+ end
+
+ it 'autolinks irc' do
+ link = 'irc://irc.freenode.net/git'
+ doc = filter("See #{link}")
+
+ expect(doc.at_css('a').text).to eq link
+ expect(doc.at_css('a')['href']).to eq link
+ end
+
+ it 'does not include trailing punctuation' do
+ doc = filter("See #{link}.")
+ expect(doc.at_css('a').text).to eq link
+
+ doc = filter("See #{link}, ok?")
+ expect(doc.at_css('a').text).to eq link
+
+ doc = filter("See #{link}...")
+ expect(doc.at_css('a').text).to eq link
+ end
+
+ it 'does not include trailing HTML entities' do
+ doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
+
+ expect(doc.at_css('a')['href']).to eq link
+ expect(doc.text).to eq "See <<<#{link}>>>"
+ end
+
+ it 'accepts link_attr options' do
+ doc = filter("See #{link}", link_attr: { class: 'custom' })
+ expect(doc.at_css('a')['class']).to eq 'custom'
+ end
+
+ described_class::IGNORE_PARENTS.each do |elem|
+ it "ignores valid links contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>See #{link}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..570c9767628
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/commit_range_reference_filter_spec.rb
@@ -0,0 +1,182 @@
+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}</#{elem}>"
+ 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(/\(<a.+>#{exp}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs' do
+ exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
+
+ expect(project).to receive(:valid_repo?).and_return(true)
+ expect(project.repository).to receive(:commit).with(commit1.id.reverse)
+ expect(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(/\(<a.+>#{exp}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+ expect(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(/\(<a.+>#{exp}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+ expect(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
new file mode 100644
index 00000000000..76e7957bbb9
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/commit_reference_filter_spec.rb
@@ -0,0 +1,163 @@
+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}</#{elem}>"
+ 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(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs' do
+ invalid = invalidate_reference(reference)
+ exp = act = "See #{invalid}"
+
+ expect(project).to receive(:valid_repo?).and_return(true)
+ expect(project.repository).to receive(:commit).with(invalid)
+ expect(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(%{"></a>whatever<a title="})
+
+ doc = reference_filter("See #{reference}")
+ expect(doc.text).to eq "See #{commit.short_id}"
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("See #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
+ 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 attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-commit')
+ expect(link.attr('data-commit')).to eq commit.id
+ end
+
+ it 'supports an :only_path context' do
+ doc = 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_commit_url(project.namespace, project, reference, only_path: true)
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("See #{reference}")
+ expect(result[:references][:commit]).not_to be_empty
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:commit) { project2.commit }
+ let(:reference) { commit.to_reference(project) }
+
+ it 'links to a valid reference' do
+ doc = 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}.)")
+
+ exp = Regexp.escape(project2.to_reference)
+ expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Committed #{invalidate_reference(reference)}"
+ expect(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(/\(<a.+>#{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(/<a.+>#{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
new file mode 100644
index 00000000000..ea9b81862cf
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/emoji_filter_spec.rb
@@ -0,0 +1,98 @@
+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('<p>:heart:</p>')
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
+ end
+
+ it 'ignores unsupported emoji' do
+ exp = act = '<p>:foo:</p>'
+ doc = filter(act)
+ expect(doc.to_html).to match Regexp.escape(exp)
+ end
+
+ it 'correctly encodes the URL' do
+ doc = filter('<p>:+1:</p>')
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
+ end
+
+ it 'matches at the start of a string' do
+ doc = filter(':+1:')
+ expect(doc.css('img').size).to eq 1
+ end
+
+ it 'matches at the end of a string' do
+ doc = filter('This gets a :-1:')
+ expect(doc.css('img').size).to eq 1
+ end
+
+ it 'matches with adjacent text' do
+ doc = filter('+1 (:+1:)')
+ expect(doc.css('img').size).to eq 1
+ end
+
+ it 'matches multiple emoji in a row' do
+ doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
+ expect(doc.css('img').size).to eq 3
+ end
+
+ it 'has a title attribute' do
+ doc = filter(':-1:')
+ expect(doc.css('img').first.attr('title')).to eq ':-1:'
+ end
+
+ it 'has an alt attribute' do
+ doc = filter(':-1:')
+ expect(doc.css('img').first.attr('alt')).to eq ':-1:'
+ end
+
+ it 'has an align attribute' do
+ doc = filter(':8ball:')
+ expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
+ end
+
+ it 'has an emoji class' do
+ doc = filter(':cat:')
+ expect(doc.css('img').first.attr('class')).to eq 'emoji'
+ end
+
+ it 'has height and width attributes' do
+ doc = filter(':dog:')
+ img = doc.css('img').first
+
+ expect(img.attr('width')).to eq '20'
+ expect(img.attr('height')).to eq '20'
+ end
+
+ it 'keeps whitespace intact' do
+ doc = filter('This deserves a :+1:, big time.')
+
+ expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
+ end
+
+ it 'uses a custom asset_root context' do
+ root = Gitlab.config.gitlab.url + 'gitlab/root'
+
+ doc = filter(':smile:', asset_root: root)
+ expect(doc.css('img').first.attr('src')).to start_with(root)
+ end
+
+ it 'uses a custom asset_host context' do
+ ActionController::Base.asset_host = 'https://cdn.example.com'
+
+ doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
+ expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
+ end
+end
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
new file mode 100644
index 00000000000..d8420102648
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/external_issue_reference_filter_spec.rb
@@ -0,0 +1,77 @@
+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}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ it 'ignores valid references when using default tracker' do
+ expect(project).to receive(:default_issues_tracker?).and_return(true)
+
+ exp = act = "Issue #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'links to a valid reference' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('href'))
+ .to eq helper.url_for_issue(reference, project)
+ end
+
+ it 'links to the external tracker' do
+ doc = filter("Issue #{reference}")
+ link = doc.css('a').first.attr('href')
+
+ expect(link).to eq "http://jira.example/browse/#{reference}"
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Issue (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
+ end
+
+ it 'escapes the title attribute' do
+ allow(project.external_issue_tracker).to receive(:title).
+ and_return(%{"></a>whatever<a title="})
+
+ doc = filter("Issue #{reference}")
+ expect(doc.text).to eq "Issue #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Issue #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb
new file mode 100644
index 00000000000..e559f5741cc
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/external_link_filter_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::Markdown::ExternalLinkFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'ignores elements without an href attribute' do
+ exp = act = %q(<a id="ignored">Ignore Me</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'ignores non-HTTP(S) links' do
+ exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'skips internal links' do
+ internal = Gitlab.config.gitlab.url
+ exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds rel="nofollow" to external links' do
+ act = %q(<a href="https://google.com/">Google</a>)
+ doc = filter(act)
+
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to eq 'nofollow'
+ end
+end
diff --git a/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb
new file mode 100644
index 00000000000..1aa5d44568e
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/issue_reference_filter_spec.rb
@@ -0,0 +1,209 @@
+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}</#{elem}>"
+ 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(/\(<a.+>#{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, %{"></a>whatever<a title="})
+
+ doc = reference_filter("Issue #{reference}")
+ expect(doc.text).to eq "Issue #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Issue #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-issue attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-issue')
+ expect(link.attr('data-issue')).to eq issue.id.to_s
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Issue #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Fixed #{reference}")
+ expect(result[:references][:issue]).to eq [issue]
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:reference) { issue.to_reference(project) }
+
+ it 'ignores valid references when cross-reference project uses external tracker' do
+ expect_any_instance_of(Project).to receive(:get_issue).
+ with(issue.iid).and_return(nil)
+
+ exp = act = "Issue #{reference}"
+ expect(reference_filter(act).to_html).to eq exp
+ end
+
+ 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(/\(<a.+>#{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(/\(<a.+>#{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{<a href="#{issue.to_reference(project)}">Reference</a>} }
+
+ 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(/\(<a.+>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{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
+
+ 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(/\(<a.+>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
new file mode 100644
index 00000000000..4fcbb329fe4
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/label_reference_filter_spec.rb
@@ -0,0 +1,179 @@
+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}</#{elem}>"
+ 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ 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{<a href="#{label.to_reference}">Label</a>} }
+
+ 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(\(<a.+>Label</a>\.\)))
+ 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
new file mode 100644
index 00000000000..589550e15c4
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/merge_request_reference_filter_spec.rb
@@ -0,0 +1,142 @@
+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}</#{elem}>"
+ 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(/\(<a.+>#{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, %{"></a>whatever<a title="})
+
+ doc = reference_filter("Merge #{reference}")
+ expect(doc.text).to eq "Merge #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("Merge #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Merge #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-merge-request attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-merge-request')
+ expect(link.attr('data-merge-request')).to eq merge.id.to_s
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Merge #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Merge #{reference}")
+ expect(result[:references][:merge_request]).to eq [merge]
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:merge) { create(:merge_request, source_project: project2) }
+ let(:reference) { merge.to_reference(project) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_merge_request_url(project2.namespace,
+ project, merge)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Merge (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid merge IDs on the referenced project' do
+ exp = act = "Merge #{invalidate_reference(reference)}"
+
+ expect(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(/\(<a.+>#{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
new file mode 100644
index 00000000000..9e6ee9f0d61
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/redactor_filter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::Markdown::RedactorFilter, lib: true do
+ include ActionView::Helpers::UrlHelper
+ include FilterSpecHelper
+
+ it 'ignores non-GFM links' do
+ html = %(See <a href="https://google.com/">Google</a>)
+ doc = filter(html, current_user: double)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ def reference_link(data)
+ link_to('text', '', class: 'gfm', data: data)
+ end
+
+ context 'with data-project' do
+ it 'removes unpermitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+
+ link = reference_link(project: project.id, reference_filter: '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
new file mode 100644
index 00000000000..abfb5ad5e49
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/reference_gatherer_filter_spec.rb
@@ -0,0 +1,87 @@
+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
new file mode 100644
index 00000000000..e0f53e2a533
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/relative_link_filter_spec.rb
@@ -0,0 +1,147 @@
+# 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)
+ %(<img src="#{path}" />)
+ end
+
+ def link(path)
+ %(<a href="#{path}">#{path}</a>)
+ end
+
+ let(:project) { create(:project) }
+ let(:project_path) { project.path_with_namespace }
+ let(:ref) { 'markdown' }
+ let(:project_wiki) { nil }
+ let(:requested_path) { '/' }
+
+ shared_examples :preserve_unchanged do
+ it 'does not modify any relative URL in anchor' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq 'README.md'
+ end
+
+ it 'does not modify any relative URL in image' do
+ doc = filter(image('files/images/logo-black.png'))
+ expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+ end
+ end
+
+ shared_examples :relative_to_requested do
+ it 'rebuilds URL relative to the requested path' do
+ doc = filter(link('users.md'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
+ end
+ end
+
+ context 'with a project_wiki' do
+ let(:project_wiki) { double('ProjectWiki') }
+ include_examples :preserve_unchanged
+ end
+
+ context 'without a repository' do
+ let(:project) { create(:empty_project) }
+ include_examples :preserve_unchanged
+ end
+
+ context 'with an empty repository' do
+ let(:project) { create(:project_empty_repo) }
+ include_examples :preserve_unchanged
+ end
+
+ it 'does not raise an exception on invalid URIs' do
+ act = link("://foo")
+ expect { filter(act) }.not_to raise_error
+ end
+
+ context 'with a valid repository' do
+ it 'rebuilds relative URL for a file in the repo' do
+ doc = filter(link('doc/api/README.md'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+ end
+
+ it 'rebuilds relative URL for a file in the repo up one directory' do
+ relative_link = link('../api/README.md')
+ doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
+
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+ end
+
+ it 'rebuilds relative URL for a file in the repo up multiple directories' do
+ relative_link = link('../../../api/README.md')
+ doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md')
+
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+ end
+
+ it 'rebuilds relative URL for a file in the repo with an anchor' do
+ doc = filter(link('README.md#section'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/README.md#section"
+ end
+
+ it 'rebuilds relative URL for a directory in the repo' do
+ doc = filter(link('doc/api/'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/tree/#{ref}/doc/api"
+ end
+
+ it 'rebuilds relative URL for an image in the repo' do
+ doc = filter(link('files/images/logo-black.png'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
+ end
+
+ it 'does not modify relative URL with an anchor only' do
+ doc = filter(link('#section-1'))
+ expect(doc.at_css('a')['href']).to eq '#section-1'
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ it 'supports Unicode filenames' do
+ path = 'files/images/한글.png'
+ escaped = Addressable::URI.escape(path)
+
+ # Stub these methods so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).
+ to receive(:file_exists?).and_return(true)
+ allow_any_instance_of(described_class).
+ to receive(:image?).with(path).and_return(true)
+
+ doc = filter(image(escaped))
+ expect(doc.at_css('img')['src']).to match '/raw/'
+ end
+
+ context 'when requested path is a file in the repo' do
+ let(:requested_path) { 'doc/api/README.md' }
+ include_examples :relative_to_requested
+ end
+
+ context 'when requested path is a directory in the repo' do
+ let(:requested_path) { 'doc/api' }
+ include_examples :relative_to_requested
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb
new file mode 100644
index 00000000000..a5e5ee0e08a
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/sanitization_filter_spec.rb
@@ -0,0 +1,197 @@
+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{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
+ exp = 'no inputs and no blinks'
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes tag attributes' do
+ act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
+ exp = %q{<a href="http://example.com/bar.html">Text</a>}
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes javascript in attributes' do
+ act = %q(<a href="javascript:alert('foo')">Text</a>)
+ exp = '<a>Text</a>'
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'allows whitelisted HTML tags from the user' do
+ exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes `class` attribute on any element' do
+ act = %q{<strong class="foo">Strong</strong>}
+ expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
+ end
+
+ it 'sanitizes `id` attribute on any element' do
+ act = %q{<em id="foo">Emphasis</em>}
+ expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
+ end
+ end
+
+ describe 'custom whitelist' do
+ it 'customizes the whitelist only once' do
+ instance = described_class.new('Foo')
+ 3.times { instance.whitelist }
+
+ expect(instance.whitelist[:transformers].size).to eq 5
+ end
+
+ it 'allows syntax highlighting' do
+ exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes `class` attribute from non-highlight spans' do
+ act = %q{<span class="k">def</span>}
+ expect(filter(act).to_html).to eq %q{<span>def</span>}
+ end
+
+ it 'allows `style` attribute on table elements' do
+ html = <<-HTML.strip_heredoc
+ <table>
+ <tr><th style="text-align: center">Head</th></tr>
+ <tr><td style="text-align: right">Body</th></tr>
+ </table>
+ HTML
+
+ doc = filter(html)
+
+ expect(doc.at_css('th')['style']).to eq 'text-align: center'
+ expect(doc.at_css('td')['style']).to eq 'text-align: right'
+ end
+
+ it 'allows `span` elements' do
+ exp = act = %q{<span>Hello</span>}
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'removes `rel` attribute from `a` elements' do
+ act = %q{<a href="#" rel="nofollow">Link</a>}
+ exp = %q{<a href="#">Link</a>}
+
+ expect(filter(act).to_html).to eq exp
+ end
+
+ # Adapted from the Sanitize test suite: http://git.io/vczrM
+ protocols = {
+ 'protocol-based JS injection: simple, no spaces' => {
+ input: '<a href="javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: simple, spaces before' => {
+ input: '<a href="javascript :alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: simple, spaces after' => {
+ input: '<a href="javascript: alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: simple, spaces before and after' => {
+ input: '<a href="javascript : alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: preceding colon' => {
+ input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: UTF-8 encoding' => {
+ input: '<a href="javascript&#58;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: long UTF-8 encoding' => {
+ input: '<a href="javascript&#0058;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
+ input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: hex encoding' => {
+ input: '<a href="javascript&#x3A;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: long hex encoding' => {
+ input: '<a href="javascript&#x003A;">foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: hex encoding without semicolons' => {
+ input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
+ output: '<a>foo</a>'
+ },
+
+ 'protocol-based JS injection: null char' => {
+ input: "<a href=java\0script:alert(\"XSS\")>foo</a>",
+ output: '<a href="java"></a>'
+ },
+
+ 'protocol-based JS injection: spaces and entities' => {
+ input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
+ output: '<a href="">foo</a>'
+ },
+ }
+
+ protocols.each do |name, data|
+ it "handles #{name}" do
+ doc = filter(data[:input])
+
+ expect(doc.to_html).to eq data[:output]
+ end
+ end
+
+ it 'allows non-standard anchor schemes' do
+ exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+
+ it 'allows relative links' do
+ exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+ end
+
+ context 'when inline_sanitization is true' do
+ it 'uses a stricter whitelist' do
+ doc = filter('<h1>Description</h1>', inline_sanitization: true)
+ expect(doc.to_html.strip).to eq 'Description'
+ end
+
+ %w(pre code img ol ul li).each do |elem|
+ it "removes '#{elem}' elements" do
+ act = "<#{elem}>Description</#{elem}>"
+ expect(filter(act, inline_sanitization: true).to_html.strip).
+ to eq 'Description'
+ end
+ end
+
+ %w(b i strong em a ins del sup sub p).each do |elem|
+ it "still allows '#{elem}' elements" do
+ exp = act = "<#{elem}>Description</#{elem}>"
+ expect(filter(act, inline_sanitization: true).to_html).to eq exp
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb
new file mode 100644
index 00000000000..51526b58597
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/snippet_reference_filter_spec.rb
@@ -0,0 +1,146 @@
+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}</#{elem}>"
+ 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(/\(<a.+>#{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, %{"></a>whatever<a title="})
+
+ doc = reference_filter("Snippet #{reference}")
+ expect(doc.text).to eq "Snippet #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("Snippet #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Snippet #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-snippet attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-snippet')
+ expect(link.attr('data-snippet')).to eq snippet.id.to_s
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Snippet #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Snippet #{reference}")
+ expect(result[:references][:snippet]).to eq [snippet]
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
+ let(:snippet) { create(:project_snippet, project: project2) }
+ let(:reference) { snippet.to_reference(project) }
+
+ it 'links to a valid reference' do
+ doc = 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(/\(<a.+>#{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(/\(<a.+>#{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(/<a.+>#{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
new file mode 100644
index 00000000000..8b76048f3e3
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/syntax_highlight_filter_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Gitlab::Markdown::SyntaxHighlightFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'highlights valid code blocks' do
+ result = filter('<pre><code>def fun end</code>')
+ expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
+ end
+
+ it 'passes through invalid code blocks' do
+ allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError)
+
+ result = filter('<pre><code>This is a test</code></pre>')
+ expect(result.to_html).to eq('<pre>This is a test</pre>')
+ end
+end
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
new file mode 100644
index 00000000000..c8c79c41847
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/table_of_contents_filter_spec.rb
@@ -0,0 +1,97 @@
+# encoding: UTF-8
+
+require 'spec_helper'
+
+describe Gitlab::Markdown::TableOfContentsFilter, lib: true do
+ include FilterSpecHelper
+
+ def header(level, text)
+ "<h#{level}>#{text}</h#{level}>\n"
+ end
+
+ it 'does nothing when :no_header_anchors is truthy' do
+ exp = act = header(1, 'Header')
+ expect(filter(act, no_header_anchors: 1).to_html).to eq exp
+ end
+
+ it 'does nothing with empty headers' do
+ exp = act = header(1, nil)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ 1.upto(6) do |i|
+ it "processes h#{i} elements" do
+ html = header(i, "Header #{i}")
+ doc = filter(html)
+
+ expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
+ end
+ end
+
+ describe 'anchor tag' do
+ it 'has an `anchor` class' do
+ doc = filter(header(1, 'Header'))
+ expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
+ end
+
+ it 'links to the id' do
+ doc = filter(header(1, 'Header'))
+ expect(doc.css('h1 a').first.attr('href')).to eq '#header'
+ end
+
+ describe 'generated IDs' do
+ it 'translates spaces to dashes' do
+ doc = filter(header(1, 'This header has spaces in it'))
+ expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
+ end
+
+ it 'squeezes multiple spaces and dashes' do
+ doc = filter(header(1, 'This---header is poorly-formatted'))
+ expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
+ end
+
+ it 'removes punctuation' do
+ doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
+ expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
+ end
+
+ it 'appends a unique number to duplicates' do
+ doc = filter(header(1, 'One') + header(2, 'One'))
+
+ expect(doc.css('h1 a').first.attr('id')).to eq 'one'
+ expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
+ end
+
+ it 'supports Unicode' do
+ doc = filter(header(1, '한글'))
+ expect(doc.css('h1 a').first.attr('id')).to eq '한글'
+ expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
+ end
+ end
+ end
+
+ describe 'result' do
+ def result(html)
+ HTML::Pipeline.new([described_class]).call(html)
+ end
+
+ let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) }
+ let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) }
+
+ it 'is contained within a `ul` element' do
+ expect(doc.children.first.name).to eq 'ul'
+ expect(doc.children.first.attr('class')).to eq 'section-nav'
+ end
+
+ it 'contains an `li` element for each header' do
+ expect(doc.css('li').length).to eq 2
+
+ links = doc.css('li a')
+
+ expect(links.first.attr('href')).to eq '#header-1'
+ expect(links.first.text).to eq 'Header 1'
+ expect(links.last.attr('href')).to eq '#header-2'
+ expect(links.last.text).to eq 'Header 2'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb
new file mode 100644
index 00000000000..1b1714ef882
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/task_list_filter_spec.rb
@@ -0,0 +1,10 @@
+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 = %(<ul><li>Item</li></ul>)
+ 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
new file mode 100644
index 00000000000..38a007b5bee
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/upload_link_filter_spec.rb
@@ -0,0 +1,73 @@
+# 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)
+ %(<img src="#{path}" />)
+ end
+
+ def link(path)
+ %(<a href="#{path}">#{path}</a>)
+ end
+
+ let(:project) { create(:project) }
+
+ shared_examples :preserve_unchanged do
+ it 'does not modify any relative URL in anchor' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq 'README.md'
+ end
+
+ it 'does not modify any relative URL in image' do
+ doc = filter(image('files/images/logo-black.png'))
+ expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+ end
+ end
+
+ it 'does not raise an exception on invalid URIs' do
+ act = link("://foo")
+ expect { filter(act) }.not_to raise_error
+ end
+
+ context 'with a valid repository' do
+ it 'rebuilds relative URL for a link' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href']).
+ to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'rebuilds relative URL for an image' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href']).
+ to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ it 'supports Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
+
+ # Stub these methods so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).
+ to receive(:file_exists?).and_return(true)
+ allow_any_instance_of(described_class).
+ to receive(:image?).with(path).and_return(true)
+
+ doc = filter(image(escaped))
+ expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb
new file mode 100644
index 00000000000..277037cf68a
--- /dev/null
+++ b/spec/lib/gitlab/markdown/filter/user_reference_filter_spec.rb
@@ -0,0 +1,147 @@
+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}</#{elem}>"
+ 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(/\(<a.+>#{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{<a href="#{user.to_reference}">User</a>} }
+
+ 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(/\(<a.+>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/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
deleted file mode 100644
index dee0826336b..00000000000
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(/\(<a.+>#{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, %{"></a>whatever<a title="})
-
- doc = reference_filter("Issue #{reference}")
- expect(doc.text).to eq "Issue #{reference}"
- end
-
- it 'includes default classes' do
- doc = reference_filter("Issue #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
- end
-
- it 'includes a data-project attribute' do
- doc = reference_filter("Issue #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-project')
- expect(link.attr('data-project')).to eq project.id.to_s
- end
-
- it 'includes a data-issue attribute' do
- doc = reference_filter("See #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-issue')
- expect(link.attr('data-issue')).to eq issue.id.to_s
- end
-
- it 'supports an :only_path context' do
- doc = reference_filter("Issue #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
-
- expect(link).not_to match %r(https?://)
- expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
- end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
- end
-
- context 'cross-project reference' do
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:empty_project, :public, namespace: namespace) }
- let(:issue) { create(:issue, project: project2) }
- let(:reference) { issue.to_reference(project) }
-
- it 'ignores valid references when cross-reference project uses external tracker' do
- expect_any_instance_of(Project).to receive(:get_issue).
- with(issue.iid).and_return(nil)
-
- exp = act = "Issue #{reference}"
- expect(reference_filter(act).to_html).to eq exp
- end
-
- 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(/\(<a.+>#{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(/\(<a.+>#{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{<a href="#{issue.to_reference(project)}">Reference</a>} }
-
- 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(/\(<a.+>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{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
-
- 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(/\(<a.+>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
-end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
deleted file mode 100644
index 799dbe6f2db..00000000000
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-require 'spec_helper'
-require 'html/pipeline'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- 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{<a href="#{label.to_reference}">Label</a>} }
-
- 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(\(<a.+>Label</a>\.\)))
- 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
-end
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
deleted file mode 100644
index ed70d5c4fa2..00000000000
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(/\(<a.+>#{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, %{"></a>whatever<a title="})
-
- doc = reference_filter("Merge #{reference}")
- expect(doc.text).to eq "Merge #{reference}"
- end
-
- it 'includes default classes' do
- doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
- end
-
- it 'includes a data-project attribute' do
- doc = reference_filter("Merge #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-project')
- expect(link.attr('data-project')).to eq project.id.to_s
- end
-
- it 'includes a data-merge-request attribute' do
- doc = reference_filter("See #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-merge-request')
- expect(link.attr('data-merge-request')).to eq merge.id.to_s
- end
-
- it 'supports an :only_path context' do
- doc = reference_filter("Merge #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
-
- expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
- end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Merge #{reference}")
- expect(result[:references][:merge_request]).to eq [merge]
- end
- end
-
- context 'cross-project reference' do
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
- let(:reference) { merge.to_reference(project) }
-
- 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(project2.namespace,
- project2, merge)
- end
-
- it 'links with adjacent text' do
- doc = reference_filter("Merge (#{reference}.)")
- expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
- end
-
- it 'ignores invalid merge IDs on the referenced project' do
- exp = act = "Merge #{invalidate_reference(reference)}"
-
- expect(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(/\(<a.+>#{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
-end
diff --git a/spec/lib/gitlab/markdown/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
deleted file mode 100644
index 27a3d8ec4ef..00000000000
--- a/spec/lib/gitlab/markdown/redactor_filter_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe RedactorFilter, lib: true do
- include ActionView::Helpers::UrlHelper
- include FilterSpecHelper
-
- it 'ignores non-GFM links' do
- html = %(See <a href="https://google.com/">Google</a>)
- doc = filter(html, current_user: double)
-
- expect(doc.css('a').length).to eq 1
- end
-
- def reference_link(data)
- link_to('text', '', class: 'gfm', data: data)
- end
-
- context 'with data-project' do
- it 'removes unpermitted Project references' do
- user = create(:user)
- project = create(:empty_project)
-
- link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
- doc = filter(link, current_user: user)
-
- expect(doc.css('a').length).to eq 0
- end
-
- it 'allows permitted Project references' do
- user = create(:user)
- project = create(:empty_project)
- project.team << [user, :master]
-
- link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
- doc = filter(link, current_user: user)
-
- expect(doc.css('a').length).to eq 1
- end
-
- it 'handles invalid Project references' do
- link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
-
- expect { filter(link) }.not_to raise_error
- end
- end
-
- context "for user references" do
-
- context 'with data-group' do
- it 'removes unpermitted Group references' do
- user = create(:user)
- group = create(:group)
-
- link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
- doc = filter(link, current_user: user)
-
- expect(doc.css('a').length).to eq 0
- end
-
- it 'allows permitted Group references' do
- user = create(:user)
- group = create(:group)
- group.add_developer(user)
-
- link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
- doc = filter(link, current_user: user)
-
- expect(doc.css('a').length).to eq 1
- end
-
- it 'handles invalid Group references' do
- link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-
- expect { filter(link) }.not_to raise_error
- end
- end
-
- context 'with data-user' do
- it 'allows any User reference' do
- user = create(:user)
-
- link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
- doc = filter(link)
-
- expect(doc.css('a').length).to eq 1
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
deleted file mode 100644
index 375dd689472..00000000000
--- a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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: Gitlab::Markdown::IssueReferenceFilter.name)
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:issue]).to be_empty
- end
-
- it 'allows permitted Project references' do
- user = create(:user)
- project = create(:empty_project)
- issue = create(:issue, project: project)
- project.team << [user, :master]
-
- link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:issue]).to eq([issue])
- end
-
- it 'handles invalid Project references' do
- link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
-
- expect { pipeline_result(link) }.not_to raise_error
- end
- end
- end
-
- context "for user references" do
-
- context 'with data-group' do
- it 'removes unpermitted Group references' do
- user = create(:user)
- group = create(:group)
-
- link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:user]).to be_empty
- end
-
- it 'allows permitted Group references' do
- user = create(:user)
- group = create(:group)
- group.add_developer(user)
-
- link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
- result = pipeline_result(link, current_user: user)
-
- expect(result[:references][:user]).to eq([user])
- end
-
- it 'handles invalid Group references' do
- link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
-
- expect { pipeline_result(link) }.not_to raise_error
- end
- end
-
- context 'with data-user' do
- it 'allows any User reference' do
- user = create(:user)
-
- link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
- result = pipeline_result(link)
-
- expect(result[:references][:user]).to eq([user])
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
deleted file mode 100644
index 7f193004e32..00000000000
--- a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-# encoding: UTF-8
-
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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)
- %(<img src="#{path}" />)
- end
-
- def link(path)
- %(<a href="#{path}">#{path}</a>)
- end
-
- let(:project) { create(:project) }
- let(:project_path) { project.path_with_namespace }
- let(:ref) { 'markdown' }
- let(:project_wiki) { nil }
- let(:requested_path) { '/' }
-
- shared_examples :preserve_unchanged do
- it 'does not modify any relative URL in anchor' do
- doc = filter(link('README.md'))
- expect(doc.at_css('a')['href']).to eq 'README.md'
- end
-
- it 'does not modify any relative URL in image' do
- doc = filter(image('files/images/logo-black.png'))
- expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
- end
- end
-
- shared_examples :relative_to_requested do
- it 'rebuilds URL relative to the requested path' do
- doc = filter(link('users.md'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
- end
- end
-
- context 'with a project_wiki' do
- let(:project_wiki) { double('ProjectWiki') }
- include_examples :preserve_unchanged
- end
-
- context 'without a repository' do
- let(:project) { create(:empty_project) }
- include_examples :preserve_unchanged
- end
-
- context 'with an empty repository' do
- let(:project) { create(:project_empty_repo) }
- include_examples :preserve_unchanged
- end
-
- it 'does not raise an exception on invalid URIs' do
- act = link("://foo")
- expect { filter(act) }.not_to raise_error
- end
-
- context 'with a valid repository' do
- it 'rebuilds relative URL for a file in the repo' do
- doc = filter(link('doc/api/README.md'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
- end
-
- it 'rebuilds relative URL for a file in the repo up one directory' do
- relative_link = link('../api/README.md')
- doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
-
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
- end
-
- it 'rebuilds relative URL for a file in the repo up multiple directories' do
- relative_link = link('../../../api/README.md')
- doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md')
-
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
- end
-
- it 'rebuilds relative URL for a file in the repo with an anchor' do
- doc = filter(link('README.md#section'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/blob/#{ref}/README.md#section"
- end
-
- it 'rebuilds relative URL for a directory in the repo' do
- doc = filter(link('doc/api/'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/tree/#{ref}/doc/api"
- end
-
- it 'rebuilds relative URL for an image in the repo' do
- doc = filter(link('files/images/logo-black.png'))
- expect(doc.at_css('a')['href']).
- to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
- end
-
- it 'does not modify relative URL with an anchor only' do
- doc = filter(link('#section-1'))
- expect(doc.at_css('a')['href']).to eq '#section-1'
- end
-
- it 'does not modify absolute URL' do
- doc = filter(link('http://example.com'))
- expect(doc.at_css('a')['href']).to eq 'http://example.com'
- end
-
- it 'supports Unicode filenames' do
- path = 'files/images/한글.png'
- escaped = Addressable::URI.escape(path)
-
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class).
- to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class).
- to receive(:image?).with(path).and_return(true)
-
- doc = filter(image(escaped))
- expect(doc.at_css('img')['src']).to match '/raw/'
- end
-
- context 'when requested path is a file in the repo' do
- let(:requested_path) { 'doc/api/README.md' }
- include_examples :relative_to_requested
- end
-
- context 'when requested path is a directory in the repo' do
- let(:requested_path) { 'doc/api' }
- include_examples :relative_to_requested
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
deleted file mode 100644
index 522481b45a5..00000000000
--- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe SanitizationFilter, lib: true do
- include FilterSpecHelper
-
- describe 'default whitelist' do
- it 'sanitizes tags that are not whitelisted' do
- act = %q{<textarea>no inputs</textarea> and <blink>no blinks</blink>}
- exp = 'no inputs and no blinks'
- expect(filter(act).to_html).to eq exp
- end
-
- it 'sanitizes tag attributes' do
- act = %q{<a href="http://example.com/bar.html" onclick="bar">Text</a>}
- exp = %q{<a href="http://example.com/bar.html">Text</a>}
- expect(filter(act).to_html).to eq exp
- end
-
- it 'sanitizes javascript in attributes' do
- act = %q(<a href="javascript:alert('foo')">Text</a>)
- exp = '<a>Text</a>'
- expect(filter(act).to_html).to eq exp
- end
-
- it 'allows whitelisted HTML tags from the user' do
- exp = act = "<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>"
- expect(filter(act).to_html).to eq exp
- end
-
- it 'sanitizes `class` attribute on any element' do
- act = %q{<strong class="foo">Strong</strong>}
- expect(filter(act).to_html).to eq %q{<strong>Strong</strong>}
- end
-
- it 'sanitizes `id` attribute on any element' do
- act = %q{<em id="foo">Emphasis</em>}
- expect(filter(act).to_html).to eq %q{<em>Emphasis</em>}
- end
- end
-
- describe 'custom whitelist' do
- it 'customizes the whitelist only once' do
- instance = described_class.new('Foo')
- 3.times { instance.whitelist }
-
- expect(instance.whitelist[:transformers].size).to eq 5
- end
-
- it 'allows syntax highlighting' do
- exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
- expect(filter(act).to_html).to eq exp
- end
-
- it 'sanitizes `class` attribute from non-highlight spans' do
- act = %q{<span class="k">def</span>}
- expect(filter(act).to_html).to eq %q{<span>def</span>}
- end
-
- it 'allows `style` attribute on table elements' do
- html = <<-HTML.strip_heredoc
- <table>
- <tr><th style="text-align: center">Head</th></tr>
- <tr><td style="text-align: right">Body</th></tr>
- </table>
- HTML
-
- doc = filter(html)
-
- expect(doc.at_css('th')['style']).to eq 'text-align: center'
- expect(doc.at_css('td')['style']).to eq 'text-align: right'
- end
-
- it 'allows `span` elements' do
- exp = act = %q{<span>Hello</span>}
- expect(filter(act).to_html).to eq exp
- end
-
- it 'removes `rel` attribute from `a` elements' do
- act = %q{<a href="#" rel="nofollow">Link</a>}
- exp = %q{<a href="#">Link</a>}
-
- expect(filter(act).to_html).to eq exp
- end
-
- # Adapted from the Sanitize test suite: http://git.io/vczrM
- protocols = {
- 'protocol-based JS injection: simple, no spaces' => {
- input: '<a href="javascript:alert(\'XSS\');">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: simple, spaces before' => {
- input: '<a href="javascript :alert(\'XSS\');">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: simple, spaces after' => {
- input: '<a href="javascript: alert(\'XSS\');">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: simple, spaces before and after' => {
- input: '<a href="javascript : alert(\'XSS\');">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: preceding colon' => {
- input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: UTF-8 encoding' => {
- input: '<a href="javascript&#58;">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: long UTF-8 encoding' => {
- input: '<a href="javascript&#0058;">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: long UTF-8 encoding without semicolons' => {
- input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: hex encoding' => {
- input: '<a href="javascript&#x3A;">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: long hex encoding' => {
- input: '<a href="javascript&#x003A;">foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: hex encoding without semicolons' => {
- input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
- output: '<a>foo</a>'
- },
-
- 'protocol-based JS injection: null char' => {
- input: "<a href=java\0script:alert(\"XSS\")>foo</a>",
- output: '<a href="java"></a>'
- },
-
- 'protocol-based JS injection: spaces and entities' => {
- input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
- output: '<a href="">foo</a>'
- },
- }
-
- protocols.each do |name, data|
- it "handles #{name}" do
- doc = filter(data[:input])
-
- expect(doc.to_html).to eq data[:output]
- end
- end
-
- it 'allows non-standard anchor schemes' do
- exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
- act = filter(exp)
-
- expect(act.to_html).to eq exp
- end
-
- it 'allows relative links' do
- exp = %q{<a href="foo/bar.md">foo/bar.md</a>}
- act = filter(exp)
-
- expect(act.to_html).to eq exp
- end
- end
-
- context 'when pipeline is :description' do
- it 'uses a stricter whitelist' do
- doc = filter('<h1>Description</h1>', pipeline: :description)
- expect(doc.to_html.strip).to eq 'Description'
- end
-
- %w(pre code img ol ul li).each do |elem|
- it "removes '#{elem}' elements" do
- act = "<#{elem}>Description</#{elem}>"
- expect(filter(act, pipeline: :description).to_html.strip).
- to eq 'Description'
- end
- end
-
- %w(b i strong em a ins del sup sub p).each do |elem|
- it "still allows '#{elem}' elements" do
- exp = act = "<#{elem}>Description</#{elem}>"
- expect(filter(act, pipeline: :description).to_html).to eq exp
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
deleted file mode 100644
index 279244c5ac2..00000000000
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(/\(<a.+>#{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, %{"></a>whatever<a title="})
-
- doc = reference_filter("Snippet #{reference}")
- expect(doc.text).to eq "Snippet #{reference}"
- end
-
- it 'includes default classes' do
- doc = reference_filter("Snippet #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
- end
-
- it 'includes a data-project attribute' do
- doc = reference_filter("Snippet #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-project')
- expect(link.attr('data-project')).to eq project.id.to_s
- end
-
- it 'includes a data-snippet attribute' do
- doc = reference_filter("See #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-snippet')
- expect(link.attr('data-snippet')).to eq snippet.id.to_s
- end
-
- it 'supports an :only_path context' do
- doc = reference_filter("Snippet #{reference}", only_path: true)
- link = doc.css('a').first.attr('href')
-
- expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
- end
-
- it 'adds to the results hash' do
- result = reference_pipeline_result("Snippet #{reference}")
- expect(result[:references][:snippet]).to eq [snippet]
- end
- end
-
- context 'cross-project reference' do
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:empty_project, :public, namespace: namespace) }
- let(:snippet) { create(:project_snippet, project: project2) }
- let(:reference) { snippet.to_reference(project) }
-
- it 'links to a valid reference' do
- doc = 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(/\(<a.+>#{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(/\(<a.+>#{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(/<a.+>#{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
-end
diff --git a/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb b/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb
deleted file mode 100644
index f9be5ad2a6e..00000000000
--- a/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe SyntaxHighlightFilter, lib: true do
- include FilterSpecHelper
-
- it 'highlights valid code blocks' do
- result = filter('<pre><code>def fun end</code>')
- expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
- end
-
- it 'passes through invalid code blocks' do
- allow_any_instance_of(SyntaxHighlightFilter).to receive(:block_code).and_raise(StandardError)
-
- result = filter('<pre><code>This is a test</code></pre>')
- expect(result.to_html).to eq('<pre>This is a test</pre>')
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
deleted file mode 100644
index 8c3c6e50b75..00000000000
--- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# encoding: UTF-8
-
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe TableOfContentsFilter, lib: true do
- include FilterSpecHelper
-
- def header(level, text)
- "<h#{level}>#{text}</h#{level}>\n"
- end
-
- it 'does nothing when :no_header_anchors is truthy' do
- exp = act = header(1, 'Header')
- expect(filter(act, no_header_anchors: 1).to_html).to eq exp
- end
-
- it 'does nothing with empty headers' do
- exp = act = header(1, nil)
- expect(filter(act).to_html).to eq exp
- end
-
- 1.upto(6) do |i|
- it "processes h#{i} elements" do
- html = header(i, "Header #{i}")
- doc = filter(html)
-
- expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}"
- end
- end
-
- describe 'anchor tag' do
- it 'has an `anchor` class' do
- doc = filter(header(1, 'Header'))
- expect(doc.css('h1 a').first.attr('class')).to eq 'anchor'
- end
-
- it 'links to the id' do
- doc = filter(header(1, 'Header'))
- expect(doc.css('h1 a').first.attr('href')).to eq '#header'
- end
-
- describe 'generated IDs' do
- it 'translates spaces to dashes' do
- doc = filter(header(1, 'This header has spaces in it'))
- expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it'
- end
-
- it 'squeezes multiple spaces and dashes' do
- doc = filter(header(1, 'This---header is poorly-formatted'))
- expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted'
- end
-
- it 'removes punctuation' do
- doc = filter(header(1, "This, header! is, filled. with @ punctuation?"))
- expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation'
- end
-
- it 'appends a unique number to duplicates' do
- doc = filter(header(1, 'One') + header(2, 'One'))
-
- expect(doc.css('h1 a').first.attr('id')).to eq 'one'
- expect(doc.css('h2 a').first.attr('id')).to eq 'one-1'
- end
-
- it 'supports Unicode' do
- doc = filter(header(1, '한글'))
- expect(doc.css('h1 a').first.attr('id')).to eq '한글'
- expect(doc.css('h1 a').first.attr('href')).to eq '#한글'
- end
- end
- end
-
- describe 'result' do
- def result(html)
- HTML::Pipeline.new([described_class]).call(html)
- end
-
- let(:results) { result(header(1, 'Header 1') + header(2, 'Header 2')) }
- let(:doc) { Nokogiri::XML::DocumentFragment.parse(results[:toc]) }
-
- it 'is contained within a `ul` element' do
- expect(doc.children.first.name).to eq 'ul'
- expect(doc.children.first.attr('class')).to eq 'section-nav'
- end
-
- it 'contains an `li` element for each header' do
- expect(doc.css('li').length).to eq 2
-
- links = doc.css('li a')
-
- expect(links.first.attr('href')).to eq '#header-1'
- expect(links.first.text).to eq 'Header 1'
- expect(links.last.attr('href')).to eq '#header-2'
- expect(links.last.text).to eq 'Header 2'
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
deleted file mode 100644
index cbcfd92956f..00000000000
--- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe TaskListFilter, lib: true do
- include FilterSpecHelper
-
- it 'does not apply `task-list` class to non-task lists' do
- exp = act = %(<ul><li>Item</li></ul>)
- expect(filter(act).to_html).to eq exp
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
deleted file mode 100644
index 9087f823d7f..00000000000
--- a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# encoding: UTF-8
-
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe UploadLinkFilter, lib: true do
- def filter(doc, contexts = {})
- contexts.reverse_merge!({
- project: project
- })
-
- described_class.call(doc, contexts)
- end
-
- def image(path)
- %(<img src="#{path}" />)
- end
-
- def link(path)
- %(<a href="#{path}">#{path}</a>)
- end
-
- let(:project) { create(:project) }
-
- shared_examples :preserve_unchanged do
- it 'does not modify any relative URL in anchor' do
- doc = filter(link('README.md'))
- expect(doc.at_css('a')['href']).to eq 'README.md'
- end
-
- it 'does not modify any relative URL in image' do
- doc = filter(image('files/images/logo-black.png'))
- expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
- end
- end
-
- it 'does not raise an exception on invalid URIs' do
- act = link("://foo")
- expect { filter(act) }.not_to raise_error
- end
-
- context 'with a valid repository' do
- it 'rebuilds relative URL for a link' do
- doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('a')['href']).
- to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
- end
-
- it 'rebuilds relative URL for an image' do
- doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.at_css('a')['href']).
- to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
- end
-
- it 'does not modify absolute URL' do
- doc = filter(link('http://example.com'))
- expect(doc.at_css('a')['href']).to eq 'http://example.com'
- end
-
- it 'supports Unicode filenames' do
- path = '/uploads/한글.png'
- escaped = Addressable::URI.escape(path)
-
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class).
- to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class).
- to receive(:image?).with(path).and_return(true)
-
- doc = filter(image(escaped))
- expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
deleted file mode 100644
index 3dca68edfa8..00000000000
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-require 'spec_helper'
-
-module Gitlab::Markdown
- describe 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}</#{elem}>"
- 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(/\(<a.+>#{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{<a href="#{user.to_reference}">User</a>} }
-
- 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(/\(<a.+>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
-end