From f272ee6eba37548cbd8919139d583a71ffdac8dc Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 2 Nov 2016 21:49:13 -0200 Subject: Add shorthand support to gitlab markdown references --- app/helpers/gitlab_markdown_helper.rb | 2 +- app/helpers/labels_helper.rb | 9 +- app/models/commit.rb | 22 +- app/models/commit_range.rb | 17 +- app/models/concerns/referable.rb | 13 - app/models/issue.rb | 6 +- app/models/label.rb | 15 +- app/models/merge_request.rb | 6 +- app/models/milestone.rb | 13 +- app/models/project.rb | 33 ++- app/models/snippet.rb | 8 +- changelogs/unreleased/glm-shorthand-reference.yml | 4 + lib/banzai/filter/abstract_reference_filter.rb | 23 +- lib/banzai/filter/commit_range_reference_filter.rb | 2 +- lib/banzai/filter/commit_reference_filter.rb | 2 +- lib/banzai/filter/label_reference_filter.rb | 54 +--- lib/banzai/filter/milestone_reference_filter.rb | 20 +- spec/features/issues/move_spec.rb | 2 +- spec/features/merge_requests/create_new_mr_spec.rb | 2 +- spec/helpers/labels_helper_spec.rb | 4 +- .../lib/banzai/filter/abstract_link_filter_spec.rb | 4 +- .../filter/commit_range_reference_filter_spec.rb | 99 +++++-- .../banzai/filter/commit_reference_filter_spec.rb | 78 +++++- .../banzai/filter/issue_reference_filter_spec.rb | 108 +++++++- .../banzai/filter/label_reference_filter_spec.rb | 291 +++++++++++++++++---- .../filter/merge_request_reference_filter_spec.rb | 92 ++++++- .../filter/milestone_reference_filter_spec.rb | 108 +++++++- .../banzai/filter/snippet_reference_filter_spec.rb | 85 +++++- spec/lib/gitlab/gfm/reference_rewriter_spec.rb | 4 +- spec/models/commit_range_spec.rb | 8 +- spec/models/commit_spec.rb | 14 +- spec/models/group_label_spec.rb | 10 + spec/models/issue_spec.rb | 10 +- spec/models/merge_request_spec.rb | 9 +- spec/models/milestone_spec.rb | 14 + spec/models/project_label_spec.rb | 4 +- spec/models/project_spec.rb | 66 ++++- spec/models/snippet_spec.rb | 29 +- spec/services/issues/move_service_spec.rb | 4 +- spec/services/system_note_service_spec.rb | 2 +- 40 files changed, 1010 insertions(+), 286 deletions(-) create mode 100644 changelogs/unreleased/glm-shorthand-reference.yml diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 0772d848289..eb435cc1783 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -174,7 +174,7 @@ module GitlabMarkdownHelper # Returns a String def cross_project_reference(project, entity) if entity.respond_to?(:to_reference) - "#{project.to_reference}#{entity.to_reference}" + entity.to_reference(project) else '' end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 4f180456b16..e5b1e6e8bc7 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -82,12 +82,6 @@ module LabelsHelper span.html_safe end - def render_colored_cross_project_label(label, source_project = nil, tooltip: true) - label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace - label_suffix = " in #{escape_once(label_suffix)}" - render_colored_label(label, label_suffix, tooltip: tooltip) - end - def suggested_colors [ '#0033CC', @@ -166,6 +160,5 @@ module LabelsHelper end # Required for Banzai::Filter::LabelReferenceFilter - module_function :render_colored_label, :render_colored_cross_project_label, - :text_color_for_bg, :escape_once + module_function :render_colored_label, :text_color_for_bg, :escape_once end diff --git a/app/models/commit.rb b/app/models/commit.rb index 176c524cf7b..248140f421b 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -92,19 +92,11 @@ class Commit end def to_reference(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.id - else - self.id - end + commit_reference(from_project, id) end def reference_link_text(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.short_id - else - self.short_id - end + commit_reference(from_project, short_id) end def diff_line_count @@ -329,6 +321,16 @@ class Commit private + def commit_reference(from_project, referable_commit_id) + reference = project.to_reference(from_project) + + if reference.present? + "#{reference}#{self.class.reference_prefix}#{referable_commit_id}" + else + referable_commit_id + end + end + def find_author_by_any_email User.find_by_any_email(author_email.downcase) end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index ac2477fd973..d9af7f6c139 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -90,21 +90,24 @@ class CommitRange alias_method :id, :to_s def to_reference(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.id + project_reference = project.to_reference(from_project) + + if project_reference.present? + project_reference + self.class.reference_prefix + self.id else self.id end end def reference_link_text(from_project = nil) - reference = ref_from + notation + ref_to + project_reference = project.to_reference(from_project) + reference = ref_from + notation + ref_to - if cross_project_reference?(from_project) - reference = project.to_reference + self.class.reference_prefix + reference + if project_reference.present? + project_reference + self.class.reference_prefix + reference + else + reference end - - reference end # Return a Hash of parameters for passing to a URL helper diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index dee940a3f88..8ba009fe04f 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -72,17 +72,4 @@ module Referable }x end end - - private - - # Check if a reference is being done cross-project - # - # from_project - Refering Project object - def cross_project_reference?(from_project) - if self.is_a?(Project) - self != from_project - else - from_project && self.project && self.project != from_project - end - end end diff --git a/app/models/issue.rb b/app/models/issue.rb index fbf07040301..7fe92051037 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference - end - - reference + "#{project.to_reference(from_project)}#{reference}" end def referenced_merge_requests(current_user = nil) diff --git a/app/models/label.rb b/app/models/label.rb index d9287f2dc29..d38c37344c9 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -144,9 +144,10 @@ class Label < ActiveRecord::Base # # Examples: # - # Label.first.to_reference # => "~1" - # Label.first.to_reference(format: :name) # => "~\"bug\"" - # Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" + # Label.first.to_reference # => "~1" + # Label.first.to_reference(format: :name) # => "~\"bug\"" + # Label.first.to_reference(project, same_namespace_project) # => "gitlab-ce~1" + # Label.first.to_reference(project, another_namespace_project) # => "gitlab-org/gitlab-ce~1" # # Returns a String # @@ -154,8 +155,8 @@ class Label < ActiveRecord::Base format_reference = label_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - if cross_project_reference?(source_project, target_project) - source_project.to_reference + reference + if source_project + "#{source_project.to_reference(target_project)}#{reference}" else reference end @@ -169,10 +170,6 @@ class Label < ActiveRecord::Base private - def cross_project_reference?(source_project, target_project) - source_project && target_project && source_project != target_project - end - def issues_count(user, params = {}) params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') IssuesFinder.new(user, params.with_indifferent_access).execute.count diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bfb016df46d..4de4a83a041 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference - end - - reference + "#{project.to_reference(from_project)}#{reference}" end def first_commit diff --git a/app/models/milestone.rb b/app/models/milestone.rb index c774e69080c..45ca97adad1 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -113,19 +113,16 @@ class Milestone < ActiveRecord::Base # # Examples: # - # Milestone.first.to_reference # => "%1" - # Milestone.first.to_reference(format: :name) # => "%\"goal\"" - # Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1" + # Milestone.first.to_reference # => "%1" + # Milestone.first.to_reference(format: :name) # => "%\"goal\"" + # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" + # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # def to_reference(from_project = nil, format: :iid) format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end + "#{project.to_reference(from_project)}#{reference}" end def reference_link_text(from_project = nil) diff --git a/app/models/project.rb b/app/models/project.rb index f01cb613b85..9d58aff4033 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -419,7 +419,11 @@ class Project < ActiveRecord::Base def reference_pattern name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR - %r{(?#{name_pattern}/#{name_pattern})} + + %r{ + ((?#{name_pattern})\/)? + (?#{name_pattern}) + }x end def trending @@ -650,8 +654,20 @@ class Project < ActiveRecord::Base end end - def to_reference(_from_project = nil) - path_with_namespace + def to_reference(from_project = nil) + if cross_namespace_reference?(from_project) + path_with_namespace + elsif cross_project_reference?(from_project) + path + end + end + + def to_human_reference(from_project = nil) + if cross_namespace_reference?(from_project) + name_with_namespace + elsif cross_project_reference?(from_project) + name + end end def web_url @@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base private + # Check if a reference is being done cross-project + # + # from_project - Refering Project object + def cross_project_reference?(from_project) + from_project && self != from_project + end + def pushes_since_gc_redis_key "projects/#{id}/pushes_since_gc" end + def cross_namespace_reference?(from_project) + from_project && namespace != from_project.namespace + end + def default_branch_protected? current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8ff4e7ae718..aa2e3a1ff18 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -67,11 +67,11 @@ class Snippet < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{id}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference + if project.present? + "#{project.to_reference(from_project)}#{reference}" + else + reference end - - reference end def self.content_types diff --git a/changelogs/unreleased/glm-shorthand-reference.yml b/changelogs/unreleased/glm-shorthand-reference.yml new file mode 100644 index 00000000000..6d60f23c798 --- /dev/null +++ b/changelogs/unreleased/glm-shorthand-reference.yml @@ -0,0 +1,4 @@ +--- +title: Add shorthand support to gitlab markdown references +merge_request: 7255 +author: Oswaldo Ferreira diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 3740d4fb4cd..d904a8bd4ae 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -33,7 +33,7 @@ module Banzai # Returns a String replaced with the return of the block. def self.references_in(text, pattern = object_class.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[object_sym].to_i, $~[:project], $~ + yield match, $~[object_sym].to_i, $~[:project], $~[:namespace], $~ end end @@ -145,8 +145,9 @@ module Banzai # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. def object_link_filter(text, pattern, link_content: nil) - references_in(text, pattern) do |match, id, project_ref, matches| - project = project_from_ref_cached(project_ref) + references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| + project_path = full_project_path(namespace_ref, project_ref) + project = project_from_ref_cached(project_path) if project && object = find_object_cached(project, id) title = object_link_title(object) @@ -217,10 +218,9 @@ module Banzai nodes.each do |node| node.to_html.scan(regex) do - project = $~[:project] || current_project_path + project_path = full_project_path($~[:namespace], $~[:project]) symbol = $~[object_sym] - - refs[project] << symbol if object_class.reference_valid?(symbol) + refs[project_path] << symbol if object_class.reference_valid?(symbol) end end @@ -272,8 +272,19 @@ module Banzai @current_project_path ||= project.path_with_namespace end + def current_project_namespace_path + @current_project_namespace_path ||= project.namespace.path + end + private + def full_project_path(namespace, project_ref) + return current_project_path unless project_ref + + namespace_ref = namespace || current_project_namespace_path + "#{namespace_ref}/#{project_ref}" + end + def project_refs_cache RequestStore[:banzai_project_refs] ||= {} end diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index 4358bf45549..eaacb9591b1 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -12,7 +12,7 @@ module Banzai def self.references_in(text, pattern = CommitRange.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[:commit_range], $~[:project], $~ + yield match, $~[:commit_range], $~[:project], $~[:namespace], $~ end end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index a26dd09c25a..69c06117eda 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -12,7 +12,7 @@ module Banzai def self.references_in(text, pattern = Commit.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[:commit], $~[:project], $~ + yield match, $~[:commit], $~[:project], $~[:namespace], $~ end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 9f9a96cdc65..a605dea149e 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -14,16 +14,18 @@ module Banzai def self.references_in(text, pattern = Label.reference_pattern) unescape_html_entities(text).gsub(pattern) do |match| - yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~ + yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~ end end def references_in(text, pattern = Label.reference_pattern) unescape_html_entities(text).gsub(pattern) do |match| - label = find_label($~[:project], $~[:label_id], $~[:label_name]) + namespace, project = $~[:namespace], $~[:project] + project_path = full_project_path(namespace, project) + label = find_label(project_path, $~[:label_id], $~[:label_name]) if label - yield match, label.id, $~[:project], $~ + yield match, label.id, project, namespace, $~ else match end @@ -64,48 +66,12 @@ module Banzai end def object_link_text(object, matches) - if same_group?(object) && namespace_match?(matches) - render_same_project_label(object) - elsif same_project?(object) - render_same_project_label(object) - else - render_cross_project_label(object, matches) - end - end - - def same_group?(object) - object.is_a?(GroupLabel) && object.group == project.group - end - - def namespace_match?(matches) - matches[:project].blank? || matches[:project] == project.path_with_namespace - end - - def same_project?(object) - object.is_a?(ProjectLabel) && object.project == project - end - - def user - context[:current_user] || context[:author] - end - - def project - context[:project] - end - - def render_same_project_label(object) - LabelsHelper.render_colored_label(object) - end - - def render_cross_project_label(object, matches) - source_project = - if matches[:project] - Project.find_with_namespace(matches[:project]) - else - object.project - end + project_path = full_project_path(matches[:namespace], matches[:project]) + project_from_ref = project_from_ref_cached(project_path) + reference = project_from_ref.to_human_reference(project) + label_suffix = " in #{reference}" if reference.present? - LabelsHelper.render_colored_cross_project_label(object, source_project) + LabelsHelper.render_colored_label(object, label_suffix) end def unescape_html_entities(text) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 58fff496d00..f12014e191f 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -19,18 +19,20 @@ module Banzai return super(text, pattern) if pattern != Milestone.reference_pattern text.gsub(pattern) do |match| - milestone = find_milestone($~[:project], $~[:milestone_iid], $~[:milestone_name]) + milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.iid, $~[:project], $~ + yield match, milestone.iid, $~[:project], $~[:namespace], $~ else match end end end - def find_milestone(project_ref, milestone_id, milestone_name) - project = project_from_ref(project_ref) + def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name) + project_path = full_project_path(namespace_ref, project_ref) + project = project_from_ref(project_path) + return unless project milestone_params = milestone_params(milestone_id, milestone_name) @@ -52,11 +54,13 @@ module Banzai end def object_link_text(object, matches) - if context[:project] == object.project - super + milestone_link = escape_once(super) + reference = object.project.to_reference(project) + + if reference.present? + "#{milestone_link} in #{reference}".html_safe else - "#{escape_once(super)} in #{escape_once(object.project.path_with_namespace)}". - html_safe + milestone_link end end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index c9bec05a9da..f89b4db9e62 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -28,7 +28,7 @@ feature 'issue move to another project' do let(:new_project) { create(:project) } let(:new_project_search) { create(:project) } let(:text) { "Text with #{mr.to_reference}" } - let(:cross_reference) { old_project.to_reference } + let(:cross_reference) { old_project.to_reference(new_project) } background do old_project.team << [user, :reporter] diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 702869b6e8b..f1b68a39343 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -40,7 +40,7 @@ feature 'Create New Merge Request', feature: true, js: true do visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) - expect(page).not_to have_content private_project.to_reference + expect(page).not_to have_content private_project.path_with_namespace end end diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index d30daf47543..7cf535fadae 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -7,7 +7,7 @@ describe LabelsHelper do context 'without subject' do it "uses the label's project" do - expect(link_to_label(label)).to match %r{.*} + expect(link_to_label(label)).to match %r{.*} end end @@ -32,7 +32,7 @@ describe LabelsHelper do ['issue', :issue, 'merge_request', :merge_request].each do |type| context "set to #{type}" do it 'links to correct page' do - expect(link_to_label(label, type: type)).to match %r{.*} + expect(link_to_label(label, type: type)).to match %r{.*} end end end diff --git a/spec/lib/banzai/filter/abstract_link_filter_spec.rb b/spec/lib/banzai/filter/abstract_link_filter_spec.rb index 1ee31a603e4..70a87fbc01e 100644 --- a/spec/lib/banzai/filter/abstract_link_filter_spec.rb +++ b/spec/lib/banzai/filter/abstract_link_filter_spec.rb @@ -5,7 +5,7 @@ describe Banzai::Filter::AbstractReferenceFilter do describe '#references_per_project' do it 'returns a Hash containing references grouped per project paths' do - doc = Nokogiri::HTML.fragment("#1 #{project.to_reference}#2") + doc = Nokogiri::HTML.fragment("#1 #{project.path_with_namespace}#2") filter = described_class.new(doc, project: project) expect(filter).to receive(:object_class).exactly(4).times.and_return(Issue) @@ -14,7 +14,7 @@ describe Banzai::Filter::AbstractReferenceFilter do refs = filter.references_per_project expect(refs).to be_an_instance_of(Hash) - expect(refs[project.to_reference]).to eq(Set.new(%w[1 2])) + expect(refs[project.path_with_namespace]).to eq(Set.new(%w[1 2])) end end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index e6c90ad87ee..9703e2315b8 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -59,9 +59,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do 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) + allow(project.repository).to receive(:commit).with(commit1.id.reverse) expect(reference_filter(act).to_html).to eq exp end @@ -100,14 +98,44 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do 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) } + context 'cross-project / cross-namespace complete reference' do + let(:project2) { create(:project, :public) } + let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } - before do - range.project = project2 + 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 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text). + to eql("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eql("Fixed (#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}.)") + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id.reverse}...#{commit2.id}" + expect(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_filter(act).to_html).to eq exp end + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -116,24 +144,65 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("Fixed (#{reference}.)") - exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") - expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) + expect(doc.css('a').first.text). + to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eql("Fixed (#{project2.path}@#{commit1.short_id}...#{commit2.short_id}.)") end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" + exp = act = "Fixed #{project2.path}@#{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}" + exp = act = "Fixed #{project2.path}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.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_compare_url(project2.namespace, project2, range.to_param) + end + + it 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text). + to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eql("Fixed (#{project2.path}@#{commit1.short_id}...#{commit2.short_id}.)") + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.path}@#{commit1.id.reverse}...#{commit2.id}" + expect(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.path}@#{commit1.id}...#{commit2.id.reverse}" expect(reference_filter(act).to_html).to eq exp end end context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:namespace) { create(:namespace) } 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') } diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index e0f08282551..2e6dcc3a434 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -41,6 +41,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do it 'links with adjacent text' do doc = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(#{commit.short_id}<\/a>\.\)/) end @@ -48,8 +49,6 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true 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 @@ -95,34 +94,85 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do end end - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + context 'cross-project / cross-namespace complete reference' do + let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, namespace: namespace) } let(:commit) { project2.commit } - let(:reference) { commit.to_reference(project) } + let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } - it 'links to a valid reference' do - doc = reference_filter("See #{reference}") + it 'link has valid text' 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) + expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}@#{commit.short_id}") end - it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{project2.path_with_namespace}@#{commit.short_id}.)") + 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 + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") - exp = Regexp.escape(project2.to_reference) - expect(doc.to_html).to match(/\(#{exp}@#{commit.short_id}<\/a>\.\)/) + expect(doc.css('a').first.text).to eql("#{project2.path}@#{commit.short_id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{project2.path}@#{commit.short_id}.)") 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 + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}@#{commit.short_id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{project2.path}@#{commit.short_id}.)") + 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 end context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 8f0b2db3e8e..456dbac0698 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -8,7 +8,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end let(:project) { create(:empty_project, :public) } - let(:issue) { create(:issue, project: project) } + let(:issue) { create(:issue, project: project) } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -24,7 +24,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do context 'internal reference' do it_behaves_like 'a reference containing an element node' - let(:reference) { issue.to_reference } + let(:reference) { "##{issue.iid}" } it 'ignores valid references when using non-default tracker' do allow(project).to receive(:default_issues_tracker?).and_return(false) @@ -42,7 +42,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + expect(doc.text).to eql("Fixed (#{reference}.)") end it 'ignores invalid issue IDs' do @@ -116,13 +116,56 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end end - context 'cross-project reference' do + context 'cross-project / cross-namespace complete reference' do it_behaves_like 'a reference containing an element node' - let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(described_class).to receive(:find_object). + with(project2, 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 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}##{issue.iid}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eq("Fixed (#{project2.path_with_namespace}##{issue.iid}.)") + 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 + end + + context 'cross-project / same-namespace complete reference' do + it_behaves_like 'a reference containing an element node' + + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } - let(:reference) { issue.to_reference(project) } + let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } it 'ignores valid references when cross-reference project uses external tracker' do expect_any_instance_of(described_class).to receive(:find_object). @@ -140,9 +183,16 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do to eq helper.url_for_issue(issue.iid, project2) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + + expect(doc.css('a').first.text).to eql("#{project2.path}##{issue.iid}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)") end it 'ignores invalid issue IDs on the referenced project' do @@ -150,9 +200,47 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do expect(reference_filter(act).to_html).to eq exp end + end + + context 'cross-project shorthand reference' do + it_behaves_like 'a reference containing an element node' - it 'ignores out-of-bounds issue IDs on the referenced project' do - exp = act = "Fixed ##{Gitlab::Database::MAX_INT_VALUE + 1}" + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { "#{project2.path}##{issue.iid}" } + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(described_class).to receive(:find_object). + with(project2, 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 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}##{issue.iid}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)") + 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 diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 9c09f00ae8a..284641fb20a 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -4,7 +4,7 @@ require 'html/pipeline' describe Banzai::Filter::LabelReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:empty_project, :public) } + let(:project) { create(:empty_project, :public, name: 'sample-project') } let(:label) { create(:label, project: project) } let(:reference) { label.to_reference } @@ -48,6 +48,14 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name) end + context 'project that does not exist referenced' do + let(:result) { reference_filter('aaa/bbb~ccc') } + + it 'does not link reference' do + expect(result.to_html).to eq 'aaa/bbb~ccc' + end + end + describe 'label span element' do it 'includes default classes' do doc = reference_filter("Label #{reference}") @@ -334,14 +342,14 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end context 'with project reference' do - let(:reference) { project.to_reference + group_label.to_reference(format: :name) } + let(:reference) { "#{project.to_reference}#{group_label.to_reference(format: :name)}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}", project: project) expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_issues_url(project.namespace, project, label_name: group_label.name) - expect(doc.text).to eq 'See gfm references' + expect(doc.text).to eq "See gfm references" end it 'links with adjacent text' do @@ -357,68 +365,247 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end - describe 'cross project label references' do - context 'valid project referenced' do - let(:another_project) { create(:empty_project, :public) } - let(:project_name) { another_project.name_with_namespace } - let(:label) { create(:label, project: another_project, color: '#00ff00') } - let(:reference) { label.to_reference(project) } + describe 'cross-project / cross-namespace complete reference' do + let(:project2) { create(:empty_project) } + let(:label) { create(:label, project: project2, color: '#00ff00') } + let(:reference) { "#{project2.path_with_namespace}~#{label.name}" } + let!(:result) { reference_filter("See #{reference}") } - let!(:result) { reference_filter("See #{reference}") } + it 'links to a valid reference' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project2.namespace, + project2, + label_name: label.name) + end - it 'points to referenced project issues page' do - expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: label.name) - end + it 'has valid color' do + expect(result.css('a span').first.attr('style')).to match /background-color: #00ff00/ + end - it 'has valid color' do - expect(result.css('a span').first.attr('style')) - .to match /background-color: #00ff00/ - end + it 'has valid link text' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name_with_namespace}" + end - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" - end + it 'has valid text' do + expect(result.text).to eq "See #{label.name} in #{project2.name_with_namespace}" end - context 'project that does not exist referenced' do - let(:result) { reference_filter('aaa/bbb~ccc') } + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" - it 'does not link reference' do - expect(result.to_html).to eq 'aaa/bbb~ccc' - end + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:empty_project, namespace: namespace) } + let(:label) { create(:label, project: project2, color: '#00ff00') } + let(:reference) { "#{project2.path_with_namespace}~#{label.name}" } + let!(:result) { reference_filter("See #{reference}") } + + it 'links to a valid reference' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project2.namespace, + project2, + label_name: label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')).to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name}" + end + + it 'has valid text' do + expect(result.text).to eq "See #{label.name} in #{project2.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:empty_project, namespace: namespace) } + let(:label) { create(:label, project: project2, color: '#00ff00') } + let(:reference) { "#{project2.path}~#{label.name}" } + let!(:result) { reference_filter("See #{reference}") } + + it 'links to a valid reference' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project2.namespace, + project2, + label_name: label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name}" + end + + it 'has valid text' do + expect(result.text).to eq "See #{label.name} in #{project2.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp end end describe 'cross group label references' do - context 'valid project referenced' do - let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } - let(:another_group) { create(:group) } - let(:another_project) { create(:empty_project, :public, namespace: another_group) } - let(:project_name) { another_project.name_with_namespace } - let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } - let(:reference) { another_project.to_reference + group_label.to_reference } - - let!(:result) { reference_filter("See #{reference}", project: project) } - - it 'points to referenced project issues page' do - expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: group_label.name) - end + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:another_group) { create(:group) } + let(:another_project) { create(:empty_project, :public, namespace: another_group) } + let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } + let(:reference) { "#{another_project.path_with_namespace}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } - it 'has valid color' do - expect(result.css('a span').first.attr('style')) - .to match /background-color: #00ff00/ - end + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: group_label.name) + end - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{group_label.name} in #{project_name}" - end + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text). + to eq "#{group_label.name} in #{another_project.name_with_namespace}" + end + + it 'has valid text' do + expect(result.text). + to eq "See #{group_label.name} in #{another_project.name_with_namespace}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'cross-project / same-group_label complete reference' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:another_project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, group: group, color: '#00ff00') } + let(:reference) { "#{another_project.path_with_namespace}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')). + to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text). + to eq "#{group_label.name} in #{another_project.name}" + end + + it 'has valid text' do + expect(result.text). + to eq "See #{group_label.name} in #{another_project.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'same project / same group_label complete reference' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, group: group, color: '#00ff00') } + let(:reference) { "#{project.path_with_namespace}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project.namespace, + project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')) + .to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq group_label.name + end + + it 'has valid text' do + expect(result.text).to eq "See #{group_label.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'same project / same group_label shorthand reference' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, group: group, color: '#00ff00') } + let(:reference) { "#{project.path}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project.namespace, + project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq group_label.name + end + + it 'has valid text' do + expect(result.text).to eq "See #{group_label.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp end end end diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 274258a045c..275010c1a2c 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:merge) { create(:merge_request, source_project: project) } it 'requires project context' do @@ -86,23 +86,97 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do 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) } + context 'cross-project / cross-namespace complete reference' do + let(:project2) { create(:empty_project, :public) } + let(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } 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) + project2, merge) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + + expect(doc.css('a').first.text).to eq(reference) + end + + it 'has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.text).to eq("Merge (#{reference}.)") + 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 + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } + + 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 'link has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}") + end + + it 'has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)") + 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 + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { "#{project2.path}!#{merge.iid}" } + + 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 'link has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}") + end + + it 'has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)") end it 'ignores invalid merge IDs on the referenced project' do diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 7419863d848..73b5edb99b3 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -148,13 +148,51 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do end end - describe 'cross project milestone references' do - let(:another_project) { create(:empty_project, :public) } - let(:project_path) { another_project.path_with_namespace } - let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { milestone.to_reference(project) } + describe 'cross-project / cross-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } - let!(:result) { reference_filter("See #{reference}") } + it 'points to referenced project milestone page' do + expect(result.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(another_project.namespace, + another_project, + milestone) + end + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text). + to eq("#{milestone.name} in #{another_project.path_with_namespace}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text). + to eq("See (#{milestone.name} in #{another_project.path_with_namespace}.)") + end + + it 'escapes the name attribute' do + allow_any_instance_of(Milestone).to receive(:title).and_return(%{">whateverwhateverwhatever#{Regexp.escape(reference)}<\/a>\.\)/) + + expect(doc.css('a').first.text).to eql(reference) + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{reference}.)") + 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 + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { "#{project2.path_with_namespace}$#{snippet.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_snippet_url(project2.namespace, project2, snippet) + end + + it 'link has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}$#{snippet.id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.text).to eql("See (#{project2.path}$#{snippet.id}.)") + 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 + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { "#{project2.path}$#{snippet.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_snippet_url(project2.namespace, project2, snippet) + end + + it 'link has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}$#{snippet.id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.text).to eql("See (#{project2.path}$#{snippet.id}.)") end it 'ignores invalid snippet IDs on the referenced project' do diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 6b3dfebd85d..d619e401897 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -64,7 +64,7 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'description with project labels' do let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } - let(:project_ref) { old_project.to_reference } + let(:project_ref) { old_project.to_reference(new_project) } context 'label referenced by id' do let(:text) { '#1 and ~123' } @@ -80,7 +80,7 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'description with group labels' do let(:old_group) { create(:group) } let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } - let(:project_ref) { old_project.to_reference } + let(:project_ref) { old_project.to_reference(new_project) } before do old_project.update(namespace: old_group) diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index c41359b55a3..d89d4342dea 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -45,7 +45,7 @@ describe CommitRange, models: true do end describe '#to_reference' do - let(:cross) { create(:project) } + let(:cross) { create(:empty_project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" @@ -56,12 +56,12 @@ describe CommitRange, models: true do end it 'supports a cross-project reference' do - expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}" + expect(range.to_reference(cross)).to eq "#{project.path}@#{full_sha_from}...#{full_sha_to}" end end describe '#reference_link_text' do - let(:cross) { create(:project) } + let(:cross) { create(:empty_project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" @@ -72,7 +72,7 @@ describe CommitRange, models: true do end it 'supports a cross-project reference' do - expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" + expect(range.reference_link_text(cross)).to eq "#{project.path}@#{sha_from}...#{sha_to}" end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 7194c20d3bf..eb482c7f913 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -34,24 +34,30 @@ describe Commit, models: true do end describe '#to_reference' do + let(:project) { create(:project, path: 'sample-project') } + let(:commit) { project.commit } + it 'returns a String reference to the object' do expect(commit.to_reference).to eq commit.id end it 'supports a cross-project reference' do - cross = double('project') - expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" end end describe '#reference_link_text' do + let(:project) { create(:project, path: 'sample-project') } + let(:commit) { project.commit } + it 'returns a String reference to the object' do expect(commit.reference_link_text).to eq commit.short_id end it 'supports a cross-project reference' do - cross = double('project') - expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" end end diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index 2369658bf78..668aa6fb357 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -37,6 +37,16 @@ describe GroupLabel, models: true do end end + context 'cross-project' do + let(:namespace) { build_stubbed(:namespace) } + let(:source_project) { build_stubbed(:empty_project, name: 'project-1', namespace: namespace) } + let(:target_project) { build_stubbed(:empty_project, name: 'project-2', namespace: namespace) } + + it 'returns a String reference to the object' do + expect(label.to_reference(source_project, target_project)).to eq %(project-1~#{label.id}) + end + end + context 'using invalid format' do it 'raises error' do expect { label.to_reference(format: :invalid) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 89e93dce8c5..24e216329a9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -43,14 +43,16 @@ describe Issue, models: true do end describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:issue) { build(:issue, iid: 1, project: project) } + it 'returns a String reference to the object' do - expect(subject.to_reference).to eq "##{subject.iid}" + expect(issue.to_reference).to eq "#1" end it 'supports a cross-project reference' do - cross = double('project') - expect(subject.to_reference(cross)). - to eq "#{subject.project.to_reference}##{subject.iid}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(issue.to_reference(another_project)).to eq "sample-project#1" end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ec22ef93465..b9deb04da3a 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -142,13 +142,16 @@ describe MergeRequest, models: true do end describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:merge_request) { build(:merge_request, target_project: project, iid: 1) } + it 'returns a String reference to the object' do - expect(subject.to_reference).to eq "!#{subject.iid}" + expect(merge_request.to_reference).to eq "!1" end it 'supports a cross-project reference' do - cross = double('project') - expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(merge_request.to_reference(another_project)).to eq "sample-project!1" end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index a4bfe851dfb..0cc2efae5f9 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -246,4 +246,18 @@ describe Milestone, models: true do end end end + + describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:milestone) { build(:milestone, iid: 1, project: project) } + + it 'returns a String reference to the object' do + expect(milestone.to_reference).to eq "%1" + end + + it 'supports a cross-project reference' do + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(milestone.to_reference(another_project)).to eq "sample-project%1" + end + end end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 18c9d449ee5..4d538cac007 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -105,14 +105,14 @@ describe ProjectLabel, models: true do context 'using name' do it 'returns cross reference with label name' do expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.to_reference}~"#{label.name}") + .to eq %Q(#{label.project.path_with_namespace}~"#{label.name}") end end context 'using id' do it 'returns cross reference with label id' do expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.to_reference}~#{label.id}) + .to eq %Q(#{label.project.path_with_namespace}~#{label.id}) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8abcce42ce0..587ca1936a3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -258,10 +258,70 @@ describe Project, models: true do end describe '#to_reference' do - let(:project) { create(:empty_project) } + let(:owner) { create(:user, name: 'Gitlab') } + let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } + let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) } + + context 'when nil argument' do + it 'returns nil' do + expect(project.to_reference).to be_nil + end + end + + context 'when same project argument' do + it 'returns nil' do + expect(project.to_reference(project)).to be_nil + end + end - it 'returns a String reference to the object' do - expect(project.to_reference).to eq project.path_with_namespace + context 'when cross namespace project argument' do + let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + + it 'returns complete path to the project' do + expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project' + end + end + + context 'when same namespace / cross-project argument' do + let(:another_project) { create(:empty_project, namespace: namespace) } + + it 'returns complete path to the project' do + expect(project.to_reference(another_project)).to eq 'sample-project' + end + end + end + + describe '#to_human_reference' do + let(:owner) { create(:user, name: 'Gitlab') } + let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) } + let(:project) { create(:empty_project, name: 'Sample project', namespace: namespace) } + + context 'when nil argument' do + it 'returns nil' do + expect(project.to_human_reference).to be_nil + end + end + + context 'when same project argument' do + it 'returns nil' do + expect(project.to_human_reference(project)).to be_nil + end + end + + context 'when cross namespace project argument' do + let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + + it 'returns complete name with namespace of the project' do + expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' + end + end + + context 'when same namespace / cross-project argument' do + let(:another_project) { create(:empty_project, namespace: namespace) } + + it 'returns name of the project' do + expect(project.to_human_reference(another_project)).to eq 'Sample project' + end end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index f62f6bacbaa..79d2843e122 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -33,16 +33,31 @@ describe Snippet, models: true do end describe '#to_reference' do - let(:project) { create(:empty_project) } - let(:snippet) { create(:snippet, project: project) } + context 'when snippet belongs to a project' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:snippet) { build(:snippet, id: 1, project: project) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$1" + end - it 'returns a String reference to the object' do - expect(snippet.to_reference).to eq "$#{snippet.id}" + it 'supports a cross-project reference' do + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(snippet.to_reference(another_project)).to eq "sample-project$1" + end end - it 'supports a cross-project reference' do - cross = double('project') - expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}" + context 'when snippet does not belong to a project' do + let(:snippet) { build(:snippet, id: 1, project: nil) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$1" + end + + it 'still returns shortest reference when project arg present' do + another_project = build(:project, name: 'another-project') + expect(snippet.to_reference(another_project)).to eq "$1" + end end end diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index c7de0d0c534..db196ed5751 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -189,7 +189,7 @@ describe Issues::MoveService, services: true do it 'rewrites references using a cross reference to old project' do expect(new_note.note) - .to eq "Note with reference to merge request #{old_project.to_reference}!1" + .to eq "Note with reference to merge request #{old_project.to_reference(new_project)}!1" end end @@ -217,7 +217,7 @@ describe Issues::MoveService, services: true do it 'rewrites referenced issues creating cross project reference' do expect(new_issue.description) - .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}" + .to eq "Some description #{another_issue.to_reference(new_project)}" end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 4a8f6c321aa..835d7467bd9 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -530,7 +530,7 @@ describe SystemNoteService, services: true do end it 'mentions referenced project' do - expect(subject.note).to include new_project.to_reference + expect(subject.note).to include new_project.path_with_namespace end end -- cgit v1.2.1