I like it :+1:
-tag (default: false). + def initialize( + nowrap: false, + cssclass: 'highlight', + linenos: nil, + linenostart: 1, + lineanchors: false, + lineanchorsid: 'L', + anchorlinenos: false, + inline_theme: nil + ) + @nowrap = nowrap + @cssclass = cssclass + @linenos = linenos + @linenostart = linenostart + @lineanchors = lineanchors + @lineanchorsid = lineanchorsid + @anchorlinenos = anchorlinenos + @inline_theme = Theme.find(@inline_theme).new if @inline_theme.is_a?(String) + end + + def render(tokens) + case @linenos + when 'table' + render_tableized(tokens) + when 'inline' + render_untableized(tokens) + else + render_untableized(tokens) + end + end + + alias_method :format, :render + + private + + def render_untableized(tokens) + data = process_tokens(tokens) + + html = '' + html << "\n" unless @nowrap + html + end + + def render_tableized(tokens) + data = process_tokens(tokens) + + html = '' + html << "" unless @nowrap + html << wrap_lines(data[:code]) + html << "" unless @nowrap + html << '' unless @nowrap + html + end + + def process_tokens(tokens) + num_lines = 0 + last_val = '' + rendered = '' + + tokens.each do |tok, val| + last_val = val + num_lines += val.scan(/\n/).size + rendered << span(tok, val) + end + + numbers = (@linenostart..num_lines + @linenostart - 1).to_a + + { numbers: numbers, code: rendered } + end + + def wrap_linenos(numbers) + if @anchorlinenos + numbers.map! do |number| + "#{number}" + end + end + numbers.join("\n") + end + + def wrap_lines(rendered) + if @lineanchors + lines = rendered.split("\n") + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + + if @linenos == 'inline' + "" \ + "#{number}" \ + "#{line}" \ + '' + else + "#{line}" \ + '' + end + end + lines.join("\n") + else + if @linenos == 'inline' + lines = rendered.split("\n") + lines = lines.each_with_index.map do |line, index| + number = index + @linenostart + "#{number}#{line}" + end + lines.join("\n") + else + rendered + end + end + end + + def wrap_values(val, element) + lines = val.split("\n") + lines = lines.map{ |x| "#{x}" } + lines.join("\n") + end + + def span(tok, val) + # http://stackoverflow.com/a/1600584/2587286 + val = CGI.escapeHTML(val) + + if tok.shortname.empty? + val + else + # In the case of multi-line values (e.g. comments), we need to apply + # styling to each line since span elements are inline. + if @inline_theme + rules = @inline_theme.style_for(tok).rendered_rules + wrap_values(val, "style=\"#{rules.to_a.join(';')}\"") + else + wrap_values(val, "class=\"#{tok.shortname}\"") + end + end + end + end + end +end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index edb987875d..efa0898900 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -38,6 +38,11 @@ upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } +## Experimental: gitlab-git-http-server +# upstream gitlab-git-http-server { +# server localhost:8181; +# } + ## Normal HTTP host server { ## Either remove "default_server" from the listen line below, @@ -109,6 +114,26 @@ server { proxy_pass http://gitlab; } + ## Experimental: send Git HTTP traffic to gitlab-git-http-server instead of Unicorn + # location ~ [-\/\w\.]+\.git\/ { + # ## If you use HTTPS make sure you disable gzip compression + # ## to be safe against BREACH attack. + # # gzip off; + + # ## https://github.com/gitlabhq/gitlabhq/issues/694 + # ## Some requests take more than 30 seconds. + # proxy_read_timeout 300; + # proxy_connect_timeout 300; + # proxy_redirect off; + + # proxy_set_header Host $http_host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + + # proxy_pass http://gitlab-git-http-server; + # } + ## Enable gzip compression as per rails guide: ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression ## WARNING: If you are using relative urls remove the block below diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 766559b49f..314525518f 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -42,6 +42,11 @@ upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } +## Experimental: gitlab-git-http-server +# upstream gitlab-git-http-server { +# server localhost:8181; +# } + ## Redirects all HTTP traffic to the HTTPS host server { ## Either remove "default_server" from the listen line below, @@ -156,6 +161,26 @@ server { proxy_pass http://gitlab; } + ## Experimental: send Git HTTP traffic to gitlab-git-http-server instead of Unicorn + # location ~ [-\/\w\.]+\.git\/ { + # ## If you use HTTPS make sure you disable gzip compression + # ## to be safe against BREACH attack. + # gzip off; + + # ## https://github.com/gitlabhq/gitlabhq/issues/694 + # ## Some requests take more than 30 seconds. + # proxy_read_timeout 300; + # proxy_connect_timeout 300; + # proxy_redirect off; + + # proxy_set_header Host $http_host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-Ssl on; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # proxy_pass http://gitlab-git-http-server; + # } + ## Enable gzip compression as per rails guide: ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression ## WARNING: If you are using relative urls remove the block below diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index aed84226a2..badb47c677 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -485,7 +485,8 @@ namespace :gitlab do if project.empty_repo? puts "repository is empty".magenta - elsif File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path) + elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) && + (File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path)) puts 'ok'.green else puts "wrong or missing hooks".red @@ -754,7 +755,7 @@ namespace :gitlab do print "Ruby version >= #{required_version} ? ... " if current_version.valid? && required_version <= current_version - puts "yes (#{current_version})".green + puts "yes (#{current_version})".green else puts "no".red try_fixing_it( @@ -772,7 +773,7 @@ namespace :gitlab do print "Git version >= #{required_version} ? ... " if current_version.valid? && required_version <= current_version - puts "yes (#{current_version})".green + puts "yes (#{current_version})".green else puts "no".red try_fixing_it( @@ -806,4 +807,3 @@ namespace :gitlab do end end end - diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index 5f83e5e8e7..c1ee271ae2 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -62,11 +62,11 @@ namespace :gitlab do project = Projects::CreateService.new(user, project_params).execute - if project.valid? + if project.persisted? puts " * Created #{project.name} (#{repo_path})".green else puts " * Failed trying to create #{project.name} (#{repo_path})".red - puts " Validation Errors: #{project.errors.messages}".red + puts " Errors: #{project.errors.messages}".red end end end diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake deleted file mode 100644 index 102c6ae55d..0000000000 --- a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake +++ /dev/null @@ -1,73 +0,0 @@ -require "#{Rails.root}/app/helpers/emails_helper" -require 'action_view/helpers' -extend ActionView::Helpers - -include ActionView::Context -include EmailsHelper - -namespace :gitlab do - desc "Email google whitelisting email with example email for actions in inbox" - task mail_google_schema_whitelisting: :environment do - subject = "Rails | Implemented feature" - url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}" - schema = email_action(url) - body = email_template(schema, url) - mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe) - if send_now - mail.deliver - else - puts "WOULD SEND:" - end - puts mail - end - - def email_template(schema, url) - " - - -' + html << "
' + html << '' + html << " " + html << wrap_linenos(data[:numbers]) + html << '' + html << ' " + html << wrap_lines(data[:code]) + html << '- GitLab - - - - - --- - - - " - end - - def send_now - if ENV['SEND'] == "true" - true - else - false - end - end -end diff --git a/lib/tasks/gitlab/update_commit_count.rake b/lib/tasks/gitlab/update_commit_count.rake new file mode 100644 index 0000000000..9b636f12d9 --- /dev/null +++ b/lib/tasks/gitlab/update_commit_count.rake @@ -0,0 +1,20 @@ +namespace :gitlab do + desc "GitLab | Update commit count for projects" + task update_commit_count: :environment do + projects = Project.where(commit_count: 0) + puts "#{projects.size} projects need to be updated. This might take a while." + ask_to_continue unless ENV['force'] == 'yes' + + projects.find_each(batch_size: 100) do |project| + print "#{project.name_with_namespace.yellow} ... " + + unless project.repo_exists? + puts "skipping, because the repo is empty".magenta + next + end + + project.update_commit_count + puts project.commit_count.to_s.green + end + end +end diff --git a/lib/unfold_form.rb b/lib/unfold_form.rb index 46b12beeaa..fcd01503d1 100644 --- a/lib/unfold_form.rb +++ b/lib/unfold_form.rb @@ -8,4 +8,5 @@ class UnfoldForm attribute :bottom, Boolean attribute :unfold, Boolean, default: true attribute :offset, Integer + attribute :indent, Integer, default: 0 end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index d47a37914d..9f89101d7f 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -1,25 +1,38 @@ require 'spec_helper' describe UsersController do - let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') } - - before do - sign_in(user) - end + let(:user) { create(:user) } describe 'GET #show' do - render_views + it 'is case-insensitive' do + user = create(:user, username: 'CamelCaseUser') + sign_in(user) - it 'renders the show template' do - get :show, username: user.username - expect(response.status).to eq(200) - expect(response).to render_template('show') + get :show, username: user.username.downcase + + expect(response).to be_success + end + + context 'with rendered views' do + render_views + + it 'renders the show template' do + sign_in(user) + + get :show, username: user.username + + expect(response).to be_success + expect(response).to render_template('show') + end end end describe 'GET #calendar' do it 'renders calendar' do + sign_in(user) + get :calendar, username: user.username + expect(response).to render_template('calendar') end end @@ -30,6 +43,8 @@ describe UsersController do before do allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id]) + + sign_in(user) project.team << [user, :developer] end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index b8199aa5e6..859a62f740 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -17,26 +17,208 @@ require 'erb' # -> Post-process HTML # -> `gfm_with_options` helper # -> HTML::Pipeline -# -> Sanitize -# -> RelativeLink -# -> Emoji -# -> Table of Contents -# -> Autolinks -# -> Rinku (http, https, ftp) -# -> Other schemes -# -> ExternalLink -# -> References -# -> TaskList +# -> SanitizationFilter +# -> Other filters, depending on pipeline # -> `html_safe` # -> Template # # See the MarkdownFeature class for setup details. describe 'GitLab Markdown', feature: true do - include ActionView::Helpers::TagHelper - include ActionView::Helpers::UrlHelper include Capybara::Node::Matchers include GitlabMarkdownHelper + include MarkdownMatchers + + # 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/--I like it :+1:
-.html`. + def write_markdown(filename = 'markdown_spec') + File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file| + file.puts @html + end + end + + def doc(html = @html) + Nokogiri::HTML::DocumentFragment.parse(html) + end + + # Shared behavior that all pipelines should exhibit + shared_examples 'all pipelines' do + describe 'Redcarpet extensions' do + it 'does not parse emphasis inside of words' do + expect(doc.to_html).not_to match('foobarbaz') + end + + it 'parses table Markdown' do + aggregate_failures do + expect(doc).to have_selector('th:contains("Header")') + expect(doc).to have_selector('th:contains("Row")') + expect(doc).to have_selector('th:contains("Example")') + end + end + + it 'allows Markdown in tables' do + expect(doc.at_css('td:contains("Baz")').children.to_html). + to eq 'Baz' + end + + it 'parses fenced code blocks' do + aggregate_failures do + expect(doc).to have_selector('pre.code.highlight.white.c') + expect(doc).to have_selector('pre.code.highlight.white.python') + end + end + + it 'parses strikethroughs' do + expect(doc).to have_selector(%{del:contains("and this text doesn't")}) + end + + it 'parses superscript' do + expect(doc).to have_selector('sup', count: 2) + end + end + + describe 'SanitizationFilter' do + 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') + end + + it 'permits span elements' do + expect(doc).to have_selector('span:contains("span tag")') + end + + it 'permits style attribute in th elements' do + 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' + end + end + + it 'permits style attribute in td elements' do + aggregate_failures do + 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 + end + + it 'removes `rel` attribute from links' do + expect(doc).not_to have_selector('a[rel="bookmark"]') + end + + it "removes `href` from `a` elements if it's fishy" do + expect(doc).not_to have_selector('a[href*="javascript"]') + end + end + + describe 'Escaping' do + it 'escapes non-tag angle brackets' do + table = doc.css('table').last.at_css('tbody') + expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 < 3 & 5' + end + end + + describe 'Edge Cases' do + it 'allows markup inside link elements' do + aggregate_failures do + expect(doc.at_css('a[href="#link-emphasis"]').to_html). + to eq %{text} + + expect(doc.at_css('a[href="#link-strong"]').to_html). + to eq %{text} + + expect(doc.at_css('a[href="#link-code"]').to_html). + to eq %{ text} + end + end + end + + describe 'ExternalLinkFilter' do + it 'adds nofollow to external link' do + link = doc.at_css('a:contains("Google")') + expect(link.attr('rel')).to match 'nofollow' + end + + it 'ignores internal link' do + link = doc.at_css('a:contains("GitLab Root")') + expect(link.attr('rel')).not_to match 'nofollow' + end + end + end + + context 'default pipeline' do + before(:all) do + @feat = MarkdownFeature.new + + # `gfm_with_options` depends on a `@project` variable + @project = @feat.project + + @html = markdown(@feat.raw_markdown) + end + + it_behaves_like 'all pipelines' + + it 'includes RelativeLinkFilter' do + expect(doc).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 + end + end + + it 'includes TaskListFilter' do + expect(doc).to parse_task_lists + end + end # `markdown` calls these two methods def current_user @@ -46,381 +228,4 @@ describe 'GitLab Markdown', feature: true do def user_color_scheme_class :white end - - # Let's only parse this thing once - before(:all) do - @feat = MarkdownFeature.new - - # `markdown` expects a `@project` variable - @project = @feat.project - - @md = markdown(@feat.raw_markdown) - @doc = Nokogiri::HTML::DocumentFragment.parse(@md) - end - - after(:all) do - @feat.teardown - end - - # Given a header ID, goes to that element's parent (the header itself), then - # its next sibling element (the body). - def get_section(id) - @doc.at_css("##{id}").parent.next_element - end - - # Sometimes it can be useful to see the parsed output of the Markdown document - # for debugging. Uncomment this block to write the output to - # tmp/capybara/markdown_spec.html. - # - # it 'writes to a file' do - # File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file| - # file.puts @md - # end - # end - - describe 'Markdown' do - describe 'No Intra Emphasis' do - it 'does not parse emphasis inside of words' do - body = get_section('no-intra-emphasis') - expect(body.to_html).not_to match('foobarbaz') - end - end - - describe 'Tables' do - it 'parses table Markdown' do - body = get_section('tables') - expect(body).to have_selector('th:contains("Header")') - expect(body).to have_selector('th:contains("Row")') - expect(body).to have_selector('th:contains("Example")') - end - - it 'allows Markdown in tables' do - expect(@doc.at_css('td:contains("Baz")').children.to_html). - to eq 'Baz' - end - end - - describe 'Fenced Code Blocks' do - it 'parses fenced code blocks' do - expect(@doc).to have_selector('pre.code.highlight.white.c') - expect(@doc).to have_selector('pre.code.highlight.white.python') - end - end - - describe 'Strikethrough' do - it 'parses strikethroughs' do - expect(@doc).to have_selector(%{del:contains("and this text doesn't")}) - end - end - - describe 'Superscript' do - it 'parses superscript' do - body = get_section('superscript') - expect(body.to_html).to match('1st') - expect(body.to_html).to match('2nd') - end - end - end - - describe 'HTML::Pipeline' do - describe 'SanitizationFilter' do - it 'uses a permissive whitelist' do - expect(@doc).to have_selector('b:contains("b tag")') - expect(@doc).to have_selector('em:contains("em tag")') - expect(@doc).to have_selector('code:contains("code tag")') - expect(@doc).to have_selector('kbd:contains("s")') - expect(@doc).to have_selector('strike:contains(Emoji)') - expect(@doc).to have_selector('img[src*="smile.png"]') - expect(@doc).to have_selector('br') - expect(@doc).to have_selector('hr') - end - - it 'permits span elements' do - expect(@doc).to have_selector('span:contains("span tag")') - end - - it 'permits table alignment' 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' - - 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 - - it 'removes `rel` attribute from links' do - body = get_section('sanitizationfilter') - expect(body).not_to have_selector('a[rel="bookmark"]') - end - - it "removes `href` from `a` elements if it's fishy" do - expect(@doc).not_to have_selector('a[href*="javascript"]') - end - end - - describe 'Escaping' do - let(:table) { @doc.css('table').last.at_css('tbody') } - - it 'escapes non-tag angle brackets' do - expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 < 3 & 5' - end - end - - describe 'Edge Cases' do - it 'allows markup inside link elements' do - expect(@doc.at_css('a[href="#link-emphasis"]').to_html). - to eq %{text} - - expect(@doc.at_css('a[href="#link-strong"]').to_html). - to eq %{text} - - expect(@doc.at_css('a[href="#link-code"]').to_html). - to eq %{text} - end - end - - describe 'EmojiFilter' do - it 'parses Emoji' do - expect(@doc).to have_selector('img.emoji', count: 10) - end - end - - describe 'TableOfContentsFilter' do - it 'creates anchors inside header elements' do - expect(@doc).to have_selector('h1 a#gitlab-markdown') - expect(@doc).to have_selector('h2 a#markdown') - expect(@doc).to have_selector('h3 a#autolinkfilter') - end - end - - describe 'AutolinkFilter' do - let(:list) { get_section('autolinkfilter').next_element } - - def item(index) - list.at_css("li:nth-child(#{index})") - end - - it 'autolinks http://' do - expect(item(1).children.first.name).to eq 'a' - expect(item(1).children.first['href']).to eq 'http://about.gitlab.com/' - end - - it 'autolinks https://' do - expect(item(2).children.first.name).to eq 'a' - expect(item(2).children.first['href']).to eq 'https://google.com/' - end - - it 'autolinks ftp://' do - expect(item(3).children.first.name).to eq 'a' - expect(item(3).children.first['href']).to eq 'ftp://ftp.us.debian.org/debian/' - end - - it 'autolinks smb://' do - expect(item(4).children.first.name).to eq 'a' - expect(item(4).children.first['href']).to eq 'smb://foo/bar/baz' - end - - it 'autolinks irc://' do - expect(item(5).children.first.name).to eq 'a' - expect(item(5).children.first['href']).to eq 'irc://irc.freenode.net/git' - end - - it 'autolinks short, invalid URLs' do - expect(item(6).children.first.name).to eq 'a' - expect(item(6).children.first['href']).to eq 'http://localhost:3000' - end - - %w(code a kbd).each do |elem| - it "ignores links inside '#{elem}' element" do - body = get_section('autolinkfilter') - expect(body).not_to have_selector("#{elem} a") - end - end - end - - describe 'ExternalLinkFilter' do - let(:links) { get_section('externallinkfilter').next_element } - - it 'adds nofollow to external link' do - expect(links.css('a').first.to_html).to match 'nofollow' - end - - it 'ignores internal link' do - expect(links.css('a').last.to_html).not_to match 'nofollow' - end - end - - describe 'ReferenceFilter' do - it 'handles references in headers' do - header = @doc.at_css('#reference-filters-eg-1').parent - - expect(header.css('a').size).to eq 2 - end - - it "handles references in Markdown" do - body = get_section('reference-filters-eg-1') - expect(body).to have_selector('em a.gfm-merge_request', count: 1) - end - - it 'parses user references' do - body = get_section('userreferencefilter') - expect(body).to have_selector('a.gfm.gfm-project_member', count: 3) - end - - it 'parses issue references' do - body = get_section('issuereferencefilter') - expect(body).to have_selector('a.gfm.gfm-issue', count: 2) - end - - it 'parses merge request references' do - body = get_section('mergerequestreferencefilter') - expect(body).to have_selector('a.gfm.gfm-merge_request', count: 2) - end - - it 'parses snippet references' do - body = get_section('snippetreferencefilter') - expect(body).to have_selector('a.gfm.gfm-snippet', count: 2) - end - - it 'parses commit range references' do - body = get_section('commitrangereferencefilter') - expect(body).to have_selector('a.gfm.gfm-commit_range', count: 2) - end - - it 'parses commit references' do - body = get_section('commitreferencefilter') - expect(body).to have_selector('a.gfm.gfm-commit', count: 2) - end - - it 'parses label references' do - body = get_section('labelreferencefilter') - expect(body).to have_selector('a.gfm.gfm-label', count: 3) - end - end - - describe 'Task Lists' do - it 'generates task lists' do - body = get_section('task-lists') - expect(body).to have_selector('ul.task-list', count: 2) - expect(body).to have_selector('li.task-list-item', count: 7) - expect(body).to have_selector('input[checked]', count: 3) - end - end - end -end - -# This is a helper class used by the GitLab Markdown feature spec -# -# Because the feature spec only cares about the output of the Markdown, and the -# test setup and teardown and parsing is fairly expensive, we only want to do it -# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)` -# block, so we fake it by encapsulating all the shared setup in this class. -# -# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for -# reference to the factory-created objects. -class MarkdownFeature - include FactoryGirl::Syntax::Methods - - def initialize - DatabaseCleaner.start - end - - def teardown - DatabaseCleaner.clean - end - - def user - @user ||= create(:user) - end - - def group - unless @group - @group = create(:group) - @group.add_user(user, Gitlab::Access::DEVELOPER) - end - - @group - end - - # Direct references ---------------------------------------------------------- - - def project - @project ||= create(:project) - end - - def issue - @issue ||= create(:issue, project: project) - end - - def merge_request - @merge_request ||= create(:merge_request, :simple, source_project: project) - end - - def snippet - @snippet ||= create(:project_snippet, project: project) - end - - def commit - @commit ||= project.commit - end - - def commit_range - unless @commit_range - commit2 = project.commit('HEAD~3') - @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project) - end - - @commit_range - end - - def simple_label - @simple_label ||= create(:label, name: 'gfm', project: project) - end - - def label - @label ||= create(:label, name: 'awaiting feedback', project: project) - end - - # Cross-references ----------------------------------------------------------- - - def xproject - unless @xproject - namespace = create(:namespace, name: 'cross-reference') - @xproject = create(:project, namespace: namespace) - @xproject.team << [user, :developer] - end - - @xproject - end - - def xissue - @xissue ||= create(:issue, project: xproject) - end - - def xmerge_request - @xmerge_request ||= create(:merge_request, :simple, source_project: xproject) - end - - def xsnippet - @xsnippet ||= create(:project_snippet, project: xproject) - end - - def xcommit - @xcommit ||= xproject.commit - end - - def xcommit_range - unless @xcommit_range - xcommit2 = xproject.commit('HEAD~2') - @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) - end - - @xcommit_range - end - - def raw_markdown - fixture = Rails.root.join('spec/fixtures/markdown.md.erb') - ERB.new(File.read(fixture)).result(binding) - end end diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb index 0f36e47498..8ce1538860 100644 --- a/spec/features/security/group_access_spec.rb +++ b/spec/features/security/group_access_spec.rb @@ -36,7 +36,7 @@ describe 'Group access', feature: true do subject { group_path(group) } context 'with public projects' do - before(:all) { create_project(:public) } + let!(:project) { create_project(:public) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -48,7 +48,7 @@ describe 'Group access', feature: true do end context 'with mixed projects' do - before(:all) { create_project(:mixed) } + let!(:project) { create_project(:mixed) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -60,7 +60,7 @@ describe 'Group access', feature: true do end context 'with internal projects' do - before(:all) { create_project(:internal) } + let!(:project) { create_project(:internal) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -86,7 +86,7 @@ describe 'Group access', feature: true do subject { issues_group_path(group) } context 'with public projects' do - before(:all) { create_project(:public) } + let!(:project) { create_project(:public) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -98,7 +98,7 @@ describe 'Group access', feature: true do end context 'with mixed projects' do - before(:all) { create_project(:mixed) } + let!(:project) { create_project(:mixed) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -110,7 +110,7 @@ describe 'Group access', feature: true do end context 'with internal projects' do - before(:all) { create_project(:internal) } + let!(:project) { create_project(:internal) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -136,7 +136,7 @@ describe 'Group access', feature: true do subject { merge_requests_group_path(group) } context 'with public projects' do - before(:all) { create_project(:public) } + let!(:project) { create_project(:public) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -148,7 +148,7 @@ describe 'Group access', feature: true do end context 'with mixed projects' do - before(:all) { create_project(:mixed) } + let!(:project) { create_project(:mixed) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -160,7 +160,7 @@ describe 'Group access', feature: true do end context 'with internal projects' do - before(:all) { create_project(:internal) } + let!(:project) { create_project(:internal) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -186,7 +186,7 @@ describe 'Group access', feature: true do subject { group_group_members_path(group) } context 'with public projects' do - before(:all) { create_project(:public) } + let!(:project) { create_project(:public) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -198,7 +198,7 @@ describe 'Group access', feature: true do end context 'with mixed projects' do - before(:all) { create_project(:mixed) } + let!(:project) { create_project(:mixed) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -210,7 +210,7 @@ describe 'Group access', feature: true do end context 'with internal projects' do - before(:all) { create_project(:internal) } + let!(:project) { create_project(:internal) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_allowed_for group_member(:master) } @@ -236,7 +236,7 @@ describe 'Group access', feature: true do subject { edit_group_path(group) } context 'with public projects' do - before(:all) { create_project(:public) } + let!(:project) { create_project(:public) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_denied_for group_member(:master) } @@ -248,7 +248,7 @@ describe 'Group access', feature: true do end context 'with mixed projects' do - before(:all) { create_project(:mixed) } + let!(:project) { create_project(:mixed) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_denied_for group_member(:master) } @@ -260,7 +260,7 @@ describe 'Group access', feature: true do end context 'with internal projects' do - before(:all) { create_project(:internal) } + let!(:project) { create_project(:internal) } it { is_expected.to be_allowed_for group_member(:owner) } it { is_expected.to be_denied_for group_member(:master) } diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 02ab46c905..41d12afa9c 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try! - [**text**](#link-strong) - [`text`](#link-code) +### RelativeLinkFilter + +Linking to a file relative to this project's repository should work. + +[Relative Link](doc/README.md) + + ### EmojiFilter Because life would be :zzz: without Emoji, right? :rocket: @@ -123,9 +130,9 @@ These are all plain text that should get turned into links: But it shouldn't autolink text inside certain tags: --http://about.gitlab.com/-- http://about.gitlab.com/ -- http://about.gitlab.com/ +-http://code.gitlab.com/+- http://a.gitlab.com/ +- http://kbd.gitlab.com/ ### ExternalLinkFilter diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb new file mode 100644 index 0000000000..e47a54fdac --- /dev/null +++ b/spec/helpers/auth_helper_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe AuthHelper do + describe "button_based_providers" do + it 'returns all enabled providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :github] } + expect(helper.button_based_providers).to include(*[:twitter, :github]) + end + + it 'does not return ldap provider' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.button_based_providers).to include(:twitter) + end + + it 'returns empty array' do + allow(helper).to receive(:auth_providers) { [] } + expect(helper.button_based_providers).to eq([]) + end + end +end diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index e49e4e6d5d..76009c3609 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -6,6 +6,14 @@ describe BlobHelper do let(:no_context_content) { ":type \"assem\"))" } let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } let(:split_content) { blob_content.split("\n") } + let(:multiline_content) do + %q( + def test(input): + """This is line 1 of a multi-line comment. + This is line 2. + """ + ) + end it 'should return plaintext for unknown lexer context' do result = highlight(blob_name, no_context_content, nowrap: true, continue: false) @@ -29,5 +37,15 @@ describe BlobHelper do result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) } expect(result).to eq(expected) end + + it 'should highlight multi-line comments' do + result = highlight(blob_name, multiline_content, nowrap: true, continue: false) + html = Nokogiri::HTML(result) + lines = html.search('.s') + expect(lines.count).to eq(3) + expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.') + expect(lines[1].text).to eq(' This is line 2.') + expect(lines[2].text).to eq(' """') + end end end diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb deleted file mode 100644 index 4b1e67178d..0000000000 --- a/spec/helpers/oauth_helper_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require "spec_helper" - -describe OauthHelper do - describe "additional_providers" do - it 'returns all enabled providers' do - allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] } - expect(helper.additional_providers).to include(*[:twitter, :github]) - end - - it 'does not return ldap provider' do - allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] } - expect(helper.additional_providers).to include(:twitter) - end - - it 'returns empty array' do - allow(helper).to receive(:enabled_oauth_providers) { [] } - expect(helper.additional_providers).to eq([]) - end - end - - describe "kerberos_enabled?" do - it 'returns true' do - allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github, :kerberos] } - expect(helper).to be_kerberos_enabled - end - - it 'returns false' do - allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] } - expect(helper).not_to be_kerberos_enabled - end - end -end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index beb9b4e438..99abb95d90 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -22,7 +22,7 @@ describe ProjectsHelper do let(:user) { create(:user) } - it "returns false if there are no approipriate permissions" do + it "returns false if there are no appropriate permissions" do allow(helper).to receive(:can?) { false } expect(helper.can_change_visibility_level?(project, user)).to be_falsey @@ -52,4 +52,22 @@ describe ProjectsHelper do end end end + + describe "readme_cache_key" do + let(:project) { create(:project) } + + before do + helper.instance_variable_set(:@project, project) + end + + it "returns a valid cach key" do + expect(helper.send(:readme_cache_key)).to eq("#{project.id}-#{project.commit.id}-readme") + end + + it "returns a valid cache key if HEAD does not exist" do + allow(project).to receive(:commit) { nil } + + expect(helper.send(:readme_cache_key)).to eq("#{project.id}-nil-readme") + end + end end diff --git a/spec/javascripts/merge_request_tabs_spec.js.coffee b/spec/javascripts/merge_request_tabs_spec.js.coffee index 6cc96fb68a..a0cfba455e 100644 --- a/spec/javascripts/merge_request_tabs_spec.js.coffee +++ b/spec/javascripts/merge_request_tabs_spec.js.coffee @@ -51,6 +51,12 @@ describe 'MergeRequestTabs', -> expect(@subject('notes')).toBe('/foo/bar/merge_requests/1') expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits') + it 'changes from diffs.html', -> + @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs.html') + + expect(@subject('notes')).toBe('/foo/bar/merge_requests/1') + expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits') + it 'changes from notes', -> @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1') diff --git a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb index 5ee5310825..7f4d67e403 100644 --- a/spec/lib/gitlab/markdown/relative_link_filter_spec.rb +++ b/spec/lib/gitlab/markdown/relative_link_filter_spec.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require 'spec_helper' module Gitlab::Markdown @@ -101,6 +103,20 @@ module Gitlab::Markdown expect(doc.at_css('a')['href']).to eq 'http://example.com' end + it 'supports Unicode filenames' do + path = 'files/images/한글.png' + escaped = Addressable::URI.escape(path) + + # Stub these methods so the file doesn't actually need to be in the repo + allow_any_instance_of(described_class).to receive(:file_exists?). + and_return(true) + allow_any_instance_of(described_class). + to receive(:image?).with(path).and_return(true) + + doc = filter(image(escaped)) + expect(doc.at_css('img')['src']).to match '/raw/' + end + context 'when requested path is a file in the repo' do let(:requested_path) { 'doc/api/README.md' } include_examples :relative_to_requested diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 17ae887eb1..871e9c6c0a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -111,14 +111,20 @@ describe Project do expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') end - it 'returns the full web URL for this repo' do - project = Project.new(path: 'somewhere') - expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere") + describe "#web_url" do + let(:project) { create(:empty_project, path: "somewhere") } + + it 'returns the full web URL for this repo' do + expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere") + end end - it 'returns the web URL without the protocol for this repo' do - project = Project.new(path: 'somewhere') - expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere") + describe "#web_url_without_protocol" do + let(:project) { create(:empty_project, path: "somewhere") } + + it 'returns the web URL without the protocol for this repo' do + expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/#{project.namespace.path}/somewhere") + end end describe 'last_activity methods' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2b1b116849..aac8f00260 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -455,6 +455,18 @@ describe User do end end + describe '.find_by_username!' do + it 'raises RecordNotFound' do + expect { described_class.find_by_username!('JohnDoe') }. + to raise_error(ActiveRecord::RecordNotFound) + end + + it 'is case-insensitive' do + user = create(:user, username: 'JohnDoe') + expect(described_class.find_by_username!('JOHNDOE')).to eq user + end + end + describe 'all_ssh_keys' do it { is_expected.to have_many(:keys).dependent(:destroy) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 7030c105b5..29db035b2d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -8,6 +8,7 @@ describe API::API, api: true do let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test") } let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test") } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } + let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } before do project.team << [user, :reporters] @@ -397,13 +398,14 @@ describe API::API, api: true do end describe "GET :id/merge_request/:merge_request_id/comments" do - it "should return merge_request comments" do + it "should return merge_request comments ordered by created_at" do get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user) expect(response.status).to eq(200) expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq("a comment on a MR") expect(json_response.first['author']['id']).to eq(user.id) + expect(json_response.last['note']).to eq("another comment on a MR") end it "should return a 404 error if merge_request_id not found" do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index b520a1d49b..b1d362b422 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -4,13 +4,19 @@ describe Projects::CreateService do describe :create_by_user do before do @user = create :user - @admin = create :user, admin: true @opts = { name: "GitLab", namespace: @user.namespace } end + it 'creates services on Project creation' do + project = create_project(@user, @opts) + project.reload + + expect(project.services).not_to be_empty + end + context 'user namespace' do before do @project = create_project(@user, @opts) @@ -75,7 +81,9 @@ describe Projects::CreateService do end it 'should allow a restricted visibility level for admins' do - project = create_project(@admin, @opts) + admin = create(:admin) + project = create_project(admin, @opts) + expect(project.errors.any?).to be(false) expect(project.saved?).to be(true) end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 439a492cea..c04e842c67 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -29,7 +29,7 @@ describe Projects::ForkService do it "fails due to transaction failure" do @to_project = fork_project(@from_project, @to_user, false) expect(@to_project.errors).not_to be_empty - expect(@to_project.errors[:base]).to include("Failed to fork repository") + expect(@to_project.errors[:base]).to include("Failed to fork repository via gitlab-shell") end end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb new file mode 100644 index 0000000000..2a868aed73 --- /dev/null +++ b/spec/support/markdown_feature.rb @@ -0,0 +1,106 @@ +# This is a helper class used by the GitLab Markdown feature spec +# +# Because the feature spec only cares about the output of the Markdown, and the +# test setup and teardown and parsing is fairly expensive, we only want to do it +# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)` +# block, so we fake it by encapsulating all the shared setup in this class. +# +# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for +# reference to the factory-created objects. +class MarkdownFeature + include FactoryGirl::Syntax::Methods + + def user + @user ||= create(:user) + end + + def group + unless @group + @group = create(:group) + @group.add_user(user, Gitlab::Access::DEVELOPER) + end + + @group + end + + # Direct references ---------------------------------------------------------- + + def project + @project ||= create(:project) + end + + def issue + @issue ||= create(:issue, project: project) + end + + def merge_request + @merge_request ||= create(:merge_request, :simple, source_project: project) + end + + def snippet + @snippet ||= create(:project_snippet, project: project) + end + + def commit + @commit ||= project.commit + end + + def commit_range + unless @commit_range + commit2 = project.commit('HEAD~3') + @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project) + end + + @commit_range + end + + def simple_label + @simple_label ||= create(:label, name: 'gfm', project: project) + end + + def label + @label ||= create(:label, name: 'awaiting feedback', project: project) + end + + # Cross-references ----------------------------------------------------------- + + def xproject + unless @xproject + namespace = create(:namespace, name: 'cross-reference') + @xproject = create(:project, namespace: namespace) + @xproject.team << [user, :developer] + end + + @xproject + end + + def xissue + @xissue ||= create(:issue, project: xproject) + end + + def xmerge_request + @xmerge_request ||= create(:merge_request, :simple, source_project: xproject) + end + + def xsnippet + @xsnippet ||= create(:project_snippet, project: xproject) + end + + def xcommit + @xcommit ||= xproject.commit + end + + def xcommit_range + unless @xcommit_range + xcommit2 = xproject.commit('HEAD~2') + @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) + end + + @xcommit_range + end + + def raw_markdown + fixture = Rails.root.join('spec/fixtures/markdown.md.erb') + ERB.new(File.read(fixture)).result(binding) + end +end diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb new file mode 100644 index 0000000000..9df226c3af --- /dev/null +++ b/spec/support/matchers/markdown_matchers.rb @@ -0,0 +1,156 @@ +# MarkdownMatchers +# +# Custom matchers for our custom HTML::Pipeline filters. These are used to test +# that specific filters are or are not used by our defined pipelines. +# +# Must be included manually. +module MarkdownMatchers + extend RSpec::Matchers::DSL + include Capybara::Node::Matchers + + # RelativeLinkFilter + matcher :parse_relative_links do + set_default_markdown_messages + + match do |actual| + link = actual.at_css('a:contains("Relative Link")') + image = actual.at_css('img[alt="Relative Image"]') + + expect(link['href']).to end_with('master/doc/README.md') + expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png') + end + end + + # EmojiFilter + matcher :parse_emoji do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('img.emoji', count: 10) + end + end + + # TableOfContentsFilter + matcher :create_header_links do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('h1 a#gitlab-markdown') + expect(actual).to have_selector('h2 a#markdown') + expect(actual).to have_selector('h3 a#autolinkfilter') + end + end + + # AutolinkFilter + matcher :create_autolinks do + def have_autolink(link) + have_link(link, href: link) + end + + set_default_markdown_messages + + match do |actual| + expect(actual).to have_autolink('http://about.gitlab.com/') + expect(actual).to have_autolink('https://google.com/') + expect(actual).to have_autolink('ftp://ftp.us.debian.org/debian/') + expect(actual).to have_autolink('smb://foo/bar/baz') + expect(actual).to have_autolink('irc://irc.freenode.net/git') + expect(actual).to have_autolink('http://localhost:3000') + + %w(code a kbd).each do |elem| + expect(body).not_to have_selector("#{elem} a") + end + end + end + + # UserReferenceFilter + matcher :reference_users do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3) + end + end + + # IssueReferenceFilter + matcher :reference_issues do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-issue', count: 3) + end + end + + # MergeRequestReferenceFilter + matcher :reference_merge_requests do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3) + expect(actual).to have_selector('em a.gfm-merge_request') + end + end + + # SnippetReferenceFilter + matcher :reference_snippets do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2) + end + end + + # CommitRangeReferenceFilter + matcher :reference_commit_ranges do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2) + end + end + + # CommitReferenceFilter + matcher :reference_commits do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-commit', count: 2) + end + end + + # LabelReferenceFilter + matcher :reference_labels do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('a.gfm.gfm-label', count: 3) + end + end + + # TaskListFilter + matcher :parse_task_lists do + set_default_markdown_messages + + match do |actual| + expect(actual).to have_selector('ul.task-list', count: 2) + expect(actual).to have_selector('li.task-list-item', count: 7) + expect(actual).to have_selector('input[checked]', count: 3) + end + end +end + +# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for +# setting the failure messages for these matchers +module RSpec::Matchers::DSL::Macros + def set_default_markdown_messages + failure_message do + # expected to parse emoji, but didn't + "expected to #{description}, but didn't" + end + + failure_message_when_negated do + # expected not to parse task lists, but did + "expected not to #{description}, but did" + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index dcf2a9e2ce..dab4535e2c 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -12,7 +12,8 @@ module TestEnv 'fix' => '12d65c8', 'improve/awesome' => '5937ac0', 'markdown' => '0ed8c6c', - 'master' => '5937ac0' + 'master' => '5937ac0', + "'test'" => 'e56497b', } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily