label_reference_filter_spec.rb 22.2 KB
Newer Older
1 2 3
require 'spec_helper'
require 'html/pipeline'

4
describe Banzai::Filter::LabelReferenceFilter do
5 6
  include FilterSpecHelper

7
  let(:project)   { create(:project, :public, name: 'sample-project') }
8 9 10 11 12 13 14 15 16 17
  let(:label)     { create(:label, project: project) }
  let(:reference) { label.to_reference }

  it 'requires project context' do
    expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
  end

  %w(pre code a style).each do |elem|
    it "ignores valid references contained inside '#{elem}' element" do
      exp = act = "<#{elem}>Label #{reference}</#{elem}>"
18
      expect(reference_filter(act).to_html).to eq exp
19 20 21 22
    end
  end

  it 'includes default classes' do
23
    doc = reference_filter("Label #{reference}")
24
    expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip'
25 26 27
  end

  it 'includes a data-project attribute' do
28
    doc = reference_filter("Label #{reference}")
29 30 31 32 33 34 35
    link = doc.css('a').first

    expect(link).to have_attribute('data-project')
    expect(link.attr('data-project')).to eq project.id.to_s
  end

  it 'includes a data-label attribute' do
36
    doc = reference_filter("See #{reference}")
37 38 39 40 41 42 43
    link = doc.css('a').first

    expect(link).to have_attribute('data-label')
    expect(link.attr('data-label')).to eq label.id.to_s
  end

  it 'supports an :only_path context' do
44
    doc = reference_filter("Label #{reference}", only_path: true)
45 46 47
    link = doc.css('a').first.attr('href')

    expect(link).not_to match %r(https?://)
48
    expect(link).to eq urls.project_issues_path(project, label_name: label.name)
49 50
  end

51 52 53 54 55 56 57 58
  context 'project that does not exist referenced' do
    let(:result) { reference_filter('aaa/bbb~ccc') }

    it 'does not link reference' do
      expect(result.to_html).to eq 'aaa/bbb~ccc'
    end
  end

59 60
  describe 'label span element' do
    it 'includes default classes' do
61
      doc = reference_filter("Label #{reference}")
62
      expect(doc.css('a span').first.attr('class')).to eq 'badge color-label has-tooltip'
63 64 65
    end

    it 'includes a style attribute' do
66
      doc = reference_filter("Label #{reference}")
67 68 69 70 71 72
      expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
    end
  end

  context 'Integer-based references' do
    it 'links to a valid reference' do
73
      doc = reference_filter("See #{reference}")
74

75
      expect(doc.css('a').first.attr('href')).to eq urls
76
        .project_issues_url(project, label_name: label.name)
77 78 79
    end

    it 'links with adjacent text' do
80
      doc = reference_filter("Label (#{reference}.)")
81 82 83 84 85 86
      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
    end

    it 'ignores invalid label IDs' do
      exp = act = "Label #{invalidate_reference(reference)}"

87
      expect(reference_filter(act).to_html).to eq exp
88 89 90 91 92 93 94 95
    end
  end

  context 'String-based single-word references' do
    let(:label)     { create(:label, name: 'gfm', project: project) }
    let(:reference) { "#{Label.reference_prefix}#{label.name}" }

    it 'links to a valid reference' do
96
      doc = reference_filter("See #{reference}")
97

98
      expect(doc.css('a').first.attr('href')).to eq urls
99
        .project_issues_url(project, label_name: label.name)
100 101 102 103
      expect(doc.text).to eq 'See gfm'
    end

    it 'links with adjacent text' do
104 105
      doc = reference_filter("Label (#{reference}).")
      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\)\.))
106 107 108 109 110
    end

    it 'ignores invalid label names' do
      exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"

111
      expect(reference_filter(act).to_html).to eq exp
112 113 114
    end
  end

115 116 117 118 119 120 121
  context 'String-based single-word references that begin with a digit' do
    let(:label)     { create(:label, name: '2fa', project: project) }
    let(:reference) { "#{Label.reference_prefix}#{label.name}" }

    it 'links to a valid reference' do
      doc = reference_filter("See #{reference}")

122
      expect(doc.css('a').first.attr('href')).to eq urls
123
        .project_issues_url(project, label_name: label.name)
124 125 126 127
      expect(doc.text).to eq 'See 2fa'
    end

    it 'links with adjacent text' do
128 129
      doc = reference_filter("Label (#{reference}).")
      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\)\.))
130 131 132 133 134 135 136 137 138
    end

    it 'ignores invalid label names' do
      exp = act = "Label #{Label.reference_prefix}#{label.id}#{label.name.reverse}"

      expect(reference_filter(act).to_html).to eq exp
    end
  end

139
  context 'String-based single-word references with special characters' do
140
    let(:label)     { create(:label, name: '?g.fm&', project: project) }
141 142 143 144 145
    let(:reference) { "#{Label.reference_prefix}#{label.name}" }

    it 'links to a valid reference' do
      doc = reference_filter("See #{reference}")

146
      expect(doc.css('a').first.attr('href')).to eq urls
147
        .project_issues_url(project, label_name: label.name)
148
      expect(doc.text).to eq 'See ?g.fm&'
149 150
    end

151 152 153 154 155
    it 'does not include trailing punctuation', :aggregate_failures do
      ['.', ', ok?', '...', '?', '!', ': is that ok?'].each do |trailing_punctuation|
        doc = filter("Label #{reference}#{trailing_punctuation}")
        expect(doc.to_html).to match(%r(<a.+><span.+>\?g\.fm&amp;</span></a>#{Regexp.escape(trailing_punctuation)}))
      end
156 157 158 159
    end

    it 'ignores invalid label names' do
      act = "Label #{Label.reference_prefix}#{label.name.reverse}"
160
      exp = "Label #{Label.reference_prefix}&amp;mf.g?"
161 162 163 164 165

      expect(reference_filter(act).to_html).to eq exp
    end
  end

166 167
  context 'String-based multi-word references in quotes' do
    let(:label)     { create(:label, name: 'gfm references', project: project) }
168
    let(:reference) { label.to_reference(format: :name) }
169 170

    it 'links to a valid reference' do
171
      doc = reference_filter("See #{reference}")
172

173
      expect(doc.css('a').first.attr('href')).to eq urls
174
        .project_issues_url(project, label_name: label.name)
175 176 177 178
      expect(doc.text).to eq 'See gfm references'
    end

    it 'links with adjacent text' do
179
      doc = reference_filter("Label (#{reference}.)")
180 181 182 183 184 185
      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
    end

    it 'ignores invalid label names' do
      exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")

186
      expect(reference_filter(act).to_html).to eq exp
187 188 189
    end
  end

190 191 192 193 194 195 196
  context 'String-based multi-word references that begin with a digit' do
    let(:label)     { create(:label, name: '2 factor authentication', project: project) }
    let(:reference) { label.to_reference(format: :name) }

    it 'links to a valid reference' do
      doc = reference_filter("See #{reference}")

197
      expect(doc.css('a').first.attr('href')).to eq urls
198
        .project_issues_url(project, label_name: label.name)
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
      expect(doc.text).to eq 'See 2 factor authentication'
    end

    it 'links with adjacent text' do
      doc = reference_filter("Label (#{reference}.)")
      expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
    end

    it 'ignores invalid label names' do
      exp = act = "Label #{Label.reference_prefix}#{label.id}#{label.name.reverse}"

      expect(reference_filter(act).to_html).to eq exp
    end
  end

214
  context 'String-based multi-word references with special characters in quotes' do
215
    let(:label)     { create(:label, name: 'g.fm & references?', project: project) }
216 217 218 219 220
    let(:reference) { label.to_reference(format: :name) }

    it 'links to a valid reference' do
      doc = reference_filter("See #{reference}")

221
      expect(doc.css('a').first.attr('href')).to eq urls
222
        .project_issues_url(project, label_name: label.name)
223
      expect(doc.text).to eq 'See g.fm & references?'
224 225 226 227
    end

    it 'links with adjacent text' do
      doc = reference_filter("Label (#{reference}.)")
228
      expect(doc.to_html).to match(%r(\(<a.+><span.+>g\.fm &amp; references\?</span></a>\.\)))
229 230 231 232
    end

    it 'ignores invalid label names' do
      act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
233
      exp = %(Label #{Label.reference_prefix}"?secnerefer &amp; mf.g\")
234 235 236 237 238

      expect(reference_filter(act).to_html).to eq exp
    end
  end

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
  describe 'consecutive references' do
    let(:bug) { create(:label, name: 'bug', project: project) }
    let(:feature_proposal) { create(:label, name: 'feature proposal', project: project) }
    let(:technical_debt) { create(:label, name: 'technical debt', project: project) }

    let(:bug_reference) { "#{Label.reference_prefix}#{bug.name}" }
    let(:feature_proposal_reference) { feature_proposal.to_reference(format: :name) }
    let(:technical_debt_reference) { technical_debt.to_reference(format: :name) }

    context 'separated with a comma' do
      let(:references) { "#{bug_reference}, #{feature_proposal_reference}, #{technical_debt_reference}" }

      it 'links to valid references' do
        doc = reference_filter("See #{references}")

        expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
255 256 257
          urls.project_issues_url(project, label_name: bug.name),
          urls.project_issues_url(project, label_name: feature_proposal.name),
          urls.project_issues_url(project, label_name: technical_debt.name)
258 259 260 261 262 263 264 265 266 267 268 269
        ])
        expect(doc.text).to eq 'See bug, feature proposal, technical debt'
      end
    end

    context 'separated with a space' do
      let(:references) { "#{bug_reference} #{feature_proposal_reference} #{technical_debt_reference}" }

      it 'links to valid references' do
        doc = reference_filter("See #{references}")

        expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
270 271 272
          urls.project_issues_url(project, label_name: bug.name),
          urls.project_issues_url(project, label_name: feature_proposal.name),
          urls.project_issues_url(project, label_name: technical_debt.name)
273 274 275 276 277 278
        ])
        expect(doc.text).to eq 'See bug feature proposal technical debt'
      end
    end
  end

279 280 281
  describe 'edge cases' do
    it 'gracefully handles non-references matching the pattern' do
      exp = act = '(format nil "~0f" 3.0) ; 3.0'
282 283 284 285 286 287 288 289 290 291
      expect(reference_filter(act).to_html).to eq exp
    end
  end

  describe 'referencing a label in a link href' do
    let(:reference) { %Q{<a href="#{label.to_reference}">Label</a>} }

    it 'links to a valid reference' do
      doc = reference_filter("See #{reference}")

292
      expect(doc.css('a').first.attr('href')).to eq urls
293
        .project_issues_url(project, label_name: label.name)
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
    end

    it 'links with adjacent text' do
      doc = reference_filter("Label (#{reference}.)")
      expect(doc.to_html).to match(%r(\(<a.+>Label</a>\.\)))
    end

    it 'includes a data-project attribute' do
      doc = reference_filter("Label #{reference}")
      link = doc.css('a').first

      expect(link).to have_attribute('data-project')
      expect(link.attr('data-project')).to eq project.id.to_s
    end

    it 'includes a data-label attribute' do
      doc = reference_filter("See #{reference}")
      link = doc.css('a').first

      expect(link).to have_attribute('data-label')
      expect(link.attr('data-label')).to eq label.id.to_s
    end
316
  end
317

318 319
  describe 'group label references' do
    let(:group)       { create(:group) }
320
    let(:project)     { create(:project, :public, namespace: group) }
321 322 323 324 325 326 327 328
    let(:group_label) { create(:group_label, name: 'gfm references', group: group) }

    context 'without project reference' do
      let(:reference) { group_label.to_reference(format: :name) }

      it 'links to a valid reference' do
        doc = reference_filter("See #{reference}", project: project)

329
        expect(doc.css('a').first.attr('href')).to eq urls
330
          .project_issues_url(project, label_name: group_label.name)
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
        expect(doc.text).to eq 'See gfm references'
      end

      it 'links with adjacent text' do
        doc = reference_filter("Label (#{reference}.)")
        expect(doc.to_html).to match(%r(\(<a.+><span.+>#{group_label.name}</span></a>\.\)))
      end

      it 'ignores invalid label names' do
        exp = act = %(Label #{Label.reference_prefix}"#{group_label.name.reverse}")

        expect(reference_filter(act).to_html).to eq exp
      end
    end

    context 'with project reference' do
347
      let(:reference) { "#{project.to_reference}#{group_label.to_reference(format: :name)}" }
348 349 350 351

      it 'links to a valid reference' do
        doc = reference_filter("See #{reference}", project: project)

352
        expect(doc.css('a').first.attr('href')).to eq urls
353
          .project_issues_url(project, label_name: group_label.name)
354
        expect(doc.text).to eq "See gfm references"
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
      end

      it 'links with adjacent text' do
        doc = reference_filter("Label (#{reference}.)")
        expect(doc.to_html).to match(%r(\(<a.+><span.+>#{group_label.name}</span></a>\.\)))
      end

      it 'ignores invalid label names' do
        exp = act = %(Label #{project.to_reference}#{Label.reference_prefix}"#{group_label.name.reverse}")

        expect(reference_filter(act).to_html).to eq exp
      end
    end
  end

370
  describe 'cross-project / cross-namespace complete reference' do
371
    let(:project2)  { create(:project) }
372
    let(:label)     { create(:label, project: project2, color: '#00ff00') }
373
    let(:reference) { "#{project2.full_path}~#{label.name}" }
374
    let!(:result)   { reference_filter("See #{reference}") }
375

376 377
    it 'links to a valid reference' do
      expect(result.css('a').first.attr('href'))
378
        .to eq urls.project_issues_url(project2, label_name: label.name)
379
    end
380

381 382 383
    it 'has valid color' do
      expect(result.css('a span').first.attr('style')).to match /background-color: #00ff00/
    end
384

385
    it 'has valid link text' do
386
      expect(result.css('a').first.text).to eq "#{label.name} in #{project2.full_name}"
387
    end
388

389
    it 'has valid text' do
390
      expect(result.text).to eq "See #{label.name} in #{project2.full_name}"
391 392
    end

393 394
    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"
395

396 397 398 399 400 401
      expect(reference_filter(act).to_html).to eq exp
    end
  end

  describe 'cross-project / same-namespace complete reference' do
    let(:namespace) { create(:namespace) }
402 403
    let(:project)   { create(:project, namespace: namespace) }
    let(:project2)  { create(:project, namespace: namespace) }
404
    let(:label)     { create(:label, project: project2, color: '#00ff00') }
405
    let(:reference) { "#{project2.full_path}~#{label.name}" }
406 407 408 409
    let!(:result)   { reference_filter("See #{reference}") }

    it 'links to a valid reference' do
      expect(result.css('a').first.attr('href'))
410
        .to eq urls.project_issues_url(project2, label_name: label.name)
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
    end

    it 'has valid color' do
      expect(result.css('a span').first.attr('style')).to match /background-color: #00ff00/
    end

    it 'has valid link text' do
      expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name}"
    end

    it 'has valid text' do
      expect(result.text).to eq "See #{label.name} in #{project2.name}"
    end

    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"

      expect(reference_filter(act).to_html).to eq exp
    end
  end

  describe 'cross-project shorthand reference' do
    let(:namespace) { create(:namespace) }
434 435
    let(:project)   { create(:project, namespace: namespace) }
    let(:project2)  { create(:project, namespace: namespace) }
436 437 438 439 440 441
    let(:label)     { create(:label, project: project2, color: '#00ff00') }
    let(:reference) { "#{project2.path}~#{label.name}" }
    let!(:result)   { reference_filter("See #{reference}") }

    it 'links to a valid reference' do
      expect(result.css('a').first.attr('href'))
442
        .to eq urls.project_issues_url(project2, label_name: label.name)
443 444 445
    end

    it 'has valid color' do
446 447
      expect(result.css('a span').first.attr('style'))
        .to match /background-color: #00ff00/
448 449 450 451 452 453 454 455 456 457 458 459 460 461
    end

    it 'has valid link text' do
      expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name}"
    end

    it 'has valid text' do
      expect(result.text).to eq "See #{label.name} in #{project2.name}"
    end

    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"

      expect(reference_filter(act).to_html).to eq exp
462 463
    end
  end
464 465

  describe 'cross group label references' do
466
    let(:group)            { create(:group) }
467
    let(:project)          { create(:project, :public, namespace: group) }
468
    let(:another_group)    { create(:group) }
469
    let(:another_project)  { create(:project, :public, namespace: another_group) }
470
    let(:group_label)      { create(:group_label, group: another_group, color: '#00ff00') }
471
    let(:reference)        { "#{another_project.full_path}~#{group_label.name}" }
472
    let!(:result)          { reference_filter("See #{reference}", project: project) }
473

474 475
    it 'points to referenced project issues page' do
      expect(result.css('a').first.attr('href'))
476
        .to eq urls.project_issues_url(another_project, label_name: group_label.name)
477
    end
478

479
    it 'has valid color' do
480 481
      expect(result.css('a span').first.attr('style'))
        .to match /background-color: #00ff00/
482 483 484
    end

    it 'has valid link text' do
485
      expect(result.css('a').first.text)
486
        .to eq "#{group_label.name} in #{another_project.full_name}"
487 488 489
    end

    it 'has valid text' do
490
      expect(result.text)
491
        .to eq "See #{group_label.name} in #{another_project.full_name}"
492 493 494 495 496 497 498 499 500 501 502
    end

    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"

      expect(reference_filter(act).to_html).to eq exp
    end
  end

  describe 'cross-project / same-group_label complete reference' do
    let(:group)            { create(:group) }
503 504
    let(:project)          { create(:project, :public, namespace: group) }
    let(:another_project)  { create(:project, :public, namespace: group) }
505
    let(:group_label)      { create(:group_label, group: group, color: '#00ff00') }
506
    let(:reference)        { "#{another_project.full_path}~#{group_label.name}" }
507 508 509
    let!(:result)          { reference_filter("See #{reference}", project: project) }

    it 'points to referenced project issues page' do
510
      expect(result.css('a').first.attr('href'))
511
        .to eq urls.project_issues_url(another_project, label_name: group_label.name)
512 513 514
    end

    it 'has valid color' do
515 516
      expect(result.css('a span').first.attr('style'))
        .to match /background-color: #00ff00/
517 518 519
    end

    it 'has valid link text' do
520 521
      expect(result.css('a').first.text)
        .to eq "#{group_label.name} in #{another_project.name}"
522 523 524
    end

    it 'has valid text' do
525 526
      expect(result.text)
        .to eq "See #{group_label.name} in #{another_project.name}"
527 528 529 530 531 532 533 534 535 536 537
    end

    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"

      expect(reference_filter(act).to_html).to eq exp
    end
  end

  describe 'same project / same group_label complete reference' do
    let(:group)       { create(:group) }
538
    let(:project)     { create(:project, :public, namespace: group) }
539
    let(:group_label) { create(:group_label, group: group, color: '#00ff00') }
540
    let(:reference)   { "#{project.full_path}~#{group_label.name}" }
541 542 543 544
    let!(:result)     { reference_filter("See #{reference}", project: project) }

    it 'points to referenced project issues page' do
      expect(result.css('a').first.attr('href'))
545
        .to eq urls.project_issues_url(project, label_name: group_label.name)
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
    end

    it 'has valid color' do
      expect(result.css('a span').first.attr('style'))
        .to match /background-color: #00ff00/
    end

    it 'has valid link text' do
      expect(result.css('a').first.text).to eq group_label.name
    end

    it 'has valid text' do
      expect(result.text).to eq "See #{group_label.name}"
    end

    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"

      expect(reference_filter(act).to_html).to eq exp
    end
  end

  describe 'same project / same group_label shorthand reference' do
    let(:group)       { create(:group) }
570
    let(:project)     { create(:project, :public, namespace: group) }
571 572 573 574 575 576
    let(:group_label) { create(:group_label, group: group, color: '#00ff00') }
    let(:reference)   { "#{project.path}~#{group_label.name}" }
    let!(:result)     { reference_filter("See #{reference}", project: project) }

    it 'points to referenced project issues page' do
      expect(result.css('a').first.attr('href'))
577
        .to eq urls.project_issues_url(project, label_name: group_label.name)
578 579 580
    end

    it 'has valid color' do
581 582
      expect(result.css('a span').first.attr('style'))
        .to match /background-color: #00ff00/
583 584 585 586 587 588 589 590 591 592 593 594 595 596
    end

    it 'has valid link text' do
      expect(result.css('a').first.text).to eq group_label.name
    end

    it 'has valid text' do
      expect(result.text).to eq "See #{group_label.name}"
    end

    it 'ignores invalid IDs on the referenced label' do
      exp = act = "See #{invalidate_reference(reference)}"

      expect(reference_filter(act).to_html).to eq exp
597 598
    end
  end
599 600

  describe 'group context' do
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
    it 'points to the page defined in label_url_method' do
      group = create(:group)
      label = create(:group_label, group: group)
      reference = "~#{label.name}"

      result = reference_filter("See #{reference}", { project: nil, group: group, label_url_method: :group_url } )

      expect(result.css('a').first.attr('href')).to eq(urls.group_url(group, label_name: label.name))
    end

    it 'finds labels also in ancestor groups' do
      group = create(:group)
      label = create(:group_label, group: group)
      subgroup = create(:group, parent: group)
      reference = "~#{label.name}"

      result = reference_filter("See #{reference}", { project: nil, group: subgroup, label_url_method: :group_url } )

      expect(result.css('a').first.attr('href')).to eq(urls.group_url(subgroup, label_name: label.name))
    end

622 623 624 625 626 627 628 629
    it 'points to referenced project issues page' do
      project = create(:project)
      label = create(:label, project: project)
      reference = "#{project.full_path}~#{label.name}"

      result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )

      expect(result.css('a').first.attr('href')).to eq(urls.project_issues_url(project, label_name: label.name))
630
      expect(result.css('a').first.text).to eq "#{label.name} in #{project.full_name}"
631 632
    end
  end
633
end