Skip to content
Snippets Groups Projects
Commit c8a115c0 authored by ash wilson's avatar ash wilson Committed by Ash Wilson
Browse files

Link issues from comments and automatically close them

Any mention of Issues, MergeRequests, or Commits via GitLab-flavored markdown
references in descriptions, titles, or attached Notes creates a back-reference
Note that links to the original referencer. Furthermore, pushing commits with
commit messages that match a (configurable) regexp to a project's default
branch will close any issues mentioned by GFM in the matched closing phrase.
If accepting a merge request would close any Issues in this way, a banner is
appended to the merge request's main panel to indicate this.
parent 2b36dee6
No related branches found
No related tags found
2 merge requests!5081fixed command to update init script,!4507Linking objects from GFM references
require 'spec_helper'
describe Gitlab::ReferenceExtractor do
it 'extracts username references' do
subject.analyze "this contains a @user reference"
subject.users.should == ["user"]
end
it 'extracts issue references' do
subject.analyze "this one talks about issue #1234"
subject.issues.should == ["1234"]
end
it 'extracts merge request references' do
subject.analyze "and here's !43, a merge request"
subject.merge_requests.should == ["43"]
end
it 'extracts snippet ids' do
subject.analyze "snippets like $12 get extracted as well"
subject.snippets.should == ["12"]
end
it 'extracts commit shas' do
subject.analyze "commit shas 98cf0ae3 are pulled out as Strings"
subject.commits.should == ["98cf0ae3"]
end
it 'extracts multiple references and preserves their order' do
subject.analyze "@me and @you both care about this"
subject.users.should == ["me", "you"]
end
it 'leaves the original note unmodified' do
text = "issue #123 is just the worst, @user"
subject.analyze text
text.should == "issue #123 is just the worst, @user"
end
it 'handles all possible kinds of references' do
accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym }
subject.should respond_to(*accessors)
end
context 'with a project' do
let(:project) { create(:project_with_code) }
it 'accesses valid user objects on the project team' do
@u_foo = create(:user, username: 'foo')
@u_bar = create(:user, username: 'bar')
create(:user, username: 'offteam')
project.team << [@u_foo, :reporter]
project.team << [@u_bar, :guest]
subject.analyze "@foo, @baduser, @bar, and @offteam"
subject.users_for(project).should == [@u_foo, @u_bar]
end
it 'accesses valid issue objects' do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
subject.analyze "##{@i0.iid}, ##{@i1.iid}, and #999."
subject.issues_for(project).should == [@i0, @i1]
end
it 'accesses valid merge requests' do
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
subject.analyze "!999, !#{@m1.iid}, and !#{@m0.iid}."
subject.merge_requests_for(project).should == [@m1, @m0]
end
it 'accesses valid snippets' do
@s0 = create(:project_snippet, project: project)
@s1 = create(:project_snippet, project: project)
@s2 = create(:project_snippet)
subject.analyze "$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}"
subject.snippets_for(project).should == [@s0, @s1]
end
it 'accesses valid commits' do
commit = project.repository.commit("master")
subject.analyze "this references commits #{commit.sha[0..6]} and 012345"
extracted = subject.commits_for(project)
extracted.should have(1).item
extracted[0].sha.should == commit.sha
extracted[0].message.should == commit.message
end
end
end
require 'spec_helper'
 
describe Commit do
let(:commit) { create(:project_with_code).repository.commit }
let(:project) { create :project_with_code }
let(:commit) { project.repository.commit }
 
describe '#title' do
it "returns no_commit_message when safe_message is blank" do
Loading
Loading
@@ -46,4 +47,23 @@ describe Commit do
it { should respond_to(:id) }
it { should respond_to(:to_patch) }
end
describe '#closes_issues' do
let(:issue) { create :issue, project: project }
it 'detects issues that this commit is marked as closing' do
commit.stub(issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, safe_message: "Fixes ##{issue.iid}")
commit.closes_issues(project).should == [issue]
end
end
it_behaves_like 'a mentionable' do
let(:subject) { commit }
let(:mauthor) { create :user, email: commit.author_email }
let(:backref_text) { "commit #{subject.sha[0..5]}" }
let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
# Include the subject in the repository stub.
let(:extra_commits) { [subject] }
end
end
Loading
Loading
@@ -56,4 +56,10 @@ describe Issue do
Issue.open_for(user).count.should eq 2
end
end
it_behaves_like 'an editable mentionable' do
let(:subject) { create :issue, project: mproject }
let(:backref_text) { "issue ##{subject.iid}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
end
end
Loading
Loading
@@ -43,7 +43,6 @@ describe MergeRequest do
it { should include_module(Issuable) }
end
 
describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) }
 
Loading
Loading
@@ -104,4 +103,34 @@ describe MergeRequest do
end
end
 
describe 'detection of issues to be closed' do
let(:issue0) { create :issue, project: subject.project }
let(:issue1) { create :issue, project: subject.project }
let(:commit0) { mock('commit0', closes_issues: [issue0]) }
let(:commit1) { mock('commit1', closes_issues: [issue0]) }
let(:commit2) { mock('commit2', closes_issues: [issue1]) }
before do
subject.stub(unmerged_commits: [commit0, commit1, commit2])
end
it 'accesses the set of issues that will be closed on acceptance' do
subject.project.default_branch = subject.target_branch
subject.closes_issues.should == [issue0, issue1].sort_by(&:id)
end
it 'only lists issues as to be closed if it targets the default branch' do
subject.project.default_branch = 'master'
subject.target_branch = 'something-else'
subject.closes_issues.should be_empty
end
end
it_behaves_like 'an editable mentionable' do
let(:subject) { create :merge_request, source_project: mproject, target_project: mproject }
let(:backref_text) { "merge request !#{subject.iid}" }
let(:set_mentionable_text) { ->(txt){ subject.title = txt } }
end
end
Loading
Loading
@@ -72,7 +72,6 @@ describe Note do
end
 
let(:project) { create(:project) }
let(:commit) { project.repository.commit }
 
describe "Commit notes" do
let!(:note) { create(:note_on_commit, note: "+1 from me") }
Loading
Loading
@@ -131,7 +130,7 @@ describe Note do
describe "Merge request notes" do
let!(:note) { create(:note_on_merge_request, note: "+1 from me") }
 
it "should not be votable" do
it "should be votable" do
note.should be_votable
end
end
Loading
Loading
@@ -150,7 +149,7 @@ describe Note do
let(:author) { create(:user) }
let(:status) { 'new_status' }
 
subject { Note.create_status_change_note(thing, project, author, status) }
subject { Note.create_status_change_note(thing, project, author, status, nil) }
 
it 'creates and saves a Note' do
should be_a Note
Loading
Loading
@@ -161,6 +160,102 @@ describe Note do
its(:project) { should == thing.project }
its(:author) { should == author }
its(:note) { should =~ /Status changed to #{status}/ }
it 'appends a back-reference if a closing mentionable is supplied' do
commit = double('commit', gfm_reference: 'commit 123456')
n = Note.create_status_change_note(thing, project, author, status, commit)
n.note.should =~ /Status changed to #{status} by commit 123456/
end
end
describe '#create_cross_reference_note' do
let(:project) { create(:project_with_code) }
let(:author) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:mergereq) { create(:merge_request, target_project: project) }
let(:commit) { project.repository.commit }
# Test all of {issue, merge request, commit} in both the referenced and referencing
# roles, to ensure that the correct information can be inferred from any argument.
context 'issue from a merge request' do
subject { Note.create_cross_reference_note(issue, mergereq, author, project) }
it { should be_valid }
its(:noteable) { should == issue }
its(:project) { should == issue.project }
its(:author) { should == author }
its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
end
context 'issue from a commit' do
subject { Note.create_cross_reference_note(issue, commit, author, project) }
it { should be_valid }
its(:noteable) { should == issue }
its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" }
end
context 'merge request from an issue' do
subject { Note.create_cross_reference_note(mergereq, issue, author, project) }
it { should be_valid }
its(:noteable) { should == mergereq }
its(:project) { should == mergereq.project }
its(:note) { should == "_mentioned in issue ##{issue.iid}_" }
end
context 'commit from a merge request' do
subject { Note.create_cross_reference_note(commit, mergereq, author, project) }
it { should be_valid }
its(:noteable) { should == commit }
its(:project) { should == project }
its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
end
end
describe '#cross_reference_exists?' do
let(:project) { create :project }
let(:author) { create :user }
let(:issue) { create :issue }
let(:commit0) { double 'commit0', gfm_reference: 'commit 123456' }
let(:commit1) { double 'commit1', gfm_reference: 'commit 654321' }
before do
Note.create_cross_reference_note(issue, commit0, author, project)
end
it 'detects if a mentionable has already been mentioned' do
Note.cross_reference_exists?(issue, commit0).should be_true
end
it 'detects if a mentionable has not already been mentioned' do
Note.cross_reference_exists?(issue, commit1).should be_false
end
end
describe '#system?' do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:other) { create(:issue, project: project) }
let(:author) { create(:user) }
it 'should recognize user-supplied notes as non-system' do
@note = create(:note_on_issue)
@note.should_not be_system
end
it 'should identify status-change notes as system notes' do
@note = Note.create_status_change_note(issue, project, author, 'closed', nil)
@note.should be_system
end
it 'should identify cross-reference notes as system notes' do
@note = Note.create_cross_reference_note(issue, other, author, project)
@note.should be_system
end
end
 
describe :authorization do
Loading
Loading
@@ -208,4 +303,11 @@ describe Note do
it { @abilities.allowed?(@u3, :admin_note, @p1).should be_false }
end
end
it_behaves_like 'an editable mentionable' do
let(:issue) { create :issue, project: project }
let(:subject) { create :note, noteable: issue, project: project }
let(:backref_text) { issue.gfm_reference }
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
end
end
Loading
Loading
@@ -3,6 +3,8 @@ require 'spec_helper'
describe ActivityObserver do
let(:project) { create(:project) }
 
before { Thread.current[:current_user] = create(:user) }
def self.it_should_be_valid_event
it { @event.should_not be_nil }
it { @event.project.should == project }
Loading
Loading
@@ -34,4 +36,26 @@ describe ActivityObserver do
it { @event.action.should == Event::COMMENTED }
it { @event.target.should == @note }
end
describe "Ignore system notes" do
let(:author) { create(:user) }
let!(:issue) { create(:issue, project: project) }
let!(:other) { create(:issue) }
it "should not create events for status change notes" do
expect do
Note.observers.enable :activity_observer do
Note.create_status_change_note(issue, project, author, 'reopened', nil)
end
end.to_not change { Event.count }
end
it "should not create events for cross-reference notes" do
expect do
Note.observers.enable :activity_observer do
Note.create_cross_reference_note(issue, other, author, issue.project)
end
end.to_not change { Event.count }
end
end
end
Loading
Loading
@@ -8,8 +8,9 @@ describe IssueObserver do
 
 
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(:current_commit).and_return(nil) }
before { subject.stub(notification: mock('NotificationService').as_null_object) }
before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) }
 
subject { IssueObserver.instance }
 
Loading
Loading
@@ -19,6 +20,15 @@ describe IssueObserver do
 
subject.after_create(mock_issue)
end
it 'should create cross-reference notes' do
other_issue = create(:issue)
mock_issue.stub(references: [other_issue])
Note.should_receive(:create_cross_reference_note).with(other_issue, mock_issue,
some_user, mock_issue.project)
subject.after_create(mock_issue)
end
end
 
context '#after_close' do
Loading
Loading
@@ -26,7 +36,7 @@ describe IssueObserver do
before { mock_issue.stub(state: 'closed') }
 
it 'note is created if the issue is being closed' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed')
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', nil)
 
subject.after_close(mock_issue, nil)
end
Loading
Loading
@@ -35,13 +45,23 @@ describe IssueObserver do
subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
subject.after_close(mock_issue, nil)
end
it 'appends a mention to the closing commit if one is present' do
commit = double('commit', gfm_reference: 'commit 123456')
subject.stub(current_commit: commit)
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', commit)
subject.after_close(mock_issue, nil)
end
end
 
context 'a status "reopened"' do
before { mock_issue.stub(state: 'reopened') }
 
it 'note is created if the issue is being reopened' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened', nil)
subject.after_reopen(mock_issue, nil)
end
end
Loading
Loading
@@ -67,5 +87,13 @@ describe IssueObserver do
subject.after_update(mock_issue)
end
end
context 'cross-references' do
it 'notices added references' do
mock_issue.should_receive(:notice_added_references)
subject.after_update(mock_issue)
end
end
end
end
Loading
Loading
@@ -5,15 +5,20 @@ describe MergeRequestObserver do
let(:assignee) { create :user }
let(:author) { create :user }
let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
let(:unassigned_mr) { create(:merge_request, author: author) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, target_project: create(:project)) }
let(:unassigned_mr) { create(:merge_request, author: author, target_project: create(:project)) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, target_project: create(:project)) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, target_project: create(:project)) }
 
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(notification: mock('NotificationService').as_null_object) }
before { mr_mock.stub(:author_id) }
before { mr_mock.stub(:target_project) }
before { mr_mock.stub(:source_project) }
before { mr_mock.stub(:project) }
before { mr_mock.stub(:create_cross_references!).and_return(true) }
before { Repository.any_instance.stub(commit: nil) }
before(:each) { enable_observers }
after(:each) { disable_observers }
 
Loading
Loading
@@ -24,11 +29,20 @@ describe MergeRequestObserver do
subject.should_receive(:notification)
subject.after_create(mr_mock)
end
it 'creates cross-reference notes' do
project = create :project
mr_mock.stub(title: "this mr references !#{assigned_mr.id}", project: project)
mr_mock.should_receive(:create_cross_references!).with(project, some_user)
subject.after_create(mr_mock)
end
end
 
context '#after_update' do
before(:each) do
mr_mock.stub(:is_being_reassigned?).and_return(false)
mr_mock.stub(:notice_added_references)
end
 
it 'is called when a merge request is changed' do
Loading
Loading
@@ -41,6 +55,12 @@ describe MergeRequestObserver do
end
end
 
it 'checks for new references' do
mr_mock.should_receive(:notice_added_references)
subject.after_update(mr_mock)
end
context 'a notification' do
it 'is sent if the merge request is being reassigned' do
mr_mock.should_receive(:is_being_reassigned?).and_return(true)
Loading
Loading
@@ -61,13 +81,13 @@ describe MergeRequestObserver do
context '#after_close' do
context 'a status "closed"' do
it 'note is created if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.target_project, some_user, 'closed')
Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.target_project, some_user, 'closed', nil)
 
assigned_mr.close
end
 
it 'notification is delivered only to author if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.target_project, some_user, 'closed')
Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.target_project, some_user, 'closed', nil)
 
unassigned_mr.close
end
Loading
Loading
@@ -77,13 +97,13 @@ describe MergeRequestObserver do
context '#after_reopen' do
context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.target_project, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.target_project, some_user, 'reopened', nil)
 
closed_assigned_mr.reopen
end
 
it 'notification is delivered only to author if the merge request is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.target_project, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.target_project, some_user, 'reopened', nil)
 
closed_unassigned_mr.reopen
end
Loading
Loading
Loading
Loading
@@ -5,9 +5,9 @@ describe NoteObserver do
before { subject.stub(notification: mock('NotificationService').as_null_object) }
 
let(:team_without_author) { (1..2).map { |n| double :user, id: n } }
let(:note) { double(:note).as_null_object }
 
describe '#after_create' do
let(:note) { double :note }
 
it 'is called after a note is created' do
subject.should_receive :after_create
Loading
Loading
@@ -22,5 +22,35 @@ describe NoteObserver do
 
subject.after_create(note)
end
it 'creates cross-reference notes as appropriate' do
@p = create(:project)
@referenced = create(:issue, project: @p)
@referencer = create(:issue, project: @p)
@author = create(:user)
Note.should_receive(:create_cross_reference_note).with(@referenced, @referencer, @author, @p)
Note.observers.enable :note_observer do
create(:note, project: @p, author: @author, noteable: @referencer,
note: "Duplicate of ##{@referenced.iid}")
end
end
it "doesn't cross-reference system notes" do
Note.should_receive(:create_cross_reference_note).once
Note.observers.enable :note_observer do
Note.create_cross_reference_note(create(:issue), create(:issue))
end
end
end
describe '#after_update' do
it 'checks for new cross-references' do
note.should_receive(:notice_added_references)
subject.after_update(note)
end
end
end
Loading
Loading
@@ -6,6 +6,7 @@ describe GitPushService do
let (:service) { GitPushService.new }
 
before do
@blankrev = '0000000000000000000000000000000000000000'
@oldrev = 'b98a310def241a6fd9c9a9a3e7934c48e498fe81'
@newrev = 'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb'
@ref = 'refs/heads/master'
Loading
Loading
@@ -98,7 +99,7 @@ describe GitPushService do
 
it "when pushing a branch for the first time" do
@project_hook.should_not_receive(:execute)
service.execute(project, user, '00000000000000000000000000000000', 'newrev', 'refs/heads/master')
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
end
 
it "when pushing tags" do
Loading
Loading
@@ -107,5 +108,107 @@ describe GitPushService do
end
end
end
describe "cross-reference notes" do
let(:issue) { create :issue, project: project }
let(:commit_author) { create :user }
let(:commit) { project.repository.commit }
before do
commit.stub({
safe_message: "this commit \n mentions ##{issue.id}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
})
project.repository.stub(commits_between: [commit])
end
it "creates a note if a pushed commit mentions an issue" do
Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @oldrev, @newrev, @ref)
end
it "only creates a cross-reference note if one doesn't already exist" do
Note.create_cross_reference_note(issue, commit, user, project)
Note.should_not_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @oldrev, @newrev, @ref)
end
it "defaults to the pushing user if the commit's author is not known" do
commit.stub(author_name: 'unknown name', author_email: 'unknown@email.com')
Note.should_receive(:create_cross_reference_note).with(issue, commit, user, project)
service.execute(project, user, @oldrev, @newrev, @ref)
end
it "finds references in the first push to a non-default branch" do
project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([])
project.repository.stub(:commits_between).with("master", @newrev).and_return([commit])
Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
end
it "finds references in the first push to a default branch" do
project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([])
project.repository.stub(:commits).with(@newrev).and_return([commit])
Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @blankrev, @newrev, 'refs/heads/master')
end
end
describe "closing issues from pushed commits" do
let(:issue) { create :issue, project: project }
let(:other_issue) { create :issue, project: project }
let(:commit_author) { create :user }
let(:closing_commit) { project.repository.commit }
before do
closing_commit.stub({
issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/,
safe_message: "this is some work.\n\ncloses ##{issue.iid}",
author_name: commit_author.name,
author_email: commit_author.email
})
project.repository.stub(commits_between: [closing_commit])
end
it "closes issues with commit messages" do
service.execute(project, user, @oldrev, @newrev, @ref)
Issue.find(issue.id).should be_closed
end
it "passes the closing commit as a thread-local" do
service.execute(project, user, @oldrev, @newrev, @ref)
Thread.current[:current_commit].should == closing_commit
end
it "doesn't create cross-reference notes for a closing reference" do
expect {
service.execute(project, user, @oldrev, @newrev, @ref)
}.not_to change { Note.where(project_id: project.id, system: true).count }
end
it "doesn't close issues when pushed to non-default branches" do
project.stub(default_branch: 'durf')
# The push still shouldn't create cross-reference notes.
expect {
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
}.not_to change { Note.where(project_id: project.id, system: true).count }
Issue.find(issue.id).should be_opened
end
end
end
 
Loading
Loading
@@ -18,6 +18,7 @@ module LoginHelpers
fill_in "user_login", with: user.email
fill_in "user_password", with: "123456"
click_button "Sign in"
Thread.current[:current_user] = user
end
 
def logout
Loading
Loading
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
# - let(:subject) { "the mentionable implementation" }
# - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " }
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
def common_mentionable_setup
# Avoid name collisions with let(:project) or let(:author) in the surrounding scope.
let(:mproject) { create :project }
let(:mauthor) { subject.author }
let(:mentioned_issue) { create :issue, project: mproject }
let(:other_issue) { create :issue, project: mproject }
let(:mentioned_mr) { create :merge_request, target_project: mproject, source_branch: 'different' }
let(:mentioned_commit) { mock('commit', sha: '1234567890abcdef').as_null_object }
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+.
let(:ref_string) do
"mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, !#{mentioned_mr.iid}, " +
"#{mentioned_commit.sha[0..5]} and itself as #{backref_text}"
end
before do
# Wire the project's repository to return the mentioned commit, and +nil+ for any
# unrecognized commits.
commitmap = { '123456' => mentioned_commit }
extra_commits.each { |c| commitmap[c.sha[0..5]] = c }
repo = mock('repository')
repo.stub(:commit) { |sha| commitmap[sha] }
mproject.stub(repository: repo)
set_mentionable_text.call(ref_string)
end
end
shared_examples 'a mentionable' do
common_mentionable_setup
it 'generates a descriptive back-reference' do
subject.gfm_reference.should == backref_text
end
it "extracts references from its reference property" do
# De-duplicate and omit itself
refs = subject.references(mproject)
refs.should have(3).items
refs.should include(mentioned_issue)
refs.should include(mentioned_mr)
refs.should include(mentioned_commit)
end
it 'creates cross-reference notes' do
[mentioned_issue, mentioned_mr, mentioned_commit].each do |referenced|
Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
end
subject.create_cross_references!(mproject, mauthor)
end
it 'detects existing cross-references' do
Note.create_cross_reference_note(mentioned_issue, subject.local_reference, mauthor, mproject)
subject.has_mentioned?(mentioned_issue).should be_true
subject.has_mentioned?(mentioned_mr).should be_false
end
end
shared_examples 'an editable mentionable' do
common_mentionable_setup
it_behaves_like 'a mentionable'
it 'creates new cross-reference notes when the mentionable text is edited' do
new_text = "this text still mentions ##{mentioned_issue.iid} and #{mentioned_commit.sha[0..5]}, " +
"but now it mentions ##{other_issue.iid}, too."
[mentioned_issue, mentioned_commit].each do |oldref|
Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference,
mauthor, mproject)
end
Note.should_receive(:create_cross_reference_note).with(other_issue, subject.local_reference, mauthor, mproject)
subject.save
set_mentionable_text.call(new_text)
subject.notice_added_references(mproject, mauthor)
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment