summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-04-23 12:08:03 +0300
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-04-23 12:08:03 +0300
commit71f6143552a47209d4d83c35260db608cac7de1a (patch)
treeb5c16ae980c71adc7af6ef803369d2f0f33d4bb3 /spec
parent63c5911961909b12b328b4182ba0f4b0e13c1bd6 (diff)
parentaac27550457eaf0503ce9bf7b04c18141ed317af (diff)
downloadgitlab-ce-71f6143552a47209d4d83c35260db608cac7de1a.tar.gz
Merge branch 'master' into new-sidebar
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> Conflicts: app/controllers/snippets_controller.rb
Diffstat (limited to 'spec')
-rw-r--r--spec/helpers/application_helper_spec.rb10
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb913
-rw-r--r--spec/helpers/labels_helper_spec.rb4
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js.coffee83
-rw-r--r--spec/javascripts/stat_graph_contributors_graph_spec.js2
-rw-r--r--spec/javascripts/stat_graph_contributors_util_spec.js2
-rw-r--r--spec/javascripts/stat_graph_spec.js2
-rw-r--r--spec/javascripts/support/jasmine.yml84
-rw-r--r--spec/javascripts/support/jasmine_helper.rb6
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb7
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb128
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb118
-rw-r--r--spec/lib/gitlab/markdown/cross_project_reference_spec.rb56
-rw-r--r--spec/lib/gitlab/markdown/emoji_filter_spec.rb97
-rw-r--r--spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb109
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb133
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb148
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb114
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb112
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb98
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb11
-rw-r--r--spec/requests/api/users_spec.rb13
-rw-r--r--spec/support/reference_filter_spec_helper.rb47
23 files changed, 1506 insertions, 791 deletions
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 015a66f7fa0..d4cf6540080 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -249,6 +249,16 @@ describe ApplicationHelper do
expect(link_to('Example', 'http://example.foo/bar')).
to eq '<a href="http://example.foo/bar">Example</a>'
end
+
+ it 'should not raise an error when given a bad URI' do
+ expect { link_to('default', 'if real=1 RANDOM; if real>1 IDLHS; if real>500 LHS') }.
+ not_to raise_error
+ end
+
+ it 'should not raise an error when given a bad mailto URL' do
+ expect { link_to('email', 'mailto://foo.bar@example.es?subject=Subject%20Line') }.
+ not_to raise_error
+ end
end
describe 'markup_render' do
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 944e743675c..64f130e4ae4 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -2,449 +2,27 @@ require 'spec_helper'
describe GitlabMarkdownHelper do
include ApplicationHelper
- include IssuesHelper
-
- # TODO: Properly test this
- def can?(*)
- true
- end
let!(:project) { create(:project) }
- let(:empty_project) { create(:empty_project) }
let(:user) { create(:user, username: 'gfm') }
let(:commit) { project.repository.commit }
- let(:earlier_commit){ project.repository.commit("HEAD~2") }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) }
- let(:member) { project.project_members.where(user_id: user).first }
# Helper expects a current_user method.
let(:current_user) { user }
- def url_helper(image_name)
- File.join(root_url, 'assets', image_name)
- end
-
before do
# Helper expects a @project instance variable
@project = project
- @ref = 'markdown'
- @repository = project.repository
- @request.host = Gitlab.config.gitlab.host
end
describe "#gfm" do
- it "should return unaltered text if project is nil" do
- actual = "Testing references: ##{issue.iid}"
-
- expect(gfm(actual)).not_to eq(actual)
-
- @project = nil
- expect(gfm(actual)).to eq(actual)
- end
-
- it "should not alter non-references" do
- actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do."
- expect(gfm(actual)).to eq(expected)
- end
-
- it "should not touch HTML entities" do
- allow(@project.issues).to receive(:where).
- with(id: '39').and_return([issue])
- actual = 'We&#39;ll accept good pull requests.'
- expect(gfm(actual)).to eq("We'll accept good pull requests.")
- end
-
it "should forward HTML options to links" do
expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')).
- to have_selector('a.gfm.foo')
- end
-
- describe "referencing a commit range" do
- let(:expected) { namespace_project_compare_path(project.namespace, project, from: earlier_commit.id, to: commit.id) }
-
- it "should link using a full id" do
- actual = "What happened in #{earlier_commit.id}...#{commit.id}"
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link using a short id" do
- actual = "What happened in #{earlier_commit.short_id}...#{commit.short_id}"
- expected = namespace_project_compare_path(project.namespace, project, from: earlier_commit.short_id, to: commit.short_id)
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link inclusively" do
- actual = "What happened in #{earlier_commit.id}..#{commit.id}"
- expected = namespace_project_compare_path(project.namespace, project, from: "#{earlier_commit.id}^", to: commit.id)
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link with adjacent text" do
- actual = "(see #{earlier_commit.id}...#{commit.id})"
- expect(gfm(actual)).to match(expected)
- end
-
- it "should keep whitespace intact" do
- actual = "Changes #{earlier_commit.id}...#{commit.id} dramatically"
- expected = /Changes <a.+>#{earlier_commit.id}...#{commit.id}<\/a> dramatically/
- expect(gfm(actual)).to match(expected)
- end
-
- it "should not link with an invalid id" do
- actual = expected = "What happened in #{earlier_commit.id.reverse}...#{commit.id.reverse}"
- expect(gfm(actual)).to eq(expected)
- end
-
- it "should include a title attribute" do
- actual = "What happened in #{earlier_commit.id}...#{commit.id}"
- expect(gfm(actual)).to match(/title="Commits #{earlier_commit.id} through #{commit.id}"/)
- end
-
- it "should include standard gfm classes" do
- actual = "What happened in #{earlier_commit.id}...#{commit.id}"
- expect(gfm(actual)).to match(/class="\s?gfm gfm-commit_range\s?"/)
- end
- end
-
- describe "referencing a commit" do
- let(:expected) { namespace_project_commit_path(project.namespace, project, commit) }
-
- it "should link using a full id" do
- actual = "Reverts #{commit.id}"
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link using a short id" do
- actual = "Backported from #{commit.short_id}"
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link with adjacent text" do
- actual = "Reverted (see #{commit.id})"
- expect(gfm(actual)).to match(expected)
- end
-
- it "should keep whitespace intact" do
- actual = "Changes #{commit.id} dramatically"
- expected = /Changes <a.+>#{commit.id}<\/a> dramatically/
- expect(gfm(actual)).to match(expected)
- end
-
- it "should not link with an invalid id" do
- actual = expected = "What happened in #{commit.id.reverse}"
- expect(gfm(actual)).to eq(expected)
- end
-
- it "should include a title attribute" do
- actual = "Reverts #{commit.id}"
- expect(gfm(actual)).to match(/title="#{commit.link_title}"/)
- end
-
- it "should include standard gfm classes" do
- actual = "Reverts #{commit.id}"
- expect(gfm(actual)).to match(/class="\s?gfm gfm-commit\s?"/)
- end
- end
-
- describe "referencing a team member" do
- let(:actual) { "@#{user.username} you are right." }
- let(:expected) { user_path(user) }
-
- before do
- project.team << [user, :master]
- end
-
- it "should link using a simple name" do
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link using a name with dots" do
- user.update_attributes(name: "alphA.Beta")
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link using name with underscores" do
- user.update_attributes(name: "ping_pong_king")
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link with adjacent text" do
- actual = "Mail the admin (@#{user.username})"
- expect(gfm(actual)).to match(expected)
- end
-
- it "should keep whitespace intact" do
- actual = "Yes, @#{user.username} is right."
- expected = /Yes, <a.+>@#{user.username}<\/a> is right/
- expect(gfm(actual)).to match(expected)
- end
-
- it "should not link with an invalid id" do
- actual = expected = "@#{user.username.reverse} you are right."
- expect(gfm(actual)).to eq(expected)
- end
-
- it "should include standard gfm classes" do
- expect(gfm(actual)).to match(/class="\s?gfm gfm-project_member\s?"/)
- end
- end
-
- # Shared examples for referencing an object
- #
- # Expects the following attributes to be available in the example group:
- #
- # - object - The object itself
- # - reference - The object reference string (e.g., #1234, $1234, !1234)
- #
- # Currently limited to Snippets, Issues and MergeRequests
- shared_examples 'referenced object' do
- let(:actual) { "Reference to #{reference}" }
- let(:expected) { polymorphic_path([project.namespace, project, object]) }
-
- it "should link using a valid id" do
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link with adjacent text" do
- # Wrap the reference in parenthesis
- expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
-
- # Append some text to the end of the reference
- expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
- to match(expected)
- end
-
- it "should keep whitespace intact" do
- actual = "Referenced #{reference} already."
- expected = /Referenced <a.+>[^\s]+<\/a> already/
- expect(gfm(actual)).to match(expected)
- end
-
- it "should not link with an invalid id" do
- # Modify the reference string so it's still parsed, but is invalid
- reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
- expect(gfm(actual)).to eq(actual)
- end
-
- it "should include a title attribute" do
- title = "#{object.class.to_s.titlecase}: #{object.title}"
- expect(gfm(actual)).to match(/title="#{title}"/)
- end
-
- it "should include standard gfm classes" do
- css = object.class.to_s.underscore
- expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
- end
- end
-
- # Shared examples for referencing an object in a different project
- #
- # Expects the following attributes to be available in the example group:
- #
- # - object - The object itself
- # - reference - The object reference string (e.g., #1234, $1234, !1234)
- # - other_project - The project that owns the target object
- #
- # Currently limited to Snippets, Issues and MergeRequests
- shared_examples 'cross-project referenced object' do
- let(:project_path) { @other_project.path_with_namespace }
- let(:full_reference) { "#{project_path}#{reference}" }
- let(:actual) { "Reference to #{full_reference}" }
- let(:expected) do
- if object.is_a?(Commit)
- namespace_project_commit_path(@other_project.namespace, @other_project, object)
- else
- polymorphic_path([@other_project.namespace, @other_project, object])
- end
- end
-
- it 'should link using a valid id' do
- expect(gfm(actual)).to match(
- /#{expected}.*#{Regexp.escape(full_reference)}/
- )
- end
-
- it 'should link with adjacent text' do
- # Wrap the reference in parenthesis
- expect(gfm(actual.gsub(full_reference, "(#{full_reference})"))).to(
- match(expected)
- )
-
- # Append some text to the end of the reference
- expect(gfm(actual.gsub(full_reference, "#{full_reference}, right?"))).
- to(match(expected))
- end
-
- it 'should keep whitespace intact' do
- actual = "Referenced #{full_reference} already."
- expected = /Referenced <a.+>[^\s]+<\/a> already/
- expect(gfm(actual)).to match(expected)
- end
-
- it 'should not link with an invalid id' do
- # Modify the reference string so it's still parsed, but is invalid
- if object.is_a?(Commit)
- reference.gsub!(/^(.).+$/, '\1' + '12345abcd')
- else
- reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
- end
- expect(gfm(actual)).to eq(actual)
- end
-
- it 'should include a title attribute' do
- if object.is_a?(Commit)
- title = object.link_title
- else
- title = "#{object.class.to_s.titlecase}: #{object.title}"
- end
- expect(gfm(actual)).to match(/title="#{title}"/)
- end
-
- it 'should include standard gfm classes' do
- css = object.class.to_s.underscore
- expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
- end
- end
-
- describe "referencing an issue" do
- let(:object) { issue }
- let(:reference) { "##{issue.iid}" }
-
- include_examples 'referenced object'
- end
-
- context 'cross-repo references' do
- before(:all) do
- @other_project = create(:project, :public)
- @commit2 = @other_project.repository.commit
- @issue2 = create(:issue, project: @other_project)
- @merge_request2 = create(:merge_request,
- source_project: @other_project,
- target_project: @other_project)
- end
-
- describe 'referencing an issue in another project' do
- let(:object) { @issue2 }
- let(:reference) { "##{@issue2.iid}" }
-
- include_examples 'cross-project referenced object'
- end
-
- describe 'referencing an merge request in another project' do
- let(:object) { @merge_request2 }
- let(:reference) { "!#{@merge_request2.iid}" }
-
- include_examples 'cross-project referenced object'
- end
-
- describe 'referencing a commit in another project' do
- let(:object) { @commit2 }
- let(:reference) { "@#{@commit2.id}" }
-
- include_examples 'cross-project referenced object'
- end
- end
-
- describe "referencing a Jira issue" do
- let(:actual) { "Reference to JIRA-#{issue.iid}" }
- let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
- let(:reference) { "JIRA-#{issue.iid}" }
-
- before do
- jira = @project.create_jira_service if @project.jira_service.nil?
- properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"}
- jira.update_attributes(properties: properties, active: true)
- end
-
- after do
- @project.jira_service.destroy! unless @project.jira_service.nil?
- end
-
- it "should link using a valid id" do
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link with adjacent text" do
- # Wrap the reference in parenthesis
- expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
-
- # Append some text to the end of the reference
- expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
- to match(expected)
- end
-
- it "should keep whitespace intact" do
- actual = "Referenced #{reference} already."
- expected = /Referenced <a.+>[^\s]+<\/a> already/
- expect(gfm(actual)).to match(expected)
- end
-
- it "should not link with an invalid id" do
- # Modify the reference string so it's still parsed, but is invalid
- invalid_reference = actual.gsub(/(\d+)$/, "r45")
- expect(gfm(invalid_reference)).to eq(invalid_reference)
- end
-
- it "should include a title attribute" do
- title = "Issue in JIRA tracker"
- expect(gfm(actual)).to match(/title="#{title}"/)
- end
-
- it "should include standard gfm classes" do
- expect(gfm(actual)).to match(/class="\s?gfm gfm-issue\s?"/)
- end
- end
-
- describe "referencing a merge request" do
- let(:object) { merge_request }
- let(:reference) { "!#{merge_request.iid}" }
-
- include_examples 'referenced object'
- end
-
- describe "referencing a snippet" do
- let(:object) { snippet }
- let(:reference) { "$#{snippet.id}" }
- let(:actual) { "Reference to #{reference}" }
- let(:expected) { namespace_project_snippet_path(project.namespace, project, object) }
-
- it "should link using a valid id" do
- expect(gfm(actual)).to match(expected)
- end
-
- it "should link with adjacent text" do
- # Wrap the reference in parenthesis
- expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
-
- # Append some text to the end of the reference
- expect(gfm(actual.gsub(reference, "#{reference}, right?"))).to match(expected)
- end
-
- it "should keep whitespace intact" do
- actual = "Referenced #{reference} already."
- expected = /Referenced <a.+>[^\s]+<\/a> already/
- expect(gfm(actual)).to match(expected)
- end
-
- it "should not link with an invalid id" do
- # Modify the reference string so it's still parsed, but is invalid
- reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
- expect(gfm(actual)).to eq(actual)
- end
-
- it "should include a title attribute" do
- title = "Snippet: #{object.title}"
- expect(gfm(actual)).to match(/title="#{title}"/)
- end
-
- it "should include standard gfm classes" do
- css = object.class.to_s.underscore
- expect(gfm(actual)).to match(/class="\s?gfm gfm-snippet\s?"/)
- end
-
+ to have_selector('a.gfm.foo')
end
describe "referencing multiple objects" do
@@ -466,90 +44,159 @@ describe GitlabMarkdownHelper do
end
end
- describe "emoji" do
- it "matches at the start of a string" do
- expect(gfm(":+1:")).to match(/<img/)
+ context 'parse_tasks: true' do
+ before(:all) do
+ @source_text_asterisk = <<-EOT.strip_heredoc
+ * [ ] valid unchecked task
+ * [x] valid lowercase checked task
+ * [X] valid uppercase checked task
+ * [ ] valid unchecked nested task
+ * [x] valid checked nested task
+
+ [ ] not an unchecked task - no list item
+ [x] not a checked task - no list item
+
+ * [ ] not an unchecked task - too many spaces
+ * [x ] not a checked task - too many spaces
+ * [] not an unchecked task - no spaces
+ * Not a task [ ] - not at beginning
+ EOT
+
+ @source_text_dash = <<-EOT.strip_heredoc
+ - [ ] valid unchecked task
+ - [x] valid lowercase checked task
+ - [X] valid uppercase checked task
+ - [ ] valid unchecked nested task
+ - [x] valid checked nested task
+ EOT
+ end
+
+ it 'should render checkboxes at beginning of asterisk list items' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+ expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid lowercase checked task/
+ )
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid uppercase checked task/
+ )
end
- it "matches at the end of a string" do
- expect(gfm("This gets a :-1:")).to match(/<img/)
- end
+ it 'should render checkboxes at beginning of dash list items' do
+ rendered_text = markdown(@source_text_dash, parse_tasks: true)
- it "matches with adjacent text" do
- expect(gfm("+1 (:+1:)")).to match(/<img/)
+ expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid lowercase checked task/
+ )
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid uppercase checked task/
+ )
end
- it "has a title attribute" do
- expect(gfm(":-1:")).to match(/title=":-1:"/)
- end
+ it 'should render checkboxes for nested tasks' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
- it "has an alt attribute" do
- expect(gfm(":-1:")).to match(/alt=":-1:"/)
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid unchecked nested task/
+ )
+ expect(rendered_text).to match(
+ /<input.*checkbox.*valid checked nested task/
+ )
end
- it "has an emoji class" do
- expect(gfm(":+1:")).to match('class="emoji"')
- end
+ it 'should not be confused by whitespace before bullets' do
+ rendered_text_asterisk = markdown(@source_text_asterisk,
+ parse_tasks: true)
+ rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
- it "sets height and width" do
- actual = gfm(":+1:")
- expect(actual).to match(/width="20"/)
- expect(actual).to match(/height="20"/)
+ expect(rendered_text_asterisk).to match(
+ /<input.*checkbox.*valid unchecked nested task/
+ )
+ expect(rendered_text_asterisk).to match(
+ /<input.*checkbox.*valid checked nested task/
+ )
+ expect(rendered_text_dash).to match(
+ /<input.*checkbox.*valid unchecked nested task/
+ )
+ expect(rendered_text_dash).to match(
+ /<input.*checkbox.*valid checked nested task/
+ )
end
- it "keeps whitespace intact" do
- expect(gfm('This deserves a :+1: big time.')).
- to match(/deserves a <img.+> big time/)
- end
+ it 'should not render checkboxes outside of list items' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
- it "ignores invalid emoji" do
- expect(gfm(":invalid-emoji:")).not_to match(/<img/)
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not an unchecked task - no list item/
+ )
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not a checked task - no list item/
+ )
end
- it "should work independent of reference links (i.e. without @project being set)" do
- @project = nil
- expect(gfm(":+1:")).to match(/<img/)
+ it 'should not render checkboxes with invalid formatting' do
+ rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
+
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not an unchecked task - too many spaces/
+ )
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not a checked task - too many spaces/
+ )
+ expect(rendered_text).not_to match(
+ /<input.*checkbox.*not an unchecked task - no spaces/
+ )
+ expect(rendered_text).not_to match(
+ /Not a task.*<input.*checkbox.*not at beginning/
+ )
end
end
end
- describe "#link_to_gfm" do
+ describe '#link_to_gfm' do
let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) }
let(:issues) { create_list(:issue, 2, project: project) }
- it "should handle references nested in links with all the text" do
+ it 'should handle references nested in links with all the text' do
actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path)
+ doc = Nokogiri::HTML.parse(actual)
- # Break the result into groups of links with their content, without
- # closing tags
- groups = actual.split("</a>")
+ # Make sure we didn't create invalid markup
+ expect(doc.errors).to be_empty
# Leading commit link
- expect(groups[0]).to match(/href="#{commit_path}"/)
- expect(groups[0]).to match(/This should finally fix $/)
+ expect(doc.css('a')[0].attr('href')).to eq commit_path
+ expect(doc.css('a')[0].text).to eq 'This should finally fix '
# First issue link
- expect(groups[1]).
- to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[0])}"/)
- expect(groups[1]).to match(/##{issues[0].iid}$/)
+ expect(doc.css('a')[1].attr('href')).
+ to eq namespace_project_issue_path(project.namespace, project, issues[0])
+ expect(doc.css('a')[1].text).to eq "##{issues[0].iid}"
# Internal commit link
- expect(groups[2]).to match(/href="#{commit_path}"/)
- expect(groups[2]).to match(/ and /)
+ expect(doc.css('a')[2].attr('href')).to eq commit_path
+ expect(doc.css('a')[2].text).to eq ' and '
# Second issue link
- expect(groups[3]).
- to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[1])}"/)
- expect(groups[3]).to match(/##{issues[1].iid}$/)
+ expect(doc.css('a')[3].attr('href')).
+ to eq namespace_project_issue_path(project.namespace, project, issues[1])
+ expect(doc.css('a')[3].text).to eq "##{issues[1].iid}"
# Trailing commit link
- expect(groups[4]).to match(/href="#{commit_path}"/)
- expect(groups[4]).to match(/ for real$/)
+ expect(doc.css('a')[4].attr('href')).to eq commit_path
+ expect(doc.css('a')[4].text).to eq ' for real'
end
- it "should forward HTML options" do
+ it 'should forward HTML options' do
actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
- expect(actual).to have_selector 'a.gfm.gfm-commit.foo'
+ doc = Nokogiri::HTML.parse(actual)
+
+ expect(doc.css('a')).to satisfy do |v|
+ # 'foo' gets added to all links
+ v.all? { |a| a.attr('class').match(/foo$/) }
+ end
end
it "escapes HTML passed in as the body" do
@@ -560,20 +207,7 @@ describe GitlabMarkdownHelper do
end
describe "#markdown" do
- it "should handle references in paragraphs" do
- actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n"
- expected = namespace_project_commit_path(project.namespace, project, commit)
- expect(markdown(actual)).to match(expected)
- end
-
- it "should handle references in headers" do
- actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
-
- expect(markdown(actual, no_header_anchors: true)).
- to match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
- expect(markdown(actual, no_header_anchors: true)).
- to match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
- end
+ # TODO (rspeicher) - This block tests multiple different contexts. Break this up!
it "should add ids and links to headers" do
# Test every rule except nested tags.
@@ -590,35 +224,15 @@ describe GitlabMarkdownHelper do
)
end
- it "should handle references in lists" do
- project.team << [user, :master]
-
- actual = "\n* dark: ##{issue.iid}\n* light by @#{member.user.username}"
-
- expect(markdown(actual)).
- to match(%r{<li>dark: <a.+>##{issue.iid}</a></li>})
- expect(markdown(actual)).
- to match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
- end
-
- it "should not link the apostrophe to issue 39" do
- project.team << [user, :master]
- allow(project.issues).
- to receive(:where).with(iid: '39').and_return([issue])
-
- actual = "Yes, it is @#{member.user.username}'s task."
- expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/
- expect(markdown(actual)).to match(expected)
- end
+ # REFERENCES (PART TWO: THE REVENGE) ---------------------------------------
- it "should not link the apostrophe to issue 39 in code blocks" do
- project.team << [user, :master]
- allow(project.issues).
- to receive(:where).with(iid: '39').and_return([issue])
+ it "should handle references in headers" do
+ actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
- actual = "Yes, `it is @#{member.user.username}'s task.`"
- expected = /Yes, <code>it is @gfm\'s task.<\/code>/
- expect(markdown(actual)).to match(expected)
+ expect(markdown(actual, no_header_anchors: true)).
+ to match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
+ expect(markdown(actual, no_header_anchors: true)).
+ to match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
end
it "should handle references in <em>" do
@@ -628,118 +242,120 @@ describe GitlabMarkdownHelper do
to match(%r{Apply <em><a.+>!#{merge_request.iid}</a></em>})
end
- it "should handle tables" do
- actual = %Q{| header 1 | header 2 |
-| -------- | -------- |
-| cell 1 | cell 2 |
-| cell 3 | cell 4 |}
-
- expect(markdown(actual)).to match(/\A<table/)
- end
+ # CODE BLOCKS -------------------------------------------------------------
it "should leave code blocks untouched" do
+ allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:user_color_scheme_class).and_return(:white)
target_html = "<pre class=\"code highlight white plaintext\"><code>some code from $#{snippet.id}\nhere too\n</code></pre>\n"
- expect(helper.markdown("\n some code from $#{snippet.id}\n here too\n")).
+ expect(markdown("\n some code from $#{snippet.id}\n here too\n")).
to eq(target_html)
- expect(helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")).
+ expect(markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")).
to eq(target_html)
end
it "should leave inline code untouched" do
- expect(markdown("\nDon't use `$#{snippet.id}` here.\n")).to eq(
- "<p>Don't use <code>$#{snippet.id}</code> here.</p>\n"
- )
+ expect(markdown("Don't use `$#{snippet.id}` here.")).
+ to eq "<p>Don't use <code>$#{snippet.id}</code> here.</p>\n"
end
+ # REF-LIKE AUTOLINKS? -----------------------------------------------------
+ # Basically: Don't parse references inside `<a>` tags.
+
it "should leave ref-like autolinks untouched" do
expect(markdown("look at http://example.tld/#!#{merge_request.iid}")).to eq("<p>look at <a href=\"http://example.tld/#!#{merge_request.iid}\">http://example.tld/#!#{merge_request.iid}</a></p>\n")
end
it "should leave ref-like href of 'manual' links untouched" do
- expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n")
+ expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\" class=\"gfm gfm-merge_request\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n")
end
it "should leave ref-like src of images untouched" do
expect(markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})")).to eq("<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.iid}\" alt=\"some image\"></p>\n")
end
- it "should generate absolute urls for refs" do
- expect(markdown("##{issue.iid}")).to include(namespace_project_issue_path(project.namespace, project, issue))
- end
+ # RELATIVE URLS -----------------------------------------------------------
+ # TODO (rspeicher): These belong in a relative link filter spec
- it "should generate absolute urls for emoji" do
- expect(markdown(':smile:')).to(
- include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/#{Emoji.emoji_filename('smile')}.png))
- )
- end
+ context 'relative links' do
+ context 'with a valid repository' do
+ before do
+ @repository = project.repository
+ @ref = 'markdown'
+ end
- it "should generate absolute urls for emoji if relative url is present" do
- allow(Gitlab.config.gitlab).to receive(:url).and_return('http://localhost/gitlab/root')
- expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
- end
+ it "should handle relative urls for a file in master" do
+ actual = "[GitLab API doc](doc/api/README.md)\n"
+ expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
- it "should generate absolute urls for emoji if asset_host is present" do
- allow(Gitlab::Application.config).to receive(:asset_host).and_return("https://cdn.example.com")
- ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
- expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
- end
+ it "should handle relative urls for a file in master with an anchor" do
+ actual = "[GitLab API doc](doc/api/README.md#section)\n"
+ expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md#section\">GitLab API doc</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
+ it "should not handle relative urls for the current file with an anchor" do
+ actual = "[GitLab API doc](#section)\n"
+ expected = "<p><a href=\"#section\">GitLab API doc</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
- it "should handle relative urls for a file in master" do
- actual = "[GitLab API doc](doc/api/README.md)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ it "should handle relative urls for a directory in master" do
+ actual = "[GitLab API doc](doc/api)\n"
+ expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
- it "should handle relative urls for a file in master with an anchor" do
- actual = "[GitLab API doc](doc/api/README.md#section)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md#section\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ it "should handle absolute urls" do
+ actual = "[GitLab](https://www.gitlab.com)\n"
+ expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
- it "should not handle relative urls for the current file with an anchor" do
- actual = "[GitLab API doc](#section)\n"
- expected = "<p><a href=\"#section\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ it "should handle relative urls in reference links for a file in master" do
+ actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
+ expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
- it "should handle relative urls for a directory in master" do
- actual = "[GitLab API doc](doc/api)\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ it "should handle relative urls in reference links for a directory in master" do
+ actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
+ expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc directory</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
- it "should handle absolute urls" do
- actual = "[GitLab](https://www.gitlab.com)\n"
- expected = "<p><a href=\"https://www.gitlab.com\">GitLab</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ it "should not handle malformed relative urls in reference links for a file in master" do
+ actual = "[GitLab readme]: doc/api/README.md\n"
+ expected = ""
+ expect(markdown(actual)).to match(expected)
+ end
- it "should handle relative urls in reference links for a file in master" do
- actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ it 'should allow whitelisted HTML tags from the user' do
+ actual = '<dl><dt>Term</dt><dd>Definition</dd></dl>'
+ expect(markdown(actual)).to match(actual)
+ end
+ end
- it "should handle relative urls in reference links for a directory in master" do
- actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
- expected = "<p><a href=\"/#{project.path_with_namespace}/tree/#{@ref}/doc/api\">GitLab API doc directory</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
+ context 'with an empty repository' do
+ before do
+ @project = create(:empty_project)
+ @repository = @project.repository
+ end
- it "should not handle malformed relative urls in reference links for a file in master" do
- actual = "[GitLab readme]: doc/api/README.md\n"
- expected = ""
- expect(markdown(actual)).to match(expected)
+ it "should not touch relative urls" do
+ actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
+ expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
+ expect(markdown(actual)).to match(expected)
+ end
+ end
end
- it 'should allow whitelisted HTML tags from the user' do
- actual = '<dl><dt>Term</dt><dd>Definition</dd></dl>'
- expect(markdown(actual)).to match(actual)
- end
+ # SANITIZATION ------------------------------------------------------------
+ # TODO (rspeicher): These are testing SanitizationFilter, not `markdown`
it 'should sanitize tags that are not whitelisted' do
actual = '<textarea>no inputs allowed</textarea> <blink>no blinks</blink>'
@@ -767,20 +383,7 @@ describe GitlabMarkdownHelper do
end
end
- describe 'markdown for empty repository' do
- before do
- @project = empty_project
- @repository = empty_project.repository
- end
-
- it "should not touch relative urls" do
- actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
- expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
- expect(markdown(actual)).to match(expected)
- end
- end
-
- describe "#render_wiki_content" do
+ describe '#render_wiki_content' do
before do
@wiki = double('WikiPage')
allow(@wiki).to receive(:content).and_return('wiki content')
@@ -803,114 +406,4 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki)
end
end
-
- describe '#gfm_with_tasks' do
- before(:all) do
- @source_text_asterisk = <<EOT.gsub(/^\s{8}/, '')
- * [ ] valid unchecked task
- * [x] valid lowercase checked task
- * [X] valid uppercase checked task
- * [ ] valid unchecked nested task
- * [x] valid checked nested task
-
- [ ] not an unchecked task - no list item
- [x] not a checked task - no list item
-
- * [ ] not an unchecked task - too many spaces
- * [x ] not a checked task - too many spaces
- * [] not an unchecked task - no spaces
- * Not a task [ ] - not at beginning
-EOT
-
- @source_text_dash = <<EOT.gsub(/^\s{8}/, '')
- - [ ] valid unchecked task
- - [x] valid lowercase checked task
- - [X] valid uppercase checked task
- - [ ] valid unchecked nested task
- - [x] valid checked nested task
-EOT
- end
-
- it 'should render checkboxes at beginning of asterisk list items' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
- expect(rendered_text).to match(
- /<input.*checkbox.*valid lowercase checked task/
- )
- expect(rendered_text).to match(
- /<input.*checkbox.*valid uppercase checked task/
- )
- end
-
- it 'should render checkboxes at beginning of dash list items' do
- rendered_text = markdown(@source_text_dash, parse_tasks: true)
-
- expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
- expect(rendered_text).to match(
- /<input.*checkbox.*valid lowercase checked task/
- )
- expect(rendered_text).to match(
- /<input.*checkbox.*valid uppercase checked task/
- )
- end
-
- it 'should render checkboxes for nested tasks' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).to match(
- /<input.*checkbox.*valid unchecked nested task/
- )
- expect(rendered_text).to match(
- /<input.*checkbox.*valid checked nested task/
- )
- end
-
- it 'should not be confused by whitespace before bullets' do
- rendered_text_asterisk = markdown(@source_text_asterisk,
- parse_tasks: true)
- rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
-
- expect(rendered_text_asterisk).to match(
- /<input.*checkbox.*valid unchecked nested task/
- )
- expect(rendered_text_asterisk).to match(
- /<input.*checkbox.*valid checked nested task/
- )
- expect(rendered_text_dash).to match(
- /<input.*checkbox.*valid unchecked nested task/
- )
- expect(rendered_text_dash).to match(
- /<input.*checkbox.*valid checked nested task/
- )
- end
-
- it 'should not render checkboxes outside of list items' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not an unchecked task - no list item/
- )
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not a checked task - no list item/
- )
- end
-
- it 'should not render checkboxes with invalid formatting' do
- rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
-
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not an unchecked task - too many spaces/
- )
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not a checked task - too many spaces/
- )
- expect(rendered_text).not_to match(
- /<input.*checkbox.*not an unchecked task - no spaces/
- )
- expect(rendered_text).not_to match(
- /Not a task.*<input.*checkbox.*not at beginning/
- )
- end
- end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 1e64a201942..0b7e3b1d11f 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
describe LabelsHelper do
- it { expect(text_color_for_bg('#EEEEEE')).to eq('#333') }
- it { expect(text_color_for_bg('#222E2E')).to eq('#FFF') }
+ it { expect(text_color_for_bg('#EEEEEE')).to eq('#333333') }
+ it { expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF') }
end
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
new file mode 100644
index 00000000000..57dcc2161d3
--- /dev/null
+++ b/spec/javascripts/shortcuts_issuable_spec.js.coffee
@@ -0,0 +1,83 @@
+#= require jquery
+#= require jasmine-fixture
+
+#= require shortcuts_issuable
+
+describe 'ShortcutsIssuable', ->
+ beforeEach ->
+ @shortcut = new ShortcutsIssuable()
+
+ describe '#replyWithSelectedText', ->
+ # Stub window.getSelection to return the provided String.
+ stubSelection = (text) ->
+ window.getSelection = -> text
+
+ beforeEach ->
+ @selector = 'form.js-main-target-form textarea#note_note'
+ affix(@selector)
+
+ describe 'with empty selection', ->
+ it 'does nothing', ->
+ stubSelection('')
+ @shortcut.replyWithSelectedText()
+ expect($(@selector).val()).toBe('')
+
+ describe 'with any selection', ->
+ beforeEach ->
+ stubSelection('Selected text.')
+
+ it 'leaves existing input intact', ->
+ $(@selector).val('This text was already here.')
+ expect($(@selector).val()).toBe('This text was already here.')
+
+ @shortcut.replyWithSelectedText()
+ expect($(@selector).val()).
+ toBe("This text was already here.\n> Selected text.\n\n")
+
+ it 'triggers `input`', ->
+ triggered = false
+ $(@selector).on 'input', -> triggered = true
+ @shortcut.replyWithSelectedText()
+
+ expect(triggered).toBe(true)
+
+ it 'triggers `focus`', ->
+ focused = false
+ $(@selector).on 'focus', -> focused = true
+ @shortcut.replyWithSelectedText()
+
+ expect(focused).toBe(true)
+
+ describe 'with a one-line selection', ->
+ it 'quotes the selection', ->
+ stubSelection('This text has been selected.')
+
+ @shortcut.replyWithSelectedText()
+
+ expect($(@selector).val()).
+ toBe("> This text has been selected.\n\n")
+
+ describe 'with a multi-line selection', ->
+ it 'quotes the selected lines as a group', ->
+ stubSelection(
+ """
+ Selected line one.
+
+ Selected line two.
+ Selected line three.
+
+ """
+ )
+
+ @shortcut.replyWithSelectedText()
+
+ expect($(@selector).val()).
+ toBe(
+ """
+ > Selected line one.
+ > Selected line two.
+ > Selected line three.
+
+
+ """
+ )
diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/stat_graph_contributors_graph_spec.js
index 1090cb7f620..78d39f1b428 100644
--- a/spec/javascripts/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/stat_graph_contributors_graph_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph_contributors_graph
+
describe("ContributorsGraph", function () {
describe("#set_x_domain", function () {
it("set the x_domain", function () {
diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js
index 9c1b588861d..ee90892eb48 100644
--- a/spec/javascripts/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/stat_graph_contributors_util_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph_contributors_util
+
describe("ContributorsStatGraphUtil", function () {
describe("#parse_log", function () {
diff --git a/spec/javascripts/stat_graph_spec.js b/spec/javascripts/stat_graph_spec.js
index b589af34610..4c652910cd6 100644
--- a/spec/javascripts/stat_graph_spec.js
+++ b/spec/javascripts/stat_graph_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph
+
describe("StatGraph", function () {
describe("#get_log", function () {
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
index 9bfa261a356..f4b01c9f27a 100644
--- a/spec/javascripts/support/jasmine.yml
+++ b/spec/javascripts/support/jasmine.yml
@@ -1,76 +1,20 @@
-# src_files
+# path to parent directory of spec_files
+# relative path from Rails.root
#
-# Return an array of filepaths relative to src_dir to include before jasmine specs.
-# Default: []
+# Alternatively accept an array of directory to include external spec files
+# spec_dir:
+# - spec/javascripts
+# - ../engine/spec/javascripts
#
-# EXAMPLE:
-#
-# src_files:
-# - lib/source1.js
-# - lib/source2.js
-# - dist/**/*.js
-#
-src_files:
- - assets/application.js
-
-# stylesheets
-#
-# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
-# Default: []
-#
-# EXAMPLE:
-#
-# stylesheets:
-# - css/style.css
-# - stylesheets/*.css
-#
-stylesheets:
- - stylesheets/**/*.css
+# defaults to spec/javascripts
+spec_dir: spec/javascripts
-# helpers
-#
-# Return an array of filepaths relative to spec_dir to include before jasmine specs.
-# Default: ["helpers/**/*.js"]
-#
-# EXAMPLE:
-#
-# helpers:
-# - helpers/**/*.js
-#
+# list of file expressions to include as helpers into spec runner
+# relative path from spec_dir
helpers:
- - helpers/**/*.js
+ - "helpers/**/*.{js.coffee,js,coffee}"
-# spec_files
-#
-# Return an array of filepaths relative to spec_dir to include.
-# Default: ["**/*[sS]pec.js"]
-#
-# EXAMPLE:
-#
-# spec_files:
-# - **/*[sS]pec.js
-#
+# list of file expressions to include as specs into spec runner
+# relative path from spec_dir
spec_files:
- - '**/*[sS]pec.js'
-
-# src_dir
-#
-# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
-# Default: project root
-#
-# EXAMPLE:
-#
-# src_dir: public
-#
-src_dir:
-
-# spec_dir
-#
-# Spec directory path. Your spec_files must be returned relative to this path.
-# Default: spec/javascripts
-#
-# EXAMPLE:
-#
-# spec_dir: spec/javascripts
-#
-spec_dir: spec/javascripts
+ - "**/*[Ss]pec.{js.coffee,js,coffee}"
diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb
index b4919802afe..4d73aec5a31 100644
--- a/spec/javascripts/support/jasmine_helper.rb
+++ b/spec/javascripts/support/jasmine_helper.rb
@@ -8,4 +8,8 @@
# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
#end
#
-
+#Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
+#Jasmine.configure do |config|
+# config.prevent_phantom_js_auto_install = true
+#end
+#
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 1c4503ae0ef..67378328336 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -57,10 +57,11 @@ describe Gitlab::GoogleCodeImport::Importer do
expect(issue.label_names).to include("Type: Enhancement")
expect(issue.title).to eq("Scrolling through tasks")
expect(issue.state).to eq("closed")
- expect(issue.description).to include("schattenpr...")
+ expect(issue.description).to include("schattenpr\\.\\.\\.")
expect(issue.description).to include("November 18, 2009 00:20")
- expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel \(like in fluxbox\).')
- expect(issue.description).to include('Patch is attached that adds two new mouse\-actions \(next\_taskprev\_task\)')
+ expect(issue.description).to include("Google Code")
+ expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel (like in fluxbox).')
+ expect(issue.description).to include('Patch is attached that adds two new mouse-actions (next_task+prev_task)')
expect(issue.description).to include('that can be used for exactly that purpose.')
expect(issue.description).to include('all the best!')
expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)')
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
new file mode 100644
index 00000000000..5ebdc8926e2
--- /dev/null
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -0,0 +1,128 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe CommitRangeReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ let(:project) { create(:project) }
+ let(:commit1) { project.repository.commit }
+ let(:commit2) { project.repository.commit("HEAD~2") }
+
+ it 'requires project context' do
+ expect { described_class.call('Commit Range 1c002d..d200c1', {}) }.
+ 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}>Commit Range #{commit1.id}..#{commit2.id}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ let(:reference) { "#{commit1.id}...#{commit2.id}" }
+ let(:reference2) { "#{commit1.id}..#{commit2.id}" }
+
+ it 'links to a valid two-dot reference' do
+ doc = filter("See #{reference2}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_compare_url(project.namespace, project, from: "#{commit1.id}^", to: commit2.id)
+ end
+
+ it 'links to a valid three-dot reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id)
+ end
+
+ it 'links to a valid short ID' do
+ reference = "#{commit1.short_id}...#{commit2.id}"
+ reference2 = "#{commit1.id}...#{commit2.short_id}"
+
+ expect(filter("See #{reference}").css('a').first.text).to eq reference
+ expect(filter("See #{reference2}").css('a').first.text).to eq reference2
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("See (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs' do
+ exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
+
+ expect(project).to receive(:valid_repo?).and_return(true)
+ expect(project.repository).to receive(:commit).with(commit1.id.reverse)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("See #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Commits #{commit1.id} through #{commit2.id}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("See #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("See #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path option' do
+ doc = filter("See #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, namespace: namespace) }
+ let(:commit1) { project.repository.commit }
+ let(:commit2) { project.repository.commit("HEAD~2") }
+ let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
+
+ context 'when user can access reference' do
+ before { allow_cross_reference! }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: commit2.id)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id.reverse}...#{commit2.id}"
+ expect(filter(act).to_html).to eq exp
+
+ exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'when user cannot access reference' do
+ before { disallow_cross_reference! }
+
+ it 'ignores valid references' do
+ exp = act = "See #{reference}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
new file mode 100644
index 00000000000..71fd2db2c58
--- /dev/null
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -0,0 +1,118 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe CommitReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ let(:project) { create(:project) }
+ let(:commit) { project.repository.commit }
+
+ it 'requires project context' do
+ expect { described_class.call('Commit 1c002d', {}) }.
+ 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}>Commit #{commit.id}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ let(:reference) { commit.id }
+
+ # Let's test a variety of commit SHA sizes just to be paranoid
+ [6, 8, 12, 18, 20, 32, 40].each do |size|
+ it "links to a valid reference of #{size} characters" do
+ doc = filter("See #{reference[0...size]}")
+
+ expect(doc.css('a').first.text).to eq reference[0...size]
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_commit_url(project.namespace, project, reference)
+ end
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("See (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs' do
+ exp = act = "See #{reference.reverse}"
+
+ expect(project).to receive(:valid_repo?).and_return(true)
+ expect(project.repository).to receive(:commit).with(reference.reverse)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("See #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq commit.link_title
+ end
+
+ it 'escapes the title attribute' do
+ allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
+
+ doc = filter("See #{reference}")
+ expect(doc.text).to eq "See #{commit.id}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("See #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("See #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("See #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, namespace: namespace) }
+ let(:commit) { project.repository.commit }
+ let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" }
+
+ context 'when user can access reference' do
+ before { allow_cross_reference! }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Committed #{project2.path_with_namespace}##{commit.id.reverse}"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'when user cannot access reference' do
+ before { disallow_cross_reference! }
+
+ it 'ignores valid references' do
+ exp = act = "See #{reference}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
new file mode 100644
index 00000000000..4698d6138c2
--- /dev/null
+++ b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe CrossProjectReference do
+ # context in the html-pipeline sense, not in the rspec sense
+ let(:context) do
+ {
+ current_user: double('user'),
+ project: double('project')
+ }
+ end
+
+ include described_class
+
+ describe '#project_from_ref' do
+ context 'when no project was referenced' do
+ it 'returns the project from context' do
+ expect(project_from_ref(nil)).to eq context[:project]
+ end
+ end
+
+ context 'when referenced project does not exist' do
+ it 'returns nil' do
+ expect(project_from_ref('invalid/reference')).to be_nil
+ end
+ end
+
+ context 'when referenced project exists' do
+ let(:project2) { double('referenced project') }
+
+ before do
+ expect(Project).to receive(:find_with_namespace).
+ with('cross/reference').and_return(project2)
+ end
+
+ context 'and the user has permission to read it' do
+ it 'returns the referenced project' do
+ expect(self).to receive(:user_can_reference_project?).
+ with(project2).and_return(true)
+
+ expect(project_from_ref('cross/reference')).to eq project2
+ end
+ end
+
+ context 'and the user does not have permission to read it' do
+ it 'returns nil' do
+ expect(self).to receive(:user_can_reference_project?).
+ with(project2).and_return(false)
+
+ expect(project_from_ref('cross/reference')).to be_nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
new file mode 100644
index 00000000000..18d55c4818f
--- /dev/null
+++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe EmojiFilter do
+ def filter(html, contexts = {})
+ described_class.call(html, contexts)
+ end
+
+ before do
+ ActionController::Base.asset_host = 'https://foo.com'
+ end
+
+ it 'replaces supported emoji' do
+ doc = filter('<p>:heart:</p>')
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
+ end
+
+ it 'ignores unsupported emoji' do
+ exp = act = '<p>:foo:</p>'
+ doc = filter(act)
+ expect(doc.to_html).to match Regexp.escape(exp)
+ end
+
+ it 'correctly encodes the URL' do
+ doc = filter('<p>:+1:</p>')
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
+ end
+
+ it 'matches at the start of a string' do
+ doc = filter(':+1:')
+ expect(doc.css('img').size).to eq 1
+ end
+
+ it 'matches at the end of a string' do
+ doc = filter('This gets a :-1:')
+ expect(doc.css('img').size).to eq 1
+ end
+
+ it 'matches with adjacent text' do
+ doc = filter('+1 (:+1:)')
+ expect(doc.css('img').size).to eq 1
+ end
+
+ it 'matches multiple emoji in a row' do
+ doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
+ expect(doc.css('img').size).to eq 3
+ end
+
+ it 'has a title attribute' do
+ doc = filter(':-1:')
+ expect(doc.css('img').first.attr('title')).to eq ':-1:'
+ end
+
+ it 'has an alt attribute' do
+ doc = filter(':-1:')
+ expect(doc.css('img').first.attr('alt')).to eq ':-1:'
+ end
+
+ it 'has an align attribute' do
+ doc = filter(':8ball:')
+ expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
+ end
+
+ it 'has an emoji class' do
+ doc = filter(':cat:')
+ expect(doc.css('img').first.attr('class')).to eq 'emoji'
+ end
+
+ it 'has height and width attributes' do
+ doc = filter(':dog:')
+ img = doc.css('img').first
+
+ expect(img.attr('width')).to eq '20'
+ expect(img.attr('height')).to eq '20'
+ end
+
+ it 'keeps whitespace intact' do
+ doc = filter('This deserves a :+1:, big time.')
+
+ expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
+ end
+
+ it 'uses a custom asset_root context' do
+ root = Gitlab.config.gitlab.url + 'gitlab/root'
+
+ doc = filter(':smile:', asset_root: root)
+ expect(doc.css('img').first.attr('src')).to start_with(root)
+ end
+
+ it 'uses a custom asset_host context' do
+ ActionController::Base.asset_host = 'https://cdn.example.com'
+
+ doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
+ expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
new file mode 100644
index 00000000000..27e930ef7da
--- /dev/null
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe ExternalIssueReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ def helper
+ IssuesHelper
+ end
+
+ let(:project) { create(:empty_project) }
+ let(:issue) { double('issue', iid: 123) }
+
+ context 'JIRA issue references' do
+ let(:reference) { "JIRA-#{issue.iid}" }
+
+ before do
+ jira = project.create_jira_service
+
+ props = {
+ 'title' => 'JIRA tracker',
+ 'project_url' => 'http://jira.example/issues/?jql=project=A',
+ 'issues_url' => 'http://jira.example/browse/:id',
+ 'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa'
+ }
+
+ jira.update_attributes(properties: props, active: true)
+ end
+
+ after do
+ project.jira_service.destroy
+ end
+
+ it 'requires project context' do
+ expect { described_class.call('Issue JIRA-123', {}) }.
+ 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}>Issue JIRA-#{issue.iid}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ it 'ignores valid references when using default tracker' do
+ expect(project).to receive(:default_issues_tracker?).and_return(true)
+
+ exp = act = "Issue #{reference}"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ %w(pre code a style).each do |elem|
+ it "ignores references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ it 'links to a valid reference' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('href'))
+ .to eq helper.url_for_issue(reference, project)
+ end
+
+ it 'links to the external tracker' do
+ doc = filter("Issue #{reference}")
+ link = doc.css('a').first.attr('href')
+
+ expect(link).to eq "http://jira.example/browse/#{reference}"
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Issue (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
+ end
+
+ it 'escapes the title attribute' do
+ allow(project.external_issue_tracker).to receive(:title).
+ and_return(%{"></a>whatever<a title="})
+
+ doc = filter("Issue #{reference}")
+ expect(doc.text).to eq "Issue #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("Issue #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Issue #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
new file mode 100644
index 00000000000..f95b37d6954
--- /dev/null
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -0,0 +1,133 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe IssueReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ def helper
+ IssuesHelper
+ end
+
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+
+ it 'requires project context' do
+ expect { described_class.call('Issue #123', {}) }.
+ 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}>Issue ##{issue.iid}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ let(:reference) { "##{issue.iid}" }
+
+ it 'ignores valid references when using non-default tracker' do
+ expect(project).to receive(:issue_exists?).with(issue.iid).and_return(false)
+
+ exp = act = "Issue ##{issue.iid}"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq helper.url_for_issue(issue.iid, project)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid issue IDs' do
+ exp = act = "Fixed ##{issue.iid + 1}"
+
+ expect(project).to receive(:issue_exists?).with(issue.iid + 1)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
+ end
+
+ it 'escapes the title attribute' do
+ issue.update_attribute(:title, %{"></a>whatever<a title="})
+
+ doc = filter("Issue #{reference}")
+ expect(doc.text).to eq "Issue #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("Issue #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("Issue #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Issue #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, namespace: namespace) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
+
+ context 'when user can access reference' do
+ before { allow_cross_reference! }
+
+ it 'ignores valid references when cross-reference project uses external tracker' do
+ expect_any_instance_of(Project).to receive(:issue_exists?).
+ with(issue.iid).and_return(false)
+
+ exp = act = "Issue ##{issue.iid}"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq helper.url_for_issue(issue.iid, project2)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid issue IDs on the referenced project' do
+ exp = act = "Fixed #{project2.path_with_namespace}##{issue.iid + 1}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'when user cannot access reference' do
+ before { disallow_cross_reference! }
+
+ it 'ignores valid references' do
+ exp = act = "See #{reference}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
new file mode 100644
index 00000000000..c84e568e172
--- /dev/null
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -0,0 +1,148 @@
+require 'spec_helper'
+require 'html/pipeline'
+
+module Gitlab::Markdown
+ describe LabelReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ let(:project) { create(:empty_project) }
+ let(:label) { create(:label, project: project) }
+ let(:reference) { "~#{label.id}" }
+
+ it 'requires project context' do
+ expect { described_class.call('Label ~123', {}) }.
+ 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}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ it 'includes default classes' do
+ doc = filter("Label #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("Label #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Label #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true)
+ end
+
+ describe 'label span element' do
+ it 'includes default classes' do
+ doc = filter("Label #{reference}")
+ expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
+ end
+
+ it 'includes a style attribute' do
+ doc = filter("Label #{reference}")
+ 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
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ end
+
+ it 'ignores invalid label IDs' do
+ exp = act = "Label ~#{label.id + 1}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'String-based single-word references' do
+ let(:label) { create(:label, name: 'gfm', project: project) }
+ let(:reference) { "~#{label.name}" }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See gfm'
+ end
+
+ it 'links with adjacent text' do
+ doc = 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.name.reverse}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'String-based multi-word references in quotes' do
+ let(:label) { create(:label, name: 'gfm references', project: project) }
+
+ context 'in single quotes' do
+ let(:reference) { "~'#{label.name}'" }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See gfm references'
+ end
+
+ it 'links with adjacent text' do
+ doc = 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.name.reverse}'"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'in double quotes' do
+ let(:reference) { %(~"#{label.name}") }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See gfm references'
+ end
+
+ it 'links with adjacent text' do
+ doc = 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.name.reverse}")
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
new file mode 100644
index 00000000000..0f66442269b
--- /dev/null
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe MergeRequestReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ let(:project) { create(:project) }
+ let(:merge) { create(:merge_request, source_project: project) }
+
+ it 'requires project context' do
+ expect { described_class.call('MergeRequest !123', {}) }.
+ 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}>Merge !#{merge.iid}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ let(:reference) { "!#{merge.iid}" }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_merge_request_url(project.namespace, project, merge)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Merge (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid merge IDs' do
+ exp = act = "Merge !#{merge.iid + 1}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("Merge #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
+ end
+
+ it 'escapes the title attribute' do
+ merge.update_attribute(:title, %{"></a>whatever<a title="})
+
+ doc = filter("Merge #{reference}")
+ expect(doc.text).to eq "Merge #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("Merge #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("Merge #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Merge #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:project, namespace: namespace) }
+ let(:merge) { create(:merge_request, source_project: project2) }
+ let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
+
+ context 'when user can access reference' do
+ before { allow_cross_reference! }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_merge_request_url(project2.namespace,
+ project, merge)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Merge (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid merge IDs on the referenced project' do
+ exp = act = "Merge #{project2.path_with_namespace}!#{merge.iid + 1}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'when user cannot access reference' do
+ before { disallow_cross_reference! }
+
+ it 'ignores valid references' do
+ exp = act = "See #{reference}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
new file mode 100644
index 00000000000..79533a90b55
--- /dev/null
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe SnippetReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ let(:project) { create(:empty_project) }
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:reference) { "$#{snippet.id}" }
+
+ it 'requires project context' do
+ expect { described_class.call('Snippet $123', {}) }.
+ 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}>Snippet #{reference}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_snippet_url(project.namespace, project, snippet)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("Snippet (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid snippet IDs' do
+ exp = act = "Snippet $#{snippet.id + 1}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'includes a title attribute' do
+ doc = filter("Snippet #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
+ end
+
+ it 'escapes the title attribute' do
+ snippet.update_attribute(:title, %{"></a>whatever<a title="})
+
+ doc = filter("Snippet #{reference}")
+ expect(doc.text).to eq "Snippet #{reference}"
+ end
+
+ it 'includes default classes' do
+ doc = filter("Snippet #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("Snippet #{reference}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Snippet #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+ end
+ end
+
+ context 'cross-project reference' do
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+ let(:project2) { create(:empty_project, namespace: namespace) }
+ let(:snippet) { create(:project_snippet, project: project2) }
+ let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
+
+ context 'when user can access reference' do
+ before { allow_cross_reference! }
+
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ end
+
+ it 'links with adjacent text' do
+ doc = filter("See (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid snippet IDs on the referenced project' do
+ exp = act = "See #{project2.path_with_namespace}$#{snippet.id + 1}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'when user cannot access reference' do
+ before { disallow_cross_reference! }
+
+ it 'ignores valid references' do
+ exp = act = "See #{reference}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
new file mode 100644
index 00000000000..a5eb927072e
--- /dev/null
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe UserReferenceFilter do
+ include ReferenceFilterSpecHelper
+
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ it 'requires project context' do
+ expect { described_class.call('Example @mention', {}) }.
+ to raise_error(ArgumentError, /:project/)
+ end
+
+ it 'ignores invalid users' do
+ exp = act = 'Hey @somebody'
+ expect(filter(act).to_html).to eq(exp)
+ end
+
+ %w(pre code a style).each do |elem|
+ it "ignores valid references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>Hey @#{user.username}</#{elem}>"
+ expect(filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'mentioning a user' do
+ it 'links to a User' do
+ doc = filter("Hey @#{user.username}")
+ expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
+ end
+
+ # TODO (rspeicher): This test might be overkill
+ it 'links to a User with a period' do
+ user = create(:user, name: 'alphA.Beta')
+
+ doc = filter("Hey @#{user.username}")
+ expect(doc.css('a').length).to eq 1
+ end
+
+ # TODO (rspeicher): This test might be overkill
+ it 'links to a User with an underscore' do
+ user = create(:user, name: 'ping_pong_king')
+
+ doc = filter("Hey @#{user.username}")
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ context 'mentioning a group' do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ it 'links to a Group that the current user can read' do
+ group.add_user(user, Gitlab::Access::DEVELOPER)
+
+ doc = filter("Hey @#{group.name}", current_user: user)
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
+
+ it 'ignores references to a Group that the current user cannot read' do
+ doc = filter("Hey @#{group.name}", current_user: user)
+ expect(doc.to_html).to eq "Hey @#{group.name}"
+ end
+ end
+
+ it 'links with adjacent text' do
+ skip 'TODO (rspeicher): Re-enable when usernames can\'t end in periods.'
+ doc = filter("Mention me (@#{user.username}.)")
+ expect(doc.to_html).to match(/\(<a.+>@#{user.username}<\/a>\.\)/)
+ end
+
+ it 'supports a special @all mention' do
+ doc = filter("Hey @all")
+ expect(doc.css('a').length).to eq 1
+ expect(doc.css('a').first.attr('href'))
+ .to eq urls.namespace_project_url(project.namespace, project)
+ end
+
+ it 'includes default classes' do
+ doc = filter("Hey @#{user.username}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
+ end
+
+ it 'includes an optional custom class' do
+ doc = filter("Hey @#{user.username}", reference_class: 'custom')
+ expect(doc.css('a').first.attr('class')).to include 'custom'
+ end
+
+ it 'supports an :only_path context' do
+ doc = filter("Hey @#{user.username}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.user_path(user)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index c9fb62b61ae..6fba140f69d 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -74,7 +74,7 @@ describe Gitlab::ReferenceExtractor do
end
it 'handles all possible kinds of references' do
- accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym }
+ accessors = described_class::TYPES.map { |t| "#{t}s".to_sym }
expect(subject).to respond_to(*accessors)
end
@@ -106,6 +106,15 @@ describe Gitlab::ReferenceExtractor do
expect(subject.merge_requests).to eq([@m1, @m0])
end
+ it 'accesses valid labels' do
+ @l0 = create(:label, title: 'one', project: project)
+ @l1 = create(:label, title: 'two', project: project)
+ @l2 = create(:label)
+
+ subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
+ expect(subject.labels).to eq([@l0, @l1])
+ end
+
it 'accesses valid snippets' do
@s0 = create(:project_snippet, project: project)
@s1 = create(:project_snippet, project: project)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e6d5545f812..327f3e6d23c 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -110,17 +110,22 @@ describe API::API, api: true do
end
it 'should return 400 error if name not given' do
- post api('/users', admin), email: 'test@example.com', password: 'pass1234'
+ post api('/users', admin), attributes_for(:user).except(:name)
expect(response.status).to eq(400)
end
it 'should return 400 error if password not given' do
- post api('/users', admin), email: 'test@example.com', name: 'test'
+ post api('/users', admin), attributes_for(:user).except(:password)
expect(response.status).to eq(400)
end
- it "should return 400 error if email not given" do
- post api('/users', admin), password: 'pass1234', name: 'test'
+ it 'should return 400 error if email not given' do
+ post api('/users', admin), attributes_for(:user).except(:email)
+ expect(response.status).to eq(400)
+ end
+
+ it 'should return 400 error if username not given' do
+ post api('/users', admin), attributes_for(:user).except(:username)
expect(response.status).to eq(400)
end
diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb
new file mode 100644
index 00000000000..bcee5715cad
--- /dev/null
+++ b/spec/support/reference_filter_spec_helper.rb
@@ -0,0 +1,47 @@
+# Common methods and setup for Gitlab::Markdown reference filter specs
+#
+# Must be included into specs manually
+module ReferenceFilterSpecHelper
+ extend ActiveSupport::Concern
+
+ included do
+ before { set_default_url_options }
+ end
+
+ # Allow *_url helpers to work
+ def set_default_url_options
+ Rails.application.routes.default_url_options = {
+ host: 'example.foo'
+ }
+ end
+
+ # Shortcut to Rails' auto-generated routes helpers, to avoid including the
+ # module
+ def urls
+ Rails.application.routes.url_helpers
+ end
+
+ # Perform `call` on the described class
+ #
+ # Automatically passes the current `project` value to the context if none is
+ # provided.
+ #
+ # html - String text to pass to the filter's `call` method.
+ # contexts - Hash context for the filter. (default: {project: project})
+ #
+ # Returns the String text returned by the filter's `call` method.
+ def filter(html, contexts = {})
+ contexts.reverse_merge!(project: project)
+ described_class.call(html, contexts)
+ end
+
+ def allow_cross_reference!
+ allow_any_instance_of(described_class).
+ to receive(:user_can_reference_project?).and_return(true)
+ end
+
+ def disallow_cross_reference!
+ allow_any_instance_of(described_class).
+ to receive(:user_can_reference_project?).and_return(false)
+ end
+end