commenter 0.2.1 → 0.2.2

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.
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Commenter::CommentSheet do
6
+ describe "#initialize" do
7
+ it "creates a comment sheet with all attributes" do
8
+ attributes = {
9
+ version: "2012-03",
10
+ date: "2024-06-04",
11
+ document: "ISO 80000-2:2019",
12
+ project: "Mathematics review",
13
+ stage: "DIS",
14
+ comments: [
15
+ {
16
+ id: "US-001",
17
+ body: "US",
18
+ locality: { clause: "5.1" },
19
+ type: "te",
20
+ comments: "Test comment",
21
+ proposed_change: "Test change"
22
+ }
23
+ ]
24
+ }
25
+
26
+ sheet = described_class.new(attributes)
27
+
28
+ expect(sheet.version).to eq("2012-03")
29
+ expect(sheet.date).to eq("2024-06-04")
30
+ expect(sheet.document).to eq("ISO 80000-2:2019")
31
+ expect(sheet.project).to eq("Mathematics review")
32
+ expect(sheet.stage).to eq("DIS")
33
+ expect(sheet.comments).to be_an(Array)
34
+ expect(sheet.comments.length).to eq(1)
35
+ expect(sheet.comments.first).to be_a(Commenter::Comment)
36
+ end
37
+
38
+ it "handles string keys in attributes" do
39
+ attributes = {
40
+ "version" => "2012-03",
41
+ "stage" => "CD",
42
+ "comments" => []
43
+ }
44
+
45
+ sheet = described_class.new(attributes)
46
+
47
+ expect(sheet.version).to eq("2012-03")
48
+ expect(sheet.stage).to eq("CD")
49
+ end
50
+
51
+ it "sets default version when not provided" do
52
+ sheet = described_class.new({})
53
+
54
+ expect(sheet.version).to eq("2012-03")
55
+ end
56
+
57
+ it "converts comment hashes to Comment objects" do
58
+ attributes = {
59
+ comments: [
60
+ {
61
+ id: "US-001",
62
+ locality: { clause: "5.1" }
63
+ }
64
+ ]
65
+ }
66
+
67
+ sheet = described_class.new(attributes)
68
+
69
+ expect(sheet.comments.first).to be_a(Commenter::Comment)
70
+ expect(sheet.comments.first.id).to eq("US-001")
71
+ end
72
+
73
+ it "preserves existing Comment objects" do
74
+ comment = Commenter::Comment.new(id: "US-001")
75
+ attributes = { comments: [comment] }
76
+
77
+ sheet = described_class.new(attributes)
78
+
79
+ expect(sheet.comments.first).to be(comment)
80
+ end
81
+ end
82
+
83
+ describe "#add_comment" do
84
+ let(:sheet) { described_class.new({}) }
85
+
86
+ it "adds a Comment object" do
87
+ comment = Commenter::Comment.new(id: "US-001")
88
+ sheet.add_comment(comment)
89
+
90
+ expect(sheet.comments).to include(comment)
91
+ end
92
+
93
+ it "converts hash to Comment object" do
94
+ comment_hash = { id: "US-001", locality: { clause: "5.1" } }
95
+ sheet.add_comment(comment_hash)
96
+
97
+ expect(sheet.comments.length).to eq(1)
98
+ expect(sheet.comments.first).to be_a(Commenter::Comment)
99
+ expect(sheet.comments.first.id).to eq("US-001")
100
+ end
101
+ end
102
+
103
+ describe "#to_h" do
104
+ it "converts comment sheet to hash including stage" do
105
+ sheet = described_class.new(
106
+ version: "2012-03",
107
+ date: "2024-06-04",
108
+ document: "Test Document",
109
+ project: "Test Project",
110
+ stage: "DIS",
111
+ comments: [
112
+ Commenter::Comment.new(id: "US-001", locality: { clause: "5.1" })
113
+ ]
114
+ )
115
+
116
+ hash = sheet.to_h
117
+
118
+ expect(hash[:version]).to eq("2012-03")
119
+ expect(hash[:date]).to eq("2024-06-04")
120
+ expect(hash[:document]).to eq("Test Document")
121
+ expect(hash[:project]).to eq("Test Project")
122
+ expect(hash[:stage]).to eq("DIS")
123
+ expect(hash[:comments]).to be_an(Array)
124
+ expect(hash[:comments].first).to be_a(Hash)
125
+ end
126
+
127
+ it "includes nil stage when not set" do
128
+ sheet = described_class.new({})
129
+ hash = sheet.to_h
130
+
131
+ expect(hash).to have_key(:stage)
132
+ expect(hash[:stage]).to be_nil
133
+ end
134
+ end
135
+
136
+ describe "#to_yaml_h" do
137
+ it "converts to YAML-friendly hash with string keys" do
138
+ sheet = described_class.new(
139
+ stage: "DIS",
140
+ comments: [
141
+ Commenter::Comment.new(id: "US-001")
142
+ ]
143
+ )
144
+
145
+ yaml_hash = sheet.to_yaml_h
146
+
147
+ expect(yaml_hash["stage"]).to eq("DIS")
148
+ expect(yaml_hash["comments"]).to be_an(Array)
149
+ expect(yaml_hash["comments"].first).to be_a(Hash)
150
+ expect(yaml_hash["comments"].first["id"]).to eq("US-001")
151
+ end
152
+ end
153
+
154
+ describe ".from_hash" do
155
+ it "creates comment sheet from hash" do
156
+ hash = {
157
+ version: "2012-03",
158
+ stage: "CD",
159
+ comments: [
160
+ { id: "US-001", locality: { clause: "5.1" } }
161
+ ]
162
+ }
163
+
164
+ sheet = described_class.from_hash(hash)
165
+
166
+ expect(sheet).to be_a(described_class)
167
+ expect(sheet.version).to eq("2012-03")
168
+ expect(sheet.stage).to eq("CD")
169
+ expect(sheet.comments.length).to eq(1)
170
+ end
171
+ end
172
+
173
+ describe "stage validation" do
174
+ it "accepts valid stage values" do
175
+ valid_stages = %w[WD CD DIS FDIS PRF PUB]
176
+
177
+ valid_stages.each do |stage|
178
+ sheet = described_class.new(stage: stage)
179
+ expect(sheet.stage).to eq(stage)
180
+ end
181
+ end
182
+
183
+ it "accepts nil stage" do
184
+ sheet = described_class.new(stage: nil)
185
+ expect(sheet.stage).to be_nil
186
+ end
187
+
188
+ it "accepts custom stage values" do
189
+ sheet = described_class.new(stage: "CUSTOM")
190
+ expect(sheet.stage).to eq("CUSTOM")
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Commenter::Comment do
6
+ describe "#initialize" do
7
+ it "creates a comment with all attributes" do
8
+ attributes = {
9
+ id: "US-001",
10
+ body: "US",
11
+ locality: { clause: "5.1", element: "Table 1", line_number: "42" },
12
+ type: "te",
13
+ comments: "Test comment",
14
+ proposed_change: "Test change",
15
+ observations: "Test observations"
16
+ }
17
+
18
+ comment = described_class.new(attributes)
19
+
20
+ expect(comment.id).to eq("US-001")
21
+ expect(comment.body).to eq("US")
22
+ expect(comment.clause).to eq("5.1")
23
+ expect(comment.element).to eq("Table 1")
24
+ expect(comment.line_number).to eq("42")
25
+ expect(comment.type).to eq("te")
26
+ expect(comment.comments).to eq("Test comment")
27
+ expect(comment.proposed_change).to eq("Test change")
28
+ expect(comment.observations).to eq("Test observations")
29
+ end
30
+
31
+ it "handles string keys in attributes" do
32
+ attributes = {
33
+ "id" => "US-001",
34
+ "locality" => { "clause" => "5.1" }
35
+ }
36
+
37
+ comment = described_class.new(attributes)
38
+
39
+ expect(comment.id).to eq("US-001")
40
+ expect(comment.clause).to eq("5.1")
41
+ end
42
+ end
43
+
44
+ describe "#brief_summary" do
45
+ context "with locality information" do
46
+ it "includes clause and element" do
47
+ comment = described_class.new(
48
+ locality: { clause: "5.1", element: "Table 1" },
49
+ comments: "This is a test comment"
50
+ )
51
+
52
+ summary = comment.brief_summary
53
+
54
+ expect(summary).to include("Clause 5.1")
55
+ expect(summary).to include("Table 1")
56
+ expect(summary).to include("This is a test comment")
57
+ end
58
+
59
+ it "includes line number when present" do
60
+ comment = described_class.new(
61
+ locality: { clause: "5.1", line_number: "42" },
62
+ comments: "Test comment"
63
+ )
64
+
65
+ summary = comment.brief_summary
66
+
67
+ expect(summary).to include("Clause 5.1")
68
+ expect(summary).to include("Line 42")
69
+ end
70
+
71
+ it "handles empty locality gracefully" do
72
+ comment = described_class.new(
73
+ locality: { clause: "" },
74
+ comments: "Test comment"
75
+ )
76
+
77
+ summary = comment.brief_summary
78
+
79
+ expect(summary).to eq("Test comment")
80
+ end
81
+ end
82
+
83
+ context "with comment text" do
84
+ it "uses first sentence when short enough" do
85
+ comment = described_class.new(
86
+ locality: { clause: "5.1" },
87
+ comments: "Short comment. This is ignored."
88
+ )
89
+
90
+ summary = comment.brief_summary
91
+
92
+ expect(summary).to include("Short comment")
93
+ expect(summary).not_to include("This is ignored")
94
+ end
95
+
96
+ it "truncates long text" do
97
+ long_text = "This is a very long comment that exceeds the typical length limit and should be truncated appropriately"
98
+ comment = described_class.new(
99
+ locality: { clause: "5.1" },
100
+ comments: long_text
101
+ )
102
+
103
+ summary = comment.brief_summary(80)
104
+
105
+ expect(summary.length).to be <= 80
106
+ expect(summary).to include("Clause 5.1")
107
+ end
108
+
109
+ it "handles empty comments" do
110
+ comment = described_class.new(
111
+ locality: { clause: "5.1" },
112
+ comments: ""
113
+ )
114
+
115
+ summary = comment.brief_summary
116
+
117
+ expect(summary).to eq("Clause 5.1")
118
+ end
119
+
120
+ it "handles nil comments" do
121
+ comment = described_class.new(
122
+ locality: { clause: "5.1" },
123
+ comments: nil
124
+ )
125
+
126
+ summary = comment.brief_summary
127
+
128
+ expect(summary).to eq("Clause 5.1")
129
+ end
130
+ end
131
+
132
+ context "without locality or comments" do
133
+ it "returns default message" do
134
+ comment = described_class.new(
135
+ locality: { clause: "" },
136
+ comments: ""
137
+ )
138
+
139
+ summary = comment.brief_summary
140
+
141
+ expect(summary).to eq("No description")
142
+ end
143
+ end
144
+
145
+ context "respects max_length parameter" do
146
+ it "truncates to specified length" do
147
+ comment = described_class.new(
148
+ locality: { clause: "5.1" },
149
+ comments: "This is a test comment"
150
+ )
151
+
152
+ summary = comment.brief_summary(20)
153
+
154
+ expect(summary.length).to be <= 20
155
+ end
156
+ end
157
+ end
158
+
159
+ describe "#to_h" do
160
+ it "converts comment to hash" do
161
+ comment = described_class.new(
162
+ id: "US-001",
163
+ body: "US",
164
+ locality: { clause: "5.1" },
165
+ type: "te",
166
+ comments: "Test",
167
+ proposed_change: "Change",
168
+ observations: "Obs"
169
+ )
170
+
171
+ hash = comment.to_h
172
+
173
+ expect(hash[:id]).to eq("US-001")
174
+ expect(hash[:body]).to eq("US")
175
+ expect(hash[:locality][:clause]).to eq("5.1")
176
+ expect(hash[:type]).to eq("te")
177
+ expect(hash[:comments]).to eq("Test")
178
+ expect(hash[:proposed_change]).to eq("Change")
179
+ expect(hash[:observations]).to eq("Obs")
180
+ end
181
+ end
182
+
183
+ describe "#to_yaml_h" do
184
+ it "converts to YAML-friendly hash with string keys" do
185
+ comment = described_class.new(
186
+ id: "US-001",
187
+ locality: { clause: "5.1" },
188
+ observations: nil
189
+ )
190
+
191
+ yaml_hash = comment.to_yaml_h
192
+
193
+ expect(yaml_hash["id"]).to eq("US-001")
194
+ expect(yaml_hash["locality"]["clause"]).to eq("5.1")
195
+ expect(yaml_hash).not_to have_key("observations") # nil observations removed
196
+ end
197
+
198
+ it "removes empty observations" do
199
+ comment = described_class.new(observations: "")
200
+ yaml_hash = comment.to_yaml_h
201
+
202
+ expect(yaml_hash).not_to have_key("observations")
203
+ end
204
+ end
205
+
206
+ describe ".from_hash" do
207
+ it "creates comment from hash" do
208
+ hash = {
209
+ id: "US-001",
210
+ locality: { clause: "5.1" }
211
+ }
212
+
213
+ comment = described_class.from_hash(hash)
214
+
215
+ expect(comment).to be_a(described_class)
216
+ expect(comment.id).to eq("US-001")
217
+ expect(comment.clause).to eq("5.1")
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "tempfile"
5
+ require "yaml"
6
+
7
+ RSpec.describe Commenter::GitHubIssueCreator do
8
+ let(:config_data) do
9
+ {
10
+ "github" => {
11
+ "repository" => "test-org/test-repo",
12
+ "token" => "test-token",
13
+ "default_labels" => ["comment-review"],
14
+ "stage_labels" => {
15
+ "DIS" => ["draft-international-standard"]
16
+ },
17
+ "default_assignee" => "test-assignee",
18
+ "milestone" => {
19
+ "name" => "Test Milestone"
20
+ }
21
+ }
22
+ }
23
+ end
24
+
25
+ let(:config_file) do
26
+ file = Tempfile.new(["config", ".yaml"])
27
+ file.write(config_data.to_yaml)
28
+ file.close
29
+ file
30
+ end
31
+
32
+ let(:title_template_file) do
33
+ file = Tempfile.new(["title", ".liquid"])
34
+ file.write("{{ comment_id }}: {{ brief_summary }}")
35
+ file.close
36
+ file
37
+ end
38
+
39
+ let(:body_template_file) do
40
+ file = Tempfile.new(["body", ".liquid"])
41
+ file.write("Comment: {{ comments }}\nType: {{ type_full_name }}")
42
+ file.close
43
+ file
44
+ end
45
+
46
+ let(:yaml_data) do
47
+ {
48
+ "version" => "2012-03",
49
+ "stage" => "DIS",
50
+ "document" => "Test Document",
51
+ "project" => "Test Project",
52
+ "comments" => [
53
+ {
54
+ "id" => "US-001",
55
+ "body" => "US",
56
+ "locality" => {
57
+ "clause" => "5.1",
58
+ "element" => "Table 1"
59
+ },
60
+ "type" => "te",
61
+ "comments" => "Test comment text",
62
+ "proposed_change" => "Test proposed change"
63
+ }
64
+ ]
65
+ }
66
+ end
67
+
68
+ let(:yaml_file) do
69
+ file = Tempfile.new(["comments", ".yaml"])
70
+ file.write(yaml_data.to_yaml)
71
+ file.close
72
+ file
73
+ end
74
+
75
+ after do
76
+ config_file.unlink
77
+ title_template_file.unlink
78
+ body_template_file.unlink
79
+ yaml_file.unlink
80
+ end
81
+
82
+ describe "#initialize" do
83
+ it "loads configuration and creates GitHub client" do
84
+ expect { described_class.new(config_file.path, title_template_file.path, body_template_file.path) }
85
+ .not_to raise_error
86
+ end
87
+
88
+ it "raises error when config file not found" do
89
+ expect { described_class.new("nonexistent.yaml") }
90
+ .to raise_error("Configuration file not found: nonexistent.yaml")
91
+ end
92
+
93
+ it "raises error when repository not specified" do
94
+ config_without_repo = config_data.dup
95
+ config_without_repo["github"].delete("repository")
96
+
97
+ file = Tempfile.new(["config", ".yaml"])
98
+ file.write(config_without_repo.to_yaml)
99
+ file.close
100
+
101
+ expect { described_class.new(file.path) }
102
+ .to raise_error("GitHub repository not specified in config")
103
+
104
+ file.unlink
105
+ end
106
+ end
107
+
108
+ describe "#create_issues_from_yaml" do
109
+ let(:creator) { described_class.new(config_file.path, title_template_file.path, body_template_file.path) }
110
+
111
+ context "with dry_run option" do
112
+ it "returns preview data without creating issues" do
113
+ results = creator.create_issues_from_yaml(yaml_file.path, dry_run: true)
114
+
115
+ expect(results).to be_an(Array)
116
+ expect(results.length).to eq(1)
117
+
118
+ result = results.first
119
+ expect(result[:comment_id]).to eq("US-001")
120
+ expect(result[:title]).to eq("US-001: Clause 5.1, Table 1: Test comment text")
121
+ expect(result[:body]).to include("Comment: Test comment text")
122
+ expect(result[:body]).to include("Type: Technical")
123
+ expect(result[:labels]).to include("comment-review", "draft-international-standard", "te")
124
+ expect(result[:assignees]).to eq(["test-assignee"])
125
+ end
126
+ end
127
+
128
+ it "processes stage override" do
129
+ results = creator.create_issues_from_yaml(yaml_file.path, dry_run: true, stage: "CD")
130
+
131
+ result = results.first
132
+ expect(result[:labels]).not_to include("draft-international-standard")
133
+ end
134
+ end
135
+
136
+ describe "template variable generation" do
137
+ let(:creator) { described_class.new(config_file.path, title_template_file.path, body_template_file.path) }
138
+ let(:comment_sheet) { Commenter::CommentSheet.from_hash(yaml_data) }
139
+ let(:comment) { comment_sheet.comments.first }
140
+
141
+ it "generates correct template variables" do
142
+ variables = creator.send(:template_variables, comment, comment_sheet)
143
+
144
+ expect(variables["stage"]).to eq("DIS")
145
+ expect(variables["document"]).to eq("Test Document")
146
+ expect(variables["comment_id"]).to eq("US-001")
147
+ expect(variables["type"]).to eq("te")
148
+ expect(variables["type_full_name"]).to eq("Technical")
149
+ expect(variables["clause"]).to eq("5.1")
150
+ expect(variables["element"]).to eq("Table 1")
151
+ expect(variables["comments"]).to eq("Test comment text")
152
+ expect(variables["brief_summary"]).to include("Clause 5.1")
153
+ expect(variables["has_proposed_change"]).to be true
154
+ end
155
+ end
156
+
157
+ describe "label determination" do
158
+ let(:creator) { described_class.new(config_file.path, title_template_file.path, body_template_file.path) }
159
+ let(:comment_sheet) { Commenter::CommentSheet.from_hash(yaml_data) }
160
+ let(:comment) { comment_sheet.comments.first }
161
+
162
+ it "combines default, stage-specific, and comment type labels" do
163
+ labels = creator.send(:determine_labels, comment, comment_sheet)
164
+
165
+ expect(labels).to include("comment-review") # default
166
+ expect(labels).to include("draft-international-standard") # stage-specific
167
+ expect(labels).to include("te") # comment type
168
+ expect(labels.uniq).to eq(labels) # no duplicates
169
+ end
170
+ end
171
+
172
+ describe "comment type expansion" do
173
+ let(:creator) { described_class.new(config_file.path, title_template_file.path, body_template_file.path) }
174
+
175
+ it "expands comment type codes correctly" do
176
+ expect(creator.send(:expand_comment_type, "ge")).to eq("General")
177
+ expect(creator.send(:expand_comment_type, "te")).to eq("Technical")
178
+ expect(creator.send(:expand_comment_type, "ed")).to eq("Editorial")
179
+ expect(creator.send(:expand_comment_type, "unknown")).to eq("unknown")
180
+ expect(creator.send(:expand_comment_type, nil)).to eq("Unknown")
181
+ end
182
+ end
183
+ end
@@ -4,5 +4,4 @@ RSpec.describe Commenter do
4
4
  it "has a version number" do
5
5
  expect(Commenter::VERSION).not_to be nil
6
6
  end
7
-
8
7
  end