1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe MarkupHelper do 6 let_it_be(:project) { create(:project, :repository) } 7 let_it_be(:user) do 8 user = create(:user, username: 'gfm') 9 project.add_maintainer(user) 10 user 11 end 12 13 let_it_be(:issue) { create(:issue, project: project) } 14 let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) } 15 let_it_be(:snippet) { create(:project_snippet, project: project) } 16 17 let(:commit) { project.commit } 18 19 before do 20 # Helper expects a @project instance variable 21 helper.instance_variable_set(:@project, project) 22 23 # Stub the `current_user` helper 24 allow(helper).to receive(:current_user).and_return(user) 25 end 26 27 describe "#markdown" do 28 describe "referencing multiple objects" do 29 let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" } 30 31 it "links to the merge request" do 32 expected = urls.project_merge_request_path(project, merge_request) 33 expect(helper.markdown(actual)).to match(expected) 34 end 35 36 it "links to the commit" do 37 expected = urls.project_commit_path(project, commit) 38 expect(helper.markdown(actual)).to match(expected) 39 end 40 41 it "links to the issue" do 42 expected = urls.project_issue_path(project, issue) 43 expect(helper.markdown(actual)).to match(expected) 44 end 45 end 46 47 describe "override default project" do 48 let(:actual) { issue.to_reference } 49 50 let_it_be(:second_project) { create(:project, :public) } 51 let_it_be(:second_issue) { create(:issue, project: second_project) } 52 53 it 'links to the issue' do 54 expected = urls.project_issue_path(second_project, second_issue) 55 expect(markdown(actual, project: second_project)).to match(expected) 56 end 57 end 58 59 describe 'uploads' do 60 let(:text) { "![ImageTest](/uploads/test.png)" } 61 62 let_it_be(:group) { create(:group) } 63 64 subject { helper.markdown(text) } 65 66 describe 'inside a project' do 67 it 'renders uploads relative to project' do 68 expect(subject).to include("#{project.full_path}/uploads/test.png") 69 end 70 end 71 72 describe 'inside a group' do 73 before do 74 helper.instance_variable_set(:@group, group) 75 helper.instance_variable_set(:@project, nil) 76 end 77 78 it 'renders uploads relative to the group' do 79 expect(subject).to include("#{group.full_path}/-/uploads/test.png") 80 end 81 end 82 83 describe "with a group in the context" do 84 let_it_be(:project_in_group) { create(:project, group: group) } 85 86 before do 87 helper.instance_variable_set(:@group, group) 88 helper.instance_variable_set(:@project, project_in_group) 89 end 90 91 it 'renders uploads relative to project' do 92 expect(subject).to include("#{project_in_group.path_with_namespace}/uploads/test.png") 93 end 94 end 95 end 96 97 context 'when text contains a relative link to an image in the repository' do 98 let(:image_file) { "logo-white.png" } 99 let(:text_with_relative_path) { "![](./#{image_file})\n" } 100 let(:generated_html) { helper.markdown(text_with_relative_path, requested_path: requested_path, ref: ref) } 101 102 subject { Nokogiri::HTML.parse(generated_html) } 103 104 context 'when requested_path is provided, but ref isn\'t' do 105 let(:requested_path) { 'files/images/README.md' } 106 let(:ref) { nil } 107 108 it 'returns the correct HTML for the image' do 109 expanded_path = "/#{project.full_path}/-/raw/master/files/images/#{image_file}" 110 111 expect(subject.css('a')[0].attr('href')).to eq(expanded_path) 112 expect(subject.css('img')[0].attr('data-src')).to eq(expanded_path) 113 end 114 end 115 116 context 'when requested_path and ref parameters are both provided' do 117 let(:requested_path) { 'files/images/README.md' } 118 let(:ref) { 'other_branch' } 119 120 it 'returns the correct HTML for the image' do 121 project.repository.create_branch('other_branch') 122 123 expanded_path = "/#{project.full_path}/-/raw/#{ref}/files/images/#{image_file}" 124 125 expect(subject.css('a')[0].attr('href')).to eq(expanded_path) 126 expect(subject.css('img')[0].attr('data-src')).to eq(expanded_path) 127 end 128 end 129 130 context 'when ref is provided, but requested_path isn\'t' do 131 let(:ref) { 'other_branch' } 132 let(:requested_path) { nil } 133 134 it 'returns the correct HTML for the image' do 135 project.repository.create_branch('other_branch') 136 137 expanded_path = "/#{project.full_path}/-/blob/#{ref}/./#{image_file}" 138 139 expect(subject.css('a')[0].attr('href')).to eq(expanded_path) 140 expect(subject.css('img')[0].attr('data-src')).to eq(expanded_path) 141 end 142 end 143 144 context 'when neither requested_path, nor ref parameter is provided' do 145 let(:ref) { nil } 146 let(:requested_path) { nil } 147 148 it 'returns the correct HTML for the image' do 149 expanded_path = "/#{project.full_path}/-/blob/master/./#{image_file}" 150 151 expect(subject.css('a')[0].attr('href')).to eq(expanded_path) 152 expect(subject.css('img')[0].attr('data-src')).to eq(expanded_path) 153 end 154 end 155 end 156 end 157 158 describe '#markdown_field' do 159 let(:attribute) { :title } 160 161 describe 'with already redacted attribute' do 162 it 'returns the redacted attribute' do 163 commit.redacted_title_html = 'commit title' 164 165 expect(Banzai).not_to receive(:render_field) 166 167 expect(helper.markdown_field(commit, attribute)).to eq('commit title') 168 end 169 end 170 171 describe 'without redacted attribute' do 172 it 'renders the markdown value' do 173 expect(Banzai).to receive(:render_field).with(commit, attribute, {}).and_call_original 174 expect(Banzai).to receive(:post_process) 175 176 helper.markdown_field(commit, attribute) 177 end 178 end 179 180 context 'when post_process is false' do 181 it 'does not run Markdown post processing' do 182 expect(Banzai).to receive(:render_field).with(commit, attribute, {}).and_call_original 183 expect(Banzai).not_to receive(:post_process) 184 185 helper.markdown_field(commit, attribute, post_process: false) 186 end 187 end 188 end 189 190 describe '#link_to_markdown_field' do 191 let(:link) { '/commits/0a1b2c3d' } 192 let(:issues) { create_list(:issue, 2, project: project) } 193 194 # Clean the cache to make sure the title is re-rendered from the stubbed one 195 it 'handles references nested in links with all the text', :clean_gitlab_redis_cache do 196 allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real") 197 198 actual = helper.link_to_markdown_field(commit, :title, link) 199 doc = Nokogiri::HTML.parse(actual) 200 201 # Make sure we didn't create invalid markup 202 expect(doc.errors).to be_empty 203 204 # Leading commit link 205 expect(doc.css('a')[0].attr('href')).to eq link 206 expect(doc.css('a')[0].text).to eq 'This should finally fix ' 207 208 # First issue link 209 expect(doc.css('a')[1].attr('href')) 210 .to eq urls.project_issue_path(project, issues[0]) 211 expect(doc.css('a')[1].text).to eq issues[0].to_reference 212 213 # Internal commit link 214 expect(doc.css('a')[2].attr('href')).to eq link 215 expect(doc.css('a')[2].text).to eq ' and ' 216 217 # Second issue link 218 expect(doc.css('a')[3].attr('href')) 219 .to eq urls.project_issue_path(project, issues[1]) 220 expect(doc.css('a')[3].text).to eq issues[1].to_reference 221 222 # Trailing commit link 223 expect(doc.css('a')[4].attr('href')).to eq link 224 expect(doc.css('a')[4].text).to eq ' for real' 225 end 226 end 227 228 describe '#link_to_markdown' do 229 let(:link) { '/commits/0a1b2c3d' } 230 let(:issues) { create_list(:issue, 2, project: project) } 231 232 it 'handles references nested in links with all the text' do 233 actual = helper.link_to_markdown("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link) 234 doc = Nokogiri::HTML.parse(actual) 235 236 # Make sure we didn't create invalid markup 237 expect(doc.errors).to be_empty 238 239 # Leading commit link 240 expect(doc.css('a')[0].attr('href')).to eq link 241 expect(doc.css('a')[0].text).to eq 'This should finally fix ' 242 243 # First issue link 244 expect(doc.css('a')[1].attr('href')) 245 .to eq urls.project_issue_path(project, issues[0]) 246 expect(doc.css('a')[1].text).to eq issues[0].to_reference 247 248 # Internal commit link 249 expect(doc.css('a')[2].attr('href')).to eq link 250 expect(doc.css('a')[2].text).to eq ' and ' 251 252 # Second issue link 253 expect(doc.css('a')[3].attr('href')) 254 .to eq urls.project_issue_path(project, issues[1]) 255 expect(doc.css('a')[3].text).to eq issues[1].to_reference 256 257 # Trailing commit link 258 expect(doc.css('a')[4].attr('href')).to eq link 259 expect(doc.css('a')[4].text).to eq ' for real' 260 end 261 262 it 'forwards HTML options' do 263 actual = helper.link_to_markdown("Fixed in #{commit.id}", link, class: 'foo') 264 doc = Nokogiri::HTML.parse(actual) 265 266 expect(doc.css('a')).to satisfy do |v| 267 # 'foo' gets added to all links 268 v.all? { |a| a.attr('class').match(/foo$/) } 269 end 270 end 271 272 it "escapes HTML passed in as the body" do 273 actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}" 274 expect(helper.link_to_markdown(actual, link)) 275 .to match('<h1>test</h1>') 276 end 277 278 it 'ignores reference links when they are the entire body' do 279 text = issues[0].to_reference 280 act = helper.link_to_markdown(text, '/foo') 281 expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>) 282 end 283 284 it 'replaces commit message with emoji to link' do 285 actual = link_to_markdown(':book: Book', '/foo') 286 expect(actual) 287 .to eq '<a href="/foo"><gl-emoji title="open book" data-name="book" data-unicode-version="6.0"></gl-emoji></a><a href="/foo"> Book</a>' 288 end 289 end 290 291 describe '#link_to_html' do 292 it 'wraps the rendered content in a link' do 293 link = '/commits/0a1b2c3d' 294 issue = create(:issue, project: project) 295 296 rendered = helper.markdown("This should finally fix #{issue.to_reference} for real", pipeline: :single_line) 297 doc = Nokogiri::HTML.parse(rendered) 298 299 expect(doc.css('a')[0].attr('href')) 300 .to eq urls.project_issue_path(project, issue) 301 expect(doc.css('a')[0].text).to eq issue.to_reference 302 303 wrapped = helper.link_to_html(rendered, link) 304 doc = Nokogiri::HTML.parse(wrapped) 305 306 expect(doc.css('a')[0].attr('href')).to eq link 307 expect(doc.css('a')[0].text).to eq 'This should finally fix ' 308 end 309 310 it "escapes HTML passed as an emoji" do 311 rendered = '<gl-emoji><div class="test">test</div></gl-emoji>' 312 expect(helper.link_to_html(rendered, '/foo')) 313 .to eq '<a href="/foo"><gl-emoji><div class="test">test</div></gl-emoji></a>' 314 end 315 end 316 317 describe '#render_wiki_content' do 318 let(:wiki) { double('WikiPage', path: "file.#{extension}") } 319 let(:wiki_repository) { double('Repository') } 320 let(:content) { 'wiki content' } 321 let(:context) do 322 { 323 pipeline: :wiki, project: project, wiki: wiki, 324 page_slug: 'nested/page', issuable_reference_expansion_enabled: true, 325 repository: wiki_repository 326 } 327 end 328 329 before do 330 expect(wiki).to receive(:content).and_return(content) 331 expect(wiki).to receive(:slug).and_return('nested/page') 332 expect(wiki).to receive(:repository).and_return(wiki_repository) 333 allow(wiki).to receive(:container).and_return(project) 334 335 helper.instance_variable_set(:@wiki, wiki) 336 end 337 338 context 'when file is Markdown' do 339 let(:extension) { 'md' } 340 341 it 'renders using #markdown_unsafe helper method' do 342 expect(helper).to receive(:markdown_unsafe).with('wiki content', context) 343 344 helper.render_wiki_content(wiki) 345 end 346 347 context 'when context has labels' do 348 let_it_be(:label) { create(:label, title: 'Bug', project: project) } 349 350 let(:content) { '~Bug' } 351 352 it 'renders label' do 353 result = helper.render_wiki_content(wiki) 354 doc = Nokogiri::HTML.parse(result) 355 356 expect(doc.css('.gl-label-link')).not_to be_empty 357 end 358 end 359 360 context 'when content has uploads' do 361 let(:upload_link) { '/uploads/test.png' } 362 let(:content) { "![ImageTest](#{upload_link})" } 363 364 before do 365 allow(wiki).to receive(:wiki_base_path).and_return(project.wiki.wiki_base_path) 366 end 367 368 it 'renders uploads relative to project' do 369 result = helper.render_wiki_content(wiki) 370 371 expect(result).to include("#{project.full_path}#{upload_link}") 372 end 373 end 374 end 375 376 context 'when file is Asciidoc' do 377 let(:extension) { 'adoc' } 378 379 it 'renders using Gitlab::Asciidoc' do 380 expect(Gitlab::Asciidoc).to receive(:render) 381 382 helper.render_wiki_content(wiki) 383 end 384 end 385 386 context 'when file is Kramdown' do 387 let(:extension) { 'rmd' } 388 let(:content) do 389 <<-EOF 390{::options parse_block_html="true" /} 391 392<div> 393FooBar 394</div> 395 EOF 396 end 397 398 it 'renders using #markdown_unsafe helper method' do 399 expect(helper).to receive(:markdown_unsafe).with(content, context) 400 401 result = helper.render_wiki_content(wiki) 402 403 expect(result).to be_empty 404 end 405 end 406 407 context 'any other format' do 408 let(:extension) { 'foo' } 409 410 it 'renders all other formats using Gitlab::OtherMarkup' do 411 expect(Gitlab::OtherMarkup).to receive(:render) 412 413 helper.render_wiki_content(wiki) 414 end 415 end 416 end 417 418 describe '#markup' do 419 let(:content) { 'Noël' } 420 421 it 'sets the :text_source to :blob in the context' do 422 context = {} 423 helper.markup('foo.md', content, context) 424 425 expect(context).to include(text_source: :blob) 426 end 427 428 it 'preserves encoding' do 429 expect(content.encoding.name).to eq('UTF-8') 430 expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8') 431 end 432 433 it 'delegates to #markdown_unsafe when file name corresponds to Markdown' do 434 expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true) 435 expect(helper).to receive(:markdown_unsafe).and_return('NOEL') 436 437 expect(helper.markup('foo.md', content)).to eq('NOEL') 438 end 439 440 it 'delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc' do 441 expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true) 442 expect(helper).to receive(:asciidoc_unsafe).and_return('NOEL') 443 444 expect(helper.markup('foo.adoc', content)).to eq('NOEL') 445 end 446 447 it 'uses passed in rendered content' do 448 expect(helper).not_to receive(:gitlab_markdown?) 449 expect(helper).not_to receive(:markdown_unsafe) 450 451 expect(helper.markup('foo.md', content, rendered: '<p>NOEL</p>')).to eq('<p>NOEL</p>') 452 end 453 454 it 'defaults to CommonMark' do 455 expect(helper.markup('foo.md', 'x^2')).to include('x^2') 456 end 457 end 458 459 describe '#markup_unsafe' do 460 subject { helper.markup_unsafe(file_name, text, context) } 461 462 let_it_be(:project_base) { create(:project, :repository) } 463 let_it_be(:context) { { project: project_base } } 464 465 let(:file_name) { 'foo.bar' } 466 let(:text) { 'Noël' } 467 468 context 'when text is missing' do 469 let(:text) { nil } 470 471 it 'returns an empty string' do 472 is_expected.to eq('') 473 end 474 end 475 476 context 'when file is a markdown file' do 477 let(:file_name) { 'foo.md' } 478 479 it 'returns html (rendered by Banzai)' do 480 expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>' 481 482 expect(Banzai).to receive(:render).with(text, context) { expected_html } 483 484 is_expected.to eq(expected_html) 485 end 486 487 context 'when renderer returns an error' do 488 before do 489 allow(Banzai).to receive(:render).and_raise(StandardError, "An error") 490 end 491 492 it 'returns html (rendered by ActionView:TextHelper)' do 493 is_expected.to eq('<p>Noël</p>') 494 end 495 496 it 'logs the error' do 497 expect(Gitlab::ErrorTracking).to receive(:track_exception).with( 498 instance_of(StandardError), 499 project_id: project.id, file_name: 'foo.md' 500 ) 501 502 subject 503 end 504 end 505 end 506 507 context 'when file is asciidoc file' do 508 let(:file_name) { 'foo.adoc' } 509 510 it 'returns html (rendered by Gitlab::Asciidoc)' do 511 expected_html = "<div>\n<p>Noël</p>\n</div>" 512 513 expect(Gitlab::Asciidoc).to receive(:render).with(text, context) { expected_html } 514 515 is_expected.to eq(expected_html) 516 end 517 end 518 519 context 'when file is a regular text file' do 520 let(:file_name) { 'foo.txt' } 521 522 it 'returns html (rendered by ActionView::TagHelper)' do 523 is_expected.to eq('<pre class="plain-readme">Noël</pre>') 524 end 525 end 526 527 context 'when file has an unknown type' do 528 let(:file_name) { 'foo.tex' } 529 530 it 'returns html (rendered by Gitlab::OtherMarkup)' do 531 expected_html = 'Noël' 532 533 expect(Gitlab::OtherMarkup).to receive(:render).with(file_name, text, context) { expected_html } 534 535 is_expected.to eq(expected_html) 536 end 537 end 538 end 539 540 describe '#first_line_in_markdown' do 541 shared_examples_for 'common markdown examples' do 542 let(:project_base) { build(:project, :repository) } 543 544 it 'displays inline code' do 545 object = create_object('Text with `inline code`') 546 expected = 'Text with <code>inline code</code>' 547 548 expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected) 549 end 550 551 it 'truncates the text with multiple paragraphs' do 552 object = create_object("Paragraph 1\n\nParagraph 2") 553 expected = 'Paragraph 1...' 554 555 expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected) 556 end 557 558 it 'displays the first line of a code block' do 559 object = create_object("```\nCode block\nwith two lines\n```") 560 expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span>\n</code></pre>} 561 562 expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected) 563 end 564 565 it 'truncates a single long line of text' do 566 text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars 567 object = create_object(text * 4) 568 expected = (text * 2).sub(/.{3}/, '...') 569 570 expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(expected) 571 end 572 573 it 'preserves a link href when link text is truncated' do 574 text = 'The quick brown fox jumped over the lazy dog' # 44 chars 575 link_url = 'http://example.com/foo/bar/baz' # 30 chars 576 input = "#{text}#{text}#{text} #{link_url}" # 163 chars 577 expected_link_text = 'http://example...</a>' 578 579 object = create_object(input) 580 581 expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(link_url) 582 expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(expected_link_text) 583 end 584 585 it 'preserves code color scheme' do 586 object = create_object("```ruby\ndef test\n 'hello world'\nend\n```") 587 expected = "\n<pre class=\"code highlight js-syntax-highlight language-ruby\">" \ 588 "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \ 589 "</code></pre>\n" 590 591 expect(first_line_in_markdown(object, attribute, 150, project: project)).to eq(expected) 592 end 593 594 context 'when images are allowed' do 595 it 'preserves data-src for lazy images' do 596 object = create_object("![ImageTest](/uploads/test.png)") 597 image_url = "data-src=\".*/uploads/test.png\"" 598 text = first_line_in_markdown(object, attribute, 150, project: project, allow_images: true) 599 600 expect(text).to match(image_url) 601 expect(text).to match('<a') 602 end 603 end 604 605 context 'when images are not allowed' do 606 it 'removes any images' do 607 object = create_object("![ImageTest](/uploads/test.png)") 608 text = first_line_in_markdown(object, attribute, 150, project: project) 609 610 expect(text).not_to match('<img') 611 expect(text).not_to match('<a') 612 end 613 end 614 615 context 'labels formatting' do 616 let(:label_title) { 'this should be ~label_1' } 617 618 def create_and_format_label(project) 619 create(:label, title: 'label_1', project: project) 620 object = create_object(label_title, project: project) 621 622 first_line_in_markdown(object, attribute, 150, project: project) 623 end 624 625 it 'preserves style attribute for a label that can be accessed by current_user' do 626 project = create(:project, :public) 627 label = create_and_format_label(project) 628 629 expect(label).to match(/span class=.*style=.*/) 630 expect(label).to include('data-html="true"') 631 end 632 633 it 'does not style a label that can not be accessed by current_user' do 634 project = create(:project, :private) 635 label = create_and_format_label(project) 636 637 expect(label).to include("~label_1") 638 expect(label).not_to match(/span class=.*style=.*/) 639 end 640 end 641 642 it 'keeps whitelisted tags' do 643 html = '<a><i></i></a> <strong>strong</strong><em>em</em><b>b</b>' 644 645 object = create_object(html) 646 result = first_line_in_markdown(object, attribute, 100, project: project) 647 648 expect(result).to include(html) 649 end 650 651 it 'truncates Markdown properly' do 652 object = create_object("@#{user.username}, can you look at this?\nHello world\n") 653 actual = first_line_in_markdown(object, attribute, 100, project: project) 654 655 doc = Nokogiri::HTML.parse(actual) 656 657 # Make sure we didn't create invalid markup 658 expect(doc.errors).to be_empty 659 660 # Leading user link 661 expect(doc.css('a').length).to eq(1) 662 expect(doc.css('a')[0].attr('href')).to eq user_path(user) 663 expect(doc.css('a')[0].text).to eq "@#{user.username}" 664 665 expect(doc.content).to eq "@#{user.username}, can you look at this?..." 666 end 667 668 it 'truncates Markdown with emoji properly' do 669 object = create_object("foo :wink:\nbar :grinning:") 670 actual = first_line_in_markdown(object, attribute, 100, project: project) 671 672 doc = Nokogiri::HTML.parse(actual) 673 674 # Make sure we didn't create invalid markup 675 # But also account for the 2 errors caused by the unknown `gl-emoji` elements 676 expect(doc.errors.length).to eq(2) 677 678 expect(doc.css('gl-emoji').length).to eq(2) 679 expect(doc.css('gl-emoji')[0].attr('data-name')).to eq 'wink' 680 expect(doc.css('gl-emoji')[1].attr('data-name')).to eq 'grinning' 681 682 expect(doc.content).to eq "foo \nbar " 683 end 684 685 it 'does not post-process truncated text', :request_store do 686 object = create_object("hello \n\n [Test](README.md)") 687 688 expect do 689 first_line_in_markdown(object, attribute, nil, project: project) 690 end.not_to change { Gitlab::GitalyClient.get_request_count } 691 end 692 end 693 694 context 'when the asked attribute can be redacted' do 695 include_examples 'common markdown examples' do 696 let(:attribute) { :note } 697 def create_object(title, project: project_base) 698 build(:note, note: title, project: project) 699 end 700 end 701 end 702 703 context 'when the asked attribute can not be redacted' do 704 include_examples 'common markdown examples' do 705 let(:attribute) { :body } 706 def create_object(title, project: project_base) 707 issue = build(:issue, title: title) 708 build(:todo, :done, project: project_base, author: user, target: issue) 709 end 710 end 711 end 712 end 713 714 describe '#cross_project_reference' do 715 it 'shows the full MR reference' do 716 expect(helper.cross_project_reference(project, merge_request)).to include(project.full_path) 717 end 718 719 it 'shows the full issue reference' do 720 expect(helper.cross_project_reference(project, issue)).to include(project.full_path) 721 end 722 end 723 724 def urls 725 Gitlab::Routing.url_helpers 726 end 727end 728