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('&lt;h1&gt;test&lt;/h1&gt;')
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>&lt;div class="test"&gt;test&lt;/div&gt;</gl-emoji>'
312      expect(helper.link_to_html(rendered, '/foo'))
313        .to eq '<a href="/foo"><gl-emoji>&lt;div class="test"&gt;test&lt;/div&gt;</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