prosereflect 0.1.1 → 0.2.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +4 -0
  3. data/.github/workflows/release.yml +5 -0
  4. data/.rubocop.yml +19 -1
  5. data/.rubocop_todo.yml +141 -191
  6. data/CLAUDE.md +78 -0
  7. data/Gemfile +8 -4
  8. data/Rakefile +3 -3
  9. data/lib/prosereflect/attribute/base.rb +4 -6
  10. data/lib/prosereflect/attribute/bold.rb +2 -4
  11. data/lib/prosereflect/attribute/href.rb +1 -3
  12. data/lib/prosereflect/attribute/id.rb +7 -7
  13. data/lib/prosereflect/attribute.rb +4 -7
  14. data/lib/prosereflect/blockquote.rb +10 -11
  15. data/lib/prosereflect/bullet_list.rb +16 -15
  16. data/lib/prosereflect/code_block.rb +26 -26
  17. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  18. data/lib/prosereflect/document.rb +14 -22
  19. data/lib/prosereflect/hard_break.rb +6 -6
  20. data/lib/prosereflect/heading.rb +14 -15
  21. data/lib/prosereflect/horizontal_rule.rb +14 -14
  22. data/lib/prosereflect/image.rb +23 -23
  23. data/lib/prosereflect/input/html.rb +83 -104
  24. data/lib/prosereflect/input.rb +7 -0
  25. data/lib/prosereflect/list_item.rb +11 -12
  26. data/lib/prosereflect/mark/base.rb +9 -11
  27. data/lib/prosereflect/mark/bold.rb +1 -3
  28. data/lib/prosereflect/mark/code.rb +1 -3
  29. data/lib/prosereflect/mark/italic.rb +1 -3
  30. data/lib/prosereflect/mark/link.rb +1 -3
  31. data/lib/prosereflect/mark/strike.rb +1 -3
  32. data/lib/prosereflect/mark/subscript.rb +1 -3
  33. data/lib/prosereflect/mark/superscript.rb +1 -3
  34. data/lib/prosereflect/mark/underline.rb +1 -3
  35. data/lib/prosereflect/mark.rb +9 -5
  36. data/lib/prosereflect/node.rb +31 -31
  37. data/lib/prosereflect/ordered_list.rb +15 -14
  38. data/lib/prosereflect/output/html.rb +52 -50
  39. data/lib/prosereflect/output.rb +7 -0
  40. data/lib/prosereflect/paragraph.rb +11 -13
  41. data/lib/prosereflect/parser.rb +47 -66
  42. data/lib/prosereflect/table.rb +12 -13
  43. data/lib/prosereflect/table_cell.rb +13 -13
  44. data/lib/prosereflect/table_header.rb +17 -17
  45. data/lib/prosereflect/table_row.rb +12 -12
  46. data/lib/prosereflect/text.rb +11 -11
  47. data/lib/prosereflect/user.rb +15 -15
  48. data/lib/prosereflect/version.rb +1 -1
  49. data/lib/prosereflect.rb +27 -17
  50. data/prosereflect.gemspec +17 -16
  51. data/spec/prosereflect/document_spec.rb +332 -330
  52. data/spec/prosereflect/hard_break_spec.rb +125 -125
  53. data/spec/prosereflect/input/html_spec.rb +522 -522
  54. data/spec/prosereflect/node_spec.rb +183 -182
  55. data/spec/prosereflect/output/html_spec.rb +105 -105
  56. data/spec/prosereflect/paragraph_spec.rb +275 -274
  57. data/spec/prosereflect/parser_spec.rb +185 -180
  58. data/spec/prosereflect/table_cell_spec.rb +183 -183
  59. data/spec/prosereflect/table_row_spec.rb +149 -149
  60. data/spec/prosereflect/table_spec.rb +320 -318
  61. data/spec/prosereflect/text_spec.rb +133 -132
  62. data/spec/prosereflect/user_spec.rb +31 -28
  63. data/spec/prosereflect_spec.rb +28 -26
  64. data/spec/spec_helper.rb +6 -6
  65. data/spec/support/matchers.rb +6 -6
  66. data/spec/support/shared_examples.rb +49 -49
  67. metadata +8 -5
  68. data/spec/prosereflect/version_spec.rb +0 -11
@@ -1,443 +1,445 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  RSpec.describe Prosereflect::Document do
6
- let(:fixtures_path) { File.join(__dir__, '..', 'fixtures') }
7
- let(:yaml_file) { File.join(fixtures_path, 'ituob-1000', 'ituob-1000-DP.yaml') }
6
+ let(:fixtures_path) { File.join(__dir__, "..", "fixtures") }
7
+ let(:yaml_file) do
8
+ File.join(fixtures_path, "ituob-1000", "ituob-1000-DP.yaml")
9
+ end
8
10
  let(:file_content) { File.read(yaml_file) }
9
11
  let(:document) { Prosereflect::Parser.parse_document(YAML.safe_load(file_content)) }
10
12
 
11
- describe 'basic properties' do
12
- it 'has the correct type' do
13
- expect(document.type).to eq('doc')
13
+ describe "basic properties" do
14
+ it "has the correct type" do
15
+ expect(document.type).to eq("doc")
14
16
  end
15
17
 
16
- it 'is a node' do
18
+ it "is a node" do
17
19
  expect(document).to be_a(Prosereflect::Node)
18
20
  end
19
21
  end
20
22
 
21
- describe '.create' do
22
- it 'creates a simple document with a paragraph' do
23
+ describe ".create" do
24
+ it "creates a simple document with a paragraph" do
23
25
  document = described_class.new
24
- document.add_paragraph('This is a test paragraph.')
26
+ document.add_paragraph("This is a test paragraph.")
25
27
 
26
28
  expected = {
27
- 'type' => 'doc',
28
- 'content' => [{
29
- 'type' => 'paragraph',
30
- 'content' => [{
31
- 'type' => 'text',
32
- 'text' => 'This is a test paragraph.'
33
- }]
34
- }]
29
+ "type" => "doc",
30
+ "content" => [{
31
+ "type" => "paragraph",
32
+ "content" => [{
33
+ "type" => "text",
34
+ "text" => "This is a test paragraph.",
35
+ }],
36
+ }],
35
37
  }
36
38
 
37
39
  expect(document.to_h).to eq(expected)
38
40
  end
39
41
 
40
- it 'creates a document with multiple paragraphs and styles' do
42
+ it "creates a document with multiple paragraphs and styles" do
41
43
  document = described_class.new
42
44
 
43
45
  # Add a heading
44
46
  heading = document.add_heading(1)
45
- heading.add_text('Document Title')
47
+ heading.add_text("Document Title")
46
48
 
47
49
  # Add paragraphs with different styles
48
50
  para1 = document.add_paragraph
49
- para1.add_text('This is a paragraph with ')
50
- para1.add_text('bold', [Prosereflect::Mark::Bold.new])
51
- para1.add_text(' and ')
52
- para1.add_text('italic', [Prosereflect::Mark::Italic.new])
53
- para1.add_text(' text.')
51
+ para1.add_text("This is a paragraph with ")
52
+ para1.add_text("bold", [Prosereflect::Mark::Bold.new])
53
+ para1.add_text(" and ")
54
+ para1.add_text("italic", [Prosereflect::Mark::Italic.new])
55
+ para1.add_text(" text.")
54
56
 
55
57
  para2 = document.add_paragraph
56
- para2.add_text('This paragraph has ')
57
- para2.add_text('underlined', [Prosereflect::Mark::Underline.new])
58
- para2.add_text(' and ')
59
- para2.add_text('struck through', [Prosereflect::Mark::Strike.new])
60
- para2.add_text(' text.')
58
+ para2.add_text("This paragraph has ")
59
+ para2.add_text("underlined", [Prosereflect::Mark::Underline.new])
60
+ para2.add_text(" and ")
61
+ para2.add_text("struck through", [Prosereflect::Mark::Strike.new])
62
+ para2.add_text(" text.")
61
63
 
62
64
  expected = {
63
- 'type' => 'doc',
64
- 'content' => [{
65
- 'type' => 'heading',
66
- 'attrs' => { 'level' => 1 },
67
- 'content' => [{
68
- 'type' => 'text',
69
- 'text' => 'Document Title'
70
- }]
65
+ "type" => "doc",
66
+ "content" => [{
67
+ "type" => "heading",
68
+ "attrs" => { "level" => 1 },
69
+ "content" => [{
70
+ "type" => "text",
71
+ "text" => "Document Title",
72
+ }],
71
73
  }, {
72
- 'type' => 'paragraph',
73
- 'content' => [{
74
- 'type' => 'text',
75
- 'text' => 'This is a paragraph with '
74
+ "type" => "paragraph",
75
+ "content" => [{
76
+ "type" => "text",
77
+ "text" => "This is a paragraph with ",
76
78
  }, {
77
- 'type' => 'text',
78
- 'text' => 'bold',
79
- 'marks' => [{ 'type' => 'bold' }]
79
+ "type" => "text",
80
+ "text" => "bold",
81
+ "marks" => [{ "type" => "bold" }],
80
82
  }, {
81
- 'type' => 'text',
82
- 'text' => ' and '
83
+ "type" => "text",
84
+ "text" => " and ",
83
85
  }, {
84
- 'type' => 'text',
85
- 'text' => 'italic',
86
- 'marks' => [{ 'type' => 'italic' }]
86
+ "type" => "text",
87
+ "text" => "italic",
88
+ "marks" => [{ "type" => "italic" }],
87
89
  }, {
88
- 'type' => 'text',
89
- 'text' => ' text.'
90
- }]
90
+ "type" => "text",
91
+ "text" => " text.",
92
+ }],
91
93
  }, {
92
- 'type' => 'paragraph',
93
- 'content' => [{
94
- 'type' => 'text',
95
- 'text' => 'This paragraph has '
94
+ "type" => "paragraph",
95
+ "content" => [{
96
+ "type" => "text",
97
+ "text" => "This paragraph has ",
96
98
  }, {
97
- 'type' => 'text',
98
- 'text' => 'underlined',
99
- 'marks' => [{ 'type' => 'underline' }]
99
+ "type" => "text",
100
+ "text" => "underlined",
101
+ "marks" => [{ "type" => "underline" }],
100
102
  }, {
101
- 'type' => 'text',
102
- 'text' => ' and '
103
+ "type" => "text",
104
+ "text" => " and ",
103
105
  }, {
104
- 'type' => 'text',
105
- 'text' => 'struck through',
106
- 'marks' => [{ 'type' => 'strike' }]
106
+ "type" => "text",
107
+ "text" => "struck through",
108
+ "marks" => [{ "type" => "strike" }],
107
109
  }, {
108
- 'type' => 'text',
109
- 'text' => ' text.'
110
- }]
111
- }]
110
+ "type" => "text",
111
+ "text" => " text.",
112
+ }],
113
+ }],
112
114
  }
113
115
 
114
116
  expect(document.to_h).to eq(expected)
115
117
  end
116
118
 
117
- it 'creates a document with tables and lists' do
119
+ it "creates a document with tables and lists" do
118
120
  document = described_class.new
119
121
 
120
122
  # Add a table
121
123
  table = document.add_table
122
124
  table.add_header(%w[Product Price Quantity])
123
- table.add_row(['Widget', '$10.00', '5'])
124
- table.add_row(['Gadget', '$15.00', '3'])
125
+ table.add_row(["Widget", "$10.00", "5"])
126
+ table.add_row(["Gadget", "$15.00", "3"])
125
127
 
126
128
  # Add an ordered list
127
129
  list = document.add_ordered_list
128
130
  list.start = 1
129
- list.add_item('First item')
130
- list.add_item('Second item with ')
131
- .add_text('emphasis', [Prosereflect::Mark::Italic.new])
131
+ list.add_item("First item")
132
+ list.add_item("Second item with ")
133
+ .add_text("emphasis", [Prosereflect::Mark::Italic.new])
132
134
 
133
135
  expected = {
134
- 'type' => 'doc',
135
- 'content' => [{
136
- 'type' => 'table',
137
- 'content' => [{
138
- 'type' => 'table_row',
139
- 'content' => [{
140
- 'type' => 'table_header',
141
- 'content' => [{
142
- 'type' => 'paragraph',
143
- 'content' => [{
144
- 'type' => 'text',
145
- 'text' => 'Product'
146
- }]
147
- }]
136
+ "type" => "doc",
137
+ "content" => [{
138
+ "type" => "table",
139
+ "content" => [{
140
+ "type" => "table_row",
141
+ "content" => [{
142
+ "type" => "table_header",
143
+ "content" => [{
144
+ "type" => "paragraph",
145
+ "content" => [{
146
+ "type" => "text",
147
+ "text" => "Product",
148
+ }],
149
+ }],
148
150
  }, {
149
- 'type' => 'table_header',
150
- 'content' => [{
151
- 'type' => 'paragraph',
152
- 'content' => [{
153
- 'type' => 'text',
154
- 'text' => 'Price'
155
- }]
156
- }]
151
+ "type" => "table_header",
152
+ "content" => [{
153
+ "type" => "paragraph",
154
+ "content" => [{
155
+ "type" => "text",
156
+ "text" => "Price",
157
+ }],
158
+ }],
157
159
  }, {
158
- 'type' => 'table_header',
159
- 'content' => [{
160
- 'type' => 'paragraph',
161
- 'content' => [{
162
- 'type' => 'text',
163
- 'text' => 'Quantity'
164
- }]
165
- }]
166
- }]
160
+ "type" => "table_header",
161
+ "content" => [{
162
+ "type" => "paragraph",
163
+ "content" => [{
164
+ "type" => "text",
165
+ "text" => "Quantity",
166
+ }],
167
+ }],
168
+ }],
167
169
  }, {
168
- 'type' => 'table_row',
169
- 'content' => [{
170
- 'type' => 'table_cell',
171
- 'content' => [{
172
- 'type' => 'paragraph',
173
- 'content' => [{
174
- 'type' => 'text',
175
- 'text' => 'Widget'
176
- }]
177
- }]
170
+ "type" => "table_row",
171
+ "content" => [{
172
+ "type" => "table_cell",
173
+ "content" => [{
174
+ "type" => "paragraph",
175
+ "content" => [{
176
+ "type" => "text",
177
+ "text" => "Widget",
178
+ }],
179
+ }],
178
180
  }, {
179
- 'type' => 'table_cell',
180
- 'content' => [{
181
- 'type' => 'paragraph',
182
- 'content' => [{
183
- 'type' => 'text',
184
- 'text' => '$10.00'
185
- }]
186
- }]
181
+ "type" => "table_cell",
182
+ "content" => [{
183
+ "type" => "paragraph",
184
+ "content" => [{
185
+ "type" => "text",
186
+ "text" => "$10.00",
187
+ }],
188
+ }],
187
189
  }, {
188
- 'type' => 'table_cell',
189
- 'content' => [{
190
- 'type' => 'paragraph',
191
- 'content' => [{
192
- 'type' => 'text',
193
- 'text' => '5'
194
- }]
195
- }]
196
- }]
190
+ "type" => "table_cell",
191
+ "content" => [{
192
+ "type" => "paragraph",
193
+ "content" => [{
194
+ "type" => "text",
195
+ "text" => "5",
196
+ }],
197
+ }],
198
+ }],
197
199
  }, {
198
- 'type' => 'table_row',
199
- 'content' => [{
200
- 'type' => 'table_cell',
201
- 'content' => [{
202
- 'type' => 'paragraph',
203
- 'content' => [{
204
- 'type' => 'text',
205
- 'text' => 'Gadget'
206
- }]
207
- }]
200
+ "type" => "table_row",
201
+ "content" => [{
202
+ "type" => "table_cell",
203
+ "content" => [{
204
+ "type" => "paragraph",
205
+ "content" => [{
206
+ "type" => "text",
207
+ "text" => "Gadget",
208
+ }],
209
+ }],
208
210
  }, {
209
- 'type' => 'table_cell',
210
- 'content' => [{
211
- 'type' => 'paragraph',
212
- 'content' => [{
213
- 'type' => 'text',
214
- 'text' => '$15.00'
215
- }]
216
- }]
211
+ "type" => "table_cell",
212
+ "content" => [{
213
+ "type" => "paragraph",
214
+ "content" => [{
215
+ "type" => "text",
216
+ "text" => "$15.00",
217
+ }],
218
+ }],
217
219
  }, {
218
- 'type' => 'table_cell',
219
- 'content' => [{
220
- 'type' => 'paragraph',
221
- 'content' => [{
222
- 'type' => 'text',
223
- 'text' => '3'
224
- }]
225
- }]
226
- }]
227
- }]
220
+ "type" => "table_cell",
221
+ "content" => [{
222
+ "type" => "paragraph",
223
+ "content" => [{
224
+ "type" => "text",
225
+ "text" => "3",
226
+ }],
227
+ }],
228
+ }],
229
+ }],
228
230
  }, {
229
- 'type' => 'ordered_list',
230
- 'attrs' => { 'start' => 1 },
231
- 'content' => [{
232
- 'type' => 'list_item',
233
- 'content' => [{
234
- 'type' => 'paragraph',
235
- 'content' => [{
236
- 'type' => 'text',
237
- 'text' => 'First item'
238
- }]
239
- }]
231
+ "type" => "ordered_list",
232
+ "attrs" => { "start" => 1 },
233
+ "content" => [{
234
+ "type" => "list_item",
235
+ "content" => [{
236
+ "type" => "paragraph",
237
+ "content" => [{
238
+ "type" => "text",
239
+ "text" => "First item",
240
+ }],
241
+ }],
240
242
  }, {
241
- 'type' => 'list_item',
242
- 'content' => [{
243
- 'type' => 'paragraph',
244
- 'content' => [{
245
- 'type' => 'text',
246
- 'text' => 'Second item with '
243
+ "type" => "list_item",
244
+ "content" => [{
245
+ "type" => "paragraph",
246
+ "content" => [{
247
+ "type" => "text",
248
+ "text" => "Second item with ",
247
249
  }, {
248
- 'type' => 'text',
249
- 'text' => 'emphasis',
250
- 'marks' => [{ 'type' => 'italic' }]
251
- }]
252
- }]
253
- }]
254
- }]
250
+ "type" => "text",
251
+ "text" => "emphasis",
252
+ "marks" => [{ "type" => "italic" }],
253
+ }],
254
+ }],
255
+ }],
256
+ }],
255
257
  }
256
258
 
257
259
  expect(document.to_h).to eq(expected)
258
260
  end
259
261
 
260
- it 'creates a document with code blocks and blockquotes' do
262
+ it "creates a document with code blocks and blockquotes" do
261
263
  document = described_class.create
262
264
  wrapper = document.add_code_block_wrapper
263
265
  wrapper.line_numbers = true
264
266
 
265
267
  code_block = wrapper.add_code_block
266
- code_block.language = 'ruby'
268
+ code_block.language = "ruby"
267
269
  code_block.content = "def example\n puts 'Hello'\nend"
268
270
 
269
271
  quote = document.add_blockquote
270
- quote.citation = 'https://example.com'
271
- quote.add_paragraph('A test quote')
272
+ quote.citation = "https://example.com"
273
+ quote.add_paragraph("A test quote")
272
274
 
273
275
  expected = {
274
- 'type' => 'doc',
275
- 'content' => [{
276
- 'type' => 'code_block_wrapper',
277
- 'attrs' => {
278
- 'line_numbers' => true
276
+ "type" => "doc",
277
+ "content" => [{
278
+ "type" => "code_block_wrapper",
279
+ "attrs" => {
280
+ "line_numbers" => true,
279
281
  },
280
- 'content' => [{
281
- 'type' => 'code_block',
282
- 'attrs' => {
283
- 'content' => "def example\n puts 'Hello'\nend",
284
- 'language' => 'ruby'
285
- }
286
- }]
282
+ "content" => [{
283
+ "type" => "code_block",
284
+ "attrs" => {
285
+ "content" => "def example\n puts 'Hello'\nend",
286
+ "language" => "ruby",
287
+ },
288
+ }],
287
289
  }, {
288
- 'type' => 'blockquote',
289
- 'attrs' => {
290
- 'citation' => 'https://example.com'
290
+ "type" => "blockquote",
291
+ "attrs" => {
292
+ "citation" => "https://example.com",
291
293
  },
292
- 'content' => [{
293
- 'type' => 'paragraph',
294
- 'content' => [{
295
- 'type' => 'text',
296
- 'text' => 'A test quote'
297
- }]
298
- }]
299
- }]
294
+ "content" => [{
295
+ "type" => "paragraph",
296
+ "content" => [{
297
+ "type" => "text",
298
+ "text" => "A test quote",
299
+ }],
300
+ }],
301
+ }],
300
302
  }
301
303
 
302
304
  expect(document.to_h).to eq(expected)
303
305
  end
304
306
 
305
- it 'creates a document with mathematical content' do
307
+ it "creates a document with mathematical content" do
306
308
  document = described_class.new
307
309
 
308
310
  # Add a heading
309
311
  heading = document.add_heading(2)
310
- heading.add_text('Mathematical Expressions')
312
+ heading.add_text("Mathematical Expressions")
311
313
 
312
314
  # Add equations
313
315
  para1 = document.add_paragraph
314
- para1.add_text('The quadratic formula: x = -b ± ')
315
- para1.add_text('', [Prosereflect::Mark::Bold.new])
316
- para1.add_text('(b')
317
- para1.add_text('2', [Prosereflect::Mark::Superscript.new])
318
- para1.add_text(' - 4ac) / 2a')
316
+ para1.add_text("The quadratic formula: x = -b ± ")
317
+ para1.add_text("", [Prosereflect::Mark::Bold.new])
318
+ para1.add_text("(b")
319
+ para1.add_text("2", [Prosereflect::Mark::Superscript.new])
320
+ para1.add_text(" - 4ac) / 2a")
319
321
 
320
322
  para2 = document.add_paragraph
321
- para2.add_text('Water molecule: H')
322
- para2.add_text('2', [Prosereflect::Mark::Subscript.new])
323
- para2.add_text('O')
323
+ para2.add_text("Water molecule: H")
324
+ para2.add_text("2", [Prosereflect::Mark::Subscript.new])
325
+ para2.add_text("O")
324
326
 
325
327
  expected = {
326
- 'type' => 'doc',
327
- 'content' => [{
328
- 'type' => 'heading',
329
- 'attrs' => { 'level' => 2 },
330
- 'content' => [{
331
- 'type' => 'text',
332
- 'text' => 'Mathematical Expressions'
333
- }]
328
+ "type" => "doc",
329
+ "content" => [{
330
+ "type" => "heading",
331
+ "attrs" => { "level" => 2 },
332
+ "content" => [{
333
+ "type" => "text",
334
+ "text" => "Mathematical Expressions",
335
+ }],
334
336
  }, {
335
- 'type' => 'paragraph',
336
- 'content' => [{
337
- 'type' => 'text',
338
- 'text' => 'The quadratic formula: x = -b ± '
337
+ "type" => "paragraph",
338
+ "content" => [{
339
+ "type" => "text",
340
+ "text" => "The quadratic formula: x = -b ± ",
339
341
  }, {
340
- 'type' => 'text',
341
- 'text' => '',
342
- 'marks' => [{ 'type' => 'bold' }]
342
+ "type" => "text",
343
+ "text" => "",
344
+ "marks" => [{ "type" => "bold" }],
343
345
  }, {
344
- 'type' => 'text',
345
- 'text' => '(b'
346
+ "type" => "text",
347
+ "text" => "(b",
346
348
  }, {
347
- 'type' => 'text',
348
- 'text' => '2',
349
- 'marks' => [{ 'type' => 'superscript' }]
349
+ "type" => "text",
350
+ "text" => "2",
351
+ "marks" => [{ "type" => "superscript" }],
350
352
  }, {
351
- 'type' => 'text',
352
- 'text' => ' - 4ac) / 2a'
353
- }]
353
+ "type" => "text",
354
+ "text" => " - 4ac) / 2a",
355
+ }],
354
356
  }, {
355
- 'type' => 'paragraph',
356
- 'content' => [{
357
- 'type' => 'text',
358
- 'text' => 'Water molecule: H'
357
+ "type" => "paragraph",
358
+ "content" => [{
359
+ "type" => "text",
360
+ "text" => "Water molecule: H",
359
361
  }, {
360
- 'type' => 'text',
361
- 'text' => '2',
362
- 'marks' => [{ 'type' => 'subscript' }]
362
+ "type" => "text",
363
+ "text" => "2",
364
+ "marks" => [{ "type" => "subscript" }],
363
365
  }, {
364
- 'type' => 'text',
365
- 'text' => 'O'
366
- }]
367
- }]
366
+ "type" => "text",
367
+ "text" => "O",
368
+ }],
369
+ }],
368
370
  }
369
371
 
370
372
  expect(document.to_h).to eq(expected)
371
373
  end
372
374
 
373
- it 'creates a document with images and links' do
375
+ it "creates a document with images and links" do
374
376
  document = described_class.new
375
377
 
376
378
  # Add an image
377
- image = document.add_image('example.jpg', 'Example image')
378
- image.title = 'A beautiful landscape'
379
+ image = document.add_image("example.jpg", "Example image")
380
+ image.title = "A beautiful landscape"
379
381
  image.width = 800
380
382
  image.height = 600
381
383
 
382
384
  # Add a paragraph with links
383
385
  para = document.add_paragraph
384
- para.add_text('Visit our ')
385
- link_text = Prosereflect::Text.new(text: 'website')
386
+ para.add_text("Visit our ")
387
+ link_text = Prosereflect::Text.new(text: "website")
386
388
  link_mark = Prosereflect::Mark::Link.new
387
- link_mark.attrs = { 'href' => 'https://example.com' }
389
+ link_mark.attrs = { "href" => "https://example.com" }
388
390
  link_text.marks = [link_mark]
389
391
  para.add_child(link_text)
390
- para.add_text(' for more information.')
392
+ para.add_text(" for more information.")
391
393
 
392
394
  expected = {
393
- 'type' => 'doc',
394
- 'content' => [{
395
- 'type' => 'image',
396
- 'attrs' => {
397
- 'src' => 'example.jpg',
398
- 'alt' => 'Example image',
399
- 'title' => 'A beautiful landscape',
400
- 'width' => 800,
401
- 'height' => 600
402
- }
395
+ "type" => "doc",
396
+ "content" => [{
397
+ "type" => "image",
398
+ "attrs" => {
399
+ "src" => "example.jpg",
400
+ "alt" => "Example image",
401
+ "title" => "A beautiful landscape",
402
+ "width" => 800,
403
+ "height" => 600,
404
+ },
403
405
  }, {
404
- 'type' => 'paragraph',
405
- 'content' => [{
406
- 'type' => 'text',
407
- 'text' => 'Visit our '
406
+ "type" => "paragraph",
407
+ "content" => [{
408
+ "type" => "text",
409
+ "text" => "Visit our ",
408
410
  }, {
409
- 'type' => 'text',
410
- 'text' => 'website',
411
- 'marks' => [{
412
- 'type' => 'link',
413
- 'attrs' => {
414
- 'href' => 'https://example.com'
415
- }
416
- }]
411
+ "type" => "text",
412
+ "text" => "website",
413
+ "marks" => [{
414
+ "type" => "link",
415
+ "attrs" => {
416
+ "href" => "https://example.com",
417
+ },
418
+ }],
417
419
  }, {
418
- 'type' => 'text',
419
- 'text' => ' for more information.'
420
- }]
421
- }]
420
+ "type" => "text",
421
+ "text" => " for more information.",
422
+ }],
423
+ }],
422
424
  }
423
425
 
424
426
  expect(document.to_h).to eq(expected)
425
427
  end
426
428
  end
427
429
 
428
- describe '#paragraphs' do
429
- it 'returns all paragraphs in the document' do
430
+ describe "#paragraphs" do
431
+ it "returns all paragraphs in the document" do
430
432
  doc = described_class.new
431
- doc.add_paragraph('First paragraph')
432
- doc.add_paragraph('Second paragraph')
433
+ doc.add_paragraph("First paragraph")
434
+ doc.add_paragraph("Second paragraph")
433
435
 
434
436
  expect(doc.paragraphs.size).to eq(2)
435
437
  expect(doc.paragraphs).to all(be_a(Prosereflect::Paragraph))
436
438
  end
437
439
  end
438
440
 
439
- describe '#tables' do
440
- it 'returns all tables in the document' do
441
+ describe "#tables" do
442
+ it "returns all tables in the document" do
441
443
  doc = described_class.new
442
444
  doc.add_table
443
445
  doc.add_table
@@ -447,28 +449,28 @@ RSpec.describe Prosereflect::Document do
447
449
  end
448
450
  end
449
451
 
450
- describe '#add_paragraph' do
451
- it 'adds a paragraph with text' do
452
+ describe "#add_paragraph" do
453
+ it "adds a paragraph with text" do
452
454
  doc = described_class.new
453
- para = doc.add_paragraph('Test paragraph')
455
+ para = doc.add_paragraph("Test paragraph")
454
456
 
455
457
  expect(doc.paragraphs.size).to eq(1)
456
458
  expect(para).to be_a(Prosereflect::Paragraph)
457
- expect(para.text_content).to eq('Test paragraph')
459
+ expect(para.text_content).to eq("Test paragraph")
458
460
  end
459
461
 
460
- it 'adds an empty paragraph' do
462
+ it "adds an empty paragraph" do
461
463
  doc = described_class.new
462
464
  para = doc.add_paragraph
463
465
 
464
466
  expect(doc.paragraphs.size).to eq(1)
465
467
  expect(para).to be_a(Prosereflect::Paragraph)
466
- expect(para.text_content).to eq('')
468
+ expect(para.text_content).to eq("")
467
469
  end
468
470
  end
469
471
 
470
- describe '#add_table' do
471
- it 'adds a table to the document' do
472
+ describe "#add_table" do
473
+ it "adds a table to the document" do
472
474
  doc = described_class.new
473
475
  table = doc.add_table
474
476
 
@@ -476,43 +478,43 @@ RSpec.describe Prosereflect::Document do
476
478
  expect(table).to be_a(Prosereflect::Table)
477
479
  end
478
480
 
479
- it 'adds a table with attributes' do
481
+ it "adds a table with attributes" do
480
482
  doc = described_class.new
481
- attrs = { 'width' => '100%' }
483
+ attrs = { "width" => "100%" }
482
484
  table = doc.add_table(attrs)
483
485
 
484
486
  expect(table.attrs).to eq(attrs)
485
487
  end
486
488
  end
487
489
 
488
- describe 'serialization' do
489
- it 'converts to hash representation' do
490
+ describe "serialization" do
491
+ it "converts to hash representation" do
490
492
  doc = described_class.new
491
- doc.add_paragraph('Test paragraph')
493
+ doc.add_paragraph("Test paragraph")
492
494
  table = doc.add_table
493
- table.add_header(['Header'])
494
- table.add_row(['Data'])
495
+ table.add_header(["Header"])
496
+ table.add_row(["Data"])
495
497
 
496
498
  hash = doc.to_h
497
- expect(hash['type']).to eq('doc')
498
- expect(hash['content'].size).to eq(2)
499
- expect(hash['content'][0]['type']).to eq('paragraph')
500
- expect(hash['content'][1]['type']).to eq('table')
499
+ expect(hash["type"]).to eq("doc")
500
+ expect(hash["content"].size).to eq(2)
501
+ expect(hash["content"][0]["type"]).to eq("paragraph")
502
+ expect(hash["content"][1]["type"]).to eq("table")
501
503
  end
502
504
 
503
- it 'converts to YAML' do
505
+ it "converts to YAML" do
504
506
  doc = described_class.new
505
- doc.add_paragraph('Test paragraph')
507
+ doc.add_paragraph("Test paragraph")
506
508
 
507
509
  yaml = doc.to_yaml
508
510
  expect(yaml).to be_a(String)
509
- expect(yaml).to include('type: doc')
510
- expect(yaml).to include('type: paragraph')
511
+ expect(yaml).to include("type: doc")
512
+ expect(yaml).to include("type: paragraph")
511
513
  end
512
514
 
513
- it 'converts to JSON' do
515
+ it "converts to JSON" do
514
516
  doc = described_class.new
515
- doc.add_paragraph('Test paragraph')
517
+ doc.add_paragraph("Test paragraph")
516
518
 
517
519
  json = doc.to_json
518
520
  expect(json).to be_a(String)
@@ -521,29 +523,29 @@ RSpec.describe Prosereflect::Document do
521
523
  end
522
524
  end
523
525
 
524
- describe '#text_content' do
525
- it 'returns plain text content from all nodes' do
526
+ describe "#text_content" do
527
+ it "returns plain text content from all nodes" do
526
528
  document = described_class.new
527
529
 
528
530
  # Add various types of content
529
- document.add_heading(1).add_text('Title')
531
+ document.add_heading(1).add_text("Title")
530
532
 
531
533
  para = document.add_paragraph
532
- para.add_text('This is ')
533
- para.add_text('bold', [Prosereflect::Mark::Bold.new])
534
- para.add_text(' text.')
534
+ para.add_text("This is ")
535
+ para.add_text("bold", [Prosereflect::Mark::Bold.new])
536
+ para.add_text(" text.")
535
537
 
536
538
  list = document.add_bullet_list
537
- list.add_item('First item')
538
- list.add_item('Second item')
539
+ list.add_item("First item")
540
+ list.add_item("Second item")
539
541
 
540
542
  expected_text = "Title\nThis is bold text.\nFirst item\nSecond item"
541
543
  expect(document.text_content).to eq(expected_text)
542
544
  end
543
545
 
544
- it 'handles empty documents' do
546
+ it "handles empty documents" do
545
547
  document = described_class.new
546
- expect(document.text_content).to eq('')
548
+ expect(document.text_content).to eq("")
547
549
  end
548
550
  end
549
551
  end