commenter 0.3.0 → 0.3.1

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,332 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zip"
4
+ require "nokogiri"
5
+ require_relative "../comment"
6
+ require_relative "../comment_sheet"
7
+
8
+ module Commenter
9
+ class Parser
10
+ # Extracts tracked changes from a redlined Word document into an ISO
11
+ # 2012-03 comment sheet.
12
+ #
13
+ # Captured track changes (w:ins, w:del, w:moveFrom, w:moveTo):
14
+ # - proposed_change renders the change itself, e.g. +Insert: "text"+
15
+ # - locality.clause is resolved from the nearest preceding heading
16
+ # - locality.element is resolved from caption/inline references
17
+ # (Table N, Figure N, Formula (N), NOTE n) within the current clause
18
+ # - observations can be stamped via the observations or accept_all options
19
+ #
20
+ # Reviewer comment threads (word/comments.xml w:comment) are emitted
21
+ # after the track changes with -CNNN ids: the remark verbatim in
22
+ # comments, its instruction reworded as the proposed change, and empty
23
+ # observations for the owner to draft.
24
+ #
25
+ # Self-closing markers (paragraph-mark insertions inside rPr) carry no
26
+ # content and are skipped.
27
+ #
28
+ # word/document.xml is streamed with Nokogiri::XML::Reader because redline
29
+ # documents can exceed 100 MB.
30
+ class TrackChangeDocxParser
31
+ W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
32
+
33
+ HEADING_STYLE = /\A(?:Heading[1-6]|h[2-5]annex\d*|ANNEX|BaseHeading)\z/i
34
+ KIND_COMMENT = { "ins" => "insertion", "del" => "deletion", "moveFrom" => "move (from)",
35
+ "moveTo" => "move (to)" }.freeze
36
+ KIND_LABEL = { "ins" => "Insert", "del" => "Delete", "moveFrom" => "Move from",
37
+ "moveTo" => "Move to" }.freeze
38
+
39
+ DEFAULT_BODY = "CS"
40
+ WHOLE_DOCUMENT = "_whole document"
41
+ ACCEPT_ALL = "Accepted. Tracked change accepted."
42
+
43
+ attr_reader :skipped_markers
44
+
45
+ def parse(path, options = {})
46
+ body = options[:body] || DEFAULT_BODY
47
+ changes = []
48
+ remarks = {}
49
+ anchors = {}
50
+ @skipped_markers = 0
51
+
52
+ Zip::File.open(path) do |zip|
53
+ entry = zip.glob("word/document.xml").first
54
+ raise Commenter::Error, "word/document.xml not found in #{path}" unless entry
55
+
56
+ remarks = read_remarks(zip)
57
+ changes = stream_changes(entry.get_input_stream, anchors)
58
+ end
59
+
60
+ CommentSheet.new(
61
+ version: "2012-03",
62
+ date: sheet_date(changes),
63
+ document: options[:document],
64
+ stage: options[:stage],
65
+ comments: build_change_comments(changes, body, options) +
66
+ build_remark_comments(remarks, anchors, body, options)
67
+ )
68
+ end
69
+
70
+ # Turns a reviewer's remark phrased as a request ("Please remove this
71
+ # NOTE...") into the corresponding proposed change ("Remove this
72
+ # NOTE...").
73
+ def self.reword_remark(text)
74
+ text.to_s.strip.sub(/\APlease\s+/i, "").sub(/\A[a-z]/, &:upcase)
75
+ end
76
+
77
+ private
78
+
79
+ def read_remarks(zip)
80
+ entry = zip.glob("word/comments.xml").first
81
+ return {} unless entry
82
+
83
+ document = Nokogiri::XML(entry.get_input_stream)
84
+ document.xpath("//w:comment", "w" => W_NS).each_with_object({}) do |node, remarks|
85
+ remarks[node["w:id"]] = {
86
+ author: node["w:author"],
87
+ date: node["w:date"],
88
+ text: node.xpath(".//w:t", "w" => W_NS).map(&:text).join
89
+ }
90
+ end
91
+ end
92
+
93
+ def sheet_date(changes)
94
+ dates = changes.filter_map { |change| change[:date].to_s[/\A\d{4}-\d{2}-\d{2}/] }
95
+ dates.max
96
+ end
97
+
98
+ def build_change_comments(changes, body, options)
99
+ changes.each_with_index.map do |change, index|
100
+ kind = change[:kind]
101
+ Comment.new(
102
+ id: format("%<body>s-%03<index>d", body: body, index: index + 1),
103
+ body: body,
104
+ locality: { clause: change[:clause], line_number: nil, element: change[:element] },
105
+ type: options[:type] || "te",
106
+ comments: "Track change (#{KIND_COMMENT.fetch(kind, kind)})",
107
+ proposed_change: "#{KIND_LABEL.fetch(kind, kind)}: \"#{change[:text].strip}\"",
108
+ observations: observations_for(options)
109
+ )
110
+ end
111
+ end
112
+
113
+ def build_remark_comments(remarks, anchors, body, options)
114
+ remarks.keys.sort_by(&:to_i).each_with_index.map do |remark_id, index|
115
+ remark = remarks[remark_id]
116
+ anchor = anchors[remark_id] || {}
117
+ clause = anchor[:clause].to_s.empty? ? WHOLE_DOCUMENT : anchor[:clause]
118
+ Comment.new(
119
+ id: format("%<body>s-C%03<index>d", body: body, index: index + 1),
120
+ body: body,
121
+ locality: { clause: clause, line_number: nil, element: anchor[:element] },
122
+ type: options[:remark_type] || "ed",
123
+ comments: remark[:text],
124
+ proposed_change: self.class.reword_remark(remark[:text]),
125
+ observations: nil
126
+ )
127
+ end
128
+ end
129
+
130
+ def observations_for(options)
131
+ return nil if options[:exclude_observations]
132
+
133
+ options[:observations] || (options[:accept_all] ? ACCEPT_ALL : nil)
134
+ end
135
+
136
+ def stream_changes(io, anchors = {})
137
+ changes = []
138
+ change_stack = []
139
+ paragraph = ParagraphState.new
140
+
141
+ Nokogiri::XML::Reader(io).each do |reader|
142
+ case reader.node_type
143
+ when Nokogiri::XML::Reader::TYPE_ELEMENT
144
+ handle_start(reader, change_stack, paragraph, anchors)
145
+ when Nokogiri::XML::Reader::TYPE_TEXT, Nokogiri::XML::Reader::TYPE_SIGNIFICANT_WHITESPACE
146
+ handle_text(reader, change_stack, paragraph)
147
+ when Nokogiri::XML::Reader::TYPE_END_ELEMENT
148
+ handle_end(reader, change_stack, paragraph, changes)
149
+ end
150
+ end
151
+
152
+ changes
153
+ end
154
+
155
+ def handle_start(reader, change_stack, paragraph, anchors = {})
156
+ case reader.local_name
157
+ when "p"
158
+ paragraph.begin_paragraph
159
+ when "pStyle"
160
+ paragraph.style = reader.attribute("w:val") || reader.attribute("val")
161
+ when "ins", "del", "moveFrom", "moveTo"
162
+ start_change(reader, change_stack, paragraph)
163
+ when "commentRangeStart"
164
+ anchor_remark(reader, paragraph, anchors)
165
+ end
166
+ end
167
+
168
+ def anchor_remark(reader, paragraph, anchors)
169
+ id = reader.attribute("w:id")
170
+ anchors[id] = {
171
+ clause: paragraph.pending_clause || paragraph.clause,
172
+ element: paragraph.pending_element || paragraph.element
173
+ }
174
+ end
175
+
176
+ def start_change(reader, change_stack, paragraph)
177
+ if reader.empty_element?
178
+ @skipped_markers += 1
179
+ return
180
+ end
181
+
182
+ change_stack.push(
183
+ kind: reader.local_name,
184
+ id: reader.attribute("w:id"),
185
+ author: reader.attribute("w:author"),
186
+ date: reader.attribute("w:date"),
187
+ text: +"",
188
+ clause: paragraph.clause,
189
+ element: paragraph.element
190
+ )
191
+ end
192
+
193
+ def handle_text(reader, change_stack, paragraph)
194
+ if (current = change_stack.last)
195
+ current[:text] << reader.value
196
+ else
197
+ paragraph.append_text(reader.value)
198
+ end
199
+ end
200
+
201
+ def handle_end(reader, change_stack, paragraph, changes)
202
+ case reader.local_name
203
+ when "p"
204
+ paragraph.commit_paragraph
205
+ when "ins", "del", "moveFrom", "moveTo"
206
+ change = change_stack.pop
207
+ changes << finalize_change(change, paragraph) if change
208
+ end
209
+ end
210
+
211
+ def finalize_change(change, paragraph)
212
+ # A change inside a heading or caption paragraph belongs to that
213
+ # paragraph, which is not committed yet at this point.
214
+ change[:clause] = paragraph.pending_clause || change[:clause]
215
+ change[:element] = paragraph.pending_element || change[:element]
216
+ change[:clause] = WHOLE_DOCUMENT if change[:clause].to_s.empty?
217
+ change
218
+ end
219
+
220
+ # Tracks the current paragraph's style and text to resolve the locality
221
+ # context of a change:
222
+ #
223
+ # - clause: the clause number of the most recent heading paragraph; an
224
+ # unnumbered sub-heading inherits its parent heading's clause
225
+ # - element: the nearest Table/Figure/Formula/NOTE reference, scoped to
226
+ # the current clause (caption paragraphs and inline mentions both
227
+ # count)
228
+ class ParagraphState
229
+ NUMBERED_CLAUSE = /\A(?:\d+(?:\.\d+)*|Annex\s+[A-Z](?:\.\d+)*|Bibliography|Foreword|Introduction)\z/i
230
+ ELEMENT_START = /\A\s*(NOTE\s+\d+|NOTE(?=\s*[—:-])|Table\s+(?:[A-Z]\.)?\d+(?:\.\d+)*|Figure\s+(?:[A-Z]\.)?\d+(?:\.\d+)*)\b/i
231
+ FORMULA = /\b(Formula\s*\(\d+(?:\.\d+)*\))/
232
+ ELEMENT_ANY = /\b(Table\s+(?:[A-Z]\.)?\d+(?:\.\d+)*|Figure\s+(?:[A-Z]\.)?\d+(?:\.\d+)*|NOTE\s+\d+)\b/i
233
+
234
+ attr_writer :style
235
+ attr_reader :clause, :element
236
+
237
+ def initialize
238
+ @style = nil
239
+ @text = +""
240
+ @clause = ""
241
+ @element = nil
242
+ @heading_stack = []
243
+ end
244
+
245
+ def begin_paragraph
246
+ @style = nil
247
+ @text = +""
248
+ end
249
+
250
+ def append_text(value)
251
+ @text << value
252
+ end
253
+
254
+ def pending_clause
255
+ return unless heading_paragraph?
256
+
257
+ clause = self.class.clause_for(@text)
258
+ clause unless clause.empty?
259
+ end
260
+
261
+ def pending_element
262
+ self.class.element_for(@text)
263
+ end
264
+
265
+ def commit_paragraph
266
+ if heading_paragraph?
267
+ commit_heading
268
+ else
269
+ @element = self.class.element_for(@text) || @element
270
+ end
271
+ end
272
+
273
+ def heading_paragraph?
274
+ @style&.match?(HEADING_STYLE)
275
+ end
276
+
277
+ def self.numbered_clause?(clause)
278
+ clause.to_s.match?(NUMBERED_CLAUSE)
279
+ end
280
+
281
+ # Extracts the clause identifier from heading text: the leading number
282
+ # ("4.2.1 Thermodynamic ..." -> "4.2.1"), an annex reference, or a
283
+ # well-known unnumbered section. Runs may be concatenated without
284
+ # spaces ("4.2.1Thermodynamic"), so the number is matched with a
285
+ # lookahead.
286
+ def self.clause_for(text)
287
+ s = text.to_s.strip
288
+ return "" if s.empty?
289
+
290
+ return Regexp.last_match(1) if s =~ /\A\s*(Annex\s+[A-Z](?:\.\d+)*)\b/i
291
+ return Regexp.last_match(1) if s =~ /\A\s*(Bibliography|Foreword|Introduction)\b/i
292
+ return Regexp.last_match(1) if s =~ /\A\s*((?:\d+\.)*\d+)(?=[A-Z\s])/
293
+ return Regexp.last_match(1) if s =~ /\A\s*((?:\d+\.)*\d+)\b/
294
+
295
+ s
296
+ end
297
+
298
+ # Resolves the element reference of a paragraph: a caption opening the
299
+ # paragraph ("Table 3 — ...", "NOTE 2 ...", "Formula (9):") wins over an
300
+ # inline mention ("the values in Table 5 shall ...").
301
+ def self.element_for(text)
302
+ s = text.to_s
303
+ match = s.match(ELEMENT_START) || s.match(FORMULA) || s.match(ELEMENT_ANY)
304
+ match[1].squeeze(" ").strip if match
305
+ end
306
+
307
+ private
308
+
309
+ def commit_heading
310
+ level = heading_level
311
+ clause = self.class.clause_for(@text)
312
+ clause = inherited_clause(level) unless self.class.numbered_clause?(clause)
313
+ @heading_stack.pop while @heading_stack.last && @heading_stack.last[0] >= level
314
+ @heading_stack << [level, clause]
315
+ @clause = clause unless clause.empty?
316
+ # Element references belong to the clause they appear in.
317
+ @element = nil
318
+ end
319
+
320
+ def inherited_clause(level)
321
+ parent = @heading_stack.reverse.find { |(lvl, _)| lvl < level }
322
+ parent ? parent[1] : ""
323
+ end
324
+
325
+ def heading_level
326
+ m = @style.match(/\A(?:Heading|h)(\d+)/i)
327
+ m ? m[1].to_i : 1
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
@@ -5,6 +5,7 @@ require "pathname"
5
5
  require_relative "comment_sheet"
6
6
  require_relative "comment"
7
7
  require_relative "parser/osd_xlsx_parser"
8
+ require_relative "parser/track_change_docx_parser"
8
9
 
9
10
  module Commenter
10
11
  class Parser
@@ -16,6 +17,8 @@ module Commenter
16
17
  parse_docx(input_path, options)
17
18
  when :xlsx
18
19
  parse_xlsx(input_path, options)
20
+ when :redline
21
+ parse_redline(input_path, options)
19
22
  else
20
23
  raise "Unsupported file format: #{input_path}. Supported formats: .docx, .xlsx"
21
24
  end
@@ -38,6 +41,10 @@ module Commenter
38
41
  OsdXlsxParser.new.parse(xlsx_path, options)
39
42
  end
40
43
 
44
+ def parse_redline(docx_path, options)
45
+ TrackChangeDocxParser.new.parse(docx_path, options)
46
+ end
47
+
41
48
  def parse_docx(docx_path, options)
42
49
  doc = Docx::Document.open(docx_path)
43
50
 
@@ -83,7 +90,7 @@ module Commenter
83
90
  clause: presence(cells[2]),
84
91
  element: presence(cells[3])
85
92
  },
86
- type: normalize_type(cells[4]),
93
+ type: CommentType.code(cells[4]),
87
94
  comments: cells[5] || "",
88
95
  proposed_change: presence(cells[6]),
89
96
  user_name: cells[0].to_s.strip
@@ -117,15 +124,6 @@ module Commenter
117
124
  value && !value.empty? ? value : nil
118
125
  end
119
126
 
120
- def normalize_type(type_str)
121
- case type_str.to_s.strip.downcase
122
- when "editorial" then "ed"
123
- when "technical" then "te"
124
- when "general" then "ge"
125
- else type_str.to_s.strip
126
- end
127
- end
128
-
129
127
  def extract_metadata(doc)
130
128
  metadata = { date: nil, document: nil, project: nil }
131
129
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Commenter
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/commenter.rb CHANGED
@@ -7,6 +7,11 @@ require_relative "commenter/parser"
7
7
  require_relative "commenter/filler"
8
8
  require_relative "commenter/github_integration"
9
9
 
10
+ # Commenter converts ISO comment sheets (DOCX/XLSX) to structured YAML and
11
+ # syncs comments to GitHub issues.
10
12
  module Commenter
11
13
  class Error < StandardError; end
14
+
15
+ autoload :CommentType, "commenter/comment_type"
16
+ autoload :GitHubSession, "commenter/github_session"
12
17
  end
@@ -169,6 +169,31 @@ RSpec.describe Commenter::CommentSheet do
169
169
  expect(sheet.comments.length).to eq(1)
170
170
  end
171
171
  end
172
+ end
173
+
174
+ RSpec.describe Commenter::CommentSheet do
175
+ describe "#schema_name" do
176
+ it "selects the schema for each sheet version" do
177
+ expect(described_class.new(version: "osd").schema_name).to eq("iso_comment_osd.yaml")
178
+ expect(described_class.new(version: "2012-03").schema_name).to eq("iso_comment_2012-03.yaml")
179
+ expect(described_class.new({}).schema_name).to eq("iso_comment_2012-03.yaml")
180
+ end
181
+ end
182
+
183
+ describe "#to_yaml_document" do
184
+ it "prepends a header referencing the schema matching the version" do
185
+ document = described_class.new(version: "osd", comments: []).to_yaml_document
186
+
187
+ expect(document.lines.first).to eq("# yaml-language-server: $schema=schema/iso_comment_osd.yaml\n")
188
+ expect(YAML.safe_load(document)).to include("version" => "osd")
189
+ end
190
+
191
+ it "accepts a schema directory" do
192
+ document = described_class.new(comments: []).to_yaml_document("schemas")
193
+
194
+ expect(document.lines.first).to eq("# yaml-language-server: $schema=schemas/iso_comment_2012-03.yaml\n")
195
+ end
196
+ end
172
197
 
173
198
  describe "stage validation" do
174
199
  it "accepts valid stage values" do
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Commenter::CommentType do
6
+ describe ".code" do
7
+ it "maps full names and codes to codes" do
8
+ expect(described_class.code("Editorial")).to eq("ed")
9
+ expect(described_class.code("general")).to eq("ge")
10
+ expect(described_class.code("te")).to eq("te")
11
+ end
12
+
13
+ it "passes unrecognized values through" do
14
+ expect(described_class.code("custom")).to eq("custom")
15
+ expect(described_class.code("")).to eq("")
16
+ expect(described_class.code(nil)).to be_nil
17
+ end
18
+ end
19
+
20
+ describe ".full_name" do
21
+ it "expands codes to full names" do
22
+ expect(described_class.full_name("ge")).to eq("general")
23
+ expect(described_class.full_name("TE")).to eq("technical")
24
+ expect(described_class.full_name("editorial")).to eq("editorial")
25
+ end
26
+
27
+ it "passes unrecognized values through" do
28
+ expect(described_class.full_name("Custom")).to eq("Custom")
29
+ expect(described_class.full_name(nil)).to be_nil
30
+ end
31
+ end
32
+
33
+ describe ".display_name" do
34
+ it "capitalizes recognized types for display" do
35
+ expect(described_class.display_name("ge")).to eq("General")
36
+ expect(described_class.display_name("technical")).to eq("Technical")
37
+ expect(described_class.display_name("ed")).to eq("Editorial")
38
+ end
39
+
40
+ it "defaults unknown or missing types" do
41
+ expect(described_class.display_name("unknown")).to eq("unknown")
42
+ expect(described_class.display_name(nil)).to eq("Unknown")
43
+ end
44
+ end
45
+ end
@@ -135,49 +135,82 @@ RSpec.describe Commenter::GitHubIssueCreator do
135
135
 
136
136
  describe "template variable generation" do
137
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("technical")
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
138
+ let(:title_template_file) do
139
+ file = Tempfile.new(["title", ".liquid"])
140
+ file.write("{{ stage }}|{{ document }}|{{ project }}|{{ version }}|{{ comment_id }}|{{ type }}|" \
141
+ "{{ type_full_name }}|{{ clause }}|{{ element }}|{{ line_number }}|" \
142
+ "{{ has_observations }}|{{ has_proposed_change }}|{{ locality_summary }}|{{ unique_id }}")
143
+ file.close
144
+ file
145
+ end
146
+
147
+ it "exposes sheet, comment, and computed variables to templates" do
148
+ result = creator.create_issues_from_yaml(yaml_file.path, dry_run: true).first
149
+ variables = result[:title].split("|")
150
+
151
+ expect(variables).to eq(
152
+ [
153
+ "DIS", "Test Document", "Test Project", "2012-03", "US-001", "technical",
154
+ "Technical", "5.1", "Table 1", "", "false", "true", "Clause 5.1, Table 1", "[DIS] US-001"
155
+ ]
156
+ )
154
157
  end
155
158
  end
156
159
 
157
160
  describe "label determination" do
158
161
  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
 
162
- it "combines default, stage-specific, and comment type labels" do
163
- labels = creator.send(:determine_labels, comment, comment_sheet)
163
+ it "combines default, stage-specific, and comment type labels without duplicates" do
164
+ result = creator.create_issues_from_yaml(yaml_file.path, dry_run: true).first
165
+ labels = result[:labels]
164
166
 
165
- expect(labels).to include("comment-review") # default
166
- expect(labels).to include("draft-international-standard") # stage-specific
167
- expect(labels).to include("technical") # comment type (expanded)
168
- expect(labels.uniq).to eq(labels) # no duplicates
167
+ expect(labels).to include("comment-review", "draft-international-standard", "technical")
168
+ expect(labels.uniq).to eq(labels)
169
169
  end
170
170
  end
171
+ end
171
172
 
172
- describe "comment type expansion" do
173
- let(:creator) { described_class.new(config_file.path, title_template_file.path, body_template_file.path) }
173
+ RSpec.describe Commenter::GitHubIssueRetriever do
174
+ let(:config_file) do
175
+ file = Tempfile.new(["config", ".yaml"])
176
+ file.write({ "github" => { "repository" => "test-org/test-repo", "token" => "test-token" } }.to_yaml)
177
+ file.close
178
+ file
179
+ end
180
+
181
+ let(:osd_yaml_file) do
182
+ file = Tempfile.new(["comments", ".yaml"])
183
+ file.write({
184
+ "version" => "osd",
185
+ "document" => "ISO/DIS 5843-6(en)",
186
+ "stage" => "DIS",
187
+ "comments" => [
188
+ {
189
+ "id" => "1",
190
+ "body" => "John Doe",
191
+ "locality" => { "clause" => "5.2.1" },
192
+ "type" => "editorial",
193
+ "comments" => "The values in Table 3 are inconsistent."
194
+ }
195
+ ]
196
+ }.to_yaml)
197
+ file.close
198
+ file
199
+ end
200
+
201
+ after do
202
+ config_file.unlink
203
+ osd_yaml_file.unlink
204
+ end
205
+
206
+ describe "#retrieve_observations_from_yaml" do
207
+ it "rewrites the YAML with the schema header matching its version" do
208
+ retriever = described_class.new(config_file.path)
209
+
210
+ retriever.retrieve_observations_from_yaml(osd_yaml_file.path)
174
211
 
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")
212
+ expect(osd_yaml_file.open.read.lines.first)
213
+ .to eq("# yaml-language-server: $schema=schema/iso_comment_osd.yaml\n")
181
214
  end
182
215
  end
183
216
  end