BigW Consortium Gitlab

markdown_spec.rb 9.26 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
  include Capybara::Node::Matchers
29
  include MarkupHelper
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 62
        expect(doc.at_css('td:contains("Baz")').children.to_html)
          .to eq '<strong>Baz</strong>'
63 64 65
      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 119 120 121 122 123 124 125
      it 'permits details elements' do
        expect(doc).to have_selector('details:contains("Hiding the details")')
      end

      it 'permits summary elements' do
        expect(doc).to have_selector('details summary:contains("collapsible")')
      end

126
      it 'permits style attribute in th elements' do
127 128 129 130
        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'
131 132
        end
      end
133

134 135
      it 'permits style attribute in td elements' do
        aggregate_failures do
136 137 138 139
          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
140 141 142
      end

      it 'removes `rel` attribute from links' do
143
        expect(doc).not_to have_selector('a[rel="bookmark"]')
144 145 146
      end

      it "removes `href` from `a` elements if it's fishy" do
147
        expect(doc).not_to have_selector('a[href*="javascript"]')
148 149 150 151 152
      end
    end

    describe 'Escaping' do
      it 'escapes non-tag angle brackets' do
153
        table = doc.css('table').last.at_css('tbody')
154 155 156 157
        expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
      end
    end

158 159
    describe 'Edge Cases' do
      it 'allows markup inside link elements' do
160
        aggregate_failures do
161 162
          expect(doc.at_css('a[href="#link-emphasis"]').to_html)
            .to eq %{<a href="#link-emphasis"><em>text</em></a>}
163

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

167 168
          expect(doc.at_css('a[href="#link-code"]').to_html)
            .to eq %{<a href="#link-code"><code>text</code></a>}
169
        end
170 171 172
      end
    end

173 174
    describe 'ExternalLinkFilter' do
      it 'adds nofollow to external link' do
175
        link = doc.at_css('a:contains("Google")')
176

Alfredo Sumaran committed
177 178 179 180 181
        expect(link.attr('rel')).to include('nofollow')
      end

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

Alfredo Sumaran committed
183
        expect(link.attr('rel')).to include('noreferrer')
184 185
      end

186 187
      it 'adds _blank to target attribute for external links' do
        link = doc.at_css('a:contains("Google")')
188

189 190 191
        expect(link.attr('target')).to match('_blank')
      end

192
      it 'ignores internal link' do
193
        link = doc.at_css('a:contains("GitLab Root")')
194

195
        expect(link.attr('rel')).not_to match 'nofollow'
196 197
        expect(link.attr('target')).not_to match '_blank'
      end
198
    end
199
  end
200

201
  before do
202
    @feat = MarkdownFeature.new
203

204 205 206
    # `markdown` helper expects a `@project` variable
    @project = @feat.project
  end
207

208
  context 'default pipeline' do
209
    before do
210 211
      @html = markdown(@feat.raw_markdown)
    end
212

213
    it_behaves_like 'all pipelines'
214

215 216 217
    it 'includes RelativeLinkFilter' do
      expect(doc).to parse_relative_links
    end
218

219 220 221
    it 'includes EmojiFilter' do
      expect(doc).to parse_emoji
    end
222

223 224 225
    it 'includes TableOfContentsFilter' do
      expect(doc).to create_header_links
    end
226

227 228 229
    it 'includes AutolinkFilter' do
      expect(doc).to create_autolinks
    end
230

231 232 233 234 235 236 237 238 239
    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
240
        expect(doc).to reference_milestones
241 242
      end
    end
243

244 245
    it 'includes TaskListFilter' do
      expect(doc).to parse_task_lists
246
    end
247 248 249 250 251 252 253 254

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

    it 'includes VideoLinkFilter' do
      expect(doc).to parse_video_links
    end
255
  end
256

257 258 259
  context 'wiki pipeline' do
    before do
      @project_wiki = @feat.project_wiki
260
      @project_wiki_page = @feat.project_wiki_page
261 262 263 264

      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)
265
      allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' }
266

267
      @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki, page_slug: @project_wiki_page.slug })
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 300 301 302 303 304 305 306 307
    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
308 309 310 311

    it 'includes InlineDiffFilter' do
      expect(doc).to parse_inline_diffs
    end
312 313 314 315

    it 'includes VideoLinkFilter' do
      expect(doc).to parse_video_links
    end
316 317
  end

318
  # Fake a `current_user` helper
319 320 321
  def current_user
    @feat.user
  end
322
end