BigW Consortium Gitlab

markup_helper_spec.rb 14.4 KB
Newer Older
1
require 'spec_helper'
Riyad Preukschas committed
2

3
describe MarkupHelper do
4
  let!(:project) { create(:project, :repository) }
5

6
  let(:user)          { create(:user, username: 'gfm') }
7
  let(:commit)        { project.commit }
8
  let(:issue)         { create(:issue, project: project) }
9
  let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
Andrew8xx8 committed
10
  let(:snippet)       { create(:project_snippet, project: project) }
11

Riyad Preukschas committed
12
  before do
13
    # Ensure the generated reference links aren't redacted
14
    project.add_master(user)
15

16
    # Helper expects a @project instance variable
17 18 19 20
    helper.instance_variable_set(:@project, project)

    # Stub the `current_user` helper
    allow(helper).to receive(:current_user).and_return(user)
Riyad Preukschas committed
21 22
  end

23
  describe "#markdown" do
24
    describe "referencing multiple objects" do
25
      let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
26

27
      it "links to the merge request" do
28
        expected = project_merge_request_path(project, merge_request)
29
        expect(helper.markdown(actual)).to match(expected)
Riyad Preukschas committed
30 31
      end

32
      it "links to the commit" do
33
        expected = project_commit_path(project, commit)
34
        expect(helper.markdown(actual)).to match(expected)
Riyad Preukschas committed
35 36
      end

37
      it "links to the issue" do
38
        expected = project_issue_path(project, issue)
39
        expect(helper.markdown(actual)).to match(expected)
Riyad Preukschas committed
40 41
      end
    end
42 43 44

    describe "override default project" do
      let(:actual) { issue.to_reference }
45
      let(:second_project) { create(:project, :public) }
46 47
      let(:second_issue) { create(:issue, project: second_project) }

48
      it 'links to the issue' do
49
        expected = project_issue_path(second_project, second_issue)
50 51 52
        expect(markdown(actual, project: second_project)).to match(expected)
      end
    end
53
  end
Riyad Preukschas committed
54

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
  describe '#markdown_field' do
    let(:attribute) { :title }

    describe 'with already redacted attribute' do
      it 'returns the redacted attribute' do
        commit.redacted_title_html = 'commit title'

        expect(Banzai).not_to receive(:render_field)

        expect(helper.markdown_field(commit, attribute)).to eq('commit title')
      end
    end

    describe 'without redacted attribute' do
      it 'renders the markdown value' do
70
        expect(Banzai).to receive(:render_field).with(commit, attribute, {}).and_call_original
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

        helper.markdown_field(commit, attribute)
      end
    end
  end

  describe '#link_to_markdown_field' do
    let(:link)    { '/commits/0a1b2c3d' }
    let(:issues)  { create_list(:issue, 2, project: project) }

    it 'handles references nested in links with all the text' do
      allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real")

      actual = helper.link_to_markdown_field(commit, :title, link)
      doc = Nokogiri::HTML.parse(actual)

      # Make sure we didn't create invalid markup
      expect(doc.errors).to be_empty

      # Leading commit link
      expect(doc.css('a')[0].attr('href')).to eq link
      expect(doc.css('a')[0].text).to eq 'This should finally fix '

      # First issue link
      expect(doc.css('a')[1].attr('href'))
        .to eq project_issue_path(project, issues[0])
      expect(doc.css('a')[1].text).to eq issues[0].to_reference

      # Internal commit link
      expect(doc.css('a')[2].attr('href')).to eq link
      expect(doc.css('a')[2].text).to eq ' and '

      # Second issue link
      expect(doc.css('a')[3].attr('href'))
        .to eq project_issue_path(project, issues[1])
      expect(doc.css('a')[3].text).to eq issues[1].to_reference

      # Trailing commit link
      expect(doc.css('a')[4].attr('href')).to eq link
      expect(doc.css('a')[4].text).to eq ' for real'
    end
  end

  describe '#link_to_markdown' do
115
    let(:link)    { '/commits/0a1b2c3d' }
116
    let(:issues)  { create_list(:issue, 2, project: project) }
Riyad Preukschas committed
117

118
    it 'handles references nested in links with all the text' do
119
      actual = helper.link_to_markdown("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link)
120
      doc = Nokogiri::HTML.parse(actual)
Riyad Preukschas committed
121

122 123 124
      # Make sure we didn't create invalid markup
      expect(doc.errors).to be_empty

125
      # Leading commit link
126
      expect(doc.css('a')[0].attr('href')).to eq link
127
      expect(doc.css('a')[0].text).to eq 'This should finally fix '
Riyad Preukschas committed
128

129
      # First issue link
130
      expect(doc.css('a')[1].attr('href'))
131
        .to eq project_issue_path(project, issues[0])
132
      expect(doc.css('a')[1].text).to eq issues[0].to_reference
133

134
      # Internal commit link
135
      expect(doc.css('a')[2].attr('href')).to eq link
136
      expect(doc.css('a')[2].text).to eq ' and '
137

138
      # Second issue link
139
      expect(doc.css('a')[3].attr('href'))
140
        .to eq project_issue_path(project, issues[1])
141
      expect(doc.css('a')[3].text).to eq issues[1].to_reference
142 143

      # Trailing commit link
144
      expect(doc.css('a')[4].attr('href')).to eq link
145
      expect(doc.css('a')[4].text).to eq ' for real'
146 147
    end

148
    it 'forwards HTML options' do
149
      actual = helper.link_to_markdown("Fixed in #{commit.id}", link, class: 'foo')
150 151 152 153 154 155 156 157
      doc = Nokogiri::HTML.parse(actual)

      expect(doc.css('a')).to satisfy do |v|
        # 'foo' gets added to all links
        v.all? { |a| a.attr('class').match(/foo$/) }
      end
    end

158
    it "escapes HTML passed in as the body" do
159
      actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
160
      expect(helper.link_to_markdown(actual, link))
161
        .to match('&lt;h1&gt;test&lt;/h1&gt;')
162
    end
163 164 165

    it 'ignores reference links when they are the entire body' do
      text = issues[0].to_reference
166
      act = helper.link_to_markdown(text, '/foo')
167 168
      expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
    end
SAKATA Sinji committed
169

170
    it 'replaces commit message with emoji to link' do
171
      actual = link_to_markdown(':book: Book', '/foo')
172 173
      expect(actual)
        .to eq '<gl-emoji title="open book" data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>'
SAKATA Sinji committed
174
    end
175
  end
176

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
  describe '#link_to_html' do
    it 'wraps the rendered content in a link' do
      link = '/commits/0a1b2c3d'
      issue = create(:issue, project: project)

      rendered = helper.markdown("This should finally fix #{issue.to_reference} for real", pipeline: :single_line)
      doc = Nokogiri::HTML.parse(rendered)

      expect(doc.css('a')[0].attr('href'))
        .to eq project_issue_path(project, issue)
      expect(doc.css('a')[0].text).to eq issue.to_reference

      wrapped = helper.link_to_html(rendered, link)
      doc = Nokogiri::HTML.parse(wrapped)

      expect(doc.css('a')[0].attr('href')).to eq link
      expect(doc.css('a')[0].text).to eq 'This should finally fix '
    end
  end

197
  describe '#render_wiki_content' do
198
    before do
skv committed
199
      @wiki = double('WikiPage')
200
      allow(@wiki).to receive(:content).and_return('wiki content')
201
      allow(@wiki).to receive(:slug).and_return('nested/page')
202
      helper.instance_variable_set(:@project_wiki, @wiki)
203 204
    end

205
    it "uses Wiki pipeline for markdown files" do
206
      allow(@wiki).to receive(:format).and_return(:markdown)
207

208
      expect(helper).to receive(:markdown_unsafe).with('wiki content', pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page", issuable_state_filter_enabled: true)
209 210 211 212

      helper.render_wiki_content(@wiki)
    end

213
    it "uses Asciidoctor for asciidoc files" do
214 215
      allow(@wiki).to receive(:format).and_return(:asciidoc)

Douwe Maan committed
216
      expect(helper).to receive(:asciidoc_unsafe).with('wiki content')
217 218 219 220

      helper.render_wiki_content(@wiki)
    end

221
    it "uses the Gollum renderer for all other file types" do
222
      allow(@wiki).to receive(:format).and_return(:rdoc)
skv committed
223
      formatted_content_stub = double('formatted_content')
224 225
      expect(formatted_content_stub).to receive(:html_safe)
      allow(@wiki).to receive(:formatted_content).and_return(formatted_content_stub)
226 227 228 229

      helper.render_wiki_content(@wiki)
    end
  end
230

Douwe Maan committed
231
  describe 'markup' do
232 233 234 235
    let(:content) { 'Noël' }

    it 'preserves encoding' do
      expect(content.encoding.name).to eq('UTF-8')
Douwe Maan committed
236
      expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8')
237 238
    end

Douwe Maan committed
239
    it "delegates to #markdown_unsafe when file name corresponds to Markdown" do
240
      expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
Douwe Maan committed
241
      expect(helper).to receive(:markdown_unsafe).and_return('NOEL')
242

Douwe Maan committed
243
      expect(helper.markup('foo.md', content)).to eq('NOEL')
244 245
    end

Douwe Maan committed
246
    it "delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc" do
247
      expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true)
Douwe Maan committed
248
      expect(helper).to receive(:asciidoc_unsafe).and_return('NOEL')
249

Douwe Maan committed
250
      expect(helper.markup('foo.adoc', content)).to eq('NOEL')
251 252 253
    end
  end

254
  describe '#first_line_in_markdown' do
255 256
    shared_examples_for 'common markdown examples' do
      let(:project_base) { build(:project, :repository) }
257

258 259 260
      it 'displays inline code' do
        object = create_object('Text with `inline code`')
        expected = 'Text with <code>inline code</code>'
261

262 263
        expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected)
      end
264

265 266 267
      it 'truncates the text with multiple paragraphs' do
        object = create_object("Paragraph 1\n\nParagraph 2")
        expected = 'Paragraph 1...'
268

269 270
        expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected)
      end
271

272 273 274
      it 'displays the first line of a code block' do
        object = create_object("```\nCode block\nwith two lines\n```")
        expected = %r{<pre.+><code><span class="line">Code block\.\.\.</span>\n</code></pre>}
275

276 277
        expect(first_line_in_markdown(object, attribute, 100, project: project)).to match(expected)
      end
278

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
      it 'truncates a single long line of text' do
        text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
        object = create_object(text * 4)
        expected = (text * 2).sub(/.{3}/, '...')

        expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(expected)
      end

      it 'preserves a link href when link text is truncated' do
        text = 'The quick brown fox jumped over the lazy dog' # 44 chars
        input = "#{text}#{text}#{text} " # 133 chars
        link_url = 'http://example.com/foo/bar/baz' # 30 chars
        input << link_url
        object = create_object(input)
        expected_link_text = 'http://example...</a>'

        expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(link_url)
        expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(expected_link_text)
      end

      it 'preserves code color scheme' do
        object = create_object("```ruby\ndef test\n  'hello world'\nend\n```")
        expected = "\n<pre class=\"code highlight js-syntax-highlight ruby\">" \
          "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \
          "</code></pre>"

        expect(first_line_in_markdown(object, attribute, 150, project: project)).to eq(expected)
      end

      it 'preserves data-src for lazy images' do
        object = create_object("![ImageTest](/uploads/test.png)")
        image_url = "data-src=\".*/uploads/test.png\""

        expect(first_line_in_markdown(object, attribute, 150, project: project)).to match(image_url)
      end

      context 'labels formatting' do
        let(:label_title) { 'this should be ~label_1' }

        def create_and_format_label(project)
          create(:label, title: 'label_1', project: project)
          object = create_object(label_title, project: project)
321

322 323
          first_line_in_markdown(object, attribute, 150, project: project)
        end
324

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
        it 'preserves style attribute for a label that can be accessed by current_user' do
          project = create(:project, :public)

          expect(create_and_format_label(project)).to match(/span class=.*style=.*/)
        end

        it 'does not style a label that can not be accessed by current_user' do
          project = create(:project, :private)

          expect(create_and_format_label(project)).to eq("<p>#{label_title}</p>")
        end
      end

      it 'truncates Markdown properly' do
        object = create_object("@#{user.username}, can you look at this?\nHello world\n")
        actual = first_line_in_markdown(object, attribute, 100, project: project)

        doc = Nokogiri::HTML.parse(actual)

        # Make sure we didn't create invalid markup
        expect(doc.errors).to be_empty

        # Leading user link
        expect(doc.css('a').length).to eq(1)
        expect(doc.css('a')[0].attr('href')).to eq user_path(user)
        expect(doc.css('a')[0].text).to eq "@#{user.username}"

        expect(doc.content).to eq "@#{user.username}, can you look at this?..."
      end

      it 'truncates Markdown with emoji properly' do
        object = create_object("foo :wink:\nbar :grinning:")
        actual = first_line_in_markdown(object, attribute, 100, project: project)

        doc = Nokogiri::HTML.parse(actual)

        # Make sure we didn't create invalid markup
        # But also account for the 2 errors caused by the unknown `gl-emoji` elements
        expect(doc.errors.length).to eq(2)

        expect(doc.css('gl-emoji').length).to eq(2)
        expect(doc.css('gl-emoji')[0].attr('data-name')).to eq 'wink'
        expect(doc.css('gl-emoji')[1].attr('data-name')).to eq 'grinning'

        expect(doc.content).to eq "foo 😉\nbar 😀"
      end
    end

    context 'when the asked attribute can be redacted' do
      include_examples 'common markdown examples' do
        let(:attribute) { :note }
        def create_object(title, project: project_base)
          build(:note, note: title, project: project)
        end
      end
    end

    context 'when the asked attribute can not be redacted' do
      include_examples 'common markdown examples' do
        let(:attribute) { :body }
        def create_object(title, project: project_base)
          issue = build(:issue, title: title)
          build(:todo, :done, project: project_base, author: user, target: issue)
        end
      end
390
    end
391
  end
392 393 394

  describe '#cross_project_reference' do
    it 'shows the full MR reference' do
395
      expect(helper.cross_project_reference(project, merge_request)).to include(project.full_path)
396 397 398
    end

    it 'shows the full issue reference' do
399
      expect(helper.cross_project_reference(project, issue)).to include(project.full_path)
400 401
    end
  end
Riyad Preukschas committed
402
end