octopolo 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/.travis.yml +4 -0
- data/CHANGELOG.markdown +5 -0
- data/lib/octopolo/commands/issue.rb +14 -0
- data/lib/octopolo/github.rb +9 -2
- data/lib/octopolo/github/issue.rb +106 -0
- data/lib/octopolo/github/issue_creator.rb +147 -0
- data/lib/octopolo/github/pull_request.rb +10 -81
- data/lib/octopolo/github/pull_request_creator.rb +12 -97
- data/lib/octopolo/renderer.rb +1 -0
- data/lib/octopolo/scripts/issue.rb +140 -0
- data/lib/octopolo/scripts/pull_request.rb +5 -71
- data/lib/octopolo/templates/issue_body.erb +22 -0
- data/lib/octopolo/version.rb +1 -1
- data/spec/octopolo/github/issue_creator_spec.rb +217 -0
- data/spec/octopolo/github/issue_spec.rb +243 -0
- data/spec/octopolo/github/pull_request_creator_spec.rb +11 -11
- data/spec/octopolo/github/pull_request_spec.rb +16 -16
- data/spec/octopolo/github_spec.rb +15 -0
- data/spec/octopolo/scripts/issue_spec.rb +233 -0
- data/spec/octopolo/scripts/pull_request_spec.rb +3 -3
- metadata +13 -2
@@ -1,16 +1,10 @@
|
|
1
|
+
require_relative "issue_creator"
|
1
2
|
require_relative "../renderer"
|
2
3
|
require 'tempfile'
|
3
4
|
|
4
5
|
module Octopolo
|
5
6
|
module GitHub
|
6
|
-
class PullRequestCreator
|
7
|
-
include ConfigWrapper
|
8
|
-
# for instantiating the pull request creator
|
9
|
-
attr_accessor :repo_name
|
10
|
-
attr_accessor :options
|
11
|
-
# for caputuring the created pull request information
|
12
|
-
attr_accessor :number
|
13
|
-
attr_accessor :pull_request_data
|
7
|
+
class PullRequestCreator < IssueCreator
|
14
8
|
|
15
9
|
# Public: Create a pull request for the given repo with the given options
|
16
10
|
#
|
@@ -20,23 +14,7 @@ module Octopolo
|
|
20
14
|
# destination_branch: Which branch to merge into
|
21
15
|
# source_branch: Which branch to be merged
|
22
16
|
def initialize repo_name, options
|
23
|
-
|
24
|
-
self.options = options
|
25
|
-
end
|
26
|
-
|
27
|
-
# Public: Create a pull request for the given repo with the given options
|
28
|
-
#
|
29
|
-
# repo_name - Full name ("account/repo") of the repo in question
|
30
|
-
# options - Hash of pull request information
|
31
|
-
# title: Title of the pull request
|
32
|
-
# destination_branch: Which branch to merge into
|
33
|
-
# source_branch: Which branch to be merged
|
34
|
-
#
|
35
|
-
# Returns the PullRequestCreator instance
|
36
|
-
def self.perform repo_name, options
|
37
|
-
new(repo_name, options).tap do |creator|
|
38
|
-
creator.perform
|
39
|
-
end
|
17
|
+
super(repo_name, options)
|
40
18
|
end
|
41
19
|
|
42
20
|
# Public: Create the pull request
|
@@ -47,21 +25,11 @@ module Octopolo
|
|
47
25
|
result = GitHub.create_pull_request(repo_name, destination_branch, source_branch, title, body)
|
48
26
|
# capture the information
|
49
27
|
self.number = result.number
|
50
|
-
self.
|
28
|
+
self.data = result
|
51
29
|
rescue => e
|
52
30
|
raise CannotCreate, e.message
|
53
31
|
end
|
54
32
|
|
55
|
-
# Public: The created pull request's details
|
56
|
-
def pull_request_data
|
57
|
-
@pull_request_data || raise(NotYetCreated)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Public: The created pull request's number
|
61
|
-
def number
|
62
|
-
@number || raise(NotYetCreated)
|
63
|
-
end
|
64
|
-
|
65
33
|
# Public: Branch to merge the pull request into
|
66
34
|
#
|
67
35
|
# Returns a String with the branch name
|
@@ -76,74 +44,21 @@ module Octopolo
|
|
76
44
|
options[:source_branch] || raise(MissingAttribute)
|
77
45
|
end
|
78
46
|
|
79
|
-
# Public:
|
80
|
-
#
|
81
|
-
# Returns a String with the title
|
82
|
-
def title
|
83
|
-
options[:title] || raise(MissingAttribute)
|
84
|
-
end
|
85
|
-
|
86
|
-
# Public: The Pivotal Tracker story IDs associated with the pull request
|
87
|
-
#
|
88
|
-
# Returns an Array of Strings
|
89
|
-
def pivotal_ids
|
90
|
-
options[:pivotal_ids] || []
|
91
|
-
end
|
92
|
-
|
93
|
-
# Public: Jira Issue IDs associated with the pull request
|
47
|
+
# Public: Rendering template for body property
|
94
48
|
#
|
95
|
-
# Returns
|
96
|
-
def
|
97
|
-
|
49
|
+
# Returns Name of template file
|
50
|
+
def renderer_template
|
51
|
+
Renderer::PULL_REQUEST_BODY
|
98
52
|
end
|
99
53
|
|
100
|
-
# Public: Jira Url associated with the pull request
|
101
|
-
#
|
102
|
-
# Returns Jira Url
|
103
|
-
def jira_url
|
104
|
-
config.jira_url
|
105
|
-
end
|
106
54
|
|
107
|
-
# Public:
|
55
|
+
# Public: Temporary file for body editing
|
108
56
|
#
|
109
|
-
# Returns
|
110
|
-
def
|
111
|
-
|
112
|
-
output = edit_body(output) if options[:editor]
|
113
|
-
output
|
114
|
-
end
|
115
|
-
|
116
|
-
def edit_body(body)
|
117
|
-
return body unless ENV['EDITOR']
|
118
|
-
|
119
|
-
# Open the file, write the contents, and close it
|
120
|
-
tempfile = Tempfile.new(['octopolo_pull_request', '.md'])
|
121
|
-
tempfile.write(body)
|
122
|
-
tempfile.close
|
123
|
-
|
124
|
-
# Allow the user to edit the file
|
125
|
-
system "#{ENV['EDITOR']} #{tempfile.path}"
|
126
|
-
|
127
|
-
# Reopen the file, read the contents, and delete it
|
128
|
-
tempfile.open
|
129
|
-
output = tempfile.read
|
130
|
-
tempfile.unlink
|
131
|
-
|
132
|
-
output
|
133
|
-
end
|
134
|
-
|
135
|
-
# Public: The local variables to pass into the template
|
136
|
-
def body_locals
|
137
|
-
{
|
138
|
-
pivotal_ids: pivotal_ids,
|
139
|
-
jira_ids: jira_ids,
|
140
|
-
jira_url: jira_url,
|
141
|
-
}
|
57
|
+
# Returns Name of temporary file
|
58
|
+
def body_edit_temp_name
|
59
|
+
'octopolo_pull_request'
|
142
60
|
end
|
143
61
|
|
144
|
-
MissingAttribute = Class.new(StandardError)
|
145
|
-
NotYetCreated = Class.new(StandardError)
|
146
|
-
CannotCreate = Class.new(StandardError)
|
147
62
|
end
|
148
63
|
end
|
149
64
|
end
|
data/lib/octopolo/renderer.rb
CHANGED
@@ -0,0 +1,140 @@
|
|
1
|
+
require_relative "../scripts"
|
2
|
+
require_relative "../github"
|
3
|
+
require_relative "../github/issue"
|
4
|
+
require_relative "../github/issue_creator"
|
5
|
+
require_relative "../pivotal/story_commenter"
|
6
|
+
require_relative "../jira/story_commenter"
|
7
|
+
|
8
|
+
module Octopolo
|
9
|
+
module Scripts
|
10
|
+
class Issue
|
11
|
+
include CLIWrapper
|
12
|
+
include ConfigWrapper
|
13
|
+
include GitWrapper
|
14
|
+
|
15
|
+
attr_accessor :title
|
16
|
+
attr_accessor :issue
|
17
|
+
attr_accessor :pivotal_ids
|
18
|
+
attr_accessor :jira_ids
|
19
|
+
attr_accessor :label
|
20
|
+
attr_accessor :options
|
21
|
+
|
22
|
+
def self.execute(options={})
|
23
|
+
new(options).execute
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(options={})
|
27
|
+
@options = options
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute
|
31
|
+
GitHub.connect do
|
32
|
+
ask_questionaire
|
33
|
+
create_issue
|
34
|
+
update_pivotal
|
35
|
+
update_jira
|
36
|
+
update_label
|
37
|
+
open_in_browser
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Protected: Ask questions to create an issue
|
42
|
+
def ask_questionaire
|
43
|
+
announce
|
44
|
+
ask_title
|
45
|
+
ask_label
|
46
|
+
ask_pivotal_ids if config.use_pivotal_tracker
|
47
|
+
ask_jira_ids if config.use_jira
|
48
|
+
end
|
49
|
+
protected :ask_questionaire
|
50
|
+
|
51
|
+
# Protected: Announce to the user the branches the issue will reference
|
52
|
+
def announce
|
53
|
+
cli.say "Preparing an issue for #{config.github_repo}."
|
54
|
+
end
|
55
|
+
protected :announce
|
56
|
+
|
57
|
+
# Protected: Ask for a title for the issue
|
58
|
+
def ask_title
|
59
|
+
self.title = cli.prompt "Title:"
|
60
|
+
end
|
61
|
+
protected :ask_title
|
62
|
+
|
63
|
+
# Protected: Ask for a label for the issue
|
64
|
+
def ask_label
|
65
|
+
choices = Octopolo::GitHub::Label.get_names(label_choices).concat(["None"])
|
66
|
+
response = cli.ask(label_prompt, choices)
|
67
|
+
self.label = Hash[label_choices.map{|l| [l.name,l]}][response]
|
68
|
+
end
|
69
|
+
protected :ask_label
|
70
|
+
|
71
|
+
# Protected: Ask for a Pivotal Tracker story IDs
|
72
|
+
def ask_pivotal_ids
|
73
|
+
self.pivotal_ids = cli.prompt("Pivotal Tracker story ID(s):").split(/[\s,]+/)
|
74
|
+
end
|
75
|
+
protected :ask_pivotal_ids
|
76
|
+
|
77
|
+
# Protected: Ask for a Pivotal Tracker story IDs
|
78
|
+
def ask_jira_ids
|
79
|
+
self.jira_ids = cli.prompt("Jira story ID(s):").split(/[\s,]+/)
|
80
|
+
end
|
81
|
+
protected :ask_pivotal_ids
|
82
|
+
|
83
|
+
# Protected: Create the issue
|
84
|
+
#
|
85
|
+
# Returns a GitHub::Issue object
|
86
|
+
def create_issue
|
87
|
+
self.issue = GitHub::Issue.create config.github_repo, issue_attributes
|
88
|
+
end
|
89
|
+
protected :create_issue
|
90
|
+
|
91
|
+
# Protected: The attributes to send to create the issue
|
92
|
+
#
|
93
|
+
# Returns a Hash
|
94
|
+
def issue_attributes
|
95
|
+
{
|
96
|
+
title: title,
|
97
|
+
pivotal_ids: pivotal_ids,
|
98
|
+
jira_ids: jira_ids,
|
99
|
+
editor: options[:editor]
|
100
|
+
}
|
101
|
+
end
|
102
|
+
protected :issue_attributes
|
103
|
+
|
104
|
+
# Protected: Handle the newly created issue
|
105
|
+
def open_in_browser
|
106
|
+
cli.copy_to_clipboard issue.url
|
107
|
+
cli.open issue.url
|
108
|
+
end
|
109
|
+
protected :open_in_browser
|
110
|
+
|
111
|
+
def label_prompt
|
112
|
+
'Label:'
|
113
|
+
end
|
114
|
+
|
115
|
+
def label_choices
|
116
|
+
Octopolo::GitHub::Label.all
|
117
|
+
end
|
118
|
+
|
119
|
+
def update_pivotal
|
120
|
+
pivotal_ids.each do |story_id|
|
121
|
+
Pivotal::StoryCommenter.new(story_id, issue.url).perform
|
122
|
+
end if pivotal_ids
|
123
|
+
end
|
124
|
+
protected :update_pivotal
|
125
|
+
|
126
|
+
def update_jira
|
127
|
+
jira_ids.each do |story_id|
|
128
|
+
Jira::StoryCommenter.new(story_id, issue.url).perform
|
129
|
+
end if jira_ids
|
130
|
+
end
|
131
|
+
protected :update_jira
|
132
|
+
|
133
|
+
def update_label
|
134
|
+
issue.add_labels(label) if label
|
135
|
+
end
|
136
|
+
protected :update_label
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -1,22 +1,16 @@
|
|
1
1
|
require_relative "../scripts"
|
2
|
+
require_relative "../scripts/issue"
|
2
3
|
require_relative "../github"
|
3
4
|
require_relative "../pivotal/story_commenter"
|
4
5
|
require_relative "../jira/story_commenter"
|
5
6
|
|
6
7
|
module Octopolo
|
7
8
|
module Scripts
|
8
|
-
class PullRequest
|
9
|
-
include CLIWrapper
|
10
|
-
include ConfigWrapper
|
11
|
-
include GitWrapper
|
12
|
-
|
13
|
-
attr_accessor :title
|
9
|
+
class PullRequest < Issue
|
14
10
|
attr_accessor :pull_request
|
15
|
-
attr_accessor :pivotal_ids
|
16
|
-
attr_accessor :jira_ids
|
17
11
|
attr_accessor :destination_branch
|
18
|
-
|
19
|
-
|
12
|
+
|
13
|
+
alias_method :issue, :pull_request
|
20
14
|
|
21
15
|
def self.execute(destination_branch=nil, options={})
|
22
16
|
new(destination_branch, options).execute
|
@@ -38,7 +32,7 @@ module Octopolo
|
|
38
32
|
update_pivotal
|
39
33
|
update_jira
|
40
34
|
update_label
|
41
|
-
|
35
|
+
open_in_browser
|
42
36
|
end
|
43
37
|
end
|
44
38
|
|
@@ -66,32 +60,6 @@ module Octopolo
|
|
66
60
|
end
|
67
61
|
private :alert_reserved_and_exit
|
68
62
|
|
69
|
-
# Private: Ask for a title for the pull request
|
70
|
-
def ask_title
|
71
|
-
self.title = cli.prompt "Title:"
|
72
|
-
end
|
73
|
-
private :ask_title
|
74
|
-
|
75
|
-
# Private: Ask for a label for the pull request
|
76
|
-
def ask_label
|
77
|
-
choices = Octopolo::GitHub::Label.get_names(label_choices).concat(["None"])
|
78
|
-
response = cli.ask(label_prompt, choices)
|
79
|
-
self.label = Hash[label_choices.map{|l| [l.name,l]}][response]
|
80
|
-
end
|
81
|
-
private :ask_label
|
82
|
-
|
83
|
-
# Private: Ask for a Pivotal Tracker story IDs
|
84
|
-
def ask_pivotal_ids
|
85
|
-
self.pivotal_ids = cli.prompt("Pivotal Tracker story ID(s):").split(/[\s,]+/)
|
86
|
-
end
|
87
|
-
private :ask_pivotal_ids
|
88
|
-
|
89
|
-
# Private: Ask for a Pivotal Tracker story IDs
|
90
|
-
def ask_jira_ids
|
91
|
-
self.jira_ids = cli.prompt("Jira story ID(s):").split(/[\s,]+/)
|
92
|
-
end
|
93
|
-
private :ask_pivotal_ids
|
94
|
-
|
95
63
|
# Private: Create the pull request
|
96
64
|
#
|
97
65
|
# Returns a GitHub::PullRequest object
|
@@ -115,40 +83,6 @@ module Octopolo
|
|
115
83
|
end
|
116
84
|
private :pull_request_attributes
|
117
85
|
|
118
|
-
# Private: Handle the newly created pull request
|
119
|
-
def open_pull_request
|
120
|
-
cli.copy_to_clipboard pull_request.url
|
121
|
-
cli.open pull_request.url
|
122
|
-
end
|
123
|
-
private :open_pull_request
|
124
|
-
|
125
|
-
def label_prompt
|
126
|
-
'Label:'
|
127
|
-
end
|
128
|
-
|
129
|
-
def label_choices
|
130
|
-
Octopolo::GitHub::Label.all
|
131
|
-
end
|
132
|
-
|
133
|
-
def update_pivotal
|
134
|
-
pivotal_ids.each do |story_id|
|
135
|
-
Pivotal::StoryCommenter.new(story_id, pull_request.url).perform
|
136
|
-
end if pivotal_ids
|
137
|
-
end
|
138
|
-
private :update_pivotal
|
139
|
-
|
140
|
-
def update_jira
|
141
|
-
jira_ids.each do |story_id|
|
142
|
-
Jira::StoryCommenter.new(story_id, pull_request.url).perform
|
143
|
-
end if jira_ids
|
144
|
-
end
|
145
|
-
private :update_jira
|
146
|
-
|
147
|
-
def update_label
|
148
|
-
pull_request.add_labels(label) if label
|
149
|
-
end
|
150
|
-
private :update_label
|
151
|
-
|
152
86
|
end
|
153
87
|
end
|
154
88
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%= description %>
|
2
|
+
|
3
|
+
Deploy Plan
|
4
|
+
-----------
|
5
|
+
Describe how this change will be deployed.
|
6
|
+
|
7
|
+
Rollback Plan
|
8
|
+
-------------
|
9
|
+
Describe how this change can be rolled back.
|
10
|
+
|
11
|
+
URLs
|
12
|
+
----
|
13
|
+
<% pivotal_ids.each do |pivotal_id| -%>
|
14
|
+
* [pivotal tracker story <%= pivotal_id %>](https://www.pivotaltracker.com/story/show/<%= pivotal_id %>)
|
15
|
+
<% end -%>
|
16
|
+
<% jira_ids.each do |jira_id| -%>
|
17
|
+
* [Jira issue <%= jira_id %>](<%= jira_url %>/browse/<%= jira_id %>)
|
18
|
+
<% end -%>
|
19
|
+
|
20
|
+
QA Plan
|
21
|
+
-------
|
22
|
+
Provide a detailed QA plan, or other developers will retain the right to mock you mercilessly.
|
data/lib/octopolo/version.rb
CHANGED
@@ -0,0 +1,217 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require_relative "../../../lib/octopolo/github/issue_creator"
|
3
|
+
|
4
|
+
module Octopolo
|
5
|
+
module GitHub
|
6
|
+
describe IssueCreator do
|
7
|
+
let(:creator) { IssueCreator.new repo_name, options }
|
8
|
+
let(:repo_name) { "foo/bar" }
|
9
|
+
let(:options) { {} }
|
10
|
+
let(:title) { "title" }
|
11
|
+
let(:body) { "body" }
|
12
|
+
let(:pivotal_ids) { %w(123 456) }
|
13
|
+
let(:jira_ids) { %w(123 456) }
|
14
|
+
let(:jira_url) { "https://example-jira.com" }
|
15
|
+
|
16
|
+
context ".perform repo_name, options" do
|
17
|
+
let(:creator) { stub }
|
18
|
+
|
19
|
+
it "instantiates a creator and perfoms it" do
|
20
|
+
IssueCreator.should_receive(:new).with(repo_name, options) { creator }
|
21
|
+
creator.should_receive(:perform)
|
22
|
+
IssueCreator.perform(repo_name, options).should == creator
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context ".new repo_name, options" do
|
27
|
+
it "remembers the repo name and options" do
|
28
|
+
creator = IssueCreator.new repo_name, options
|
29
|
+
creator.repo_name.should == repo_name
|
30
|
+
creator.options.should == options
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#perform" do
|
35
|
+
let(:data) { stub(:mash, number: 123) }
|
36
|
+
|
37
|
+
before do
|
38
|
+
creator.stub({
|
39
|
+
title: title,
|
40
|
+
body: body,
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
it "generates the issue with the given details and retains the information" do
|
45
|
+
GitHub.should_receive(:create_issue).with(repo_name, title, body, labels: []) { data }
|
46
|
+
creator.perform.should == data
|
47
|
+
creator.number.should == data.number
|
48
|
+
creator.data.should == data
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raises CannotCreate if any exception occurs" do
|
52
|
+
GitHub.should_receive(:create_issue).and_raise(Octokit::UnprocessableEntity)
|
53
|
+
expect { creator.perform }.to raise_error(IssueCreator::CannotCreate)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "#number" do
|
58
|
+
let(:number) { 123 }
|
59
|
+
|
60
|
+
it "returns the stored issue number" do
|
61
|
+
creator.number = number
|
62
|
+
creator.number.should == number
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises an exception if no issue has been created yet" do
|
66
|
+
creator.number = nil
|
67
|
+
expect { creator.number }.to raise_error(IssueCreator::NotYetCreated)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "#data" do
|
72
|
+
let(:details) { stub(:data) }
|
73
|
+
|
74
|
+
it "returns the stored issue details" do
|
75
|
+
creator.data = details
|
76
|
+
creator.data.should == details
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises an exception if no information has been captured yet" do
|
80
|
+
creator.data = nil
|
81
|
+
expect { creator.data }.to raise_error(IssueCreator::NotYetCreated)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "#title" do
|
86
|
+
context "having the option set" do
|
87
|
+
before { creator.options[:title] = title }
|
88
|
+
|
89
|
+
it "fetches from the options" do
|
90
|
+
creator.title.should == title
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "raises an exception if it's missing" do
|
95
|
+
creator.options[:title] = nil
|
96
|
+
expect { creator.title }.to raise_error(IssueCreator::MissingAttribute)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "#pivotal_ids" do
|
101
|
+
it "fetches from the options" do
|
102
|
+
creator.options[:pivotal_ids] = pivotal_ids
|
103
|
+
creator.pivotal_ids.should == pivotal_ids
|
104
|
+
end
|
105
|
+
|
106
|
+
it "defaults to an empty array if it's missing" do
|
107
|
+
creator.options[:pivotal_ids] = nil
|
108
|
+
creator.pivotal_ids.should == []
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "#body_locals" do
|
113
|
+
let(:urls) { %w(link1 link2) }
|
114
|
+
|
115
|
+
before do
|
116
|
+
creator.stub({
|
117
|
+
pivotal_ids: pivotal_ids,
|
118
|
+
jira_ids: jira_ids,
|
119
|
+
jira_url: jira_url,
|
120
|
+
})
|
121
|
+
end
|
122
|
+
it "includes the necessary keys to render the template" do
|
123
|
+
creator.body_locals[:pivotal_ids].should == creator.pivotal_ids
|
124
|
+
creator.body_locals[:jira_ids].should == creator.jira_ids
|
125
|
+
creator.body_locals[:jira_url].should == creator.jira_url
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "#edit_body" do
|
130
|
+
let(:path) { stub(:path) }
|
131
|
+
let(:body) { stub(:string) }
|
132
|
+
let(:tempfile) { stub(:tempfile) }
|
133
|
+
let(:edited_body) { stub(:edited_body) }
|
134
|
+
|
135
|
+
before do
|
136
|
+
Tempfile.stub(:new) { tempfile }
|
137
|
+
tempfile.stub(path: path, write: nil, read: edited_body, unlink: nil, close: nil, open: nil)
|
138
|
+
creator.stub(:system)
|
139
|
+
end
|
140
|
+
|
141
|
+
context "without the $EDITOR env var set" do
|
142
|
+
before do
|
143
|
+
stub_const('ENV', {'EDITOR' => nil})
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns the un-edited output" do
|
147
|
+
creator.edit_body(body).should == body
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "with the $EDITOR env set" do
|
152
|
+
|
153
|
+
before do
|
154
|
+
stub_const('ENV', {'EDITOR' => 'vim'})
|
155
|
+
end
|
156
|
+
|
157
|
+
it "creates a tempfile, write default contents, and close it" do
|
158
|
+
Tempfile.should_receive(:new).with(['octopolo_issue', '.md']) { tempfile }
|
159
|
+
tempfile.should_receive(:write).with(body)
|
160
|
+
tempfile.should_receive(:close)
|
161
|
+
creator.edit_body body
|
162
|
+
end
|
163
|
+
|
164
|
+
it "edits the tempfile with the $EDITOR" do
|
165
|
+
tempfile.should_receive(:path) { path }
|
166
|
+
creator.should_receive(:system).with("vim #{path}")
|
167
|
+
creator.edit_body body
|
168
|
+
end
|
169
|
+
|
170
|
+
it "reopens the file, gets the contents, and deletes the temp file" do
|
171
|
+
tempfile.should_receive(:open)
|
172
|
+
tempfile.should_receive(:read) { edited_body }
|
173
|
+
tempfile.should_receive(:unlink)
|
174
|
+
creator.edit_body body
|
175
|
+
end
|
176
|
+
|
177
|
+
it "returns the user edited output" do
|
178
|
+
creator.edit_body(body).should == edited_body
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "#body" do
|
184
|
+
let(:locals) { stub(:hash) }
|
185
|
+
let(:output) { stub(:string) }
|
186
|
+
|
187
|
+
before do
|
188
|
+
creator.stub({
|
189
|
+
body_locals: locals,
|
190
|
+
})
|
191
|
+
end
|
192
|
+
|
193
|
+
it "renders the body template with the body locals" do
|
194
|
+
Renderer.should_receive(:render).with(Renderer::ISSUE_BODY, locals) { output }
|
195
|
+
creator.body.should == output
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when the editor option is set" do
|
199
|
+
let(:edited_output) { stub(:output) }
|
200
|
+
|
201
|
+
before do
|
202
|
+
creator.stub({
|
203
|
+
body_locals: locals,
|
204
|
+
options: { editor: true }
|
205
|
+
})
|
206
|
+
end
|
207
|
+
|
208
|
+
it "calls the edit_body method" do
|
209
|
+
Renderer.should_receive(:render).with(Renderer::ISSUE_BODY, locals) { output }
|
210
|
+
creator.should_receive(:edit_body).with(output) { edited_output }
|
211
|
+
creator.body.should == edited_output
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|