587557e1ac
There's a lot in this PR. - Added a Section class to more easily make the other changes and hopefully add flexibility for the future - Added an option called `configure_sections` that allows you create your own custom sections. It blows away all other sections and uses only the ones you give it. - Added an option called `add_sections` that allows you to add_sections to the default section set - Added an option called `include_merged` that can be used when configure_sections is defined. Configure sections blows away any and all default sections so to get this one back, you have to set this option. - Added tests for this stuff @HAIL9000 was a co-author. Because of a little git snafu, I accidentally squashed all of our work into one so it looks like it was just me. --- Refactor details: Before this change, the code in generator.rb and generator_generation.rb was conflated and method call flow went back and forth between the two files seemingly randomly. They also both defined the exact same class, which is un-ruby-ish. I tried to separate methods used for the whole changelog generation from methods used for specific parts of the changelog and move them into specific classes. I reasoned that a changelog is a series of "entries" of all tagged releases plus an extra entry for the unreleased entry. Each entry is comprised of a header and a series of "sections" for that entry. Each section is comprized of a list of issues and/or pull requests for that entry. So the log contains entries, entries contain sections, and sections contain issues & prs. I have structured the classes around this idea. - lib/github_changelog_generator/generator/generator.rb is for code related to generating the entire changelog. - lib/github_changelog_generator/generator/entry.rb is for code related to generating entries. - lib/github_changelog_generator/generator/section.rb is for code relating to geneating entry sections. Issues and PRs are already special objects, so it doesn't make sense to break those out into their own class.
364 lines
13 KiB
Ruby
364 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# rubocop:disable Metrics/ModuleLength
|
|
module GitHubChangelogGenerator
|
|
RSpec.describe Entry do
|
|
def label(name)
|
|
{ "name" => name }
|
|
end
|
|
|
|
def issue(title, labels, number = "1", user = { "login" => "user" })
|
|
{
|
|
"title" => "issue #{title}",
|
|
"labels" => labels.map { |l| label(l) },
|
|
"number" => number,
|
|
"html_url" => "https://github.com/owner/repo/issue/#{number}",
|
|
"user" => user
|
|
}
|
|
end
|
|
|
|
def pr(title, labels, number = "1", user = { "login" => "user" })
|
|
{
|
|
"pull_request" => true,
|
|
"title" => "pr #{title}",
|
|
"labels" => labels.map { |l| label(l) },
|
|
"number" => number,
|
|
"html_url" => "https://github.com/owner/repo/pull/#{number}",
|
|
"user" => user.merge("html_url" => "https://github.com/#{user['login']}")
|
|
}
|
|
end
|
|
|
|
def titles_for(issues)
|
|
issues.map { |issue| issue["title"] }
|
|
end
|
|
|
|
def default_sections
|
|
%w[enhancements bugs breaking issues]
|
|
end
|
|
|
|
describe "#create_entry_for_tag" do
|
|
let(:options) do
|
|
Parser.default_options.merge(
|
|
user: "owner",
|
|
project: "repo",
|
|
bug_labels: ["bug"],
|
|
enhancement_labels: ["enhancement"],
|
|
breaking_labels: ["breaking"]
|
|
)
|
|
end
|
|
|
|
let(:issues) do
|
|
[
|
|
issue("no labels", [], "5", "login" => "user1"),
|
|
issue("enhancement", ["enhancement"], "6", "login" => "user2"),
|
|
issue("bug", ["bug"], "7", "login" => "user1"),
|
|
issue("breaking", ["breaking"], "8", "login" => "user5"),
|
|
issue("all the labels", %w[enhancement bug breaking], "9", "login" => "user9")
|
|
]
|
|
end
|
|
|
|
let(:pull_requests) do
|
|
[
|
|
pr("no labels", [], "10", "login" => "user1"),
|
|
pr("enhancement", ["enhancement"], "11", "login" => "user5"),
|
|
pr("bug", ["bug"], "12", "login" => "user5"),
|
|
pr("breaking", ["breaking"], "13", "login" => "user5"),
|
|
pr("all the labels", %w[enhancement bug breaking], "14", "login" => "user5")
|
|
]
|
|
end
|
|
|
|
subject { described_class.new(options) }
|
|
|
|
it "generates a header and body" do
|
|
expect(subject.create_entry_for_tag(pull_requests, issues, "1.0.1", "1.0.1", Time.new(2017, 12, 4), "1.0.0")).to eq(<<-CHANGELOG.gsub(/^ {8}/, "")
|
|
## [1.0.1](https://github.com/owner/repo/tree/1.0.1) (2017-12-04)
|
|
|
|
[Full Changelog](https://github.com/owner/repo/compare/1.0.0...1.0.1)
|
|
|
|
**Breaking changes:**
|
|
|
|
- issue breaking [\\#8](https://github.com/owner/repo/issue/8)
|
|
- pr breaking [\\#13](https://github.com/owner/repo/pull/13) ([user5](https://github.com/user5))
|
|
|
|
**Implemented enhancements:**
|
|
|
|
- issue enhancement [\\#6](https://github.com/owner/repo/issue/6)
|
|
- issue all the labels [\\#9](https://github.com/owner/repo/issue/9)
|
|
- pr enhancement [\\#11](https://github.com/owner/repo/pull/11) ([user5](https://github.com/user5))
|
|
- pr all the labels [\\#14](https://github.com/owner/repo/pull/14) ([user5](https://github.com/user5))
|
|
|
|
**Fixed bugs:**
|
|
|
|
- issue bug [\\#7](https://github.com/owner/repo/issue/7)
|
|
- pr bug [\\#12](https://github.com/owner/repo/pull/12) ([user5](https://github.com/user5))
|
|
|
|
**Closed issues:**
|
|
|
|
- issue no labels [\\#5](https://github.com/owner/repo/issue/5)
|
|
|
|
**Merged pull requests:**
|
|
|
|
- pr no labels [\\#10](https://github.com/owner/repo/pull/10) ([user1](https://github.com/user1))
|
|
|
|
CHANGELOG
|
|
)
|
|
end
|
|
end
|
|
describe "#parse_sections" do
|
|
before do
|
|
subject { described_class.new }
|
|
end
|
|
context "valid json" do
|
|
let(:sections_string) { "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}" }
|
|
|
|
let(:sections_array) do
|
|
[
|
|
Section.new(name: "foo", prefix: "foofix", labels: %w[test1 test2]),
|
|
Section.new(name: "bar", prefix: "barfix", labels: %w[test3 test4])
|
|
]
|
|
end
|
|
|
|
it "returns an array with 2 objects" do
|
|
arr = subject.send(:parse_sections, sections_string)
|
|
expect(arr.size).to eq 2
|
|
arr.each { |section| expect(section).to be_an_instance_of Section }
|
|
end
|
|
|
|
it "returns correctly constructed sections" do
|
|
require "json"
|
|
|
|
sections_json = JSON.parse(sections_string)
|
|
sections_array.each_index do |i|
|
|
aggregate_failures "checks each component" do
|
|
expect(sections_array[i].name).to eq sections_json.first[0]
|
|
expect(sections_array[i].prefix).to eq sections_json.first[1]["prefix"]
|
|
expect(sections_array[i].labels).to eq sections_json.first[1]["labels"]
|
|
expect(sections_array[i].issues).to eq []
|
|
end
|
|
sections_json.shift
|
|
end
|
|
end
|
|
end
|
|
context "hash" do
|
|
let(:sections_hash) do
|
|
{
|
|
enhancements: {
|
|
prefix: "**Enhancements**",
|
|
labels: %w[feature enhancement]
|
|
},
|
|
breaking: {
|
|
prefix: "**Breaking**",
|
|
labels: ["breaking"]
|
|
},
|
|
bugs: {
|
|
prefix: "**Bugs**",
|
|
labels: ["bug"]
|
|
}
|
|
}
|
|
end
|
|
|
|
let(:sections_array) do
|
|
[
|
|
Section.new(name: "enhancements", prefix: "**Enhancements**", labels: %w[feature enhancement]),
|
|
Section.new(name: "breaking", prefix: "**Breaking**", labels: ["breaking"]),
|
|
Section.new(name: "bugs", prefix: "**Bugs**", labels: ["bug"])
|
|
]
|
|
end
|
|
|
|
it "returns an array with 3 objects" do
|
|
arr = subject.send(:parse_sections, sections_hash)
|
|
expect(arr.size).to eq 3
|
|
arr.each { |section| expect(section).to be_an_instance_of Section }
|
|
end
|
|
|
|
it "returns correctly constructed sections" do
|
|
sections_array.each_index do |i|
|
|
aggregate_failures "checks each component" do
|
|
expect(sections_array[i].name).to eq sections_hash.first[0].to_s
|
|
expect(sections_array[i].prefix).to eq sections_hash.first[1][:prefix]
|
|
expect(sections_array[i].labels).to eq sections_hash.first[1][:labels]
|
|
expect(sections_array[i].issues).to eq []
|
|
end
|
|
sections_hash.shift
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#parse_by_sections" do
|
|
context "default sections" do
|
|
let(:options) do
|
|
{
|
|
bug_labels: ["bug"],
|
|
enhancement_labels: ["enhancement"],
|
|
breaking_labels: ["breaking"]
|
|
}
|
|
end
|
|
|
|
let(:issues) do
|
|
[
|
|
issue("no labels", []),
|
|
issue("enhancement", ["enhancement"]),
|
|
issue("bug", ["bug"]),
|
|
issue("breaking", ["breaking"]),
|
|
issue("all the labels", %w[enhancement bug breaking])
|
|
]
|
|
end
|
|
|
|
let(:pull_requests) do
|
|
[
|
|
pr("no labels", []),
|
|
pr("enhancement", ["enhancement"]),
|
|
pr("bug", ["bug"]),
|
|
pr("breaking", ["breaking"]),
|
|
pr("all the labels", %w[enhancement bug breaking])
|
|
]
|
|
end
|
|
|
|
subject { described_class.new(options) }
|
|
|
|
before do
|
|
subject.send(:set_sections_and_maps)
|
|
@arr = subject.send(:parse_by_sections, pull_requests, issues)
|
|
end
|
|
|
|
it "returns 4 sections" do
|
|
expect(@arr.size).to eq 4
|
|
end
|
|
|
|
it "returns default sections" do
|
|
default_sections.each { |default_section| expect(@arr.select { |section| section.name == default_section }.size).to eq 1 }
|
|
end
|
|
|
|
it "assigns issues to the correct sections" do
|
|
breaking_section = @arr.select { |section| section.name == "breaking" }[0]
|
|
enhancement_section = @arr.select { |section| section.name == "enhancements" }[0]
|
|
issue_section = @arr.select { |section| section.name == "issues" }[0]
|
|
bug_section = @arr.select { |section| section.name == "bugs" }[0]
|
|
|
|
expect(titles_for(breaking_section.issues)).to eq(["issue breaking", "pr breaking"])
|
|
expect(titles_for(enhancement_section.issues)).to eq(["issue enhancement", "issue all the labels", "pr enhancement", "pr all the labels"])
|
|
expect(titles_for(issue_section.issues)).to eq(["issue no labels"])
|
|
expect(titles_for(bug_section.issues)).to eq(["issue bug", "pr bug"])
|
|
expect(titles_for(pull_requests)).to eq(["pr no labels"])
|
|
end
|
|
end
|
|
context "configure sections" do
|
|
let(:options) do
|
|
{
|
|
configure_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}"
|
|
}
|
|
end
|
|
|
|
let(:issues) do
|
|
[
|
|
issue("no labels", []),
|
|
issue("test1", ["test1"]),
|
|
issue("test3", ["test3"]),
|
|
issue("test4", ["test4"]),
|
|
issue("all the labels", %w[test1 test2 test3 test4])
|
|
]
|
|
end
|
|
|
|
let(:pull_requests) do
|
|
[
|
|
pr("no labels", []),
|
|
pr("test1", ["test1"]),
|
|
pr("test3", ["test3"]),
|
|
pr("test4", ["test4"]),
|
|
pr("all the labels", %w[test1 test2 test3 test4])
|
|
]
|
|
end
|
|
|
|
subject { described_class.new(options) }
|
|
|
|
before do
|
|
subject.send(:set_sections_and_maps)
|
|
@arr = subject.send(:parse_by_sections, pull_requests, issues)
|
|
end
|
|
|
|
it "returns 2 sections" do
|
|
expect(@arr.size).to eq 2
|
|
end
|
|
|
|
it "returns only configured sections" do
|
|
expect(@arr.select { |section| section.name == "foo" }.size).to eq 1
|
|
expect(@arr.select { |section| section.name == "bar" }.size).to eq 1
|
|
end
|
|
|
|
it "assigns issues to the correct sections" do
|
|
foo_section = @arr.select { |section| section.name == "foo" }[0]
|
|
bar_section = @arr.select { |section| section.name == "bar" }[0]
|
|
|
|
aggregate_failures "checks all sections" do
|
|
expect(titles_for(foo_section.issues)).to eq(["issue test1", "issue all the labels", "pr test1", "pr all the labels"])
|
|
expect(titles_for(bar_section.issues)).to eq(["issue test3", "issue test4", "pr test3", "pr test4"])
|
|
expect(titles_for(pull_requests)).to eq(["pr no labels"])
|
|
end
|
|
end
|
|
end
|
|
context "add sections" do
|
|
let(:options) do
|
|
{
|
|
bug_labels: ["bug"],
|
|
enhancement_labels: ["enhancement"],
|
|
breaking_labels: ["breaking"],
|
|
add_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}}"
|
|
}
|
|
end
|
|
|
|
let(:issues) do
|
|
[
|
|
issue("no labels", []),
|
|
issue("test1", ["test1"]),
|
|
issue("bugaboo", ["bug"]),
|
|
issue("all the labels", %w[test1 test2 enhancement bug])
|
|
]
|
|
end
|
|
|
|
let(:pull_requests) do
|
|
[
|
|
pr("no labels", []),
|
|
pr("test1", ["test1"]),
|
|
pr("enhance", ["enhancement"]),
|
|
pr("all the labels", %w[test1 test2 enhancement bug])
|
|
]
|
|
end
|
|
|
|
subject { described_class.new(options) }
|
|
|
|
before do
|
|
subject.send(:set_sections_and_maps)
|
|
@arr = subject.send(:parse_by_sections, pull_requests, issues)
|
|
end
|
|
|
|
it "returns 5 sections" do
|
|
expect(@arr.size).to eq 5
|
|
end
|
|
|
|
it "returns default sections" do
|
|
default_sections.each { |default_section| expect(@arr.select { |section| section.name == default_section }.size).to eq 1 }
|
|
end
|
|
|
|
it "returns added section" do
|
|
expect(@arr.select { |section| section.name == "foo" }.size).to eq 1
|
|
end
|
|
|
|
it "assigns issues to the correct sections" do
|
|
foo_section = @arr.select { |section| section.name == "foo" }[0]
|
|
enhancement_section = @arr.select { |section| section.name == "enhancements" }[0]
|
|
bug_section = @arr.select { |section| section.name == "bugs" }[0]
|
|
|
|
aggregate_failures "checks all sections" do
|
|
expect(titles_for(foo_section.issues)).to eq(["issue test1", "issue all the labels", "pr test1", "pr all the labels"])
|
|
expect(titles_for(enhancement_section.issues)).to eq(["pr enhance"])
|
|
expect(titles_for(bug_section.issues)).to eq(["issue bugaboo"])
|
|
expect(titles_for(pull_requests)).to eq(["pr no labels"])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
# rubocop:enable Metrics/ModuleLength
|