summaryrefslogtreecommitdiff
path: root/spec/lib/banzai
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/banzai')
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/code_language_filter_spec.rb82
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb21
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb32
-rw-r--r--spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/inline_observability_filter_spec.rb88
-rw-r--r--spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb196
-rw-r--r--spec/lib/banzai/filter/kroki_filter_spec.rb42
-rw-r--r--spec/lib/banzai/filter/markdown_engines/base_spec.rb17
-rw-r--r--spec/lib/banzai/filter/markdown_engines/common_mark_spec.rb17
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb21
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb11
-rw-r--r--spec/lib/banzai/filter/mermaid_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/references/design_reference_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/references/issue_reference_filter_spec.rb9
-rw-r--r--spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb9
-rw-r--r--spec/lib/banzai/filter/references/reference_cache_spec.rb5
-rw-r--r--spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb314
-rw-r--r--spec/lib/banzai/filter/repository_link_filter_spec.rb13
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb84
-rw-r--r--spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/timeout_text_pipeline_filter_spec.rb15
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb15
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb6
-rw-r--r--spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb4
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb40
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb31
-rw-r--r--spec/lib/banzai/reference_parser/work_item_parser_spec.rb46
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb17
-rw-r--r--spec/lib/banzai/renderer_spec.rb4
33 files changed, 913 insertions, 266 deletions
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 575d4879f84..934a0a9fd58 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -31,4 +31,8 @@ RSpec.describe Banzai::Filter::BlockquoteFenceFilter, feature_category: :team_pl
end.not_to raise_error
end
end
+
+ it_behaves_like 'text filter timeout' do
+ let(:text) { ">>>\ntest\n>>>" }
+ end
end
diff --git a/spec/lib/banzai/filter/code_language_filter_spec.rb b/spec/lib/banzai/filter/code_language_filter_spec.rb
new file mode 100644
index 00000000000..25f844ee575
--- /dev/null
+++ b/spec/lib/banzai/filter/code_language_filter_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::CodeLanguageFilter, feature_category: :team_planning do
+ include FilterSpecHelper
+
+ shared_examples 'XSS prevention' do |lang|
+ it 'escapes HTML tags' do
+ # This is how a script tag inside a code block is presented to this filter
+ # after Markdown rendering.
+ result = filter(%(<pre lang="#{lang}"><code>&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>))
+
+ # `(1)` symbols are wrapped by lexer tags.
+ expect(result.to_html).not_to match(%r{<script>alert.*</script>})
+
+ # `<>` stands for lexer tags like <span ...>, not &lt;s above.
+ expect(result.to_html).to match(%r{alert(<.*>)?\((<.*>)?1(<.*>)?\)})
+ end
+ end
+
+ context 'when no language is specified' do
+ it 'does nothing' do
+ result = filter('<pre><code>def fun end</code></pre>')
+
+ expect(result.to_html.delete("\n")).to eq('<pre><code>def fun end</code></pre>')
+ end
+ end
+
+ context 'when lang is specified' do
+ it 'adds data-canonical-lang and removes lang attribute' do
+ result = filter('<pre lang="ruby"><code>def fun end</code></pre>')
+
+ expect(result.to_html.delete("\n"))
+ .to eq('<pre data-canonical-lang="ruby"><code>def fun end</code></pre>')
+ end
+ end
+
+ context 'when lang has extra params' do
+ let(:lang_params) { 'foo-bar-kux' }
+ let(:xss_lang) { %(ruby data-meta="foo-bar-kux"&lt;script&gt;alert(1)&lt;/script&gt;) }
+
+ it 'includes data-lang-params tag with extra information and removes data-meta' do
+ expected_result = <<~HTML
+ <pre data-canonical-lang="ruby" data-lang-params="#{lang_params}">
+ <code>This is a test</code></pre>
+ HTML
+
+ result = filter(%(<pre lang="ruby" data-meta="#{lang_params}"><code>This is a test</code></pre>))
+
+ expect(result.to_html.delete("\n")).to eq(expected_result.delete("\n"))
+ end
+
+ include_examples 'XSS prevention', 'ruby'
+
+ include_examples 'XSS prevention',
+ %(ruby data-meta="foo-bar-kux"&lt;script&gt;alert(1)&lt;/script&gt;)
+
+ include_examples 'XSS prevention',
+ %(ruby data-meta="foo-bar-kux"<script>alert(1)</script>)
+ end
+
+ context 'when multiple param delimiters are used' do
+ let(:lang) { 'suggestion' }
+ let(:lang_params) { '-1+10' }
+
+ let(:expected_result) do
+ <<~HTML
+ <pre data-canonical-lang="#{lang}" data-lang-params="#{lang_params} more-things">
+ <code>This is a test</code></pre>
+ HTML
+ end
+
+ context 'when delimiter is colon' do
+ it 'delimits on the first appearance' do
+ result = filter(%(<pre lang="#{lang}:#{lang_params} more-things"><code>This is a test</code></pre>))
+
+ expect(result.to_html.delete("\n")).to eq(expected_result.delete("\n"))
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index 896f3beb7c2..3d992f962ec 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -8,16 +8,15 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter, feature_category: :source_c
include CommitTrailersSpecHelper
let(:secondary_email) { create(:email, :confirmed) }
- let(:user) { create(:user) }
+ let(:user) { create(:user, :public_email) }
+ let(:email) { FFaker::Internet.email }
let(:trailer) { "#{FFaker::Lorem.word}-by:" }
- let(:commit_message) { trailer_line(trailer, user.name, user.email) }
+ let(:commit_message) { trailer_line(trailer, user.name, user.public_email) }
let(:commit_message_html) { commit_html(commit_message) }
context 'detects' do
- let(:email) { FFaker::Internet.email }
-
context 'trailers in the form of *-by' do
where(:commit_trailer) do
["#{FFaker::Lorem.word}-by:", "#{FFaker::Lorem.word}-BY:", "#{FFaker::Lorem.word}-By:"]
@@ -42,7 +41,7 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter, feature_category: :source_c
expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
end
- it 'GitLab users via a secondary email' do
+ it 'does not detect GitLab users via a secondary email' do
_, message_html = build_commit_message(
trailer: trailer,
name: secondary_email.user.name,
@@ -51,9 +50,8 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter, feature_category: :source_c
doc = filter(message_html)
- expect_to_have_user_link_with_avatar(
+ expect_to_have_mailto_link_with_avatar(
doc,
- user: secondary_email.user,
trailer: trailer,
email: secondary_email.email
)
@@ -185,17 +183,16 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter, feature_category: :source_c
it 'preserves the original email used in the commit message' do
message, message_html = build_commit_message(
trailer: trailer,
- name: secondary_email.user.name,
- email: secondary_email.email
+ name: user.name,
+ email: email
)
doc = filter(message_html)
- expect_to_have_user_link_with_avatar(
+ expect_to_have_mailto_link_with_avatar(
doc,
- user: secondary_email.user,
trailer: trailer,
- email: secondary_email.email
+ email: email
)
expect(doc.text).to match Regexp.escape(message)
end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 3f72896939d..de259342998 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -2,25 +2,25 @@
require 'spec_helper'
-RSpec.shared_examples 'an external link with rel attribute', feature_category: :team_planning do
- it 'adds rel="nofollow" to external links' do
- expect(doc.at_css('a')).to have_attribute('rel')
- expect(doc.at_css('a')['rel']).to include 'nofollow'
- end
+RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_planning do
+ include FilterSpecHelper
- it 'adds rel="noreferrer" to external links' do
- expect(doc.at_css('a')).to have_attribute('rel')
- expect(doc.at_css('a')['rel']).to include 'noreferrer'
- end
+ shared_examples 'an external link with rel attribute' do
+ it 'adds rel="nofollow" to external links' do
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to include 'nofollow'
+ end
- it 'adds rel="noopener" to external links' do
- expect(doc.at_css('a')).to have_attribute('rel')
- expect(doc.at_css('a')['rel']).to include 'noopener'
- end
-end
+ it 'adds rel="noreferrer" to external links' do
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to include 'noreferrer'
+ end
-RSpec.describe Banzai::Filter::ExternalLinkFilter do
- include FilterSpecHelper
+ it 'adds rel="noopener" to external links' do
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to include 'noopener'
+ end
+ end
it 'ignores elements without an href attribute' do
exp = act = %q(<a id="ignored">Ignore Me</a>)
diff --git a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
index db0c10a802b..746fa6c48a5 100644
--- a/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_grafana_metrics_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::InlineGrafanaMetricsFilter do
+RSpec.describe Banzai::Filter::InlineGrafanaMetricsFilter, feature_category: :metrics do
include FilterSpecHelper
let_it_be(:project) { create(:project) }
@@ -29,6 +29,10 @@ RSpec.describe Banzai::Filter::InlineGrafanaMetricsFilter do
)
end
+ before do
+ stub_feature_flags(remove_monitor_metrics: false)
+ end
+
around do |example|
travel_to(Time.utc(2019, 3, 17, 13, 10)) { example.run }
end
diff --git a/spec/lib/banzai/filter/inline_observability_filter_spec.rb b/spec/lib/banzai/filter/inline_observability_filter_spec.rb
index 69a9dc96c2c..81896faced8 100644
--- a/spec/lib/banzai/filter/inline_observability_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_observability_filter_spec.rb
@@ -2,25 +2,20 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::InlineObservabilityFilter do
+RSpec.describe Banzai::Filter::InlineObservabilityFilter, feature_category: :metrics do
include FilterSpecHelper
let(:input) { %(<a href="#{url}">example</a>) }
let(:doc) { filter(input) }
- let(:group) { create(:group) }
- let(:user) { create(:user) }
- describe '#filter?' do
- context 'when the document has an external link' do
- let(:url) { 'https://foo.com' }
-
- it 'leaves regular non-observability links unchanged' do
- expect(doc.to_s).to eq(input)
- end
- end
+ before do
+ allow(Gitlab::Observability).to receive(:embeddable_url).and_return('embeddable-url')
+ stub_config_setting(url: "https://www.gitlab.com")
+ end
- context 'when the document contains an embeddable observability link' do
- let(:url) { 'https://observe.gitlab.com/12345' }
+ describe '#filter?' do
+ context 'when the document contains a valid observability link' do
+ let(:url) { "https://www.gitlab.com/groups/some-group/-/observability/test" }
it 'leaves the original link unchanged' do
expect(doc.at_css('a').to_s).to eq(input)
@@ -30,32 +25,34 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do
node = doc.at_css('.js-render-observability')
expect(node).to be_present
- expect(node.attribute('data-frame-url').to_s).to eq(url)
+ expect(node.attribute('data-frame-url').to_s).to eq('embeddable-url')
+ expect(Gitlab::Observability).to have_received(:embeddable_url).with(url).once
end
end
- context 'when the document contains an embeddable observability link with redirect' do
- let(:url) { 'https://observe.gitlab.com@example.com/12345' }
+ context 'with duplicate URLs' do
+ let(:url) { "https://www.gitlab.com/groups/some-group/-/observability/test" }
+ let(:input) { %(<a href="#{url}">example1</a><a href="#{url}">example2</a>) }
- it 'leaves the original link unchanged' do
- expect(doc.at_css('a').to_s).to eq(input)
+ where(:embeddable_url) do
+ [
+ 'not-nil',
+ nil
+ ]
end
- it 'does not append an observability charts placeholder' do
- node = doc.at_css('.js-render-observability')
-
- expect(node).not_to be_present
- end
- end
+ with_them do
+ it 'calls Gitlab::Observability.embeddable_url only once' do
+ allow(Gitlab::Observability).to receive(:embeddable_url).with(url).and_return(embeddable_url)
- context 'when the document contains an embeddable observability link with different port' do
- let(:url) { 'https://observe.gitlab.com:3000/12345' }
- let(:observe_url) { 'https://observe.gitlab.com:3001' }
+ filter(input)
- before do
- stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url)
+ expect(Gitlab::Observability).to have_received(:embeddable_url).with(url).once
+ end
end
+ end
+ shared_examples 'does not embed observabilty' do
it 'leaves the original link unchanged' do
expect(doc.at_css('a').to_s).to eq(input)
end
@@ -67,40 +64,37 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do
end
end
- context 'when the document contains an embeddable observability link with auth/start' do
- let(:url) { 'https://observe.gitlab.com/auth/start' }
- let(:observe_url) { 'https://observe.gitlab.com' }
+ context 'when the embeddable url is nil' do
+ let(:url) { "https://www.gitlab.com/groups/some-group/-/something-else/test" }
before do
- stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url)
+ allow(Gitlab::Observability).to receive(:embeddable_url).and_return(nil)
end
- it 'leaves the original link unchanged' do
- expect(doc.at_css('a').to_s).to eq(input)
- end
+ it_behaves_like 'does not embed observabilty'
+ end
- it 'does not append an observability charts placeholder' do
- node = doc.at_css('.js-render-observability')
+ context 'when the document has an unrecognised link' do
+ let(:url) { "https://www.gitlab.com/groups/some-group/-/something-else/test" }
- expect(node).not_to be_present
+ it_behaves_like 'does not embed observabilty'
+
+ it 'does not build the embeddable url' do
+ expect(Gitlab::Observability).not_to have_received(:embeddable_url)
end
end
context 'when feature flag is disabled' do
- let(:url) { 'https://observe.gitlab.com/12345' }
+ let(:url) { "https://www.gitlab.com/groups/some-group/-/observability/test" }
before do
stub_feature_flags(observability_group_tab: false)
end
- it 'leaves the original link unchanged' do
- expect(doc.at_css('a').to_s).to eq(input)
- end
+ it_behaves_like 'does not embed observabilty'
- it 'does not append an observability charts placeholder' do
- node = doc.at_css('.js-render-observability')
-
- expect(node).not_to be_present
+ it 'does not build the embeddable url' do
+ expect(Gitlab::Observability).not_to have_received(:embeddable_url)
end
end
end
diff --git a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb
index 1fdb29b688e..e14b1362687 100644
--- a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb
@@ -21,6 +21,10 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor
create(:issue, state, attributes.merge(project: project))
end
+ def create_item(issuable_type, state, attributes = {})
+ create(issuable_type, state, attributes.merge(project: project))
+ end
+
def create_merge_request(state, attributes = {})
create(:merge_request, state, attributes.merge(source_project: project, target_project: project))
end
@@ -115,53 +119,128 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor
end
end
- context 'for issue references' do
- it 'ignores open issue references' do
- issue = create_issue(:opened)
- link = create_link(issue.to_reference, issue: issue.id, reference_type: 'issue')
+ shared_examples 'issue / work item references' do
+ it 'ignores open references' do
+ issuable = create_item(issuable_type, :opened)
+ link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type)
doc = filter(link, context)
- expect(doc.css('a').last.text).to eq(issue.to_reference)
+ expect(doc.css('a').last.text).to eq(issuable.to_reference)
end
- it 'appends state to closed issue references' do
- link = create_link(closed_issue.to_reference, issue: closed_issue.id, reference_type: 'issue')
+ it 'appends state to moved references' do
+ moved_issuable = create_item(issuable_type, :closed, project: project,
+ moved_to: create_item(issuable_type, :opened))
+ link = create_link(moved_issuable.to_reference, "#{issuable_type}": moved_issuable.id,
+ reference_type: issuable_type)
doc = filter(link, context)
- expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference} (closed)")
+ expect(doc.css('a').last.text).to eq("#{moved_issuable.to_reference} (moved)")
end
- it 'appends state to moved issue references' do
- moved_issue = create(:issue, :closed, project: project, moved_to: create_issue(:opened))
- link = create_link(moved_issue.to_reference, issue: moved_issue.id, reference_type: 'issue')
+ it 'appends state to closed references' do
+ issuable = create_item(issuable_type, :closed)
+ link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type)
doc = filter(link, context)
- expect(doc.css('a').last.text).to eq("#{moved_issue.to_reference} (moved)")
+ expect(doc.css('a').last.text).to eq("#{issuable.to_reference} (closed)")
end
it 'shows title for references with +' do
- issue = create_issue(:opened, title: 'Some issue')
- link = create_link(issue.to_reference, issue: issue.id, reference_type: 'issue', reference_format: '+')
+ issuable = create_item(issuable_type, :opened, title: 'Some issue')
+ link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type,
+ reference_format: '+')
doc = filter(link, context)
- expect(doc.css('a').last.text).to eq("#{issue.title} (#{issue.to_reference})")
+ expect(doc.css('a').last.text).to eq("#{issuable.title} (#{issuable.to_reference})")
end
it 'truncates long title for references with +' do
- issue = create_issue(:opened, title: 'Some issue ' * 10)
- link = create_link(issue.to_reference, issue: issue.id, reference_type: 'issue', reference_format: '+')
+ issuable = create_item(issuable_type, :opened, title: 'Some issue ' * 10)
+ link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type,
+ reference_format: '+')
doc = filter(link, context)
- expect(doc.css('a').last.text).to eq("#{issue.title.truncate(50)} (#{issue.to_reference})")
+ expect(doc.css('a').last.text).to eq("#{issuable.title.truncate(50)} (#{issuable.to_reference})")
end
it 'shows both title and state for closed references with +' do
- issue = create_issue(:closed, title: 'Some issue')
- link = create_link(issue.to_reference, issue: issue.id, reference_type: 'issue', reference_format: '+')
+ issuable = create_item(issuable_type, :closed, title: 'Some issue')
+ link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type,
+ reference_format: '+')
+ doc = filter(link, context)
+
+ expect(doc.css('a').last.text).to eq("#{issuable.title} (#{issuable.to_reference} - closed)")
+ end
+
+ it 'shows title for references with +s' do
+ issuable = create_item(issuable_type, :opened, title: 'Some issue')
+ link = create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type,
+ reference_format: '+s')
doc = filter(link, context)
- expect(doc.css('a').last.text).to eq("#{issue.title} (#{issue.to_reference} - closed)")
+ expect(doc.css('a').last.text).to eq("#{issuable.title} (#{issuable.to_reference}) • Unassigned")
end
+
+ context 'when extended summary props are present' do
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:assignees) { create_list(:user, 3) }
+ let_it_be(:issuable) do
+ create_item(issuable_type, :opened, title: 'Some issue', milestone: milestone,
+ assignees: assignees)
+ end
+
+ let_it_be(:link) do
+ create_link(issuable.to_reference, "#{issuable_type}": issuable.id, reference_type: issuable_type,
+ reference_format: '+s')
+ end
+
+ it 'shows extended summary for references with +s' do
+ doc = filter(link, context)
+
+ expect(doc.css('a').last.text).to eq(
+ "#{issuable.title} (#{issuable.to_reference}) • #{assignees[0].name}, #{assignees[1].name}+ " \
+ "• #{milestone.title}"
+ )
+ end
+
+ describe 'checking N+1' do
+ let_it_be(:milestone2) { create(:milestone, project: project) }
+ let_it_be(:assignees2) { create_list(:user, 3) }
+
+ it 'does not have N+1 for extended summary', :use_sql_query_cache do
+ issuable2 = create_item(issuable_type, :opened, title: 'Another issue',
+ milestone: milestone2, assignees: assignees2)
+ link2 = create_link(issuable2.to_reference, "#{issuable_type}": issuable2.id,
+ reference_type: issuable_type, reference_format: '+s')
+
+ # warm up
+ filter(link, context)
+
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ filter(link, context)
+ end.count
+
+ expect(control_count).to eq 12
+
+ expect do
+ filter("#{link} #{link2}", context)
+ end.not_to exceed_all_query_limit(control_count)
+ end
+ end
+ end
+ end
+
+ context 'for work item references' do
+ let_it_be(:issuable_type) { :work_item }
+
+ it_behaves_like 'issue / work item references'
+ end
+
+ context 'for issue references' do
+ let_it_be(:issuable_type) { :issue }
+
+ it_behaves_like 'issue / work item references'
end
context 'for merge request references' do
@@ -235,5 +314,80 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor
expect(doc.css('a').last.text).to eq("#{merge_request.title} (#{merge_request.to_reference})")
end
+
+ it 'shows title for references with +s' do
+ merge_request = create_merge_request(:opened, title: 'Some merge request')
+
+ link = create_link(
+ merge_request.to_reference,
+ merge_request: merge_request.id,
+ reference_type: 'merge_request',
+ reference_format: '+s'
+ )
+
+ doc = filter(link, context)
+
+ expect(doc.css('a').last.text).to eq("#{merge_request.title} (#{merge_request.to_reference}) • Unassigned")
+ end
+
+ context 'when extended summary props are present' do
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:assignees) { create_list(:user, 2) }
+ let_it_be(:merge_request) do
+ create_merge_request(:opened, title: 'Some merge request', milestone: milestone, assignees: assignees)
+ end
+
+ let_it_be(:link) do
+ create_link(
+ merge_request.to_reference,
+ merge_request: merge_request.id,
+ reference_type: 'merge_request',
+ reference_format: '+s'
+ )
+ end
+
+ it 'shows extended summary for references with +s' do
+ doc = filter(link, context)
+
+ expect(doc.css('a').last.text).to eq(
+ "#{merge_request.title} (#{merge_request.to_reference}) • #{assignees[0].name}, #{assignees[1].name} • " \
+ "#{milestone.title}"
+ )
+ end
+
+ describe 'checking N+1' do
+ let_it_be(:milestone2) { create(:milestone, project: project) }
+ let_it_be(:assignees2) { create_list(:user, 3) }
+
+ it 'does not have N+1 for extended summary', :use_sql_query_cache do
+ merge_request2 = create_merge_request(
+ :closed,
+ title: 'Some merge request',
+ milestone: milestone2,
+ assignees: assignees2
+ )
+
+ link2 = create_link(
+ merge_request2.to_reference,
+ merge_request: merge_request2.id,
+ reference_type: 'merge_request',
+ reference_format: '+s'
+ )
+
+ # warm up
+ filter(link, context)
+
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ filter(link, context)
+ end.count
+
+ expect(control_count).to eq 10
+
+ expect do
+ filter("#{link} #{link2}", context)
+ end.not_to exceed_all_query_limit(control_count)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/banzai/filter/kroki_filter_spec.rb b/spec/lib/banzai/filter/kroki_filter_spec.rb
index 1cd11161439..3915d9fb8f8 100644
--- a/spec/lib/banzai/filter/kroki_filter_spec.rb
+++ b/spec/lib/banzai/filter/kroki_filter_spec.rb
@@ -7,42 +7,46 @@ RSpec.describe Banzai::Filter::KrokiFilter, feature_category: :team_planning do
it 'replaces nomnoml pre tag with img tag if kroki is enabled' do
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
- doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
+ doc = filter("<pre data-canonical-lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==" class="js-render-kroki" data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,W1BpcmF0ZXxleWVDb3VudDogSW50fHJhaWQoKTtwaWxsYWdlKCl8CiAgW2JlYXJkXS0tW3BhcnJvdF0KICBbYmVhcmRdLTo+W2ZvdWwgbW91dGhdCl0=">'
end
it 'replaces nomnoml pre tag with img tag if both kroki and plantuml are enabled' do
- stub_application_setting(kroki_enabled: true,
- kroki_url: "http://localhost:8000",
- plantuml_enabled: true,
- plantuml_url: "http://localhost:8080")
- doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
+ stub_application_setting(
+ kroki_enabled: true,
+ kroki_url: "http://localhost:8000",
+ plantuml_enabled: true,
+ plantuml_url: "http://localhost:8080"
+ )
+ doc = filter("<pre data-canonical-lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==" class="js-render-kroki" data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,W1BpcmF0ZXxleWVDb3VudDogSW50fHJhaWQoKTtwaWxsYWdlKCl8CiAgW2JlYXJkXS0tW3BhcnJvdF0KICBbYmVhcmRdLTo+W2ZvdWwgbW91dGhdCl0=">'
end
it 'does not replace nomnoml pre tag with img tag if kroki is disabled' do
stub_application_setting(kroki_enabled: false)
- doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
+ doc = filter("<pre data-canonical-lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
- expect(doc.to_s).to eq "<pre lang=\"nomnoml\"><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:&gt;[foul mouth]\n]</code></pre>"
+ expect(doc.to_s).to eq "<pre data-canonical-lang=\"nomnoml\"><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:&gt;[foul mouth]\n]</code></pre>"
end
it 'does not replace plantuml pre tag with img tag if both kroki and plantuml are enabled' do
- stub_application_setting(kroki_enabled: true,
- kroki_url: "http://localhost:8000",
- plantuml_enabled: true,
- plantuml_url: "http://localhost:8080")
- doc = filter("<pre lang='plantuml'><code>Bob->Alice : hello</code></pre>")
-
- expect(doc.to_s).to eq '<pre lang="plantuml"><code>Bob-&gt;Alice : hello</code></pre>'
+ stub_application_setting(
+ kroki_enabled: true,
+ kroki_url: "http://localhost:8000",
+ plantuml_enabled: true,
+ plantuml_url: "http://localhost:8080"
+ )
+ doc = filter("<pre data-canonical-lang='plantuml'><code>Bob->Alice : hello</code></pre>")
+
+ expect(doc.to_s).to eq '<pre data-canonical-lang="plantuml"><code>Bob-&gt;Alice : hello</code></pre>'
end
it 'adds hidden attribute when content size is large' do
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
text = '[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]' * 25
- doc = filter("<pre lang='nomnoml'><code>#{text}</code></pre>")
+ doc = filter("<pre data-canonical-lang='nomnoml'><code>#{text}</code></pre>")
expect(doc.to_s).to start_with '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KyJyVNQiE5KTSxKidXVjS5ILCrKL4lFFrSyi07LL81RyM0vLckAysRGjxo8avCowaMGjxo8avCowaMGU8lgAE7mIdc=" hidden="" class="js-render-kroki" data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,W1BpcmF0ZXxleWVDb3VudDog'
end
@@ -50,15 +54,15 @@ RSpec.describe Banzai::Filter::KrokiFilter, feature_category: :team_planning do
it 'allows the lang attribute on the code tag to support RST files processed by gitlab-markup gem' do
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
text = '[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]' * 25
- doc = filter("<pre><code lang='nomnoml'>#{text}</code></pre>")
+ doc = filter("<pre><code data-canonical-lang='nomnoml'>#{text}</code></pre>")
expect(doc.to_s).to start_with '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KyJyVNQiE5KTSxKidXVjS5ILCrKL4lFFrSyi07LL81RyM0vLckAysRGjxo8avCowaMGjxo8avCowaMGU8lgAE7mIdc=" hidden="" class="js-render-kroki" data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,W1BpcmF0ZXxleWVDb3VudDog'
end
it 'verifies diagram type to avoid possible XSS' do
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
- doc = filter(%(<a><pre lang='f/" onerror=alert(1) onload=alert(1) '><code lang="wavedrom">xss</code></pre></a>))
+ doc = filter(%(<a><pre data-canonical-lang='f/" onerror=alert(1) onload=alert(1) '><code data-canonical-lang="wavedrom">xss</code></pre></a>))
- expect(doc.to_s).to eq %(<a><pre lang='f/" onerror=alert(1) onload=alert(1) '><code lang="wavedrom">xss</code></pre></a>)
+ expect(doc.to_s).to eq %(<a><pre data-canonical-lang='f/" onerror=alert(1) onload=alert(1) '><code data-canonical-lang="wavedrom">xss</code></pre></a>)
end
end
diff --git a/spec/lib/banzai/filter/markdown_engines/base_spec.rb b/spec/lib/banzai/filter/markdown_engines/base_spec.rb
new file mode 100644
index 00000000000..e7b32876610
--- /dev/null
+++ b/spec/lib/banzai/filter/markdown_engines/base_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::MarkdownEngines::Base, feature_category: :team_planning do
+ it 'raise error if render not implemented' do
+ engine = described_class.new({})
+
+ expect { engine.render('# hi') }.to raise_error(NotImplementedError)
+ end
+
+ it 'turns off sourcepos' do
+ engine = described_class.new({ no_sourcepos: true })
+
+ expect(engine.send(:sourcepos_disabled?)).to be_truthy
+ end
+end
diff --git a/spec/lib/banzai/filter/markdown_engines/common_mark_spec.rb b/spec/lib/banzai/filter/markdown_engines/common_mark_spec.rb
new file mode 100644
index 00000000000..74fac75abe8
--- /dev/null
+++ b/spec/lib/banzai/filter/markdown_engines/common_mark_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::MarkdownEngines::CommonMark, feature_category: :team_planning do
+ it 'defaults to generating sourcepos' do
+ engine = described_class.new({})
+
+ expect(engine.render('# hi')).to eq %(<h1 data-sourcepos="1:1-1:4">hi</h1>\n)
+ end
+
+ it 'turns off sourcepos' do
+ engine = described_class.new({ no_sourcepos: true })
+
+ expect(engine.render('# hi')).to eq %(<h1>hi</h1>\n)
+ end
+end
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index c79cd58255d..64d65528426 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -6,20 +6,19 @@ RSpec.describe Banzai::Filter::MarkdownFilter, feature_category: :team_planning
include FilterSpecHelper
describe 'markdown engine from context' do
- it 'defaults to CommonMark' do
- expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
- expect(instance).to receive(:render).and_return('test')
- end
-
- filter('test')
+ it 'finds the correct engine' do
+ expect(described_class.render_engine(:common_mark)).to eq Banzai::Filter::MarkdownEngines::CommonMark
end
- it 'uses CommonMark' do
- expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance|
- expect(instance).to receive(:render).and_return('test')
- end
+ it 'defaults to the DEFAULT_ENGINE' do
+ default_engine = Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE.to_s.classify
+ default = "Banzai::Filter::MarkdownEngines::#{default_engine}".constantize
+
+ expect(described_class.render_engine(nil)).to eq default
+ end
- filter('test', { markdown_engine: :common_mark })
+ it 'raise error for unrecognized engines' do
+ expect { described_class.render_engine(:foo_bar) }.to raise_error(NameError)
end
end
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index 374983e40a1..ded94dd6ce5 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do
end
shared_examples 'display math' do
- let_it_be(:template_prefix_with_pre) { '<pre lang="math" data-math-style="display" class="js-render-math"><code>' }
+ let_it_be(:template_prefix_with_pre) { '<pre data-canonical-lang="math" data-math-style="display" class="js-render-math"><code>' }
let_it_be(:template_prefix_with_code) { '<code data-math-style="display" class="code math js-render-math">' }
let(:use_pre_tags) { false }
@@ -101,6 +101,7 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do
context 'with valid syntax' do
where(:text, :result_template) do
"$$\n2+2\n$$" | "<math>2+2\n</math>"
+ "$$ \n2+2\n$$" | "<math>2+2\n</math>"
"$$\n2+2\n3+4\n$$" | "<math>2+2\n3+4\n</math>"
end
@@ -164,11 +165,11 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do
input = "```plaintext\n2+2\n```"
doc = pipeline_filter(input)
- expect(doc.to_s).to eq "<pre lang=\"plaintext\"><code>2+2\n</code></pre>"
+ expect(doc.to_s).to eq "<pre data-canonical-lang=\"plaintext\"><code>2+2\n</code></pre>"
end
it 'requires the pre to contain both code and math' do
- input = '<pre lang="math">something</pre>'
+ input = '<pre data-canonical-lang="math">something</pre>'
doc = pipeline_filter(input)
expect(doc.to_s).to eq input
@@ -216,9 +217,11 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do
def pipeline_filter(text)
context = { project: nil, no_sourcepos: true }
+
doc = Banzai::Pipeline::PreProcessPipeline.call(text, {})
doc = Banzai::Pipeline::PlainMarkdownPipeline.call(doc[:output], context)
- doc = Banzai::Filter::SanitizationFilter.call(doc[:output], context, nil)
+ doc = Banzai::Filter::CodeLanguageFilter.call(doc[:output], context, nil)
+ doc = Banzai::Filter::SanitizationFilter.call(doc, context, nil)
filter(doc)
end
diff --git a/spec/lib/banzai/filter/mermaid_filter_spec.rb b/spec/lib/banzai/filter/mermaid_filter_spec.rb
index de558a0774d..395a6a0bee5 100644
--- a/spec/lib/banzai/filter/mermaid_filter_spec.rb
+++ b/spec/lib/banzai/filter/mermaid_filter_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Banzai::Filter::MermaidFilter, feature_category: :team_planning d
include FilterSpecHelper
it 'adds `js-render-mermaid` class to the `code` tag' do
- doc = filter("<pre class='code highlight js-syntax-highlight mermaid' lang='mermaid' v-pre='true'><code>graph TD;\n A--&gt;B;\n</code></pre>")
+ doc = filter("<pre class='code highlight js-syntax-highlight mermaid' data-canonical-lang='mermaid' v-pre='true'><code>graph TD;\n A--&gt;B;\n</code></pre>")
result = doc.css('code').first
expect(result[:class]).to include('js-render-mermaid')
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index a1eabc23327..8b1e566376c 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :team_planning
it 'replaces plantuml pre tag with img tag' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
- input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
+ input = '<pre data-canonical-lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IFNhcmEgOiBIZWxsbw==">'
doc = filter(input)
@@ -18,7 +18,7 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :team_planning
it 'allows the lang attribute on the code tag to support RST files processed by gitlab-markup gem' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
- input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>'
+ input = '<pre><code data-canonical-lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IFNhcmEgOiBIZWxsbw==">'
doc = filter(input)
@@ -28,8 +28,8 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :team_planning
it 'does not replace plantuml pre tag with img tag if disabled' do
stub_application_setting(plantuml_enabled: false)
- input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
- output = '<pre lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
+ input = '<pre data-canonical-lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
+ output = '<pre data-canonical-lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
doc = filter(input)
expect(doc.to_s).to eq output
@@ -38,8 +38,8 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :team_planning
it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
- input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
- output = '<pre lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
+ input = '<pre data-canonical-lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
+ output = '<pre data-canonical-lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'
doc = filter(input)
expect(doc.to_s).to eq output
diff --git a/spec/lib/banzai/filter/references/design_reference_filter_spec.rb b/spec/lib/banzai/filter/references/design_reference_filter_spec.rb
index 08de9700cad..d97067de155 100644
--- a/spec/lib/banzai/filter/references/design_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/design_reference_filter_spec.rb
@@ -128,10 +128,12 @@ RSpec.describe Banzai::Filter::References::DesignReferenceFilter, feature_catego
let(:subject) { filter_instance.data_attributes_for(input_text, project, design) }
specify do
- is_expected.to include(issue: design.issue_id,
- original: input_text,
- project: project.id,
- design: design.id)
+ is_expected.to include(
+ issue: design.issue_id,
+ original: input_text,
+ project: project.id,
+ design: design.id
+ )
end
end
diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
index d8a97c6c3dc..aadd726ac40 100644
--- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
@@ -150,6 +150,15 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
expect(link.attr('href')).to eq(issue_url)
end
+ it 'includes a data-reference-format attribute for extended summary URL references' do
+ doc = reference_filter("Issue #{issue_url}+s")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-reference-format')
+ expect(link.attr('data-reference-format')).to eq('+s')
+ expect(link.attr('href')).to eq(issue_url)
+ end
+
it 'supports an :only_path context' do
doc = reference_filter("Issue #{written_reference}", only_path: true)
link = doc.css('a').first.attr('href')
diff --git a/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb
index 9853d6f4093..156455221cf 100644
--- a/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb
@@ -128,6 +128,15 @@ RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter, feature_
expect(link.attr('href')).to eq(merge_request_url)
end
+ it 'includes a data-reference-format attribute for extended summary URL references' do
+ doc = reference_filter("Merge #{merge_request_url}+s")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-reference-format')
+ expect(link.attr('data-reference-format')).to eq('+s')
+ expect(link.attr('href')).to eq(merge_request_url)
+ end
+
it 'supports an :only_path context' do
doc = reference_filter("Merge #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
diff --git a/spec/lib/banzai/filter/references/reference_cache_spec.rb b/spec/lib/banzai/filter/references/reference_cache_spec.rb
index 7307daca516..577e4471433 100644
--- a/spec/lib/banzai/filter/references/reference_cache_spec.rb
+++ b/spec/lib/banzai/filter/references/reference_cache_spec.rb
@@ -76,12 +76,11 @@ RSpec.describe Banzai::Filter::References::ReferenceCache, feature_category: :te
cache_single.load_records_per_parent
end.count
- expect(control_count).to eq 1
-
+ expect(control_count).to eq 3
# Since this is an issue filter that is not batching issue queries
# across projects, we have to account for that.
# 1 for original issue, 2 for second route/project, 1 for other issue
- max_count = control_count + 3
+ max_count = control_count + 4
expect do
cache.load_references_per_parent(filter.nodes)
diff --git a/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb b/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb
new file mode 100644
index 00000000000..e59e53891bf
--- /dev/null
+++ b/spec/lib/banzai/filter/references/work_item_reference_filter_spec.rb
@@ -0,0 +1,314 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::References::WorkItemReferenceFilter, feature_category: :team_planning do
+ include FilterSpecHelper
+
+ let_it_be(:namespace) { create(:namespace, name: 'main-namespace') }
+ let_it_be(:project) { create(:project, :public, namespace: namespace, path: 'main-project') }
+ let_it_be(:cross_namespace) { create(:namespace, name: 'cross-namespace') }
+ let_it_be(:cross_project) { create(:project, :public, namespace: cross_namespace, path: 'cross-project') }
+ let_it_be(:work_item) { create(:work_item, project: project) }
+
+ def item_url(item)
+ work_item_path = "/#{item.project.namespace.path}/#{item.project.path}/-/work_items/#{item.iid}"
+
+ "http://#{Gitlab.config.gitlab.host}#{work_item_path}"
+ end
+
+ it 'subclasses from IssueReferenceFilter' do
+ expect(described_class.superclass).to eq Banzai::Filter::References::IssueReferenceFilter
+ end
+
+ shared_examples 'a reference with work item type information' do
+ it 'contains work-item-type as a data attribute' do
+ doc = reference_filter("Fixed #{reference}")
+
+ expect(doc.css('a').first.attr('data-work-item-type')).to eq('issue')
+ end
+ end
+
+ shared_examples 'a work item reference' do
+ it_behaves_like 'a reference containing an element node'
+
+ it_behaves_like 'a reference with work item type information'
+
+ it 'links to a valid reference' do
+ doc = reference_filter("Fixed #{written_reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq work_item_url
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{written_reference}.)")
+
+ expect(doc.text).to match(%r{^Fixed \(.*\.\)})
+ end
+
+ it 'includes a title attribute' do
+ doc = reference_filter("Issue #{written_reference}")
+
+ expect(doc.css('a').first.attr('title')).to eq work_item.title
+ end
+
+ it 'escapes the title attribute' do
+ work_item.update_attribute(:title, %("></a>whatever<a title="))
+
+ doc = reference_filter("Issue #{written_reference}")
+
+ expect(doc.text).not_to include 'whatever'
+ end
+
+ it 'renders non-HTML tooltips' do
+ doc = reference_filter("Issue #{written_reference}")
+
+ expect(doc.at_css('a')).not_to have_attribute('data-html')
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("Issue #{written_reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-work_item'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("Issue #{written_reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq cross_project.id.to_s
+ end
+
+ it 'includes a data-issue attribute' do
+ doc = reference_filter("See #{written_reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-work-item')
+ expect(link.attr('data-work-item')).to eq work_item.id.to_s
+ end
+
+ it 'includes data attributes for issuable popover' do
+ doc = reference_filter("See #{written_reference}")
+ link = doc.css('a').first
+
+ expect(link.attr('data-project-path')).to eq cross_project.full_path
+ expect(link.attr('data-iid')).to eq work_item.iid.to_s
+ end
+
+ it 'includes a data-original attribute' do
+ doc = reference_filter("See #{written_reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-original')
+ expect(link.attr('data-original')).to eq inner_text
+ end
+
+ it 'does not escape the data-original attribute' do
+ skip if written_reference.start_with?('<a')
+
+ inner_html = 'element <code>node</code> inside'
+ doc = reference_filter(%(<a href="#{written_reference}">#{inner_html}</a>))
+
+ expect(doc.children.first.attr('data-original')).to eq inner_html
+ end
+
+ it 'includes a data-reference-format attribute' do
+ skip if written_reference.start_with?('<a')
+
+ doc = reference_filter("Issue #{written_reference}+")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-reference-format')
+ expect(link.attr('data-reference-format')).to eq('+')
+ expect(link.attr('href')).to eq(work_item_url)
+ end
+
+ it 'includes a data-reference-format attribute for URL references' do
+ doc = reference_filter("Issue #{work_item_url}+")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-reference-format')
+ expect(link.attr('data-reference-format')).to eq('+')
+ expect(link.attr('href')).to eq(work_item_url)
+ end
+
+ it 'includes a data-reference-format attribute for extended summary URL references' do
+ doc = reference_filter("Issue #{work_item_url}+s")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-reference-format')
+ expect(link.attr('data-reference-format')).to eq('+s')
+ expect(link.attr('href')).to eq(work_item_url)
+ end
+
+ it 'does not process links containing issue numbers followed by text' do
+ href = "#{written_reference}st"
+ doc = reference_filter("<a href='#{href}'></a>")
+ link = doc.css('a').first.attr('href')
+
+ expect(link).to eq(href)
+ end
+ end
+
+ # Example:
+ # "See #1"
+ context 'when standard internal reference' do
+ it 'is handled by IssueReferenceFilter, not WorkItemReferenceFilter' do
+ doc = reference_filter("Fixed ##{work_item.iid}")
+
+ expect(doc.css('a')).to be_empty
+ end
+ end
+
+ # Example:
+ # "See cross-namespace/cross-project#1"
+ context 'when cross-project / cross-namespace complete reference' do
+ let_it_be(:work_item2) { create(:work_item, project: cross_project) }
+ let_it_be(:reference) { "#{cross_project.full_path}##{work_item2.iid}" }
+
+ it 'is handled by IssueReferenceFilter, not WorkItemReferenceFilter' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a')).to be_empty
+ end
+ end
+
+ # Example:
+ # "See main-namespace/cross-project#1"
+ context 'when cross-project / same-namespace complete reference' do
+ let_it_be(:cross_project) { create(:project, :public, namespace: namespace, path: 'cross-project') }
+ let_it_be(:work_item) { create(:work_item, project: cross_project) }
+ let_it_be(:reference) { "#{cross_project.full_path}##{work_item.iid}" }
+
+ it 'is handled by IssueReferenceFilter, not WorkItemReferenceFilter' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a')).to be_empty
+ end
+ end
+
+ # Example:
+ # "See cross-project#1"
+ context 'when cross-project / same-namespace shorthand reference' do
+ let_it_be(:cross_project) { create(:project, :public, namespace: namespace, path: 'cross-project') }
+ let_it_be(:work_item) { create(:work_item, project: cross_project) }
+ let_it_be(:reference) { "#{cross_project.path}##{work_item.iid}" }
+
+ it 'is handled by IssueReferenceFilter, not WorkItemReferenceFilter' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a')).to be_empty
+ end
+ end
+
+ # Example:
+ # "See http://localhost/cross-namespace/cross-project/-/work_items/1"
+ context 'when cross-project URL reference' do
+ let_it_be(:work_item, reload: true) { create(:work_item, project: cross_project) }
+ let_it_be(:work_item_url) { item_url(work_item) }
+ let_it_be(:reference) { work_item_url }
+ let_it_be(:written_reference) { reference }
+ let_it_be(:inner_text) { written_reference }
+
+ it_behaves_like 'a work item reference'
+ end
+
+ # Example:
+ # "See http://localhost/cross-namespace/cross-project/-/work_items/1#note_123"
+ context 'when cross-project URL reference with comment anchor' do
+ let_it_be(:work_item) { create(:work_item, project: cross_project) }
+ let_it_be(:work_item_url) { item_url(work_item) }
+ let_it_be(:reference) { "#{work_item_url}#note_123" }
+
+ it_behaves_like 'a reference containing an element node'
+
+ it_behaves_like 'a reference with work item type information'
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq reference
+ end
+
+ it 'link with trailing slash' do
+ doc = reference_filter("Fixed (#{work_item_url}/.)")
+
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(work_item.to_reference(project))}</a>\.\)})
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Fixed (#{reference}.)")
+
+ expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(work_item.to_reference(project))} \(comment 123\)</a>\.\)})
+ end
+ end
+
+ # Example:
+ # 'See <a href="cross-namespace/cross-project#1">Reference</a>''
+ context 'when cross-project reference in link href' do
+ let_it_be(:work_item) { create(:work_item, project: cross_project) }
+ let_it_be(:reference) { work_item.to_reference(project) }
+ let_it_be(:reference_link) { %(<a href="#{reference}">Reference</a>) }
+ let_it_be(:work_item_url) { item_url(work_item) }
+
+ it 'is handled by IssueReferenceFilter, not WorkItemReferenceFilter' do
+ doc = reference_filter("See #{reference_link}")
+
+ expect(doc.css('a').first[:href]).to eq reference
+ expect(doc.css('a').first[:href]).not_to eq work_item_url
+ end
+ end
+
+ # Example:
+ # 'See <a href=\"http://localhost/cross-namespace/cross-project/-/work_items/1\">Reference</a>''
+ context 'when cross-project URL in link href' do
+ let_it_be(:work_item, reload: true) { create(:work_item, project: cross_project) }
+ let_it_be(:work_item_url) { item_url(work_item) }
+ let_it_be(:reference) { work_item_url }
+ let_it_be(:reference_link) { %(<a href="#{reference}">Reference</a>) }
+ let_it_be(:written_reference) { reference_link }
+ let_it_be(:inner_text) { 'Reference' }
+
+ it_behaves_like 'a work item reference'
+ end
+
+ context 'for group context' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:context) { { project: nil, group: group } }
+ let_it_be(:work_item_url) { item_url(work_item) }
+
+ it 'links to a valid reference for url cross-namespace' do
+ reference = "#{work_item_url}#note_123"
+
+ doc = reference_filter("See #{reference}", context)
+
+ link = doc.css('a').first
+ expect(link.attr('href')).to eq("#{work_item_url}#note_123")
+ expect(link.text).to include("#{project.full_path}##{work_item.iid}")
+ end
+
+ it 'links to a valid reference for cross-namespace in link href' do
+ reference = "#{work_item_url}#note_123"
+ reference_link = %(<a href="#{reference}">Reference</a>)
+
+ doc = reference_filter("See #{reference_link}", context)
+
+ link = doc.css('a').first
+ expect(link.attr('href')).to eq("#{work_item_url}#note_123")
+ expect(link.text).to include('Reference')
+ end
+ end
+
+ describe 'performance' do
+ let(:another_work_item) { create(:work_item, project: project) }
+
+ it 'does not have a N+1 query problem' do
+ single_reference = "Work item #{work_item.to_reference}"
+ multiple_references = "Work items #{work_item.to_reference} and #{another_work_item.to_reference}"
+
+ control_count = ActiveRecord::QueryRecorder.new { reference_filter(single_reference).to_html }.count
+
+ expect { reference_filter(multiple_references).to_html }.not_to exceed_query_limit(control_count)
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/repository_link_filter_spec.rb b/spec/lib/banzai/filter/repository_link_filter_spec.rb
index b2162ea2756..b6966709f5c 100644
--- a/spec/lib/banzai/filter/repository_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/repository_link_filter_spec.rb
@@ -369,7 +369,18 @@ RSpec.describe Banzai::Filter::RepositoryLinkFilter, feature_category: :team_pla
end
end
- context 'with a valid commit' do
+ context 'when public project repo with a valid commit' do
+ include_examples 'valid repository'
+ end
+
+ context 'when private project repo with a valid commit' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+
+ before do
+ # user must have `read_code` ability
+ project.add_developer(user)
+ end
+
include_examples 'valid repository'
end
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index e65a9214e76..12cd411a613 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Banzai::Filter::SuggestionFilter, feature_category: :team_plannin
end
context 'multi-line suggestions' do
- let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR }
+ let(:data_attr) { Banzai::Filter::CodeLanguageFilter::LANG_PARAMS_ATTR }
let(:input) { %(<pre class="code highlight js-syntax-highlight language-suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) }
it 'element has correct data-lang-params' do
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 0d7f322d08f..4aacebe6024 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
it "escapes HTML tags" do
# This is how a script tag inside a code block is presented to this filter
# after Markdown rendering.
- result = filter(%{<pre lang="#{lang}"><code>&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>})
+ result = filter(%{<pre data-canonical-lang="#{lang}"><code>&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>})
# `(1)` symbols are wrapped by lexer tags.
expect(result.to_html).not_to match(%r{<script>alert.*</script>})
@@ -23,7 +23,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", ""
@@ -31,9 +31,9 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
context "when contains mermaid diagrams" do
it "ignores mermaid blocks" do
- result = filter('<pre data-mermaid-style="display" lang="mermaid"><code class="js-render-mermaid">mermaid code</code></pre>')
+ result = filter('<pre data-mermaid-style="display" data-canonical-lang="mermaid"><code class="js-render-mermaid">mermaid code</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-mermaid-style="display" lang="mermaid" class="code highlight js-syntax-highlight language-mermaid" v-pre="true"><code class="js-render-mermaid"><span id="LC1" class="line" lang="mermaid">mermaid code</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-mermaid-style="display" data-canonical-lang="mermaid" class="code highlight js-syntax-highlight language-mermaid" lang="mermaid" v-pre="true"><code class="js-render-mermaid"><span id="LC1" class="line" lang="mermaid">mermaid code</span></code></pre><copy-code></copy-code></div>')
end
end
@@ -62,15 +62,15 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
text = "<div>\n<pre><code>\nsomething\n<pre><code>else\n</code></pre></code></pre>\n</div>"
result = filter(text)
- expect(result.to_html.delete("\n")).to eq('<div><div class="gl-relative markdown-code-block js-markdown-code"><pre lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"></span><span id="LC2" class="line" lang="plaintext">something</span><span id="LC3" class="line" lang="plaintext">else</span></code></pre><copy-code></copy-code></div></div>')
+ expect(result.to_html.delete("\n")).to eq('<div><div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"></span><span id="LC2" class="line" lang="plaintext">something</span><span id="LC3" class="line" lang="plaintext">else</span></code></pre><copy-code></copy-code></div></div>')
end
end
context "when a valid language is specified" do
it "highlights as that language" do
- result = filter('<pre lang="ruby"><code>def fun end</code></pre>')
+ result = filter('<pre data-canonical-lang="ruby"><code>def fun end</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="ruby" class="code highlight js-syntax-highlight language-ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-canonical-lang="ruby" class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
@@ -78,88 +78,40 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
context "when an invalid language is specified" do
it "highlights as plaintext" do
- result = filter('<pre lang="gnuplot"><code>This is a test</code></pre>')
+ result = filter('<pre data-canonical-lang="gnuplot"><code>This is a test</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="gnuplot" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-canonical-lang="gnuplot" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "gnuplot"
end
context "languages that should be passed through" do
- let(:delimiter) { described_class::LANG_PARAMS_DELIMITER }
- let(:data_attr) { described_class::LANG_PARAMS_ATTR }
-
%w(math mermaid plantuml suggestion).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
- result = filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>})
- copy_code_btn = '<copy-code></copy-code>' unless lang == 'suggestion'
-
- expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>#{copy_code_btn}</div>})
- end
-
- include_examples "XSS prevention", lang
- end
-
- context "when #{lang} has extra params" do
- let(:lang_params) { 'foo-bar-kux' }
- let(:xss_lang) { "#{lang} data-meta=\"foo-bar-kux\"&lt;script&gt;alert(1)&lt;/script&gt;" }
-
- it "includes data-lang-params tag with extra information" do
- result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params}"><code>This is a test</code></pre>})
+ result = filter(%{<pre data-canonical-lang="#{lang}"><code>This is a test</code></pre>})
copy_code_btn = '<copy-code></copy-code>' unless lang == 'suggestion'
- expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>#{copy_code_btn}</div>})
+ expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre data-canonical-lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>#{copy_code_btn}</div>})
end
include_examples "XSS prevention", lang
-
- include_examples "XSS prevention",
- "#{lang} data-meta=\"foo-bar-kux\"&lt;script&gt;alert(1)&lt;/script&gt;"
-
- include_examples "XSS prevention",
- "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>"
- end
- end
-
- context 'when multiple param delimiters are used' do
- let(:lang) { 'suggestion' }
- let(:lang_params) { '-1+10' }
-
- let(:expected_result) do
- %{<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre></div>}
- end
-
- context 'when delimiter is space' do
- it 'delimits on the first appearance' do
- result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params} more-things"><code>This is a test</code></pre>})
-
- expect(result.to_html.delete("\n")).to eq(expected_result)
- end
- end
-
- context 'when delimiter is colon' do
- it 'delimits on the first appearance' do
- result = filter(%{<pre lang="#{lang}#{delimiter}#{lang_params} more-things"><code>This is a test</code></pre>})
-
- expect(result.to_html.delete("\n")).to eq(expected_result)
- end
end
end
end
context "when sourcepos metadata is available" do
it "includes it in the highlighted code block" do
- result = filter('<pre data-sourcepos="1:1-3:3"><code lang="plaintext">This is a test</code></pre>')
+ result = filter('<pre data-sourcepos="1:1-3:3" data-canonical-lang="plaintext"><code>This is a test</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code lang="plaintext"><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" data-canonical-lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
it "escape sourcepos metadata to prevent XSS" do
result = filter('<pre data-sourcepos="&#34;%22 href=&#34;x&#34;></pre><base href=http://unsafe-website.com/><pre x=&#34;"><code></code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos=\'"%22 href="x"&gt;&lt;/pre&gt;&lt;base href=http://unsafe-website.com/&gt;&lt;pre x="\' lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos=\'"%22 href="x"&gt;&lt;/pre&gt;&lt;base href=http://unsafe-website.com/&gt;&lt;pre x="\' class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code></code></pre><copy-code></copy-code></div>')
end
end
@@ -171,9 +123,9 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
end
it "highlights as plaintext" do
- result = filter('<pre lang="ruby"><code>This is a test</code></pre>')
+ result = filter('<pre data-canonical-lang="ruby"><code>This is a test</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="" class="code highlight js-syntax-highlight" data-canonical-lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-canonical-lang="ruby" class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
@@ -195,7 +147,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_pl
include_examples "XSS prevention", "ruby"
end
- it_behaves_like "filter timeout" do
- let(:text) { '<pre lang="ruby"><code>def fun end</code></pre>' }
+ it_behaves_like "html filter timeout" do
+ let(:text) { '<pre data-canonical-lang="ruby"><code>def fun end</code></pre>' }
end
end
diff --git a/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb b/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb
index 066f59758f0..52e4b70cfe5 100644
--- a/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb
+++ b/spec/lib/banzai/filter/timeout_html_pipeline_filter_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::TimeoutHtmlPipelineFilter, feature_category: :team_planning do
include FilterSpecHelper
- it_behaves_like 'filter timeout' do
+ it_behaves_like 'html filter timeout' do
let(:text) { '<p>some text</p>' }
end
diff --git a/spec/lib/banzai/filter/timeout_text_pipeline_filter_spec.rb b/spec/lib/banzai/filter/timeout_text_pipeline_filter_spec.rb
new file mode 100644
index 00000000000..4e22594389b
--- /dev/null
+++ b/spec/lib/banzai/filter/timeout_text_pipeline_filter_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::TimeoutTextPipelineFilter, feature_category: :team_planning do
+ include FilterSpecHelper
+
+ it_behaves_like 'text filter timeout' do
+ let(:text) { '<p>some text</p>' }
+ end
+
+ it 'raises NotImplementedError' do
+ expect { filter('test') }.to raise_error NotImplementedError
+ end
+end
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index b2c869bd066..5bbd98592e7 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Banzai::IssuableExtractor, feature_category: :team_planning do
let(:user) { create(:user) }
let(:extractor) { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:issue) { create(:issue, project: project) }
+ let(:work_item) { create(:work_item, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:issue_link) do
html_to_node(
@@ -14,6 +15,12 @@ RSpec.describe Banzai::IssuableExtractor, feature_category: :team_planning do
)
end
+ let(:work_item_link) do
+ html_to_node(
+ "<a href='' data-work-item='#{work_item.id}' data-reference-type='work_item' class='gfm'>text</a>"
+ )
+ end
+
let(:merge_request_link) do
html_to_node(
"<a href='' data-merge-request='#{merge_request.id}' data-reference-type='merge_request' class='gfm'>text</a>"
@@ -27,17 +34,17 @@ RSpec.describe Banzai::IssuableExtractor, feature_category: :team_planning do
end
it 'returns instances of issuables for nodes with references' do
- result = extractor.extract([issue_link, merge_request_link])
+ result = extractor.extract([issue_link, work_item_link, merge_request_link])
- expect(result).to eq(issue_link => issue, merge_request_link => merge_request)
+ expect(result).to eq(issue_link => issue, work_item_link => work_item, merge_request_link => merge_request)
end
describe 'caching', :request_store do
it 'saves records to cache' do
- extractor.extract([issue_link, merge_request_link])
+ extractor.extract([issue_link, work_item_link, merge_request_link])
second_call_queries = ActiveRecord::QueryRecorder.new do
- extractor.extract([issue_link, merge_request_link])
+ extractor.extract([issue_link, work_item_link, merge_request_link])
end.count
expect(second_call_queries).to eq 0
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index d0b85a1d043..58d6b9b9a2c 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::GfmPipeline, feature_category: :team_planning do
describe 'integration between parsing regular and external issue references' do
- let(:project) { create(:project, :with_redmine_integration, :public) }
+ let_it_be(:project) { create(:project, :with_redmine_integration, :public) }
context 'when internal issue tracker is enabled' do
context 'when shorthand pattern #ISSUE_ID is used' do
diff --git a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
index 8d15dbc8f2f..12a6be6bc18 100644
--- a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
@@ -78,7 +78,7 @@ RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
it 'replaces existing label to a link' do
# rubocop:disable Layout/LineLength
is_expected.to match(
- %r(<p>.+<a href="[\w/]+-/issues\?label_name=#{label.name}".+style="background-color: #\d{6}".*>#{label.name}</span></a></span> ~unknown</p>)
+ %r{<p>.+<a href="[\w\-/]+-/issues\?label_name=#{label.name}".+style="background-color: #\d{6}".*>#{label.name}</span></a></span> ~unknown</p>}
)
# rubocop:enable Layout/LineLength
end
@@ -95,7 +95,7 @@ RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
let(:markdown) { "issue ##{issue.iid}" }
it 'contains a link to the issue' do
- is_expected.to match(%r(<p>issue <a href="[\w/]+-/issues/#{issue.iid}".*>##{issue.iid}</a></p>))
+ is_expected.to match(%r{<p>issue <a href="[\w\-/]+-/issues/#{issue.iid}".*>##{issue.iid}</a></p>})
end
end
@@ -104,7 +104,7 @@ RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
let(:markdown) { "MR !#{mr.iid}" }
it 'contains a link to the merge request' do
- is_expected.to match(%r(<p>MR <a href="[\w/]+-/merge_requests/#{mr.iid}".*>!#{mr.iid}</a></p>))
+ is_expected.to match(%r{<p>MR <a href="[\w\-/]+-/merge_requests/#{mr.iid}".*>!#{mr.iid}</a></p>})
end
end
end
diff --git a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb
index e7c15ed9cf6..b8d2b6f7d7e 100644
--- a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline, feature_category: :team_
let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) }
it 'renders correct html' do
- correct_html_included(markdown, %Q(<pre data-sourcepos="1:1-3:3" lang="foo@bar"><code>foo\n</code></pre>))
+ correct_html_included(markdown, %Q(<pre lang="foo@bar"><code>foo\n</code></pre>))
end
where(:markdown, :expected) do
@@ -95,7 +95,7 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline, feature_category: :team_
end
def correct_html_included(markdown, expected)
- result = described_class.call(markdown, {})
+ result = described_class.call(markdown, { no_sourcepos: true })
expect(result[:output].to_html).to include(expected)
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 837ea2d7bc0..81406eae0c9 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
- let_it_be(:namespace) { create(:namespace, name: "wiki_link_ns") }
- let_it_be(:project) { create(:project, :public, name: "wiki_link_project", namespace: namespace) }
+ let_it_be(:namespace) { create(:namespace) }
+ let_it_be(:project) { create(:project, :public, namespace: namespace) }
let_it_be(:wiki) { ProjectWiki.new(project, nil) }
let_it_be(:page) { build(:wiki_page, wiki: wiki, title: 'nested/twice/start-page') }
@@ -85,14 +85,14 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
markdown = "[Page](./page)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/page\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/twice/page\"")
end
it "rewrites file links to be at the scope of the current directory" do
markdown = "[Link to Page](./page.md)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/page.md\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/twice/page.md\"")
end
end
@@ -101,14 +101,14 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
markdown = "[Link to Page](../page)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/page\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/page\"")
end
it "rewrites file links to be at the scope of the parent directory" do
markdown = "[Link to Page](../page.md)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/page.md\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/page.md\"")
end
end
@@ -117,14 +117,14 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
markdown = "[Link to Page](./subdirectory/page)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/subdirectory/page\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/twice/subdirectory/page\"")
end
it "rewrites file links to be at the scope of the sub-directory" do
markdown = "[Link to Page](./subdirectory/page.md)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/subdirectory/page.md\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/twice/subdirectory/page.md\"")
end
end
@@ -133,35 +133,35 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
markdown = "[Link to Page](page)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/page\"")
end
it 'rewrites non-file links (with spaces) to be at the scope of the wiki root' do
markdown = "[Link to Page](page slug)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page%20slug\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/page%20slug\"")
end
it "rewrites file links to be at the scope of the current directory" do
markdown = "[Link to Page](page.md)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/page.md\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/nested/twice/page.md\"")
end
it 'rewrites links with anchor' do
markdown = '[Link to Header](start-page#title)'
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/start-page#title\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/start-page#title\"")
end
it 'rewrites links (with spaces) with anchor' do
markdown = '[Link to Header](start page#title)'
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/start%20page#title\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/start%20page#title\"")
end
end
@@ -170,14 +170,14 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
markdown = "[Link to Page](/page)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/page\"")
end
it 'rewrites file links to be at the scope of the wiki root' do
markdown = "[Link to Page](/page.md)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/-/wikis/page.md\"")
+ expect(output).to include("href=\"#{relative_url_root}/#{project.full_path}/-/wikis/page.md\"")
end
end
end
@@ -278,28 +278,28 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
markdown = "![video_file](video_file_name.mp4)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/video_file_name.mp4"')
+ expect(output).to include(%(<video src="/#{project.full_path}/-/wikis/nested/twice/video_file_name.mp4"))
end
it 'rewrites and replaces video links names with white spaces to %20' do
markdown = "![video file](video file name.mp4)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include('<video src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/video%20file%20name.mp4"')
+ expect(output).to include(%(<video src="/#{project.full_path}/-/wikis/nested/twice/video%20file%20name.mp4"))
end
it 'generates audio html structure' do
markdown = "![audio_file](audio_file_name.wav)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/audio_file_name.wav"')
+ expect(output).to include(%(<audio src="/#{project.full_path}/-/wikis/nested/twice/audio_file_name.wav"))
end
it 'rewrites and replaces audio links names with white spaces to %20' do
markdown = "![audio file](audio file name.wav)"
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
- expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/audio%20file%20name.wav"')
+ expect(output).to include(%(<audio src="/#{project.full_path}/-/wikis/nested/twice/audio%20file%20name.wav"))
end
end
@@ -320,7 +320,7 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
doc = Nokogiri::HTML::DocumentFragment.parse(output)
- full_path = "/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/#{wiki_file.path}"
+ full_path = "/#{project.full_path}/-/wikis/nested/twice/#{wiki_file.path}"
expect(doc.css('a')[0].attr('href')).to eq(full_path)
expect(doc.css('img')[0].attr('class')).to eq('gfm lazy')
expect(doc.css('img')[0].attr('data-src')).to eq(full_path)
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index 081bfa26fb2..7a1eed2e35e 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -5,8 +5,9 @@ require 'spec_helper'
RSpec.describe Banzai::ReferenceParser::CommitParser, feature_category: :source_code_management do
include ReferenceParserHelpers
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:user) { create(:user) }
+
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
let(:link) { empty_html_link }
@@ -130,20 +131,28 @@ RSpec.describe Banzai::ReferenceParser::CommitParser, feature_category: :source_
end
describe '#find_commits' do
- it 'returns an Array of commit objects' do
- commit = double(:commit)
+ let_it_be(:ids) { project.repository.commits(project.default_branch, limit: 3).map(&:id) }
+
+ it 'is empty when repo is invalid' do
+ allow(project).to receive(:valid_repo?).and_return(false)
- expect(project).to receive(:commit).with('123').and_return(commit)
- expect(project).to receive(:valid_repo?).and_return(true)
+ expect(subject.find_commits(project, ids)).to eq([])
+ end
- expect(subject.find_commits(project, %w{123})).to eq([commit])
+ it 'returns commits by the specified ids' do
+ expect(subject.find_commits(project, ids).map(&:id)).to eq(%w[
+ b83d6e391c22777fca1ed3012fce84f633d7fed0
+ 498214de67004b1da3d820901307bed2a68a8ef6
+ 1b12f15a11fc6e62177bef08f47bc7b5ce50b141
+ ])
end
- it 'skips commit IDs for which no commit could be found' do
- expect(project).to receive(:commit).with('123').and_return(nil)
- expect(project).to receive(:valid_repo?).and_return(true)
+ it 'is limited' do
+ stub_const("#{described_class}::COMMITS_LIMIT", 1)
- expect(subject.find_commits(project, %w{123})).to eq([])
+ expect(subject.find_commits(project, ids).map(&:id)).to eq([
+ "b83d6e391c22777fca1ed3012fce84f633d7fed0"
+ ])
end
end
diff --git a/spec/lib/banzai/reference_parser/work_item_parser_spec.rb b/spec/lib/banzai/reference_parser/work_item_parser_spec.rb
new file mode 100644
index 00000000000..dbde01cc94f
--- /dev/null
+++ b/spec/lib/banzai/reference_parser/work_item_parser_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::ReferenceParser::WorkItemParser, feature_category: :team_planning do
+ include ReferenceParserHelpers
+
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be_with_reload(:project) { create(:project, :public, group: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:work_item) { create(:work_item, project: project) }
+ let_it_be(:link) { empty_html_link }
+
+ subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
+ describe '#records_for_nodes' do
+ it 'returns a Hash containing the work items for a list of nodes' do
+ link['data-work-item'] = work_item.id.to_s
+ nodes = [link]
+
+ expect(subject.records_for_nodes(nodes)).to eq({ link => work_item })
+ end
+ end
+
+ context 'when checking multiple work items on another project' do
+ let_it_be(:other_project) { create(:project, :public) }
+ let_it_be(:other_work_item) { create(:work_item, project: other_project) }
+ let_it_be(:control_links) do
+ [work_item_link(other_work_item)]
+ end
+
+ let_it_be(:actual_links) do
+ control_links + [work_item_link(create(:work_item, project: other_project))]
+ end
+
+ def work_item_link(work_item)
+ Nokogiri::HTML.fragment(%(<a data-work-item="#{work_item.id}"></a>)).children[0]
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'no N+1 queries'
+ end
+end
diff --git a/spec/lib/banzai/reference_redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 8a8f3ce586a..21736903cbf 100644
--- a/spec/lib/banzai/reference_redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -111,13 +111,16 @@ RSpec.describe Banzai::ReferenceRedactor, feature_category: :team_planning do
def create_link(issuable)
type = issuable.class.name.underscore.downcase
- ActionController::Base.helpers.link_to(issuable.to_reference, '',
- class: 'gfm has-tooltip',
- title: issuable.title,
- data: {
- reference_type: type,
- "#{type}": issuable.id
- })
+ ActionController::Base.helpers.link_to(
+ issuable.to_reference,
+ '',
+ class: 'gfm has-tooltip',
+ title: issuable.title,
+ data: {
+ reference_type: type,
+ "#{type}": issuable.id
+ }
+ )
end
before do
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index 8c9d8d51d5f..ef503b8ec52 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -87,15 +87,11 @@ RSpec.describe Banzai::Renderer, feature_category: :team_planning do
describe '#cacheless_render' do
context 'without cache' do
let(:object) { fake_object(fresh: false) }
- let(:histogram) { double('prometheus histogram') }
it 'returns cacheless render field' do
allow(renderer).to receive(:render_result).and_return(output: 'test')
- allow(renderer).to receive(:real_duration_histogram).and_return(histogram)
- allow(renderer).to receive(:cpu_duration_histogram).and_return(histogram)
expect(renderer).to receive(:render_result).with('test', {})
- expect(histogram).to receive(:observe).twice
renderer.cacheless_render('test')
end