BigW Consortium Gitlab

markdown_spec.rb 9.01 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
require 'spec_helper'
require 'erb'

# This feature spec is intended to be a comprehensive exercising of all of
# GitLab's non-standard Markdown parsing and the integration thereof.
#
# These tests should be very high-level. Anything low-level belongs in the specs
# for the corresponding HTML::Pipeline filter or helper method.
#
# The idea is to pass a Markdown document through our entire processing stack.
#
# The process looks like this:
#
#   Raw Markdown
#   -> `markdown` helper
#     -> Redcarpet::Render::GitlabHTML converts Markdown to HTML
#       -> Post-process HTML
18
#         -> `gfm` helper
19
#           -> HTML::Pipeline
20 21
#             -> SanitizationFilter
#             -> Other filters, depending on pipeline
22 23 24 25 26
#           -> `html_safe`
#           -> Template
#
# See the MarkdownFeature class for setup details.

27
describe 'GitLab Markdown', feature: true do
28 29
  include Capybara::Node::Matchers
  include GitlabMarkdownHelper
30
  include MarkdownMatchers
31

32 33 34 35 36 37 38
  # Sometimes it can be useful to see the parsed output of the Markdown document
  # for debugging. Call this method to write the output to
  # `tmp/capybara/<filename>.html`.
  def write_markdown(filename = 'markdown_spec')
    File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file|
      file.puts @html
    end
39 40
  end

41
  def doc(html = @html)
42
    @doc ||= Nokogiri::HTML::DocumentFragment.parse(html)
43 44
  end

45 46 47
  # Shared behavior that all pipelines should exhibit
  shared_examples 'all pipelines' do
    describe 'Redcarpet extensions' do
48
      it 'does not parse emphasis inside of words' do
49
        expect(doc.to_html).not_to match('foo<em>bar</em>baz')
50 51 52
      end

      it 'parses table Markdown' do
53
        aggregate_failures do
54 55 56
          expect(doc).to have_selector('th:contains("Header")')
          expect(doc).to have_selector('th:contains("Row")')
          expect(doc).to have_selector('th:contains("Example")')
57
        end
58 59 60
      end

      it 'allows Markdown in tables' do
61
        expect(doc.at_css('td:contains("Baz")').children.to_html).
62 63 64 65
          to eq '<strong>Baz</strong>'
      end

      it 'parses fenced code blocks' do
66
        aggregate_failures do
Robert Speicher committed
67 68
          expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c')
          expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python')
69
        end
70 71 72
      end

      it 'parses strikethroughs' do
73
        expect(doc).to have_selector(%{del:contains("and this text doesn't")})
74 75 76
      end

      it 'parses superscript' do
77
        expect(doc).to have_selector('sup', count: 2)
78 79 80 81
      end
    end

    describe 'SanitizationFilter' do
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
      it 'permits b elements' do
        expect(doc).to have_selector('b:contains("b tag")')
      end

      it 'permits em elements' do
        expect(doc).to have_selector('em:contains("em tag")')
      end

      it 'permits code elements' do
        expect(doc).to have_selector('code:contains("code tag")')
      end

      it 'permits kbd elements' do
        expect(doc).to have_selector('kbd:contains("s")')
      end

      it 'permits strike elements' do
        expect(doc).to have_selector('strike:contains(Emoji)')
      end

      it 'permits img elements' do
        expect(doc).to have_selector('img[src*="smile.png"]')
      end

      it 'permits br elements' do
        expect(doc).to have_selector('br')
      end

      it 'permits hr elements' do
        expect(doc).to have_selector('hr')
112 113 114
      end

      it 'permits span elements' do
115
        expect(doc).to have_selector('span:contains("span tag")')
116 117
      end

118
      it 'permits style attribute in th elements' do
119 120 121 122
        aggregate_failures do
          expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
          expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
          expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
123 124
        end
      end
125

126 127
      it 'permits style attribute in td elements' do
        aggregate_failures do
128 129 130 131
          expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
          expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
          expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
        end
132 133 134
      end

      it 'removes `rel` attribute from links' do
135
        expect(doc).not_to have_selector('a[rel="bookmark"]')
136 137 138
      end

      it "removes `href` from `a` elements if it's fishy" do
139
        expect(doc).not_to have_selector('a[href*="javascript"]')
140 141 142 143 144
      end
    end

    describe 'Escaping' do
      it 'escapes non-tag angle brackets' do
145
        table = doc.css('table').last.at_css('tbody')
146 147 148 149
        expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
      end
    end

150 151
    describe 'Edge Cases' do
      it 'allows markup inside link elements' do
152 153 154
        aggregate_failures do
          expect(doc.at_css('a[href="#link-emphasis"]').to_html).
            to eq %{<a href="#link-emphasis"><em>text</em></a>}
155

156 157
          expect(doc.at_css('a[href="#link-strong"]').to_html).
            to eq %{<a href="#link-strong"><strong>text</strong></a>}
158

159 160 161
          expect(doc.at_css('a[href="#link-code"]').to_html).
            to eq %{<a href="#link-code"><code>text</code></a>}
        end
162 163 164
      end
    end

165 166
    describe 'ExternalLinkFilter' do
      it 'adds nofollow to external link' do
167
        link = doc.at_css('a:contains("Google")')
168

Alfredo Sumaran committed
169 170 171 172 173
        expect(link.attr('rel')).to include('nofollow')
      end

      it 'adds noreferrer to external link' do
        link = doc.at_css('a:contains("Google")')
174

Alfredo Sumaran committed
175
        expect(link.attr('rel')).to include('noreferrer')
176 177
      end

178 179
      it 'adds _blank to target attribute for external links' do
        link = doc.at_css('a:contains("Google")')
180

181 182 183
        expect(link.attr('target')).to match('_blank')
      end

184
      it 'ignores internal link' do
185
        link = doc.at_css('a:contains("GitLab Root")')
186

187
        expect(link.attr('rel')).not_to match 'nofollow'
188 189
        expect(link.attr('target')).not_to match '_blank'
      end
190
    end
191
  end
192

193
  before do
194
    @feat = MarkdownFeature.new
195

196 197 198
    # `markdown` helper expects a `@project` variable
    @project = @feat.project
  end
199

200
  context 'default pipeline' do
201
    before do
202 203
      @html = markdown(@feat.raw_markdown)
    end
204

205
    it_behaves_like 'all pipelines'
206

207 208 209
    it 'includes RelativeLinkFilter' do
      expect(doc).to parse_relative_links
    end
210

211 212 213
    it 'includes EmojiFilter' do
      expect(doc).to parse_emoji
    end
214

215 216 217
    it 'includes TableOfContentsFilter' do
      expect(doc).to create_header_links
    end
218

219 220 221
    it 'includes AutolinkFilter' do
      expect(doc).to create_autolinks
    end
222

223 224 225 226 227 228 229 230 231
    it 'includes all reference filters' do
      aggregate_failures do
        expect(doc).to reference_users
        expect(doc).to reference_issues
        expect(doc).to reference_merge_requests
        expect(doc).to reference_snippets
        expect(doc).to reference_commit_ranges
        expect(doc).to reference_commits
        expect(doc).to reference_labels
232
        expect(doc).to reference_milestones
233 234
      end
    end
235

236 237
    it 'includes TaskListFilter' do
      expect(doc).to parse_task_lists
238
    end
239 240 241 242 243 244 245 246

    it 'includes InlineDiffFilter' do
      expect(doc).to parse_inline_diffs
    end

    it 'includes VideoLinkFilter' do
      expect(doc).to parse_video_links
    end
247
  end
248

249 250 251
  context 'wiki pipeline' do
    before do
      @project_wiki = @feat.project_wiki
252
      @project_wiki_page = @feat.project_wiki_page
253 254 255 256

      file = Gollum::File.new(@project_wiki.wiki)
      expect(file).to receive(:path).and_return('images/example.jpg')
      expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file)
257
      allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' }
258

259
      @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki, page_slug: @project_wiki_page.slug })
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    end

    it_behaves_like 'all pipelines'

    it 'includes RelativeLinkFilter' do
      expect(doc).not_to parse_relative_links
    end

    it 'includes EmojiFilter' do
      expect(doc).to parse_emoji
    end

    it 'includes TableOfContentsFilter' do
      expect(doc).to create_header_links
    end

    it 'includes AutolinkFilter' do
      expect(doc).to create_autolinks
    end

    it 'includes all reference filters' do
      aggregate_failures do
        expect(doc).to reference_users
        expect(doc).to reference_issues
        expect(doc).to reference_merge_requests
        expect(doc).to reference_snippets
        expect(doc).to reference_commit_ranges
        expect(doc).to reference_commits
        expect(doc).to reference_labels
        expect(doc).to reference_milestones
      end
    end

    it 'includes TaskListFilter' do
      expect(doc).to parse_task_lists
    end

    it 'includes GollumTagsFilter' do
      expect(doc).to parse_gollum_tags
    end
300 301 302 303

    it 'includes InlineDiffFilter' do
      expect(doc).to parse_inline_diffs
    end
304 305 306 307

    it 'includes VideoLinkFilter' do
      expect(doc).to parse_video_links
    end
308 309
  end

310
  # Fake a `current_user` helper
311 312 313
  def current_user
    @feat.user
  end
314
end