gitlab_markdown_helper_spec.rb 12.7 KB
Newer Older
Riyad Preukschas's avatar
Riyad Preukschas committed
1 2
require "spec_helper"

randx's avatar
randx committed
3
describe GitlabMarkdownHelper do
4
  include ApplicationHelper
5
  include IssuesHelper
6

Robert Speicher's avatar
Robert Speicher committed
7 8
  let!(:project) { create(:project) }

9
  let(:user)          { create(:user, username: 'gfm') }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
10
  let(:commit)        { CommitDecorator.decorate(project.repository.commit) }
Robert Speicher's avatar
Robert Speicher committed
11 12 13 14 15
  let(:issue)         { create(:issue, project: project) }
  let(:merge_request) { create(:merge_request, project: project) }
  let(:snippet)       { create(:snippet, project: project) }
  let(:member)        { project.users_projects.where(user_id: user).first }

Riyad Preukschas's avatar
Riyad Preukschas committed
16
  before do
Robert Speicher's avatar
Robert Speicher committed
17 18
    # Helper expects a @project instance variable
    @project = project
Riyad Preukschas's avatar
Riyad Preukschas committed
19 20 21
  end

  describe "#gfm" do
Robert Speicher's avatar
Robert Speicher committed
22 23 24 25 26
    it "should return unaltered text if project is nil" do
      actual = "Testing references: ##{issue.id}"

      gfm(actual).should_not == actual

27
      @project = nil
Robert Speicher's avatar
Robert Speicher committed
28 29
      gfm(actual).should == actual
    end
30

Robert Speicher's avatar
Robert Speicher committed
31 32 33 34 35 36
    it "should not alter non-references" do
      actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do."
      gfm(actual).should == expected
    end

    it "should not touch HTML entities" do
37
      @project.issues.stub(:where).with(id: '39').and_return([issue])
Robert Speicher's avatar
Robert Speicher committed
38 39 40 41 42 43
      actual = expected = "We'll accept good pull requests."
      gfm(actual).should == expected
    end

    it "should forward HTML options to links" do
      gfm("Fixed in #{commit.id}", class: "foo").should have_selector("a.gfm.foo")
44 45
    end

Riyad Preukschas's avatar
Riyad Preukschas committed
46
    describe "referencing a commit" do
Robert Speicher's avatar
Robert Speicher committed
47 48
      let(:expected) { project_commit_path(project, commit) }

Riyad Preukschas's avatar
Riyad Preukschas committed
49
      it "should link using a full id" do
Robert Speicher's avatar
Robert Speicher committed
50 51
        actual = "Reverts #{commit.id}"
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
52 53 54
      end

      it "should link using a short id" do
Robert Speicher's avatar
Robert Speicher committed
55 56 57 58 59 60 61
        actual = "Backported from #{commit.short_id(6)}"
        gfm(actual).should match(expected)
      end

      it "should link with adjacent text" do
        actual = "Reverted (see #{commit.id})"
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
62 63
      end

Robert Speicher's avatar
Robert Speicher committed
64 65 66 67
      it "should keep whitespace intact" do
        actual   = "Changes #{commit.id} dramatically"
        expected = /Changes <a.+>#{commit.id}<\/a> dramatically/
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
68 69 70
      end

      it "should not link with an invalid id" do
Robert Speicher's avatar
Robert Speicher committed
71 72 73 74 75 76 77 78 79 80 81 82
        actual = expected = "What happened in #{commit.id.reverse}"
        gfm(actual).should == expected
      end

      it "should include a title attribute" do
        actual = "Reverts #{commit.id}"
        gfm(actual).should match(/title="#{commit.link_title}"/)
      end

      it "should include standard gfm classes" do
        actual = "Reverts #{commit.id}"
        gfm(actual).should match(/class="\s?gfm gfm-commit\s?"/)
Riyad Preukschas's avatar
Riyad Preukschas committed
83 84 85 86
      end
    end

    describe "referencing a team member" do
87
      let(:actual)   { "@#{user.username} you are right." }
Robert Speicher's avatar
Robert Speicher committed
88
      let(:expected) { project_team_member_path(project, member) }
Riyad Preukschas's avatar
Riyad Preukschas committed
89

Robert Speicher's avatar
Robert Speicher committed
90
      before do
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
91
        project.team << [user, :master]
Riyad Preukschas's avatar
Riyad Preukschas committed
92 93
      end

Robert Speicher's avatar
Robert Speicher committed
94 95 96
      it "should link using a simple name" do
        gfm(actual).should match(expected)
      end
Riyad Preukschas's avatar
Riyad Preukschas committed
97

Robert Speicher's avatar
Robert Speicher committed
98 99 100
      it "should link using a name with dots" do
        user.update_attributes(name: "alphA.Beta")
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
101 102 103
      end

      it "should link using name with underscores" do
Robert Speicher's avatar
Robert Speicher committed
104 105
        user.update_attributes(name: "ping_pong_king")
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
106 107
      end

Robert Speicher's avatar
Robert Speicher committed
108
      it "should link with adjacent text" do
109
        actual = "Mail the admin (@#{user.username})"
Robert Speicher's avatar
Robert Speicher committed
110 111
        gfm(actual).should match(expected)
      end
Riyad Preukschas's avatar
Riyad Preukschas committed
112

Robert Speicher's avatar
Robert Speicher committed
113
      it "should keep whitespace intact" do
114 115
        actual   = "Yes, @#{user.username} is right."
        expected = /Yes, <a.+>@#{user.username}<\/a> is right/
Robert Speicher's avatar
Robert Speicher committed
116
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
117 118
      end

Robert Speicher's avatar
Robert Speicher committed
119
      it "should not link with an invalid id" do
120
        actual = expected = "@#{user.username.reverse} you are right."
Robert Speicher's avatar
Robert Speicher committed
121
        gfm(actual).should == expected
Riyad Preukschas's avatar
Riyad Preukschas committed
122 123
      end

Robert Speicher's avatar
Robert Speicher committed
124 125
      it "should include standard gfm classes" do
        gfm(actual).should match(/class="\s?gfm gfm-team_member\s?"/)
Riyad Preukschas's avatar
Riyad Preukschas committed
126 127 128
      end
    end

Robert Speicher's avatar
Robert Speicher committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142
    # Shared examples for referencing an object
    #
    # Expects the following attributes to be available in the example group:
    #
    # - object    - The object itself
    # - reference - The object reference string (e.g., #1234, $1234, !1234)
    #
    # Currently limited to Snippets, Issues and MergeRequests
    shared_examples 'referenced object' do
      let(:actual)   { "Reference to #{reference}" }
      let(:expected) { polymorphic_path([project, object]) }

      it "should link using a valid id" do
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
143 144
      end

Robert Speicher's avatar
Robert Speicher committed
145 146 147
      it "should link with adjacent text" do
        # Wrap the reference in parenthesis
        gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
148

Robert Speicher's avatar
Robert Speicher committed
149 150
        # Append some text to the end of the reference
        gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
151 152
      end

Robert Speicher's avatar
Robert Speicher committed
153 154 155 156
      it "should keep whitespace intact" do
        actual   = "Referenced #{reference} already."
        expected = /Referenced <a.+>[^\s]+<\/a> already/
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
157 158
      end

Robert Speicher's avatar
Robert Speicher committed
159 160 161 162
      it "should not link with an invalid id" do
        # Modify the reference string so it's still parsed, but is invalid
        reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
        gfm(actual).should == actual
Riyad Preukschas's avatar
Riyad Preukschas committed
163 164
      end

Robert Speicher's avatar
Robert Speicher committed
165 166 167
      it "should include a title attribute" do
        title = "#{object.class.to_s.titlecase}: #{object.title}"
        gfm(actual).should match(/title="#{title}"/)
Riyad Preukschas's avatar
Riyad Preukschas committed
168 169
      end

Robert Speicher's avatar
Robert Speicher committed
170 171 172
      it "should include standard gfm classes" do
        css = object.class.to_s.underscore
        gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
Riyad Preukschas's avatar
Riyad Preukschas committed
173
      end
Robert Speicher's avatar
Robert Speicher committed
174
    end
Riyad Preukschas's avatar
Riyad Preukschas committed
175

Robert Speicher's avatar
Robert Speicher committed
176 177 178
    describe "referencing an issue" do
      let(:object)    { issue }
      let(:reference) { "##{issue.id}" }
Riyad Preukschas's avatar
Riyad Preukschas committed
179

Robert Speicher's avatar
Robert Speicher committed
180 181
      include_examples 'referenced object'
    end
Riyad Preukschas's avatar
Riyad Preukschas committed
182

Robert Speicher's avatar
Robert Speicher committed
183 184 185 186 187
    describe "referencing a merge request" do
      let(:object)    { merge_request }
      let(:reference) { "!#{merge_request.id}" }

      include_examples 'referenced object'
Riyad Preukschas's avatar
Riyad Preukschas committed
188 189 190
    end

    describe "referencing a snippet" do
Robert Speicher's avatar
Robert Speicher committed
191 192
      let(:object)    { snippet }
      let(:reference) { "$#{snippet.id}" }
Riyad Preukschas's avatar
Riyad Preukschas committed
193

Robert Speicher's avatar
Robert Speicher committed
194 195
      include_examples 'referenced object'
    end
Riyad Preukschas's avatar
Riyad Preukschas committed
196

Robert Speicher's avatar
Robert Speicher committed
197 198 199 200 201 202
    describe "referencing multiple objects" do
      let(:actual) { "!#{merge_request.id} -> #{commit.id} -> ##{issue.id}" }

      it "should link to the merge request" do
        expected = project_merge_request_path(project, merge_request)
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
203 204
      end

Robert Speicher's avatar
Robert Speicher committed
205 206 207
      it "should link to the commit" do
        expected = project_commit_path(project, commit)
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
208 209
      end

Robert Speicher's avatar
Robert Speicher committed
210 211 212
      it "should link to the issue" do
        expected = project_issue_path(project, issue)
        gfm(actual).should match(expected)
Riyad Preukschas's avatar
Riyad Preukschas committed
213 214
      end
    end
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240

    describe "emoji" do
      it "matches at the start of a string" do
        gfm(":+1:").should match(/<img/)
      end

      it "matches at the end of a string" do
        gfm("This gets a :-1:").should match(/<img/)
      end

      it "matches with adjacent text" do
        gfm("+1 (:+1:)").should match(/<img/)
      end

      it "has a title attribute" do
        gfm(":-1:").should match(/title=":-1:"/)
      end

      it "has an alt attribute" do
        gfm(":-1:").should match(/alt=":-1:"/)
      end

      it "has an emoji class" do
        gfm(":+1:").should match('class="emoji"')
      end

241 242 243 244 245 246
      it "sets height and width" do
        actual = gfm(":+1:")
        actual.should match(/width="20"/)
        actual.should match(/height="20"/)
      end

247 248 249 250 251 252 253
      it "keeps whitespace intact" do
        gfm("This deserves a :+1: big time.").should match(/deserves a <img.+\/> big time/)
      end

      it "ignores invalid emoji" do
        gfm(":invalid-emoji:").should_not match(/<img/)
      end
254 255 256 257 258

      it "should work independet of reference links (i.e. without @project being set)" do
        @project = nil
        gfm(":+1:").should match(/<img/)
      end
259
    end
Robert Speicher's avatar
Robert Speicher committed
260
  end
Riyad Preukschas's avatar
Riyad Preukschas committed
261

Robert Speicher's avatar
Robert Speicher committed
262 263 264
  describe "#link_to_gfm" do
    let(:commit_path) { project_commit_path(project, commit) }
    let(:issues)      { create_list(:issue, 2, project: project) }
Riyad Preukschas's avatar
Riyad Preukschas committed
265

Robert Speicher's avatar
Robert Speicher committed
266 267
    it "should handle references nested in links with all the text" do
      actual = link_to_gfm("This should finally fix ##{issues[0].id} and ##{issues[1].id} for real", commit_path)
Riyad Preukschas's avatar
Riyad Preukschas committed
268

Robert Speicher's avatar
Robert Speicher committed
269 270 271
      # Break the result into groups of links with their content, without
      # closing tags
      groups = actual.split("</a>")
Riyad Preukschas's avatar
Riyad Preukschas committed
272

Robert Speicher's avatar
Robert Speicher committed
273 274 275
      # Leading commit link
      groups[0].should match(/href="#{commit_path}"/)
      groups[0].should match(/This should finally fix $/)
Riyad Preukschas's avatar
Riyad Preukschas committed
276

Robert Speicher's avatar
Robert Speicher committed
277
      # First issue link
278
      groups[1].should match(/href="#{project_issue_url(project, issues[0])}"/)
Robert Speicher's avatar
Robert Speicher committed
279
      groups[1].should match(/##{issues[0].id}$/)
280

Robert Speicher's avatar
Robert Speicher committed
281 282 283
      # Internal commit link
      groups[2].should match(/href="#{commit_path}"/)
      groups[2].should match(/ and /)
284

Robert Speicher's avatar
Robert Speicher committed
285
      # Second issue link
286
      groups[3].should match(/href="#{project_issue_url(project, issues[1])}"/)
Robert Speicher's avatar
Robert Speicher committed
287 288 289 290 291
      groups[3].should match(/##{issues[1].id}$/)

      # Trailing commit link
      groups[4].should match(/href="#{commit_path}"/)
      groups[4].should match(/ for real$/)
292 293 294
    end

    it "should forward HTML options" do
Robert Speicher's avatar
Robert Speicher committed
295 296
      actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
      actual.should have_selector 'a.gfm.gfm-commit.foo'
297
    end
298 299 300 301 302

    it "escapes HTML passed in as the body" do
      actual = "This is a <h1>test</h1> - see ##{issues[0].id}"
      link_to_gfm(actual, commit_path).should match('&lt;h1&gt;test&lt;/h1&gt;')
    end
303
  end
304 305 306

  describe "#markdown" do
    it "should handle references in paragraphs" do
307 308 309
      actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n"
      expected = project_commit_path(project, commit)
      markdown(actual).should match(expected)
310 311 312
    end

    it "should handle references in headers" do
Robert Speicher's avatar
Robert Speicher committed
313 314 315 316
      actual = "\n# Working around ##{issue.id}\n## Apply !#{merge_request.id}"

      markdown(actual).should match(%r{<h1[^<]*>Working around <a.+>##{issue.id}</a></h1>})
      markdown(actual).should match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.id}</a></h2>})
317 318 319
    end

    it "should handle references in lists" do
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
320
      project.team << [user, :master]
Robert Speicher's avatar
Robert Speicher committed
321

322
      actual = "\n* dark: ##{issue.id}\n* light by @#{member.user.username}"
Robert Speicher's avatar
Robert Speicher committed
323 324

      markdown(actual).should match(%r{<li>dark: <a.+>##{issue.id}</a></li>})
325
      markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
326 327 328
    end

    it "should handle references in <em>" do
Robert Speicher's avatar
Robert Speicher committed
329 330 331
      actual = "Apply _!#{merge_request.id}_ ASAP"

      markdown(actual).should match(%r{Apply <em><a.+>!#{merge_request.id}</a></em>})
332 333 334
    end

    it "should leave code blocks untouched" do
335
      helper.stub(:user_color_scheme_class).and_return(:white)
336

337
      helper.markdown("\n    some code from $#{snippet.id}\n    here too\n").should include("<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>")
338

339
      helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should include("<div class=\"white\"><div class=\"highlight\"><pre><span class=\"n\">some</span> <span class=\"n\">code</span> <span class=\"n\">from</span> $#{snippet.id}\n<span class=\"n\">here</span> <span class=\"n\">too</span>\n</pre></div></div>")
340 341 342
    end

    it "should leave inline code untouched" do
Robert Speicher's avatar
Robert Speicher committed
343
      markdown("\nDon't use `$#{snippet.id}` here.\n").should == "<p>Don&#39;t use <code>$#{snippet.id}</code> here.</p>\n"
344
    end
345

346 347 348 349 350
    it "should leave ref-like autolinks untouched" do
      markdown("look at http://example.tld/#!#{merge_request.id}").should == "<p>look at <a href=\"http://example.tld/#!#{merge_request.id}\">http://example.tld/#!#{merge_request.id}</a></p>\n"
    end

    it "should leave ref-like href of 'manual' links untouched" do
Riyad Preukschas's avatar
Riyad Preukschas committed
351
      markdown("why not [inspect !#{merge_request.id}](http://example.tld/#!#{merge_request.id})").should == "<p>why not <a href=\"http://example.tld/#!#{merge_request.id}\">inspect </a><a href=\"#{project_merge_request_url(project, merge_request)}\" class=\"gfm gfm-merge_request \" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.id}</a><a href=\"http://example.tld/#!#{merge_request.id}\"></a></p>\n"
352 353 354 355 356 357
    end

    it "should leave ref-like src of images untouched" do
      markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.id})").should == "<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.id}\" alt=\"some image\"></p>\n"
    end

358 359 360
    it "should generate absolute urls for refs" do
      markdown("##{issue.id}").should include(project_issue_url(project, issue))
    end
361 362 363 364

    it "should generate absolute urls for emoji" do
      markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}")
    end
365
  end
Riyad Preukschas's avatar
Riyad Preukschas committed
366
end